@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
package/docs/hugo.md CHANGED
@@ -1,120 +1,132 @@
1
- # Using MotifSpec in Hugo
2
-
3
- The module ships the components as Hugo partials and the Markdown render hooks that turn content into them, so a site writes its page templates and content and nothing else. The partials follow `docs/components/*.md`; when a partial and its doc disagree, the doc is the contract and the partial is the bug.
4
-
5
- ## Setup
6
-
7
- ```toml
8
- [module]
9
- [[module.imports]]
10
- path = "github.com/aitrien/motifspec"
11
-
12
- [markup.highlight]
13
- noClasses = false
14
-
15
- [markup.goldmark.parser]
16
- wrapStandAloneImageWithinParagraph = false # standalone images reach render-image.html, so they can become figures
17
- [markup.goldmark.parser.attribute]
18
- block = true # {caption="…"} on tables, {file="…"} on code, {width="prose"} on figures
19
- ```
20
-
21
- Pin a release with `hugo mod get github.com/aitrien/motifspec@vX.Y.Z`. The module mounts `dist/` at `assets/motifspec`, `fonts/` at `static/fonts` and `layouts/` at `layouts`.
22
-
23
- ## The page shell
24
-
25
- ```go-html-template
26
- <!doctype html>
27
- <html lang="{{ site.Language.LanguageCode }}">
28
- <head>
29
- <meta charset="utf-8">
30
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
31
- {{ partial "motifspec/head.html" . }}
32
- {{/* the site's own <title>, meta description, favicon */}}
33
- </head>
34
- <body class="cx-backdrop-page">
35
- {{ partial "motifspec/body-start.html" . }}
36
- {{ partial "motifspec/site-header.html" . }}
37
- <main id="main" tabindex="-1">{{ block "main" . }}{{ end }}</main>
38
- {{/* the site's footer (site-footer.md) */}}
39
- {{ partial "motifspec/body-end.html" . }}
40
- </body>
41
- </html>
42
- ```
43
-
44
- | Partial | What it writes |
45
- |---|---|
46
- | `motifspec/head.html` | The stored-theme snippet, font preloads, `codex.css` and `codex.js`, in that order. |
47
- | `motifspec/body-start.html` | The backdrop, the icon sprite and the skip link to `#main`. |
48
- | `motifspec/site-header.html` | `site-header.md`: wordmark from `site.Title` and `site.Params.role`, nav from `site.Menus.main`, the backdrop pause control and the theme toggle. A menu entry is current when its `identifier` is the page's section. |
49
- | `motifspec/body-end.html` | `plate.js`, which drifts the backdrop and reveals the pause control. |
50
-
51
- The footer stays in the site: its columns are the site's own links and colophon. Use the markup in `site-footer.md`.
52
-
53
- ## Component partials
54
-
55
- Every partial below is called as `partial "motifspec/<name>.html" <context>`.
56
-
57
- | Partial | Doc | Context |
58
- |---|---|---|
59
- | `article.html` | `prose.md`, `table-of-contents.md`, `ornament.md` | `dict "page" . "toc" "always"` or `"auto"` (auto shows the TOC at three or more h2s). Ends the prose with the end mark, so the content must end with a paragraph. |
60
- | `project-card.html` | `project-card.md` | `dict "page" . "level" 2` (the heading level that fits the page outline; default 3). |
61
- | `post-list-item.html` | `post-list-item.md` | `dict "page" . "level" 3`. Writes an `<li>`, so put it in `<ol class="cx-posts">`. |
62
- | `accession/label.html` | `accession-label.md` | `dict "page" . "variant" "lead"` above the title, or `"card"` after the article (shown on a phone). |
63
- | `accession/id.html` | `accession-label.md` | A page. The compact number, date and reading time line. |
64
- | `accession/status.html` | `accession-label.md` | `live`, `building` or `archived`. |
65
- | `figure.html` | `figure.md` | `dict "image" <resource> "alt" "…" "caption" <HTML> "plate" 1`, plus `"eager" true` for the largest image above the fold and `"prose" true` to keep it to the text measure. |
66
- | `table-of-contents.html` | `table-of-contents.md` | A page. `article.html` already calls it. |
67
- | `tags.html` | `tag.md` | `dict "page" . "max" 3`. |
68
- | `tag-filter.html` | `tag.md` | `dict "section" "work" "current" nil` on a section page, or `dict "section" "" "current" .` on a term page. |
69
- | `filed-under.html` | `tag.md`, `docs/templates.md` | A page. "Filed under" and its tags, after an article. |
70
- | `pagination-entries.html` | `pagination.md` | `dict "page" . "label" "Post navigation" "noun" "Post"`. Previous and next in the page's section, oldest first. |
71
- | `pagination-pages.html` | `pagination.md` | `dict "pager" $pager "label" "Writing pages"`. Pages of a paginated list; nothing when it fits on one page. |
72
- | `back-link.html` | `button-and-link.md` | `dict "href" "…" "label" "All work"`. |
73
- | `section-heading.html` | `heading-anchor.md` | `dict "id" "work" "title" "Selected work"`, inside a `<section id="work" aria-labelledby="work-title">`. |
74
- | `tokens.html` | | Call with `partialCached "motifspec/tokens.html" "tokens"`. A map from token name to its entry in `dist/tokens.json`, for templates that need a token's value at build time. |
75
- | `h2s.html` | | A page. Its h2 headings with their h3 children; used by the article and the TOC. |
76
-
77
- ## Front matter the partials read
78
-
79
- | Field | Used by | Notes |
80
- |---|---|---|
81
- | `summary` | cards, rows | The lede and the card or row summary. |
82
- | `tags` | tags, filters | At most five, lowercase kebab-case; cards show three. |
83
- | `accession` | accession label, cards, rows | The catalogue number. The site checks its own numbering scheme. |
84
- | `status` | accession label, cards | `live`, `building` or `archived`. |
85
- | `role`, `stack` | accession label | `stack` is a list. |
86
- | `hero` | cards, figures | `src`, `alt` and `caption`. With a hero, the hero is Plate 1 and figures in the content start at Plate 2. |
87
- | `thumb` | cards | Optional SVG for the card instead of the cropped hero, for line drawings whose strokes must stay one pixel wide. |
88
-
89
- ## Render hooks
90
-
91
- These apply to all Markdown content once the module is imported.
92
-
93
- | Hook | Markdown | Doc |
94
- |---|---|---|
95
- | `render-heading.html` | Headings get ids; h2s get a `#` anchor. | `heading-anchor.md` |
96
- | `render-image.html` | A standalone image is a numbered figure: `![alt](file.png "caption")`, image in the page bundle, alt and caption required. `{width="prose"}` on the next line keeps it to the text measure. Inline images stay plain. | `figure.md` |
97
- | `render-table.html` | `{caption="Title \| subtitle"}` on the line after a table is required. Numeric columns align right; more than four columns break out. | `data-table.md` |
98
- | `render-codeblock.html` | Header bar with language, optional `{file="path"}` and copy button. Breaks out when a line is longer than the prose measure. | `code-block.md` |
99
- | `render-blockquote.html` | `> [!NOTE]`, `[!TIP]`, `[!WARNING]` and `[!CAUTION]` become callouts. A quotation must end with a line `— Name \| Source title`. | `callout.md`, `prose.md` |
100
- | `render-link.html` | Internal links are resolved, and a broken one fails the build. External links get the outward arrow. | `icon.md` |
101
-
102
- ## Checking a site
103
-
104
- Run `scripts/check.mjs` from the pinned module, so the site is checked against the release it uses. `hugo config mounts` prints one JSON object per module; MotifSpec's is the one that mounts `assets/motifspec` (under a local `HUGO_MODULE_REPLACEMENTS` too), and its `dir` is where the module is:
105
-
106
- ```js
107
- // scripts/audit.mjs in the site
108
- import { execFileSync } from 'node:child_process';
109
- import { join } from 'node:path';
110
-
111
- const mounts = execFileSync('hugo', ['config', 'mounts'], { encoding: 'utf8' });
112
- const motifspec = mounts.split(/\n(?=\{)/).map((m) => JSON.parse(m)).find((m) => m.mounts?.some((x) => x.target === 'assets/motifspec'));
113
- execFileSync('node', [join(motifspec.dir, 'scripts', 'check.mjs'), '.'], { stdio: 'inherit' });
114
- ```
115
-
116
- It checks `layouts/` and `content/` and fails on stylesheets, style attributes, raw colours and units, primitive, unknown or deprecated tokens, classes and icons MotifSpec doesn't define, and "·" separators. In GitHub Actions each problem is also an annotation on the pull request; `--json` prints the problems as JSON. In CI, give the step the same `HUGO_CACHEDIR` and `HUGO_MODULE_PROXY` as the build, because `hugo config mounts` downloads the module if it isn't cached.
117
-
118
- ## Overriding
119
-
120
- A file in the site at the same path wins over the module's. Override a partial only to change content, never markup or classes. If a component needs to change, change it here and release it, so every site gets the fix.
1
+ # Using MotifSpec in Hugo
2
+
3
+ The module ships the components as Hugo partials and the Markdown render hooks that turn content into them, so a site writes its page templates and content and nothing else. The partials follow `docs/components/*.md`; when a partial and its doc disagree, the doc is the contract and the partial is the bug.
4
+
5
+ ## Setup
6
+
7
+ ```toml
8
+ [module]
9
+ [[module.imports]]
10
+ path = "github.com/aitrien/motifspec"
11
+
12
+ [markup.highlight]
13
+ noClasses = false
14
+
15
+ [markup.goldmark.parser]
16
+ wrapStandAloneImageWithinParagraph = false # standalone images reach render-image.html, so they can become figures
17
+ [markup.goldmark.parser.attribute]
18
+ block = true # {caption="…"} on tables, {file="…"} on code, {width="prose"} on figures
19
+ ```
20
+
21
+ Pin a release with `hugo mod get github.com/aitrien/motifspec@vX.Y.Z`. The module mounts `dist/` at `assets/motifspec`, `fonts/` at `static/fonts` and `layouts/` at `layouts`. Hugo publishes the stylesheet at `/motifspec/`; keep it one level below the site root, because its font URLs are `../fonts/…`.
22
+
23
+ ## The page shell
24
+
25
+ ```go-html-template
26
+ <!doctype html>
27
+ <html lang="{{ site.Language.LanguageCode }}">
28
+ <head>
29
+ <meta charset="utf-8">
30
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
31
+ {{ partial "motifspec/head.html" . }}
32
+ {{/* the site's own <title>, meta description, favicon */}}
33
+ </head>
34
+ <body class="cx-backdrop-page">
35
+ {{ partial "motifspec/body-start.html" . }}
36
+ {{ partial "motifspec/site-header.html" . }}
37
+ <main id="main" tabindex="-1">{{ block "main" . }}{{ end }}</main>
38
+ {{/* the site's footer (site-footer.md) */}}
39
+ {{ partial "motifspec/body-end.html" . }}
40
+ </body>
41
+ </html>
42
+ ```
43
+
44
+ | Partial | What it writes |
45
+ |---|---|
46
+ | `motifspec/head.html` | The stored-theme snippet, font preloads, `codex.css` and `codex.js`, in that order. |
47
+ | `motifspec/body-start.html` | The backdrop, the icon sprite and the skip link to `#main`. |
48
+ | `motifspec/site-header.html` | `site-header.md`: wordmark from `site.Title` and `site.Params.role`, nav from `site.Menus.main`, the backdrop pause control and the theme toggle. A menu entry is current when its `identifier` is the page's section. |
49
+ | `motifspec/body-end.html` | `plate.js`, which drifts the backdrop and reveals the pause control. |
50
+
51
+ The footer stays in the site: its columns are the site's own links and colophon. Use the markup in `site-footer.md`.
52
+
53
+ ## Component partials
54
+
55
+ Every partial below is called as `partial "motifspec/<name>.html" <context>`.
56
+
57
+ | Partial | Doc | Context |
58
+ |---|---|---|
59
+ | `article.html` | `prose.md`, `table-of-contents.md`, `ornament.md` | `dict "page" . "toc" "always"` or `"auto"` (auto shows the TOC at three or more h2s). Ends the prose with the end mark, so the content must end with a paragraph. |
60
+ | `project-card.html` | `project-card.md` | `dict "page" . "level" 2` (the heading level that fits the page outline; default 3). |
61
+ | `post-list-item.html` | `post-list-item.md` | `dict "page" . "level" 3`. Writes an `<li>`, so put it in `<ol class="cx-posts">`. |
62
+ | `accession/label.html` | `accession-label.md` | `dict "page" . "variant" "lead"` above the title, or `"card"` after the article (shown on a phone). |
63
+ | `accession/id.html` | `accession-label.md` | A page. The compact number, date and reading time line. |
64
+ | `accession/status.html` | `accession-label.md` | `live`, `building` or `archived`. |
65
+ | `figure.html` | `figure.md` | `dict "image" <resource> "alt" "…" "caption" <HTML> "plate" 1`, plus `"eager" true` for the largest image above the fold and `"prose" true` to keep it to the text measure. |
66
+ | `table-of-contents.html` | `table-of-contents.md` | A page. `article.html` already calls it. |
67
+ | `tags.html` | `tag.md` | `dict "page" . "max" 3`. |
68
+ | `tag-filter.html` | `tag.md` | `dict "section" "work" "current" nil` on a section page, or `dict "section" "" "current" .` on a term page. |
69
+ | `filed-under.html` | `tag.md`, `docs/templates.md` | A page. "Filed under" and its tags, after an article. |
70
+ | `pagination-entries.html` | `pagination.md` | `dict "page" . "label" "Post navigation" "noun" "Post"`. Previous and next in the page's section, oldest first. |
71
+ | `pagination-pages.html` | `pagination.md` | `dict "pager" $pager "label" "Writing pages"`. Pages of a paginated list; nothing when it fits on one page. |
72
+ | `back-link.html` | `button-and-link.md` | `dict "href" "…" "label" "All work"`. |
73
+ | `section-heading.html` | `heading-anchor.md` | `dict "id" "work" "title" "Selected work"`, inside a `<section id="work" aria-labelledby="work-title">`. |
74
+ | `tokens.html` | | Call with `partialCached "motifspec/tokens.html" "tokens"`. A map from token name to its entry in `dist/tokens.json`, for templates that need a token's value at build time. |
75
+ | `h2s.html` | | A page. Its h2 headings with their h3 children; used by the article and the TOC. |
76
+
77
+ ## Front matter the partials read
78
+
79
+ ```yaml
80
+ title: Interactive WebGL globe
81
+ accession: LM-W-001
82
+ date: 2026-03-14
83
+ summary: A React app that renders live routes on a 3D globe.
84
+ role: Sole engineer
85
+ stack: [React, WebGL, Vite]
86
+ status: live # live | building | archived
87
+ tags: [react, webgl, a11y]
88
+ hero: { src: hero.png, alt: "…", caption: "…" }
89
+ ```
90
+
91
+ | Field | Used by | Notes |
92
+ |---|---|---|
93
+ | `summary` | cards, rows | The lede and the card or row summary. |
94
+ | `tags` | tags, filters | At most five, lowercase kebab-case; cards show three. |
95
+ | `accession` | accession label, cards, rows | The catalogue number. The site checks its own numbering scheme. |
96
+ | `status` | accession label, cards | `live`, `building` or `archived`. |
97
+ | `role`, `stack` | accession label | `stack` is a list. |
98
+ | `hero` | cards, figures | `src`, `alt` and `caption`. With a hero, the hero is Plate 1 and figures in the content start at Plate 2. |
99
+ | `thumb` | cards | Optional SVG for the card instead of the cropped hero, for line drawings whose strokes must stay one pixel wide. |
100
+
101
+ ## Render hooks
102
+
103
+ These apply to all Markdown content once the module is imported.
104
+
105
+ | Hook | Markdown | Doc |
106
+ |---|---|---|
107
+ | `render-heading.html` | Headings get ids; h2s get a `#` anchor. | `heading-anchor.md` |
108
+ | `render-image.html` | A standalone image is a numbered figure: `![alt](file.png "caption")`, image in the page bundle, alt and caption required. `{width="prose"}` on the next line keeps it to the text measure. Inline images stay plain. | `figure.md` |
109
+ | `render-table.html` | `{caption="Title \| subtitle"}` on the line after a table is required. Numeric columns align right; more than four columns break out. | `data-table.md` |
110
+ | `render-codeblock.html` | Header bar with language, optional `{file="path"}` and copy button. Breaks out when a line is longer than the prose measure. | `code-block.md` |
111
+ | `render-blockquote.html` | `> [!NOTE]`, `[!TIP]`, `[!WARNING]` and `[!CAUTION]` become callouts. A quotation must end with a line `— Name \| Source title`. | `callout.md`, `prose.md` |
112
+ | `render-link.html` | Internal links are resolved, and a broken one fails the build. External links get the outward arrow. | `icon.md` |
113
+
114
+ ## Checking a site
115
+
116
+ Run `scripts/check.mjs` from the pinned module, so the site is checked against the release it uses. `hugo config mounts` prints one JSON object per module; MotifSpec's is the one that mounts `assets/motifspec` (under a local `HUGO_MODULE_REPLACEMENTS` too), and its `dir` is where the module is:
117
+
118
+ ```js
119
+ // scripts/audit.mjs in the site
120
+ import { execFileSync } from 'node:child_process';
121
+ import { join } from 'node:path';
122
+
123
+ const mounts = execFileSync('hugo', ['config', 'mounts'], { encoding: 'utf8' });
124
+ const motifspec = mounts.split(/\n(?=\{)/).map((m) => JSON.parse(m)).find((m) => m.mounts?.some((x) => x.target === 'assets/motifspec'));
125
+ execFileSync('node', [join(motifspec.dir, 'scripts', 'check.mjs'), '.'], { stdio: 'inherit' });
126
+ ```
127
+
128
+ It checks `layouts/` and `content/` for what the README's Commands section lists. In GitHub Actions each problem is also an annotation on the pull request; `--json` prints the problems as JSON. In CI, give the step the same `HUGO_CACHEDIR` and `HUGO_MODULE_PROXY` as the build, because `hugo config mounts` downloads the module if it isn't cached.
129
+
130
+ ## Overriding
131
+
132
+ A file in the site at the same path wins over the module's. Override a partial only to change content, never markup or classes. If a component needs to change, change it here and release it, so every site gets the fix.
@@ -1,55 +1,55 @@
1
- # Syntax highlighting
2
-
3
- Hugo highlights code with [Chroma](https://github.com/alecthomas/chroma). MotifSpec maps Chroma's CSS classes to `color.syntax.*` roles, so one stylesheet covers both themes and every colour is checked for contrast.
4
-
5
- ## Setup
6
-
7
- In `hugo.toml`:
8
-
9
- ```toml
10
- [markup.highlight]
11
- noClasses = false # emit classes, not inline colours
12
- lineNos = false # opt in per block with {linenos=table}
13
- codeFences = true
14
- guessSyntax = false
15
- ```
16
-
17
- Load `dist/chroma.css` (also bundled in `dist/codex.css`). Do not generate a Chroma theme with `hugo gen chromastyles`; it would hard-code colours and bypass the tokens.
18
-
19
- Wrap highlighted output in the code-block component (see `components/code-block.md`) so it gets the header bar, border and copy button.
20
-
21
- ## Rules
22
-
23
- - Every role has a contrast pair against `color.surface.sunken` and `color.syntax.line-highlight` at 4.5:1 or more, in both themes.
24
- - Comments stay upright. No mono italic is loaded, and synthetic italic in code is hard to read.
25
- - Keywords and headings are the only bold roles. Weight is a second cue so highlighting never depends on hue alone.
26
- - Plain identifiers use `color.syntax.variable`, which matches body text, so most code reads as text and the colour goes to structure.
27
- - Diff lines keep their `+` and `-` prefixes; colour is a second signal, never the only one.
28
-
29
- ## Mapping
30
-
31
- Generated by `npm run build` from `syntax/chroma.json`. Values are light / dark.
32
-
33
- <!-- TABLE:START -->
34
- | Role | Chroma classes | Token | Light / dark | Contrast on code surface (light / dark) |
35
- |---|---|---|---|---|
36
- | Keywords | `.k` `.kd` `.kn` `.kp` `.kr` `.ow` `.bp` | `color.syntax.keyword` + weight `font.weight.strong` | #942f29 / #feb5ac | 6.79 / 10.36 |
37
- | Type keywords, classes, built-ins, namespaces, exceptions | `.kt` `.nb` `.nc` `.nn` `.ne` | `color.syntax.type` | #123961 / #cde4fe | 10.27 / 13.46 |
38
- | Constants: numbers, booleans, null, dates, symbols, entities, escapes | `.kc` `.no` `.m` `.mb` `.mf` `.mh` `.mi` `.il` `.mo` `.ld` `.ss` `.ni` `.se` | `color.syntax.number` | #6c5302 / #e0c88f | 6.37 / 10.70 |
39
- | Strings and regex | `.s` `.sa` `.sb` `.sc` `.dl` `.s2` `.sh` `.sx` `.sr` `.s1` `.cpf` | `color.syntax.string` | #126836 / #a7d9b3 | 5.99 / 11.03 |
40
- | Functions | `.nf` `.fm` | `color.syntax.function` | #26598e / #abcef6 | 6.31 / 10.76 |
41
- | Markup tags | `.nt` | `color.syntax.tag` | #651916 / #ffd6d1 | 10.75 / 13.18 |
42
- | Attributes, properties, object keys | `.na` `.py` | `color.syntax.attribute` | #473502 / #f9dfa1 | 10.30 / 13.43 |
43
- | Names and variables | `.n` `.nx` `.nv` `.vc` `.vg` `.vi` `.vm` `.l` | `color.syntax.variable` | #171a19 / #eaf1ee | 15.28 / 15.28 |
44
- | Operators and punctuation | `.o` `.p` | `color.syntax.operator` | #323a36 / #c4cdc8 | 10.21 / 10.78 |
45
- | Comments and doc strings | `.c` `.ch` `.cm` `.c1` `.cs` `.sd` `.go` | `color.syntax.comment` | #515955 / #a4ada9 | 6.29 / 7.62 |
46
- | Meta: decorators, preprocessor, labels, interpolation, prompts | `.nd` `.cp` `.nl` `.si` `.gp` | `color.syntax.meta` | #01441f / #bbf1c8 | 9.91 / 13.79 |
47
- | Diff: inserted | `.gi` | `color.syntax.inserted` | #01441f / #bbf1c8 | 9.91 / 13.79 |
48
- | Diff: deleted, errors, tracebacks | `.gd` `.gr` `.gt` `.err` | `color.syntax.deleted` | #651916 / #ffd6d1 | 10.75 / 13.18 |
49
- | Headings and diff hunks | `.gh` `.gu` | `color.syntax.heading` + weight `font.weight.strong` | #123961 / #cde4fe | 10.27 / 13.46 |
50
- | Generic strong and emphasis (no italic available) | `.gs` `.ge` | weight `font.weight.strong` | — | — |
51
- | Line numbers | `.ln` `.lnt` | `color.syntax.line-number` | #515955 / #a4ada9 | 6.29 / 7.62 |
52
- | Highlighted lines (hl_lines) | `.hl` | background `color.syntax.line-highlight` | #dae4df / #252a28 | — |
53
- <!-- TABLE:END -->
54
-
55
- Classes not listed (for example `.w` whitespace, `.x` other) inherit `color.syntax.variable` from `.chroma`.
1
+ # Syntax highlighting
2
+
3
+ Hugo highlights code with [Chroma](https://github.com/alecthomas/chroma). MotifSpec maps Chroma's CSS classes to `color.syntax.*` roles, so one stylesheet covers both themes and every colour is checked for contrast.
4
+
5
+ ## Setup
6
+
7
+ In `hugo.toml`:
8
+
9
+ ```toml
10
+ [markup.highlight]
11
+ noClasses = false # emit classes, not inline colours
12
+ lineNos = false # opt in per block with {linenos=table}
13
+ codeFences = true
14
+ guessSyntax = false
15
+ ```
16
+
17
+ Load `dist/chroma.css` (also bundled in `dist/codex.css`). Do not generate a Chroma theme with `hugo gen chromastyles`; it would hard-code colours and bypass the tokens.
18
+
19
+ Wrap highlighted output in the code-block component (see `components/code-block.md`) so it gets the header bar, border and copy button.
20
+
21
+ ## Rules
22
+
23
+ - Every role has a contrast pair against `color.surface.sunken` and `color.syntax.line-highlight` at 4.5:1 or more, in both themes.
24
+ - Comments stay upright. No mono italic is loaded, and synthetic italic in code is hard to read.
25
+ - Keywords and headings are the only bold roles. Weight is a second cue so highlighting never depends on hue alone.
26
+ - Plain identifiers use `color.syntax.variable`, which matches body text, so most code reads as text and the colour goes to structure.
27
+ - Diff lines keep their `+` and `-` prefixes; colour is a second signal, never the only one.
28
+
29
+ ## Mapping
30
+
31
+ Generated by `npm run build` from `syntax/chroma.json`. Values are light / dark.
32
+
33
+ <!-- TABLE:START -->
34
+ | Role | Chroma classes | Token | Light / dark | Contrast on code surface (light / dark) |
35
+ |---|---|---|---|---|
36
+ | Keywords | `.k` `.kd` `.kn` `.kp` `.kr` `.ow` `.bp` | `color.syntax.keyword` + weight `font.weight.strong` | #942f29 / #feb5ac | 6.79 / 10.36 |
37
+ | Type keywords, classes, built-ins, namespaces, exceptions | `.kt` `.nb` `.nc` `.nn` `.ne` | `color.syntax.type` | #123961 / #cde4fe | 10.27 / 13.46 |
38
+ | Constants: numbers, booleans, null, dates, symbols, entities, escapes | `.kc` `.no` `.m` `.mb` `.mf` `.mh` `.mi` `.il` `.mo` `.ld` `.ss` `.ni` `.se` | `color.syntax.number` | #6c5302 / #e0c88f | 6.37 / 10.70 |
39
+ | Strings and regex | `.s` `.sa` `.sb` `.sc` `.dl` `.s2` `.sh` `.sx` `.sr` `.s1` `.cpf` | `color.syntax.string` | #126836 / #a7d9b3 | 5.99 / 11.03 |
40
+ | Functions | `.nf` `.fm` | `color.syntax.function` | #26598e / #abcef6 | 6.31 / 10.76 |
41
+ | Markup tags | `.nt` | `color.syntax.tag` | #651916 / #ffd6d1 | 10.75 / 13.18 |
42
+ | Attributes, properties, object keys | `.na` `.py` | `color.syntax.attribute` | #473502 / #f9dfa1 | 10.30 / 13.43 |
43
+ | Names and variables | `.n` `.nx` `.nv` `.vc` `.vg` `.vi` `.vm` `.l` | `color.syntax.variable` | #171a19 / #eaf1ee | 15.28 / 15.28 |
44
+ | Operators and punctuation | `.o` `.p` | `color.syntax.operator` | #323a36 / #c4cdc8 | 10.21 / 10.78 |
45
+ | Comments and doc strings | `.c` `.ch` `.cm` `.c1` `.cs` `.sd` `.go` | `color.syntax.comment` | #515955 / #a4ada9 | 6.29 / 7.62 |
46
+ | Meta: decorators, preprocessor, labels, interpolation, prompts | `.nd` `.cp` `.nl` `.si` `.gp` | `color.syntax.meta` | #01441f / #bbf1c8 | 9.91 / 13.79 |
47
+ | Diff: inserted | `.gi` | `color.syntax.inserted` | #01441f / #bbf1c8 | 9.91 / 13.79 |
48
+ | Diff: deleted, errors, tracebacks | `.gd` `.gr` `.gt` `.err` | `color.syntax.deleted` | #651916 / #ffd6d1 | 10.75 / 13.18 |
49
+ | Headings and diff hunks | `.gh` `.gu` | `color.syntax.heading` + weight `font.weight.strong` | #123961 / #cde4fe | 10.27 / 13.46 |
50
+ | Generic strong and emphasis (no italic available) | `.gs` `.ge` | weight `font.weight.strong` | — | — |
51
+ | Line numbers | `.ln` `.lnt` | `color.syntax.line-number` | #515955 / #a4ada9 | 6.29 / 7.62 |
52
+ | Highlighted lines (hl_lines) | `.hl` | background `color.syntax.line-highlight` | #dae4df / #252a28 | — |
53
+ <!-- TABLE:END -->
54
+
55
+ Classes not listed (for example `.w` whitespace, `.x` other) inherit `color.syntax.variable` from `.chroma`.
package/docs/templates.md CHANGED
@@ -1,30 +1,21 @@
1
- # Page templates
2
-
3
- Content hierarchy for the six page types. Every template sits on the backdrop (`components/backdrop.md`): the frost shows through behind the header and the opening of the page, and again behind the footer. Every template keeps the full site header (Work, Writing, About, the backdrop pause, the theme toggle) and the footer. Every `<h2>` section heading carries a heading anchor.
4
-
5
- | Template | Order | Dominant | Secondary |
6
- |---|---|---|---|
7
- | **Home** | Header → statement (eyebrow, display h1, lede, primary + secondary button) on the frost, inside the top fade → selected work (2–3 project cards) → recent writing (5 post rows) → capabilities table → footer | Statement and selected work | Writing rows, capabilities, the backdrop |
8
- | **Project index** | Header → page head (h1 "Work", entry count, one-line intro) → tag filter → project cards or ledger, newest first → footer | The list of projects | Filter and intro |
9
- | **Case study** | Header → back link "← All work" → accession label (`cx-label--lead`; on a phone, number, date and status only) → h1 and lede → hero figure (Plate 1) → article layout: TOC + prose (Problem, Constraints, Approach, Outcome with a data table, What I'd change) → on a phone, the full label as a card (`cx-label--card`) → pagination → footer | Title and hero, then prose | Label, TOC |
10
- | **Writing list** | Header → page head (h1 "Writing", description, RSS link) → tag filter → post rows grouped by year → pagination → footer | Post titles | Numbers, dates, tags |
11
- | **Blog post** | Header → back link "← All writing" → meta line (`cx-id`: number, date, reading time) → h1 and lede → article layout: TOC (when three or more h2s) + prose with figures, code blocks and callouts → end mark → "Filed under" tags → pagination → footer | The prose column | Meta, TOC |
12
- | **404** | Header → sprout ornament (lg) → accession label "No. — / Not in collection" → h1 "Page not found" → the requested path in mono → three links: Home, Work, Writing → footer | Heading and the routes out | Ornament, path |
13
-
14
- ## Catalogue numbers
15
-
16
- - Work: `LM-W-001` onward. Writing: `LM-P-001` onward. Zero-padded, never reused, stored in front matter as `accession`.
17
- - They appear on cards, post rows, post meta lines and case-study labels.
18
-
19
- ## Hugo front matter
20
-
21
- ```yaml
22
- title: Interactive WebGL globe
23
- accession: LM-W-001
24
- date: 2026-03-14
25
- summary: A React app that renders live routes on a 3D globe.
26
- role: Sole engineer
27
- stack: [React, WebGL, Vite]
28
- status: live # live | building | archived
29
- tags: [react, webgl, a11y]
30
- ```
1
+ # Page templates
2
+
3
+ Content hierarchy for the six page types. Every template sits on the backdrop (`components/backdrop.md`): the frost shows through behind the header and the opening of the page, and again behind the footer. Every template keeps the full site header (Work, Writing, About, the backdrop pause, the theme toggle) and the footer. Every `<h2>` section heading carries a heading anchor.
4
+
5
+ | Template | Order | Dominant | Secondary |
6
+ |---|---|---|---|
7
+ | **Home** | Header → statement (eyebrow, display h1, lede, primary + secondary button) on the frost, inside the top fade → selected work (2–3 project cards) → recent writing (5 post rows) → capabilities table → footer | Statement and selected work | Writing rows, capabilities, the backdrop |
8
+ | **Project index** | Header → page head (h1 "Work", entry count, one-line intro) → tag filter → project cards or ledger, newest first → footer | The list of projects | Filter and intro |
9
+ | **Case study** | Header → back link "← All work" → accession label (`cx-label--lead`; on a phone, number, date and status only) → h1 and lede → hero figure (Plate 1) → article layout: TOC + prose (Problem, Constraints, Approach, Outcome with a data table, What I'd change) → on a phone, the full label as a card (`cx-label--card`) → pagination → footer | Title and hero, then prose | Label, TOC |
10
+ | **Writing list** | Header → page head (h1 "Writing", description, RSS link) → tag filter → post rows grouped by year → pagination → footer | Post titles | Numbers, dates, tags |
11
+ | **Blog post** | Header → back link "← All writing" → meta line (`cx-id`: number, date, reading time) → h1 and lede → article layout: TOC (when three or more h2s) + prose with figures, code blocks and callouts → end mark → "Filed under" tags → pagination → footer | The prose column | Meta, TOC |
12
+ | **404** | Header → sprout ornament (lg) → accession label "No. — / Not in collection" → h1 "Page not found" → the requested path in mono → three links: Home, Work, Writing → footer | Heading and the routes out | Ornament, path |
13
+
14
+ ## Catalogue numbers
15
+
16
+ - Work: `LM-W-001` onward. Writing: `LM-P-001` onward. Zero-padded, never reused, stored in front matter as `accession`.
17
+ - They appear on cards, post rows, post meta lines and case-study labels.
18
+
19
+ ## Front matter
20
+
21
+ The fields each template reads are in `docs/hugo.md` (Front matter the partials read).
@@ -1,93 +1,93 @@
1
- Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
2
-
3
- This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
- This license is copied below, and is also available with a FAQ at:
5
- http://scripts.sil.org/OFL
6
-
7
-
8
- -----------------------------------------------------------
9
- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
- -----------------------------------------------------------
11
-
12
- PREAMBLE
13
- The goals of the Open Font License (OFL) are to stimulate worldwide
14
- development of collaborative font projects, to support the font creation
15
- efforts of academic and linguistic communities, and to provide a free and
16
- open framework in which fonts may be shared and improved in partnership
17
- with others.
18
-
19
- The OFL allows the licensed fonts to be used, studied, modified and
20
- redistributed freely as long as they are not sold by themselves. The
21
- fonts, including any derivative works, can be bundled, embedded,
22
- redistributed and/or sold with any software provided that any reserved
23
- names are not used by derivative works. The fonts and derivatives,
24
- however, cannot be released under any other type of license. The
25
- requirement for fonts to remain under this license does not apply
26
- to any document created using the fonts or their derivatives.
27
-
28
- DEFINITIONS
29
- "Font Software" refers to the set of files released by the Copyright
30
- Holder(s) under this license and clearly marked as such. This may
31
- include source files, build scripts and documentation.
32
-
33
- "Reserved Font Name" refers to any names specified as such after the
34
- copyright statement(s).
35
-
36
- "Original Version" refers to the collection of Font Software components as
37
- distributed by the Copyright Holder(s).
38
-
39
- "Modified Version" refers to any derivative made by adding to, deleting,
40
- or substituting -- in part or in whole -- any of the components of the
41
- Original Version, by changing formats or by porting the Font Software to a
42
- new environment.
43
-
44
- "Author" refers to any designer, engineer, programmer, technical
45
- writer or other person who contributed to the Font Software.
46
-
47
- PERMISSION & CONDITIONS
48
- Permission is hereby granted, free of charge, to any person obtaining
49
- a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
- redistribute, and sell modified and unmodified copies of the Font
51
- Software, subject to the following conditions:
52
-
53
- 1) Neither the Font Software nor any of its individual components,
54
- in Original or Modified Versions, may be sold by itself.
55
-
56
- 2) Original or Modified Versions of the Font Software may be bundled,
57
- redistributed and/or sold with any software, provided that each copy
58
- contains the above copyright notice and this license. These can be
59
- included either as stand-alone text files, human-readable headers or
60
- in the appropriate machine-readable metadata fields within text or
61
- binary files as long as those fields can be easily viewed by the user.
62
-
63
- 3) No Modified Version of the Font Software may use the Reserved Font
64
- Name(s) unless explicit written permission is granted by the corresponding
65
- Copyright Holder. This restriction only applies to the primary font name as
66
- presented to the users.
67
-
68
- 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
- Software shall not be used to promote, endorse or advertise any
70
- Modified Version, except to acknowledge the contribution(s) of the
71
- Copyright Holder(s) and the Author(s) or with their explicit written
72
- permission.
73
-
74
- 5) The Font Software, modified or unmodified, in part or in whole,
75
- must be distributed entirely under this license, and must not be
76
- distributed under any other license. The requirement for fonts to
77
- remain under this license does not apply to any document created
78
- using the Font Software.
79
-
80
- TERMINATION
81
- This license becomes null and void if any of the above conditions are
82
- not met.
83
-
84
- DISCLAIMER
85
- THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
- OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
- COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
- INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
- DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
- FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
- OTHER DEALINGS IN THE FONT SOFTWARE.
1
+ Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.