baseline-foundry 0.1.3 → 0.1.5

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 (84) hide show
  1. package/README.md +577 -553
  2. package/config/experiments/ibm-plex-engine-smoke.json +115 -115
  3. package/config/experiments/ubuntu-engine-smoke.json +115 -115
  4. package/config/foundation-theme.json +110 -110
  5. package/config/tiers/app.json +116 -116
  6. package/config/tiers/documentation.json +116 -116
  7. package/config/tiers/editorial.json +116 -116
  8. package/config/tiers/os.json +116 -116
  9. package/dist/baseline-grid-overlay.js +43 -43
  10. package/dist/build.js +4 -4
  11. package/dist/code-snippet.js +1 -1
  12. package/dist/css-app-tier.js +78 -73
  13. package/dist/css-components/article-pagination.js +141 -140
  14. package/dist/css-components/button-actions.js +181 -161
  15. package/dist/css-components/cards-options.js +176 -176
  16. package/dist/css-components/chip-badge-status.d.ts +0 -3
  17. package/dist/css-components/chip-badge-status.js +174 -159
  18. package/dist/css-components/content-card.d.ts +7 -7
  19. package/dist/css-components/content-card.js +498 -497
  20. package/dist/css-components/control-row.d.ts +1 -1
  21. package/dist/css-components/control-row.js +15 -15
  22. package/dist/css-components/cta-figure-aspect.js +120 -120
  23. package/dist/css-components/document-navigation.js +427 -390
  24. package/dist/css-components/editorial-content.js +42 -42
  25. package/dist/css-components/icon.js +72 -72
  26. package/dist/css-components/interactive-feedback.js +239 -217
  27. package/dist/css-components/interactive-tables.js +160 -155
  28. package/dist/css-components/legacy-navigation.d.ts +8 -0
  29. package/dist/css-components/legacy-navigation.js +816 -0
  30. package/dist/css-components/linked-logo-site-layout.d.ts +3 -3
  31. package/dist/css-components/linked-logo-site-layout.js +177 -180
  32. package/dist/css-components/list-tree.js +96 -94
  33. package/dist/css-components/list.js +9 -7
  34. package/dist/css-components/logo-media.d.ts +6 -6
  35. package/dist/css-components/logo-media.js +218 -207
  36. package/dist/css-components/navigation-layout.js +144 -144
  37. package/dist/css-components/panel.js +148 -144
  38. package/dist/css-components/search-box-and-filter.d.ts +0 -1
  39. package/dist/css-components/search-box-and-filter.js +263 -264
  40. package/dist/css-components/sites-editorial-ports.d.ts +3 -4
  41. package/dist/css-components/sites-editorial-ports.js +279 -247
  42. package/dist/css-components/sites-foundation.js +142 -146
  43. package/dist/css-components/sites-rich-lists.d.ts +5 -5
  44. package/dist/css-components/sites-rich-lists.js +339 -349
  45. package/dist/css-components/static-content-ports.d.ts +3 -3
  46. package/dist/css-components/static-content-ports.js +200 -186
  47. package/dist/css-components/tab-section.d.ts +3 -3
  48. package/dist/css-components/tab-section.js +93 -85
  49. package/dist/css-components/table.js +76 -75
  50. package/dist/css-components/tabs-choice-breadcrumbs.js +307 -303
  51. package/dist/css-components/tiered-list-equal-height-row.js +271 -271
  52. package/dist/css-components.js +1881 -2554
  53. package/dist/css-grid.d.ts +1 -1
  54. package/dist/css-grid.js +183 -173
  55. package/dist/css.js +317 -241
  56. package/dist/experiments/ibm-plex-engine-smoke/styles.css +894 -538
  57. package/dist/experiments/ibm-plex-engine-smoke/surfaces.json +2 -2
  58. package/dist/experiments/ibm-plex-engine-smoke/tokens.json +1 -1
  59. package/dist/in-page-navigation.js +1 -1
  60. package/dist/presets/app-tier/styles.css +928 -568
  61. package/dist/presets/app-tier/surfaces.json +17 -17
  62. package/dist/presets/app-tier/tokens.json +7 -7
  63. package/dist/presets/prose/styles.css +919 -562
  64. package/dist/presets/prose/surfaces.json +17 -17
  65. package/dist/presets/prose/tokens.json +1 -1
  66. package/dist/resizable-aside.js +12 -4
  67. package/dist/styles.css +919 -562
  68. package/dist/surfaces.json +17 -17
  69. package/dist/tiers/app/styles.css +928 -568
  70. package/dist/tiers/app/surfaces.json +17 -17
  71. package/dist/tiers/app/tokens.json +7 -7
  72. package/dist/tiers/documentation/styles.css +920 -563
  73. package/dist/tiers/documentation/surfaces.json +17 -17
  74. package/dist/tiers/documentation/tokens.json +2 -2
  75. package/dist/tiers/editorial/styles.css +919 -562
  76. package/dist/tiers/editorial/surfaces.json +17 -17
  77. package/dist/tiers/editorial/tokens.json +1 -1
  78. package/dist/tiers/os/styles.css +925 -568
  79. package/dist/tiers/os/surfaces.json +17 -17
  80. package/dist/tiers/os/tokens.json +7 -7
  81. package/dist/tokens.json +1 -1
  82. package/dist/types.d.ts +1 -1
  83. package/docs/publishing.md +31 -14
  84. package/package.json +119 -108
