@obvi/blueprint 1.0.10 → 1.1.0
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 +13 -436
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/dist/blueprint-choices.js +64 -4
- package/dist/blueprint.css +1655 -668
- package/dist/blueprint.js +2460 -36
- package/dist/code-highlighting/blueprint-code.css +12 -7
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,446 +1,23 @@
|
|
|
1
|
-
# blueprint
|
|
1
|
+
# @obvi/blueprint
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The CSS design system behind [Obvious](https://obvious.ai) blueprints.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
7
|
+
## The idea
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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.
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
The result is one portable stylesheet, no build step, that turns plain semantic markup into a polished blueprint.
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
## Learn more
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
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
20
|
|
|
24
|
-
|
|
21
|
+
## License
|
|
25
22
|
|
|
26
|
-
|
|
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.
|
|
23
|
+
MIT. See [`LICENSE`](./LICENSE) and [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md).
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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`
|