@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.
- package/CHANGELOG.md +275 -257
- package/LICENSE +21 -21
- package/README.md +128 -150
- package/dist/backdrop.html +1 -1
- package/dist/chroma.css +36 -36
- package/dist/codex.css +1145 -1145
- package/dist/codex.js +126 -126
- package/dist/contrast.md +101 -101
- package/dist/fonts.css +3 -3
- package/dist/icons.svg +18 -18
- package/dist/plate.html +1 -1
- package/dist/plate.js +292 -292
- package/dist/theme-init.js +1 -1
- package/dist/tokens.css +466 -466
- package/dist/tokens.js +3330 -3330
- package/dist/tokens.json +3330 -3330
- package/docs/components/_template.md +37 -37
- package/docs/components/accession-label.md +106 -106
- package/docs/components/backdrop.md +83 -83
- package/docs/components/button-and-link.md +87 -87
- package/docs/components/callout.md +75 -75
- package/docs/components/code-block.md +109 -109
- package/docs/components/data-table.md +73 -73
- package/docs/components/figure.md +68 -68
- package/docs/components/heading-anchor.md +76 -76
- package/docs/components/icon.md +63 -63
- package/docs/components/ornament.md +77 -77
- package/docs/components/pagination.md +78 -78
- package/docs/components/post-list-item.md +91 -91
- package/docs/components/project-card.md +82 -82
- package/docs/components/prose.md +122 -122
- package/docs/components/site-footer.md +99 -99
- package/docs/components/site-header.md +133 -133
- package/docs/components/specimen-plate.md +92 -92
- package/docs/components/table-of-contents.md +83 -83
- package/docs/components/tag.md +70 -70
- package/docs/components/tooltip.md +67 -67
- package/docs/hugo.md +132 -120
- package/docs/syntax-highlighting.md +55 -55
- package/docs/templates.md +21 -30
- package/fonts/OFL-jetbrains-mono.txt +93 -93
- package/fonts/OFL-national-park.txt +93 -93
- package/fonts/OFL-yarndings-12.txt +93 -93
- package/icons/LICENSE +202 -202
- package/icons/manifest.json +75 -75
- package/layouts/_markup/render-blockquote.html +30 -30
- package/layouts/_markup/render-codeblock.html +18 -18
- package/layouts/_markup/render-heading.html +2 -2
- package/layouts/_markup/render-image.html +14 -14
- package/layouts/_markup/render-link.html +14 -14
- package/layouts/_markup/render-table.html +31 -31
- package/layouts/_partials/motifspec/accession/id.html +2 -2
- package/layouts/_partials/motifspec/accession/label.html +18 -18
- package/layouts/_partials/motifspec/accession/status.html +2 -2
- package/layouts/_partials/motifspec/article.html +24 -24
- package/layouts/_partials/motifspec/back-link.html +2 -2
- package/layouts/_partials/motifspec/body-end.html +5 -5
- package/layouts/_partials/motifspec/body-start.html +5 -5
- package/layouts/_partials/motifspec/figure.html +14 -14
- package/layouts/_partials/motifspec/filed-under.html +5 -5
- package/layouts/_partials/motifspec/h2s.html +7 -7
- package/layouts/_partials/motifspec/head.html +15 -15
- package/layouts/_partials/motifspec/pagination-entries.html +34 -34
- package/layouts/_partials/motifspec/pagination-pages.html +30 -30
- package/layouts/_partials/motifspec/post-list-item.html +15 -15
- package/layouts/_partials/motifspec/project-card.html +23 -23
- package/layouts/_partials/motifspec/section-heading.html +3 -3
- package/layouts/_partials/motifspec/site-header.html +27 -27
- package/layouts/_partials/motifspec/table-of-contents.html +21 -21
- package/layouts/_partials/motifspec/tag-filter.html +22 -22
- package/layouts/_partials/motifspec/tags.html +8 -8
- package/layouts/_partials/motifspec/tokens.html +9 -9
- package/package.json +68 -68
- package/scripts/check.mjs +153 -153
- package/scripts/lib.mjs +176 -176
- package/tokens/README.md +3 -3
- package/tokens/border.json +63 -63
- package/tokens/color.json +1902 -1902
- package/tokens/motion.json +207 -207
- package/tokens/radius.json +53 -53
- package/tokens/size.json +378 -378
- package/tokens/space.json +289 -289
- 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.
|