@obvi/blueprint 1.0.9 → 1.0.10

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
@@ -74,6 +74,9 @@ Reusable primitives promoted from repeated blueprint authoring patterns:
74
74
  - `.bp-lede` — narrative lede escape hatch
75
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
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
77
80
  - `.bp-deflist` — widened definition-list variant
78
81
  - `.bp-option-grid`, `.bp-opt--rec`, `.bp-verdict` — parallel option comparisons
79
82
  - `.bp-sequence` — numbered linear pipeline
@@ -109,7 +112,7 @@ an npm token with access to the organization without committing it:
109
112
  Pin the exact package version so the stylesheet is immutable across installs:
110
113
 
111
114
  ```bash
112
- npm install --save-exact @obvi/blueprint@1.0.9
115
+ npm install --save-exact @obvi/blueprint@1.0.10
113
116
  ```
114
117
 
115
118
  Import the canonical stylesheet from the supported package export:
@@ -0,0 +1,507 @@
1
+ // blueprint-choices.js — interactive choice / preflight Web Components.
2
+ //
3
+ // Authoring shrinks to a small set of elements; each component expands to
4
+ // light-DOM markup styled by blueprint.css. Interaction is CSS-only (radio /
5
+ // checkbox + :has() + native form reset). Per-instance scoped rules are
6
+ // injected only where value-specific selectors are required (tab panels,
7
+ // verdict labels, preflight gate).
8
+ //
9
+ // <bp-choice layout="tabs" verdict="Adopted">
10
+ // <bp-choice-option value="a" tab="Draft A" title="Phased rollout">
11
+ // <p>…</p>
12
+ // <bp-rationale><p><strong>Trade-off.</strong> …</p></bp-rationale>
13
+ // </bp-choice-option>
14
+ // </bp-choice>
15
+ //
16
+ // layout: tabs | stack | gallery
17
+ // verdict: kicker on the committed banner (default "Chosen")
18
+ // adopt: tabs-only commit button label (default "Adopt this direction")
19
+ // hint: footer hint; reconsider: reset button label (default "Reconsider")
20
+ // compare: gallery-only <details> summary (omit to skip compare block)
21
+
22
+ /** @typedef {'tabs' | 'stack' | 'gallery'} ChoiceLayout */
23
+
24
+ let choiceSeq = 0
25
+
26
+ /** @param {string} prefix */
27
+ function nextId(prefix) {
28
+ choiceSeq += 1
29
+ return `${prefix}-${choiceSeq}`
30
+ }
31
+
32
+ /** @param {ParentNode} root */
33
+ function extractRationale(root) {
34
+ const node = root.querySelector(':scope > bp-rationale')
35
+ if (!node) return null
36
+ const wrap = node.ownerDocument.createElement('div')
37
+ wrap.className = 'bp-choice-rationale'
38
+ wrap.append(...node.childNodes)
39
+ return wrap
40
+ }
41
+
42
+ /** @param {Element} root */
43
+ function bodyWithoutRationale(root) {
44
+ const frag = root.ownerDocument.createDocumentFragment()
45
+ for (const child of root.childNodes) {
46
+ if (child instanceof Element && child.tagName === 'BP-RATIONALE') continue
47
+ if (child instanceof Element && child.getAttribute('slot') === 'frame') continue
48
+ frag.append(child.cloneNode(true))
49
+ }
50
+ return frag
51
+ }
52
+
53
+ /** @param {Element} root */
54
+ function frameContent(root) {
55
+ const slotted = root.querySelector(':scope > [slot="frame"]')
56
+ if (slotted) {
57
+ const frame = slotted.cloneNode(true)
58
+ frame.removeAttribute('slot')
59
+ return frame
60
+ }
61
+ return null
62
+ }
63
+
64
+ /**
65
+ * @param {Document} doc
66
+ * @param {string} tag
67
+ * @param {string} [className]
68
+ */
69
+ function el(doc, tag, className) {
70
+ const node = doc.createElement(tag)
71
+ if (className) node.className = className
72
+ return node
73
+ }
74
+
75
+ /**
76
+ * @param {Document} doc
77
+ * @param {string} text
78
+ * @param {string} [className]
79
+ */
80
+ function label(doc, text, className) {
81
+ const node = el(doc, 'span', className ?? 'bp-label')
82
+ node.textContent = text
83
+ return node
84
+ }
85
+
86
+ /**
87
+ * @param {Document} doc
88
+ * @param {string} kicker
89
+ * @param {Array<{ value: string, title: string }>} options
90
+ */
91
+ function buildVerdict(doc, kicker, options) {
92
+ const verdict = el(doc, 'div', 'bp-choice__verdict')
93
+ verdict.append(label(doc, kicker))
94
+ for (const opt of options) {
95
+ const pick = el(doc, 'p', 'bp-choice__verdict-pick')
96
+ pick.dataset.for = opt.value
97
+ pick.textContent = opt.title
98
+ verdict.append(pick)
99
+ }
100
+ const meta = el(doc, 'span', 'bp-choice__verdict-meta')
101
+ meta.textContent = 'You · just now'
102
+ verdict.append(meta)
103
+ return verdict
104
+ }
105
+
106
+ /**
107
+ * @param {Document} doc
108
+ * @param {string} hint
109
+ * @param {string} reconsider
110
+ */
111
+ function buildFooter(doc, hint, reconsider) {
112
+ const actions = el(doc, 'div', 'bp-choice__actions')
113
+ if (hint) {
114
+ const hintEl = el(doc, 'span', 'bp-choice__hint')
115
+ hintEl.textContent = hint
116
+ actions.append(hintEl)
117
+ }
118
+ const reset = el(doc, 'button', 'bp-choice__reset')
119
+ reset.type = 'reset'
120
+ reset.textContent = reconsider
121
+ actions.append(reset)
122
+ return actions
123
+ }
124
+
125
+ /**
126
+ * @param {string} scope
127
+ * @param {string} viewName
128
+ * @param {string} pickName
129
+ * @param {Array<{ value: string, title: string }>} options
130
+ */
131
+ function scopedTabRules(scope, viewName, pickName, options) {
132
+ const lines = options.map(
133
+ (opt) =>
134
+ `${scope}:has([name="${viewName}"][value="${opt.value}"]:checked) .bp-choice__panel[data-value="${opt.value}"]{display:block}` +
135
+ `${scope}:has([name="${pickName}"][value="${opt.value}"]:checked) .bp-choice__verdict-pick[data-for="${opt.value}"]{display:block}`
136
+ )
137
+ return lines.join('\n')
138
+ }
139
+
140
+ /** @param {HTMLElement} host */
141
+ function readOptions(host) {
142
+ return [...host.querySelectorAll(':scope > bp-choice-option')].map((node) => ({
143
+ el: node,
144
+ value: node.getAttribute('value') ?? '',
145
+ tab: node.getAttribute('tab') ?? node.getAttribute('label') ?? node.getAttribute('value') ?? '',
146
+ title: node.getAttribute('title') ?? node.getAttribute('caption') ?? node.getAttribute('tab') ?? '',
147
+ optionLabel: node.getAttribute('label') ?? node.getAttribute('tab') ?? '',
148
+ caption: node.getAttribute('caption') ?? node.getAttribute('title') ?? '',
149
+ }))
150
+ }
151
+
152
+ class BlueprintRationaleElement extends HTMLElement {
153
+ connectedCallback() {
154
+ if (this.dataset.bpRendered) return
155
+ this.dataset.bpRendered = '1'
156
+ this.classList.add('bp-choice-rationale')
157
+ }
158
+ }
159
+
160
+ class BlueprintChoiceOptionElement extends HTMLElement {}
161
+
162
+ class BlueprintChoiceElement extends HTMLElement {
163
+ connectedCallback() {
164
+ if (this.dataset.bpRendered) return
165
+ this.dataset.bpRendered = '1'
166
+
167
+ const doc = this.ownerDocument
168
+ /** @type {ChoiceLayout} */
169
+ const layout = (this.getAttribute('layout') || 'stack').toLowerCase()
170
+ const verdictKicker = this.getAttribute('verdict') ?? 'Chosen'
171
+ const adoptLabel = this.getAttribute('adopt') ?? 'Adopt this direction'
172
+ const hint = this.getAttribute('hint') ?? ''
173
+ const reconsider = this.getAttribute('reconsider') ?? 'Reconsider'
174
+ const compareSummary = this.getAttribute('compare')
175
+ const compareBody = this.querySelector(':scope > [slot="compare"]')
176
+
177
+ const options = readOptions(this)
178
+ const scopeId = nextId('bp-choice')
179
+ const viewName = `${scopeId}-view`
180
+ const pickName = `${scopeId}-pick`
181
+
182
+ const form = el(doc, 'form', `bp-choice bp-choice--${layout}`)
183
+ form.dataset.bpChoice = scopeId
184
+
185
+ const titleOpts = options.map((o) => ({ value: o.value, title: o.title }))
186
+ form.append(buildVerdict(doc, verdictKicker, titleOpts))
187
+
188
+ if (layout === 'tabs') {
189
+ const seg = el(doc, 'div', 'bp-choice__seg')
190
+ seg.setAttribute('role', 'tablist')
191
+ for (const [index, opt] of options.entries()) {
192
+ const segOpt = el(doc, 'label', 'bp-choice__seg-opt')
193
+ const input = doc.createElement('input')
194
+ input.type = 'radio'
195
+ input.name = viewName
196
+ input.value = opt.value
197
+ input.className = 'bp-choice__view'
198
+ if (index === 0) input.checked = true
199
+ segOpt.append(input, doc.createTextNode(opt.tab))
200
+ seg.append(segOpt)
201
+ }
202
+ form.append(seg)
203
+
204
+ const panels = el(doc, 'div', 'bp-choice__panels')
205
+ for (const opt of options) {
206
+ const panel = el(doc, 'div', 'bp-choice__panel')
207
+ panel.dataset.value = opt.value
208
+ const h3 = el(doc, 'h3')
209
+ h3.textContent = opt.title
210
+ panel.append(h3)
211
+ panel.append(bodyWithoutRationale(opt.el))
212
+ const rationale = extractRationale(opt.el)
213
+ if (rationale) panel.append(rationale)
214
+
215
+ const actions = el(doc, 'div', 'bp-choice__actions')
216
+ const adopt = el(doc, 'label', 'bp-choice__adopt')
217
+ const commit = doc.createElement('input')
218
+ commit.type = 'radio'
219
+ commit.name = pickName
220
+ commit.value = opt.value
221
+ commit.className = 'bp-choice__commit'
222
+ adopt.append(commit, doc.createTextNode(adoptLabel))
223
+ actions.append(adopt)
224
+ panel.append(actions)
225
+ panels.append(panel)
226
+ }
227
+ form.append(panels)
228
+
229
+ const style = el(doc, 'style')
230
+ style.textContent = scopedTabRules(`[data-bp-choice="${scopeId}"]`, viewName, pickName, titleOpts)
231
+ form.prepend(style)
232
+
233
+ form.append(
234
+ buildFooter(
235
+ doc,
236
+ hint || 'Switch tabs to preview · adopt to commit',
237
+ reconsider
238
+ )
239
+ )
240
+ }
241
+
242
+ if (layout === 'stack') {
243
+ const stack = el(doc, 'div', 'bp-choice__stack')
244
+ for (const opt of options) {
245
+ const card = el(doc, 'label', 'bp-choice__card')
246
+ const input = doc.createElement('input')
247
+ input.type = 'radio'
248
+ input.name = pickName
249
+ input.value = opt.value
250
+ input.className = 'bp-choice__pick'
251
+ card.append(input)
252
+
253
+ const head = el(doc, 'div', 'bp-choice__card-head')
254
+ if (opt.optionLabel) head.append(label(doc, opt.optionLabel))
255
+ const chosen = el(doc, 'span', 'bp-choice__tag bp-choice__tag--ink bp-choice__card-chosen')
256
+ chosen.textContent = '✓ Chosen'
257
+ const rejected = el(doc, 'span', 'bp-choice__tag bp-choice__tag--out bp-choice__card-rejected')
258
+ rejected.textContent = 'Not chosen'
259
+ head.append(chosen, rejected)
260
+ card.append(head)
261
+
262
+ const h4 = el(doc, 'h4')
263
+ h4.textContent = opt.title
264
+ card.append(h4)
265
+ card.append(bodyWithoutRationale(opt.el))
266
+ const rationale = extractRationale(opt.el)
267
+ if (rationale) card.append(rationale)
268
+ stack.append(card)
269
+ }
270
+ form.append(stack)
271
+ form.append(
272
+ buildFooter(
273
+ doc,
274
+ hint || 'Select a card to commit · rejected drafts stay readable below',
275
+ reconsider
276
+ )
277
+ )
278
+ }
279
+
280
+ if (layout === 'gallery') {
281
+ const gallery = el(doc, 'div', 'bp-choice__gallery')
282
+ for (const opt of options) {
283
+ const mock = el(doc, 'label', 'bp-choice__mock')
284
+ const input = doc.createElement('input')
285
+ input.type = 'radio'
286
+ input.name = pickName
287
+ input.value = opt.value
288
+ input.className = 'bp-choice__pick'
289
+ mock.append(input)
290
+
291
+ const frame = el(doc, 'div', 'bp-choice__mock-frame')
292
+ const frameInner = frameContent(opt.el)
293
+ if (frameInner) frame.append(frameInner)
294
+ mock.append(frame)
295
+
296
+ const cap = el(doc, 'div', 'bp-choice__mock-cap')
297
+ const h4 = el(doc, 'h4')
298
+ h4.textContent = opt.caption
299
+ cap.append(h4)
300
+ const chosen = el(doc, 'span', 'bp-choice__tag bp-choice__tag--ink bp-choice__mock-pick bp-choice__mock-chosen')
301
+ chosen.textContent = '✓ Chosen'
302
+ const rejected = el(doc, 'span', 'bp-choice__tag bp-choice__tag--out bp-choice__mock-pick bp-choice__mock-rejected')
303
+ rejected.textContent = 'Not chosen'
304
+ cap.append(chosen, rejected)
305
+ mock.append(cap)
306
+ gallery.append(mock)
307
+ }
308
+ form.append(gallery)
309
+
310
+ if (compareSummary && compareBody) {
311
+ const details = el(doc, 'details', 'bp-choice__compare')
312
+ const summary = el(doc, 'summary')
313
+ summary.textContent = compareSummary
314
+ details.append(summary)
315
+ const body = el(doc, 'div', 'bp-choice__compare-body')
316
+ body.append(compareBody.cloneNode(true))
317
+ details.append(body)
318
+ form.append(details)
319
+ }
320
+
321
+ form.append(
322
+ buildFooter(doc, hint || 'Select a mockup to track the decision', reconsider)
323
+ )
324
+ }
325
+
326
+ this.replaceChildren(form)
327
+ }
328
+ }
329
+
330
+ class BlueprintPreflightAElement extends HTMLElement {}
331
+
332
+ class BlueprintPreflightQElement extends HTMLElement {}
333
+
334
+ class BlueprintPreflightElement extends HTMLElement {
335
+ connectedCallback() {
336
+ if (this.dataset.bpRendered) return
337
+ this.dataset.bpRendered = '1'
338
+
339
+ const doc = this.ownerDocument
340
+ const title = this.getAttribute('title') ?? 'Before drafting'
341
+ const draftName = this.getAttribute('draft') ?? 'section'
342
+ const hint = this.getAttribute('hint') ?? 'Answer all questions to unlock · choices stay editable'
343
+ const reconsider = this.getAttribute('reconsider') ?? 'Reset answers'
344
+ const scopeId = nextId('bp-preflight')
345
+
346
+ const questions = [...this.querySelectorAll(':scope > bp-preflight-q')]
347
+ const form = el(doc, 'form')
348
+ const panel = el(doc, 'div', 'bp-preflight')
349
+ panel.dataset.bpPreflight = scopeId
350
+
351
+ const bar = el(doc, 'div', 'bp-preflight__bar')
352
+ bar.append(label(doc, title))
353
+ const count = el(doc, 'span', 'bp-preflight__count')
354
+ count.textContent = `${questions.length} required`
355
+ bar.append(count)
356
+ panel.append(bar)
357
+
358
+ const list = el(doc, 'div', 'bp-preflight__questions')
359
+ /** @type {string[]} */
360
+ const gateSelectors = []
361
+
362
+ for (const qNode of questions) {
363
+ const qName = qNode.getAttribute('name') ?? nextId('q')
364
+ const kind = (qNode.getAttribute('kind') || 'one').toLowerCase()
365
+ const prompt = qNode.getAttribute('prompt') ?? ''
366
+ const answers = [...qNode.querySelectorAll(':scope > bp-preflight-a')]
367
+
368
+ const q = el(doc, 'div', 'bp-preflight__q')
369
+ const promptEl = el(doc, 'p', 'bp-preflight__prompt')
370
+ promptEl.append(doc.createTextNode(prompt))
371
+ const kindEl = el(doc, 'span', 'bp-preflight__kind')
372
+ kindEl.textContent = kind === 'many' ? '· choose any' : '· choose one'
373
+ promptEl.append(kindEl)
374
+ const resolved = el(doc, 'span', 'bp-choice__tag bp-choice__tag--ink bp-preflight__resolved')
375
+ resolved.textContent = '✓ Resolved'
376
+ promptEl.append(resolved)
377
+ q.append(promptEl)
378
+
379
+ const chips = el(doc, 'div', 'bp-preflight__chips')
380
+ for (const aNode of answers) {
381
+ const chip = el(doc, 'label', 'bp-preflight__chip')
382
+ const input = doc.createElement('input')
383
+ input.type = kind === 'many' ? 'checkbox' : 'radio'
384
+ input.name = qName
385
+ input.value = aNode.getAttribute('value') ?? aNode.textContent?.trim() ?? ''
386
+ chip.append(input, doc.createTextNode(aNode.textContent?.trim() ?? ''))
387
+ chips.append(chip)
388
+ }
389
+ q.append(chips)
390
+
391
+ const rationale = extractRationale(qNode)
392
+ if (rationale) q.append(rationale)
393
+ list.append(q)
394
+ gateSelectors.push(`[name="${qName}"]:checked`)
395
+ }
396
+ panel.append(list)
397
+
398
+ const gateWrap = el(doc, 'div')
399
+ gateWrap.style.padding = 'var(--bp-space-3)'
400
+ const gate = el(doc, 'div', 'bp-preflight__gate')
401
+ const locked = el(doc, 'div', 'bp-preflight__gate-locked')
402
+ const lockedTag = el(doc, 'span', 'bp-choice__tag')
403
+ lockedTag.textContent = `⌧ Section fenced — resolve the ${questions.length} decisions above to draft`
404
+ locked.append(lockedTag)
405
+ const ready = el(doc, 'div', 'bp-preflight__gate-ready')
406
+ const readyP = el(doc, 'p')
407
+ readyP.style.margin = '0 0 var(--bp-space-1)'
408
+ readyP.textContent = 'All decisions resolved.'
409
+ const draftBtn = el(doc, 'span', 'bp-preflight__draft')
410
+ draftBtn.textContent = `Draft “${draftName}” →`
411
+ ready.append(readyP, draftBtn)
412
+ gate.append(locked, ready)
413
+ gateWrap.append(gate)
414
+ panel.append(gateWrap)
415
+
416
+ const style = el(doc, 'style')
417
+ const gateRule = gateSelectors.map((sel) => `:has(${sel})`).join('')
418
+ style.textContent =
419
+ `[data-bp-preflight="${scopeId}"]${gateRule}{--bp-preflight-ready:1}` +
420
+ `[data-bp-preflight="${scopeId}"]${gateRule}{border-color:var(--bp-ink-line)}` +
421
+ `[data-bp-preflight="${scopeId}"]${gateRule} .bp-preflight__gate{background-image:none;border-style:solid}` +
422
+ `[data-bp-preflight="${scopeId}"]${gateRule} .bp-preflight__gate-locked{display:none}` +
423
+ `[data-bp-preflight="${scopeId}"]${gateRule} .bp-preflight__gate-ready{display:block}`
424
+ panel.prepend(style)
425
+
426
+ const footer = el(doc, 'div', 'bp-preflight__footer')
427
+ const hintEl = el(doc, 'span', 'bp-choice__hint')
428
+ hintEl.textContent = hint
429
+ const reset = el(doc, 'button', 'bp-choice__reset')
430
+ reset.type = 'reset'
431
+ reset.textContent = reconsider
432
+ footer.append(hintEl, reset)
433
+
434
+ form.append(panel, footer)
435
+ this.replaceChildren(form)
436
+ }
437
+ }
438
+
439
+ class BlueprintChoiceRecordRowElement extends HTMLElement {
440
+ connectedCallback() {
441
+ if (this.dataset.bpRendered) return
442
+ this.dataset.bpRendered = '1'
443
+
444
+ const doc = this.ownerDocument
445
+ const rowLabel = this.getAttribute('label') ?? ''
446
+ const value = this.getAttribute('value') ?? ''
447
+ const alts = this.getAttribute('alts') ?? ''
448
+
449
+ const dl = el(doc, 'dl', 'bp-choice-record__row')
450
+ const dt = el(doc, 'dt')
451
+ dt.textContent = rowLabel
452
+ const dd = el(doc, 'dd')
453
+ dd.textContent = value
454
+ dl.append(dt, dd)
455
+ if (alts) {
456
+ const tag = el(doc, 'span', 'bp-choice__tag bp-choice__tag--out')
457
+ tag.textContent = alts
458
+ dl.append(tag)
459
+ }
460
+
461
+ const considered = this.querySelector(':scope > [slot="considered"]')
462
+ const nodes = [dl]
463
+ if (considered) {
464
+ const details = el(doc, 'details', 'bp-choice__compare')
465
+ details.style.marginTop = 'var(--bp-space-2)'
466
+ const summary = el(doc, 'summary')
467
+ summary.textContent = '+ Show the options considered'
468
+ details.append(summary)
469
+ const body = el(doc, 'div', 'bp-choice__compare-body')
470
+ body.append(considered.cloneNode(true))
471
+ details.append(body)
472
+ nodes.push(details)
473
+ }
474
+ this.replaceChildren(...nodes)
475
+ }
476
+ }
477
+
478
+ class BlueprintChoiceRecordElement extends HTMLElement {
479
+ connectedCallback() {
480
+ if (this.dataset.bpRendered) return
481
+ this.dataset.bpRendered = '1'
482
+ const wrap = this.ownerDocument.createElement('div')
483
+ wrap.className = 'bp-choice-record'
484
+ wrap.append(...this.childNodes)
485
+ this.replaceChildren(wrap)
486
+ }
487
+ }
488
+
489
+ /** @param {string} name @param {typeof HTMLElement} ctor */
490
+ function define(name, ctor) {
491
+ if (typeof customElements !== 'undefined' && !customElements.get(name)) {
492
+ customElements.define(name, ctor)
493
+ }
494
+ }
495
+
496
+ export function registerBlueprintChoiceElements() {
497
+ define('bp-rationale', BlueprintRationaleElement)
498
+ define('bp-choice-option', BlueprintChoiceOptionElement)
499
+ define('bp-choice', BlueprintChoiceElement)
500
+ define('bp-preflight-a', BlueprintPreflightAElement)
501
+ define('bp-preflight-q', BlueprintPreflightQElement)
502
+ define('bp-preflight', BlueprintPreflightElement)
503
+ define('bp-choice-record-row', BlueprintChoiceRecordRowElement)
504
+ define('bp-choice-record', BlueprintChoiceRecordElement)
505
+ }
506
+
507
+ registerBlueprintChoiceElements()
@@ -1472,7 +1472,8 @@
1472
1472
  }
