@obvi/blueprint 1.0.10 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,446 +1,24 @@
1
- # blueprint.css
1
+ # @obvi/blueprint
2
2
 
3
- A classless-first CSS design system for beautiful technical blueprint documents.
3
+ The CSS design system behind [Obvious](https://obvious.ai) blueprints.
4
4
 
5
- Drop a single stylesheet onto a plain semantic HTML document and it renders as a polished blueprint monochrome ink on paper, set in Booton, with the Obvious drafting-blue identity reserved for illustrations alone. No classes required. On top of that semantic base sits a small, zero-specificity component layer for the compositions blueprint authors keep hand-rolling.
5
+ This package is built for and used by Obvious. It is published so Obvious-generated documents resolve their stylesheet, not as a general-purpose framework outside of an Obvious blueprint there is little reason to reach for it.
6
6
 
7
- ## What ships
7
+ ## The idea
8
8
 
9
- - **`blueprint.css`** the portable visual library. One file, no build step.
10
- - **`blueprint.js`** — optional TOC-current-state and reading-progress behavior.
11
- - **`SKILL.md`** — the canonical Blueprint authoring workflow and design contract.
12
- - **`code-highlighting/`** — optional Prism theme, source component behavior, and vendored runtime.
13
- - **`index.html`** — a self-demonstrating reference page built *with* the library. Every component is shown live beside its source markup.
14
- - **`harness/`** — Puppeteer-based render + measure gate that validates the build across desktop/tablet/mobile and light/dark themes.
9
+ A blueprint is a technical document that should look like one: monochrome ink on paper, drafting-blue reserved for illustrations, and structure that carries its own meaning.
15
10
 
16
- ## Philosophy
11
+ - **Semantic HTML is the API.** Headings, paragraphs, lists, tables, figures, and landmarks render as a finished document with no classes to memorize. You write meaning; the design arrives for free.
12
+ - **Classless-first, not class-only.** Components exist only for the few patterns native HTML cannot express, and each is a composition of real semantic elements rather than a replacement for them.
13
+ - **Zero-specificity by contract.** Everything ships in cascade layers wrapped in `:where()`, so the library never fights your own CSS. Your styles always win, with no `!important` and no parent-scoping.
14
+ - **Runtime-complete package.** The default stylesheet includes the chrome that `blueprint.js` injects, and runtime icons are inline SVG, so installed documents do not depend on repository-only CSS or assets.
17
15
 
18
- ### Semantic HTML is the API
16
+ The result is one portable stylesheet, no build step, that turns plain semantic markup into a polished blueprint.
19
17
 
20
- The default authoring mode is plain HTML: headings, paragraphs, lists, tables, figures, quotes, definition lists, landmarks, and disclosure elements. The library styles those base elements intentionally so authors write *meaning* and the design arrives for free.
18
+ ## Learn more
21
19
 
22
- ### Classless-first, not class-only
20
+ Blueprints are created and rendered through Obvious. To see what they are and how to use them, go to **[obvious.ai](https://obvious.ai)**.
23
21
 
24
- Classes are reserved for patterns native HTML cannot express on its own — things like a primary decision panel, a typed callout family, or a true option-comparison grid. Every promoted component is a composition of real semantic elements, not a replacement for them.
22
+ ## License
25
23
 
26
- ### The specificity contract
27
-
28
- This library is engineered to eliminate the silent cascade trap that plagued the prior base sheet.
29
-
30
- - The stylesheet is ordered with **cascade layers**:
31
- `@layer reset, tokens, base, components, utilities`
32
- - Every selector inside those layers is wrapped in **`:where()`**, so its specificity is zero.
33
-
34
- That means:
35
-
36
- 1. **Components always beat base elements** because `components` comes after `base`, regardless of selector specificity.
37
- 2. **Your own CSS always beats the library** because unlayered author CSS sits above all layered rules.
38
- 3. A bare authored class inside the typographic base wins with **no parent-scoping** and no `!important`.
39
-
40
- The harness proves this with a cascade fixture.
41
-
42
- ## Library structure
43
-
44
- ### 1. Tokens & scales
45
-
46
- The `tokens` layer defines:
47
-
48
- - a neutral ink scale (the Obvious opacity-based black/white) for all text and chrome
49
- - a dedicated illustration accent (`--bp-illustration-*`) — the only blue, used by SVG marks
50
- - neutral document surfaces
51
- - light/dark theme tokens via `data-obvious-theme`
52
- - a named type scale
53
- - a 3-step monospace label scale
54
- - an 8px spacing rhythm
55
- - a 1px SVG stroke token
56
-
57
- ### 2. Classless semantic base
58
-
59
- The `base` layer styles raw HTML at zero specificity, including:
60
-
61
- - document landmarks (`main`, `article`, `section`, `header`, `footer`, `nav`, `aside`, `hgroup`)
62
- - headings with automatic section numbering
63
- - paragraphs, lists, emphasis, links, code/pre, definitions, and abbreviations
64
- - citation + provenance (`blockquote[cite]`, `cite`, `q`, `figure`, `figcaption`, footnotes, `time`)
65
- - semantic tables with row-header vs column-header distinction
66
- - disclosure (`details` / `summary`)
67
-
68
- Body text defaults to **left-aligned** — not justified.
69
-
70
- ### 3. Component layer
71
-
72
- Reusable primitives promoted from repeated blueprint authoring patterns:
73
-
74
- - `.bp-lede` — narrative lede escape hatch
75
- - `.bp-decision` — primary decision panel: an inverted `.bp-decision__bar` (kicker `.bp-label` + optional `.bp-decision__status` pill + optional `.bp-decision__meta` provenance with `<time>`), the hero `.bp-decision-stmt`, stacked `.bp-decision__tenets` (a `<dl>`), and a hatched `.bp-decision__revisit` change-condition footer. Add `.bp-decision--collapsible` on a `<details>` whose `<summary>` is the `.bp-decision__bar` (kicker + a compact `.bp-decision__title` left, `.bp-decision__meta` + `.bp-decision__caret` right) for a native, script-free collapsed form: collapsed it is just the bar, and the tenets + revisit footer reveal on expand
76
- - `<bp-callout type="locked|invariant|ref">` — typed callout element (expands to the `.bp-callout` family below). Drafting-style: four L-shaped corner registration ticks (drawn as background gradients, no extra DOM) bracket the body instead of a box, headed by a `.bp-ctag` label (icon + compact mono caption). `--locked` uses solid ink ticks, `--invariant` adds the hatch fill with heavier ticks, `--ref` uses soft ticks. `label="…"` overrides the caption and `icon="none"` drops the glyph; the raw `.bp-callout--locked` / `--invariant` / `--ref` markup stays valid for hand-built or stored documents
77
- - `<bp-choice layout="tabs|stack|gallery">` — interactive deliberation for section directions or mockup picks; expands to a CSS-only form (verdict banner, inline rationale, reconsider reset)
78
- - `<bp-preflight>` — pre-draft decision panel; gates a fenced draft target until required questions are answered
79
- - `<bp-choice-record>` — compact archive of resolved decisions with optional considered-options disclosure
80
- - `.bp-deflist` — widened definition-list variant
81
- - `.bp-option-grid`, `.bp-opt--rec`, `.bp-verdict` — parallel option comparisons
82
- - `.bp-sequence` — numbered linear pipeline
83
- - `.bp-state-grid` — state model grid
84
- - `.bp-table-wrap` — wide-table scroll container
85
- - `<bp-callout>` — typed callout Web Component; `type` picks the role (locked / invariant / ref) and it expands to the ticked frame, icon, and label
86
- - `<bp-source>` — collapsible, syntax-highlighted source Web Component with copy control
87
- - `<bp-cite>` — inline code-citation tooltip; hovering/focusing the cited text reveals the source in a top-layer popover
88
- - `.bp-node`, `.bp-edge`, `.bp-svg-label`, `.bp-svg-meta` — SVG figure marks (the drafting-blue illustration layer)
89
- - `.bp-stack-iso`, `.bp-stack-top`, `.bp-stack-face`, `.bp-stack-grid`, `.bp-svg-dot` — isometric solids for box/machine/conveyor illustrations with callout leaders
90
- - `.bp-sidebar`, `.bp-toc` — the fixed contents rail: auto-numbered entries, a hairline guide track, and a gliding active marker driven by `blueprint.js`
91
- - `.bp-contents` — a full-width, inline contents section: a calm numbered, multi-column index with dotted leaders and no active/selected state (a top-of-document index has no "current" entry; the fixed rail handles position tracking)
92
- - `.scroll-progress`, `.bp-title-block` — document chrome
93
-
94
- ### Compatibility and TOC state contract
95
-
96
- Version 1.0.4 retains a transitional compatibility layer for markup emitted by current Blueprint authoring tools and stored documents. Legacy token names and `.bp-content`, `.bp-layout`, `.bp-card-grid`, `.bp-card`, `.bp-chip`, `.bp-document`, `.bp-title`, `.bp-placeholder`, `.bp-section`, `.bp-section-number`, `.bp-figure`, `.bp-figure-caption`, `.bp-table`, and `.bp-toc` remain supported while semantic HTML is preferred for new documents.
97
-
98
- A document with section navigation uses `blueprint.js` to set **exactly one** TOC link to `aria-current="location"`. The stylesheet also recognizes `.active` and `.is-active`, but `aria-current` is canonical and provides the accessibility contract.
99
-
100
- At 860px and below, `.bp-sidebar` and `.bp-toc` become horizontally scrollable section navigation instead of disappearing. Print hides only Blueprint TOC/sidebar chrome, not unrelated authored `nav` elements.
101
-
102
- ## Install from npm
103
-
104
- The package is published privately under the standard `@obvi` npm scope. Supply
105
- an npm token with access to the organization without committing it:
106
-
107
- ```ini
108
- @obvi:registry=https://registry.npmjs.org
109
- //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
110
- ```
111
-
112
- Pin the exact package version so the stylesheet is immutable across installs:
113
-
114
- ```bash
115
- npm install --save-exact @obvi/blueprint@1.0.10
116
- ```
117
-
118
- Import the canonical stylesheet from the supported package export:
119
-
120
- ```js
121
- import "@obvi/blueprint/blueprint.css";
122
- ```
123
-
124
- For static HTML that does not use a bundler, copy the exported stylesheet from
125
- `node_modules/@obvi/blueprint/dist/blueprint.css` into your deployed
126
- assets and link that copy.
127
-
128
- The npm tarball intentionally excludes the authoring `SKILL.md`, docs site,
129
- examples, harness, and source scripts. Consumers receive only the built runtime
130
- assets under `dist/` plus package metadata and license notices.
131
-
132
- ## Include the stylesheet
133
-
134
- ```html
135
- <link rel="stylesheet" href="blueprint.css" />
136
- ```
137
-
138
- Set theme on the root element:
139
-
140
- ```html
141
- <html data-obvious-theme="light">
142
- <html data-obvious-theme="dark">
143
- ```
144
-
145
- ## Fonts & offline use
146
-
147
- The stylesheet loads the Booton variable font from the Obvious CDN (`https://obvious.ai/fonts/`). This works out of the box for online consumers.
148
-
149
- **Self-hosting:** If you need the font available offline or want to avoid an external network dependency, download `BootonVF.woff2` and `BootonItalicsVF.woff2` from that URL, host them alongside your stylesheet, and replace the `src` URLs in the two `@font-face` blocks at the top of `blueprint.css`:
150
-
151
- ```css
152
- /* Before (CDN) */
153
- src: url("https://obvious.ai/fonts/BootonVF.woff2") format("woff2");
154
-
155
- /* After (self-hosted) */
156
- src: url("./fonts/BootonVF.woff2") format("woff2");
157
- ```
158
-
159
- **Fallback:** If Booton fails to load for any reason, `--bp-sans` falls back through `system-ui → -apple-system → sans-serif`. All text remains legible; only the Booton identity is lost.
160
-
161
- ## Example: no classes required
162
-
163
- ```html
164
- <section>
165
- <h2>Recovery saga</h2>
166
- <p>A crashed worker must never leave a session both orphaned and still-claimed.</p>
167
- <p>Each step below is idempotent, so replaying from any checkpoint converges to a single owner.</p>
168
- </section>
169
- ```
170
-
171
- The section number, heading hierarchy, and first-paragraph lede all render from structure alone.
172
-
173
- ## Example: authored class beats the base naturally
174
-
175
- ```html
176
- <style>
177
- .author-lede {
178
- font-size: 28px;
179
- line-height: 36px;
180
- }
181
- </style>
182
-
183
- <p class="author-lede">A bare authored class wins with no parent-scoping.</p>
184
- ```
185
-
186
- The harness verifies that this computes to `28px / 36px`, while a sibling paragraph remains at the base `14px / 20px`.
187
-
188
- ## Docs page
189
-
190
- Open `index.html` in a browser. It includes:
191
-
192
- - the semantic-first story
193
- - the specificity contract
194
- - token and scale samples
195
- - citation/provenance examples
196
- - semantic table behavior
197
- - the full inline technical family
198
- - every promoted component rendered live beside its source
199
- - a theme toggle and scroll-progress bar
200
-
201
- ## Harness
202
-
203
- One-time setup:
204
-
205
- ```bash
206
- npm install
207
- npx puppeteer browsers install chrome
208
- ```
209
-
210
- Run the gate:
211
-
212
- ```bash
213
- npm run measure
214
- ```
215
-
216
- This checks:
217
-
218
- - computed hierarchy (font-size / line-height)
219
- - the no-parent-scoping cascade contract
220
- - no page-level horizontal overflow at `1440 / 700 / 390`
221
- - TOC DOM state and computed visual distinction across `1440 / 861 / 860 / 390`
222
- - initial hash, click, reload, history, down/up scroll, and document-bottom transitions
223
- - hover, keyboard focus, reduced motion, mobile navigation, and print scoping
224
- - light and dark themes
225
- - the same behavioral fixture against source and packed package CSS
226
-
227
- For screenshots:
228
-
229
- ```bash
230
- npm run render
231
- ```
232
-
233
- Both commands write to `harness/output/`.
234
-
235
- ## Project layout
236
-
237
- ```text
238
- .
239
- ├── blueprint.css
240
- ├── blueprint.js
241
- ├── dist/
242
- │ ├── blueprint.css
243
- │ ├── blueprint.js
244
- │ └── code-highlighting/
245
- │ ├── blueprint-code.css
246
- │ └── blueprint-code.js
247
- ├── index.html
248
- ├── package.json
249
- └── harness/
250
- ├── README.md
251
- ├── lib.mjs
252
- ├── measure.mjs
253
- ├── render.mjs
254
- └── fixtures/
255
- └── cascade.html
256
- ```
257
-
258
- ## Code highlighting: embed decision + monochrome theme
259
-
260
- ### Decision
261
-
262
- **Use Prism (a class-based runtime syntax highlighter) plus a CSS theme file. Do not use Shiki.**
263
-
264
- This project’s core promise is **one portable CSS file, no build step**. A class-based runtime highlighter emits stable token classes into the DOM (`.token.keyword`, `.token.string`, etc.), and the monochrome syntax theme is therefore **just CSS**. It drops into a blueprint exactly the way `blueprint.css` does and keys off the same `data-obvious-theme` attribute for light/dark.
265
-
266
- Shiki was considered and rejected for this use case. It is the most accurate option because it uses TextMate grammars, but it also **bakes colors inline at build time** and typically requires a rendering/build step. That fights both parts of this deliverable:
267
-
268
- 1. the theme should be a **portable CSS file**, not generated HTML with inline styles, and
269
- 2. blueprint documents should remain **drop-in and static** rather than requiring a compilation pipeline.
270
-
271
- Shiki is a good choice when grammar accuracy outweighs portability. It is the wrong fit when the deliverable itself is a reusable CSS theme.
272
-
273
- ### Why Prism instead of highlight.js?
274
-
275
- Both Prism and highlight.js satisfy the core architectural decision because both are runtime highlighters that can be themed with CSS. Prism is the better fit here for one concrete reason: **its token taxonomy is finer-grained**.
276
-
277
- A monochrome theme cannot rely on hue changes to separate syntax classes. It needs the highlighter to expose meaningful semantic buckets so the theme can differentiate by **luminance, weight, and italic** instead. Prism gives us stable classes like:
278
-
279
- - `.token.keyword`
280
- - `.token.string`
281
- - `.token.comment`
282
- - `.token.function`
283
- - `.token.number`
284
- - `.token.operator`
285
- - `.token.punctuation`
286
- - `.token.class-name`
287
- - `.token.attr-name`
288
-
289
- That richer taxonomy made it possible to create a readable hierarchy in a single neutral ink — code is text, so it stays monochrome and leaves blue to the illustrations. Prism also has a simple drop-in story for static HTML and an optional line-numbers plugin that layers on cleanly.
290
-
291
- ### Markup contract for blueprint documents
292
-
293
- Canonical block pattern:
294
-
295
- ```html
296
- <pre class="language-typescript"><code class="language-typescript">
297
- export const answer = 42;
298
- </code></pre>
299
- ```
300
-
301
- Rules:
302
-
303
- 1. **Put the language on both elements** (`pre` and `code`) using Prism’s `language-xxx` convention.
304
- 2. **Escape HTML-sensitive characters** inside code samples when authoring static HTML (`<`, `>`, `&`).
305
- 3. For a filename or caption, wrap the block in a figure:
306
-
307
- ```html
308
- <figure class="bpc-code">
309
- <figcaption>session-scheduler.ts</figcaption>
310
- <pre class="language-typescript line-numbers"><code class="language-typescript">
311
- // code…
312
- </code></pre>
313
- </figure>
314
- ```
315
-
316
- 4. Use plain inline `<code>` for short fragments in prose. Use `<pre><code>` for multi-line listings.
317
- 5. Highlighting is **additive**. If JavaScript never runs, the document still renders readable monospace code via the base `pre` / `code` styles. The theme only adds color, weight, and optional line-number chrome on top.
318
-
319
- ### Runtime initialization contract
320
-
321
- Static HTML loads one self-contained browser module:
322
-
323
- ```html
324
- <link rel="stylesheet" href="./blueprint-code.css" />
325
- <script type="module" src="./blueprint-code.js"></script>
326
- ```
327
-
328
- Application builds import the same registration entry once:
329
-
330
- ```js
331
- import "blueprint-css/web-components";
332
- ```
333
-
334
- The browser module bundles Prism core, the bounded language set, custom-element registration, and Clipboard API behavior. Consumers never load or order Prism scripts. It highlights conventional `<pre><code>` blocks and every `<bp-source>` instance.
335
-
336
- ### Web Component integration
337
-
338
- Use [`<bp-source>`](#bp-source) when a blueprint should show its own source without hand-authoring the repeated disclosure, toolbar, label, Prism classes, and copy control. Load the module once in the page shell or app entry; component markup needs no colocated script. The copy action uses clean source text, so Prism token spans never leak into the clipboard.
339
-
340
- ### Files in this side-mission deliverable
341
-
342
- - `code-highlighting/blueprint-code.css` — the portable monochrome Prism theme
343
- - `code-highlighting/blueprint-code.js` — self-contained browser bundle: Prism + native `<bp-source>` + Clipboard API
344
- - `code-highlighting/src/blueprint-code.js` — component source
345
- - `code-highlighting/scripts/build.mjs` — deterministic esbuild bundle
346
- - `code-highlighting/demo.html` — standalone multi-language demo page
347
- - `code-highlighting/vendor/prism/` — repo-only standalone-theme fixtures (MIT)
348
- - `code-highlighting/scripts/measure.mjs` — headless render + measurement harness
349
-
350
- ### Token → treatment mapping
351
-
352
- | Token role | Treatment principle |
353
- | --- | --- |
354
- | comment | faint gray, italic, de-emphasized |
355
- | punctuation | muted gray; structure recedes |
356
- | operator | muted but stronger than punctuation |
357
- | variable / plain code | baseline code ink |
358
- | string | mid gray, calm |
359
- | number / constant | darker gray stop; literals pop |
360
- | function | darker ink with medium weight |
361
- | class-name / builtin | darker ink with medium weight |
362
- | keyword | darkest ink, heaviest weight |
363
- | markup tag | keyword-adjacent weight; structural |
364
- | attr-name | mid-strong ink |
365
- | diff inserted / deleted | differentiated by luminance/tint + the +/- glyph, not hue |
366
-
367
- ### Verification
368
-
369
- The demo is verified by measurement, not eyeballing.
370
-
371
- One-time setup:
372
-
373
- ```bash
374
- cd code-highlighting
375
- npm install --no-save puppeteer@23
376
- npx puppeteer browsers install chrome
377
- ```
378
-
379
- Run the harness:
380
-
381
- ```bash
382
- node scripts/measure.mjs
383
- ```
384
-
385
- What it checks:
386
-
387
- - Prism actually ran (`.token` spans exist)
388
- - token colors resolved to the theme rather than falling back to plain text color
389
- - at least five distinct token colors exist (the hierarchy is not flattened)
390
- - no page-level horizontal overflow
391
- - WCAG contrast is adequate in **both** light and dark themes
392
- - screenshots are written to `code-highlighting/.measure/demo-light.png` and `demo-dark.png`
393
-
394
- ### How to include the theme in a blueprint
395
-
396
- 1. Load the base blueprint stylesheet.
397
- 2. Load `blueprint-code.css` after it.
398
- 3. Load or import `blueprint-code.js` once in the page shell or app entry.
399
- 4. Use `<bp-source>` or write conventional blocks with the `language-xxx` markup contract above.
400
-
401
- That keeps the consumer contract portable: **one CSS theme, one bounded browser module, and no Prism configuration.**
402
- See the feature branch / open PR for the full library, docs page, and harness.
403
-
404
- ## Web Components
405
-
406
- Blueprint stays semantic-HTML-first. A native custom element is added only when a repeated pattern needs behavior that HTML and CSS cannot provide alone. Components render their semantic structure into light DOM so the existing Blueprint cascade and David's syntax theme remain the single visual source of truth.
407
-
408
- ### `<bp-callout>`
409
-
410
- `<bp-callout>` collapses the typed-callout markup (the type icon, `.bp-ctag` label, and the corner-ticked `<aside>`) into one element. The body can be bare text or block content; bare text is wrapped in a `<p>`:
411
-
412
- ```html
413
- <bp-callout type="invariant">
414
- The fence token is monotonic and never reused.
415
- </bp-callout>
416
- ```
417
-
418
- | Input | Contract |
419
- | --- | --- |
420
- | `type` | `locked` (commitment), `invariant` (must-hold rule), or `ref` / `reference` (enumerated values). Defaults to `locked`. |
421
- | `label` | Optional caption override; defaults to the type's name. |
422
- | `icon` | `none` drops the leading glyph. |
423
-
424
- This is light DOM and registers from `blueprint.js`; the raw `.bp-callout` class markup remains valid for documents authored or stored without the element.
425
-
426
- ### `<bp-source>`
427
-
428
- `<bp-source>` turns escaped source text into a native `<details>` disclosure with an explicit language label, a Prism-highlighted `<pre><code>` block, and a copy control:
429
-
430
- ```html
431
- <bp-source language="typescript" open>
432
- interface Session {
433
- id: string;
434
- epoch: number;
435
- }
436
- </bp-source>
437
- ```
438
-
439
- | Input | Contract |
440
- | --- | --- |
441
- | `language` | Explicit bounded Prism grammar or supported alias; unavailable and unknown grammars fall back to readable plain text. |
442
- | `summary` | Optional disclosure label; defaults to `Source`. |
443
- | `open` | Uses the native boolean disclosure state. |
444
- | `sourceText` | Read-only clean source used by the built-in copy action. |
445
-
446
- No author-written clipboard JavaScript or separate copy library is needed. Browsers do not expose declarative copy-to-clipboard HTML, so the Web Component owns the small native Clipboard API call internally.
24
+ MIT. See [`LICENSE`](./LICENSE) and [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md).
@@ -1,5 +1,32 @@
1
1
  # Third-party notices
2
2
 
3
+ ## Iconoir
4
+
5
+ The distributed `blueprint.js` includes chrome icon path data derived from
6
+ Iconoir (https://iconoir.com).
7
+
8
+ MIT LICENSE
9
+
10
+ Copyright (c) 2021 Luca Burgio
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in
20
+ all copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
+ THE SOFTWARE.
29
+
3
30
  ## PrismJS 1.30.0
4
31
 
5
32
  The distributed `code-highlighting/blueprint-code.js` includes PrismJS.
@@ -14,6 +14,8 @@
14
14
  // </bp-choice>
15
15
  //
16
16
  // layout: tabs | stack | gallery
17
+ // resolved: option value of an already-made decision — renders statically
18
+ // (no inputs, no reset); the named option carries the verdict
17
19
  // verdict: kicker on the committed banner (default "Chosen")
18
20
  // adopt: tabs-only commit button label (default "Adopt this direction")
19
21
  // hint: footer hint; reconsider: reset button label (default "Reconsider")
@@ -88,7 +90,7 @@ function label(doc, text, className) {
88
90
  * @param {string} kicker
89
91
  * @param {Array<{ value: string, title: string }>} options
90
92
  */
91
- function buildVerdict(doc, kicker, options) {
93
+ function buildVerdict(doc, kicker, options, meta = 'You · just now') {
92
94
  const verdict = el(doc, 'div', 'bp-choice__verdict')
93
95
  verdict.append(label(doc, kicker))
94
96
  for (const opt of options) {
@@ -97,9 +99,11 @@ function buildVerdict(doc, kicker, options) {
97
99
  pick.textContent = opt.title
98
100
  verdict.append(pick)
99
101
  }
100
- const meta = el(doc, 'span', 'bp-choice__verdict-meta')
101
- meta.textContent = 'You · just now'
102
- verdict.append(meta)
102
+ if (meta) {
103
+ const metaEl = el(doc, 'span', 'bp-choice__verdict-meta')
104
+ metaEl.textContent = meta
105
+ verdict.append(metaEl)
106
+ }
103
107
  return verdict
104
108
  }
105
109
 
@@ -149,6 +153,54 @@ function readOptions(host) {
149
153
  }))
150
154
  }
151
155
 
156
+ /**
157
+ * Static, non-interactive rendering: the decision is already made. The option
158
+ * named by `resolved` carries the verdict; the rest read as considered-and-set-
159
+ * aside. No form, no inputs, no reset.
160
+ *
161
+ * @param {Document} doc
162
+ * @param {{ options: ReturnType<typeof readOptions>, verdictKicker: string, resolvedValue: string }} spec
163
+ */
164
+ function buildResolvedChoice(doc, { options, verdictKicker, resolvedValue }) {
165
+ const root = el(doc, 'div', 'bp-choice bp-choice--resolved')
166
+ const winner = options.find((o) => o.value === resolvedValue) ?? options[0]
167
+
168
+ const verdict = buildVerdict(
169
+ doc,
170
+ verdictKicker,
171
+ options.map((o) => ({ value: o.value, title: o.title })),
172
+ ''
173
+ )
174
+ const winnerPick = winner && verdict.querySelector(`.bp-choice__verdict-pick[data-for="${winner.value}"]`)
175
+ if (winnerPick) winnerPick.setAttribute('data-resolved', '')
176
+ root.append(verdict)
177
+
178
+ const stack = el(doc, 'div', 'bp-choice__stack')
179
+ for (const opt of options) {
180
+ const card = el(doc, 'div', 'bp-choice__card')
181
+ if (winner && opt.value === winner.value) card.setAttribute('data-resolved', '')
182
+
183
+ const head = el(doc, 'div', 'bp-choice__card-head')
184
+ if (opt.optionLabel) head.append(label(doc, opt.optionLabel))
185
+ const chosen = el(doc, 'span', 'bp-choice__tag bp-choice__tag--ink bp-choice__card-chosen')
186
+ chosen.textContent = `✓ ${verdictKicker}`
187
+ const rejected = el(doc, 'span', 'bp-choice__tag bp-choice__tag--out bp-choice__card-rejected')
188
+ rejected.textContent = 'Not chosen'
189
+ head.append(chosen, rejected)
190
+ card.append(head)
191
+
192
+ const h4 = el(doc, 'h4')
193
+ h4.textContent = opt.title
194
+ card.append(h4)
195
+ card.append(bodyWithoutRationale(opt.el))
196
+ const rationale = extractRationale(opt.el)
197
+ if (rationale) card.append(rationale)
198
+ stack.append(card)
199
+ }
200
+ root.append(stack)
201
+ return root
202
+ }
203
+
152
204
  class BlueprintRationaleElement extends HTMLElement {
153
205
  connectedCallback() {
154
206
  if (this.dataset.bpRendered) return
@@ -175,6 +227,14 @@ class BlueprintChoiceElement extends HTMLElement {
175
227
  const compareBody = this.querySelector(':scope > [slot="compare"]')
176
228
 
177
229
  const options = readOptions(this)
230
+
231
+ // resolved: render the decision as already made (no interaction).
232
+ const resolvedValue = this.getAttribute('resolved')
233
+ if (resolvedValue !== null) {
234
+ this.replaceChildren(buildResolvedChoice(doc, { options, verdictKicker, resolvedValue }))
235
+ return
236
+ }
237
+
178
238
  const scopeId = nextId('bp-choice')
179
239
  const viewName = `${scopeId}-view`
180
240
  const pickName = `${scopeId}-pick`