diff --git a/TODO.md b/TODO.md index fc1fe13..971c645 100644 --- a/TODO.md +++ b/TODO.md @@ -104,9 +104,13 @@ approved plan for full rationale on each decision referenced below. - [x] Series-nav (multi-part post series) — `writings/[...slug].astro`, uses the existing `extra.series`/`extra.series_index` fields -- [x] Table of contents — inline only, no separate sidebar layout yet (that's - a design-system concern); heading ids + `headings` metadata now flow - through `toml-content-loader.ts` so `render(entry)` exposes them +- [x] Table of contents — sticky sidebar on desktop (>=74rem), falls back to + the original inline list below the tags on narrower viewports; same + heading data rendered via a shared `TocList.astro` so the two aren't + duplicated markup by hand. `.container--wide` bumped 64rem → 76rem so + big monitors use more of the screen (prose column itself stays capped + at 42rem for readability — the extra width is the TOC gutter); + verified at 390/900/1180/1920px with no distortion at any width - [x] Prev/next post navigation - [x] Related posts (tag-based, tag-overlap ranked, limit 3) - [x] Per-tag descriptions on tag archive pages — `/tags/` + `/tags/[tag]/`, diff --git a/apps/site/src/components/TocList.astro b/apps/site/src/components/TocList.astro new file mode 100644 index 0000000..79b297e --- /dev/null +++ b/apps/site/src/components/TocList.astro @@ -0,0 +1,24 @@ +--- +interface Heading { + depth: number; + slug: string; + text: string; +} + +interface Props { + headings: Heading[]; +} + +const { headings } = Astro.props; +--- + +

Table of Contents

+ diff --git a/apps/site/src/pages/writings/[...slug].astro b/apps/site/src/pages/writings/[...slug].astro index bc9b7a6..42475d6 100644 --- a/apps/site/src/pages/writings/[...slug].astro +++ b/apps/site/src/pages/writings/[...slug].astro @@ -1,5 +1,6 @@ --- import { getCollection, render } from "astro:content"; +import TocList from "../../components/TocList.astro"; import Base from "../../layouts/Base.astro"; export async function getStaticPaths() { @@ -51,68 +52,105 @@ const description = const tocHeadings = headings.filter((h) => h.depth <= 3); --- - -

{entry.data.title}

-

- {entry.data.date.toDateString()} - {entry.data.readingMinutes && <> · {entry.data.readingMinutes} min read} - {entry.data.updated && <> · updated {entry.data.updated.toDateString()}} -

- {tags.map((tag) => #{tag})} + +
+
+

{entry.data.title}

+

+ {entry.data.date.toDateString()} + {entry.data.readingMinutes && <> · {entry.data.readingMinutes} min read} + {entry.data.updated && <> · updated {entry.data.updated.toDateString()}} +

+ {tags.map((tag) => #{tag})} - { - series && ( -
-
- -
- - - - { - related.length > 0 && ( -
-

Related Posts

- {related.map((w) => ( -
-

- {w.data.title} -

-
- ))} -
- ) - } + { + tocHeadings.length > 0 && ( + + ) + } +
+ + diff --git a/apps/site/src/styles/global.css b/apps/site/src/styles/global.css index 7fd6e2e..260a1b3 100644 --- a/apps/site/src/styles/global.css +++ b/apps/site/src/styles/global.css @@ -152,7 +152,41 @@ pre { } .container--wide { - max-width: 64rem; + max-width: 76rem; +} + +.toc { + font-family: var(--font-mono); + font-size: var(--font-size-sm); + margin-bottom: var(--space-5); +} + +.toc p.meta { + margin-bottom: var(--space-2); +} + +.toc ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.toc a { + text-decoration: none; + color: var(--text-muted); +} + +.toc a:hover { + color: var(--accent); +} + +.toc-sidebar { + padding-top: var(--space-1); + border-left: 1px solid var(--border); + padding-left: var(--space-4); } .eyebrow {