@allons-y/webc-patterns 0.1.0-beta.1 → 0.1.0-beta.4

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 (3) hide show
  1. package/README.md +97 -92
  2. package/llms.txt +709 -0
  3. package/package.json +6 -5
package/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # @allons-y/webc-patterns
2
2
 
3
- Part of [Norn](https://github.com/allonsy-studio/webc-resources#readme). Generic page and docs patterns for Eleventy sites that use WebC: the page frame and docs shell as Eleventy layouts, and the components they are built from, such as the header, footer, hero, cards, side nav, pager, skip link, icons, link buttons, and tags. One Eleventy plugin registers all of it. Content goes in slots: a component styles the markup it's given. Each component carries its own CSS in-file, so a page ships only the styles of the components it uses.
3
+ Part of [Norn](https://github.com/allonsy-studio/webc-resources#readme). Generic page & docs patterns for Eleventy sites that use WebC: the page frame & docs shell as Eleventy layouts, & the components they are built from, such as the header, footer, hero, cards, side nav, pager, skip link, icons, link buttons, & tags. One Eleventy plugin registers all of it. Content goes in slots: a component styles the markup it's given. Each component carries its own CSS in-file, so a page ships only the styles of the components it uses.
4
4
 
5
5
  See it in the [component preview](https://allonsy-studio.github.io/webc-resources/).
6
6
 
7
+ Using an AI coding agent? Point it at [`llms.txt`](llms.txt): the rules for calling & styling the components, & a reference of every prop, slot, & token.
8
+
7
9
  ## Status
8
10
 
9
11
  Beta. Versions are `0.1.0-beta.N` until 1.0.
@@ -15,7 +17,7 @@ npm install --save-dev @11ty/eleventy @11ty/eleventy-plugin-webc @allons-y/webc-
15
17
  npm install --save-dev @allons-y/webc-code-sample # optional: highlighted code samples
16
18
  ```
17
19
 
18
- `@11ty/eleventy` and `@11ty/eleventy-plugin-webc` are peer dependencies. `@allons-y/webc-code-sample` is an optional one: with it installed, the plugin registers `norn-code-block` and highlights Markdown fences, and the layouts show code with it; without it, the plugin registers a plain `norn-code-block` under the same tag, so a hand-written sample and the layouts' samples show as `<pre><code>`, with their `id` and caption. The project is an ES module (`"type": "module"`). Node 22 or later.
20
+ `@11ty/eleventy` & `@11ty/eleventy-plugin-webc` are peer dependencies. `@allons-y/webc-code-sample` is an optional one: with it installed, the plugin registers `norn-code-block` & highlights Markdown fences, & the layouts show code with it; without it, the plugin registers a plain `norn-code-block` under the same tag, so a hand-written sample & the layouts' samples show as `<pre><code>`, with their `id` & caption. The project is an ES module (`"type": "module"`). Node 22 or later.
19
21
 
20
22
  Evergreen browsers from about 2024. Features newer than that, such as `@scope`, degrade gracefully.
21
23
 
@@ -35,7 +37,7 @@ export default function (config) {
35
37
  }
36
38
  ```
37
39
 
38
- Then pick a layout per page in its front matter: `layout: norn-docs.webc`. The plugin owns the project's one `@11ty/eleventy-plugin-webc` registration: pass more component globs as `components`, and don't add the WebC plugin separately. For the OSS site theme, add `@allons-y/webc-oss-site`'s plugin in place of this one (see its [README](https://github.com/allonsy-studio/webc-resources/tree/main/packages/webc-oss-site#readme)).
40
+ Then pick a layout per page in its front matter: `layout: norn-docs.webc`. The plugin owns the project's one `@11ty/eleventy-plugin-webc` registration: pass more component globs as `components`, & don't add the WebC plugin separately. For the OSS site theme, add `@allons-y/webc-oss-site`'s plugin in place of this one (see its [README](https://github.com/allonsy-studio/webc-resources/tree/main/packages/webc-oss-site#readme)).
39
41
 
40
42
  | Option | Default | Does |
41
43
  | --- | --- | --- |
@@ -43,17 +45,17 @@ Then pick a layout per page in its front matter: `layout: norn-docs.webc`. The p
43
45
  | `layouts` | `true` | `false` ships no layout; a list of names, as `["norn-landing"]`, leaves those out so the project can supply its own |
44
46
  | `components` | `[]` | More component globs: a theme's, the project's `_includes/*.webc` |
45
47
  | `exclude` | `[]` | Packaged components to leave out, so the project can supply its own |
46
- | `strings` | `{}` | The site's strings for every component and layout (see Translating) |
48
+ | `strings` | `{}` | The site's strings for every component & layout (see Translating) |
47
49
  | `markdown` | `true` | Amend Markdown with the add-ons below |
48
50
  | `defaults` | `{}` | Prop values a theme sets, under the mapping's |
49
51
  | `icons` | none | Project icons for `norn-icon` (see Project icons) |
50
52
 
51
53
  The plugin registers:
52
54
 
53
- - `@11ty/eleventy-plugin-webc` once, with the patterns components, `norn-code-block` from the code sample when it is installed (else the plain one), and the `components` globs.
55
+ - `@11ty/eleventy-plugin-webc` once, with the patterns components, `norn-code-block` from the code sample when it is installed (else the plain one), & the `components` globs.
54
56
  - The six layouts, under the project's configured layouts folder (or its includes folder), through `addTemplate`.
55
- - The helpers the components and layouts call: `nornAdjacent`, `nornHeadings`, `nornComponentStrings`, `nornHook`, `nornIcons`, `nornCompiles`, and `nornTokenRanges` with the code sample.
56
- - With `markdown`, the Markdown add-ons through `amendLibrary`: `webcTags`, `highlightFences` from the code sample, `scrollTables`, `callouts`, `singleTitle`, and `headingAnchors`.
57
+ - The helpers the components & layouts call: `nornAdjacent`, `nornHeadings`, `nornComponentStrings`, `nornHook`, `nornIcons`, `nornCompiles`, & `nornTokenRanges` with the code sample.
58
+ - With `markdown`, the Markdown add-ons through `amendLibrary`: `webcTags`, `highlightFences` from the code sample, `scrollTables`, `callouts`, `singleTitle`, & `headingAnchors`.
57
59
  - `dropEmptyBundleTags` as a transform, so a page with no CSS or JS keeps no empty `<link>` or `<script>`.
58
60
 
59
61
  ### Project icons
@@ -67,23 +69,23 @@ config.addPlugin(norn, {
67
69
  });
68
70
  ```
69
71
 
70
- Then `<norn-icon @name="logo"></norn-icon>`. Paths resolve against the working directory. A name is lowercase kebab case. A missing file, a bad name, or two files with one name fails the build, and so does a name no icon has: the error lists the built-in and project names.
72
+ Then `<norn-icon @name="logo"></norn-icon>`. Paths resolve against the working directory. A name is lowercase kebab case. A missing file, a bad name, or two files with one name fails the build, & so does a name no icon has: the error lists the built-in & project names.
71
73
 
72
74
  - A project icon with a built-in's name replaces the built-in.
73
- - The icon keeps its `viewBox`, or takes one from a numeric `width` and `height`, or gets `0 0 24 24`, and its own paint. Draw it with `currentColor` so it follows the text color; a file with no paint is filled with `currentColor`. The built-in stroke styling applies to the built-in icons only, so a filled SVG renders filled.
74
- - The size, `<title>`, `<desc>`, scripts, and event handler attributes are dropped, and the drawing is always hidden from assistive tech. Name an icon that carries meaning in the `label` slot, as for any icon.
75
- - Ids in the drawing, and the references to them (`url(#id)`, `href`, and a `<style>`'s `#id` selectors), are prefixed with `norn-icon-` and the icon's name. Inlined icons share the page's one id space, so two icons that each define `id="a"` would otherwise clip and fill each other.
75
+ - The icon keeps its `viewBox`, or takes one from a numeric `width` & `height`, or gets `0 0 24 24`, & its own paint. Draw it with `currentColor` so it follows the text color; a file with no paint is filled with `currentColor`. The built-in stroke styling applies to the built-in icons only, so a filled SVG renders filled.
76
+ - The size, `<title>`, `<desc>`, scripts, & event handler attributes are dropped, & the drawing is always hidden from assistive tech. Name an icon that carries meaning in the `label` slot, as for any icon.
77
+ - Ids in the drawing, & the references to them (`url(#id)`, `href`, & a `<style>`'s `#id` selectors), are prefixed with `norn-icon-` & the icon's name. Inlined icons share the page's one id space, so two icons that each define `id="a"` would otherwise clip & fill each other.
76
78
  - `mirror: true` flips the icon in right-to-left text, as the built-in arrows do.
77
79
 
78
80
  Without the plugin, `norn-icon` draws the built-in set only.
79
81
 
80
- Without the plugin, register the components as `patterns()` (or `npm:@allons-y/webc-patterns/components/*.webc`) in the project's own WebC `components` list. The layouts, helpers, and Markdown add-ons are then the project's to wire up.
82
+ Without the plugin, register the components as `patterns()` (or `npm:@allons-y/webc-patterns/components/*.webc`) in the project's own WebC `components` list. The layouts, helpers, & Markdown add-ons are then the project's to wire up.
81
83
 
82
84
  ### Layout props
83
85
 
84
- The layouts read their props as `$data.norn`. Without `props`, each prop is read off the page data of the same name. Every list defaults to `[]`, and a value that is not a list counts as empty. Eleventy also runs the mapping against placeholder data to learn what it reads, so it must not throw on missing data: write `data.collections?.docs ?? []`, not `data.collections.docs`. `norn` is a reserved data key: a page, or a data file, that sets it fails the build with the page's path.
86
+ The layouts read their props as `$data.norn`. Without `props`, each prop is read off the page data of the same name. Every list defaults to `[]`, & a value that is not a list counts as empty. Eleventy also runs the mapping against placeholder data to learn what it reads, so it must not throw on missing data: write `data.collections?.docs ?? []`, not `data.collections.docs`. `norn` is a reserved data key: a page, or a data file, that sets it fails the build with the page's path.
85
87
 
86
- One mapping serves every layout, because each layout reads only its own props and ignores the rest:
88
+ One mapping serves every layout, because each layout reads only its own props & ignores the rest:
87
89
 
88
90
  ```js
89
91
  config.addPlugin(norn, {
@@ -101,21 +103,21 @@ config.addPlugin(norn, {
101
103
  });
102
104
  ```
103
105
 
104
- Every list item that links somewhere or describes something has one shape: `href` (the address), `title` (the label), and `description` (the short text), plus `icon` where a layout draws one. That holds for `nav`, the footer's links, `docs`, `prev` and `next`, `features`, `packages`, and `workspaces`, and for the `items` of `norn-side-nav` and `norn-breadcrumbs`. A mapping turns an Eleventy page into an item with `href: page.url`. The props that are not list items keep their names: `name` is the project's own name, `url` the page's canonical address, and `footer.license` is `{ name, url }`.
106
+ Every list item that links somewhere or describes something has one shape: `href` (the address), `title` (the label), & `description` (the short text), plus `icon` where a layout draws one. That holds for `nav`, the footer's links, `docs`, `prev` & `next`, `features`, `packages`, & `workspaces`, & for the `items` of `norn-side-nav` & `norn-breadcrumbs`. A mapping turns an Eleventy page into an item with `href: page.url`. The props that are not list items keep their names: `name` is the project's own name, `url` the page's canonical address, & `footer.license` is `{ name, url }`.
105
107
 
106
108
  `norn-base` is the frame of every layout, so its props apply to all of them:
107
109
 
108
110
  | Prop | Shape | Default | Rendered as |
109
111
  | --- | --- | --- | --- |
110
112
  | `lang` | string | `page.lang`, then `en` | `<html lang>` |
111
- | `dir` | `"ltr"`, `"rtl"`, or `"auto"` | `rtl` for a right-to-left `lang`, else `ltr` | `<html dir>`. Right-to-left languages are `ar`, `fa`, `he`, `ur`, `ps`, `yi`, `dv`, `ckb`, `sd`, and `ug`, matched on the primary subtag of `lang` |
113
+ | `dir` | `"ltr"`, `"rtl"`, or `"auto"` | `rtl` for a right-to-left `lang`, else `ltr` | `<html dir>`. Right-to-left languages are `ar`, `fa`, `he`, `ur`, `ps`, `yi`, `dv`, `ckb`, `sd`, & `ug`, matched on the primary subtag of `lang` |
112
114
  | `title` | string | none | `<title>`, as "title: name"; the `<h1>` of the docs layouts |
113
- | `name` | string | none | The brand link, the footer's lead, `og:site_name`, and the hero `<h1>`. Each is left out without one (the brand link also shows for a logo) |
114
- | `tagline` | string | none | After the name in the title and the hero `<h1>`; the footer's text |
115
- | `description` | string | none | `<meta name="description">` and `og:description`; the hero paragraph |
116
- | `url` | string | none | `<link rel="canonical">` and `og:url`. Absolute, for a relative `ogImage` |
115
+ | `name` | string | none | The brand link, the footer's lead, `og:site_name`, & the hero `<h1>`. Each is left out without one (the brand link also shows for a logo) |
116
+ | `tagline` | string | none | After the name in the title & the hero `<h1>`; the footer's text |
117
+ | `description` | string | none | `<meta name="description">` & `og:description`; the hero paragraph |
118
+ | `url` | string | none | `<link rel="canonical">` & `og:url`. Absolute, for a relative `ogImage` |
117
119
  | `ogTitle` | string | the document title | `og:title` |
118
- | `ogImage` | `{ src, alt? }` or a URL | none | `og:image`, `og:image:alt`, and `twitter:card` as `summary_large_image` |
120
+ | `ogImage` | `{ src, alt? }` or a URL | none | `og:image`, `og:image:alt`, & `twitter:card` as `summary_large_image` |
119
121
  | `locale` | string | none | `og:locale`, such as `en_US` |
120
122
  | `themeColor` | string | the page surface in each scheme | `<meta name="theme-color">` |
121
123
  | `themeToggle` | boolean | `false` | An inline script that applies the scheme a `norn-theme-toggle` saved, before the first paint |
@@ -124,54 +126,54 @@ Every list item that links somewhere or describes something has one shape: `href
124
126
  | `logo` | `{ src, alt? }` or a URL | none | An image in the header in place of the name. `alt` falls back to the name |
125
127
  | `nav` | `{ href, title, icon? }[]` | `[]` | The links of `norn-header`. `icon` names a `norn-icon` |
126
128
  | `footer` | object | `{ badges: [], groups: [] }` | `norn-footer`, from the fields below |
127
- | `footer.name`, `footer.text` | string | the name, the tagline | The lead column's title and line |
129
+ | `footer.name`, `footer.text` | string | the name, the tagline | The lead column's title & line |
128
130
  | `footer.license` | `{ name, url }` | none | "Licensed under {license}", linked |
129
131
  | `footer.badges` | `{ href, src, alt, width?, height? }[]` | `[]` | Linked images under the lead |
130
132
  | `footer.groups` | `{ heading, links: { href, title }[] }[]` | `[]` | The footer navigation, one list per group |
131
133
  | `footer.colophon` | string | none | A closing line |
132
134
 
133
- A relative `ogImage.src` resolves against `url`. When `url` is not absolute, the build logs a `norn-head:` warning, leaves out `og:image` and `og:image:alt`, and sets the card to `summary`. A path with a leading slash (`/img/share.png`) resolves against the origin of `url`, one without (`share.png`) against the page. Without an image, no image tag renders and the card is `summary`.
135
+ A relative `ogImage.src` resolves against `url`. When `url` is not absolute, the build logs a `norn-head:` warning, leaves out `og:image` & `og:image:alt`, & sets the card to `summary`. A path with a leading slash (`/img/share.png`) resolves against the origin of `url`, one without (`share.png`) against the page. Without an image, no image tag renders & the card is `summary`.
134
136
 
135
137
  Each of the other layouts adds its own props, below. The base props a layout reads are named under its heading.
136
138
 
137
139
  #### `norn-base.webc`
138
140
 
139
- The page frame: head, skip link, header, `main` around the content, and footer. It takes the props above. It compiles Markdown content once, so a Markdown page's callouts, code fences, and tables render as components, also through a Nunjucks or Liquid layout of the project's or a layout alias. Content that WebC already compiled goes in as it is: a WebC page, a `templateEngineOverride` with `webc`, a `{% renderTemplate "webc" %}` block, and a page on any shipped layout or on a WebC layout of the project's own. A Nunjucks, Liquid, or HTML page renders its components through `{% renderTemplate "webc" %}`. The `nornCompiles` helper decides, from `page.templateSyntax`. Every other shipped layout sets `nornCompiled: true` in its front matter, so the frame leaves the content it compiled; a WebC layout of the project's own that compiles the content and sits deeper in the chain than the page's own layout sets it too.
141
+ The page frame: head, skip link, header, `main` around the content, & footer. It takes the props above. It compiles Markdown content once, so a Markdown page's callouts, code fences, & tables render as components, also through a Nunjucks or Liquid layout of the project's or a layout alias. Content that WebC already compiled goes in as it is: a WebC page, a `templateEngineOverride` with `webc`, a `{% renderTemplate "webc" %}` block, & a page on any shipped layout or on a WebC layout of the project's own. A Nunjucks, Liquid, or HTML page renders its components through `{% renderTemplate "webc" %}`. The `nornCompiles` helper decides, from `page.templateSyntax`. Every other shipped layout sets `nornCompiled: true` in its front matter, so the frame leaves the content it compiled; a WebC layout of the project's own that compiles the content & sits deeper in the chain than the page's own layout sets it too.
140
142
 
141
- A `<section>` directly in `main` is a centered column at `--norn-layout-width`, with block padding, a gap, and a second-level heading style. Its `id` is the anchor. The rule is in the `norn.layout` layer under `:where(main)`, so a project rule overrides it, and a section anywhere else is left alone.
143
+ A `<section>` directly in `main` is a centered column at `--norn-layout-width`, with block padding, a gap, & a second-level heading style. Its `id` is the anchor. The rule is in the `norn.layout` layer under `:where(main)`, so a project rule overrides it, & a section anywhere else is left alone.
142
144
 
143
145
  #### `norn-docs.webc`
144
146
 
145
- A docs page: the sidebar with site search, the title, the contents, the body in `norn-rich-text`, and the pager. Reads `title`.
147
+ A docs page: the sidebar with site search, the title, the contents, the body in `norn-rich-text`, & the pager. Reads `title`.
146
148
 
147
149
  | Prop | Shape | Default | Rendered as |
148
150
  | --- | --- | --- | --- |
149
151
  | `docs` | `{ href, title, icon? }[]` | `[]` | The sidebar's `norn-side-nav`, in order, with the current page marked |
150
152
  | `prev`, `next` | `{ href, title }` | the neighbors in `docs` | The `norn-pager` |
151
- | `search` | `{ bundle?, site? }` or `false` | the Pagefind script at `/pagefind/pagefind.js`, and the host of `url` | `norn-search` above the sidebar list. `false` leaves it out |
153
+ | `search` | `{ bundle?, site? }` or `false` | the Pagefind script at `/pagefind/pagefind.js`, & the host of `url` | `norn-search` above the sidebar list. `false` leaves it out |
152
154
 
153
- The contents come from `nornHeadings` over the body, so they list the headings the Markdown add-on anchors and any `norn-heading-anchor` written by hand. The layout shows the title as the page's h1, so a Markdown body keeps no h1 of its own: a leading `# Title` that repeats the title is dropped, and any other `#` heading renders as an h2 (`singleTitle`). A page 60rem wide or more has them as a sticky column beside the body; narrower, they fold under their title, and the sidebar folds into a closed `<details>` named "Documentation".
155
+ The contents come from `nornHeadings` over the body, so they list the headings the Markdown add-on anchors & any `norn-heading-anchor` written by hand. The layout shows the title as the page's h1, so a Markdown body keeps no h1 of its own: a leading `# Title` that repeats the title is dropped, & any other `#` heading renders as an h2 (`singleTitle`). A page 60rem wide or more has them as a sticky column beside the body; narrower, they fold under their title, & the sidebar folds into a closed `<details>` named "Documentation".
154
156
 
155
157
  #### `norn-docs-home.webc`
156
158
 
157
- The hero with the install sample, then the docs pages as a contents list, then the page body. Reads `name`, `tagline`, and `description`.
159
+ The hero with the install sample, then the docs pages as a contents list, then the page body. Reads `name`, `tagline`, & `description`.
158
160
 
159
161
  | Prop | Shape | Default | Rendered as |
160
162
  | --- | --- | --- | --- |
161
163
  | `lede` | string | `description` | The hero paragraph |
162
164
  | `install` | string | none | The install sample in the hero |
163
165
  | `repo` | string | none | The "View on GitHub" button |
164
- | `docs` | `{ href, title, description? }[]` | `[]` | The contents list, and the "Read the docs" button to the first page |
166
+ | `docs` | `{ href, title, description? }[]` | `[]` | The contents list, & the "Read the docs" button to the first page |
165
167
 
166
168
  #### `norn-landing.webc`
167
169
 
168
- The hero with a sample, then install, features, and packages sections, then the page body. Reads `name`, `tagline`, and `description`.
170
+ The hero with a sample, then install, features, & packages sections, then the page body. Reads `name`, `tagline`, & `description`.
169
171
 
170
172
  | Prop | Shape | Default | Rendered as |
171
173
  | --- | --- | --- | --- |
172
174
  | `lede` | string | `description` | The hero paragraph |
173
175
  | `repo` | string | none | The "View on GitHub" button |
174
- | `license`, `node` | string | none | Facts under the hero, such as "License: MIT" and "Node 24+" |
176
+ | `license`, `node` | string | none | Facts under the hero, such as "License: MIT" & "Node 24+" |
175
177
  | `install` | string | none | The install sample; with `usage`, its own section |
176
178
  | `usage` | `{ language, code, caption? }` | none | The hero's sample |
177
179
  | `features` | `{ title, description, href? }[]` | `[]` | Cards in the features section |
@@ -179,7 +181,7 @@ The hero with a sample, then install, features, and packages sections, then the
179
181
 
180
182
  #### `norn-workspaces-home.webc`
181
183
 
182
- A monorepo's home page: the hero with the install sample, then an index of the workspaces, then the page body. Reads `name`, `tagline`, and `description`.
184
+ A monorepo's home page: the hero with the install sample, then an index of the workspaces, then the page body. Reads `name`, `tagline`, & `description`.
183
185
 
184
186
  | Prop | Shape | Default | Rendered as |
185
187
  | --- | --- | --- | --- |
@@ -195,21 +197,21 @@ One workspace's page in the docs frame. Reads `title`.
195
197
  | Prop | Shape | Default | Rendered as |
196
198
  | --- | --- | --- | --- |
197
199
  | `workspaces` | `{ href, title, icon? }[]` | `[]` | The sidebar |
198
- | `workspace` | `{ name, description?, html? }` | none | The `<h1>`, the description, and `html`, compiled as WebC. The page's own content follows |
200
+ | `workspace` | `{ name, description?, html? }` | none | The `<h1>`, the description, & `html`, compiled as WebC. The page's own content follows |
199
201
 
200
- On the three home layouts, the page body follows the shipped sections in a `<section>` of its own, in `norn-rich-text`, and a page with no body renders none. Markdown there is compiled once, as on `norn-docs`.
202
+ On the three home layouts, the page body follows the shipped sections in a `<section>` of its own, in `norn-rich-text`, & a page with no body renders none. Markdown there is compiled once, as on `norn-docs`.
201
203
 
202
204
  The layouts' texts ("Get started", "Read the docs", "Packages") come from `componentStrings` under the layout's name, then English (see Translating).
203
205
 
204
206
  ### Copy a layout
205
207
 
206
- The layouts ship as files in the package's `layouts/` folder. To change one, copy it into the project and turn the shipped one off:
208
+ The layouts ship as files in the package's `layouts/` folder. To change one, copy it into the project & turn the shipped one off:
207
209
 
208
210
  1. Leave the layout out: `config.addPlugin(norn, { layouts: ["norn-docs"], props })`. The other five stay.
209
211
  2. Copy `node_modules/@allons-y/webc-patterns/layouts/norn-docs.webc` into the project's layouts folder (`_includes/` unless `dir.layouts` says otherwise).
210
- 3. Edit the copy. It still extends `norn-base.webc` and reads `$data.norn`, so the mapping needs no change.
212
+ 3. Edit the copy. It still extends `norn-base.webc` & reads `$data.norn`, so the mapping needs no change.
211
213
 
212
- A file in the layouts folder with the name of a shipped layout, and no matching entry in `layouts`, fails the build: it would replace the shipped one without a word. A copy of `norn-base.webc` also needs the text of `css/base.css` and `css/print.css` pasted over its two style placeholders, which the plugin fills only in the shipped file.
214
+ A file in the layouts folder with the name of a shipped layout, & no matching entry in `layouts`, fails the build: it would replace the shipped one without a word. A copy of `norn-base.webc` also needs the text of `css/base.css` & `css/print.css` pasted over its two style placeholders, which the plugin fills only in the shipped file.
213
215
 
214
216
  ### Hooks
215
217
 
@@ -228,11 +230,11 @@ import { fillHooks } from "@allons-y/webc-patterns/plugin";
228
230
  fillHooks(config, { head: '<link rel="stylesheet" href="/assets/tokens.css" webc:keep />', credit: "<my-credit></my-credit>" });
229
231
  ```
230
232
 
231
- A fill is WebC markup, compiled with the page, so it may call components; give a `<link>` or `<script src>` `webc:keep`. Fills are appended in the order they are added, before or after the plugin. An unknown hook name throws, and no page data can change a hook: `norn-base` reads them through the `nornHook` helper. Fills are trusted markup, written in the config.
233
+ A fill is WebC markup, compiled with the page, so it may call components; give a `<link>` or `<script src>` `webc:keep`. Fills are appended in the order they are added, before or after the plugin. An unknown hook name throws, & no page data can change a hook: `norn-base` reads them through the `nornHook` helper. Fills are trusted markup, written in the config.
232
234
 
233
235
  ### Theming
234
236
 
235
- A shared token is a custom property named for its path in `tokens.json`: `ui.border.color` is `--norn-ui-border-color`. Every component reads its tokens with the default compiled in as the fallback, so nothing has to define them and a component renders the same with none set. Set any of them in the project's own `tokens.css`, unlayered, and every component follows. Link it from the `head` hook, and copy the file to the output:
237
+ A shared token is a custom property named for its path in `tokens.json`: `ui.border.color` is `--norn-ui-border-color`. Every component reads its tokens with the default compiled in as the fallback, so nothing has to define them & a component renders the same with none set. Set any of them in the project's own `tokens.css`, unlayered, & every component follows. Link it from the `head` hook, & copy the file to the output:
236
238
 
237
239
  ```css
238
240
  /* assets/tokens.css */
@@ -248,7 +250,7 @@ fillHooks(config, { head: '<link rel="stylesheet" href="/assets/tokens.css" webc
248
250
  config.addPassthroughCopy("assets");
249
251
  ```
250
252
 
251
- The accent drives the link color (`--norn-text-accent`) and the text on an accent surface (`--norn-text-on-accent`) too, since their defaults are derived from it. The heading face (`--norn-font-heading`) follows the body face until it is set. The properties sit in no layer, so the order of the stylesheets does not matter. To read a token in the project's own CSS, give it the default as the fallback, as the components do: `var(--norn-space-m, 1.25rem)`. The component preview lists every token with its default.
253
+ The accent drives the link color (`--norn-text-accent`) & the text on an accent surface (`--norn-text-on-accent`) too, since their defaults are derived from it. The heading face (`--norn-font-heading`) follows the body face until it is set. The properties sit in no layer, so the order of the stylesheets does not matter. To read a token in the project's own CSS, give it the default as the fallback, as the components do: `var(--norn-space-m, 1.25rem)`. The component preview lists every token with its default.
252
254
 
253
255
  | Group | Tokens |
254
256
  | --- | --- |
@@ -261,7 +263,7 @@ The accent drives the link color (`--norn-text-accent`) and the text on an accen
261
263
  | `layout` | `--norn-layout-width` |
262
264
  | `status` | `--norn-status-info`, `--norn-status-success`, `--norn-status-warning`, `--norn-status-danger` |
263
265
 
264
- A theme is a file like that, plus the components and hook fills it adds through the plugin's `components` option and `fillHooks`. A theme styles a component with CSS against its tag, in its own layer; no component exposes a custom property of its own. [`@allons-y/webc-oss-site`](https://github.com/allonsy-studio/webc-resources/tree/main/packages/webc-oss-site#readme) is a theme. Its `css/tokens.css` sets the mono face, its `css/brand.css` draws the header strip and styles the footer name by the component tags, both in the `norn.oss` layer, and its plugin fills all four hooks:
266
+ A theme is a file like that, plus the components & hook fills it adds through the plugin's `components` option & `fillHooks`. A theme styles a component with CSS against its tag, in its own layer; no component exposes a custom property of its own. [`@allons-y/webc-oss-site`](https://github.com/allonsy-studio/webc-resources/tree/main/packages/webc-oss-site#readme) is a theme. Its `css/tokens.css` sets the mono face, its `css/brand.css` draws the header strip & styles the footer name by the component tags, both in the `norn.oss` layer, & its plugin fills all four hooks:
265
267
 
266
268
  ```css
267
269
  @layer norn.oss {
@@ -281,7 +283,7 @@ A theme is a file like that, plus the components and hook fills it adds through
281
283
 
282
284
  ### Collisions
283
285
 
284
- Tag names are the file names of the components: `norn-*` for the patterns, and `oss-*` for the OSS site theme. A project component with the tag of a packaged one, a `norn-link-button.webc` of its own, fails the build with "Global component name collision". Give the project's components another prefix, or replace a packaged one on purpose by leaving it out:
286
+ Tag names are the file names of the components: `norn-*` for the patterns, & `oss-*` for the OSS site theme. A project component with the tag of a packaged one, a `norn-link-button.webc` of its own, fails the build with "Global component name collision". Give the project's components another prefix, or replace a packaged one on purpose by leaving it out:
285
287
 
286
288
  ```js
287
289
  config.addPlugin(norn, { props, exclude: ["norn-card"], components: ["_includes/*.webc"] });
@@ -295,7 +297,7 @@ config.addPlugin(norn, { props, exclude: ["norn-card"], components: ["_includes/
295
297
 
296
298
  ### The reset
297
299
 
298
- `css/reset.css` is optional, in the `norn.reset` layer: the components and layouts look the same without it. The plugin exports its path as `reset`. Copy it to the site and link it from the head hook, or embed its text:
300
+ `css/reset.css` is optional, in the `norn.reset` layer: the components & layouts look the same without it. The plugin exports its path as `reset`. Copy it to the site & link it from the head hook, or embed its text:
299
301
 
300
302
  ```js
301
303
  import fs from "node:fs";
@@ -306,7 +308,7 @@ fillHooks(config, { head: `<style>${fs.readFileSync(reset, "utf8")}</style>` });
306
308
 
307
309
  ### A layout of the project's own
308
310
 
309
- A layout that does not extend `norn-base` calls `norn-head` in its head, which declares the layer order and links the bundles, and adds the base and print styles, `css/base.css` and `css/print.css` (the plugin exports their paths as `base` and `print`):
311
+ A layout that does not extend `norn-base` calls `norn-head` in its head, which declares the layer order & links the bundles, & adds the base & print styles, `css/base.css` & `css/print.css` (the plugin exports their paths as `base` & `print`):
310
312
 
311
313
  ```html
312
314
  <!doctype html>
@@ -323,15 +325,15 @@ A layout that does not extend `norn-base` calls `norn-head` in its head, which d
323
325
  </html>
324
326
  ```
325
327
 
326
- - **Render a Markdown page's `content` with `@html`.** WebC then compiles the `norn-callout` and `norn-code-block` tags the add-ons write.
327
- - **Call head components with `<meta webc:is>`.** A custom tag in `<head>` makes the parser close the head early, so a head component is called as a `<meta>` with `webc:is` and `webc:nokeep`. A void tag has no children, so `norn-head` has no slot: write extra head tags after it.
328
+ - **Render a Markdown page's `content` with `@html`.** WebC then compiles the `norn-callout` & `norn-code-block` tags the add-ons write.
329
+ - **Call head components with `<meta webc:is>`.** A custom tag in `<head>` makes the parser close the head early, so a head component is called as a `<meta>` with `webc:is` & `webc:nokeep`. A void tag has no children, so `norn-head` has no slot: write extra head tags after it.
328
330
  - **The layer order comes first.** `norn-head` declares it in an inline style ahead of the bundle links, so it holds whatever order the page's styles bundle in.
329
- - **Link bundles with `webc:keep`.** WebC removes `<link>` and `<script src>` without it. `norn-head` does this for the bundles.
330
- - **Every packaged layer sits inside one `norn` layer:** `norn.reset`, `norn.base`, `norn.layout`, `norn.components`, `norn.oss`, `norn.print`. A project that uses its own layers, or a framework such as Tailwind, orders the whole package with one name, as in `@layer norn, base, components, utilities;`, and no layer names collide.
331
+ - **Link bundles with `webc:keep`.** WebC removes `<link>` & `<script src>` without it. `norn-head` does this for the bundles.
332
+ - **Every packaged layer sits inside one `norn` layer:** `norn.reset`, `norn.base`, `norn.layout`, `norn.components`, `norn.oss`, `norn.print`. A project that uses its own layers, or a framework such as Tailwind, orders the whole package with one name, as in `@layer norn, base, components, utilities;`, & no layer names collide.
331
333
  - **Project CSS stays unlayered.** It beats every layer without `!important`.
332
334
  - **`.visually-hidden` hides text but keeps it read.** `css/base.css` defines it. It shows again while it or a child has focus.
333
335
 
334
- Copy the two stylesheets to the output with `addPassthroughCopy`, or embed their text as `norn-base` does. See the [neutral fixture](https://github.com/allonsy-studio/webc-resources/tree/main/fixtures/neutral) for a working site on the plugin and the layouts, with no theme.
336
+ Copy the two stylesheets to the output with `addPassthroughCopy`, or embed their text as `norn-base` does. See the [neutral fixture](https://github.com/allonsy-studio/webc-resources/tree/main/fixtures/neutral) for a working site on the plugin & the layouts, with no theme.
335
337
 
336
338
  ### Markdown
337
339
 
@@ -348,15 +350,15 @@ config.amendLibrary("md", headingAnchors);
348
350
  config.addJavaScriptFunction("nornHeadings", headings);
349
351
  ```
350
352
 
351
- - `webcTags` keeps a component tag with WebC attributes (`@variant`, `:@level`, `webc:if`) as HTML. Without it, markdown-it escapes the tag to text: CommonMark's attribute names have no `@`. It acts only on custom elements, names with a hyphen, and only when the library's `html` option is on, as it is in Eleventy. It also gives a `<script>` or a `<link>` in Markdown `webc:keep`, so an embed (`<script async src="https://…">`, `<script src="/local.js">`) reaches the browser as written: without it, WebC fails the build on a full URL or a site path, and moves an inline script into the page bundle. Where a layout inserts the HTML without compiling it, the attribute stays in the output, where browsers ignore it.
353
+ - `webcTags` keeps a component tag with WebC attributes (`@variant`, `:@level`, `webc:if`) as HTML. Without it, markdown-it escapes the tag to text: CommonMark's attribute names have no `@`. It acts only on custom elements, names with a hyphen, & only when the library's `html` option is on, as it is in Eleventy. It also gives a `<script>` or a `<link>` in Markdown `webc:keep`, so an embed (`<script async src="https://…">`, `<script src="/local.js">`) reaches the browser as written: without it, WebC fails the build on a full URL or a site path, & moves an inline script into the page bundle. Where a layout inserts the HTML without compiling it, the attribute stays in the output, where browsers ignore it.
352
354
  - `scrollTables` wraps each table in `norn-simple-table`.
353
355
  - `callouts` turns a `> [!NOTE]` blockquote into a `norn-callout` of that kind.
354
- - `singleTitle` keeps a page on `norn-docs` or `norn-workspace`, which show the title as their h1, to one h1: a leading `# Title` that repeats the title goes, and any other h1 becomes an h2. `{ layouts }` names a project layout that shows the title too.
355
- - `headingAnchors` renders each h2 and h3 as `norn-heading-anchor`, with an id from `slugify`, as an author writes it: `## Install` becomes `<norn-heading-anchor @slug="install">Install</norn-heading-anchor>`. A heading whose text holds a link keeps that link and gets the id only, since a link can't hold another. Like the callouts, the tag needs WebC to compile it: on a layout that doesn't, such as a Nunjucks one of the project's own, or in a feed built from `templateContent`, it stays an uncompiled tag.
356
+ - `singleTitle` keeps a page on `norn-docs` or `norn-workspace`, which show the title as their h1, to one h1: a leading `# Title` that repeats the title goes, & any other h1 becomes an h2. `{ layouts }` names a project layout that shows the title too.
357
+ - `headingAnchors` renders each h2 & h3 as `norn-heading-anchor`, with an id from `slugify`, as an author writes it: `## Install` becomes `<norn-heading-anchor @slug="install">Install</norn-heading-anchor>`. A heading whose text holds a link keeps that link & gets the id only, since a link can't hold another. Like the callouts, the tag needs WebC to compile it: on a layout that doesn't, such as a Nunjucks one of the project's own, or in a feed built from `templateContent`, it stays an uncompiled tag.
356
358
  - `headings(html)` lists the anchored headings for `norn-toc`, from compiled HTML or from Markdown output that WebC has not compiled yet, `norn-heading-anchor` tags included. Register it as `nornHeadings`.
357
- - `slugify(text)` is the id function `headingAnchors` uses. It keeps the letters and digits of any script (`## 日本語の見出し` gets `id="日本語の見出し"`), lowercases them, takes the accents off Latin letters, and joins the rest with hyphens. A repeated heading gets `-2`, `-3`, and so on, counted across the page, so two Markdown blocks on one page never share an id.
359
+ - `slugify(text)` is the id function `headingAnchors` uses. It keeps the letters & digits of any script (`## 日本語の見出し` gets `id="日本語の見出し"`), lowercases them, takes the accents off Latin letters, & joins the rest with hyphens. A repeated heading gets `-2`, `-3`, & so on, counted across the page, so two Markdown blocks on one page never share an id.
358
360
 
359
- The add-on writes only what an author would write by hand, and no text of its own. A callout title, a code block's labels, and every other string come from the component's slots, which fall back to `componentStrings` and then English (see Translating).
361
+ The add-on writes only what an author would write by hand, & no text of its own. A callout title, a code block's labels, & every other string come from the component's slots, which fall back to `componentStrings` & then English (see Translating).
360
362
 
361
363
  ### Components in Markdown
362
364
 
@@ -379,18 +381,18 @@ Write a component in Markdown as its tag, with its props, as in a WebC file. Two
379
381
  </norn-callout>
380
382
  ```
381
383
 
382
- plugin-webc renders each block with the site's Markdown library, so the add-ons apply, and the page then compiles the output. Keep the Markdown in a block flush left: an indented `## Heading` renders as a paragraph. `11ty:type="liquid,md"` runs Liquid first, for page data.
384
+ plugin-webc renders each block with the site's Markdown library, so the add-ons apply, & the page then compiles the output. Keep the Markdown in a block flush left: an indented `## Heading` renders as a paragraph. `11ty:type="liquid,md"` runs Liquid first, for page data.
383
385
 
384
386
  In both routes:
385
387
 
386
- - A block component takes its open tag alone on a line, a blank line, the Markdown body, a blank line, and its close tag alone on a line. Without the blank lines, CommonMark keeps the body as raw HTML, so `**bold**` stays literal. On one line with text, the component lands inside a `<p>`.
388
+ - A block component takes its open tag alone on a line, a blank line, the Markdown body, a blank line, & its close tag alone on a line. Without the blank lines, CommonMark keeps the body as raw HTML, so `**bold**` stays literal. On one line with text, the component lands inside a `<p>`.
387
389
  - An inline component, such as `norn-icon` or `norn-link-button`, may sit in a sentence.
388
390
  - A tag fits on one line. CommonMark reads no HTML tag whose attributes span lines.
389
- - Code spans and fences stay literal: `` `<norn-icon @name="info">` `` shows the tag.
390
- - A `<script>` or `<link>` stays where it is written, for the browser, and WebC's bundler leaves it alone.
391
+ - Code spans & fences stay literal: `` `<norn-icon @name="info">` `` shows the tag.
392
+ - A `<script>` or `<link>` stays where it is written, for the browser, & WebC's bundler leaves it alone.
391
393
  - The content is compiled as WebC, so it is as trusted as a `.webc` file: a dynamic attribute (`:data-x="…"`) runs at build time. Render content from outside the repository with `markdown: false` or through `@raw`.
392
394
 
393
- Avoid `markdownTemplateEngine: "webc"`. WebC then runs before Markdown, and Markdown parses WebC's output again: a blank line in a component's output turns into a paragraph, `> [!TIP]` loses its `>`, a tag in a fence is compiled, so a code sample shows rendered HTML, and any dynamic attribute in the page text runs at build time. Keep `markdownTemplateEngine: false`, as in Setup, and use one of the two routes above.
395
+ Avoid `markdownTemplateEngine: "webc"`. WebC then runs before Markdown, & Markdown parses WebC's output again: a blank line in a component's output turns into a paragraph, `> [!TIP]` loses its `>`, a tag in a fence is compiled, so a code sample shows rendered HTML, & any dynamic attribute in the page text runs at build time. Keep `markdownTemplateEngine: false`, as in Setup, & use one of the two routes above.
394
396
 
395
397
  ### Project data
396
398
 
@@ -398,8 +400,8 @@ Avoid `markdownTemplateEngine: "webc"`. WebC then runs before Markdown, and Mark
398
400
 
399
401
  | Export | Purpose |
400
402
  | --- | --- |
401
- | `buildSite({ packagePath, projectPath, host })` | Site facts from `package.json` and `project.json`: name, tagline, description, license, repository links, the Node version, and the URL (the project's, or `host` plus the repository name) |
402
- | `buildWorkspaces({ root })` | One entry per workspace, an item for `workspaces`: `href`, `title`, and `description`, with the package `name` and `slug`, and its SKILL.md or README.md as `html`, rendered with the site's Markdown add-ons, so headings get the same ids and anchors as a docs page. The README's raw HTML renders as HTML, as on GitHub. Nothing is sanitized: the README is the project's own, and `norn-workspace` compiles it as WebC, as trusted as any page |
403
+ | `buildSite({ packagePath, projectPath, host })` | Site facts from `package.json` & `project.json`: name, tagline, description, license, repository links, the Node version, & the URL (the project's, or `host` plus the repository name) |
404
+ | `buildWorkspaces({ root })` | One entry per workspace, an item for `workspaces`: `href`, `title`, & `description`, with the package `name` & `slug`, & its SKILL.md or README.md as `html`, rendered with the site's Markdown add-ons, so headings get the same ids & anchors as a docs page. The README's raw HTML renders as HTML, as on GitHub. Nothing is sanitized: the README is the project's own, & `norn-workspace` compiles it as WebC, as trusted as any page |
403
405
  | `npmRelease(packageName)` | Whether the package has a `latest` release on npm, for badges. Cached an hour; offline is no release |
404
406
  | `accentHex(cssPath)` | The `--norn-ui-accent` hex digits a stylesheet sets, for a badge color |
405
407
 
@@ -407,40 +409,40 @@ The `project.json` schema is `@allons-y/webc-patterns/schema/project.schema.json
407
409
 
408
410
  ## Components
409
411
 
410
- | Component | Props and slots | Purpose |
412
+ | Component | Props & slots | Purpose |
411
413
  | --- | --- | --- |
412
- | `norn-head` | `@title`, `@description`, `@url`, `@theme-color`, `@og-title`, `@og-image`, `@og-image-alt`, `:@theme-toggle` | Head metadata, Open Graph and Twitter card tags, the layer order, and the bundle links. `norn-base` calls it |
414
+ | `norn-head` | `@title`, `@description`, `@url`, `@theme-color`, `@og-title`, `@og-image`, `@og-image-alt`, `:@theme-toggle` | Head metadata, Open Graph & Twitter card tags, the layer order, & the bundle links. `norn-base` calls it |
413
415
  | `norn-skip-link` | `@href`, slot `label` (the text) | Link past the page chrome to the main content |
414
- | `norn-header` | slot `brand` (the home link), default slot (the links), slots `label` and `summary`, `@logo-height` (a CSS length, 2rem by default) | Brand link and primary navigation. Below 40rem the links fold into a closed `<details>` |
415
- | `norn-footer` | default slot (the columns), slot `colophon`, `@width` and `@lead-width` (CSS lengths) | Footer lock-up: a lead column up to 25rem, then columns sized to their content |
416
+ | `norn-header` | slot `brand` (the home link), default slot (the links), slots `label` & `summary`, `@logo-height` (a CSS length, 2rem by default) | Brand link & primary navigation. Below 40rem the links fold into a closed `<details>` |
417
+ | `norn-footer` | default slot (the columns), slot `colophon`, `@width` & `@lead-width` (CSS lengths) | Footer lock-up: a lead column up to 25rem, then columns sized to their content |
416
418
  | `norn-hero` | `@width` (`layout` or `measure`), default slot (h1, lede, actions, `norn-fact-list` with `@gap="l"`, extras) | Page intro |
417
- | `norn-card` | `@accent` (`accent`, `info`, `success`, `warning`, `danger`, or `none`), `@title-face` (`mono`), default slot (a heading, then the body) | Card on a raised surface. A heading link covers the whole card, and an accent draws a 2px line along the top |
419
+ | `norn-card` | `@accent` (`accent`, `info`, `success`, `warning`, `danger`, or `none`), `@title-face` (`mono`), default slot (a heading, then the body) | Card on a raised surface. A heading link covers the whole card, & an accent draws a 2px line along the top |
418
420
  | `norn-card-grid` | `@gap` (`s`, `m`, or `l`), `@min` (a length), default slot (`<li>` cards) | Responsive grid of cards |
419
- | `norn-side-nav` | `:@items` (`{ href, title, icon?, children? }[]`), slots `label` and `search` | Page links, with `aria-current` on the current page and its parent |
420
- | `norn-sidebar-layout` | slots `nav` and `summary`, default slot (the body, from its h1), `@nav-width` (a CSS length), `@gap` (`s`, `m`, `l`, `xl`), `@columns` (the body's grid columns) | Navigation column beside a page body. The navigation stays in view; below 37.5rem the columns stack and the navigation folds into a closed `<details>` |
421
+ | `norn-side-nav` | `:@items` (`{ href, title, icon?, children? }[]`), slots `label` & `search` | Page links, with `aria-current` on the current page & its parent |
422
+ | `norn-sidebar-layout` | slots `nav` & `summary`, default slot (the body, from its h1), `@nav-width` (a CSS length), `@gap` (`s`, `m`, `l`, `xl`), `@columns` (the body's grid columns) | Navigation column beside a page body. The navigation stays in view; below 37.5rem the columns stack & the navigation folds into a closed `<details>` |
421
423
  | `norn-rich-text` | `@measure` (`text` or `none`), default slot | Prose styles for long-form content |
422
424
  | `norn-pager` | `:@prev`, `:@next` (`{ href, title }`), slot `label` | Links to the neighboring pages |
423
425
  | `norn-link-button` | `@href`, `@variant` (`primary`, `secondary`, or `quiet`), `@tone` (`accent` or `current`), default slot | A link styled as a button, for a call to action |
424
426
  | `norn-fact-list` | `@gap` (`xs`, `s`, `m`, or `l`, the space between rows), default slot (`<li>` facts) | A row of short facts, each with an optional icon |
425
- | `norn-segmented-control` | `:@icon-only`, slot `legend` (required), default slot of `<label>` + radio, or `@name` and `:@options` | One choice from a few, from native radios. The checked option is tinted in the accent. A missing legend fails the build |
427
+ | `norn-segmented-control` | `:@icon-only`, slot `legend` (required), default slot of `<label>` + radio, or `@name` & `:@options` | One choice from a few, from native radios. The checked option is tinted in the accent. A missing legend fails the build |
426
428
  | `norn-theme-toggle` | slots `label`, `light`, `system`, `dark` | Light, system, or dark, saved in this browser. Hidden without a script. Pair with `norn-head`'s `:@theme-toggle="true"` |
427
- | `norn-copy-button` | `@value` or `@source` (an id), `:@compact`, `@surface` (`base`, `raised`, or `code`), slots `copy`, `copied`, `failed`, `label` | Copies text and says so. Hidden where the Clipboard API is missing |
428
- | `norn-simple-table` | default slot (a `<table>`), slot `label` | A wide table scrolls in its own box, with a shadow at the edge that has more, and a tab stop named by the caption while it scrolls |
429
- | `norn-notice` | `@severity` (`info`, `success`, `warning`, `danger`), `:@live`, default slot, slots `prefix` and `action` | A message about the page, with an icon and a severity word read first |
429
+ | `norn-copy-button` | `@value` or `@source` (an id), `:@compact`, `@surface` (`base`, `raised`, or `code`), slots `copy`, `copied`, `failed`, `label` | Copies text & says so. Hidden where the Clipboard API is missing |
430
+ | `norn-simple-table` | default slot (a `<table>`), slot `label` | A wide table scrolls in its own box, with a shadow at the edge that has more, & a tab stop named by the caption while it scrolls |
431
+ | `norn-notice` | `@severity` (`info`, `success`, `warning`, `danger`), `:@live`, default slot, slots `prefix` & `action` | A message about the page, with an icon & a severity word read first |
430
432
  | `norn-callout` | `@variant` (`note`, `tip`, `important`, `warning`, `caution`), slot `heading`, default slot | An aside in long-form text, set like a pull quote on a raised panel with an eyebrow label, as `role="note"`. For a status message, use `norn-notice` |
431
433
  | `norn-accordion` | default slot (`<details>` sections) | Disclosures from native `details`. Give them one `name` to open one at a time |
432
434
  | `norn-heading-anchor` | `@slug`, `:@level`, default slot | A heading whose text links to its own section |
433
435
  | `norn-toc` | `:@items` (`{ id, text, level }[]`, from `nornHeadings`), `@fold` (`always`, or `narrow` below 60rem), slot `label` | The page's contents, marking the section in view. Folds under its title as `@fold` says |
434
436
  | `norn-breadcrumbs` | `:@items` (`{ href, title }[]`), slot `label` | The trail to the current page, with chevrons that mirror in right-to-left text |
435
- | `norn-code-tabs` | `@group`, default slot (`norn-code-block`s), slots `label` and `sample` | Tabs over code samples, kept in step by group; stacked without a script |
437
+ | `norn-code-tabs` | `@group`, default slot (`norn-code-block`s), slots `label` & `sample` | Tabs over code samples, kept in step by group; stacked without a script |
436
438
  | `norn-search` | `@bundle`, `@action`, `@site`, slots `label`, `none`, `results-one`, `results-other` (and `-zero`, `-two`, `-few`, `-many`) | Live results from a Pagefind index; a plain web search form without one |
437
439
  | `norn-icon` | `@name`, `@size` (`s` or `m`), slot `label` | An inline icon from a built-in set or the plugin's `icons` option, at 1em, or the title size for `m`. Decorative unless it has a label; directional icons mirror in right-to-left text |
438
440
 
439
- A component is styled through the shared tokens in `tokens.json` (`--norn-ui-accent`, `--norn-ui-border-color`, `--norn-space-m`, and so on). The component preview lists the tokens each one reads, from its source. Each token falls back to its default, compiled into the component, so a component renders the same with no tokens set, and no stylesheet defines them. A custom property of a component's own is not part of its documented API.
441
+ A component is styled through the shared tokens in `tokens.json` (`--norn-ui-accent`, `--norn-ui-border-color`, `--norn-space-m`, & so on). The component preview lists the tokens each one reads, from its source. Each token falls back to its default, compiled into the component, so a component renders the same with no tokens set, & no stylesheet defines them. A custom property of a component's own is not part of its documented API.
440
442
 
441
443
  ### Pager
442
444
 
443
- `norn-docs` links the neighbors itself. In a page of the project's own, `adjacent` returns the pages around the current one: map them to `{ href, title }` at the call site, and keep the lookup in the expression: a `webc:setup` block in a layout runs once per template, so a value computed there is shared by every page.
445
+ `norn-docs` links the neighbors itself. In a page of the project's own, `adjacent` returns the pages around the current one: map them to `{ href, title }` at the call site, & keep the lookup in the expression: a `webc:setup` block in a layout runs once per template, so a value computed there is shared by every page.
444
446
 
445
447
  ```html
446
448
  <norn-pager
@@ -453,7 +455,7 @@ Here `link` is a pure helper in the layout's `webc:setup` block: `(page) => (pag
453
455
 
454
456
  ### Translating
455
457
 
456
- Every string a visitor sees or hears, accessible names and text a script shows included, is content in the page: a named slot on the component. Fill the slot on the tag for one instance, as `<span slot="label">Rechercher</span>`. An empty slot falls back to the site's strings from `componentStrings`, then to the English default:
458
+ Every string a visitor sees or hears, accessible names & text a script shows included, is content in the page: a named slot on the component. Fill the slot on the tag for one instance, as `<span slot="label">Rechercher</span>`. An empty slot falls back to the site's strings from `componentStrings`, then to the English default:
457
459
 
458
460
  ```html
459
461
  <norn-search @bundle="/pagefind/pagefind.js">
@@ -463,7 +465,7 @@ Every string a visitor sees or hears, accessible names and text a script shows i
463
465
  </norn-search>
464
466
  ```
465
467
 
466
- A line break inside the tag leaves every named slot's fallback in place, so `<norn-skip-link>` and `</norn-skip-link>` can sit on their own lines. Set the strings once for the whole site with the plugin's `strings` option, keyed by component and slot name. The plugin registers them through `componentStrings`:
468
+ A line break inside the tag leaves every named slot's fallback in place, so `<norn-skip-link>` & `</norn-skip-link>` can sit on their own lines. Set the strings once for the whole site with the plugin's `strings` option, keyed by component & slot name. The plugin registers them through `componentStrings`:
467
469
 
468
470
  ```js
469
471
  config.addPlugin(norn, {
@@ -478,25 +480,25 @@ config.addPlugin(norn, {
478
480
  });
479
481
  ```
480
482
 
481
- A layout has no caller to fill a slot, so its texts are keys of their own under the layout's name: `norn-landing`'s `start` ("Get started") and `license` ("License: {license}"), `norn-base`'s `license` ("Licensed under {license}"), and so on. `defaultStrings` lists every component, layout, and key with its English default. A key is the slot it fills; `norn-callout`'s `heading` and `norn-notice`'s `prefix` take one key per variant or severity, and `norn-code-block` reads its button texts from the `norn-copy-button` keys. An unknown component or key fails the build with the list of valid ones. For a site in several languages, key the tables by language tag, as `{ fr: { … }, de: { … } }`: each page picks its table by `page.lang`, which [Eleventy's i18n plugin](https://www.11ty.dev/docs/plugins/i18n/) sets from the URL (`/fr/…`), and `fr-CA` falls back to `fr`. `norn-base` sets `<html lang>` from the `lang` prop, which falls back to `page.lang`. Then:
483
+ A layout has no caller to fill a slot, so its texts are keys of their own under the layout's name: `norn-landing`'s `start` ("Get started") & `license` ("License: {license}"), `norn-base`'s `license` ("Licensed under {license}"), & so on. `defaultStrings` lists every component, layout, & key with its English default. A key is the slot it fills; `norn-callout`'s `heading` & `norn-notice`'s `prefix` take one key per variant or severity, & `norn-code-block` reads its button texts from the `norn-copy-button` keys. An unknown component or key fails the build with the list of valid ones. For a site in several languages, key the tables by language tag, as `{ fr: { … }, de: { … } }`: each page picks its table by `page.lang`, which [Eleventy's i18n plugin](https://www.11ty.dev/docs/plugins/i18n/) sets from the URL (`/fr/…`), & `fr-CA` falls back to `fr`. `norn-base` sets `<html lang>` from the `lang` prop, which falls back to `page.lang`. Then:
482
484
 
483
- - A landmark names itself with `aria-labelledby`, pointing at its own text: the title of `norn-toc`, or visually hidden text in `norn-header`, `norn-side-nav`, `norn-pager`, and `norn-breadcrumbs`.
484
- - Text only a script shows sits in the page as hidden elements that the script reads: `norn-copy-button`'s texts, `norn-search`'s statuses, `norn-theme-toggle`'s names, and `norn-code-tabs`'s fallback tab name.
485
- - `norn-search` picks `results-one`, `results-other`, or another plural form with `Intl.PluralRules` for the nearest `lang`, and formats the count for it. `{count}` stands in for the number. An empty form falls back to `results-other`.
485
+ - A landmark names itself with `aria-labelledby`, pointing at its own text: the title of `norn-toc`, or visually hidden text in `norn-header`, `norn-side-nav`, `norn-pager`, & `norn-breadcrumbs`.
486
+ - Text only a script shows sits in the page as hidden elements that the script reads: `norn-copy-button`'s texts, `norn-search`'s statuses, `norn-theme-toggle`'s names, & `norn-code-tabs`'s fallback tab name.
487
+ - `norn-search` picks `results-one`, `results-other`, or another plural form with `Intl.PluralRules` for the nearest `lang`, & formats the count for it. `{count}` stands in for the number. An empty form falls back to `results-other`.
486
488
  - `norn-notice`'s `prefix` carries its own punctuation, as in `"Remarque : "`.
487
489
  - `norn-simple-table` without a caption or a `label` slot names its scroll box from the column headings, so it needs no string of its own.
488
490
  - Markdown callouts take their titles from `componentStrings`, like any callout: the Markdown add-on writes no `heading` slot.
489
- - `norn-pager`'s arrows are drawn in CSS and point the other way in right-to-left text.
491
+ - `norn-pager`'s arrows are drawn in CSS & point the other way in right-to-left text.
490
492
 
491
493
  ## Exports
492
494
 
493
495
  | Export | Purpose |
494
496
  | --- | --- |
495
497
  | `patterns({ exclude })` | Absolute glob for the package's components, minus any names in `exclude` |
496
- | `adjacent(pages, page)` | Previous and next page in a collection, for `norn-pager` |
498
+ | `adjacent(pages, page)` | Previous & next page in a collection, for `norn-pager` |
497
499
  | `dropEmptyBundleTags` | Eleventy transform that drops the empty bundle tags a page without bundles is left with |
498
- | `componentStrings(table)` | Site-wide fallbacks for the strings, by component or layout and key, or by language tag, as the `nornComponentStrings` helper |
499
- | `defaultStrings` | Every component and layout string and its English default, by name and key |
500
+ | `componentStrings(table)` | Site-wide fallbacks for the strings, by component or layout & key, or by language tag, as the `nornComponentStrings` helper |
501
+ | `defaultStrings` | Every component & layout string & its English default, by name & key |
500
502
 
501
503
  `@allons-y/webc-patterns/plugin` exports the plugin:
502
504
 
@@ -504,8 +506,8 @@ A layout has no caller to fill a slot, so its texts are keys of their own under
504
506
  | --- | --- |
505
507
  | `default` | The Norn plugin, `config.addPlugin(norn, options)` |
506
508
  | `fillHooks(config, { head, banner, cta, credit })` | Adds markup to `norn-base`'s hooks |
507
- | `reset`, `base`, `print` | Absolute paths to `css/reset.css`, `css/base.css`, and `css/print.css` |
508
- | `HOOKS`, `LAYOUTS` | The hook names and the layout names |
509
+ | `reset`, `base`, `print` | Absolute paths to `css/reset.css`, `css/base.css`, & `css/print.css` |
510
+ | `HOOKS`, `LAYOUTS` | The hook names & the layout names |
509
511
  | `compiles(data, layout, aliases)` | Whether a shipped layout compiles the page content: the rule behind `nornCompiles` |
510
512
 
511
513
  `@allons-y/webc-patterns/markdown` exports the Markdown add-on:
@@ -515,16 +517,19 @@ A layout has no caller to fill a slot, so its texts are keys of their own under
515
517
  | `webcTags(md)` | markdown-it amend that keeps component tags with WebC attributes, such as `@variant`, as HTML |
516
518
  | `scrollTables(md)` | markdown-it amend that wraps each table in `norn-simple-table` |
517
519
  | `callouts(md)` | markdown-it amend that renders `> [!NOTE]` blockquotes as `norn-callout` |
518
- | `singleTitle(md, { layouts })` | markdown-it amend that drops a leading h1 that repeats the title, and turns any other h1 into an h2, on `norn-docs` and `norn-workspace` by default |
519
- | `headingAnchors(md, { levels })` | markdown-it amend that renders headings as `norn-heading-anchor` with an id; levels 2 and 3 by default |
520
+ | `singleTitle(md, { layouts })` | markdown-it amend that drops a leading h1 that repeats the title, & turns any other h1 into an h2, on `norn-docs` & `norn-workspace` by default |
521
+ | `headingAnchors(md, { levels })` | markdown-it amend that renders headings as `norn-heading-anchor` with an id; levels 2 & 3 by default |
520
522
  | `headings(html, { levels })` | The anchored headings of rendered HTML or Markdown output as `{ id, text, level }[]`, for `norn-toc`; register it as `nornHeadings` |
521
523
  | `slugify(text)` | The URL fragment `headingAnchors` makes from heading text, in any script |
522
524
 
523
- `@allons-y/webc-patterns/project` exports the project data builders above. The stylesheets are exported as `@allons-y/webc-patterns/css/reset.css`, `/css/base.css`, and `/css/print.css`, and the schema as `@allons-y/webc-patterns/schema/project.schema.json`.
525
+ `@allons-y/webc-patterns/project` exports the project data builders above. The stylesheets are exported as `@allons-y/webc-patterns/css/reset.css`, `/css/base.css`, & `/css/print.css`, & the schema as `@allons-y/webc-patterns/schema/project.schema.json`.
524
526
 
525
527
  ## License
526
528
 
527
- [MPL-2.0](LICENSE): use freely, modify as needed; changes to MPL-licensed files should be shared back under the same license.
529
+ > [MPL-2.0](LICENSE): use freely, modify as needed; changes to MPL-licensed files
530
+ > should be shared back under the same license.
531
+
532
+ ---
528
533
 
529
534
  <p align="center">
530
535
  <a href="https://allons-y.studio">