package/README.md CHANGED
@@ -1,108 +1,108 @@
1
- # Baseline Foundry
2
-
3
- Lean baseline-aligned design system focused on:
4
-
5
- - editorial typescale
6
- - container-owned semantic spacing
7
- - grid primitives
8
- - page and section rhythm
9
- - a small amount of demo/runtime support
10
-
11
- This repo is the clean sibling to `portable-vertical-rhythm`.
12
- That older package remains the compatibility line for `design-foundry`.
13
- This repo is the forward-looking line: smaller, more versatile, and centered on baseline, prose flow, and grid rather than broad component parity.
14
-
15
- ## Workflow Map
16
-
17
- - Always-on invariants and cold start: `AGENTS.md`
18
- - Live state and handover: `AGENT-INBOX.md`
19
- - Operational commands and source routing: `docs/agent-index.md`
20
- - Cross-spec order and short backlog: `TODO.md`
21
- - Spec catalog and status: `docs/specs.md`
22
- - Durable architecture: `docs/architecture.md`
23
- - Active feature intent, tasks, and evidence: `specs/<id>-<slug>/`
24
- - Human async notes: `INBOX.md`
25
-
26
- Spec Kit packages are the source of truth for feature work. Closed packages move
27
- to `docs/spec-archive/`; Git is the chronological history.
28
-
29
- ## LLM Efficiency Notes
30
-
31
- These habits matter more than prompt cleverness when you are using a coding agent in this repo.
32
-
33
- - Pick one model per task. Model switches often invalidate caches and force the tool to reprocess the same context again.
34
- - Keep permanent instructions short. Durable invariants belong in `AGENTS.md`;
35
- task detail and evidence belong in one active Spec Kit package.
36
- - Keep project memory in the repo, not only in chat, but give each fact one
37
- owner. Avoid global status, roadmap, and history narratives.
38
- - Prefer markdown, plain text, and lists over complex pages or dense tables when accuracy matters.
39
- - Search in smaller passes instead of one giant query, then verify against the owning file or spec.
40
- - Checkpoint and restart freely. Short resumable sessions are usually cheaper and more reliable than preserving one huge thread.
41
-
42
- ## Source Of Truth
43
-
44
- When sources disagree, this repo follows:
45
-
46
- 1. Current user direction and the active local spec
47
- 2. `.specify/memory/constitution.md`
48
- 3. `AGENTS.md` and `docs/architecture.md`
49
- 4. Accepted archived local specs
50
- 5. External design references catalogued in `docs/specs.md`
51
- 6. Public README/API documentation
52
- 7. Undocumented local implementation details
53
-
54
- Pragma and the Canonical official design system are related products, not BF
55
- authorities. BF's container-owned spacing contract is an explicit local owner
56
- decision recorded in the active spec and constitution.
57
-
58
- ## Linked Specs
59
-
60
- See `docs/specs.md` for the concrete linked spec paths and the legacy/reference boundaries.
61
-
62
- ## Principles
63
-
64
- See `AGENTS.md` and `docs/architecture.md` for the full set. Summary:
65
-
66
- - Baseline alignment is non-negotiable.
67
- - Semantic spacing is owned by nested stacks in editorial, documentation, app, and OS.
68
- - Text keeps metric top-nudge and bottom-margin compensation for baseline alignment.
69
- - OS is the fourth first-class built-in tier.
70
- - Grid and layout primitives are small and composable.
71
- - Dogfooding: demos use only `bf-*` classes.
72
-
73
- For a longer write-up on empirical nudges, cap-unit alignment, raw metrics, and compensated metrics, see `docs/comparing-baseline-alignment-techniques.md`. Its visual companion lives at `demo/components/engine-illustration.html`.
74
-
75
- ## Output
76
-
77
- Build output includes:
78
-
79
- - `dist/styles.css`
80
- - `dist/tokens.json`
81
- - `dist/surfaces.json`
82
- - `dist/experiments/ibm-plex-engine-smoke/styles.css`
83
- - `dist/experiments/ibm-plex-engine-smoke/tokens.json`
84
- - `dist/experiments/ibm-plex-engine-smoke/surfaces.json`
85
- - `dist/tiers/editorial/styles.css`
86
- - `dist/tiers/editorial/tokens.json`
87
- - `dist/tiers/editorial/surfaces.json`
88
- - `dist/tiers/documentation/styles.css`
89
- - `dist/tiers/documentation/tokens.json`
90
- - `dist/tiers/documentation/surfaces.json`
91
- - `dist/tiers/app/styles.css`
92
- - `dist/tiers/app/tokens.json`
93
- - `dist/tiers/app/surfaces.json`
94
- - `dist/tiers/os/styles.css`
95
- - `dist/tiers/os/tokens.json`
96
- - `dist/tiers/os/surfaces.json`
97
- - `dist/presets/prose/styles.css`
98
- - `dist/presets/prose/tokens.json`
99
- - `dist/presets/prose/surfaces.json`
100
- - `dist/presets/app-tier/styles.css`
101
- - `dist/presets/app-tier/tokens.json`
102
- - `dist/presets/app-tier/surfaces.json`
103
- - `dist/index.js`
104
- - `dist/build.js`
105
-
1
+ # Baseline Foundry
2
+
3
+ Lean baseline-aligned design system focused on:
4
+
5
+ - editorial typescale
6
+ - container-owned semantic spacing
7
+ - grid primitives
8
+ - page and section rhythm
9
+ - a small amount of demo/runtime support
10
+
11
+ This repo is the clean sibling to `portable-vertical-rhythm`.
12
+ That older package remains the compatibility line for `design-foundry`.
13
+ This repo is the forward-looking line: smaller, more versatile, and centered on baseline, prose flow, and grid rather than broad component parity.
14
+
15
+ ## Workflow Map
16
+
17
+ - Always-on invariants and cold start: `AGENTS.md`
18
+ - Live state and handover: `AGENT-INBOX.md`
19
+ - Operational commands and source routing: `docs/agent-index.md`
20
+ - Cross-spec order and short backlog: `TODO.md`
21
+ - Spec catalog and status: `docs/specs.md`
22
+ - Durable architecture: `docs/architecture.md`
23
+ - Active feature intent, tasks, and evidence: `specs/<id>-<slug>/`
24
+ - Human async notes: `INBOX.md`
25
+
26
+ Spec Kit packages are the source of truth for feature work. Closed packages move
27
+ to `docs/spec-archive/`; Git is the chronological history.
28
+
29
+ ## LLM Efficiency Notes
30
+
31
+ These habits matter more than prompt cleverness when you are using a coding agent in this repo.
32
+
33
+ - Pick one model per task. Model switches often invalidate caches and force the tool to reprocess the same context again.
34
+ - Keep permanent instructions short. Durable invariants belong in `AGENTS.md`;
35
+ task detail and evidence belong in one active Spec Kit package.
36
+ - Keep project memory in the repo, not only in chat, but give each fact one
37
+ owner. Avoid global status, roadmap, and history narratives.
38
+ - Prefer markdown, plain text, and lists over complex pages or dense tables when accuracy matters.
39
+ - Search in smaller passes instead of one giant query, then verify against the owning file or spec.
40
+ - Checkpoint and restart freely. Short resumable sessions are usually cheaper and more reliable than preserving one huge thread.
41
+
42
+ ## Source Of Truth
43
+
44
+ When sources disagree, this repo follows:
45
+
46
+ 1. Current user direction and the active local spec
47
+ 2. `.specify/memory/constitution.md`
48
+ 3. `AGENTS.md` and `docs/architecture.md`
49
+ 4. Accepted archived local specs
50
+ 5. External design references catalogued in `docs/specs.md`
51
+ 6. Public README/API documentation
52
+ 7. Undocumented local implementation details
53
+
54
+ Pragma and the Canonical official design system are related products, not BF
55
+ authorities. BF's container-owned spacing contract is an explicit local owner
56
+ decision recorded in the active spec and constitution.
57
+
58
+ ## Linked Specs
59
+
60
+ See `docs/specs.md` for the concrete linked spec paths and the legacy/reference boundaries.
61
+
62
+ ## Principles
63
+
64
+ See `AGENTS.md` and `docs/architecture.md` for the full set. Summary:
65
+
66
+ - Baseline alignment is non-negotiable.
67
+ - Semantic spacing is owned by nested stacks in editorial, documentation, app, and OS.
68
+ - Text keeps metric top-nudge and bottom-margin compensation for baseline alignment.
69
+ - OS is the fourth first-class built-in tier.
70
+ - Grid and layout primitives are small and composable.
71
+ - Dogfooding: demos use only `bf-*` classes.
72
+
73
+ For a longer write-up on empirical nudges, cap-unit alignment, raw metrics, and compensated metrics, see `docs/comparing-baseline-alignment-techniques.md`. Its visual companion lives at `demo/components/engine-illustration.html`.
74
+
75
+ ## Output
76
+
77
+ Build output includes:
78
+
79
+ - `dist/styles.css`
80
+ - `dist/tokens.json`
81
+ - `dist/surfaces.json`
82
+ - `dist/experiments/ibm-plex-engine-smoke/styles.css`
83
+ - `dist/experiments/ibm-plex-engine-smoke/tokens.json`
84
+ - `dist/experiments/ibm-plex-engine-smoke/surfaces.json`
85
+ - `dist/tiers/editorial/styles.css`
86
+ - `dist/tiers/editorial/tokens.json`
87
+ - `dist/tiers/editorial/surfaces.json`
88
+ - `dist/tiers/documentation/styles.css`
89
+ - `dist/tiers/documentation/tokens.json`
90
+ - `dist/tiers/documentation/surfaces.json`
91
+ - `dist/tiers/app/styles.css`
92
+ - `dist/tiers/app/tokens.json`
93
+ - `dist/tiers/app/surfaces.json`
94
+ - `dist/tiers/os/styles.css`
95
+ - `dist/tiers/os/tokens.json`
96
+ - `dist/tiers/os/surfaces.json`
97
+ - `dist/presets/prose/styles.css`
98
+ - `dist/presets/prose/tokens.json`
99
+ - `dist/presets/prose/surfaces.json`
100
+ - `dist/presets/app-tier/styles.css`
101
+ - `dist/presets/app-tier/tokens.json`
102
+ - `dist/presets/app-tier/surfaces.json`
103
+ - `dist/index.js`
104
+ - `dist/build.js`
105
+
106
106
  ## Install