1473
1473
  :where(.bp-opt--rec) {
1474
1474
  background: var(--bp-fill-amb);
1475
- box-shadow: inset 3px 0 0 var(--bp-ink);
1475
+ outline: 2px solid var(--bp-ink);
1476
+ outline-offset: -2px;
1476
1477
  }
1477
1478
  :where(.bp-verdict) {
1478
1479
  display: inline-block;
@@ -1548,6 +1549,523 @@
1548
1549
  background-image: var(--bp-hatch);
1549
1550
  }
1550
1551
 
1552
+ /* ---- Interactive choice family ----------------------------------
1553
+ CSS-only deliberation primitives (radio / checkbox + :has() +
1554
+ native form reset). Author with <bp-choice>, <bp-preflight>, and
1555
+ related elements; blueprint-choices.js expands them to this markup.
1556
+ Selected items always get a full ink border — never a left-bar only. */
1557
+
1558
+ :where(bp-choice),
1559
+ :where(bp-preflight),
1560
+ :where(bp-choice-record) {
1561
+ display: block;
1562
+ }
1563
+
1564
+ :where(.bp-choice) {
1565
+ margin: var(--bp-space-4) 0;
1566
+ }
1567
+
1568
+ /* Verdict banner — hidden until a choice is committed. */
1569
+ :where(.bp-choice__verdict) {
1570
+ display: none;
1571
+ align-items: center;
1572
+ gap: var(--bp-space-2);
1573
+ padding: var(--bp-space-2) var(--bp-space-3);
1574
+ background: var(--bp-ink);
1575
+ color: var(--bp-paper);
1576
+ border-radius: var(--bp-radius-4);
1577
+ margin-bottom: var(--bp-space-3);
1578
+ }
1579
+ :where(.bp-choice:has(.bp-choice__commit:checked)) .bp-choice__verdict,
1580
+ :where(.bp-choice:has(.bp-choice__pick:checked)) .bp-choice__verdict {
1581
+ display: flex;
1582
+ }
1583
+ :where(.bp-choice__verdict) > :where(.bp-label) {
1584
+ margin: 0;
1585
+ color: var(--bp-paper);
1586
+ font-size: var(--bp-label-lg);
1587
+ letter-spacing: var(--bp-label-lg-ls);
1588
+ }
1589
+ :where(.bp-choice__verdict-pick) {
1590
+ font-family: var(--bp-sans);
1591
+ font-weight: var(--bp-weight-strong);
1592
+ font-size: var(--bp-text-h4);
1593
+ margin: 0;
1594
+ }
1595
+ :where(.bp-choice__verdict-pick[data-for]) {
1596
+ display: none;
1597
+ }
1598
+ :where(.bp-choice__verdict-meta) {
1599
+ margin: 0 0 0 auto;
1600
+ font-family: var(--bp-mono);
1601
+ font-size: var(--bp-label-md);
1602
+ letter-spacing: var(--bp-label-md-ls);
1603
+ text-transform: uppercase;
1604
+ color: color-mix(in oklch, var(--bp-paper) 72%, transparent);
1605
+ }
1606
+
1607
+ :where(.bp-choice-rationale) {
1608
+ margin-top: var(--bp-space-2);
1609
+ font-size: var(--bp-text-small);
1610
+ line-height: var(--bp-lh-small);
1611
+ color: var(--bp-text-secondary);
1612
+ }
1613
+ :where(.bp-choice-rationale) > :where(* + *) {
1614
+ margin-top: var(--bp-space-1);
1615
+ }
1616
+
1617
+ :where(.bp-choice__actions) {
1618
+ display: flex;
1619
+ align-items: center;
1620
+ gap: var(--bp-space-3);
1621
+ margin-top: var(--bp-space-3);
1622
+ }
1623
+ :where(.bp-choice__hint) {
1624
+ font-family: var(--bp-mono);
1625
+ font-size: var(--bp-label-md);
1626
+ letter-spacing: var(--bp-label-md-ls);
1627
+ text-transform: uppercase;
1628
+ color: var(--bp-text-secondary);
1629
+ }
1630
+ :where(.bp-choice__reset) {
1631
+ appearance: none;
1632
+ background: none;
1633
+ border: 0;
1634
+ padding: 0;
1635
+ cursor: pointer;
1636
+ font-family: var(--bp-mono);
1637
+ font-size: var(--bp-label-md);
1638
+ letter-spacing: var(--bp-label-md-ls);
1639
+ text-transform: uppercase;
1640
+ color: var(--bp-text-secondary);
1641
+ text-decoration: underline dotted;
1642
+ text-underline-offset: 3px;
1643
+ }
1644
+ :where(.bp-choice__reset:hover) {
1645
+ color: var(--bp-ink);
1646
+ }
1647
+
1648
+ :where(.bp-choice__tag) {
1649
+ display: inline-flex;
1650
+ align-items: center;
1651
+ gap: 5px;
1652
+ font-family: var(--bp-mono);
1653
+ font-size: var(--bp-label-sm);
1654
+ letter-spacing: var(--bp-label-sm-ls);
1655
+ text-transform: uppercase;
1656
+ color: var(--bp-text-secondary);
1657
+ }
1658
+ :where(.bp-choice__tag--ink) {
1659
+ color: var(--bp-paper);
1660
+ background: var(--bp-ink);
1661
+ padding: 2px 8px;
1662
+ }
1663
+ :where(.bp-choice__tag--out) {
1664
+ color: var(--bp-ink);
1665
+ border: 1px solid var(--bp-ink-faint);
1666
+ padding: 1px 7px;
1667
+ }
1668
+
1669
+ /* ---- layout: tabs (preview drafts, then adopt) ------------------- */
1670
+ :where(.bp-choice--tabs) :where(.bp-choice__seg) {
1671
+ display: flex;
1672
+ flex-wrap: wrap;
1673
+ gap: 0;
1674
+ border: 1px solid var(--bp-ink-line);
1675
+ border-radius: var(--bp-radius-4);
1676
+ overflow: hidden;
1677
+ margin: 0 0 var(--bp-space-3);
1678
+ width: fit-content;
1679
+ }
1680
+ :where(.bp-choice__seg-opt) {
1681
+ position: relative;
1682
+ cursor: pointer;
1683
+ font-family: var(--bp-mono);
1684
+ font-size: var(--bp-label-md);
1685
+ letter-spacing: var(--bp-label-md-ls);
1686
+ text-transform: uppercase;
1687
+ color: var(--bp-text-secondary);
1688
+ padding: 6px 14px;
1689
+ border-right: 1px solid var(--bp-ink-line);
1690
+ }
1691
+ :where(.bp-choice__seg-opt:last-child) {
1692
+ border-right: 0;
1693
+ }
1694
+ :where(.bp-choice__seg-opt) :where(input) {
1695
+ position: absolute;
1696
+ opacity: 0;
1697
+ pointer-events: none;
1698
+ }
1699
+ :where(.bp-choice__seg-opt:has(input:checked)) {
1700
+ background: var(--bp-ink);
1701
+ color: var(--bp-paper);
1702
+ }
1703
+ :where(.bp-choice--tabs) :where(.bp-choice__panel) {
1704
+ display: none;
1705
+ }
1706
+ :where(.bp-choice__panel > h3:first-child) {
1707
+ margin-top: 0;
1708
+ }
1709
+ :where(.bp-choice__adopt) {
1710
+ display: inline-flex;
1711
+ align-items: center;
1712
+ gap: 8px;
1713
+ cursor: pointer;
1714
+ border: 1px solid var(--bp-ink);
1715
+ border-radius: var(--bp-radius-4);
1716
+ padding: 6px 14px;
1717
+ font-family: var(--bp-mono);
1718
+ font-size: var(--bp-label-md);
1719
+ letter-spacing: var(--bp-label-md-ls);
1720
+ text-transform: uppercase;
1721
+ color: var(--bp-ink);
1722
+ background: var(--bp-paper);
1723
+ }
1724
+ :where(.bp-choice__adopt:hover) {
1725
+ background: var(--bp-fill-hi);
1726
+ }
1727
+ :where(.bp-choice__adopt) :where(input) {
1728
+ position: absolute;
1729
+ opacity: 0;
1730
+ pointer-events: none;
1731
+ }
1732
+ :where(.bp-choice__panel:has(.bp-choice__commit:checked)) {
1733
+ border: 1px solid var(--bp-ink);
1734
+ background: var(--bp-fill-amb);
1735
+ padding: var(--bp-space-3);
1736
+ margin-left: calc(-1 * var(--bp-space-3));
1737
+ margin-right: calc(-1 * var(--bp-space-3));
1738
+ border-radius: var(--bp-radius-4);
1739
+ }
1740
+ :where(.bp-choice__panel:has(.bp-choice__commit:checked)) :where(.bp-choice__adopt) {
1741
+ background: var(--bp-ink);
1742
+ color: var(--bp-paper);
1743
+ }
1744
+
1745
+ /* ---- layout: stack (all cards visible) --------------------------- */
1746
+ :where(.bp-choice--stack) :where(.bp-choice__stack) {
1747
+ display: grid;
1748
+ gap: var(--bp-space-3);
1749
+ }
1750
+ :where(.bp-choice__card) {
1751
+ display: block;
1752
+ position: relative;
1753
+ border: 1px solid var(--bp-edge);
1754
+ border-radius: var(--bp-radius-4);
1755
+ padding: var(--bp-space-3);
1756
+ cursor: pointer;
1757
+ background: var(--bp-paper);
1758
+ }
1759
+ :where(.bp-choice__card:hover) {
1760
+ border-color: var(--bp-ink-line);
1761
+ }
1762
+ :where(.bp-choice__card) > :where(input.bp-choice__pick) {
1763
+ position: absolute;
1764
+ top: var(--bp-space-3);
1765
+ right: var(--bp-space-3);
1766
+ accent-color: var(--bp-ink);
1767
+ width: 16px;
1768
+ height: 16px;
1769
+ }
1770
+ :where(.bp-choice__card-head) {
1771
+ display: flex;
1772
+ align-items: baseline;
1773
+ gap: var(--bp-space-2);
1774
+ margin-bottom: var(--bp-space-1);
1775
+ }
1776
+ :where(.bp-choice__card) :where(h4) {
1777
+ margin: 0;
1778
+ padding-right: var(--bp-space-4);
1779
+ }
1780
+ :where(.bp-choice__card-chosen),
1781
+ :where(.bp-choice__card-rejected) {
1782
+ display: none;
1783
+ }
1784
+ :where(.bp-choice__card:has(.bp-choice__pick:checked)) {
1785
+ border: 2px solid var(--bp-ink);
1786
+ background: var(--bp-fill-amb);
1787
+ }
1788
+ :where(.bp-choice__card:has(.bp-choice__pick:checked)) :where(.bp-choice__card-chosen) {
1789
+ display: inline-flex;
1790
+ }
1791
+ :where(.bp-choice--stack:has(.bp-choice__pick:checked)) :where(.bp-choice__card:not(:has(.bp-choice__pick:checked))) {
1792
+ opacity: 0.62;
1793
+ background-image: var(--bp-hatch);
1794
+ }
1795
+ :where(.bp-choice--stack:has(.bp-choice__pick:checked)) :where(.bp-choice__card:not(:has(.bp-choice__pick:checked))) :where(.bp-choice__card-rejected) {
1796
+ display: inline-flex;
1797
+ }
1798
+
1799
+ /* ---- layout: gallery (mockup pick) ------------------------------- */
1800
+ :where(.bp-choice--gallery) :where(.bp-choice__gallery) {
1801
+ display: grid;
1802
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1803
+ gap: var(--bp-space-3);
1804
+ }
1805
+ :where(.bp-choice__mock) {
1806
+ display: block;
1807
+ position: relative;
1808
+ cursor: pointer;
1809
+ border: 1px solid var(--bp-edge);
1810
+ border-radius: var(--bp-radius-4);
1811
+ overflow: hidden;
1812
+ background: var(--bp-paper);
1813
+ }
1814
+ :where(.bp-choice__mock:hover) {
1815
+ border-color: var(--bp-ink-line);
1816
+ }
1817
+ :where(.bp-choice__mock) > :where(input.bp-choice__pick) {
1818
+ position: absolute;
1819
+ opacity: 0;
1820
+ pointer-events: none;
1821
+ }
1822
+ :where(.bp-choice__mock-frame) {
1823
+ aspect-ratio: 4 / 3;
1824
+ border-bottom: 1px solid var(--bp-edge);
1825
+ background: var(--bp-bg);
1826
+ padding: 10px;
1827
+ display: grid;
1828
+ gap: 6px;
1829
+ }
1830
+ :where(.bp-choice__mock-cap) {
1831
+ display: flex;
1832
+ align-items: baseline;
1833
+ gap: var(--bp-space-2);
1834
+ padding: var(--bp-space-2) var(--bp-space-3);
1835
+ }
1836
+ :where(.bp-choice__mock-cap) :where(h4) {
1837
+ margin: 0;
1838
+ font-size: var(--bp-text-body);
1839
+ }
1840
+ :where(.bp-choice__mock-pick) {
1841
+ margin-left: auto;
1842
+ }
1843
+ :where(.bp-choice__mock-chosen),
1844
+ :where(.bp-choice__mock-rejected) {
1845
+ display: none;
1846
+ }
1847
+ :where(.bp-choice__mock:has(.bp-choice__pick:checked)) {
1848
+ border: 2px solid var(--bp-ink);
1849
+ }
1850
+ :where(.bp-choice__mock:has(.bp-choice__pick:checked)) :where(.bp-choice__mock-chosen) {
1851
+ display: inline-flex;
1852
+ }
1853
+ :where(.bp-choice--gallery:has(.bp-choice__pick:checked)) :where(.bp-choice__mock:not(:has(.bp-choice__pick:checked))) {
1854
+ opacity: 0.5;
1855
+ }
1856
+ :where(.bp-choice--gallery:has(.bp-choice__pick:checked)) :where(.bp-choice__mock:not(:has(.bp-choice__pick:checked))) :where(.bp-choice__mock-frame) {
1857
+ background-image: var(--bp-hatch);
1858
+ }
1859
+ :where(.bp-choice--gallery:has(.bp-choice__pick:checked)) :where(.bp-choice__mock:not(:has(.bp-choice__pick:checked))) :where(.bp-choice__mock-rejected) {
1860
+ display: inline-flex;
1861
+ }
1862
+
1863
+ :where(.bp-choice__compare) {
1864
+ margin-top: var(--bp-space-3);
1865
+ border: 1px solid var(--bp-edge);
1866
+ border-radius: var(--bp-radius-4);
1867
+ }
1868
+ :where(.bp-choice__compare > summary) {
1869
+ cursor: pointer;
1870
+ list-style: none;
1871
+ padding: var(--bp-space-2) var(--bp-space-3);
1872
+ font-family: var(--bp-mono);
1873
+ font-size: var(--bp-label-md);
1874
+ letter-spacing: var(--bp-label-md-ls);
1875
+ text-transform: uppercase;
1876
+ color: var(--bp-text-secondary);
1877
+ }
1878
+ :where(.bp-choice__compare > summary)::-webkit-details-marker {
1879
+ display: none;
1880
+ }
1881
+ :where(.bp-choice__compare-body) {
1882
+ padding: var(--bp-space-3);
1883
+ border-top: 1px solid var(--bp-edge);
1884
+ }
1885
+
1886
+ /* ---- Pre-flight (decisions before drafting) ---------------------- */
1887
+ :where(.bp-preflight) {
1888
+ border: 1px solid var(--bp-ink-line);
1889
+ border-radius: var(--bp-radius-6);
1890
+ overflow: hidden;
1891
+ margin: var(--bp-space-4) 0;
1892
+ }
1893
+ :where(.bp-preflight__bar) {
1894
+ display: flex;
1895
+ align-items: center;
1896
+ gap: var(--bp-space-2);
1897
+ padding: var(--bp-space-2) var(--bp-space-3);
1898
+ background: var(--bp-ink);
1899
+ color: var(--bp-paper);
1900
+ }
1901
+ :where(.bp-preflight__bar) > :where(.bp-label) {
1902
+ margin: 0;
1903
+ color: var(--bp-paper);
1904
+ }
1905
+ :where(.bp-preflight__count) {
1906
+ margin-left: auto;
1907
+ font-family: var(--bp-mono);
1908
+ font-size: var(--bp-label-md);
1909
+ letter-spacing: var(--bp-label-md-ls);
1910
+ text-transform: uppercase;
1911
+ color: color-mix(in oklch, var(--bp-paper) 78%, transparent);
1912
+ }
1913
+ :where(.bp-preflight__questions) {
1914
+ counter-reset: bp-preflight-q;
1915
+ }
1916
+ :where(.bp-preflight__q) {
1917
+ counter-increment: bp-preflight-q;
1918
+ padding: var(--bp-space-3) var(--bp-space-3) var(--bp-space-3) calc(var(--bp-space-6) + 4px);
1919
+ position: relative;
1920
+ border-bottom: 1px solid var(--bp-edge);
1921
+ }
1922
+ :where(.bp-preflight__q::before) {
1923
+ content: counter(bp-preflight-q, decimal-leading-zero);
1924
+ position: absolute;
1925
+ left: var(--bp-space-3);
1926
+ top: var(--bp-space-3);
1927
+ font-family: var(--bp-mono);
1928
+ font-size: var(--bp-label-lg);
1929
+ letter-spacing: 0.08em;
1930
+ color: var(--bp-ink-soft);
1931
+ }
1932
+ :where(.bp-preflight__q:has(input:checked)::before) {
1933
+ color: var(--bp-ink);
1934
+ }
1935
+ :where(.bp-preflight__prompt) {
1936
+ font-family: var(--bp-sans);
1937
+ font-weight: var(--bp-weight-strong);
1938
+ font-size: var(--bp-text-h4);
1939
+ margin: 0 0 var(--bp-space-1);
1940
+ display: flex;
1941
+ align-items: baseline;
1942
+ gap: var(--bp-space-2);
1943
+ }
1944
+ :where(.bp-preflight__kind) {
1945
+ font-family: var(--bp-mono);
1946
+ font-size: var(--bp-label-sm);
1947
+ letter-spacing: var(--bp-label-sm-ls);
1948
+ text-transform: uppercase;
1949
+ color: var(--bp-text-secondary);
1950
+ font-weight: var(--bp-weight-body);
1951
+ }
1952
+ :where(.bp-preflight__resolved) {
1953
+ display: none;
1954
+ margin-left: auto;
1955
+ }
1956
+ :where(.bp-preflight__q:has(input:checked)) :where(.bp-preflight__resolved) {
1957
+ display: inline-flex;
1958
+ }
1959
+ :where(.bp-preflight__chips) {
1960
+ display: flex;
1961
+ flex-wrap: wrap;
1962
+ gap: var(--bp-space-2);
1963
+ margin-top: var(--bp-space-2);
1964
+ }
1965
+ :where(.bp-preflight__chip) {
1966
+ display: inline-flex;
1967
+ align-items: center;
1968
+ gap: 7px;
1969
+ cursor: pointer;
1970
+ border: 1px solid var(--bp-ink-faint);
1971
+ border-radius: var(--bp-radius-pill);
1972
+ padding: 5px 12px;
1973
+ font-size: var(--bp-text-small);
1974
+ background: var(--bp-paper);
1975
+ }
1976
+ :where(.bp-preflight__chip:hover) {
1977
+ border-color: var(--bp-ink-line);
1978
+ }
1979
+ :where(.bp-preflight__chip) :where(input) {
1980
+ accent-color: var(--bp-ink);
1981
+ margin: 0;
1982
+ }
1983
+ :where(.bp-preflight__chip:has(input:checked)) {
1984
+ background: var(--bp-ink);
1985
+ color: var(--bp-paper);
1986
+ border-color: var(--bp-ink);
1987
+ }
1988
+ :where(.bp-preflight__gate) {
1989
+ margin: var(--bp-space-3);
1990
+ border: 1px dashed var(--bp-ink-line);
1991
+ border-radius: var(--bp-radius-6);
1992
+ padding: var(--bp-space-4);
1993
+ text-align: center;
1994
+ background-image: var(--bp-hatch);
1995
+ color: var(--bp-text-secondary);
1996
+ }
1997
+ :where(.bp-preflight__gate-ready) {
1998
+ display: none;
1999
+ }
2000
+ :where(.bp-preflight--ready) :where(.bp-preflight__gate) {
2001
+ background-image: none;
2002
+ border-style: solid;
2003
+ border-color: var(--bp-ink-line);
2004
+ }
2005
+ :where(.bp-preflight--ready) :where(.bp-preflight__gate-locked) {
2006
+ display: none;
2007
+ }
2008
+ :where(.bp-preflight--ready) :where(.bp-preflight__gate-ready) {
2009
+ display: block;
2010
+ }
2011
+ :where(.bp-preflight__draft) {
2012
+ display: inline-flex;
2013
+ align-items: center;
2014
+ gap: 8px;
2015
+ border: 1px solid var(--bp-ink);
2016
+ border-radius: var(--bp-radius-4);
2017
+ padding: 8px 16px;
2018
+ font-family: var(--bp-mono);
2019
+ font-size: var(--bp-label-md);
2020
+ letter-spacing: var(--bp-label-md-ls);
2021
+ text-transform: uppercase;
2022
+ background: var(--bp-ink);
2023
+ color: var(--bp-paper);
2024
+ cursor: pointer;
2025
+ margin-top: var(--bp-space-2);
2026
+ }
2027
+ :where(.bp-preflight__footer) {
2028
+ display: flex;
2029
+ align-items: center;
2030
+ gap: var(--bp-space-3);
2031
+ margin-top: var(--bp-space-3);
2032
+ }
2033
+
2034
+ /* ---- Choice record (static decided archive) ---------------------- */
2035
+ :where(.bp-choice-record) {
2036
+ margin: var(--bp-space-4) 0;
2037
+ }
2038
+ :where(.bp-choice-record__row) {
2039
+ border: 1px solid var(--bp-edge);
2040
+ border-radius: var(--bp-radius-4);
2041
+ padding: var(--bp-space-2) var(--bp-space-3);
2042
+ display: grid;
2043
+ grid-template-columns: minmax(8rem, 14rem) 1fr auto;
2044
+ gap: var(--bp-space-2) var(--bp-space-3);
2045
+ align-items: baseline;
2046
+ }
2047
+ :where(.bp-choice-record__row + .bp-choice-record__row) {
2048
+ margin-top: var(--bp-space-2);
2049
+ }
2050
+ :where(.bp-choice-record__row) :where(dt) {
2051
+ font-family: var(--bp-mono);
2052
+ font-size: var(--bp-label-md);
2053
+ letter-spacing: var(--bp-label-md-ls);
2054
+ text-transform: uppercase;
2055
+ color: var(--bp-text-secondary);
2056
+ margin: 0;
2057
+ }
2058
+ :where(.bp-choice-record__row) :where(dd) {
2059
+ margin: 0;
2060
+ font-weight: var(--bp-weight-medium);
2061
+ }
2062
+ @media (max-width: 700px) {
2063
+ :where(.bp-choice-record__row) {
2064
+ grid-template-columns: 1fr;
2065
+ gap: 2px;
2066
+ }
2067
+ }
2068
+
1551
2069
  /* ---- Data-table wrap: let wide tables break past the prose measure */
1552
2070
  :where(.bp-table-wrap) {
1553
2071
  max-width: 100%;
package/dist/blueprint.js CHANGED
@@ -1,4 +1,8 @@
1
1
  // Optional behavior for TOC current state and reading progress.
2
+ import { registerBlueprintChoiceElements } from './blueprint-choices.js'
3
+
4
+ registerBlueprintChoiceElements()
5
+
2
6
  export function initializeBlueprintRuntime(doc = document, win = window) {
3
7
  const root = doc.documentElement
4
8
  if (root.dataset.blueprintRuntime === 'ready') return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obvi/blueprint",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "A classless-first CSS design system for beautiful technical blueprint documents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,6 +23,7 @@
23
23
  "exports": {
24
24
  ".": "./dist/blueprint.css",
25
25
  "./blueprint.js": "./dist/blueprint.js",
26
+ "./blueprint-choices.js": "./dist/blueprint-choices.js",
26
27
  "./blueprint.css": "./dist/blueprint.css",
27
28
  "./styles.css": "./dist/blueprint.css",
28
29
  "./code-highlighting.css": "./dist/code-highlighting/blueprint-code.css",