diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-08-22 00:18:56 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-08-22 00:19:26 +0900 |
| commit | 9f2fb3bda733c32995c008bd0f40365e43c348da (patch) | |
| tree | 663a6b48ec83b2b6ba2c998dcfed89e0bb29f172 | |
| parent | 31b19ee74469c23633bbea4e021d91ddd8c34e3a (diff) | |
feat: Zolaで「たかしの記録」を構築
Specifications.mdとDesign.mdを実装の基準として、個人アーカイブサイトをZola 0.23.3向けに構築する。
- Home、Notes、Software、About、Tagsの情報設計と専用URLを追加
- Notesの年別アーカイブ、タグ分類、目次、脚注、参考文献、更新日表示を実装
- Softwareの一覧・詳細、cgit/GitHub情報、Notesとの明示的な相互参照を実装
- Tera 2 componentによる再帰TOCと共通partialsを追加
- Minimal Editorial ArchiveのSCSS、レスポンシブ表示、アクセシビリティ対応を追加
- OS設定とlocalStorageに対応するLight/Darkテーマ切替を外部JavaScriptで実装
- Atom/RSS、sitemap、robots.txt、404、favicon、syntax highlightingを設定
- メールアドレスをzola.tomlへ一元化し、AboutとFooterから参照
- 表示名・内部ディレクトリ・変数・URLをSoftware/softwareへ統一
- public/をGit管理対象外とし、外部CDNやNode.js依存を導入しない
- zola checkおよびzola buildの成功を確認
47 files changed, 3978 insertions, 515 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87174b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/public/ diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..7db2929 --- /dev/null +++ b/content/_index.md @@ -0,0 +1,4 @@ ++++ +title = "たかしの記録" +description = "日々考えたこととか思ったことを記録します" ++++ diff --git a/content/about.md b/content/about.md new file mode 100644 index 0000000..b0f203c --- /dev/null +++ b/content/about.md @@ -0,0 +1,26 @@ ++++ +title = "About" +template = "page.html" ++++ + +## 名前 + +たかし。ソフトウェアを作り、本を読み、考えたことを記録しています。 + +## このサイトについて + +「たかしの記録」は、日々考えたこと、感じたこと、調べたことと、作成したソフトウェアを長く保存するための個人アーカイブです。更新の速さよりも、あとから読み返せることを大切にしています。 + +## 関心分野 + +- 情報工学とソフトウェア +- 論理学 +- マルクス、資本論、政治経済 +- 地球環境 +- 読書を通じた考察 + +## 連絡先 + +[{{ config.extra.mail }}](mailto:{{ config.extra.mail }}) + +掲載内容は初期サンプルです。公開時に、プロフィールと連絡先を実際の情報へ差し替えてください。 diff --git a/content/notes/_index.md b/content/notes/_index.md new file mode 100644 index 0000000..3e6bb92 --- /dev/null +++ b/content/notes/_index.md @@ -0,0 +1,9 @@ ++++ +title = "Notes" +description = "考えたこと、感じたこと、調べたことの記録。" +sort_by = "date" +template = "notes/section.html" +page_template = "notes/page.html" ++++ + +分野を固定せず、文章として残したいものをまとめています。 diff --git a/content/notes/reading-and-questions.md b/content/notes/reading-and-questions.md new file mode 100644 index 0000000..6f67c0c --- /dev/null +++ b/content/notes/reading-and-questions.md @@ -0,0 +1,12 @@ ++++ +title = "読書の途中にある問いを残す" +description = "結論だけでなく、読んでいる途中の問いを記録することについて。" +date = 2025-11-03 + +[taxonomies] +tags = ["読書", "論理学"] ++++ + +本を読んだあとに結論だけを書こうとすると、文章を残す機会を逃すことがあります。理解できなかった箇所や、別の文章とのつながりも、その時点での大切な記録です。 + +問いを未完成のまま残しておけば、次に同じ主題へ戻ったときの出発点になります。 diff --git a/content/notes/static-sites-and-maintenance.md b/content/notes/static-sites-and-maintenance.md new file mode 100644 index 0000000..10fcd7d --- /dev/null +++ b/content/notes/static-sites-and-maintenance.md @@ -0,0 +1,46 @@ ++++ +title = "静的サイトと長期的な保守" +description = "仕組みを小さく保つことが、長く書き続けるためにどう役立つか。" +date = 2026-08-21 +updated = 2026-08-22 + +[taxonomies] +tags = ["情報工学", "Web"] + +[extra] +related_software = [ + "software/archive-site/index.md", +] ++++ + +個人サイトの目的は、必ずしも最新の機能を提供することではありません。文章を残し、必要なときに読み返せることが中心なら、静的な構成は今もよく合います。 + +## 保守する対象を減らす + +動的な仕組みを採用すると、アプリケーションだけでなく、実行環境やデータベースも保守対象になります。静的サイトでは、公開環境に置くものを生成済みのHTMLとCSSへ限定できます。 + +```text +Markdown → Zola → HTML / CSS / JS +``` + +構成を小さくすることは、機能を諦めることではありません。目的に必要な部分を見極め、理解できる範囲に留める判断です[^small]. + +### 更新の手順 + +記事を書くときの流れも単純です。 + +1. Markdownを追加する +2. `zola check`で内部リンクを確認する +3. `zola build`で静的ファイルを生成する +4. 生成物だけを配信する + +## 読む時間を中心に置く + +長く残す文章では、画面の動きより、行長や余白、リンクの見分けやすさが重要です。このサイトでは[Archive Site](@/software/archive-site/index.md)の設計を通して、その考え方を試しています。 + +## 参考文献 + +1. Zola, *Documentation*. +2. W3C, *Web Content Accessibility Guidelines (WCAG)*. + +[^small]: ここでいう「小さい」は、ファイル数ではなく、運用時に理解すべき境界が少ないことを指します。 diff --git a/content/software/_index.md b/content/software/_index.md new file mode 100644 index 0000000..d7e092e --- /dev/null +++ b/content/software/_index.md @@ -0,0 +1,9 @@ ++++ +title = "Software" +description = "作成したソフトウェアと、その背景や過程。" +sort_by = "date" +template = "software/section.html" +page_template = "software/page.html" ++++ + +ソースコードの正本は自前のcgitで公開し、ここでは人間向けの説明を記録します。 diff --git a/content/software/archive-site/index.md b/content/software/archive-site/index.md new file mode 100644 index 0000000..ef83fe1 --- /dev/null +++ b/content/software/archive-site/index.md @@ -0,0 +1,36 @@ ++++ +title = "archive-site" +description = "文章とソフトウェアを長く残すためのZolaサイト。" +date = 2026-08-21 +include_in_feeds = false + +[extra] +language = ["Zola", "Tera", "SCSS"] +license = "MIT" +source = "https://git.takashi-2521.com/archive-site/" +github_mirror = "https://github.com/takashi-2521/archive-site" +related_notes = [ + "notes/static-sites-and-maintenance.md", +] ++++ + +## 概要 + +NotesとSoftwareを中心に、文章と制作物を長期的に保存するための静的サイトです。 + + + +## 作った背景 + +断片的なメモとソースコードの間にある、設計意図や調査の過程を一か所に残すために作りました。更新頻度が低くても自然に見える、索引としての情報構造を重視しています。 + +## 開発の過程 + +ZolaのSection、Page、Taxonomyをそのまま用い、独自のコンテンツ管理層を作らない方針にしました。表示はTera、スタイルはSCSS、テーマ切替だけを小さなJavaScriptで担当します。 + +## 特徴 + +- 年別のNotesアーカイブ +- タグによる横断的な分類 +- NotesとSoftwareの明示的な関連付け +- JavaScriptなしでも読める本文とナビゲーション diff --git a/content/software/archive-site/screenshot.svg b/content/software/archive-site/screenshot.svg new file mode 100644 index 0000000..18b05c5 --- /dev/null +++ b/content/software/archive-site/screenshot.svg @@ -0,0 +1,17 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 540" role="img" aria-labelledby="title desc"> + <title id="title">archive-siteの画面構成例</title> + <desc id="desc">ヘッダー、Notes一覧、Software一覧を罫線で整理した画面。</desc> + <rect width="960" height="540" fill="#f6f5f1"/> + <rect x="72" y="54" width="816" height="46" fill="none" stroke="#d6d8da"/> + <rect x="72" y="144" width="500" height="28" fill="#202124"/> + <rect x="72" y="194" width="620" height="12" fill="#aeb2b5"/> + <rect x="72" y="246" width="816" height="1" fill="#d6d8da"/> + <rect x="72" y="280" width="104" height="18" fill="#315f82"/> + <rect x="72" y="326" width="816" height="1" fill="#d6d8da"/> + <rect x="72" y="348" width="104" height="12" fill="#aeb2b5"/> + <rect x="220" y="344" width="420" height="20" fill="#202124"/> + <rect x="72" y="392" width="816" height="1" fill="#d6d8da"/> + <rect x="72" y="414" width="104" height="12" fill="#aeb2b5"/> + <rect x="220" y="410" width="350" height="20" fill="#202124"/> + <rect x="72" y="458" width="816" height="1" fill="#d6d8da"/> +</svg> diff --git a/content/software/content-notes.md b/content/software/content-notes.md new file mode 100644 index 0000000..0d5f6df --- /dev/null +++ b/content/software/content-notes.md @@ -0,0 +1,23 @@ ++++ +title = "content-notes" +description = "Markdownだけで考えの断片を整理する小さな道具。" +date = 2025-10-18 +include_in_feeds = false + +[extra] +language = ["Rust"] +license = "MIT" +source = "https://git.takashi-2521.com/content-notes/" ++++ + +## 概要 + +短いメモに日付と題名を付け、単純なディレクトリへ保存するためのコマンドラインツールです。 + +## 作った背景 + +思いついたことを残す前に保存場所や形式で迷わないよう、操作を限定した道具が必要でした。 + +## 開発の過程 + +保存形式には可搬性の高いMarkdownを採用しました。検索や分類を内部データベースへ閉じ込めず、通常のファイル操作でも扱えることを優先しています。 diff --git a/doc/Design.md b/doc/Design.md new file mode 100644 index 0000000..bddd04f --- /dev/null +++ b/doc/Design.md @@ -0,0 +1,1851 @@ +# Design.md + +# たかしの記録 — Zola 基本・詳細設計書 + +## 1. 文書目的 + +本書は `Specifications.md` の要求を、Zolaによる静的Webサイトとして矛盾なく実装するための基本設計・詳細設計を定義する。 + +設計上の基本原則は次のとおり。 + +1. Zola公式のSection / Page / Taxonomy / Tera modelに従う +2. Zola標準機能を優先する +3. 外部dependencyを増やさない +4. Build artifact `public/` のみで本番サイトを成立させる +5. Client PCでbuildし、VPSでbuildしない +6. 本番runtimeはCaddyのみ +7. コンテンツ構造とデザインを不要に複雑化しない + +--- + +## 2. 参照仕様とZola準拠方針 + +本設計は `Specifications.md` を上位仕様とする。 + +実装時は使用するZolaバージョンの公式ドキュメントを確認すること。 +設定キー・front matter・Tera関数を推測して追加してはならない。 + +主に以下のZola概念を使用する。 + +- `zola.toml` +- Section (`_index.md`) +- Page (`*.md`) +- Taxonomy +- Tera templates +- Sass/SCSS compilation +- Static assets +- Colocated assets +- Built-in feeds +- Built-in sitemap +- Built-in robots.txt +- `page.toc` +- `get_page` +- `get_section` +- `get_url` +- `get_taxonomy_url` +- Internal link `@/` + +Zolaはディレクトリ構造からSection/Pageを構成するため、独自CMS層やJSON indexを追加しない。 + +--- + +## 3. システム構成 + +```text +Client PC +│ +├── Zola +├── Git repository +│ ├── zola.toml +│ ├── content/ +│ ├── templates/ +│ ├── sass/ +│ └── static/ +│ +├── zola check +└── zola build + │ + ▼ + public/ + │ + │ archive + SSH + ▼ +VPS +└── /srv/www/takashi-2521.com/ + ├── releases/<release-id>/ + └── current -> releases/<release-id>/ + │ + ▼ + Caddy +``` + +ZolaはVPSへインストールしない。 + +--- + +## 4. Zolaサイト構造 + +```text +website/ +├── zola.toml +├── content/ +│ ├── _index.md +│ ├── about.md +│ ├── notes/ +│ │ ├── _index.md +│ │ ├── example-note.md +│ │ └── ... +│ └── software/ +│ ├── _index.md +│ ├── example-software.md +│ └── ... +├── templates/ +│ ├── base.html +│ ├── index.html +│ ├── page.html +│ ├── notes/ +│ │ ├── section.html +│ │ └── page.html +│ ├── software/ +│ │ ├── section.html +│ │ └── page.html +│ ├── tags/ +│ │ ├── list.html +│ │ └── single.html +│ ├── partials/ +│ │ ├── header.html +│ │ ├── footer.html +│ │ ├── note-list-item.html +│ │ ├── software-list-item.html +│ │ ├── toc.html +│ │ ├── related-notes.html +│ │ └── related-software.html +│ └── 404.html +├── sass/ +│ ├── main.scss +│ ├── _tokens.scss +│ ├── _reset.scss +│ ├── _base.scss +│ ├── _typography.scss +│ ├── _layout.scss +│ ├── _header.scss +│ ├── _footer.scss +│ ├── _archive.scss +│ ├── _article.scss +│ ├── _software.scss +│ ├── _taxonomy.scss +│ ├── _code.scss +│ ├── _theme.scss +│ └── _utilities.scss +├── static/ +│ ├── favicon.svg +│ └── js/ +│ └── theme.js +├── themes/ +└── public/ # generated; Git ignored +``` + +### 4.1 Themes + +`themes/` はZola標準ディレクトリとして存在してよいが、空のままとする。 +外部themeを使用しない。 + +### 4.2 public + +`public/` は生成物でありGit管理対象外。 + +--- + +## 5. `zola.toml` 基本設計 + +初期値の基準を以下とする。 + +```toml +base_url = "https://www.takashi-2521.com" +title = "たかしの記録" +description = "日々考えたこととか思ったことを記録します" +default_language = "ja" + +output_dir = "public" +compile_sass = true +minify_html = false + +generate_feeds = true +feed_filenames = ["atom.xml", "rss.xml"] + +generate_sitemap = true +generate_robots_txt = true + +build_search_index = false + +taxonomies = [ + { name = "tags", feed = false } +] + +[markdown] +render_emoji = false +external_links_class = "external-link" +external_links_target_blank = false +external_links_no_follow = false +external_links_no_referrer = false +external_links_external = true +smart_punctuation = false +lazy_async_image = true +bottom_footnotes = true +insert_anchor_links = "heading" + +[markdown.highlighting] +error_on_missing_language = true +style = "class" +light_theme = "github-light" +dark_theme = "github-dark" + +[link_checker] +internal_level = "error" +external_level = "warn" + +[slugify] +paths = "on" +taxonomies = "on" +anchors = "on" +paths_keep_dates = false + +[extra] +home_notes_limit = 5 +home_software_limit = 4 +``` + +### 5.1 設定上の注意 + +- `base_url` は本番URLと一致させる。 +- Searchは初期無効。 +- Taxonomyは`tags`のみ。 +- FeedはAtomとRSS。 +- Syntax highlightはclass方式とし、inline styleへの依存を避ける。 +- `external_links_class` を利用し外部リンク記号をCSSで表現する。 +- `minify_html` は開発中false。最終的にtrueへ変更する場合は生成結果を確認してから行う。 +- Highlight themeの具体設定は使用Zola版の公式仕様に合わせる。 +- 利用中Zola版で設定名・構造が異なる場合は、公式ドキュメントを優先してこの設計を等価に実現する。 + +--- + +## 6. Content model + +### 6.1 Root Section + +`content/_index.md` + +```toml ++++ +title = "たかしの記録" +description = "日々考えたこととか思ったことを記録します" ++++ +``` + +Home自体はZolaのroot sectionとして扱い、`templates/index.html` で描画する。 + +### 6.2 About Page + +`content/about.md` + +```toml ++++ +title = "About" +template = "page.html" ++++ +``` + +本文に以下をMarkdownで記述する。 + +- 名前 +- 一言プロフィール +- サイトの目的 +- 関心分野 +- 連絡先 + +構造化metadataを必要以上に増やさない。 + +### 6.3 Notes Section + +`content/notes/_index.md` + +```toml ++++ +title = "Notes" +sort_by = "date" +template = "notes/section.html" +page_template = "notes/page.html" ++++ +``` + +#### 6.3.1 Notesのsort + +Sectionはdate順にする。 +テンプレートでは新しい順で表示されることを検証する。 + +#### 6.3.2 Notes Page front matter + +```toml ++++ +title = "記事タイトル" +description = "記事の短い説明" +date = 2026-08-21 +updated = 2026-08-22 + +[taxonomies] +tags = ["情報工学", "AI"] + +[extra] +related_software = [ + "software/example-software.md" +] ++++ + +本文... +``` + +#### 6.3.3 Notes必須/任意項目 + +| Field | 必須 | 用途 | +|---|---:|---| +| `title` | Yes | タイトル | +| `date` | Yes | 公開日 | +| `description` | No | 一覧・meta description | +| `updated` | No | 更新日 | +| `taxonomies.tags` | No | 一覧分類 | +| `extra.related_software` | No | 明示的な関連Software | +| Body | Yes | 本文 | + +#### 6.3.4 Slug + +Markdown filenameを英語ASCII slugとする。 + +例: + +```text +content/notes/llm-compute-and-energy.md +``` + +表示タイトルは日本語でよい。 + +### 6.4 Software Section + +`content/software/_index.md` + +```toml ++++ +title = "Software" +sort_by = "date" +template = "software/section.html" +page_template = "software/page.html" ++++ +``` + +Software一覧の順序は初期状態ではdate順とする。 +公開日を表示する必要はないが、並び順制御に利用する。 + +将来、並びを明示したくなった場合は`weight`へ変更可能。 + +#### 6.4.1 Software Page front matter + +```toml ++++ +title = "example-software" +description = "短い概要" +date = 2026-08-21 +include_in_feeds = false + +[extra] +language = ["Rust", "HTML"] +license = "MIT" +source = "https://git.takashi-2521.com/example-software/" +github_mirror = "https://github.com/example/example-software" +related_notes = [ + "notes/example-note.md" +] ++++ + +## 概要 + +... + +## 作った背景 + +... + +## 開発の過程 + +... +``` + +#### 6.4.2 Software項目 + +| Field | 必須 | 用途 | +|---|---:|---| +| `title` | Yes | PJ名 | +| `description` | No | 短い概要 | +| `date` | Yes | 並び順 | +| `include_in_feeds` | Yes (`false`) | Notes中心のsite feedからSoftwareを除外 | +| `extra.language` | No | 使用言語 | +| `extra.license` | No | ライセンス | +| `extra.source` | Yes | cgit正本 | +| `extra.github_mirror` | No | GitHub mirror | +| `extra.related_notes` | No | 関連Note | +| Body | Yes | 背景・過程・詳細 | + +#### 6.4.3 Software image + +画像をSoftwareに紐付ける場合、Zolaのcolocated assetを優先する。 + +例: + +```text +content/software/example-software/ +├── index.md +└── screenshot.webp +``` + +この場合、Page URLは `/software/example-software/` とし、画像を同じPage Bundle内に置く。 + +画像が不要なSoftwareは単一Markdownでよい。 + +--- + +## 7. Related content設計 + +### 7.1 基本方針 + +関連付けはタグから自動推測せず、front matterで明示する。 + +理由: + +- 意図しない関連付けを防止 +- 記事数が少ないため手動管理コストが低い +- Zola `get_page` で確実に参照できる + +### 7.2 Path形式 + +`extra.related_software` / `extra.related_notes` には **contentディレクトリからのMarkdown path** を保存する。 + +例: + +```toml +related_software = ["software/ollama-web.md"] +related_notes = ["notes/ollama-tool-calling.md"] +``` + +### 7.3 Tera resolution + +例: + +```tera +{% for path in page.extra.related_notes %} + {% set related = get_page(path=path) %} + <a href="{{ related.permalink }}">{{ related.title }}</a> +{% endfor %} +``` + +Missing referenceはbuild/checkで検出できるよう、原則 `allow_missing=true` を使用しない。 + +--- + +## 8. Template設計 + +### 8.1 `base.html` + +責務: + +- HTML document skeleton +- `<html lang="ja">` +- `<head>` +- charset / viewport +- title +- description +- canonical +- RSS/Atom autodiscovery +- CSS +- Zola生成syntax-highlight CSS (`giallo-light.css` / `giallo-dark.css`) +- theme JS +- Header +- Main block +- Footer + +概念構造: + +```tera +<!doctype html> +<html lang="ja"> +<head> + ... + <link rel="stylesheet" href="{{ get_url(path='main.css') }}"> + <link id="syntax-light" rel="stylesheet" href="{{ get_url(path='giallo-light.css') }}" media="(prefers-color-scheme: light)"> + <link id="syntax-dark" rel="stylesheet" href="{{ get_url(path='giallo-dark.css') }}" media="(prefers-color-scheme: dark)"> + <script src="{{ get_url(path='js/theme.js') }}" defer></script> +</head> +<body> + {% include "partials/header.html" %} + <main id="main-content"> + {% block content %}{% endblock content %} + </main> + {% include "partials/footer.html" %} +</body> +</html> +``` + +#### 8.1.1 Page title + +原則: + +- Home: `たかしの記録` +- Other: `<Page title> | たかしの記録` + +Tera blockまたは共通変数で実現する。 + +#### 8.1.2 meta description + +優先順位: + +1. `page.description` / `section.description` +2. `config.description` + +### 8.2 Header + +`partials/header.html` + +Desktop: + +```text +たかしの記録 Home Notes Software About [Theme] +``` + +Mobileでは自然にwrapさせ、初期実装ではHamburger menuを導入しない。 + +サイト名自体はHomeへのリンク。 + +Navigationには`aria-label="メインナビゲーション"`を設定する。 + +### 8.3 Footer + +`partials/footer.html` + +表示: + +- `© <year> Takashi` +- RSS +- Mail + +Mail addressは設定値に直接埋め込むか、`config.extra`へ置く。 +公開時のspam対策を別途必要と判断した場合のみ変更する。 + +### 8.4 Home Template + +`templates/index.html` + +処理: + +1. root `section` +2. `get_section(path="notes/_index.md")` +3. Notesから上位5件取得 +4. `get_section(path="software/_index.md")` +5. Softwareから上位4件取得 +6. Aboutへのリンク + +概念: + +```tera +{% set notes = get_section(path="notes/_index.md") %} +{% set software = get_section(path="software/_index.md") %} +``` + +件数は`config.extra.home_notes_limit` / `home_software_limit`で管理する。 + +Homeでタグは必須表示としない。 + +### 8.5 Notes一覧 Template + +`templates/notes/section.html` + +#### 8.5.1 年別グルーピング + +Zola公式Archive patternに従い、`section.pages | group_by(attribute="year")` を利用する。 + +表示: + +```text +Notes + +2026 + 08-21 タイトル + タグ1 / タグ2 + +2025 + ... +``` + +ZolaのSection `sort_by = "date"` を前提とする。 + +#### 8.5.2 Note list item + +`partials/note-list-item.html` + +- `<article>` または適切なlist item +- `<time datetime="">` +- title link +- tags +- optional description + +タグURLは `get_taxonomy_url(kind="tags", term=...)` を利用する。 + +### 8.6 Note detail Template + +`templates/notes/page.html` + +構造: + +```text +article +├── header +│ ├── h1 +│ ├── published +│ └── updated (optional) +├── TOC +├── body +├── related software (optional) +└── article footer +``` + +#### 8.6.1 Tags + +仕様に従い、本文詳細ではタグ一覧を主要表示しない。 + +#### 8.6.2 TOC + +`page.toc` を使用する。 + +- `page.toc` が空なら非表示 +- Sidebar化しない +- 記事headerと本文の間に置く +- H2/H3等の階層を維持 +- Recursive Tera partial/componentで描画可能 + +#### 8.6.3 TOC accessibility + +- `<nav aria-label="目次">` +- 見出し「目次」 +- ordered/unordered list +- Anchorは`header.permalink` + +#### 8.6.4 Date + +公開: + +```html +<time datetime="2026-08-21">2026-08-21</time> +``` + +更新日がある場合だけ表示。 + +#### 8.6.5 Footnotes + +Zola Markdown rendererが生成するfootnote markupへSCSSを適用する。 + +独自脚注システムを作らない。 + +#### 8.6.6 References + +Markdown本文の通常Headingとして「参考文献」を記述する。 + +例: + +```markdown +## 参考文献 + +1. ... +2. ... +``` + +専用front matter schemaは導入しない。 + +### 8.7 Software一覧 Template + +`templates/software/section.html` + +表示の中心はSoftware名。 + +例: + +```text +Software + +ollama-web +短い説明(存在する場合) + +winagent-sandbox-mcp +... +``` + +一覧には以下を表示しない。 + +- screenshot +- license +- source URL全文 +- language一覧 +- 大量のbadge + +### 8.8 Software detail Template + +`templates/software/page.html` + +構造: + +```text +article +├── h1 Software名 +├── description +├── metadata +│ ├── 使用言語 +│ ├── ライセンス +│ ├── Source +│ └── GitHub mirror (optional) +├── Body +│ ├── 概要 +│ ├── 背景 +│ └── 過程等 +├── screenshot/assets (optional) +└── Related Notes (optional) +``` + +SourceはGitHubより先に表示。 + +外部リンクclassによって`↗`表示を適用する。 + +### 8.9 About + +標準`page.html`で十分な場合は専用templateを作らない。 + +`page.html` は汎用Editorial pageとして以下を描画。 + +- h1 +- page.content + +About固有レイアウトが必要になった時だけ`about.html`を追加する。 + +### 8.10 Taxonomy templates + +`tags/list.html` + +- 全タグの一覧 +- term name +- article count + +`tags/single.html` + +- タグ名 +- 該当Pages一覧 +- Note/Softwareの混在が読みづらい場合は種類ごとに分離可能 + +初期仕様ではタグは主にNotes用だが、Zola taxonomy自体はPages全般で利用可能。 + +### 8.11 404 + +`templates/404.html` + +- base designと同じHeader/Footer +- 「ページが見つかりません」 +- Homeへのリンク +- Notesへのリンク +- Softwareへのリンク + +外部依存なし。 + +--- + +## 9. Internal Link設計 + +Markdown本文内の内部リンクはZolaの `@/` syntaxを使用する。 + +例: + +```markdown +[関連する記事](@/notes/example-note.md) +``` + +Template内は`get_url`, `get_page`, `get_section`を使用する。 + +Content pathを手書きURLへ変換しない。 + +Broken internal linkは`zola check`でerror扱い。 + +--- + +## 10. Tags / Taxonomy設計 + +`zola.toml`: + +```toml +taxonomies = [ + { name = "tags", feed = false } +] +``` + +Page: + +```toml +[taxonomies] +tags = ["情報工学", "地球環境"] +``` + +### 10.1 Tag URL + +Zola slugificationに任せる。 + +表示termは日本語を許容する。 +Taxonomy URL生成はZola APIを使用し、自前slug生成をしない。 + +### 10.2 Category + +初期実装ではcategory taxonomyを作らない。 + +--- + +## 11. Feed設計 + +Site-level feed: + +- `/atom.xml` +- `/rss.xml` + +Site-level feedを使用する。Software Pageには `include_in_feeds = false` を設定し、日付を持つSoftwareがsite feedへ混入しないようにする。これにより初期feedは実質的にNotesを購読対象とする。 + +Notes section固有feedは初期実装では生成しない。必要になった場合のみ `content/notes/_index.md` の `generate_feeds = true` を追加する。 + +`base.html`にautodiscoveryを設定。 + +概念: + +```tera +<link rel="alternate" + type="application/atom+xml" + title="Atom" + href="{{ get_url(path='atom.xml', trailing_slash=false) }}"> +<link rel="alternate" + type="application/rss+xml" + title="RSS" + href="{{ get_url(path='rss.xml', trailing_slash=false) }}"> +``` + +FooterのRSSリンクは`rss.xml`を優先してよい。 +Atomもアクセス可能とする。 + +--- + +## 12. Sitemap / robots + +Zola built-in生成を利用する。 + +```toml +generate_sitemap = true +generate_robots_txt = true +``` + +独自generatorを作らない。 + +robots policyを変更する必要が生じた場合のみbuilt-in template overrideまたはstatic fileを検討する。 + +--- + +## 13. Sass設計 + +### 13.1 基本原則 + +- 自前SCSS +- CSS frameworkなし +- Tailwindなし +- external reset packageなし +- BEM等の厳格な命名体系を強制しない +- component responsibilityが分かる程度のclass設計 + +### 13.2 Design tokens + +`_tokens.scss` + +初期候補: + +```scss +:root { + --color-bg: #f6f5f1; + --color-surface: #fbfaf7; + --color-text: #202124; + --color-muted: #6d7177; + --color-border: #d6d8da; + --color-link: #315f82; + --color-link-hover: #244a67; + + --font-sans: + system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", "Noto Sans JP", sans-serif; + + --font-mono: + ui-monospace, "Cascadia Code", "SFMono-Regular", + Consolas, "Liberation Mono", monospace; + + --content-max: 72ch; + --site-max: 72rem; +} +``` + +具体色は実装時に視認性を確認して調整可能。 +Accentは青系から逸脱しない。 + +### 13.3 Dark tokens + +```scss +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --color-bg: #17191b; + --color-surface: #1d2022; + --color-text: #e9e9e6; + --color-muted: #a0a3a6; + --color-border: #363a3e; + --color-link: #86b6d8; + --color-link-hover: #add3ed; + } +} +``` + +User override用`data-theme`も定義する。 + +--- + +## 14. Theme switch設計 + +### 14.1 初期状態 + +CSS `prefers-color-scheme` を優先。 + +### 14.2 手動切替 + +`static/js/theme.js` のみを使用。 + +責務: + +- 現在theme取得 +- light/dark切替 +- `document.documentElement.dataset.theme` +- `localStorage`保存 +- toggle buttonの`aria-pressed`/label更新 +- `#syntax-light` / `#syntax-dark` の `media` を切り替え、本文themeとsyntax highlighting themeを同期 + +明示的にLightを選んだ場合: + +- `syntax-light.media = "all"` +- `syntax-dark.media = "not all"` + +明示的にDarkを選んだ場合: + +- `syntax-light.media = "not all"` +- `syntax-dark.media = "all"` + +保存themeがない場合はHTML/CSSとも `prefers-color-scheme` に従う。 + +### 14.3 CSP + +JSは外部fileのみ。 +Inline scriptは使用しない。 + +Theme JSが失敗してもOS themeにより読みやすい状態を保つ。 + +### 14.4 No-JS + +No-JSではmanual toggleだけ利用不可。 +本文・navigation・Light/Dark OS preferenceは利用可能。 + +--- + +## 15. External link表示 + +Zola Markdown設定: + +```toml +external_links_class = "external-link" +``` + +SCSS: + +```scss +.external-link::after { + content: " ↗"; + font-size: 0.8em; + text-decoration: none; +} +``` + +装飾SVG dependencyを使用しない。 + +Navigationや内部リンクには付けない。 + +--- + +## 16. Layout設計 + +### 16.1 Site container + +```scss +.site-container { + width: min(100% - 2rem, var(--site-max)); + margin-inline: auto; +} +``` + +### 16.2 Article + +初期値: + +```scss +.prose { + max-width: var(--content-max); // 72ch + margin-inline: auto; +} +``` + +仕様で本文幅は未確定だったため、初期実装は **72ch** とする。 +実際の日本語可読性を確認し、65〜75ch相当の範囲で調整可能。 + +### 16.3 Mobile + +- Side padding最低1rem +- Header navigation wrap可 +- Horizontal body scroll禁止 +- pre/code/tableは必要に応じ内部scroll +- 画像max-width:100% + +--- + +## 17. Typography設計 + +### 17.1 Body + +Sans-serif system stack。 + +```scss +body { + font-family: var(--font-sans); + line-height: 1.75; +} +``` + +### 17.2 Heading + +Sans-serif。 +過度な巨大見出しにしない。 +Home H1だけやや大きくする。 + +### 17.3 Metadata + +Monospaceまたは小さめSans。 +日付などにMonoを利用。 + +### 17.4 Code + +`var(--font-mono)`。 + +--- + +## 18. Editorial visual rules + +- Border radius最小限 +- Card shadow原則なし +- Gradientなし +- Neon/glowなし +- Glassmorphismなし +- 1px ruleを主要区切りに使用 +- Section間余白でhierarchyを作る +- 大きなHero画像なし +- Iconsは最小限 +- Badge乱用なし + +--- + +## 19. Code block設計 + +Zola syntax highlightingを利用。 + +- Client-side highlighting libraryなし +- Prism/highlight.jsなし +- class-based highlight +- `github-light` / `github-dark` のdual theme +- Zola生成 `giallo-light.css` / `giallo-dark.css` を読み込む +- theme toggle時にstylesheet mediaも同期する +- code blockは横scroll可 +- light/darkの視認性を確認 + +Highlight CSSの生成/適用方法は使用Zola versionの公式仕様に従う。 + +--- + +## 20. Image設計 + +### 20.1 Notes + +Markdown imageを使用。 + +`lazy_async_image = true` により遅延loadを利用する。 + +Altはplain textで記述。 + +### 20.2 Software + +Colocated assetを推奨。 + +Screenshotは本文内で説明と一緒に表示。 + +### 20.3 Formats + +原則: + +- SVG: icon/diagram +- WebP/AVIF/JPEG/PNG: screenshot用途に応じて選択 + +画像最適化toolchainは初期実装に追加しない。 + +--- + +## 21. Accessibility設計 + +### 21.1 Landmarks + +- `<header>` +- `<nav>` +- `<main>` +- `<article>` +- `<footer>` + +### 21.2 Skip link + +Header先頭にskip linkを設けることを推奨。 + +```html +<a class="skip-link" href="#main-content">本文へ移動</a> +``` + +### 21.3 Focus + +`:focus-visible`を明確に表示。 + +### 21.4 Color + +リンクは青色だけでなく下線でも識別可能にする。 + +### 21.5 Theme toggle + +Button要素を使用。 +Keyboard操作可能。 + +### 21.6 TOC + +`nav aria-label="目次"`。 + +--- + +## 22. SEO / metadata設計 + +最低限: + +- `<title>` +- `<meta name="description">` +- canonical URL +- `lang="ja"` +- RSS/Atom autodiscovery +- sitemap +- robots + +Open Graph / Twitter Cardは初期必須ではない。 +外部依存なしで容易に実装可能な場合は後で追加可。 + +--- + +## 23. SNS共有 + +初期実装対象外。 + +理由: + +- 仕様が条件付き +- Editorial designを優先 +- Mastodon等は単一固定share endpointにしづらい +- 外部service/仕様変化への依存を避ける + +将来追加する場合、以下の順で検討。 + +1. Web Share API +2. Copy link +3. 外部dependencyなしのSNS share URL + +--- + +## 24. Comments + +初期実装対象外。 + +Template上に空のcomment UIや未使用placeholderを作らない。 + +将来commentsを採用する場合は別設計として以下を再評価。 + +- Data persistence +- Authentication/abuse +- Spam +- Moderation +- Privacy +- CSP +- Backup +- VPS resource +- External service dependency + +--- + +## 25. Search + +初期: + +```toml +build_search_index = false +``` + +Search UIを作らない。 + +将来追加時はZola built-in search indexを第一候補とし、日本語indexing対応について使用Zola binaryの機能を確認する。 + +--- + +## 26. Git設計 + +Git管理対象: + +- `zola.toml` +- `content/` +- `templates/` +- `sass/` +- `static/` +- documentation +- deploy script(別Phaseで追加) + +`.gitignore`: + +```gitignore +/public/ +``` + +Editor/OS生成物は必要に応じ追加。 + +`themes/` が空でGitに残らない場合、`.gitkeep`は必須ではない。 + +--- + +## 27. Build設計 + +### 27.1 Development + +```bash +zola serve +``` + +### 27.2 Validation + +```bash +zola check +``` + +### 27.3 Production build + +```bash +zola build +``` + +Build成功後、`public/`が自己完結していること。 + +### 27.4 Build Gate + +以下のいずれかで失敗したらDeploy不可。 + +- `zola check` failure +- `zola build` failure +- broken internal link +- required related content path missing +- missing required template +- missing syntax language when error_on_missing_language=true + +--- + +## 28. Deployment interface + +Deployment詳細は別Phaseだが、Client siteは以下の契約を満たす。 + +Input: + +```text +source repository +``` + +Build output: + +```text +public/ +``` + +Deploy sideは`public/`の中身をrelease rootへ展開する。 + +最終URL rootに余計な`public/` directory layerを作らない。 + +例: + +```text +release/ +├── index.html +├── notes/ +├── software/ +├── tags/ +├── main.css +└── ... +``` + +--- + +## 29. Caddy互換性 + +既存Caddy: + +```text +root * /srv/www/takashi-2521.com/current +file_server +``` + +Zola outputはCaddy static file serverだけで利用できること。 + +以下を要求しない。 + +- rewrite application router +- server-side include +- PHP +- CGI +- Node +- Python backend + +404 handlingがCaddyで専用設定を必要とする場合は、Deploy統合Phaseで既存Caddy設定との整合を確認する。 + +--- + +## 30. CSP compatibility + +目標CSP: + +```text +default-src 'self'; +script-src 'self'; +style-src 'self'; +img-src 'self' data:; +font-src 'self'; +object-src 'none'; +base-uri 'self'; +frame-ancestors 'none'; +``` + +実装規則: + +- Inline JSなし +- Inline event handlerなし +- External fontなし +- External CSSなし +- External JSなし +- Evalなし +- CSS assetはself +- imageはself/data中心 + +Syntax highlightがinline styleを生成しないclass modeを優先。 + +--- + +## 31. Error handling + +Static siteのためruntime error UIは不要。 + +Build-timeに可能な限り失敗を検知する。 + +- Broken internal links: error +- Related page missing: Tera error +- Missing templates: build error +- Invalid front matter: build error + +External linksは一時障害を考慮し`warn`を基本とする。 + +--- + +## 32. Content authoring rules + +### 32.1 Notes + +- filename: lowercase ASCII kebab-case +- `date`必須 +- title日本語可 +- tags任意 +- headingsはH2以降を本文主階層に使用 +- H1はtemplate titleに委ねる +- referencesは本文末尾H2 + +### 32.2 Software + +- filename: software slug +- cgit source必須 +- GitHubはmirrorとしてoptional +- Overview/background/processはMarkdown本文 +- screenshotは必要時のみ + +### 32.3 Internal references + +`@/` linkまたはfront matter pathを使用。 +公開URLを内部参照用にhard-codeしない。 + +--- + +## 33. Date display + +内部データはISO date。 + +表示は初期実装で: + +```text +2026-08-21 +``` + +とする。 + +将来日本語表記`2026年8月21日`へ変更してもcontent metadataは変えない。 + +--- + +## 34. Home content algorithm + +Pseudo: + +```text +notes = get_section(notes) +software = get_section(software) + +recent_notes = first 5 notes +recent_software = first 4 software + +render: + site intro + recent notes + all notes link + software + all software link + about short text +``` + +Homeは最新更新日を強く演出せず、`最近のNotes`と表記する。 + +--- + +## 35. Archive algorithm + +Zola公式archive patternを使用。 + +Pseudo Tera: + +```tera +{% set pages_by_year = section.pages | group_by(attribute="year") %} +{% for year, pages in pages_by_year %} + <section> + <h2>{{ year }}</h2> + {% for page in pages %} + ... + {% endfor %} + </section> +{% endfor %} +``` + +使用Zola/Tera版でyear orderが期待通りでない場合、公式Archive例のreverse処理を採用する。 + +--- + +## 36. TOC recursive rendering + +`page.toc`の`children`を階層描画する。 + +概念: + +```text +toc(headers): + ul + foreach header + li + a header.permalink / title + if children + toc(children) +``` + +使用Zola版がTera componentを推奨する場合はcomponentを使用する。 +古いshortcode設計を新規導入しない。 + +--- + +## 37. Dark mode persistence + +`theme.js` concept: + +```text +on load: + stored = localStorage.getItem("theme") + if stored light/dark: + set data-theme + +on toggle: + determine current + toggle + store + update accessible label +``` + +System preferenceはCSS側で処理する。 + +localStorageが利用不可でもerrorで本文表示を妨げない。 + +--- + +## 38. Favicon + +初期faviconは自作SVG 1ファイル。 + +モチーフは単純な文字または記号。 + +例候補: + +- 「記」 +- `T` +- 小さな本/ノートを抽象化した幾何形状 + +初期実装では複雑なfavicon pipelineを作らない。 + +--- + +## 39. 404 design + +通常Pageと同じwidth/token/themeを使用。 + +内容: + +```text +404 +ページが見つかりません。 + +Home +Notes +Software +``` + +Animationやillustration不要。 + +--- + +## 40. Testing / Validation checklist + +### 40.1 Zola + +- [ ] `zola check` +- [ ] `zola build` +- [ ] `/public/index.html` +- [ ] `/public/notes/index.html` +- [ ] `/public/software/index.html` +- [ ] `/public/about/index.html` +- [ ] `/public/404.html` +- [ ] `/public/atom.xml` +- [ ] `/public/rss.xml` +- [ ] `/public/giallo-light.css` +- [ ] `/public/giallo-dark.css` +- [ ] sitemap +- [ ] robots + +### 40.2 Content + +- [ ] Note 2件以上でyear grouping確認 +- [ ] 複数年でarchive確認 +- [ ] tag link確認 +- [ ] updatedあり/なし確認 +- [ ] TOCあり/なし確認 +- [ ] footnote確認 +- [ ] references style確認 +- [ ] related software確認 +- [ ] related note確認 +- [ ] Software GitHub mirrorあり/なし確認 +- [ ] Screenshotあり/なし確認 + +### 40.3 Responsive + +- [ ] Desktop +- [ ] Narrow desktop +- [ ] Tablet +- [ ] Mobile +- [ ] Long title +- [ ] Long URL +- [ ] Code block +- [ ] Table +- [ ] Image + +### 40.4 Accessibility + +- [ ] Keyboard only +- [ ] Focus visible +- [ ] Skip link +- [ ] Heading hierarchy +- [ ] Theme toggle keyboard +- [ ] Link underline +- [ ] Dark contrast +- [ ] Light contrast + +### 40.5 Security + +- [ ] no external JS +- [ ] no external CSS +- [ ] no npm +- [ ] no Node +- [ ] no inline event handler +- [ ] no eval +- [ ] CSP-compatible +- [ ] no tracking script + +--- + +## 41. Traceability / 仕様整合性確認 + +| Specifications要求 | Design対応 | 状態 | +|---|---|---| +| Notes + Software中心 | Sectionsとして分離 | OK | +| Home index | `index.html`で5 Notes / 4 Software | OK | +| 年別Notes | Zola archive `group_by(year)` | OK | +| tagsのみ | `taxonomies = [{name="tags"}]` | OK | +| Note Sans/余白 | Typography/Layout | OK | +| Note TOC常時 | `page.toc` inline nav | OK | +| Footnotes | Zola Markdown標準 | OK | +| References | Markdown H2 +共通SCSS | OK | +| 公開日/更新日 | date/updated | OK | +| Software一覧はPJ名中心 | software/section | OK | +| Software背景・過程 | Markdown body | OK | +| 言語/License | `extra` | OK | +| cgit正本 | `extra.source`必須 | OK | +| GitHub mirror | optional extra | OK | +| Related Notes/Software | explicit content path + `get_page` | OK | +| About | `about.md` | OK | +| Header小型 | partial/header | OK | +| Source navなし | Headerには含めない | OK | +| 日本語のみ | `default_language="ja"` | OK | +| Sans + Mono | SCSS token | OK | +| 青accent | color token | OK | +| Dark mode | CSS + minimal JS | OK | +| Sidebarなし | article inline layout | OK | +| タグは一覧中心 | Note detailで主要表示しない | OK | +| RSS + Atom | built-in feeds | OK | +| Search将来 | initial false | OK | +| Comments条件付き | initial scope外 | OK | +| SNS条件付き | initial scope外 | OK | +| Analyticsなし | scriptなし | OK | +| Images最小限 | colocated assets optional | OK | +| No animation | motionなし | OK | +| Blue links | SCSS | OK | +| External marker | `external_links_class` + CSS | OK | +| ASCII slug | authoring rules | OK | +| Caddy static | public artifact only | OK | +| npm/Nodeなし | no dependency toolchain | OK | + +--- + +## 42. 仕様上の未確定事項に対する設計判断 + +### 42.1 本文最大幅 + +仕様では後決めだったため、初期値 **72ch** を採用。 +日本語本文で広すぎる場合は65〜75ch相当の範囲で調整する。 + +### 42.2 青の具体色 + +初期候補として落ち着いた`#315f82`系を採用。 +WCAG contrast確認後に調整可。 + +### 42.3 Home件数 + +仕様上「おまかせ」だったため以下を採用。 + +- Notes 5 +- Software 4 + +### 42.4 favicon + +自作単純SVGとし、外部assetを導入しない。 +最終モチーフはvisual tuning時に決定可能。 + +### 42.5 Comments + +静的構成との境界が未確定のため、初期実装対象外。 + +### 42.6 SNS共有 + +「簡単に対応できれば」という条件付き要件のため、初期実装対象外。 +外部dependencyなしで必要性が確認できた時点で追加する。 + +--- + +## 43. 実装Phase + +### Phase C1 — Foundation + +- Zola version確認 +- `zola.toml` +- content structure +- base template +- SCSS entry +- `zola check/build` + +PASS: +- Build成功 +- Home生成 + +### Phase C2 — Information Architecture + +- Notes Section +- Software Section +- About +- Tags taxonomy +- URLs + +PASS: +- `/notes/`, `/software/`, `/about/`, `/tags/` + +### Phase C3 — Editorial UI + +- Header/Footer +- typography +- layout +- light/dark +- links +- responsive + +PASS: +- Desktop/Mobile +- no external dependencies + +### Phase C4 — Notes + +- year archive +- tags in list +- note dates +- TOC +- footnotes +- references + +PASS: +- 複数年sampleで表示確認 + +### Phase C5 — Software + +- software list +- detail metadata +- source/mirror +- related notes +- optional images + +PASS: +- Source優先表示 +- related page resolution + +### Phase C6 — Static Features + +- feeds +- sitemap +- robots +- 404 +- syntax highlighting +- favicon + +PASS: +- expected artifacts生成 + +### Phase C7 — Final Validation + +- `zola check` +- `zola build` +- link checks +- accessibility +- responsive +- CSP compatibility +- Git status + +PASS: +- Specifications受入条件を全件満たす + +--- + +## 44. 実装禁止事項 + +Codex等の実装エージェントは以下を勝手に追加しない。 + +- npm +- Node.js +- React +- Vue +- Svelte +- htmx +- Tailwind +- Bootstrap +- external CSS framework +- external icon framework +- external JS CDN +- Google Fonts等external font +- client-side syntax highlighter +- analytics +- comments +- search +- server backend +- database +- Docker +- unnecessary build script +- external Zola theme +- animations + +必要が生じた場合は実装前に要求変更として報告する。 + +--- + +## 45. 完了判定 + +Design実装完了時に以下を報告する。 + +1. 使用Zola version +2. 実装したdirectory tree +3. `zola check`結果 +4. `zola build`結果 +5. 生成ファイル確認 +6. 主要URL一覧 +7. external dependencyがないこと +8. Specifications traceability結果 +9. 未実装項目 +10. 仕様からの逸脱の有無 + +仕様から逸脱した場合は、理由・影響・代替案を明記する。 + +--- + +## 46. 公式Zola仕様との整合上の注意 + +- `zola.toml` を正式設定ファイルとして使用する。 +- `content/notes/_index.md` と `content/software/_index.md` をSection定義とする。 +- `about.md` はPageとして扱う。 +- Homepageはroot Sectionとして`templates/index.html`を使用する。 +- Notes/SoftwareはSectionの`template` / `page_template`で専用Templateへ接続する。 +- ZolaのTaxonomyで`tags`を実装する。 +- 年別ArchiveはZola公式例と同じ`group_by(attribute="year")`系の実装を使う。 +- 関連Pageは`get_page(path=...)`で解決する。 +- Markdown本文内の内部リンクは`@/`を優先する。 +- Feed、sitemap、robotsはZola built-inを優先する。 +- `static/`はas-is copy、`sass/`はZola compile対象として扱う。 +- Page/SectionのTera contextを使用し、独自content indexを作らない。 +- 利用Zola版でTera Componentsが利用可能・推奨なら、TOC等の再利用可能UIに採用してよい。 +- 将来Searchを有効化する際、日本語検索index対応はZola binary build featureの確認を必須とする。 diff --git a/doc/Specifications.md b/doc/Specifications.md index d2e9157..9f9c60f 100644 --- a/doc/Specifications.md +++ b/doc/Specifications.md @@ -1,755 +1,720 @@ -はい。公式ドキュメントに沿って見直すと、前回の要求仕様にはいくつか **Zola固有の構成・命名に寄せた方がよい点**があります。 +# Specifications.md -特に重要なのは、Zolaは公式に「サイト構造を決めつけない」としており、基本は `content/` 配下の **Section (`_index.md`) と Page (`*.md`)**、そして `template` / `page_template` でテンプレートを結びつける構造です。公式の初期ディレクトリも `zola.toml / content / sass / static / templates / themes` です。 ([Zola][1]) +# たかしの記録 — 要求仕様書 -その前提で、Codex向け仕様は次のように修正するのがよいです。 +## 1. 文書目的 -````markdown -# Zola Web Site Client Implementation Requirements +本書は、個人Webサイト「たかしの記録」の利用者視点・コンテンツ視点での要求仕様を定義する。 -## 1. 目的 - -自前VPS上で Caddy により配信する個人Webサイトを Zola で構築する。 - -本番VPSでは動的Webアプリケーションを実行せず、クライアントPC上で Zola により静的HTML/CSS/JSを生成し、生成済み成果物のみをVPSへデプロイする。 - -npm / Node.js に依存しない構成とし、Webサイト構築に伴うサプライチェーン攻撃面を最小化する。 +本書では「何を提供するサイトか」「誰がどのように読むか」「どの情報をどのように見せるか」を定義し、具体的なZola/Tera/SCSS実装方法は `Design.md` に委ねる。 --- -# 2. Zola採用方針 - -Zola公式ドキュメントの構造・概念・標準機能に従うこと。 - -Zolaは以下を基本概念として使用する。 - -- Site configuration: `zola.toml` -- Content - - Section: `_index.md` - - Page: 通常のMarkdownファイル -- Templates: Tera -- Sass: `sass/` -- Static assets: `static/` -- Themes: `themes/` -- Build output: `public/` +## 2. サイト概要 -独自のディレクトリ規約や別SSG由来の設計を持ち込まず、Zola標準のSection/Pageモデルを優先する。 +### 2.1 サイト名 ---- +**たかしの記録** -# 3. 必須技術構成 - -| 領域 | 採用 | -|---|---| -| Static Site Generator | Zola | -| Configuration | `zola.toml` | -| Content | CommonMark Markdown | -| Content model | Zola Section / Page | -| Template engine | Tera | -| Style | Sass / SCSS | -| JavaScript | 必要最小限の vanilla JavaScript | -| Backend | なし | -| Database | なし | -| npm / Node.js | 使用しない | -| htmx | 使用しない | -| External CDN | 原則使用しない | -| Production Web server | Caddy | +### 2.2 サイトの目的 ---- +本サイトは、運営者が日頃考えたこと・感じたこと・調べたことを記録し、作成したソフトウェアとともに長期的に保存・公開する個人アーカイブである。 -# 4. Zola標準ディレクトリ構造 +主な用途は次のとおり。 -`zola init` が生成する構造を基準とする。 +- 日々考えたことや感じたことの記録 +- 読書を通じて考えたことの記録 +- 技術調査・技術メモの公開 +- 長文記事・考察の公開 +- 作成したソフトウェアの紹介 +- ソフトウェア制作の背景・過程・設計意図の記録 +- ソフトウェアに関連する技術記事との相互参照 -```text -website/ -├── zola.toml -├── content/ -├── sass/ -├── static/ -├── templates/ -└── themes/ -```` +### 2.3 主な対象分野 -build後には以下が生成される。 +Notesでは特定分野に限定せず、少なくとも以下を扱う。 -```text -public/ -``` +- 情報工学・ソフトウェア +- 論理学 +- マルクス・資本論・政治経済 +- 地球環境 +- 読書を通じた考察 +- 上記にまたがる複合的なテーマ -`public/` は生成物でありGit管理しない。 +分類は固定カテゴリではなくタグを基本とし、分野横断の記事を許容する。 -`themes/` はZola標準ディレクトリとして保持してよいが、本プロジェクトでは外部themeを原則使用しない。 +### 2.4 想定読者 ---- +- 運営者本人 +- ソフトウェアエンジニア +- 技術分野に関心のある一般読者 +- 学生 +- 個々の記事テーマに関心のある一般人 -# 5. 初期サイト構造 +専門読者だけに限定せず、記事冒頭で主題を把握できることを重視する。 -以下のコンテンツ領域を実装する。 +### 2.5 更新頻度 -```text -/ -├── about/ -├── projects/ -└── notes/ -``` +通常は **2〜3か月に1〜2回程度** を想定する。 -想定URL: +更新頻度を競うニュースサイトやSNSではなく、長期間残るアーカイブとして設計する。 -```text -https://www.takashi-2521.com/ -https://www.takashi-2521.com/about/ -https://www.takashi-2521.com/projects/ -https://www.takashi-2521.com/notes/ -``` +--- -ZolaのSection/Pageモデルに従い、以下を基本構造とする。 +## 3. サイトの基本コンセプト -```text -content/ -├── about.md -│ -├── projects/ -│ ├── _index.md -│ ├── project-a.md -│ └── project-b.md -│ -└── notes/ - ├── _index.md - ├── note-a.md - └── note-b.md -``` +### 3.1 中心コンテンツ -`projects/_index.md` と `notes/_index.md` は Zola Section とする。 +サイトの主役は以下の2つとする。 -`projects/*.md` および `notes/*.md` は Zola Page とする。 +1. **Notes** +2. **Software** -About は単独Pageとして扱う。 +HomeとAboutは、この2つへの入口・補助情報として扱う。 -必要な理由がある場合のみPage Bundle等の別構造を採用してよい。 +### 3.2 デザインコンセプト ---- +デザインは **Minimal Editorial Archive** を基本とする。 -# 6. Section設計 +方向性は以下。 -## 6.1 Projects Section +- 文章とリンクを主役にする +- 装飾を抑える +- 罫線・余白・タイポグラフィで情報階層を表現する +- カードUIを乱用しない +- 画像を主役にしない +- 技術者向けダッシュボード風・SaaSランディングページ風にしない +- 長文を落ち着いて読める +- 更新間隔が長くても「止まったサイト」に見えにくい +- Gwern.netの長文アーカイブ性とSimon Willison's Weblogの実用的な情報構造を参考にする -`content/projects/_index.md` を作成する。 +### 3.3 基本画面構造 -例: +全画面の基本構造は以下。 -```toml -+++ -title = "Projects" -sort_by = "date" -template = "projects.html" -page_template = "project-page.html" -+++ -``` +- 小さなHeader +- Main content +- 小さなFooter -Zola標準のSection変数を使用する。 +Header/Footerは本文より目立たせず、高さも必要以上に取らない。 -Projects一覧はTera側で、 +Sidebarは使用しない。 -```text -section.pages -``` +--- -を利用して生成する。 +## 4. 情報アーキテクチャ -独自JSON index等を作成しない。 +### 4.1 グローバル構成 ---- +トップレベルは以下を基本とする。 -## 6.2 Notes Section +- `/` — Home +- `/notes/` — Notes一覧 +- `/software/` — Software一覧 +- `/about/` — About +- `/tags/` — タグ一覧 +- `/tags/<tag>/` — タグ別Notes等の一覧 -`content/notes/_index.md` を作成する。 +### 4.2 グローバルナビゲーション -例: +Headerに以下を表示する。 -```toml -+++ -title = "Notes" -sort_by = "date" -template = "notes.html" -page_template = "note-page.html" -+++ -``` +- Home +- Notes +- Software +- About -新しいNoteは `content/notes/*.md` を追加するだけで一覧へ反映されること。 +Source/cgitへのリンクはグローバルナビゲーションには常設せず、Software内で案内する。 --- -# 7. Page設計 - -## 7.1 Project Page +## 5. Home仕様 -Projectは1 Markdown = 1 Zola Pageとする。 +### 5.1 役割 -例: +Homeはサイト全体の入口・索引として機能する。 -```toml -+++ -title = "Example Project" -date = 2026-08-21 -description = "Project description" +ポートフォリオ用の大きなHero、自己宣伝CTA、プロフィール写真などは必須としない。 -[extra] -status = "active" -repository = "https://example.invalid/" -+++ -``` +### 5.2 表示内容 -Zola標準front matterで直接扱えないプロジェクト固有metadataは `[extra]` を使用する。 +以下の順序を基本とする。 -最低限以下を扱えるようにする。 +1. サイト名 +2. 短い説明文 +3. 最近のNotes +4. Software +5. サイトについての短い説明 +6. Aboutへの導線 -* `title` -* `date` -* `description` +### 5.3 トップ説明文 -Project固有情報: +初期文言: -* `extra.status` -* `extra.repository` +> 日々考えたこととか思ったことを記録します -必要なら以下も追加可能。 +将来文言を修正できること。 -* `extra.homepage` -* `extra.language` -* `extra.license` +### 5.4 最近のNotes -metadataを過剰に増やさないこと。 +初期表示件数は **5件** とする。 ---- +各項目には以下を表示する。 -## 7.2 Note Page +- 公開日 +- タイトル +- 必要に応じ短いdescription -例: +Homeでは情報量を抑え、本文を直接大量表示しない。 -```toml -+++ -title = "Example Note" -date = 2026-08-21 -description = "Note description" -+++ +### 5.5 Software -本文... -``` +初期表示件数は **4件** とする。 -本文はZolaの `page.content` としてtemplateから表示する。 +Homeでは原則としてSoftware名を中心に表示する。 --- -# 8. Templates +## 6. Notes仕様 -Zola公式ドキュメントのtemplate inheritanceを利用する。 +### 6.1 Notesの定義 -最低限以下を構成する。 +文章として残したい内容は原則としてNotesとして扱う。 -```text -templates/ -├── base.html -├── index.html -├── projects.html -├── project-page.html -├── notes.html -├── note-page.html -└── page.html -``` +技術メモ、読書記録、調査、考察、論考を別々のコンテンツ型へ過度に分割しない。 -必要であれば以下を追加する。 +### 6.2 Notes一覧 -```text -templates/ -└── partials/ - ├── header.html - ├── navigation.html - └── footer.html -``` +Notes一覧は **年別アーカイブ** とする。 -`base.html` を共通レイアウトとし、各templateはTeraの +例: -```text -{% extends "base.html" %} -``` - -およびblockを使用する。 - ---- +- 2026 + - 08-21 記事タイトル + - 05-03 記事タイトル +- 2025 + - ... -# 9. Internal Link方針 +年は新しい順、各年内も原則として公開日の新しい順に表示する。 -サイト内部リンクは、可能な限りURL文字列をハードコードせず、Zolaの `get_url` と `@/` internal link記法を使用する。 +### 6.3 Notes一覧に表示する情報 -例: +- 公開日 +- タイトル +- タグ +- 必要に応じdescription -```tera -<a href="{{ get_url(path='@/projects/_index.md') }}">Projects</a> -``` +タグはNotes一覧およびタグ一覧で表示する。 -これによりcontent path変更時のリンク破損を減らす。 +### 6.4 Note詳細 ---- +Note詳細では以下を表示する。 -# 10. Home Page +- タイトル +- 公開日 +- 更新日(存在する場合) +- 目次 +- 本文 +- 脚注 +- 参考文献 +- 関連Software(設定されている場合) +- 前後の記事導線は必須としない -Homeは `templates/index.html` で生成する。 +タグはNote詳細の主要表示項目にしない。 -最低限以下を表示可能にする。 +### 6.5 目次 -* サイト名 -* 簡単な説明 -* Aboutへのリンク -* Projectsへのリンク -* Notesへのリンク +Note詳細では **目次を常時表示**する。 -Home用の不要なCMS的データ構造は作らない。 +Sidebarは使わないため、本文冒頭付近にインラインで配置する。 -必要なら `zola.toml` の `extra` をサイト全体metadataとして使用してよい。 +見出しが存在しない場合は空の目次領域を表示しない。 ---- +### 6.6 脚注 -# 11. Sass / SCSS +Markdown/Zola標準機能による脚注を日常的に利用できること。 -Zola標準Sass compilationを使用する。 +脚注の見た目を本文と一貫させる。 -`zola.toml`: +### 6.7 参考文献 -```toml -compile_sass = true -``` +長文記事の末尾にReferences/参考文献セクションを設けられること。 -構成例: +参考文献は通常のMarkdownで記述可能とし、共通の見た目を持つ。 -```text -sass/ -├── main.scss -├── _variables.scss -├── _base.scss -├── _typography.scss -├── _layout.scss -└── _components.scss -``` +### 6.8 公開日と更新日 -npm Sass / Dart Sass / PostCSS等は追加しない。 +- 公開日は必須 +- 更新日は任意 +- 更新日が存在する場合は公開日とともに表示する ---- +### 6.9 記事の成熟度 -# 12. Static assets +Draft / Note / Essay 等の成熟度・状態ラベルは導入しない。 -Zolaの `static/` はbuild時に出力先へそのままコピーされる前提で使用する。 +Zola内部のdraft機能は公開制御に使用してよいが、利用者向けの成熟度表示は行わない。 -例: +--- -```text -static/ -├── favicon.svg -├── robots.txt -├── images/ -└── js/ - └── site.js -``` +## 7. Tags仕様 -外部CDN依存を原則禁止する。 +### 7.1 分類方針 -JavaScriptが必要な場合は `static/js/site.js` 等として自前配信する。 +初期実装ではカテゴリを設けず、**tagsのみ**を使用する。 ---- +### 7.2 タグの目的 -# 13. JavaScript +- 分野横断の記事を柔軟に分類する +- 同じテーマの記事を探せるようにする +- 固定カテゴリ階層を避ける -JavaScriptは必要最小限の vanilla JavaScript のみ使用する。 +### 7.3 想定タグ例 -以下の原則に従う。 +- 情報工学 +- 論理学 +- 資本論 +- 政治経済 +- 地球環境 +- AI +- LLM +- 読書 +- Windows +- Git -1. HTML標準機能で実現可能ならJSを使用しない。 -2. CSSのみで実現可能ならJSを使用しない。 -3. 外部frontend frameworkを導入しない。 -4. inline event handlerを使用しない。 -5. `eval()` を使用しない。 -6. JavaScript無効でも主要コンテンツを閲覧可能にする。 +タグ語彙はコンテンツ追加に応じて増やす。 --- -# 14. Syntax Highlighting - -技術記事用のコードブロックsyntax highlightingにはZola標準機能を使用する。 +## 8. Software仕様 -独自のPrism.js / highlight.js等は初期実装では導入しない。 +### 8.1 役割 -設定は採用するZola versionの公式configuration仕様に従うこと。 +Softwareは作成したソフトウェアを人間向けに説明する場所とする。 ---- +ソースコードそのものの閲覧・履歴表示はcgitへ委ねる。 -# 15. Feeds +### 8.2 Software一覧 -NotesについてRSS/Atom等のfeedを生成する。 +一覧では情報量を抑え、**Software名を主表示**とする。 -Zola標準feed生成機能を使用する。 +必要に応じ短いdescriptionを添えてよいが、画像や大量のmetadataは表示しない。 -独自feed generatorは実装しない。 +### 8.3 Software詳細 -採用するZola versionにおける推奨設定を確認して実装すること。 +以下の情報を扱えること。 ---- +- Software名 +- 概要 +- 作成した背景・目的 +- 開発・制作の過程 +- 特徴 +- 使用言語 +- ライセンス +- Source(自前cgit) +- GitHub mirror(存在する場合) +- 関連Notes +- スクリーンショット(必要な場合のみ) -# 16. Sitemap +Implementation detailsは本文中で必要な範囲だけ記述し、固定項目として過剰に構造化しない。 -Zola標準のsitemap生成機能を使用する。 +### 8.4 Sourceの優先順位 -独自 `sitemap.xml` generatorは作らない。 +ソースコードの正本は **自前cgit** とする。 ---- +Software詳細では以下の順で扱う。 -# 17. robots.txt +1. Source — cgit +2. GitHub mirror — 当面維持 -Zolaの標準 `robots.txt` template機能または `static/robots.txt` のうち、公式ドキュメントに沿った単純な方法を採用する。 +### 8.5 スクリーンショット -不要な独自処理を作らない。 +- Software一覧では表示しない +- Software詳細で必要な場合のみ表示する +- 画像が多くなり本文の可読性を損なわないようにする --- -# 18. 404 Page +## 9. NotesとSoftwareの関連付け -Zola標準の404 template仕様に従って実装する。 +NotesとSoftwareは相互に関連付けられること。 -想定: +例: -```text -templates/404.html -``` +- Softwareから関連Notesへ +- Noteから関連Softwareへ -Caddyから静的404として扱える成果物を生成する。 +関連付けは明示的に管理し、単なるタグ一致だけで自動的に関連付けない。 --- -# 19. Search +## 10. About仕様 -初期実装では検索を無効とする。 +Aboutには以下を掲載する。 -`zola init` / configuration上もsearch indexを生成しない設定とする。 +- 名前 +- 一言プロフィール +- このサイトの目的 +- 主な関心分野 +- 連絡先 -将来必要になった場合のみZola標準search indexを再検討する。 - -外部検索libraryは現時点では導入しない。 +職務経歴書・詳細な履歴書としては設計しない。 --- -# 20. Themes +## 11. Header仕様 -Zolaは `themes/` を標準で持つが、本プロジェクトでは外部themeを原則使用しない。 +### 11.1 表示内容 -理由: +- サイト名「たかしの記録」 +- Home +- Notes +- Software +- About +- Dark/Light theme切替(提供する場合) -* supply-chain面積を増やさない -* upstream theme更新への依存を避ける -* CSS/templateを完全に管理可能にする -* 不要なJavaScriptを取り込まない +### 11.2 レイアウト -したがって自前template + 自前SCSSを基本とする。 +- 高さを抑える +- サイト本文より主張しない +- DesktopとMobileの双方で簡潔に表示 +- 必要でなければHamburger menuは使用しない --- -# 21. Zola Configuration - -設定ファイル名は公式構成に合わせて、 - -```text -zola.toml -``` +## 12. Footer仕様 -を使用する。 +Footerには最低限以下を表示する。 -最低限: +- Copyright +- RSS +- Mail -```toml -base_url = "https://www.takashi-2521.com" -compile_sass = true -build_search_index = false -``` +cgitリンクはSoftware側で扱う。 -実際のsyntax highlighting、feed、taxonomy等については、使用するZola versionの公式documentationを確認して正しいconfiguration keyを使用すること。 +--- -推測で設定名を追加しないこと。 +## 13. Typography仕様 ---- +### 13.1 本文 -# 22. Development +Sans-serifを使用する。 -ローカル開発ではZola標準development serverを使用する。 +Web fontは外部取得せず、system font stackを使用する。 -```bash -zola serve -``` +### 13.2 UI -Zolaは `zola.toml`, `content`, `sass`, `static`, `templates` の変更を監視し、local live reloadに使用できる。 +Sans-serif + Monospaceを役割分担して使用する。 -追加のdev serverは作成しない。 +Monospaceは以下のような補助情報に使用できる。 ---- +- 日付 +- metadata +- ラベル +- コード +- 技術的な短い補助情報 -# 23. Validation / Build +### 13.3 コード -実装中は以下を使用する。 +system monospaceを使用する。 -```bash -zola check -zola build -``` +--- -local preview: +## 14. 色・テーマ仕様 -```bash -zola serve -``` +### 14.1 Light theme -build output: +- 紙に近い暖かみのある明るい背景 +- ほぼ黒の本文 +- muted gray +- 薄い罫線 +- 青をaccent/link色として使用 -```text -public/ -``` +### 14.2 Dark theme -`public/` のみでサイトが成立すること。 +Dark modeに対応する。 ---- +- 真っ黒ではなく濃いgrayを基本 +- 明るい本文 +- muted gray +- dark border +- light themeと対応する青系accent -# 24. Git +### 14.3 Accent -sourceのみGit管理する。 +Accentは青系1色を基本とする。 -`.gitignore`: +具体的な色値はDesignで定義する。 -```gitignore -/public/ -``` +--- -Zolaが実際に生成しない独自cache directoryを推測で追加しないこと。 +## 15. リンク仕様 -Editor / OS固有ファイルは必要に応じて追加可能。 +- 通常リンクは青 +- 原則として下線を付け、リンクであることを明確にする +- Hoverでは色等を軽く変える +- 大きなアニメーションを付けない +- 外部リンクには `↗` 等の簡潔な外部リンク記号を表示する +- 外部アイコンライブラリは使用しない --- -# 25. Deployment境界 +## 16. Layout仕様 -本仕様ではVPS側の配信基盤は変更しない。 +- Sidebarなし +- Main contentを中央付近に配置 +- 長文は左右に十分な余白を確保 +- 最大本文幅はDesign段階で具体値を定義 +- Desktopでは広すぎない +- Mobileでは横スクロールを発生させない +- コードブロック等、必要な要素のみ内部横スクロールを許容 -既存: +--- -```text -/srv/www/takashi-2521.com/ -├── releases/ -└── current -> releases/<release-id> -``` +## 17. Motion仕様 -Caddy: +Animationは原則使用しない。 -```text -root * /srv/www/takashi-2521.com/current -``` +以下も初期実装では不要。 -クライアント側では将来、 +- View Transition +- Parallax +- Scroll animation +- Fade-in animation +- Decorative motion -```text -zola check -→ zola build -→ public/ -→ archive -→ SSH transfer -→ releases/<release-id> -→ current切替 -``` +--- -を行う。 +## 18. 画像仕様 -本Client Phaseでは `public/` が自己完結したbuild artifactになることを保証する。 +- 画像は最小限 +- Homeに装飾目的の大量画像を置かない +- Notesでは説明に必要な画像のみ +- Softwareでは詳細ページのみ +- 外部画像hotlinkを原則避ける +- 代替テキストを付与する --- -# 26. Security Requirements - -必須: +## 19. Feed仕様 -* npmを使用しない -* Node.js dependencyを使用しない -* React/Vue/Svelteを使用しない -* htmxを使用しない -* Tailwindを使用しない -* 外部CDNを使用しない -* external themeを原則使用しない -* backendを実装しない -* DBを使用しない -* buildをVPSで実行しない +以下を提供する。 -可能な限り以下のCSPと互換な構造にする。 +- Atom +- RSS -```text -default-src 'self'; -script-src 'self'; -style-src 'self'; -img-src 'self' data:; -font-src 'self'; -object-src 'none'; -base-uri 'self'; -frame-ancestors 'none'; -``` +HeaderまたはFooterから購読導線を用意する。 -inline script / inline event handlerは避ける。 +Feed autodiscoveryに対応する。 --- -# 27. Accessibility +## 20. Search仕様 -最低限以下を満たす。 +初期リリースではSearchを提供しない。 -* semantic HTML -* 正しいheading hierarchy -* keyboard navigation -* visible focus -* alt text -* color contrast -* `prefers-reduced-motion` を考慮 -* JSなしでも主要navigationが利用可能 +将来の記事数増加に応じて、Zolaの検索インデックス機能を第一候補として追加検討する。 --- -# 28. Client Phase +## 21. Comments仕様 -## Client-1: Zola initialization +初期リリースではコメント機能を提供しない。 -* Zola version確認 -* `zola init` 相当の標準構成を確認 -* `zola.toml` -* 標準directory作成 -* `zola check` -* `zola build` +理由: -PASS: +- 完全静的構成では保存先が存在しない +- 自前backendを追加すると現行セキュリティ境界が変化する +- 外部コメントサービスは外部依存・プライバシーの再検討が必要 -```text -zola check PASS -zola build PASS -public/index.html exists -``` +将来機能として、以下を別途評価する。 ---- +- 自前軽量コメントbackend +- Gitベース方式 +- 外部コメントサービス +- Mailによる感想受付 -## Client-2: Section / Page architecture +--- -* Home -* About Page -* Projects Section -* Project Pages -* Notes Section -* Note Pages +## 22. SNS共有仕様 -PASS: +初期リリースでは大きなSNS共有ボタン群を必須としない。 -```text -/ - /about/ - /projects/ - /projects/<page>/ - /notes/ - /notes/<page>/ -``` +外部dependencyを追加せず、簡潔なテキストリンクまたはWeb Share API等だけで実現できる場合に追加を検討する。 -が生成される。 +サイトのEditorialデザインを損なわないことを優先する。 --- -## Client-3: Tera templates / UI +## 23. Analytics仕様 -* `base.html` -* template inheritance -* navigation -* header/footer -* SCSS -* responsive layout +アクセス解析は導入しない。 -PASS: +- 外部Analytics scriptなし +- Tracking cookieなし +- Marketing trackerなし -* Desktop正常 -* Mobile正常 -* internal navigation正常 +Webサーバの運用ログはサーバ運用上のログであり、サイト機能としてのアクセス解析とは扱わない。 --- -## Client-4: Content model +## 24. URL仕様 -Projects/NotesをZola Pageとして管理する。 +### 24.1 URL構造 -PASS: +- `/notes/<slug>/` +- `/software/<slug>/` +- `/tags/<slug>/` +- `/about/` -Markdownを1件追加すると、 +### 24.2 Slug -```text -Section listing -+ -individual Page -``` +Content filename / slugは **英語ASCIIを基本**とする。 -の両方へ自動反映される。 +記事本文や表示タイトルは日本語でよい。 ---- - -## Client-5: Zola static features - -* syntax highlighting -* feed -* sitemap -* robots.txt -* 404 -* favicon +URLの安定性を優先し、公開後のslug変更は原則避ける。 -Zola標準機能を優先する。 +--- -PASS: +## 25. 日本語・多言語仕様 -`zola build` の成果物に必要な静的ファイルが生成される。 +- 表示言語は日本語のみ +- UI文言も日本語 +- 初期設計では多言語対応を実装しない +- 多言語化のための不要なUIやselectorは設けない --- -## Client-6: Git / final validation +## 26. Accessibility要求 -* `.gitignore` -* sourceのみGit管理 -* external dependenciesなし -* final `zola check` -* final `zola build` +最低限以下を満たす。 ---- +- Semantic HTML +- 適切なheading hierarchy +- Keyboard navigation +- Visible focus +- 十分な色contrast +- 画像alt +- Linkが色だけで判別不能にならない +- HTML/主要navigationはJavaScriptなしでも利用可能 +- Theme切替機能がなくてもOS themeで十分読めること +- prefers-reduced-motionを尊重(原則motion自体を使用しない) + +--- + +## 27. Performance要求 + +サイトは静的HTML/CSS/最小JSのみで成立すること。 -# 29. Final Acceptance Criteria - -以下をすべて満たすこと。 - -* [ ] Zola公式のSection/Pageモデルを使用している -* [ ] `zola.toml` を使用している -* [ ] `content/` を使用している -* [ ] Sectionは `_index.md` で定義されている -* [ ] ProjectはZola Pageとして実装されている -* [ ] NoteはZola Pageとして実装されている -* [ ] Section `template` / `page_template` を適切に使用している -* [ ] Tera template inheritanceを使用している -* [ ] internal linkにZola標準機能を利用している -* [ ] Sass compilationはZola標準機能 -* [ ] syntax highlightingはZola標準機能 -* [ ] feedはZola標準機能 -* [ ] sitemapはZola標準機能 -* [ ] 404はZola標準機能 -* [ ] `zola serve` が動作 -* [ ] `zola check` が成功 -* [ ] `zola build` が成功 -* [ ] `public/` 単独で静的サイトとして成立 -* [ ] npm dependencyなし -* [ ] Node.js dependencyなし -* [ ] frontend frameworkなし -* [ ] htmxなし -* [ ] external CDNなし -* [ ] external theme依存なし -* [ ] backendなし -* [ ] DBなし -* [ ] `public/` はGit管理外 - -[1]: https://www.getzola.org/documentation/getting-started/overview/ "Overview | Zola" +- Backend runtime不要 +- DB不要 +- npm/Node.js不要 +- 外部CDN不要 +- Web font不要 +- 大きなfrontend framework不要 +- 初期表示に不要な画像を読み込まない +- JavaScriptが無効でも主要本文が読める + +--- + +## 28. Security / Privacy要求 + +- npm package dependencyを使用しない +- Node.js runtimeを使用しない +- 外部themeを原則使用しない +- 外部JavaScript CDNを使用しない +- Inline JavaScriptを避ける +- `eval()` を使用しない +- 外部trackingを使用しない +- CSP `default-src 'self'` を中心とした厳格な設定と互換性を持つ +- Static Siteとして本番にserver-side codeを置かない +- Caddyは生成済み成果物のみを配信する + +--- + +## 29. 技術基盤に関する制約 + +以下はDesignに委ねるが、要求上の前提とする。 + +- SSG: Zola +- Template: Tera +- Content: Markdown +- Style: SCSS +- Client JS: minimal vanilla JavaScript +- Production: Caddy static file server +- Build: Client PC +- Deployment: `public/` build artifactをVPSへ転送 +- Git source管理とDeployは独立 +- cgitをソースコード正本とする + +--- + +## 30. 初期リリースの非目標 + +以下は初期リリースで実装しない。 + +- コメント +- Search +- CMS +- User login +- Database +- SSR +- SPA +- API backend +- WebSocket +- Analytics +- Frontend framework +- htmx +- Tailwind CSS +- npm/Node.js +- External theme +- Heavy animation +- Large image gallery +- Multilingual UI + +--- + +## 31. 受入条件 + +初期リリースは以下を満たすこと。 + +- [ ] 「たかしの記録」というサイト名が表示される +- [ ] Home / Notes / Software / Aboutへ到達可能 +- [ ] Homeに最近のNotes最大5件を表示 +- [ ] HomeにSoftware最大4件を表示 +- [ ] Notesが年別に表示される +- [ ] Notes一覧にタグが表示される +- [ ] Note詳細に公開日・更新日・目次・本文を表示できる +- [ ] 脚注を利用できる +- [ ] 参考文献を自然に表示できる +- [ ] Software一覧を画像なしの簡潔な一覧として表示 +- [ ] Software詳細に背景・過程・言語・ライセンス・Source等を記載できる +- [ ] cgitをSource正本として案内できる +- [ ] GitHub mirrorを任意表示できる +- [ ] NoteとSoftwareを明示的に相互関連付けできる +- [ ] Aboutに名前・一言プロフィール・目的・関心分野・連絡先を表示できる +- [ ] Light/Dark双方で読める +- [ ] 外部リンクを視覚的に判別できる +- [ ] RSSとAtomを提供 +- [ ] 404ページを提供 +- [ ] Mobile/Desktop双方で利用可能 +- [ ] JavaScriptなしでも主要コンテンツを閲覧可能 +- [ ] 外部CDN・npm・Node.js・frontend frameworkを必要としない +- [ ] サイト全体が静的ファイルとして生成可能 + +--- + +## 32. 将来拡張 + +要件変更時に別途設計する。 + +- Search +- Comments +- SNS共有 +- より高度な関連コンテンツ表示 +- タグ数増加へのUI改善 +- Page pagination +- 多言語 +- 画像処理の高度化 + +初期実装で将来拡張のための複雑な抽象化を先行導入しない。 diff --git a/sass/_archive.scss b/sass/_archive.scss new file mode 100644 index 0000000..32318f5 --- /dev/null +++ b/sass/_archive.scss @@ -0,0 +1,90 @@ +.compact-list, +.archive, +.software-list-full { + border-top: var(--rule); +} + +.note-list-item { + display: grid; + grid-template-columns: 8rem minmax(0, 1fr); + gap: 1.5rem; + padding-block: 1.25rem; + border-bottom: var(--rule); +} + +.note-list-item time { + color: var(--color-muted); + font-size: 0.8rem; +} + +.note-list-item h3 { + margin-bottom: 0.25rem; +} + +.note-list-item h3 a, +.software-list-item h3 a { + color: var(--color-text); +} + +.note-list-item p, +.software-list-item p { + color: var(--color-muted); + font-size: 0.95rem; +} + +.tag-list { + display: flex; + flex-wrap: wrap; + gap: 0.3rem 0.8rem; + margin-top: 0.45rem; + list-style: none; + font-size: 0.75rem; +} + +.tag-list a { + color: var(--color-muted); +} + +.archive-year { + display: grid; + grid-template-columns: 7rem minmax(0, 1fr); + gap: 2rem; + padding-block: 2rem; + border-bottom: var(--rule); +} + +.archive-year > h2 { + font-family: var(--font-mono); + font-size: 1rem; + font-weight: 500; +} + +.archive-year .note-list-item:first-child { + padding-top: 0; +} + +.archive-year .note-list-item:last-child { + padding-bottom: 0; + border-bottom: 0; +} + +.empty-state { + padding-block: 2rem; + border-block: var(--rule); + color: var(--color-muted); +} + +@media (max-width: 40rem) { + .archive-year, + .note-list-item { + grid-template-columns: 1fr; + } + + .archive-year { + gap: 1rem; + } + + .note-list-item { + gap: 0.25rem; + } +} diff --git a/sass/_article.scss b/sass/_article.scss new file mode 100644 index 0000000..91a0ac4 --- /dev/null +++ b/sass/_article.scss @@ -0,0 +1,159 @@ +.article-header { + margin-bottom: 2.5rem; +} + +.article-header h1 { + margin-block: 0.7rem 1.25rem; +} + +.article-dates { + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1.5rem; + margin-bottom: 1.5rem; + color: var(--color-muted); + font-size: 0.78rem; +} + +.article-dates div { + display: flex; + gap: 0.65rem; +} + +.toc { + margin-bottom: 3.5rem; + padding: 1.25rem 1.5rem; + border-block: var(--rule); + background: var(--color-surface); +} + +.toc h2 { + margin-bottom: 0.6rem; + font-size: 0.95rem; +} + +.toc ul { + padding-left: 1.2rem; +} + +.toc > ul { + padding-left: 0; + list-style: none; +} + +.toc li + li { + margin-top: 0.25rem; +} + +.toc a { + font-size: 0.9rem; +} + +.prose { + font-size: 1.03rem; +} + +.prose > * + * { + margin-top: 1.45em; +} + +.prose h2, +.prose h3 { + scroll-margin-top: 2rem; +} + +.prose h2 { + margin-top: 3.2em; + padding-top: 0.8em; + border-top: var(--rule); +} + +.prose h3 { + margin-top: 2.3em; +} + +.prose .zola-anchor { + color: inherit; + text-decoration: none; +} + +.prose ul, +.prose ol { + padding-left: 1.5rem; +} + +.prose li + li { + margin-top: 0.35rem; +} + +.prose blockquote { + padding-left: 1.25rem; + border-left: 3px solid var(--color-border); + color: var(--color-muted); +} + +.prose table { + display: block; + width: max-content; + max-width: 100%; + overflow-x: auto; + border-collapse: collapse; +} + +.prose th, +.prose td { + padding: 0.5rem 0.75rem; + border: var(--rule); + text-align: left; +} + +.prose figure, +.prose img { + margin-inline: auto; +} + +.prose img { + height: auto; + border: var(--rule); +} + +.footnote-definition, +[role="doc-endnotes"] { + color: var(--color-muted); + font-size: 0.9rem; +} + +.footnote-definition { + display: grid; + grid-template-columns: auto 1fr; + gap: 0.5rem; +} + +.footnote-definition p { + margin: 0; +} + +.related-content { + margin-top: 4rem; + padding-top: 1.5rem; + border-top: var(--rule); +} + +.related-content h2 { + margin-bottom: 1rem; + font-size: 1.1rem; +} + +.related-content ul { + list-style: none; +} + +.related-content li + li { + margin-top: 0.75rem; +} + +.related-content span { + display: block; + color: var(--color-muted); + font-size: 0.9rem; +} diff --git a/sass/_base.scss b/sass/_base.scss new file mode 100644 index 0000000..d460c41 --- /dev/null +++ b/sass/_base.scss @@ -0,0 +1,47 @@ +body { + min-height: 100vh; + overflow-x: hidden; + overflow-wrap: anywhere; + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-sans); + font-size: 1rem; + line-height: 1.75; + text-rendering: optimizeLegibility; +} + +main, +article, +section, +div { + min-width: 0; +} + +a { + color: var(--color-link); + text-decoration-line: underline; + text-decoration-thickness: 0.075em; + text-underline-offset: 0.18em; +} + +a:hover { + color: var(--color-link-hover); +} + +:focus-visible { + outline: 3px solid var(--color-focus); + outline-offset: 3px; +} + +::selection { + background: var(--color-link); + color: var(--color-bg); +} + +.external-link::after { + display: inline-block; + margin-left: 0.2em; + content: "↗"; + font-size: 0.78em; + text-decoration: none; +} diff --git a/sass/_code.scss b/sass/_code.scss new file mode 100644 index 0000000..bc83892 --- /dev/null +++ b/sass/_code.scss @@ -0,0 +1,38 @@ +code, +pre { + font-family: var(--font-mono); +} + +.prose code:not(pre code) { + padding: 0.1em 0.3em; + border: var(--rule); + background: var(--color-surface); + font-size: 0.88em; +} + +.prose pre { + width: 100%; + max-width: 100%; + overflow-x: auto; + padding: 1rem 1.15rem; + border: var(--rule); + background: var(--color-surface); + font-size: 0.88rem; + line-height: 1.65; +} + +.giallo-l { + display: inline-block; + min-height: 1lh; + width: 100%; +} + +.giallo-ln { + display: inline-block; + min-width: 3ch; + margin-right: 0.4em; + padding: 0.4em; + opacity: 0.8; + text-align: right; + user-select: none; +} diff --git a/sass/_footer.scss b/sass/_footer.scss new file mode 100644 index 0000000..2d5c475 --- /dev/null +++ b/sass/_footer.scss @@ -0,0 +1,29 @@ +.site-footer { + margin-top: 4rem; + border-top: var(--rule); + color: var(--color-muted); + font-size: 0.85rem; +} + +.footer-inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; + min-height: 5rem; +} + +.footer-inner nav { + display: flex; + gap: 1rem; +} + +@media (max-width: 32rem) { + .footer-inner { + align-items: flex-start; + flex-direction: column; + justify-content: center; + gap: 0.3rem; + padding-block: 1rem; + } +} diff --git a/sass/_header.scss b/sass/_header.scss new file mode 100644 index 0000000..8512a55 --- /dev/null +++ b/sass/_header.scss @@ -0,0 +1,77 @@ +.site-header { + border-bottom: var(--rule); +} + +.header-inner { + display: flex; + align-items: center; + gap: 1.5rem; + min-height: 3.8rem; +} + +.site-title { + margin-right: auto; + color: var(--color-text); + font-weight: 700; + text-decoration: none; +} + +.nav-list { + display: flex; + gap: 1.2rem; + list-style: none; +} + +.nav-list a { + color: var(--color-muted); + font-size: 0.9rem; + text-decoration: none; +} + +.nav-list a:hover, +.nav-list a[aria-current="page"] { + color: var(--color-text); + text-decoration: underline; + text-underline-offset: 0.3em; +} + +.theme-toggle { + display: none; + padding: 0.2rem 0; + border: 0; + border-bottom: 1px solid currentColor; + background: transparent; + color: var(--color-muted); + cursor: pointer; + font-size: 0.75rem; +} + +.theme-ready .theme-toggle { + display: block; +} + +@media (max-width: 42rem) { + .header-inner { + flex-wrap: wrap; + gap: 0.55rem 1rem; + padding-block: 0.8rem; + } + + .site-title { + flex: 1 1 auto; + } + + .header-inner nav { + order: 3; + width: 100%; + } + + .nav-list { + flex-wrap: wrap; + gap: 0.4rem 1.25rem; + } + + .theme-toggle { + margin-left: auto; + } +} diff --git a/sass/_layout.scss b/sass/_layout.scss new file mode 100644 index 0000000..126fa96 --- /dev/null +++ b/sass/_layout.scss @@ -0,0 +1,106 @@ +.site-container { + width: min(calc(100% - 2rem), var(--site-max)); + max-width: calc(100% - 2rem); + margin-inline: auto; +} + +.site-main { + min-height: 65vh; +} + +.narrow, +.prose, +.related-content { + width: min(100%, var(--content-max)); + margin-inline: auto; +} + +.home-intro { + padding-block: clamp(4rem, 10vw, 7rem); + border-bottom: var(--rule); +} + +.home-intro h1 { + margin-bottom: 1rem; +} + +.home-intro p { + max-width: 38rem; +} + +.home-sections, +.listing-page, +.editorial-page, +.article-page { + padding-block: clamp(3.5rem, 8vw, 6rem); +} + +.home-sections { + display: grid; + gap: clamp(4rem, 9vw, 7rem); +} + +.section-heading { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1.1rem; + padding-bottom: 0.75rem; + border-bottom: var(--rule); +} + +.section-heading a { + white-space: nowrap; +} + +.home-about { + width: min(100%, var(--content-max)); + padding-top: 2rem; + border-top: var(--rule); +} + +.home-about h2, +.home-about p { + margin-bottom: 0.8rem; +} + +.page-header { + margin-bottom: clamp(2.5rem, 6vw, 4.5rem); +} + +.page-header h1 { + margin-bottom: 0.8rem; +} + +.editorial-page .page-header { + margin-bottom: 2.5rem; +} + +.error-page { + padding-block: clamp(5rem, 14vw, 10rem); +} + +.error-page .error-code { + margin-bottom: 1rem; + color: var(--color-muted); + font-size: 0.9rem; +} + +.error-page h1, +.error-page > p { + margin-bottom: 1rem; +} + +.error-page nav { + display: flex; + gap: 1.25rem; + margin-top: 2rem; +} + +@media (max-width: 38rem) { + .section-heading { + align-items: flex-start; + flex-direction: column; + } +} diff --git a/sass/_reset.scss b/sass/_reset.scss new file mode 100644 index 0000000..72924c4 --- /dev/null +++ b/sass/_reset.scss @@ -0,0 +1,41 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + overflow-x: hidden; +} + +body, +h1, +h2, +h3, +p, +ul, +ol, +dl, +dd, +figure, +blockquote { + margin: 0; +} + +ul, +ol { + padding: 0; +} + +img, +svg { + display: block; + max-width: 100%; +} + +button, +input, +textarea, +select { + font: inherit; +} diff --git a/sass/_software.scss b/sass/_software.scss new file mode 100644 index 0000000..4ad6e42 --- /dev/null +++ b/sass/_software.scss @@ -0,0 +1,70 @@ +.software-list-item { + padding-block: 1.3rem; + border-bottom: var(--rule); +} + +.software-list-item h3 { + margin-bottom: 0.25rem; + font-family: var(--font-mono); +} + +.software-list-full .software-list-item { + display: grid; + grid-template-columns: minmax(10rem, 0.6fr) minmax(0, 1.4fr); + gap: 2rem; +} + +.source-note { + margin-top: 1.5rem; + color: var(--color-muted); + font-size: 0.9rem; +} + +.software-meta { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-bottom: 3.5rem; + border-block: var(--rule); +} + +.software-meta div { + display: grid; + grid-template-columns: 7rem minmax(0, 1fr); + gap: 1rem; + padding-block: 0.75rem; + border-bottom: var(--rule); +} + +.software-meta div:nth-last-child(-n + 2) { + border-bottom: 0; +} + +.software-meta dt { + color: var(--color-muted); + font-size: 0.85rem; +} + +.software-meta dd { + font-family: var(--font-mono); + font-size: 0.82rem; +} + +@media (max-width: 42rem) { + .software-list-full .software-list-item, + .software-meta { + grid-template-columns: 1fr; + } + + .software-list-full .software-list-item { + gap: 0.3rem; + } + + .software-meta div, + .software-meta div:nth-last-child(-n + 2) { + border-bottom: var(--rule); + } + + .software-meta div:last-child { + border-bottom: 0; + } +} diff --git a/sass/_taxonomy.scss b/sass/_taxonomy.scss new file mode 100644 index 0000000..9f635bb --- /dev/null +++ b/sass/_taxonomy.scss @@ -0,0 +1,19 @@ +.taxonomy-list { + border-top: var(--rule); + list-style: none; +} + +.taxonomy-list li { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + padding-block: 0.85rem; + border-bottom: var(--rule); +} + +.taxonomy-list span { + color: var(--color-muted); + font-family: var(--font-mono); + font-size: 0.78rem; +} diff --git a/sass/_theme.scss b/sass/_theme.scss new file mode 100644 index 0000000..ad39b22 --- /dev/null +++ b/sass/_theme.scss @@ -0,0 +1,16 @@ +:root[data-theme="light"] body, +:root[data-theme="dark"] body { + background: var(--color-bg); + color: var(--color-text); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/sass/_tokens.scss b/sass/_tokens.scss new file mode 100644 index 0000000..a0ed8f3 --- /dev/null +++ b/sass/_tokens.scss @@ -0,0 +1,42 @@ +:root { + color-scheme: light; + --color-bg: #f6f5f1; + --color-surface: #fbfaf7; + --color-text: #202124; + --color-muted: #666b70; + --color-border: #d6d8da; + --color-link: #315f82; + --color-link-hover: #244a67; + --color-focus: #1e668f; + --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans JP", sans-serif; + --font-mono: ui-monospace, "Cascadia Code", "SFMono-Regular", Consolas, "Liberation Mono", monospace; + --content-max: 72ch; + --site-max: 72rem; + --rule: 1px solid var(--color-border); +} + +:root[data-theme="dark"] { + color-scheme: dark; + --color-bg: #17191b; + --color-surface: #1d2022; + --color-text: #e9e9e6; + --color-muted: #a0a3a6; + --color-border: #363a3e; + --color-link: #86b6d8; + --color-link-hover: #add3ed; + --color-focus: #86b6d8; +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + color-scheme: dark; + --color-bg: #17191b; + --color-surface: #1d2022; + --color-text: #e9e9e6; + --color-muted: #a0a3a6; + --color-border: #363a3e; + --color-link: #86b6d8; + --color-link-hover: #add3ed; + --color-focus: #86b6d8; + } +} diff --git a/sass/_typography.scss b/sass/_typography.scss new file mode 100644 index 0000000..4b11814 --- /dev/null +++ b/sass/_typography.scss @@ -0,0 +1,42 @@ +h1, +h2, +h3 { + overflow-wrap: anywhere; + color: var(--color-text); + font-weight: 650; + line-height: 1.3; + letter-spacing: -0.02em; +} + +h1 { + font-size: clamp(2rem, 5vw, 3.25rem); +} + +h2 { + font-size: clamp(1.35rem, 3vw, 1.7rem); +} + +h3 { + font-size: 1.08rem; +} + +time, +.article-kind, +.article-dates, +.theme-toggle, +.tag-list, +.error-code { + font-family: var(--font-mono); +} + +.article-kind { + font-size: 0.8rem; + letter-spacing: 0.06em; +} + +.article-description, +.page-header > p, +.home-intro > p { + color: var(--color-muted); + font-size: 1.08rem; +} diff --git a/sass/_utilities.scss b/sass/_utilities.scss new file mode 100644 index 0000000..0f9beef --- /dev/null +++ b/sass/_utilities.scss @@ -0,0 +1,25 @@ +.skip-link { + position: fixed; + z-index: 10; + top: 0.5rem; + left: 0.5rem; + padding: 0.45rem 0.7rem; + transform: translateY(-160%); + background: var(--color-text); + color: var(--color-bg); + font-weight: 700; +} + +.skip-link:focus { + transform: translateY(0); +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + clip-path: inset(50%); +} diff --git a/sass/main.scss b/sass/main.scss new file mode 100644 index 0000000..7735763 --- /dev/null +++ b/sass/main.scss @@ -0,0 +1,14 @@ +@import "tokens"; +@import "reset"; +@import "base"; +@import "typography"; +@import "layout"; +@import "header"; +@import "footer"; +@import "archive"; +@import "article"; +@import "software"; +@import "taxonomy"; +@import "code"; +@import "theme"; +@import "utilities"; diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..39493b6 --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title"> + <title id="title">たかしの記録</title> + <rect width="64" height="64" rx="6" fill="#202124"/> + <path d="M15 15h34v34H15z" fill="none" stroke="#86b6d8" stroke-width="3"/> + <path d="M24 23h16M24 31h16M24 39h11" stroke="#f6f5f1" stroke-width="3"/> +</svg> diff --git a/static/js/theme.js b/static/js/theme.js new file mode 100644 index 0000000..3d2b25c --- /dev/null +++ b/static/js/theme.js @@ -0,0 +1,75 @@ +(function () { + "use strict"; + + var root = document.documentElement; + root.classList.add("theme-ready"); + var toggle = document.querySelector("[data-theme-toggle]"); + var syntaxLight = document.getElementById("syntax-light"); + var syntaxDark = document.getElementById("syntax-dark"); + var media = window.matchMedia("(prefers-color-scheme: dark)"); + var storedTheme = null; + + try { + storedTheme = window.localStorage.getItem("theme"); + } catch (error) { + storedTheme = null; + } + + function activeTheme() { + if (root.dataset.theme === "light" || root.dataset.theme === "dark") { + return root.dataset.theme; + } + return media.matches ? "dark" : "light"; + } + + function syncSyntax(theme) { + if (!syntaxLight || !syntaxDark) { + return; + } + syntaxLight.media = theme === "light" ? "all" : "not all"; + syntaxDark.media = theme === "dark" ? "all" : "not all"; + } + + function updateToggle() { + if (!toggle) { + return; + } + var dark = activeTheme() === "dark"; + toggle.setAttribute("aria-pressed", String(dark)); + toggle.setAttribute("aria-label", dark ? "ライトテーマに切り替える" : "ダークテーマに切り替える"); + toggle.textContent = dark ? "Light" : "Dark"; + } + + function applyTheme(theme, persist) { + root.dataset.theme = theme; + syncSyntax(theme); + updateToggle(); + if (persist) { + try { + window.localStorage.setItem("theme", theme); + } catch (error) { + // Storageが利用できなくても、現在のpageではthemeを維持する。 + } + } + } + + if (storedTheme === "light" || storedTheme === "dark") { + applyTheme(storedTheme, false); + } else { + syncSyntax(activeTheme()); + updateToggle(); + } + + if (toggle) { + toggle.addEventListener("click", function () { + applyTheme(activeTheme() === "dark" ? "light" : "dark", true); + }); + } + + media.addEventListener("change", function () { + if (!root.dataset.theme) { + syncSyntax(activeTheme()); + updateToggle(); + } + }); +})(); diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..408e222 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block title %}404 | {{ config.title }}{% endblock title %} +{% block description %}ページが見つかりませんでした。{% endblock description %} + +{% block content %} +<section class="site-container error-page" aria-labelledby="error-title"> + <p class="error-code">404</p> + <h1 id="error-title">ページが見つかりません。</h1> + <p>URLが変更されたか、ページが存在しない可能性があります。</p> + <nav aria-label="エラーページからの移動"> + <a href="{{ get_url(path='@/_index.md') }}">Home</a> + <a href="{{ get_url(path='@/notes/_index.md') }}">Notes</a> + <a href="{{ get_url(path='@/software/_index.md') }}">Software</a> + </nav> +</section> +{% endblock content %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..543259a --- /dev/null +++ b/templates/base.html @@ -0,0 +1,26 @@ +<!doctype html> +<html lang="ja"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="color-scheme" content="light dark"> + <meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}"> + <title>{% block title %}{{ config.title }}{% endblock title %}</title> + <link rel="canonical" href="{{ current_url | default(value=config.base_url) }}"> + <link rel="icon" href="{{ get_url(path='favicon.svg') }}" type="image/svg+xml"> + <link rel="alternate" type="application/atom+xml" title="{{ config.title }} Atom" href="{{ get_url(path='atom.xml', trailing_slash=false) }}"> + <link rel="alternate" type="application/rss+xml" title="{{ config.title }} RSS" href="{{ get_url(path='rss.xml', trailing_slash=false) }}"> + <link id="syntax-light" rel="stylesheet" href="{{ get_url(path='giallo-light.css') }}" media="(prefers-color-scheme: light)"> + <link id="syntax-dark" rel="stylesheet" href="{{ get_url(path='giallo-dark.css') }}" media="(prefers-color-scheme: dark)"> + <link rel="stylesheet" href="{{ get_url(path='main.css') }}"> + <script src="{{ get_url(path='js/theme.js') }}" defer></script> +</head> +<body> + <a class="skip-link" href="#main-content">本文へ移動</a> + {% include "partials/header.html" %} + <main id="main-content" class="site-main" tabindex="-1"> + {% block content %}{% endblock content %} + </main> + {% include "partials/footer.html" %} +</body> +</html> diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..3f0c8c6 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} + +{% block title %}{{ config.title }}{% endblock title %} +{% block description %}{{ section.description }}{% endblock description %} + +{% block content %} +{% set about_url = get_url(path='@/about.md') %} +{% set notes_url = get_url(path='@/notes/_index.md') %} +{% set software_url = get_url(path='@/software/_index.md') %} +{% set software = get_section(path='software/_index.md') %} +{% set notes = get_section(path='notes/_index.md') %} +<section class="site-container home-intro" aria-labelledby="home-title"> + <h1 id="home-title">{{ section.title }}</h1> + <p>{{ section.description }}</p> +</section> + +<div class="site-container home-sections"> + <section class="home-section" aria-labelledby="latest-notes"> + <div class="section-heading"> + <h2 id="latest-notes">最近のNotes</h2> + <a href="{{ notes_url }}">すべてのNotes</a> + </div> + {% if notes.pages %} + <div class="note-list compact-list"> + {% for note in notes.pages %} + {% if loop.index <= config.extra.home_notes_limit %} + {% set item = note %} + {% include "partials/note-list-item.html" %} + {% endif %} + {% endfor %} + </div> + {% else %} + <p class="empty-state">ノートは準備中です。新しい記録はここに追加されます。</p> + {% endif %} + </section> + + <section class="home-section" aria-labelledby="home-software"> + <div class="section-heading"> + <h2 id="home-software">Software</h2> + <a href="{{ software_url }}">すべてのSoftware</a> + </div> + {% if software.pages %} + <div class="software-list compact-list"> + {% for software_item in software.pages %} + {% if loop.index <= config.extra.home_software_limit %} + {% set item = software_item %} + {% include "partials/software-list-item.html" %} + {% endif %} + {% endfor %} + </div> + {% else %} + <p class="empty-state">Softwareは準備中です。</p> + {% endif %} + </section> + + <section class="home-about" aria-labelledby="home-about-title"> + <h2 id="home-about-title">このサイトについて</h2> + <p>考えたこと、読んだこと、調べたことと、作成したソフトウェアを長期的に残すための個人アーカイブです。</p> + <a href="{{ about_url }}">Aboutを読む</a> + </section> +</div> +{% endblock content %} diff --git a/templates/notes/page.html b/templates/notes/page.html new file mode 100644 index 0000000..bcabff8 --- /dev/null +++ b/templates/notes/page.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %} +{% block description %}{{ page.description | default(value=config.description) }}{% endblock description %} + +{% block content %} +<article class="site-container article-page"> + <header class="article-header narrow"> + <p class="article-kind"><a href="{{ get_url(path='@/notes/_index.md') }}">Notes</a></p> + <h1>{{ page.title }}</h1> + <dl class="article-dates"> + <div><dt>公開</dt><dd><time datetime="{{ page.date }}">{{ page.date | date(format='%Y-%m-%d') }}</time></dd></div> + {% if page.updated %}<div><dt>更新</dt><dd><time datetime="{{ page.updated }}">{{ page.updated | date(format='%Y-%m-%d') }}</time></dd></div>{% endif %} + </dl> + {% if page.description %}<p class="article-description">{{ page.description }}</p>{% endif %} + </header> + + {% if page.toc %} + <nav class="toc narrow" aria-label="目次"> + <h2>目次</h2> + {{<toc.render headers={page.toc} />}} + </nav> + {% endif %} + + <div class="prose"> + {{ page.content | safe }} + </div> + + {% include "partials/related-software.html" %} +</article> +{% endblock content %} diff --git a/templates/notes/section.html b/templates/notes/section.html new file mode 100644 index 0000000..4fc2675 --- /dev/null +++ b/templates/notes/section.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block title %}{{ section.title }} | {{ config.title }}{% endblock title %} +{% block description %}{{ section.description }}{% endblock description %} + +{% block content %} +<div class="site-container listing-page"> + <header class="page-header"> + <h1>{{ section.title }}</h1> + <p>{{ section.description }}</p> + </header> + + {% if section.pages %} + <div class="archive"> + {% for year, pages in section.pages | group_by(attribute="year") %} + <section class="archive-year" aria-labelledby="year-{{ year }}"> + <h2 id="year-{{ year }}">{{ year }}</h2> + <div class="note-list"> + {% for note in pages %} + {% set item = note %} + {% include "partials/note-list-item.html" %} + {% endfor %} + </div> + </section> + {% endfor %} + </div> + {% else %} + <p class="empty-state">Notesはまだありません。</p> + {% endif %} +</div> +{% endblock content %} diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..cc0494e --- /dev/null +++ b/templates/page.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %} +{% block description %}{{ page.description | default(value=config.description) }}{% endblock description %} + +{% block content %} +<article class="site-container editorial-page"> + <header class="page-header narrow"> + <h1>{{ page.title }}</h1> + </header> + <div class="prose"> + {{ page.content | safe }} + </div> +</article> +{% endblock content %} diff --git a/templates/partials/footer.html b/templates/partials/footer.html new file mode 100644 index 0000000..a04136f --- /dev/null +++ b/templates/partials/footer.html @@ -0,0 +1,10 @@ +<footer class="site-footer"> + <div class="site-container footer-inner"> + <p>© {{ config.extra.copyright_year }} Takashi</p> + <nav aria-label="フッターナビゲーション"> + <a href="{{ get_url(path='atom.xml', trailing_slash=false) }}">Atom</a> + <a href="{{ get_url(path='rss.xml', trailing_slash=false) }}">RSS</a> + <a href="mailto:{{ config.extra.mail }}">Mail</a> + </nav> + </div> +</footer> diff --git a/templates/partials/header.html b/templates/partials/header.html new file mode 100644 index 0000000..dcd4ea2 --- /dev/null +++ b/templates/partials/header.html @@ -0,0 +1,19 @@ +{% set home_url = get_url(path='@/_index.md') %} +{% set about_url = get_url(path='@/about.md') %} +{% set software_url = get_url(path='@/software/_index.md') %} +{% set notes_url = get_url(path='@/notes/_index.md') %} +{% set active_path = current_path | default(value='') %} +<header class="site-header"> + <div class="site-container header-inner"> + <a class="site-title" href="{{ home_url }}">{{ config.title }}</a> + <nav aria-label="メインナビゲーション"> + <ul class="nav-list"> + <li><a href="{{ home_url }}"{% if active_path == '/' %} aria-current="page"{% endif %}>Home</a></li> + <li><a href="{{ notes_url }}"{% if '/notes/' in active_path %} aria-current="page"{% endif %}>Notes</a></li> + <li><a href="{{ software_url }}"{% if '/software/' in active_path %} aria-current="page"{% endif %}>Software</a></li> + <li><a href="{{ about_url }}"{% if active_path == '/about/' %} aria-current="page"{% endif %}>About</a></li> + </ul> + </nav> + <button class="theme-toggle" type="button" data-theme-toggle aria-pressed="false" aria-label="ダークテーマに切り替える">Dark</button> + </div> +</header> diff --git a/templates/partials/note-list-item.html b/templates/partials/note-list-item.html new file mode 100644 index 0000000..67ca8f7 --- /dev/null +++ b/templates/partials/note-list-item.html @@ -0,0 +1,14 @@ +<article class="note-list-item"> + <time datetime="{{ item.date }}">{{ item.date | date(format='%Y-%m-%d') }}</time> + <div class="list-item-main"> + <h3><a href="{{ item.permalink }}">{{ item.title }}</a></h3> + {% if item.description %}<p>{{ item.description }}</p>{% endif %} + {% if item.taxonomies.tags %} + <ul class="tag-list" aria-label="タグ"> + {% for tag in item.taxonomies.tags %} + <li><a href="{{ get_taxonomy_url(kind='tags', term=tag) }}">{{ tag }}</a></li> + {% endfor %} + </ul> + {% endif %} + </div> +</article> diff --git a/templates/partials/related-notes.html b/templates/partials/related-notes.html new file mode 100644 index 0000000..59cca8b --- /dev/null +++ b/templates/partials/related-notes.html @@ -0,0 +1,11 @@ +{% if page.extra.related_notes %} +<aside class="related-content" aria-labelledby="related-notes-title"> + <h2 id="related-notes-title">関連Notes</h2> + <ul> + {% for path in page.extra.related_notes %} + {% set related = get_page(path=path) %} + <li><a href="{{ related.permalink }}">{{ related.title }}</a>{% if related.description %}<span>{{ related.description }}</span>{% endif %}</li> + {% endfor %} + </ul> +</aside> +{% endif %} diff --git a/templates/partials/related-software.html b/templates/partials/related-software.html new file mode 100644 index 0000000..a29df4f --- /dev/null +++ b/templates/partials/related-software.html @@ -0,0 +1,11 @@ +{% if page.extra.related_software %} +<aside class="related-content" aria-labelledby="related-software-title"> + <h2 id="related-software-title">関連Software</h2> + <ul> + {% for path in page.extra.related_software %} + {% set related = get_page(path=path) %} + <li><a href="{{ related.permalink }}">{{ related.title }}</a>{% if related.description %}<span>{{ related.description }}</span>{% endif %}</li> + {% endfor %} + </ul> +</aside> +{% endif %} diff --git a/templates/partials/software-list-item.html b/templates/partials/software-list-item.html new file mode 100644 index 0000000..e6c970e --- /dev/null +++ b/templates/partials/software-list-item.html @@ -0,0 +1,4 @@ +<article class="software-list-item"> + <h3><a href="{{ item.permalink }}">{{ item.title }}</a></h3> + {% if item.description %}<p>{{ item.description }}</p>{% endif %} +</article> diff --git a/templates/partials/toc.html b/templates/partials/toc.html new file mode 100644 index 0000000..2089d15 --- /dev/null +++ b/templates/partials/toc.html @@ -0,0 +1,10 @@ +{% component toc.render(headers: array) %} +<ul> + {% for header in headers %} + <li> + <a href="{{ header.permalink | safe }}">{{ header.title }}</a> + {% if header.children %}{{<toc.render headers={header.children} />}}{% endif %} + </li> + {% endfor %} +</ul> +{% endcomponent toc.render %} diff --git a/templates/software/page.html b/templates/software/page.html new file mode 100644 index 0000000..513aee9 --- /dev/null +++ b/templates/software/page.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %} +{% block description %}{{ page.description | default(value=config.description) }}{% endblock description %} + +{% block content %} +<article class="site-container article-page software-page"> + <header class="article-header narrow"> + <p class="article-kind"><a href="{{ get_url(path='@/software/_index.md') }}">Software</a></p> + <h1>{{ page.title }}</h1> + {% if page.description %}<p class="article-description">{{ page.description }}</p>{% endif %} + </header> + + <dl class="software-meta narrow"> + {% if page.extra.language %}<div><dt>使用言語</dt><dd>{{ page.extra.language | join(sep=", ") }}</dd></div>{% endif %} + {% if page.extra.license %}<div><dt>ライセンス</dt><dd>{{ page.extra.license }}</dd></div>{% endif %} + <div><dt>Source</dt><dd><a class="external-link" href="{{ page.extra.source }}" rel="external">cgit</a></dd></div> + {% if page.extra.github_mirror %}<div><dt>Mirror</dt><dd><a class="external-link" href="{{ page.extra.github_mirror }}" rel="external">GitHub</a></dd></div>{% endif %} + </dl> + + <div class="prose"> + {{ page.content | safe }} + </div> + + {% include "partials/related-notes.html" %} +</article> +{% endblock content %} diff --git a/templates/software/section.html b/templates/software/section.html new file mode 100644 index 0000000..813f793 --- /dev/null +++ b/templates/software/section.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block title %}{{ section.title }} | {{ config.title }}{% endblock title %} +{% block description %}{{ section.description }}{% endblock description %} + +{% block content %} +<div class="site-container listing-page"> + <header class="page-header"> + <h1>{{ section.title }}</h1> + <p>{{ section.description }}</p> + </header> + {% if section.pages %} + <div class="software-list software-list-full"> + {% for software_item in section.pages %} + {% set item = software_item %} + {% include "partials/software-list-item.html" %} + {% endfor %} + </div> + {% else %} + <p class="empty-state">Softwareはまだありません。</p> + {% endif %} + <p class="source-note">ソースコードの正本は各Softwareから自前cgitへ案内します。</p> +</div> +{% endblock content %} diff --git a/templates/tags/list.html b/templates/tags/list.html new file mode 100644 index 0000000..6567506 --- /dev/null +++ b/templates/tags/list.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}タグ | {{ config.title }}{% endblock title %} +{% block description %}Notesを分類するタグの一覧。{% endblock description %} + +{% block content %} +<div class="site-container listing-page taxonomy-page"> + <header class="page-header"> + <h1>タグ</h1> + <p>主題を横断してNotesを探すための索引です。</p> + </header> + <ul class="taxonomy-list"> + {% for term in terms %} + <li><a href="{{ term.permalink }}">{{ term.name }}</a><span>{{ term.page_count }}件</span></li> + {% endfor %} + </ul> +</div> +{% endblock content %} diff --git a/templates/tags/single.html b/templates/tags/single.html new file mode 100644 index 0000000..2ec6fa1 --- /dev/null +++ b/templates/tags/single.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block title %}{{ term.name }} | タグ | {{ config.title }}{% endblock title %} +{% block description %}タグ「{{ term.name }}」の記事一覧。{% endblock description %} + +{% block content %} +<div class="site-container listing-page"> + <header class="page-header"> + <p class="article-kind"><a href="{{ get_url(path='tags', trailing_slash=true) }}">タグ</a></p> + <h1>{{ term.name }}</h1> + <p>{{ term.page_count }}件の記録</p> + </header> + <div class="note-list"> + {% for page in term.pages %} + {% set item = page %} + {% include "partials/note-list-item.html" %} + {% endfor %} + </div> +</div> +{% endblock content %} diff --git a/zola.toml b/zola.toml new file mode 100644 index 0000000..7e74e4c --- /dev/null +++ b/zola.toml @@ -0,0 +1,218 @@ +# Zola 0.23.3 configuration +# https://www.getzola.org/documentation/getting-started/configuration/ + +# ----------------------------------------------------------------------------- +# Main +# ----------------------------------------------------------------------------- + +# サイトの基準URL(唯一の必須項目) +base_url = "https://www.takashi-2521.com" + +# サイト全体のメタデータ。標準feedでも使用される +title = "たかしの記録" +description = "日々考えたこととか思ったことを記録します" +default_language = "ja" + +# 外部themeは使用しない。空文字でも指定扱いになるため未指定にする +# theme = "my-theme" + +# build成果物の出力先 +output_dir = "public" + +# 再build時に出力先直下のdotfileを保持するか +preserve_dotfiles_in_output = false + +# sass/内のSassをcompileする +compile_sass = true + +# 生成HTMLをminifyするか +minify_html = false + +# content/から除外するassetのglob +ignored_content = [] + +# content/内でTera処理を行わないfileのglob +skip_content_templating = [] + +# static/から除外するfileのglob +ignored_static = [] + +# サイト全体のAtom/RSS feedを生成する +generate_feeds = true + +# sitemapから除外するpagination範囲("none"または"all") +exclude_paginated_pages_in_sitemap = "none" + +# feedのfile名兼template名 +feed_filenames = ["atom.xml", "rss.xml"] + +# feedに含める記事数。未指定なら全件 +# feed_limit = 20 + +# static/および併置assetをcopyではなくhard linkするか +hard_link_static = false + +# Pageとfeedの既定author +author = "Takashi" + +# Notesを分類するtags taxonomy +taxonomies = [ + { name = "tags", feed = false }, +] + +# taxonomy URLの共通prefix。未指定ならサイトroot +# taxonomy_root = "blog" + +# 既定言語のsearch indexを生成するか +build_search_index = false + +# Zola標準のsitemap.xmlを生成する +generate_sitemap = true + +# Zola標準のrobots.txtを生成する +generate_robots_txt = true + +# ----------------------------------------------------------------------------- +# Markdown +# ----------------------------------------------------------------------------- + +[markdown] + +# :smile:などのemoji aliasをUnicodeへ変換するか +render_emoji = false + +# 外部linkへ付けるCSS class +external_links_class = "external-link" + +# 外部linkを新しいtabで開くか +external_links_target_blank = false + +# 外部linkへrel="nofollow"を付けるか +external_links_no_follow = false + +# 外部linkへrel="noreferrer"を付けるか +external_links_no_referrer = false + +# 外部linkへrel="external"を付けるか +external_links_external = true + +# 引用符や三点リーダーなどを組版向け文字へ変換する +smart_punctuation = false + +# definition list構文を有効にするか +definition_list = false + +# 画像へdecoding="async"とloading="lazy"を付けるか +lazy_async_image = true + +# footnoteを本文位置ではなく文末へまとめるか +bottom_footnotes = true + +# GitHub形式のalert/calloutを有効にするか +github_alerts = false + +# 見出しへanchor linkを挿入する位置("left"、"right"、"heading"、"none") +insert_anchor_links = "heading" + +# ----------------------------------------------------------------------------- +# Markdown syntax highlighting +# ----------------------------------------------------------------------------- + +[markdown.highlighting] + +# 未対応言語を含むcode blockをerrorにする +error_on_missing_language = true + +# inline styleではなくclassを出力し、厳格なCSPに対応する +style = "class" + +# 単一theme使用時のtheme名。light/darkとの同時指定はできないため未指定にする +# theme = "github-light" + +# OSのcolor schemeに対応する組み込みtheme +light_theme = "github-light" +dark_theme = "github-dark" + +# 追加するTextMate grammar/themeのJSON file +extra_grammars = [] +extra_themes = [] + +# code blockのdata属性を付ける要素("code"、"pre"、"both"、"none") +data_attr_position = "code" + +# dual themeの生成CSSへcolor-schemeを設定するか +add_color_scheme = true + +# ----------------------------------------------------------------------------- +# Link checker +# ----------------------------------------------------------------------------- + +[link_checker] + +# このprefixで始まる外部URLはlink確認を省略する +skip_prefixes = [ + "http://[2001:db8::]/", +] + +# このprefixで始まる外部URLはanchor確認を省略する +skip_anchor_prefixes = [ + "https://caniuse.com/", +] + +# 内部/外部link問題の扱い("error"または"warn") +internal_level = "error" +external_level = "warn" + +# ----------------------------------------------------------------------------- +# Slugification +# ----------------------------------------------------------------------------- + +[slugify] + +# path、taxonomy、anchorのslug化方式("on"、"safe"、"off") +paths = "on" +taxonomies = "on" +anchors = "on" + +# path slug生成時にfile名先頭の日付を保持するか +paths_keep_dates = false + +# ----------------------------------------------------------------------------- +# Search +# build_search_index=falseのため現時点では出力されないが、全項目を明示する +# ----------------------------------------------------------------------------- + +[search] + +include_title = true +include_description = false +include_date = false +include_path = false +include_content = true + +# indexへ含める本文の最大code point数。未指定なら切り詰めない +# truncate_content_length = 100 + +# "elasticlunr_javascript"、"elasticlunr_json"、"fuse_javascript"、"fuse_json" +index_format = "elasticlunr_javascript" + +# ----------------------------------------------------------------------------- +# Translations / Languages +# ----------------------------------------------------------------------------- + +# default_language向けの任意翻訳。現時点では使用しない +[translations] + +# 追加言語の設定。単一言語サイトのため現時点では使用しない +[languages] + +# ----------------------------------------------------------------------------- +# Custom site metadata +# ----------------------------------------------------------------------------- + +[extra] + +home_notes_limit = 5 +home_software_limit = 4 +copyright_year = 2026 +mail = "takashi@takashi-2521.com" |
