baseline-foundry 0.1.3 → 0.1.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 (43) hide show
  1. package/README.md +568 -552
  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/css-app-tier.js +73 -73
  11. package/dist/css-components/article-pagination.js +140 -140
  12. package/dist/css-components/button-actions.js +161 -161
  13. package/dist/css-components/cards-options.js +176 -176
  14. package/dist/css-components/chip-badge-status.js +158 -158
  15. package/dist/css-components/content-card.js +490 -490
  16. package/dist/css-components/control-row.js +14 -14
  17. package/dist/css-components/cta-figure-aspect.js +120 -120
  18. package/dist/css-components/document-navigation.js +390 -390
  19. package/dist/css-components/editorial-content.js +42 -42
  20. package/dist/css-components/icon.js +72 -72
  21. package/dist/css-components/interactive-feedback.js +217 -217
  22. package/dist/css-components/interactive-tables.js +155 -155
  23. package/dist/css-components/legacy-navigation.d.ts +9 -0
  24. package/dist/css-components/legacy-navigation.js +763 -0
  25. package/dist/css-components/linked-logo-site-layout.js +177 -177
  26. package/dist/css-components/list-tree.js +94 -94
  27. package/dist/css-components/logo-media.js +201 -201
  28. package/dist/css-components/navigation-layout.js +144 -144
  29. package/dist/css-components/panel.js +144 -144
  30. package/dist/css-components/search-box-and-filter.js +263 -263
  31. package/dist/css-components/sites-editorial-ports.js +243 -243
  32. package/dist/css-components/sites-foundation.js +146 -146
  33. package/dist/css-components/sites-rich-lists.js +344 -344
  34. package/dist/css-components/static-content-ports.js +182 -182
  35. package/dist/css-components/tab-section.js +82 -82
  36. package/dist/css-components/table.js +75 -75
  37. package/dist/css-components/tabs-choice-breadcrumbs.js +303 -303
  38. package/dist/css-components/tiered-list-equal-height-row.js +271 -271
  39. package/dist/css-components.js +1791 -2542
  40. package/dist/css-grid.js +172 -172
  41. package/dist/css.js +240 -240
  42. package/docs/publishing.md +30 -13
  43. 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:
