@aitrien/motifspec 1.13.0 → 1.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +275 -257
  2. package/LICENSE +21 -21
  3. package/README.md +128 -150
  4. package/dist/backdrop.html +1 -1
  5. package/dist/chroma.css +36 -36
  6. package/dist/codex.css +1145 -1145
  7. package/dist/codex.js +126 -126
  8. package/dist/contrast.md +101 -101
  9. package/dist/fonts.css +3 -3
  10. package/dist/icons.svg +18 -18
  11. package/dist/plate.html +1 -1
  12. package/dist/plate.js +292 -292
  13. package/dist/theme-init.js +1 -1
  14. package/dist/tokens.css +466 -466
  15. package/dist/tokens.js +3330 -3330
  16. package/dist/tokens.json +3330 -3330
  17. package/docs/components/_template.md +37 -37
  18. package/docs/components/accession-label.md +106 -106
  19. package/docs/components/backdrop.md +83 -83
  20. package/docs/components/button-and-link.md +87 -87
  21. package/docs/components/callout.md +75 -75
  22. package/docs/components/code-block.md +109 -109
  23. package/docs/components/data-table.md +73 -73
  24. package/docs/components/figure.md +68 -68
  25. package/docs/components/heading-anchor.md +76 -76
  26. package/docs/components/icon.md +63 -63
  27. package/docs/components/ornament.md +77 -77
  28. package/docs/components/pagination.md +78 -78
  29. package/docs/components/post-list-item.md +91 -91
  30. package/docs/components/project-card.md +82 -82
  31. package/docs/components/prose.md +122 -122
  32. package/docs/components/site-footer.md +99 -99
  33. package/docs/components/site-header.md +133 -133
  34. package/docs/components/specimen-plate.md +92 -92
  35. package/docs/components/table-of-contents.md +83 -83
  36. package/docs/components/tag.md +70 -70
  37. package/docs/components/tooltip.md +67 -67
  38. package/docs/hugo.md +132 -120
  39. package/docs/syntax-highlighting.md +55 -55
  40. package/docs/templates.md +21 -30
  41. package/fonts/OFL-jetbrains-mono.txt +93 -93
  42. package/fonts/OFL-national-park.txt +93 -93
  43. package/fonts/OFL-yarndings-12.txt +93 -93
  44. package/icons/LICENSE +202 -202
  45. package/icons/manifest.json +75 -75
  46. package/layouts/_markup/render-blockquote.html +30 -30
  47. package/layouts/_markup/render-codeblock.html +18 -18
  48. package/layouts/_markup/render-heading.html +2 -2
  49. package/layouts/_markup/render-image.html +14 -14
  50. package/layouts/_markup/render-link.html +14 -14
  51. package/layouts/_markup/render-table.html +31 -31
  52. package/layouts/_partials/motifspec/accession/id.html +2 -2
  53. package/layouts/_partials/motifspec/accession/label.html +18 -18
  54. package/layouts/_partials/motifspec/accession/status.html +2 -2
  55. package/layouts/_partials/motifspec/article.html +24 -24
  56. package/layouts/_partials/motifspec/back-link.html +2 -2
  57. package/layouts/_partials/motifspec/body-end.html +5 -5
  58. package/layouts/_partials/motifspec/body-start.html +5 -5
  59. package/layouts/_partials/motifspec/figure.html +14 -14
  60. package/layouts/_partials/motifspec/filed-under.html +5 -5
  61. package/layouts/_partials/motifspec/h2s.html +7 -7
  62. package/layouts/_partials/motifspec/head.html +15 -15
  63. package/layouts/_partials/motifspec/pagination-entries.html +34 -34
  64. package/layouts/_partials/motifspec/pagination-pages.html +30 -30
  65. package/layouts/_partials/motifspec/post-list-item.html +15 -15
  66. package/layouts/_partials/motifspec/project-card.html +23 -23
  67. package/layouts/_partials/motifspec/section-heading.html +3 -3
  68. package/layouts/_partials/motifspec/site-header.html +27 -27
  69. package/layouts/_partials/motifspec/table-of-contents.html +21 -21
  70. package/layouts/_partials/motifspec/tag-filter.html +22 -22
  71. package/layouts/_partials/motifspec/tags.html +8 -8
  72. package/layouts/_partials/motifspec/tokens.html +9 -9
  73. package/package.json +68 -68
  74. package/scripts/check.mjs +153 -153
  75. package/scripts/lib.mjs +176 -176
  76. package/tokens/README.md +3 -3
  77. package/tokens/border.json +63 -63
  78. package/tokens/color.json +1902 -1902
  79. package/tokens/motion.json +207 -207
  80. package/tokens/radius.json +53 -53
  81. package/tokens/size.json +378 -378
  82. package/tokens/space.json +289 -289
  83. package/tokens/typography.json +859 -859