107
107
 
108
108
  Install the public package from npm:
@@ -115,7 +115,7 @@ The verified GitHub release artifact remains available as a fallback for
115
115
  environments that cannot reach the npm registry:
116
116
 
117
117
  ```bash
118
- npm install https://github.com/lyubomir-popov/baseline-foundry/releases/download/v0.1.3/baseline-foundry-0.1.3.tgz
118
+ npm install https://github.com/lyubomir-popov/baseline-foundry/releases/download/v0.1.5/baseline-foundry-0.1.5.tgz
119
119
  ```
120
120
 
121
121
  Use the attached `.tgz`, not the GitHub source archive: generated `dist/`
@@ -128,188 +128,212 @@ The package name stays unscoped so existing imports such as
128
128
  migration contract.
129
129
 
130
130
  ## Repository quick start
131
-
132
- ```bash
133
- npm install
134
- npm run setup:demo-font
135
- npm run playwright:install
136
- npm run build
137
- npm run test
138
- npm run screenshots:components
139
- npm run demo
140
- ```
141
-
142
- List or build tiers directly with:
143
-
144
- ```bash
145
- npm run build:theme -- --list-tiers
146
- npm run build:theme -- --tier=os
147
- ```
148
-
149
- List or build presets directly with:
150
-
151
- ```bash
152
- npm run build:theme -- --list-presets
153
- npm run build:theme -- --preset=prose
154
- ```
155
-
156
- While `npm run demo` is running, edits under `config/**/*.json` now rerun `npm run build:theme` automatically and force a full page reload.
157
-
158
- `npm run setup:demo-font` downloads the Ubuntu Sans development font plus the IBM Plex Sans variable asset required by the engine-smoke experiment.
159
-
160
- Built-in CSS does not emit `@font-face`: consumers own the runtime font URL and
161
- must declare the same Ubuntu Sans variable face measured by BF. The repository
162
- demo declares its downloaded development asset separately. Custom
163
- `buildThemeFromConfig` outputs may still emit the face declared by a
164
- consumer-owned config.
165
-
166
- The demo runs at:
167
-
168
- - [http://127.0.0.1:4174/](http://127.0.0.1:4174/) Living spec home
169
- - [http://127.0.0.1:4174/demo/spec/typography.html](http://127.0.0.1:4174/demo/spec/typography.html)
170
- - [http://127.0.0.1:4174/demo/panel.html](http://127.0.0.1:4174/demo/panel.html) — OS tier
171
- - [http://127.0.0.1:4174/demo/components/index.html](http://127.0.0.1:4174/demo/components/index.html) — BF foundations and component primitives
172
- - [http://127.0.0.1:4174/demo/patterns/index.html](http://127.0.0.1:4174/demo/patterns/index.html) Patterns, site compositions, recipes, layouts, and documented exclusions
173
- - [http://127.0.0.1:4174/demo/components/engine-illustration.html](http://127.0.0.1:4174/demo/components/engine-illustration.html) — Three-way raw / compensated / cap comparison
174
-
175
- Standalone historical Canonical example batches also live under `examples/grid/` and `examples/spacing/`. Each family has one shared stylesheet (`grid-examples.css` / `spacing-examples.css`); current decisions live in the active Spec Kit package rather than one-off root prompts.
176
-
177
- ## Component and pattern QA
178
-
179
- The repo includes isolated demo pages for visual rhythm and interaction checks.
180
- `demo/components/index.html` catalogs BF foundations and component primitives;
181
- `demo/patterns/index.html` catalogs patterns, site compositions, and layouts while
182
- linking to the same isolated QA routes. The authoritative saved-page inventory
183
- lives in `scripts/component-demo-shared.ts`, so the README does not mirror that
184
- detail list.
185
-
186
- All component/spec/control pages now share the same thin page chrome: hamburger page list plus tone, baseline-grid, and tier controls. That chrome is excluded from screenshot comparisons and disabled during Playwright hit-testing so behavior checks interact with the component under test rather than the surrounding shell.
187
-
188
- Component QA currently covers:
189
-
190
- - Playwright screenshot capture for the saved demo inventory
191
- - baseline verification for baseline-aligned component surfaces across all four built-in tiers and non-tier locked-manifest variants
192
- - behavior verification for pinned-aside resize, drawer overlay, and application-layout interactions
193
- - the narrow-panel regression page so dense controls and media must still fit a tight rail
194
-
195
- The grouped overview pages still exist as convenience entry points:
196
-
197
- - `demo/components/controls.html`
198
- - `demo/components/surfaces-navigation.html`
199
-
200
- Install Playwright once with:
201
-
202
- ```bash
203
- npm run playwright:install
204
- ```
205
-
206
- Then capture the current component screenshots with:
207
-
208
- ```bash
209
- npm run screenshots:components
210
- ```
211
-
212
- Run the browser-enforced baseline verification with:
213
-
214
- ```bash
215
- npm run verify:components
216
- ```
217
-
218
- Run the browser-enforced resize behavior verification with:
219
-
220
- ```bash
221
- npm run verify:behavior
222
- ```
223
-
224
- Or do both in one pass:
225
-
226
- ```bash
227
- npm run qa:components
228
- ```
229
-
230
- The screenshots and manifest are written to:
231
-
232
- - `tmp/screenshots/components/`
233
-
234
- Those screenshots power both visual atlases at `demo/components/index.html` and
235
- `demo/patterns/index.html`, so run `npm run screenshots:components` when new
236
- demos are added or the saved preview set changes. Atlas frames use
237
- `object-fit: contain`, so saved previews remain legible when different surfaces
238
- naturally want different capture widths.
239
-
240
- The baseline verification report is also written to:
241
-
242
- - `tmp/screenshots/components/baseline-report.json`
243
-
244
- That report records one entry per verified component surface, not just one per
245
- route. Shared-tier pages are walked through `editorial`, `documentation`,
246
- `app`, and `os`; app-authored pages stay app-only unless they explicitly opt
247
- into a broader tier set.
248
-
249
- `npm test` now includes this Playwright baseline check, so once Chromium is installed the grid-alignment gate is part of the normal regression suite.
250
-
251
- ## Theme Model
252
-
253
- The default theme uses Ubuntu Sans Variable and generates metric-driven typography tokens, spacing tokens, layout values, component density tokens, and a published surface manifest. Four first-class tiers plus two legacy preset aliases:
254
-
255
- | Tier/Preset | Purpose |
256
- |---|---|
257
- | `editorial` | Root default, widest long-form composition |
258
- | `documentation` | Tighter chapter-reading tier |
259
- | `app` | Canonical-facing application chrome |
260
- | `os` | Dense OS-style tier with metric alignment and compact control geometry |
261
-
262
- Legacy aliases: `prose` → editorial, `app-tier` → app.
263
-
264
- Independent surface contract:
265
-
266
- - each built-in tier emits a complete scoped token surface instead of inheriting editorial defaults through diffs
267
- - tier choice is a top-level class on any `.bf-theme` container: `.bf-tier-editorial`, `.bf-tier-documentation`, `.bf-tier-app`, `.bf-tier-os`
268
- - multiple containers can coexist side by side under the same stylesheet
269
- - `dist/surfaces.json` stores the runtime tokens and the font-metric artifact that produced each shipped surface — see [docs/surfaces-manifest.md](docs/surfaces-manifest.md) for the full schema, stability guarantees, and consumer recipes
270
- - the published manifest omits local build-machine config/baseline file paths, so the shipped JSON stays portable
271
- - every tier keeps metric-derived runtime alignment while nested stacks own semantic spacing
272
-
273
- Example:
274
-
275
- ```html
276
- <section class="bf-theme bf-tier-editorial bf-stack is-section">
277
- <div class="bf-prose bf-stack">
278
- <h1>Editorial surface</h1>
279
- <p>Metric-derived nudges stay on.</p>
280
- </div>
281
- </section>
282
-
283
- <section class="bf-theme bf-tier-app bf-stack is-section">
284
- <div class="bf-prose bf-stack">
285
- <h1>App surface</h1>
286
- <p>Metric-derived compensation and container-owned gaps remain active at application density.</p>
287
- </div>
288
- </section>
289
-
290
- <section class="bf-theme bf-tier-os bf-stack is-section">
291
- <div class="bf-prose bf-stack">
292
- <h1>OS surface</h1>
293
- <p>Metrics stay on, but the measure and control geometry compress toward dense system surfaces.</p>
294
- </div>
295
- </section>
296
- ```
297
-
298
- Engine choice remains separate: `.bf-engine-metrics` is the default production path, `.bf-engine-cap` is demo-only.
299
-
300
- See `config/tiers/` for the four canonical source configs. Compatibility preset names resolve to those same owners rather than duplicate JSON files.
301
-
302
- ## Public API
303
-
304
- Package root exports:
305
-
306
- - `initAccordions`
307
- - `toggleAccordionButton`
308
- - `initApplicationLayouts`
309
- - `initBaselineGridToggles`
310
- - `setupBaselineGridToggle`
311
- - `generateBaselineGridOverlayCss`
312
- - `generateBaselineGridThemeOverrideCss`
131
+
132
+ Repository development and the public build API are supported on Node.js
133
+ 22.14 or newer with npm 11.19. The checked-in package metadata is the
134
+ authoritative toolchain contract.
135
+
136
+ ```bash
137
+ npm install
138
+ npm run setup:demo-font
139
+ npm run playwright:install
140
+ npm run build
141
+ npm run test
142
+ npm run screenshots:components
143
+ npm run demo
144
+ ```
145
+
146
+ Release maintainers can validate the immutable publication boundary without
147
+ publishing:
148
+
149
+ ```bash
150
+ npm run release:preflight:test
151
+ npm run release:verify -- --pack-current
152
+ ```
153
+
154
+ The trusted workflow, resume procedure, checksum evidence, and private-source
155
+ provenance limitation are documented in
156
+ [`docs/publishing.md`](docs/publishing.md).
157
+
158
+ List or build tiers directly with:
159
+
160
+ ```bash
161
+ npm run build:theme -- --list-tiers
162
+ npm run build:theme -- --tier=os
163
+ ```
164
+
165
+ List or build presets directly with:
166
+
167
+ ```bash
168
+ npm run build:theme -- --list-presets
169
+ npm run build:theme -- --preset=prose
170
+ ```
171
+
172
+ While `npm run demo` is running, edits under `config/**/*.json` now rerun `npm run build:theme` automatically and force a full page reload.
173
+
174
+ `npm run setup:demo-font` downloads the Ubuntu Sans development font plus the IBM Plex Sans variable asset required by the engine-smoke experiment.
175
+
176
+ Built-in CSS does not emit `@font-face`: consumers own the runtime font URL and
177
+ must declare the same Ubuntu Sans variable face measured by BF. The repository
178
+ demo declares its downloaded development asset separately. Custom
179
+ `buildThemeFromConfig` outputs may still emit the face declared by a
180
+ consumer-owned config.
181
+
182
+ The demo runs at:
183
+
184
+ - [http://127.0.0.1:4174/](http://127.0.0.1:4174/) — Living spec home
185
+ - [http://127.0.0.1:4174/demo/spec/typography.html](http://127.0.0.1:4174/demo/spec/typography.html)
186
+ - [http://127.0.0.1:4174/demo/panel.html](http://127.0.0.1:4174/demo/panel.html) OS tier
187
+ - [http://127.0.0.1:4174/demo/components/index.html](http://127.0.0.1:4174/demo/components/index.html) — BF foundations and component primitives
188
+ - [http://127.0.0.1:4174/demo/patterns/index.html](http://127.0.0.1:4174/demo/patterns/index.html) Patterns, site compositions, recipes, layouts, and documented exclusions
189
+ - [http://127.0.0.1:4174/demo/components/engine-illustration.html](http://127.0.0.1:4174/demo/components/engine-illustration.html) — Three-way raw / compensated / cap comparison
190
+
191
+ Standalone grid examples live under `examples/grid/` and share
192
+ `grid-examples.css`. Spacing behavior is documented by the living spacing
193
+ chapter and verified on the component or pattern that owns each relationship;
194
+ the catalog does not expose separate diagnostic spacing pages.
195
+
196
+ ## Component and pattern QA
197
+
198
+ The repo includes isolated demo pages for visual rhythm and interaction checks.
199
+ `demo/components/index.html` catalogs BF foundations and component primitives;
200
+ `demo/patterns/index.html` catalogs patterns, site compositions, and layouts while
201
+ linking to the same isolated QA routes. The authoritative saved-page inventory
202
+ lives in `scripts/component-demo-shared.ts`, so the README does not mirror that
203
+ detail list.
204
+
205
+ All component/spec/control pages now share the same thin page chrome: hamburger page list plus tone, baseline-grid, and tier controls. That chrome is excluded from screenshot comparisons and disabled during Playwright hit-testing so behavior checks interact with the component under test rather than the surrounding shell.
206
+
207
+ Component QA currently covers:
208
+
209
+ - Playwright screenshot capture for the saved demo inventory
210
+ - baseline verification for baseline-aligned component surfaces across all four built-in tiers and non-tier locked-manifest variants
211
+ - behavior verification for pinned-aside resize, drawer overlay, and application-layout interactions
212
+ - the narrow-panel regression page so dense controls and media must still fit a tight rail
213
+
214
+ The grouped overview pages still exist as convenience entry points:
215
+
216
+ - `demo/components/controls.html`
217
+ - `demo/components/surfaces-navigation.html`
218
+
219
+ Install Playwright once with:
220
+
221
+ ```bash
222
+ npm run playwright:install
223
+ ```
224
+
225
+ Then capture the current component screenshots with:
226
+
227
+ ```bash
228
+ npm run screenshots:components
229
+ ```
230
+
231
+ Run the browser-enforced baseline verification with:
232
+
233
+ ```bash
234
+ npm run verify:components
235
+ ```
236
+
237
+ Run the browser-enforced resize behavior verification with:
238
+
239
+ ```bash
240
+ npm run verify:behavior
241
+ ```
242
+
243
+ Or do both in one pass:
244
+
245
+ ```bash
246
+ npm run qa:components
247
+ ```
248
+
249
+ The screenshots and manifest are written to:
250
+
251
+ - `tmp/screenshots/components/`
252
+
253
+ Those screenshots power both visual atlases at `demo/components/index.html` and
254
+ `demo/patterns/index.html`, so run `npm run screenshots:components` when new
255
+ demos are added or the saved preview set changes. Atlas frames use
256
+ `object-fit: contain`, so saved previews remain legible when different surfaces
257
+ naturally want different capture widths.
258
+
259
+ The baseline verification report is also written to:
260
+
261
+ - `tmp/screenshots/components/baseline-report.json`
262
+
263
+ That report records one entry per verified component surface, not just one per
264
+ route. Shared-tier pages are walked through `editorial`, `documentation`,
265
+ `app`, and `os`; app-authored pages stay app-only unless they explicitly opt
266
+ into a broader tier set.
267
+
268
+ `npm test` now includes this Playwright baseline check, so once Chromium is installed the grid-alignment gate is part of the normal regression suite.
269
+
270
+ ## Theme Model
271
+
272
+ The default theme uses Ubuntu Sans Variable and generates metric-driven typography tokens, spacing tokens, layout values, component density tokens, and a published surface manifest. Four first-class tiers plus two legacy preset aliases:
273
+
274
+ | Tier/Preset | Content cap | Purpose |
275
+ |---|---:|---|
276
+ | `editorial` | `90rem` | Root default, widest long-form composition |
277
+ | `documentation` | `80rem` | Tighter chapter-reading tier |
278
+ | `app` | `60rem` | Fluid application chrome with a cap only on explicit `bf-fixed-width` rows |
279
+ | `os` | `60rem` | Dense OS-style tier with metric alignment and compact control geometry |
280
+
281
+ Legacy aliases: `prose` → editorial, `app-tier` → app.
282
+
283
+ The caps are a non-increasing density progression (`90 >= 80 >= 60 >= 60`).
284
+ App `.bf-page` and application grids remain uncapped and fluid; the App/OS
285
+ value governs only explicit bounded content. Tier and density are currently one
286
+ coupled choice, not independent axes.
287
+
288
+ Independent surface contract:
289
+
290
+ - each built-in tier emits a complete scoped token surface instead of inheriting editorial defaults through diffs
291
+ - tier choice is a top-level class on any `.bf-theme` container: `.bf-tier-editorial`, `.bf-tier-documentation`, `.bf-tier-app`, `.bf-tier-os`
292
+ - multiple containers can coexist side by side under the same stylesheet
293
+ - `dist/surfaces.json` stores the runtime tokens and the font-metric artifact that produced each shipped surface — see [docs/surfaces-manifest.md](docs/surfaces-manifest.md) for the full schema, stability guarantees, and consumer recipes
294
+ - the published manifest omits local build-machine config/baseline file paths, so the shipped JSON stays portable
295
+ - every tier keeps metric-derived runtime alignment while nested stacks own semantic spacing
296
+
297
+ Example:
298
+
299
+ ```html
300
+ <section class="bf-theme bf-tier-editorial bf-stack is-section">
301
+ <div class="bf-prose bf-stack">
302
+ <h1>Editorial surface</h1>
303
+ <p>Metric-derived nudges stay on.</p>
304
+ </div>
305
+ </section>
306
+
307
+ <section class="bf-theme bf-tier-app bf-stack is-section">
308
+ <div class="bf-prose bf-stack">
309
+ <h1>App surface</h1>
310
+ <p>Metric-derived compensation and container-owned gaps remain active at application density.</p>
311
+ </div>
312
+ </section>
313
+
314
+ <section class="bf-theme bf-tier-os bf-stack is-section">
315
+ <div class="bf-prose bf-stack">
316
+ <h1>OS surface</h1>
317
+ <p>Metrics stay on, but the measure and control geometry compress toward dense system surfaces.</p>
318
+ </div>
319
+ </section>
320
+ ```
321
+
322
+ Engine choice remains separate: `.bf-engine-metrics` is the default production path, `.bf-engine-cap` is demo-only.
323
+
324
+ See `config/tiers/` for the four canonical source configs. Compatibility preset names resolve to those same owners rather than duplicate JSON files.
325
+
326
+ ## Public API
327
+
328
+ Package root exports:
329
+
330
+ - `initAccordions`
331
+ - `toggleAccordionButton`
332
+ - `initApplicationLayouts`
333
+ - `initBaselineGridToggles`
334
+ - `setupBaselineGridToggle`
335
+ - `generateBaselineGridOverlayCss`
336
+ - `generateBaselineGridThemeOverrideCss`
313
337
  - `initCodeSnippets`
314
338
  - `initContextualMenus`
315
339
  - `initInPageNavigations`
@@ -321,268 +345,268 @@ Package root exports:
321
345
  - `initExpandingTables`
322
346
  - `initMobileCardTables`
323
347
  - `initListTree`
324
- - `initPanelDrawers`
325
- - `initRangeControls`
326
- - `setupRangeControl`
327
- - `updateRangeFill`
328
- - `initResizableAsides`
329
- - `initSideNavigations`
330
- - `initTopNavigations`
331
- - `initTabs`
332
- - `initTooltips`
333
- - `tierNames`, `tierDescriptions`, and `isTierName`
334
- - `TierName`, `BuiltInThemeName`, `ThemeSurfaceManifest`, and related public types
335
-
336
- Node/build exports:
337
-
338
- - `buildThemeFromConfig`
339
- - `buildThemeFromTier`
340
- - `buildThemeFromPreset`
341
- - `deriveBaselineTokensFromConfig`
342
- - `readThemeConfig`
343
-
344
- Static assets:
345
-
346
- - `baseline-foundry/styles.css`
347
- - `baseline-foundry/tokens.json`
348
- - `baseline-foundry/surfaces.json`
349
- - `baseline-foundry/tiers/editorial.css`
350
- - `baseline-foundry/tiers/editorial.tokens.json`
351
- - `baseline-foundry/tiers/editorial.surfaces.json`
352
- - `baseline-foundry/tiers/documentation.css`
353
- - `baseline-foundry/tiers/documentation.tokens.json`
354
- - `baseline-foundry/tiers/documentation.surfaces.json`
355
- - `baseline-foundry/tiers/app.css`
356
- - `baseline-foundry/tiers/app.tokens.json`
357
- - `baseline-foundry/tiers/app.surfaces.json`
358
- - `baseline-foundry/tiers/os.css`
359
- - `baseline-foundry/tiers/os.tokens.json`
360
- - `baseline-foundry/tiers/os.surfaces.json`
361
- - `baseline-foundry/presets/prose.css`
362
- - `baseline-foundry/presets/prose.tokens.json`
363
- - `baseline-foundry/presets/prose.surfaces.json`
364
- - `baseline-foundry/presets/app-tier.css`
365
- - `baseline-foundry/presets/app-tier.tokens.json`
366
- - `baseline-foundry/presets/app-tier.surfaces.json`
367
- - `baseline-foundry/presets`
368
- - `baseline-foundry/types`
369
-
370
- ### Entry point guidance
371
-
372
- Downstream consumers have two supported ways to load the built-in OS surface, depending on whether they need a neutral shared bundle or an OS-only default surface.
373
-
374
- - Use `baseline-foundry/styles.css` as the neutral entrypoint when the consumer wants the shared root bundle and will opt into OS with class switching such as `.bf-theme.bf-tier-os`.
375
- - Use `baseline-foundry/tiers/os.css` only when the consumer wants OS to be the unscoped default surface for that stylesheet import.
376
- - Do not import `baseline-foundry/presets/app-tier.css` just to preload the shared bundle before switching to `bf-tier-os`; that preset remains the legacy app alias, not the neutral OS entrypoint.
377
-
378
- Example neutral entrypoint for a downstream such as `a4-generator`:
379
-
380
- ```html
381
- <link rel="stylesheet" href="baseline-foundry/styles.css" />
382
-
383
- <section class="bf-theme bf-tier-os">
384
- <div class="bf-prose">
385
- <h1>OS surface</h1>
386
- <p>The shared root bundle is loaded once, and the container opts into the OS tier explicitly.</p>
387
- </div>
388
- </section>
389
- ```
390
-
391
- Example OS-default entrypoint when class switching is not needed:
392
-
393
- ```html
394
- <link rel="stylesheet" href="baseline-foundry/tiers/os.css" />
395
- ```
396
-
397
- ## Downstream Fonts
398
-
399
- The built-in default is Ubuntu Sans Variable, but downstream repos are not locked to it.
400
- Point the build at a downstream theme config and derive fresh nudges from that font's real metrics.
401
-
402
- The npm package does not ship BF's development font file and built-in CSS does
403
- not guess a URL for it. A consumer using a built-in tier must serve Ubuntu Sans
404
- Variable and declare one normal variable face covering weights 100 through
405
- 800 and stretches 75% through 100%. The manifest's relative
406
- `fontFiles[*].path` records the source asset used for metric generation; it is
407
- not a package runtime URL.
408
-
409
- The key rule is simple:
410
-
411
- - **do not reuse nudges from a different font**
412
- - **do not switch font-family in CSS without regenerating tokens**
413
- - **derive a fresh `nudgeTop` set for the actual font files that will ship**
414
-
415
- ### What the downstream config needs
416
-
417
- Create a theme JSON that follows the same shape as the tier configs under `config/tiers/`.
418
- The font files are resolved relative to that config file, so a downstream repo can keep its own font assets and still use the same build path.
419
-
420
- At minimum, define:
421
-
422
- - `baselineUnit`
423
- - `fontFiles`
424
- - `fontStacks`
425
- - `elements`
426
- - `roles`
427
- - `layout`
428
- - `components`
429
-
430
- Example sketch for a downstream Ubuntu Sans theme:
431
-
432
- ```json
433
- {
434
- "baselineUnit": 0.25,
435
- "fontFiles": [
436
- {
437
- "family": "ubuntu-sans",
438
- "path": "../apps/overlay-preview/public/assets/fonts/UbuntuSans-Regular.ttf",
439
- "cssFamily": "Ubuntu Sans",
440
- "fontStyle": "normal",
441
- "fontWeight": "100 800",
442
- "fontDisplay": "swap"
443
- }
444
- ],
445
- "fontStacks": {
446
- "ubuntu-sans": "\"Ubuntu Sans\", \"Ubuntu\", system-ui, sans-serif"
447
- },
448
- "elements": [
449
- {
450
- "identifier": "body",
451
- "fontSize": 0.75,
452
- "lineHeight": 4,
453
- "spaceAfter": 1,
454
- "fontFamily": "ubuntu-sans",
455
- "fontWeight": 400,
456
- "fontStyle": "normal"
457
- }
458
- ],
459
- "roles": {
460
- "body": "body"
461
- },
462
- "layout": {
463
- "contentMaxWidthRem": 90,
464
- "contentPaddingInlineRem": 1,
465
- "measureRem": 40,
466
- "sectionSpaceBaselineUnits": 8,
467
- "sectionSpaceDeepBaselineUnits": 16,
468
- "stripSpaceBaselineUnits": 8,
469
- "gridGapInlineBaselineUnits": 2,
470
- "gridGapBlockBaselineUnits": 2,
471
- "pageMarginBaselineUnits": 2
472
- },
473
- "components": {
474
- "borderWidthPx": 1,
475
- "radiusRem": 0,
476
- "controlBlockPaddingRem": 0.5,
477
- "controlCompactBlockPaddingRem": 0.25,
478
- "controlInlinePaddingRem": 1,
479
- "controlVisualSizeRem": 0.75,
480
- "fieldGapBaselineUnits": 1,
481
- "panelPaddingInlineBaselineUnits": 2,
482
- "panelPaddingBlockBaselineUnits": 2,
483
- "accordionIndentBaselineUnits": 3
484
- }
485
- }
486
- ```
487
-
488
- ### Generate full downstream CSS and tokens
489
-
490
- Use the Node/build subpath so the downstream repo does not need to duplicate any build logic:
491
-
492
- ```ts
493
- import { buildThemeFromConfig } from "baseline-foundry/build";
494
-
495
- await buildThemeFromConfig("config/ubuntu-foundry-theme.json", {
496
- distDir: "generated/foundry/ubuntu",
497
- baselineDir: ".generated/baseline/ubuntu"
498
- });
499
- ```
500
-
501
- If a downstream surface bundle should ship multiple named fonts or brand variants in one stylesheet, pass a label for the default surface plus sibling named surfaces:
502
-
503
- Baseline Foundry does not ship IBM Plex as a built-in tier or preset. Ubuntu Sans remains the only built-in tier font. For non-Ubuntu downstream bundles, provide your own config file and treat `config/experiments/ibm-plex-engine-smoke.json` as a reference/example rather than as a published preset.
504
-
505
- ```ts
506
- await buildThemeFromConfig("config/brand-ibm-plex-theme.json", {
507
- distDir: "generated/foundry/smoke",
508
- baselineDir: ".generated/baseline/smoke",
509
- surfaceLabel: "IBM Plex Sans",
510
- additionalSurfaces: [
511
- {
512
- name: "ubuntu-smoke",
513
- label: "Ubuntu Sans",
514
- className: "bf-surface-ubuntu-smoke",
515
- configPath: "config/ubuntu-foundry-theme.json"
516
- }
517
- ]
518
- });
519
- ```
520
-
521
- That does three things:
522
-
523
- 1. writes the reduced baseline-generator input JSON
524
- 2. runs `@lyubomir-popov/baseline-nudge-generator`
525
- 3. emits `tokens.json`, `styles.css`, and `surfaces.json` for the downstream font or surface set
526
-
527
- `surfaces.json` will then expose each named surface's runtime tokens, stored metrics, and optional UI label under one manifest-backed bundle.
528
-
529
- ### Derive nudges only
530
-
531
- If the downstream repo wants the font metrics and `nudgeTop` values but plans to own CSS generation itself, use `deriveBaselineTokensFromConfig`:
532
-
533
- ```ts
534
- import { deriveBaselineTokensFromConfig } from "baseline-foundry/build";
535
-
536
- const result = await deriveBaselineTokensFromConfig("config/ubuntu-foundry-theme.json", {
537
- baselineDir: ".generated/baseline/ubuntu"
538
- });
539
-
540
- console.log(result.tokens.elements.body.nudgeTop);
541
- console.log(result.baselineConfigPath);
542
- console.log(result.baselineTokensPath);
543
- ```
544
-
545
- ### Direct utility usage
546
-
547
- `baseline-foundry` uses `@lyubomir-popov/baseline-nudge-generator` under the hood.
548
- The reduced config passed to that utility contains only:
549
-
550
- - `baselineUnit`
551
- - `fontFiles` with build-time font paths
552
- - `elements` with `identifier`, `fontSize`, `lineHeight`, `spaceAfter`, `fontFamily`, `fontWeight`, and `fontStyle`
553
-
554
- Equivalent direct usage looks like this:
555
-
556
- ```ts
557
- import { generateFromConfig } from "@lyubomir-popov/baseline-nudge-generator";
558
-
559
- await generateFromConfig(".generated/baseline/ubuntu/ubuntu-foundry-theme.baseline.json", ".generated/baseline/ubuntu");
560
- ```
561
-
562
- The generated `tokens.json` then contains the derived metric nudges per element, including `nudgeTop`, which `baseline-foundry` turns into the scoped `--bf-<role>-nudge-start` / `--bf-<role>-nudge-end` variables inside each emitted surface.
563
-
564
- ### Practical downstream advice
565
-
566
- - Regenerate tokens whenever the downstream font files change.
567
- - Regenerate tokens whenever font size, line-height, or baseline unit changes.
568
- - Keep at least one non-`runtimeOnly` font file in `fontFiles`; that is the file the nudge generator reads for metrics.
569
- - If a downstream repo ships multiple runtime faces, mark only the non-metric extras as `runtimeOnly`.
570
- - Keep metrics as the default engine for production fonts; `.bf-engine-cap` remains an opt-in fallback, not the default path.
571
-
572
- ## Demo
573
-
574
- The demo surface at `/` shows editorial prose rhythm, tier switching, dark
575
- theme, grid, spacing, and component specimens. BF primitives are indexed at
576
- `demo/components/index.html`; patterns, site compositions, and layouts are
577
- indexed separately at `demo/patterns/index.html`. Their isolated test routes
578
- remain under `demo/components/`, and the authoritative saved-page inventory is
579
- in `scripts/component-demo-shared.ts`.
580
-
581
- ## Start Here
582
-
583
- If you resume this repo in a new chat, read:
584
-
585
- 1. `AGENTS.md`
586
- 2. `AGENT-INBOX.md`
587
- 3. `docs/agent-index.md`
588
- 4. `docs/specs.md`
348
+ - `initPanelDrawers`
349
+ - `initRangeControls`
350
+ - `setupRangeControl`
351
+ - `updateRangeFill`
352
+ - `initResizableAsides`
353
+ - `initSideNavigations`
354
+ - `initTopNavigations`
355
+ - `initTabs`
356
+ - `initTooltips`
357
+ - `tierNames`, `tierDescriptions`, and `isTierName`
358
+ - `TierName`, `BuiltInThemeName`, `ThemeSurfaceManifest`, and related public types
359
+
360
+ Node/build exports:
361
+
362
+ - `buildThemeFromConfig`
363
+ - `buildThemeFromTier`
364
+ - `buildThemeFromPreset`
365
+ - `deriveBaselineTokensFromConfig`
366
+ - `readThemeConfig`
367
+
368
+ Static assets:
369
+
370
+ - `baseline-foundry/styles.css`
371
+ - `baseline-foundry/tokens.json`
372
+ - `baseline-foundry/surfaces.json`
373
+ - `baseline-foundry/tiers/editorial.css`
374
+ - `baseline-foundry/tiers/editorial.tokens.json`
375
+ - `baseline-foundry/tiers/editorial.surfaces.json`
376
+ - `baseline-foundry/tiers/documentation.css`
377
+ - `baseline-foundry/tiers/documentation.tokens.json`
378
+ - `baseline-foundry/tiers/documentation.surfaces.json`
379
+ - `baseline-foundry/tiers/app.css`
380
+ - `baseline-foundry/tiers/app.tokens.json`
381
+ - `baseline-foundry/tiers/app.surfaces.json`
382
+ - `baseline-foundry/tiers/os.css`
383
+ - `baseline-foundry/tiers/os.tokens.json`
384
+ - `baseline-foundry/tiers/os.surfaces.json`
385
+ - `baseline-foundry/presets/prose.css`
386
+ - `baseline-foundry/presets/prose.tokens.json`
387
+ - `baseline-foundry/presets/prose.surfaces.json`
388
+ - `baseline-foundry/presets/app-tier.css`
389
+ - `baseline-foundry/presets/app-tier.tokens.json`
390
+ - `baseline-foundry/presets/app-tier.surfaces.json`
391
+ - `baseline-foundry/presets`
392
+ - `baseline-foundry/types`
393
+
394
+ ### Entry point guidance
395
+
396
+ Downstream consumers have two supported ways to load the built-in OS surface, depending on whether they need a neutral shared bundle or an OS-only default surface.
397
+
398
+ - Use `baseline-foundry/styles.css` as the neutral entrypoint when the consumer wants the shared root bundle and will opt into OS with class switching such as `.bf-theme.bf-tier-os`.
399
+ - Use `baseline-foundry/tiers/os.css` only when the consumer wants OS to be the unscoped default surface for that stylesheet import.
400
+ - Do not import `baseline-foundry/presets/app-tier.css` just to preload the shared bundle before switching to `bf-tier-os`; that preset remains the legacy app alias, not the neutral OS entrypoint.
401
+
402
+ Example neutral entrypoint for a downstream such as `a4-generator`:
403
+
404
+ ```html
405
+ <link rel="stylesheet" href="baseline-foundry/styles.css" />
406
+
407
+ <section class="bf-theme bf-tier-os">
408
+ <div class="bf-prose">
409
+ <h1>OS surface</h1>
410
+ <p>The shared root bundle is loaded once, and the container opts into the OS tier explicitly.</p>
411
+ </div>
412
+ </section>
413
+ ```
414
+
415
+ Example OS-default entrypoint when class switching is not needed:
416
+
417
+ ```html
418
+ <link rel="stylesheet" href="baseline-foundry/tiers/os.css" />
419
+ ```
420
+
421
+ ## Downstream Fonts
422
+
423
+ The built-in default is Ubuntu Sans Variable, but downstream repos are not locked to it.
424
+ Point the build at a downstream theme config and derive fresh nudges from that font's real metrics.
425
+
426
+ The npm package does not ship BF's development font file and built-in CSS does
427
+ not guess a URL for it. A consumer using a built-in tier must serve Ubuntu Sans
428
+ Variable and declare one normal variable face covering weights 100 through
429
+ 800 and stretches 75% through 100%. The manifest's relative
430
+ `fontFiles[*].path` records the source asset used for metric generation; it is
431
+ not a package runtime URL.
432
+
433
+ The key rule is simple:
434
+
435
+ - **do not reuse nudges from a different font**
436
+ - **do not switch font-family in CSS without regenerating tokens**
437
+ - **derive a fresh `nudgeTop` set for the actual font files that will ship**
438
+
439
+ ### What the downstream config needs
440
+
441
+ Create a theme JSON that follows the same shape as the tier configs under `config/tiers/`.
442
+ The font files are resolved relative to that config file, so a downstream repo can keep its own font assets and still use the same build path.
443
+
444
+ At minimum, define:
445
+
446
+ - `baselineUnit`
447
+ - `fontFiles`
448
+ - `fontStacks`
449
+ - `elements`
450
+ - `roles`
451
+ - `layout`
452
+ - `components`
453
+
454
+ Example sketch for a downstream Ubuntu Sans theme:
455
+
456
+ ```json
457
+ {
458
+ "baselineUnit": 0.25,
459
+ "fontFiles": [
460
+ {
461
+ "family": "ubuntu-sans",
462
+ "path": "../apps/overlay-preview/public/assets/fonts/UbuntuSans-Regular.ttf",
463
+ "cssFamily": "Ubuntu Sans",
464
+ "fontStyle": "normal",
465
+ "fontWeight": "100 800",
466
+ "fontDisplay": "swap"
467
+ }
468
+ ],
469
+ "fontStacks": {
470
+ "ubuntu-sans": "\"Ubuntu Sans\", \"Ubuntu\", system-ui, sans-serif"
471
+ },
472
+ "elements": [
473
+ {
474
+ "identifier": "body",
475
+ "fontSize": 0.75,
476
+ "lineHeight": 4,
477
+ "spaceAfter": 1,
478
+ "fontFamily": "ubuntu-sans",
479
+ "fontWeight": 400,
480
+ "fontStyle": "normal"
481
+ }
482
+ ],
483
+ "roles": {
484
+ "body": "body"
485
+ },
486
+ "layout": {
487
+ "contentMaxWidthRem": 90,
488
+ "contentPaddingInlineRem": 1,
489
+ "measureRem": 40,
490
+ "sectionSpaceBaselineUnits": 8,
491
+ "sectionSpaceDeepBaselineUnits": 16,
492
+ "stripSpaceBaselineUnits": 8,
493
+ "gridGapInlineBaselineUnits": 2,
494
+ "gridGapBlockBaselineUnits": 2,
495
+ "pageMarginBaselineUnits": 2
496
+ },
497
+ "components": {
498
+ "borderWidthRem": 0.0625,
499
+ "radiusRem": 0,
500
+ "controlBlockPaddingRem": 0.5,
501
+ "controlCompactBlockPaddingRem": 0.25,
502
+ "controlInlinePaddingRem": 1,
503
+ "controlVisualSizeRem": 0.75,
504
+ "fieldGapBaselineUnits": 1,
505
+ "panelPaddingInlineBaselineUnits": 2,
506
+ "panelPaddingBlockBaselineUnits": 2,
507
+ "accordionIndentBaselineUnits": 3
508
+ }
509
+ }
510
+ ```
511
+
512
+ ### Generate full downstream CSS and tokens
513
+
514
+ Use the Node/build subpath so the downstream repo does not need to duplicate any build logic:
515
+
516
+ ```ts
517
+ import { buildThemeFromConfig } from "baseline-foundry/build";
518
+
519
+ await buildThemeFromConfig("config/ubuntu-foundry-theme.json", {
520
+ distDir: "generated/foundry/ubuntu",
521
+ baselineDir: ".generated/baseline/ubuntu"
522
+ });
523
+ ```
524
+
525
+ If a downstream surface bundle should ship multiple named fonts or brand variants in one stylesheet, pass a label for the default surface plus sibling named surfaces:
526
+
527
+ Baseline Foundry does not ship IBM Plex as a built-in tier or preset. Ubuntu Sans remains the only built-in tier font. For non-Ubuntu downstream bundles, provide your own config file and treat `config/experiments/ibm-plex-engine-smoke.json` as a reference/example rather than as a published preset.
528
+
529
+ ```ts
530
+ await buildThemeFromConfig("config/brand-ibm-plex-theme.json", {
531
+ distDir: "generated/foundry/smoke",
532
+ baselineDir: ".generated/baseline/smoke",
533
+ surfaceLabel: "IBM Plex Sans",
534
+ additionalSurfaces: [
535
+ {
536
+ name: "ubuntu-smoke",
537
+ label: "Ubuntu Sans",
538
+ className: "bf-surface-ubuntu-smoke",
539
+ configPath: "config/ubuntu-foundry-theme.json"
540
+ }
541
+ ]
542
+ });
543
+ ```
544
+
545
+ That does three things:
546
+
547
+ 1. writes the reduced baseline-generator input JSON
548
+ 2. runs `@lyubomir-popov/baseline-nudge-generator`
549
+ 3. emits `tokens.json`, `styles.css`, and `surfaces.json` for the downstream font or surface set
550
+
551
+ `surfaces.json` will then expose each named surface's runtime tokens, stored metrics, and optional UI label under one manifest-backed bundle.
552
+
553
+ ### Derive nudges only
554
+
555
+ If the downstream repo wants the font metrics and `nudgeTop` values but plans to own CSS generation itself, use `deriveBaselineTokensFromConfig`:
556
+
557
+ ```ts
558
+ import { deriveBaselineTokensFromConfig } from "baseline-foundry/build";
559
+
560
+ const result = await deriveBaselineTokensFromConfig("config/ubuntu-foundry-theme.json", {
561
+ baselineDir: ".generated/baseline/ubuntu"
562
+ });
563
+
564
+ console.log(result.tokens.elements.body.nudgeTop);
565
+ console.log(result.baselineConfigPath);
566
+ console.log(result.baselineTokensPath);
567
+ ```
568
+
569
+ ### Direct utility usage
570
+
571
+ `baseline-foundry` uses `@lyubomir-popov/baseline-nudge-generator` under the hood.
572
+ The reduced config passed to that utility contains only:
573
+
574
+ - `baselineUnit`
575
+ - `fontFiles` with build-time font paths
576
+ - `elements` with `identifier`, `fontSize`, `lineHeight`, `spaceAfter`, `fontFamily`, `fontWeight`, and `fontStyle`
577
+
578
+ Equivalent direct usage looks like this:
579
+
580
+ ```ts
581
+ import { generateFromConfig } from "@lyubomir-popov/baseline-nudge-generator";
582
+
583
+ await generateFromConfig(".generated/baseline/ubuntu/ubuntu-foundry-theme.baseline.json", ".generated/baseline/ubuntu");
584
+ ```
585
+
586
+ The generated `tokens.json` then contains the derived metric nudges per element, including `nudgeTop`, which `baseline-foundry` turns into the scoped `--bf-<role>-nudge-start` / `--bf-<role>-nudge-end` variables inside each emitted surface.
587
+
588
+ ### Practical downstream advice
589
+
590
+ - Regenerate tokens whenever the downstream font files change.
591
+ - Regenerate tokens whenever font size, line-height, or baseline unit changes.
592
+ - Keep at least one non-`runtimeOnly` font file in `fontFiles`; that is the file the nudge generator reads for metrics.
593
+ - If a downstream repo ships multiple runtime faces, mark only the non-metric extras as `runtimeOnly`.
594
+ - Keep metrics as the default engine for production fonts; `.bf-engine-cap` remains an opt-in fallback, not the default path.
595
+
596
+ ## Demo
597
+
598
+ The demo surface at `/` shows editorial prose rhythm, tier switching, dark
599
+ theme, grid, spacing, and component specimens. BF primitives are indexed at
600
+ `demo/components/index.html`; patterns, site compositions, and layouts are
601
+ indexed separately at `demo/patterns/index.html`. Their isolated test routes
602
+ remain under `demo/components/`, and the authoritative saved-page inventory is
603
+ in `scripts/component-demo-shared.ts`.
604
+
605
+ ## Start Here
606
+
607
+ If you resume this repo in a new chat, read:
608
+
609
+ 1. `AGENTS.md`
610
+ 2. `AGENT-INBOX.md`
611
+ 3. `docs/agent-index.md`
612
+ 4. `docs/specs.md`