@@ -128,188 +128,204 @@ 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 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.
192
+
193
+ ## Component and pattern QA
194
+
195
+ The repo includes isolated demo pages for visual rhythm and interaction checks.
196
+ `demo/components/index.html` catalogs BF foundations and component primitives;
197
+ `demo/patterns/index.html` catalogs patterns, site compositions, and layouts while
198
+ linking to the same isolated QA routes. The authoritative saved-page inventory
199
+ lives in `scripts/component-demo-shared.ts`, so the README does not mirror that
200
+ detail list.
201
+
202
+ 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.
203
+
204
+ Component QA currently covers:
205
+
206
+ - Playwright screenshot capture for the saved demo inventory
207
+ - baseline verification for baseline-aligned component surfaces across all four built-in tiers and non-tier locked-manifest variants
208
+ - behavior verification for pinned-aside resize, drawer overlay, and application-layout interactions
209
+ - the narrow-panel regression page so dense controls and media must still fit a tight rail
210
+
211
+ The grouped overview pages still exist as convenience entry points:
212
+
213
+ - `demo/components/controls.html`
214
+ - `demo/components/surfaces-navigation.html`
215
+
216
+ Install Playwright once with:
217
+
218
+ ```bash
219
+ npm run playwright:install
220
+ ```
221
+
222
+ Then capture the current component screenshots with:
223
+
224
+ ```bash
225
+ npm run screenshots:components
226
+ ```
227
+
228
+ Run the browser-enforced baseline verification with:
229
+
230
+ ```bash
231
+ npm run verify:components
232
+ ```
233
+
234
+ Run the browser-enforced resize behavior verification with:
235
+
236
+ ```bash
237
+ npm run verify:behavior
238
+ ```
239
+
240
+ Or do both in one pass:
241
+
242
+ ```bash
243
+ npm run qa:components
244
+ ```
245
+
246
+ The screenshots and manifest are written to:
247
+
248
+ - `tmp/screenshots/components/`
249
+
250
+ Those screenshots power both visual atlases at `demo/components/index.html` and
251
+ `demo/patterns/index.html`, so run `npm run screenshots:components` when new
252
+ demos are added or the saved preview set changes. Atlas frames use
253
+ `object-fit: contain`, so saved previews remain legible when different surfaces
254
+ naturally want different capture widths.
255
+
256
+ The baseline verification report is also written to:
257
+
258
+ - `tmp/screenshots/components/baseline-report.json`
259
+
260
+ That report records one entry per verified component surface, not just one per
261
+ route. Shared-tier pages are walked through `editorial`, `documentation`,
262
+ `app`, and `os`; app-authored pages stay app-only unless they explicitly opt
263
+ into a broader tier set.
264
+
265
+ `npm test` now includes this Playwright baseline check, so once Chromium is installed the grid-alignment gate is part of the normal regression suite.
266
+
267
+ ## Theme Model
268
+
269
+ 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:
270
+
271
+ | Tier/Preset | Purpose |
272
+ |---|---|
273
+ | `editorial` | Root default, widest long-form composition |
274
+ | `documentation` | Tighter chapter-reading tier |
275
+ | `app` | Canonical-facing application chrome |
276
+ | `os` | Dense OS-style tier with metric alignment and compact control geometry |
277
+
278
+ Legacy aliases: `prose` → editorial, `app-tier` → app.
279
+
280
+ Independent surface contract:
281
+
282
+ - each built-in tier emits a complete scoped token surface instead of inheriting editorial defaults through diffs
283
+ - tier choice is a top-level class on any `.bf-theme` container: `.bf-tier-editorial`, `.bf-tier-documentation`, `.bf-tier-app`, `.bf-tier-os`
284
+ - multiple containers can coexist side by side under the same stylesheet
285
+ - `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
286
+ - the published manifest omits local build-machine config/baseline file paths, so the shipped JSON stays portable
287
+ - every tier keeps metric-derived runtime alignment while nested stacks own semantic spacing
288
+
289
+ Example:
290
+
291
+ ```html
292
+ <section class="bf-theme bf-tier-editorial bf-stack is-section">
293
+ <div class="bf-prose bf-stack">
294
+ <h1>Editorial surface</h1>
295
+ <p>Metric-derived nudges stay on.</p>
296
+ </div>
297
+ </section>
298
+
299
+ <section class="bf-theme bf-tier-app bf-stack is-section">
300
+ <div class="bf-prose bf-stack">
301
+ <h1>App surface</h1>
302
+ <p>Metric-derived compensation and container-owned gaps remain active at application density.</p>
303
+ </div>
304
+ </section>
305
+
306
+ <section class="bf-theme bf-tier-os bf-stack is-section">
307
+ <div class="bf-prose bf-stack">
308
+ <h1>OS surface</h1>
309
+ <p>Metrics stay on, but the measure and control geometry compress toward dense system surfaces.</p>
310
+ </div>
311
+ </section>
312
+ ```
313
+
314
+ Engine choice remains separate: `.bf-engine-metrics` is the default production path, `.bf-engine-cap` is demo-only.
315
+
316
+ See `config/tiers/` for the four canonical source configs. Compatibility preset names resolve to those same owners rather than duplicate JSON files.
317
+
318
+ ## Public API
319
+
320
+ Package root exports:
321
+
322
+ - `initAccordions`
323
+ - `toggleAccordionButton`
324
+ - `initApplicationLayouts`
325
+ - `initBaselineGridToggles`
326
+ - `setupBaselineGridToggle`
327
+ - `generateBaselineGridOverlayCss`
328
+ - `generateBaselineGridThemeOverrideCss`
313
329
  - `initCodeSnippets`
314
330
  - `initContextualMenus`
315
331
  - `initInPageNavigations`
@@ -321,268 +337,268 @@ Package root exports:
321
337
  - `initExpandingTables`
322
338
  - `initMobileCardTables`
323
339
  - `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`
340
+ - `initPanelDrawers`
341
+ - `initRangeControls`
342
+ - `setupRangeControl`
343
+ - `updateRangeFill`
344
+ - `initResizableAsides`
345
+ - `initSideNavigations`
346
+ - `initTopNavigations`
347
+ - `initTabs`
348
+ - `initTooltips`
349
+ - `tierNames`, `tierDescriptions`, and `isTierName`
350
+ - `TierName`, `BuiltInThemeName`, `ThemeSurfaceManifest`, and related public types
351
+
352
+ Node/build exports:
353
+
354
+ - `buildThemeFromConfig`
355
+ - `buildThemeFromTier`
356
+ - `buildThemeFromPreset`
357
+ - `deriveBaselineTokensFromConfig`
358
+ - `readThemeConfig`
359
+
360
+ Static assets:
361
+
362
+ - `baseline-foundry/styles.css`
363
+ - `baseline-foundry/tokens.json`
364
+ - `baseline-foundry/surfaces.json`
365
+ - `baseline-foundry/tiers/editorial.css`
366
+ - `baseline-foundry/tiers/editorial.tokens.json`
367
+ - `baseline-foundry/tiers/editorial.surfaces.json`
368
+ - `baseline-foundry/tiers/documentation.css`
369
+ - `baseline-foundry/tiers/documentation.tokens.json`
370
+ - `baseline-foundry/tiers/documentation.surfaces.json`
371
+ - `baseline-foundry/tiers/app.css`
372
+ - `baseline-foundry/tiers/app.tokens.json`
373
+ - `baseline-foundry/tiers/app.surfaces.json`
374
+ - `baseline-foundry/tiers/os.css`
375
+ - `baseline-foundry/tiers/os.tokens.json`
376
+ - `baseline-foundry/tiers/os.surfaces.json`
377
+ - `baseline-foundry/presets/prose.css`
378
+ - `baseline-foundry/presets/prose.tokens.json`
379
+ - `baseline-foundry/presets/prose.surfaces.json`
380
+ - `baseline-foundry/presets/app-tier.css`
381
+ - `baseline-foundry/presets/app-tier.tokens.json`
382
+ - `baseline-foundry/presets/app-tier.surfaces.json`
383
+ - `baseline-foundry/presets`
384
+ - `baseline-foundry/types`
385
+
386
+ ### Entry point guidance
387
+
388
+ 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.
389
+
390
+ - 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`.
391
+ - Use `baseline-foundry/tiers/os.css` only when the consumer wants OS to be the unscoped default surface for that stylesheet import.
392
+ - 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.
393
+
394
+ Example neutral entrypoint for a downstream such as `a4-generator`:
395
+
396
+ ```html
397
+ <link rel="stylesheet" href="baseline-foundry/styles.css" />
398
+
399
+ <section class="bf-theme bf-tier-os">
400
+ <div class="bf-prose">
401
+ <h1>OS surface</h1>
402
+ <p>The shared root bundle is loaded once, and the container opts into the OS tier explicitly.</p>
403
+ </div>
404
+ </section>
405
+ ```
406
+
407
+ Example OS-default entrypoint when class switching is not needed:
408
+
409
+ ```html
410
+ <link rel="stylesheet" href="baseline-foundry/tiers/os.css" />
411
+ ```
412
+
413
+ ## Downstream Fonts
414
+
415
+ The built-in default is Ubuntu Sans Variable, but downstream repos are not locked to it.
416
+ Point the build at a downstream theme config and derive fresh nudges from that font's real metrics.
417
+
418
+ The npm package does not ship BF's development font file and built-in CSS does
419
+ not guess a URL for it. A consumer using a built-in tier must serve Ubuntu Sans
420
+ Variable and declare one normal variable face covering weights 100 through
421
+ 800 and stretches 75% through 100%. The manifest's relative
422
+ `fontFiles[*].path` records the source asset used for metric generation; it is
423
+ not a package runtime URL.
424
+
425
+ The key rule is simple:
426
+
427
+ - **do not reuse nudges from a different font**
428
+ - **do not switch font-family in CSS without regenerating tokens**
429
+ - **derive a fresh `nudgeTop` set for the actual font files that will ship**
430
+
431
+ ### What the downstream config needs
432
+
433
+ Create a theme JSON that follows the same shape as the tier configs under `config/tiers/`.
434
+ 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.
435
+
436
+ At minimum, define:
437
+
438
+ - `baselineUnit`
439
+ - `fontFiles`
440
+ - `fontStacks`
441
+ - `elements`
442
+ - `roles`
443
+ - `layout`
444
+ - `components`
445
+
446
+ Example sketch for a downstream Ubuntu Sans theme:
447
+
448
+ ```json
449
+ {
450
+ "baselineUnit": 0.25,
451
+ "fontFiles": [
452
+ {
453
+ "family": "ubuntu-sans",
454
+ "path": "../apps/overlay-preview/public/assets/fonts/UbuntuSans-Regular.ttf",
455
+ "cssFamily": "Ubuntu Sans",
456
+ "fontStyle": "normal",
457
+ "fontWeight": "100 800",
458
+ "fontDisplay": "swap"
459
+ }
460
+ ],
461
+ "fontStacks": {
462
+ "ubuntu-sans": "\"Ubuntu Sans\", \"Ubuntu\", system-ui, sans-serif"
463
+ },
464
+ "elements": [
465
+ {
466
+ "identifier": "body",
467
+ "fontSize": 0.75,
468
+ "lineHeight": 4,
469
+ "spaceAfter": 1,
470
+ "fontFamily": "ubuntu-sans",
471
+ "fontWeight": 400,
472
+ "fontStyle": "normal"
473
+ }
474
+ ],
475
+ "roles": {
476
+ "body": "body"
477
+ },
478
+ "layout": {
479
+ "contentMaxWidthRem": 90,
480
+ "contentPaddingInlineRem": 1,
481
+ "measureRem": 40,
482
+ "sectionSpaceBaselineUnits": 8,
483
+ "sectionSpaceDeepBaselineUnits": 16,
484
+ "stripSpaceBaselineUnits": 8,
485
+ "gridGapInlineBaselineUnits": 2,
486
+ "gridGapBlockBaselineUnits": 2,
487
+ "pageMarginBaselineUnits": 2
488
+ },
489
+ "components": {
490
+ "borderWidthPx": 1,
491
+ "radiusRem": 0,
492
+ "controlBlockPaddingRem": 0.5,
493
+ "controlCompactBlockPaddingRem": 0.25,
494
+ "controlInlinePaddingRem": 1,
495
+ "controlVisualSizeRem": 0.75,
496
+ "fieldGapBaselineUnits": 1,
497
+ "panelPaddingInlineBaselineUnits": 2,
498
+ "panelPaddingBlockBaselineUnits": 2,
499
+ "accordionIndentBaselineUnits": 3
500
+ }
501
+ }
502
+ ```
503
+
504
+ ### Generate full downstream CSS and tokens
505
+
506
+ Use the Node/build subpath so the downstream repo does not need to duplicate any build logic:
507
+
508
+ ```ts
509
+ import { buildThemeFromConfig } from "baseline-foundry/build";
510
+
511
+ await buildThemeFromConfig("config/ubuntu-foundry-theme.json", {
512
+ distDir: "generated/foundry/ubuntu",
513
+ baselineDir: ".generated/baseline/ubuntu"
514
+ });
515
+ ```
516
+
517
+ 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:
518
+
519
+ 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.
520
+
521
+ ```ts
522
+ await buildThemeFromConfig("config/brand-ibm-plex-theme.json", {
523
+ distDir: "generated/foundry/smoke",
524
+ baselineDir: ".generated/baseline/smoke",
525
+ surfaceLabel: "IBM Plex Sans",
526
+ additionalSurfaces: [
527
+ {
528
+ name: "ubuntu-smoke",
529
+ label: "Ubuntu Sans",
530
+ className: "bf-surface-ubuntu-smoke",
531
+ configPath: "config/ubuntu-foundry-theme.json"
532
+ }
533
+ ]
534
+ });
535
+ ```
536
+
537
+ That does three things:
538
+
539
+ 1. writes the reduced baseline-generator input JSON
540
+ 2. runs `@lyubomir-popov/baseline-nudge-generator`
541
+ 3. emits `tokens.json`, `styles.css`, and `surfaces.json` for the downstream font or surface set
542
+
543
+ `surfaces.json` will then expose each named surface's runtime tokens, stored metrics, and optional UI label under one manifest-backed bundle.
544
+
545
+ ### Derive nudges only
546
+
547
+ If the downstream repo wants the font metrics and `nudgeTop` values but plans to own CSS generation itself, use `deriveBaselineTokensFromConfig`:
548
+
549
+ ```ts
550
+ import { deriveBaselineTokensFromConfig } from "baseline-foundry/build";
551
+
552
+ const result = await deriveBaselineTokensFromConfig("config/ubuntu-foundry-theme.json", {
553
+ baselineDir: ".generated/baseline/ubuntu"
554
+ });
555
+
556
+ console.log(result.tokens.elements.body.nudgeTop);
557
+ console.log(result.baselineConfigPath);
558
+ console.log(result.baselineTokensPath);
559
+ ```
560
+
561
+ ### Direct utility usage
562
+
563
+ `baseline-foundry` uses `@lyubomir-popov/baseline-nudge-generator` under the hood.
564
+ The reduced config passed to that utility contains only:
565
+
566
+ - `baselineUnit`
567
+ - `fontFiles` with build-time font paths
568
+ - `elements` with `identifier`, `fontSize`, `lineHeight`, `spaceAfter`, `fontFamily`, `fontWeight`, and `fontStyle`
569
+
570
+ Equivalent direct usage looks like this:
571
+
572
+ ```ts
573
+ import { generateFromConfig } from "@lyubomir-popov/baseline-nudge-generator";
574
+
575
+ await generateFromConfig(".generated/baseline/ubuntu/ubuntu-foundry-theme.baseline.json", ".generated/baseline/ubuntu");
576
+ ```
577
+
578
+ 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.
579
+
580
+ ### Practical downstream advice
581
+
582
+ - Regenerate tokens whenever the downstream font files change.
583
+ - Regenerate tokens whenever font size, line-height, or baseline unit changes.
584
+ - Keep at least one non-`runtimeOnly` font file in `fontFiles`; that is the file the nudge generator reads for metrics.
585
+ - If a downstream repo ships multiple runtime faces, mark only the non-metric extras as `runtimeOnly`.
586
+ - Keep metrics as the default engine for production fonts; `.bf-engine-cap` remains an opt-in fallback, not the default path.
587
+
588
+ ## Demo
589
+
590
+ The demo surface at `/` shows editorial prose rhythm, tier switching, dark
591
+ theme, grid, spacing, and component specimens. BF primitives are indexed at
592
+ `demo/components/index.html`; patterns, site compositions, and layouts are
593
+ indexed separately at `demo/patterns/index.html`. Their isolated test routes
594
+ remain under `demo/components/`, and the authoritative saved-page inventory is
595
+ in `scripts/component-demo-shared.ts`.
596
+
597
+ ## Start Here
598
+
599
+ If you resume this repo in a new chat, read:
600
+
601
+ 1. `AGENTS.md`
602
+ 2. `AGENT-INBOX.md`
603
+ 3. `docs/agent-index.md`
604
+ 4. `docs/specs.md`