@@ -1,99 +1,99 @@
1
- ---
2
- name: site-footer
3
- status: stable
4
- tokens:
5
- - border.width.hairline
6
- - color.border.subtle
7
- - color.link.hover
8
- - color.ornament.rest
9
- - color.surface.raised
10
- - color.text.muted
11
- - color.text.secondary
12
- - font.family.mono
13
- - font.family.ornament
14
- - font.line-height.display
15
- - font.line-height.ui
16
- - font.size.label
17
- - font.size.small
18
- - font.tracking.label
19
- - font.weight.ui
20
- - size.column.sidebar
21
- - size.measure.caption
22
- - size.ornament.sm
23
- - size.target.min
24
- - space.gutter.grid
25
- - space.inline.lg
26
- - space.inline.xs
27
- - space.inset.md
28
- - space.inset.xl
29
- - space.section.lg
30
- - space.stack.lg
31
- - space.stack.sm
32
- ---
33
-
34
- # Site footer
35
-
36
- The closing band of every page: a knitted ornament band, grouped links, a colophon and the legal line.
37
-
38
- ## When to use
39
-
40
- - On every page, below `<main>`.
41
- - To repeat the primary nav and add secondary destinations (RSS, recent work, source code).
42
-
43
- ## When not to use
44
-
45
- - For content a reader needs to finish the page. Put it in `<main>`.
46
- - For newsletter sign-ups or promotional blocks.
47
-
48
- ## Markup
49
-
50
- ```html preview
51
- <footer class="cx-footer">
52
- <span class="cx-band" aria-hidden="true"></span>
53
- <div class="cx-container">
54
- <div class="cx-footer__grid">
55
- <div>
56
- <h2>Pages</h2>
57
- <ul>
58
- <li><a href="/work/">Work</a></li>
59
- <li><a href="/writing/">Writing</a></li>
60
- <li><a href="/about/">About</a></li>
61
- <li><a href="/index.xml"><svg class="cx-icon cx-icon--sm" aria-hidden="true" focusable="false"><use href="#cx-icon-rss"></use></svg>RSS feed</a></li>
62
- </ul>
63
- </div>
64
- <div>
65
- <h2>Colophon</h2>
66
- <p>Set in National Park and JetBrains Mono, with ornaments from Yarndings 12. Built with Hugo.</p>
67
- </div>
68
- </div>
69
- </div>
70
- <div class="cx-container">
71
- <div class="cx-footer__base">
72
- <span>© 2026 Liam Marshall</span>
73
- <a class="cx-version" href="https://github.com/Aitrien/motifspec/blob/main/CHANGELOG.md" aria-label="Version 1.6.0: view changelog">MotifSpec v1.6.0<svg class="cx-icon cx-icon--sm" aria-hidden="true" focusable="false"><use href="#cx-icon-arrow-outward"></use></svg><span class="cx-tooltip cx-tooltip--above" aria-hidden="true">View changelog</span></a>
74
- <a href="#top">Back to top<svg class="cx-icon cx-icon--sm" aria-hidden="true" focusable="false"><use href="#cx-icon-arrow-upward"></use></svg></a>
75
- </div>
76
- </div>
77
- </footer>
78
- ```
79
-
80
- ## Do
81
-
82
- - Let the groups wrap. Each is an equal-width column: a link list needs half of `size.column.sidebar`, a paragraph (the colophon) a whole one. On a phone the link lists sit side by side in two columns and the colophon takes the full row below; with room, all of them share one row.
83
- - Keep two to four columns; the grid wraps on its own.
84
- - Use the footer band as the page's single ornament when the page has no other.
85
- - On a backdrop page, let the footer sit on the frost: `cx-backdrop-page` removes its raised surface, and the bottom fade starts above it.
86
- - Put the MotifSpec version in the base row on sites that consume it, as a `cx-version` link to the changelog (`changelog` in `package.json`, also served in `dist/tokens.json`). Its tooltip opens above (`cx-tooltip--above`) because it sits at the bottom of the page.
87
-
88
- ## Don't
89
-
90
- - Don't add a second ornament band elsewhere on the same page.
91
- - Don't put social icons in place of text links. Name each destination.
92
- - Mark links that leave the site with `arrow-outward` after the text; the only other footer icons are `rss` and `arrow-upward`.
93
-
94
- ## Accessibility
95
-
96
- - `<footer>` is the page's contentinfo landmark; column headings are `<h2>` so they appear in the headings list.
97
- - The band is `aria-hidden="true"` and has no text alternative because it carries no meaning.
98
- - Links are at least 24px tall (`size.target.min`).
99
- - Pairs: `color.text.secondary` on `color.surface.raised` (7:1); `color.text.muted` on `color.surface.raised` (4.5:1, labels only). On a backdrop page the frost is measured as `color.surface.base`; the contour lines beneath it are not part of the check.
1
+ ---
2
+ name: site-footer
3
+ status: stable
4
+ tokens:
5
+ - border.width.hairline
6
+ - color.border.subtle
7
+ - color.link.hover
8
+ - color.ornament.rest
9
+ - color.surface.raised
10
+ - color.text.muted
11
+ - color.text.secondary
12
+ - font.family.mono
13
+ - font.family.ornament
14
+ - font.line-height.display
15
+ - font.line-height.ui
16
+ - font.size.label
17
+ - font.size.small
18
+ - font.tracking.label
19
+ - font.weight.ui
20
+ - size.column.sidebar
21
+ - size.measure.caption
22
+ - size.ornament.sm
23
+ - size.target.min
24
+ - space.gutter.grid
25
+ - space.inline.lg
26
+ - space.inline.xs
27
+ - space.inset.md
28
+ - space.inset.xl
29
+ - space.section.lg
30
+ - space.stack.lg
31
+ - space.stack.sm
32
+ ---
33
+
34
+ # Site footer
35
+
36
+ The closing band of every page: a knitted ornament band, grouped links, a colophon and the legal line.
37
+
38
+ ## When to use
39
+
40
+ - On every page, below `<main>`.
41
+ - To repeat the primary nav and add secondary destinations (RSS, recent work, source code).
42
+
43
+ ## When not to use
44
+
45
+ - For content a reader needs to finish the page. Put it in `<main>`.
46
+ - For newsletter sign-ups or promotional blocks.
47
+
48
+ ## Markup
49
+
50
+ ```html preview
51
+ <footer class="cx-footer">
52
+ <span class="cx-band" aria-hidden="true"></span>
53
+ <div class="cx-container">
54
+ <div class="cx-footer__grid">
55
+ <div>
56
+ <h2>Pages</h2>
57
+ <ul>
58
+ <li><a href="/work/">Work</a></li>
59
+ <li><a href="/writing/">Writing</a></li>
60
+ <li><a href="/about/">About</a></li>
61
+ <li><a href="/index.xml"><svg class="cx-icon cx-icon--sm" aria-hidden="true" focusable="false"><use href="#cx-icon-rss"></use></svg>RSS feed</a></li>
62
+ </ul>
63
+ </div>
64
+ <div>
65
+ <h2>Colophon</h2>
66
+ <p>Set in National Park and JetBrains Mono, with ornaments from Yarndings 12. Built with Hugo.</p>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ <div class="cx-container">
71
+ <div class="cx-footer__base">
72
+ <span>© 2026 Liam Marshall</span>
73
+ <a class="cx-version" href="https://github.com/Aitrien/motifspec/blob/main/CHANGELOG.md" aria-label="Version 1.6.0: view changelog">MotifSpec v1.6.0<svg class="cx-icon cx-icon--sm" aria-hidden="true" focusable="false"><use href="#cx-icon-arrow-outward"></use></svg><span class="cx-tooltip cx-tooltip--above" aria-hidden="true">View changelog</span></a>
74
+ <a href="#top">Back to top<svg class="cx-icon cx-icon--sm" aria-hidden="true" focusable="false"><use href="#cx-icon-arrow-upward"></use></svg></a>
75
+ </div>
76
+ </div>
77
+ </footer>
78
+ ```
79
+
80
+ ## Do
81
+
82
+ - Let the groups wrap. Each is an equal-width column: a link list needs half of `size.column.sidebar`, a paragraph (the colophon) a whole one. On a phone the link lists sit side by side in two columns and the colophon takes the full row below; with room, all of them share one row.
83
+ - Keep two to four columns; the grid wraps on its own.
84
+ - Use the footer band as the page's single ornament when the page has no other.
85
+ - On a backdrop page, let the footer sit on the frost: `cx-backdrop-page` removes its raised surface, and the bottom fade starts above it.
86
+ - Put the MotifSpec version in the base row on sites that consume it, as a `cx-version` link to the changelog (`changelog` in `package.json`, also served in `dist/tokens.json`). Its tooltip opens above (`cx-tooltip--above`) because it sits at the bottom of the page.
87
+
88
+ ## Don't
89
+
90
+ - Don't add a second ornament band elsewhere on the same page.
91
+ - Don't put social icons in place of text links. Name each destination.
92
+ - Mark links that leave the site with `arrow-outward` after the text; the only other footer icons are `rss` and `arrow-upward`.
93
+
94
+ ## Accessibility
95
+
96
+ - `<footer>` is the page's contentinfo landmark; column headings are `<h2>` so they appear in the headings list.
97
+ - The band is `aria-hidden="true"` and has no text alternative because it carries no meaning.
98
+ - Links are at least 24px tall (`size.target.min`).
99
+ - Pairs: `color.text.secondary` on `color.surface.raised` (7:1); `color.text.muted` on `color.surface.raised` (4.5:1, labels only). On a backdrop page the frost is measured as `color.surface.base`; the contour lines beneath it are not part of the check.
@@ -1,133 +1,133 @@
1
- ---
2
- name: site-header
3
- status: stable
4
- tokens:
5
- - border.focus.offset
6
- - border.focus.width
7
- - border.width.hairline
8
- - color.accent.border
9
- - color.accent.fill.rest
10
- - color.border.subtle
11
- - color.focus.ring
12
- - color.surface.base
13
- - color.surface.highlight
14
- - color.text.muted
15
- - color.text.on-accent
16
- - color.text.primary
17
- - color.text.secondary
18
- - color.wash.hover
19
- - font.family.display
20
- - font.family.mono
21
- - font.line-height.ui
22
- - font.size.heading-4
23
- - font.size.label
24
- - font.tracking.heading
25
- - font.tracking.label
26
- - font.weight.display
27
- - font.weight.strong
28
- - font.weight.ui
29
- - motion.duration.fast
30
- - motion.duration.slow
31
- - motion.easing.standard
32
- - radius.pill
33
- - size.container.page
34
- - size.target.touch
35
- - space.gutter.page
36
- - space.inline.lg
37
- - space.inline.sm
38
- - space.inline.xs
39
- - space.inset.md
40
- - space.inset.sm
41
- - space.inset.xs
42
- ---
43
-
44
- # Site header and nav
45
-
46
- The bar at the top of every page: wordmark, primary nav, the backdrop pause control and the theme toggle. On a backdrop page (`backdrop.md`) it sits on the frost and scrolls away with the page; elsewhere it is sticky.
47
-
48
- ## When to use
49
-
50
- - On every page of a site, including the 404, so a reader is never stranded.
51
- - For three to five primary destinations with plain, functional labels (Work, Writing, About).
52
-
53
- ## When not to use
54
-
55
- - For in-page navigation; use `table-of-contents`.
56
- - For more than five links. Move the rest to the footer.
57
- - For calls to action. The header holds destinations, not buttons.
58
-
59
- ## Markup
60
-
61
- ```html preview
62
- <a class="cx-skip-link" href="#main">Skip to content</a>
63
- <header class="cx-header">
64
- <div class="cx-container cx-header__inner">
65
- <a class="cx-wordmark" href="/">Liam Marshall <span class="cx-wordmark__role">Web engineer</span></a>
66
- <div class="cx-header__end">
67
- <nav class="cx-nav" aria-label="Main">
68
- <ul>
69
- <li><a href="/work/" aria-current="page">Work</a></li>
70
- <li><a href="/writing/">Writing</a></li>
71
- <li><a href="/about/">About</a></li>
72
- </ul>
73
- </nav>
74
- <label class="cx-backdrop__pause" hidden>
75
- <input class="cx-visually-hidden" type="checkbox" aria-label="Pause background animation">
76
- <svg class="cx-icon" data-when="running" aria-hidden="true" focusable="false"><use href="#cx-icon-pause"></use></svg>
77
- <svg class="cx-icon" data-when="paused" aria-hidden="true" focusable="false"><use href="#cx-icon-play"></use></svg>
78
- <span class="cx-tooltip" aria-hidden="true"><span data-when="running">Pause background</span><span data-when="paused">Play background</span></span>
79
- </label>
80
- <button class="cx-theme-toggle" type="button" data-theme-toggle hidden aria-label="Switch to dark theme">
81
- <svg class="cx-icon" data-icon="to-dark" aria-hidden="true" focusable="false"><use href="#cx-icon-theme-dark"></use></svg>
82
- <svg class="cx-icon" data-icon="to-light" aria-hidden="true" focusable="false"><use href="#cx-icon-theme-light"></use></svg>
83
- <span class="cx-tooltip" aria-hidden="true">Switch to dark theme</span>
84
- </button>
85
- </div>
86
- </div>
87
- </header>
88
- ```
89
-
90
- ### Scripts
91
-
92
- The toggle needs two scripts, both shipped in `dist/`. The first is a one-line snippet (`dist/theme-init.js`) inlined in `<head>` before the stylesheet, so a stored choice is on `<html>` before the first paint and the page never flashes the wrong theme. The second is `dist/codex.js`, loaded with `defer`, which reveals the toggle and wires it up (it also sets the header's scroll state on backdrop pages, and runs copy buttons, tooltip dismissal and the table of contents).
93
-
94
- ```html
95
- <head>
96
- <script>try{var t=localStorage.getItem('cx-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t)}catch(e){}</script>
97
- <link rel="stylesheet" href="/motifspec/codex.css">
98
- <script src="/motifspec/codex.js" defer></script>
99
- </head>
100
- ```
101
-
102
- Copy the snippet as it is: the build fails if this doc and `js/theme-init.js` differ. It reads the same `cx-theme` key that `codex.js` writes (`light` or `dark`; no key means follow the system), and does nothing when storage is blocked. In Hugo, inline it from the mounted file: `{{ with resources.Get "motifspec/theme-init.js" }}<script>{{ .Content | safeJS }}</script>{{ end }}`. If your Content-Security-Policy blocks inline scripts, allow this one by its hash.
103
-
104
- ## Do
105
-
106
- - Mark the current section with `aria-current="page"`, including on child pages (a post marks Writing). It gets a `color.surface.highlight` background with a `color.accent.border` hairline inside its edge, not an underline. The outline is what stands out from the header (3:1); the fill alone is close to `color.surface.base` in both themes.
107
- - Nav items are pills (`radius.pill`), the same shape as the pause control and the theme toggle, so every header control reads as one set.
108
- - When the nav and controls wrap below the wordmark, `dist/codex.js` sets `data-wrapped` on the header: the nav moves to the start of its row and is offset by its pills' padding, so the link text lines up with the wordmark and the page text, while the controls stay at the end. Without the script the row stays right-aligned.
109
- - Every header control rests in `color.text.secondary` and shares one hover, shown only where the pointer can hover (`@media (hover: hover)`), so it never sticks after a tap: `color.text.primary` text on a wash of `color.text.primary` at `color.wash.hover`. A solid `color.surface.hover` would all but match the frost.
110
- - Include the backdrop pause control on every backdrop page, between the nav and the theme toggle. It ships `hidden` and `dist/plate.js` reveals it when the drift starts; it never appears on a page without the backdrop. The choice is kept like the theme: `dist/plate.js` stores `paused` under `cx-backdrop` and restores it on every page.
111
- - On a backdrop page, let the header start on the frost. `dist/codex.js` sets `data-scrolled` on it: from then on it is sticky, and as soon as the page leaves the top (`"true"`) its `color.surface.base` surface fades in over `motion.duration.slow`. Back at the top (`"false"`) the surface fades out and the frost shows through again. Without the script the header has no surface and scrolls away with the page.
112
- - Keep labels to one functional word each.
113
- - Ship the toggle with `hidden` and let the script reveal it, so no dead control appears without JavaScript.
114
- - Make the toggle an icon-only circle that shows the action, not the state: a moon while light is showing, a sun while dark is showing. Its tooltip says the same ("Switch to dark theme").
115
- - Each click flips the theme you see. If the result matches the system setting, clear the stored choice (`auto`) so the page follows the system again; otherwise store `light` or `dark` on `data-theme`.
116
- - Include both icons. CSS picks the right one from `data-theme` and `prefers-color-scheme`, so the script only updates the label.
117
-
118
- ## Don't
119
-
120
- - Don't hide the nav behind a menu button at small widths. The list scrolls inside its row instead.
121
- - Don't add icons or ornaments to nav items. The pause control and the theme toggle are the only header controls with icons.
122
- - Don't make the header sticky on a backdrop page without its surface; see-through, it would sit on the text below it. The `data-scrolled` rules keep the two together.
123
- - Don't animate the header on scroll beyond that surface fade (no shrink, hide or reveal).
124
-
125
- ## Accessibility
126
-
127
- - The skip link is the first focusable element and targets `<main id="main">`.
128
- - `<nav aria-label="Main">`; only one nav per page uses that label.
129
- - Nav links, the pause control and the toggle are 44px (`size.target.touch`); the pause control and the toggle are pills with equal sides, so they render as circles.
130
- - The pause control is a native checkbox named "Pause background animation" (checked means paused), focusable by keyboard, with its focus ring on the round control.
131
- - The toggle's `aria-label` is its action ("Switch to light theme"); the visible tooltip repeats it and is `aria-hidden`. Escape hides the tooltip without moving the pointer (see `tooltip.md`).
132
- - `aria-current` carries the current state for assistive tech; the highlight is the visual cue.
133
- - Pairs: `color.text.secondary` and `color.text.primary` on `color.surface.base` (7:1); `color.text.primary` on `color.surface.highlight` (current, 7:1); `color.accent.border` on `color.surface.base` (current outline, 3:1). On a backdrop page the frost is measured as `color.surface.base`; the contour lines beneath it are not part of the check.
1
+ ---
2
+ name: site-header
3
+ status: stable
4
+ tokens:
5
+ - border.focus.offset
6
+ - border.focus.width
7
+ - border.width.hairline
8
+ - color.accent.border
9
+ - color.accent.fill.rest
10
+ - color.border.subtle
11
+ - color.focus.ring
12
+ - color.surface.base
13
+ - color.surface.highlight
14
+ - color.text.muted
15
+ - color.text.on-accent
16
+ - color.text.primary
17
+ - color.text.secondary
18
+ - color.wash.hover
19
+ - font.family.display
20
+ - font.family.mono
21
+ - font.line-height.ui
22
+ - font.size.heading-4
23
+ - font.size.label
24
+ - font.tracking.heading
25
+ - font.tracking.label
26
+ - font.weight.display
27
+ - font.weight.strong
28
+ - font.weight.ui
29
+ - motion.duration.fast
30
+ - motion.duration.slow
31
+ - motion.easing.standard
32
+ - radius.pill
33
+ - size.container.page
34
+ - size.target.touch
35
+ - space.gutter.page
36
+ - space.inline.lg
37
+ - space.inline.sm
38
+ - space.inline.xs
39
+ - space.inset.md
40
+ - space.inset.sm
41
+ - space.inset.xs
42
+ ---
43
+
44
+ # Site header and nav
45
+
46
+ The bar at the top of every page: wordmark, primary nav, the backdrop pause control and the theme toggle. On a backdrop page (`backdrop.md`) it sits on the frost and scrolls away with the page; elsewhere it is sticky.
47
+
48
+ ## When to use
49
+
50
+ - On every page of a site, including the 404, so a reader is never stranded.
51
+ - For three to five primary destinations with plain, functional labels (Work, Writing, About).
52
+
53
+ ## When not to use
54
+
55
+ - For in-page navigation; use `table-of-contents`.
56
+ - For more than five links. Move the rest to the footer.
57
+ - For calls to action. The header holds destinations, not buttons.
58
+
59
+ ## Markup
60
+
61
+ ```html preview
62
+ <a class="cx-skip-link" href="#main">Skip to content</a>
63
+ <header class="cx-header">
64
+ <div class="cx-container cx-header__inner">
65
+ <a class="cx-wordmark" href="/">Liam Marshall <span class="cx-wordmark__role">Web engineer</span></a>
66
+ <div class="cx-header__end">
67
+ <nav class="cx-nav" aria-label="Main">
68
+ <ul>
69
+ <li><a href="/work/" aria-current="page">Work</a></li>
70
+ <li><a href="/writing/">Writing</a></li>
71
+ <li><a href="/about/">About</a></li>
72
+ </ul>
73
+ </nav>
74
+ <label class="cx-backdrop__pause" hidden>
75
+ <input class="cx-visually-hidden" type="checkbox" aria-label="Pause background animation">
76
+ <svg class="cx-icon" data-when="running" aria-hidden="true" focusable="false"><use href="#cx-icon-pause"></use></svg>
77
+ <svg class="cx-icon" data-when="paused" aria-hidden="true" focusable="false"><use href="#cx-icon-play"></use></svg>
78
+ <span class="cx-tooltip" aria-hidden="true"><span data-when="running">Pause background</span><span data-when="paused">Play background</span></span>
79
+ </label>
80
+ <button class="cx-theme-toggle" type="button" data-theme-toggle hidden aria-label="Switch to dark theme">
81
+ <svg class="cx-icon" data-icon="to-dark" aria-hidden="true" focusable="false"><use href="#cx-icon-theme-dark"></use></svg>
82
+ <svg class="cx-icon" data-icon="to-light" aria-hidden="true" focusable="false"><use href="#cx-icon-theme-light"></use></svg>
83
+ <span class="cx-tooltip" aria-hidden="true">Switch to dark theme</span>
84
+ </button>
85
+ </div>
86
+ </div>
87
+ </header>
88
+ ```
89
+
90
+ ### Scripts
91
+
92
+ The toggle needs two scripts, both shipped in `dist/`. The first is a one-line snippet (`dist/theme-init.js`) inlined in `<head>` before the stylesheet, so a stored choice is on `<html>` before the first paint and the page never flashes the wrong theme. The second is `dist/codex.js`, loaded with `defer`, which reveals the toggle and wires it up (it also sets the header's scroll state on backdrop pages, and runs copy buttons, tooltip dismissal and the table of contents).
93
+
94
+ ```html
95
+ <head>
96
+ <script>try{var t=localStorage.getItem('cx-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t)}catch(e){}</script>
97
+ <link rel="stylesheet" href="/motifspec/codex.css">
98
+ <script src="/motifspec/codex.js" defer></script>
99
+ </head>
100
+ ```
101
+
102
+ Copy the snippet as it is: the build fails if this doc and `js/theme-init.js` differ. It reads the same `cx-theme` key that `codex.js` writes (`light` or `dark`; no key means follow the system), and does nothing when storage is blocked. In Hugo, inline it from the mounted file: `{{ with resources.Get "motifspec/theme-init.js" }}<script>{{ .Content | safeJS }}</script>{{ end }}`. If your Content-Security-Policy blocks inline scripts, allow this one by its hash.
103
+
104
+ ## Do
105
+
106
+ - Mark the current section with `aria-current="page"`, including on child pages (a post marks Writing). It gets a `color.surface.highlight` background with a `color.accent.border` hairline inside its edge, not an underline. The outline is what stands out from the header (3:1); the fill alone is close to `color.surface.base` in both themes.
107
+ - Nav items are pills (`radius.pill`), the same shape as the pause control and the theme toggle, so every header control reads as one set.
108
+ - When the nav and controls wrap below the wordmark, `dist/codex.js` sets `data-wrapped` on the header: the nav moves to the start of its row and is offset by its pills' padding, so the link text lines up with the wordmark and the page text, while the controls stay at the end. Without the script the row stays right-aligned.
109
+ - Every header control rests in `color.text.secondary` and shares one hover, shown only where the pointer can hover (`@media (hover: hover)`), so it never sticks after a tap: `color.text.primary` text on a wash of `color.text.primary` at `color.wash.hover`. A solid `color.surface.hover` would all but match the frost.
110
+ - Include the backdrop pause control on every backdrop page, between the nav and the theme toggle. It ships `hidden` and `dist/plate.js` reveals it when the drift starts; it never appears on a page without the backdrop. The choice is kept like the theme: `dist/plate.js` stores `paused` under `cx-backdrop` and restores it on every page.
111
+ - On a backdrop page, let the header start on the frost. `dist/codex.js` sets `data-scrolled` on it: from then on it is sticky, and as soon as the page leaves the top (`"true"`) its `color.surface.base` surface fades in over `motion.duration.slow`. Back at the top (`"false"`) the surface fades out and the frost shows through again. Without the script the header has no surface and scrolls away with the page.
112
+ - Keep labels to one functional word each.
113
+ - Ship the toggle with `hidden` and let the script reveal it, so no dead control appears without JavaScript.
114
+ - Make the toggle an icon-only circle that shows the action, not the state: a moon while light is showing, a sun while dark is showing. Its tooltip says the same ("Switch to dark theme").
115
+ - Each click flips the theme you see. If the result matches the system setting, clear the stored choice (`auto`) so the page follows the system again; otherwise store `light` or `dark` on `data-theme`.
116
+ - Include both icons. CSS picks the right one from `data-theme` and `prefers-color-scheme`, so the script only updates the label.
117
+
118
+ ## Don't
119
+
120
+ - Don't hide the nav behind a menu button at small widths. The list scrolls inside its row instead.
121
+ - Don't add icons or ornaments to nav items. The pause control and the theme toggle are the only header controls with icons.
122
+ - Don't make the header sticky on a backdrop page without its surface; see-through, it would sit on the text below it. The `data-scrolled` rules keep the two together.
123
+ - Don't animate the header on scroll beyond that surface fade (no shrink, hide or reveal).
124
+
125
+ ## Accessibility
126
+
127
+ - The skip link is the first focusable element and targets `<main id="main">`.
128
+ - `<nav aria-label="Main">`; only one nav per page uses that label.
129
+ - Nav links, the pause control and the toggle are 44px (`size.target.touch`); the pause control and the toggle are pills with equal sides, so they render as circles.
130
+ - The pause control is a native checkbox named "Pause background animation" (checked means paused), focusable by keyboard, with its focus ring on the round control.
131
+ - The toggle's `aria-label` is its action ("Switch to light theme"); the visible tooltip repeats it and is `aria-hidden`. Escape hides the tooltip without moving the pointer (see `tooltip.md`).
132
+ - `aria-current` carries the current state for assistive tech; the highlight is the visual cue.
133
+ - Pairs: `color.text.secondary` and `color.text.primary` on `color.surface.base` (7:1); `color.text.primary` on `color.surface.highlight` (current, 7:1); `color.accent.border` on `color.surface.base` (current outline, 3:1). On a backdrop page the frost is measured as `color.surface.base`; the contour lines beneath it are not part of the check.