@obvi/blueprint 1.1.2 → 1.2.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.
@@ -0,0 +1,2932 @@
1
+ // blueprint-choices.js
2
+ var choiceSeq = 0;
3
+ function nextId(prefix) {
4
+ choiceSeq += 1;
5
+ return `${prefix}-${choiceSeq}`;
6
+ }
7
+ function extractRationale(root) {
8
+ const node = root.querySelector(":scope > bp-rationale");
9
+ if (!node) return null;
10
+ const wrap = node.ownerDocument.createElement("div");
11
+ wrap.className = "bp-choice-rationale";
12
+ wrap.append(...node.childNodes);
13
+ return wrap;
14
+ }
15
+ function bodyWithoutRationale(root) {
16
+ const frag = root.ownerDocument.createDocumentFragment();
17
+ for (const child of root.childNodes) {
18
+ if (child instanceof Element && child.tagName === "BP-RATIONALE") continue;
19
+ if (child instanceof Element && child.getAttribute("slot") === "frame") continue;
20
+ frag.append(child.cloneNode(true));
21
+ }
22
+ return frag;
23
+ }
24
+ function frameContent(root) {
25
+ const slotted = root.querySelector(':scope > [slot="frame"]');
26
+ if (slotted) {
27
+ const frame = slotted.cloneNode(true);
28
+ frame.removeAttribute("slot");
29
+ return frame;
30
+ }
31
+ return null;
32
+ }
33
+ function el(doc, tag, className) {
34
+ const node = doc.createElement(tag);
35
+ if (className) node.className = className;
36
+ return node;
37
+ }
38
+ function label(doc, text, className) {
39
+ const node = el(doc, "span", className ?? "bp-label");
40
+ node.textContent = text;
41
+ return node;
42
+ }
43
+ function buildVerdict(doc, kicker, options, meta = "You · just now") {
44
+ const verdict = el(doc, "div", "bp-choice__verdict");
45
+ verdict.append(label(doc, kicker));
46
+ for (const opt of options) {
47
+ const pick = el(doc, "p", "bp-choice__verdict-pick");
48
+ pick.dataset.for = opt.value;
49
+ pick.textContent = opt.title;
50
+ verdict.append(pick);
51
+ }
52
+ if (meta) {
53
+ const metaEl = el(doc, "span", "bp-choice__verdict-meta");
54
+ metaEl.textContent = meta;
55
+ verdict.append(metaEl);
56
+ }
57
+ return verdict;
58
+ }
59
+ function buildFooter(doc, hint, reconsider) {
60
+ const actions = el(doc, "div", "bp-choice__actions");
61
+ if (hint) {
62
+ const hintEl = el(doc, "span", "bp-choice__hint");
63
+ hintEl.textContent = hint;
64
+ actions.append(hintEl);
65
+ }
66
+ const reset = el(doc, "button", "bp-choice__reset");
67
+ reset.type = "reset";
68
+ reset.textContent = reconsider;
69
+ actions.append(reset);
70
+ return actions;
71
+ }
72
+ function scopedTabRules(scope, viewName, pickName, options) {
73
+ const lines = options.map(
74
+ (opt) => `${scope}:has([name="${viewName}"][value="${opt.value}"]:checked) .bp-choice__panel[data-value="${opt.value}"]{display:block}${scope}:has([name="${pickName}"][value="${opt.value}"]:checked) .bp-choice__verdict-pick[data-for="${opt.value}"]{display:block}`
75
+ );
76
+ return lines.join("\n");
77
+ }
78
+ function readOptions(host) {
79
+ return [...host.querySelectorAll(":scope > bp-choice-option")].map((node) => ({
80
+ el: node,
81
+ value: node.getAttribute("value") ?? "",
82
+ tab: node.getAttribute("tab") ?? node.getAttribute("label") ?? node.getAttribute("value") ?? "",
83
+ title: node.getAttribute("title") ?? node.getAttribute("caption") ?? node.getAttribute("tab") ?? "",
84
+ optionLabel: node.getAttribute("label") ?? node.getAttribute("tab") ?? "",
85
+ caption: node.getAttribute("caption") ?? node.getAttribute("title") ?? ""
86
+ }));
87
+ }
88
+ function buildResolvedChoice(doc, { options, verdictKicker, resolvedValue }) {
89
+ const root = el(doc, "div", "bp-choice bp-choice--resolved");
90
+ const winner = options.find((o) => o.value === resolvedValue) ?? options[0];
91
+ const verdict = buildVerdict(
92
+ doc,
93
+ verdictKicker,
94
+ options.map((o) => ({ value: o.value, title: o.title })),
95
+ ""
96
+ );
97
+ const winnerPick = winner && verdict.querySelector(`.bp-choice__verdict-pick[data-for="${winner.value}"]`);
98
+ if (winnerPick) winnerPick.setAttribute("data-resolved", "");
99
+ root.append(verdict);
100
+ const stack = el(doc, "div", "bp-choice__stack");
101
+ for (const opt of options) {
102
+ const card = el(doc, "div", "bp-choice__card");
103
+ if (winner && opt.value === winner.value) card.setAttribute("data-resolved", "");
104
+ const head = el(doc, "div", "bp-choice__card-head");
105
+ if (opt.optionLabel) head.append(label(doc, opt.optionLabel));
106
+ const chosen = el(doc, "span", "bp-choice__tag bp-choice__tag--ink bp-choice__card-chosen");
107
+ chosen.textContent = `✓ ${verdictKicker}`;
108
+ const rejected = el(doc, "span", "bp-choice__tag bp-choice__tag--out bp-choice__card-rejected");
109
+ rejected.textContent = "Not chosen";
110
+ head.append(chosen, rejected);
111
+ card.append(head);
112
+ const h4 = el(doc, "h4");
113
+ h4.textContent = opt.title;
114
+ card.append(h4);
115
+ card.append(bodyWithoutRationale(opt.el));
116
+ const rationale = extractRationale(opt.el);
117
+ if (rationale) card.append(rationale);
118
+ stack.append(card);
119
+ }
120
+ root.append(stack);
121
+ return root;
122
+ }
123
+ var BlueprintRationaleElement = class extends HTMLElement {
124
+ connectedCallback() {
125
+ if (this.dataset.bpRendered) return;
126
+ this.dataset.bpRendered = "1";
127
+ this.classList.add("bp-choice-rationale");
128
+ }
129
+ };
130
+ var BlueprintChoiceOptionElement = class extends HTMLElement {
131
+ };
132
+ var BlueprintChoiceElement = class extends HTMLElement {
133
+ connectedCallback() {
134
+ if (this.dataset.bpRendered) return;
135
+ this.dataset.bpRendered = "1";
136
+ const doc = this.ownerDocument;
137
+ const layout = (this.getAttribute("layout") || "stack").toLowerCase();
138
+ const verdictKicker = this.getAttribute("verdict") ?? "Chosen";
139
+ const adoptLabel = this.getAttribute("adopt") ?? "Adopt this direction";
140
+ const hint = this.getAttribute("hint") ?? "";
141
+ const reconsider = this.getAttribute("reconsider") ?? "Reconsider";
142
+ const compareSummary = this.getAttribute("compare");
143
+ const compareBody = this.querySelector(':scope > [slot="compare"]');
144
+ const options = readOptions(this);
145
+ const resolvedValue = this.getAttribute("resolved");
146
+ if (resolvedValue !== null) {
147
+ this.replaceChildren(buildResolvedChoice(doc, { options, verdictKicker, resolvedValue }));
148
+ return;
149
+ }
150
+ const scopeId = nextId("bp-choice");
151
+ const viewName = `${scopeId}-view`;
152
+ const pickName = `${scopeId}-pick`;
153
+ const form = el(doc, "form", `bp-choice bp-choice--${layout}`);
154
+ form.dataset.bpChoice = scopeId;
155
+ const titleOpts = options.map((o) => ({ value: o.value, title: o.title }));
156
+ form.append(buildVerdict(doc, verdictKicker, titleOpts));
157
+ if (layout === "tabs") {
158
+ const seg = el(doc, "div", "bp-choice__seg");
159
+ seg.setAttribute("role", "tablist");
160
+ for (const [index, opt] of options.entries()) {
161
+ const segOpt = el(doc, "label", "bp-choice__seg-opt");
162
+ const input = doc.createElement("input");
163
+ input.type = "radio";
164
+ input.name = viewName;
165
+ input.value = opt.value;
166
+ input.className = "bp-choice__view";
167
+ if (index === 0) {
168
+ input.defaultChecked = true;
169
+ input.checked = true;
170
+ }
171
+ segOpt.append(input, doc.createTextNode(opt.tab));
172
+ seg.append(segOpt);
173
+ }
174
+ form.append(seg);
175
+ const panels = el(doc, "div", "bp-choice__panels");
176
+ for (const opt of options) {
177
+ const panel = el(doc, "div", "bp-choice__panel");
178
+ panel.dataset.value = opt.value;
179
+ const h3 = el(doc, "h3");
180
+ h3.textContent = opt.title;
181
+ panel.append(h3);
182
+ panel.append(bodyWithoutRationale(opt.el));
183
+ const rationale = extractRationale(opt.el);
184
+ if (rationale) panel.append(rationale);
185
+ const actions = el(doc, "div", "bp-choice__actions");
186
+ const adopt = el(doc, "label", "bp-choice__adopt");
187
+ const commit = doc.createElement("input");
188
+ commit.type = "radio";
189
+ commit.name = pickName;
190
+ commit.value = opt.value;
191
+ commit.className = "bp-choice__commit";
192
+ adopt.append(commit, doc.createTextNode(adoptLabel));
193
+ actions.append(adopt);
194
+ panel.append(actions);
195
+ panels.append(panel);
196
+ }
197
+ form.append(panels);
198
+ const style = el(doc, "style");
199
+ style.textContent = scopedTabRules(`[data-bp-choice="${scopeId}"]`, viewName, pickName, titleOpts);
200
+ form.prepend(style);
201
+ form.append(
202
+ buildFooter(
203
+ doc,
204
+ hint || "Switch tabs to preview · adopt to commit",
205
+ reconsider
206
+ )
207
+ );
208
+ }
209
+ if (layout === "stack") {
210
+ const stack = el(doc, "div", "bp-choice__stack");
211
+ for (const opt of options) {
212
+ const card = el(doc, "label", "bp-choice__card");
213
+ const input = doc.createElement("input");
214
+ input.type = "radio";
215
+ input.name = pickName;
216
+ input.value = opt.value;
217
+ input.className = "bp-choice__pick";
218
+ card.append(input);
219
+ const head = el(doc, "div", "bp-choice__card-head");
220
+ if (opt.optionLabel) head.append(label(doc, opt.optionLabel));
221
+ const chosen = el(doc, "span", "bp-choice__tag bp-choice__tag--ink bp-choice__card-chosen");
222
+ chosen.textContent = "✓ Chosen";
223
+ const rejected = el(doc, "span", "bp-choice__tag bp-choice__tag--out bp-choice__card-rejected");
224
+ rejected.textContent = "Not chosen";
225
+ head.append(chosen, rejected);
226
+ card.append(head);
227
+ const h4 = el(doc, "h4");
228
+ h4.textContent = opt.title;
229
+ card.append(h4);
230
+ card.append(bodyWithoutRationale(opt.el));
231
+ const rationale = extractRationale(opt.el);
232
+ if (rationale) card.append(rationale);
233
+ stack.append(card);
234
+ }
235
+ form.append(stack);
236
+ form.append(
237
+ buildFooter(
238
+ doc,
239
+ hint || "Select a card to commit · rejected drafts stay readable below",
240
+ reconsider
241
+ )
242
+ );
243
+ }
244
+ if (layout === "gallery") {
245
+ const gallery = el(doc, "div", "bp-choice__gallery");
246
+ for (const opt of options) {
247
+ const mock = el(doc, "label", "bp-choice__mock");
248
+ const input = doc.createElement("input");
249
+ input.type = "radio";
250
+ input.name = pickName;
251
+ input.value = opt.value;
252
+ input.className = "bp-choice__pick";
253
+ mock.append(input);
254
+ const frame = el(doc, "div", "bp-choice__mock-frame");
255
+ const frameInner = frameContent(opt.el);
256
+ if (frameInner) frame.append(frameInner);
257
+ mock.append(frame);
258
+ const cap = el(doc, "div", "bp-choice__mock-cap");
259
+ const h4 = el(doc, "h4");
260
+ h4.textContent = opt.caption;
261
+ cap.append(h4);
262
+ const chosen = el(doc, "span", "bp-choice__tag bp-choice__tag--ink bp-choice__mock-pick bp-choice__mock-chosen");
263
+ chosen.textContent = "✓ Chosen";
264
+ const rejected = el(doc, "span", "bp-choice__tag bp-choice__tag--out bp-choice__mock-pick bp-choice__mock-rejected");
265
+ rejected.textContent = "Not chosen";
266
+ cap.append(chosen, rejected);
267
+ mock.append(cap);
268
+ gallery.append(mock);
269
+ }
270
+ form.append(gallery);
271
+ if (compareSummary && compareBody) {
272
+ const details = el(doc, "details", "bp-choice__compare");
273
+ const summary = el(doc, "summary");
274
+ summary.textContent = compareSummary;
275
+ details.append(summary);
276
+ const body = el(doc, "div", "bp-choice__compare-body");
277
+ body.append(compareBody.cloneNode(true));
278
+ details.append(body);
279
+ form.append(details);
280
+ }
281
+ form.append(
282
+ buildFooter(doc, hint || "Select a mockup to track the decision", reconsider)
283
+ );
284
+ }
285
+ this.replaceChildren(form);
286
+ }
287
+ };
288
+ var BlueprintPreflightAElement = class extends HTMLElement {
289
+ };
290
+ var BlueprintPreflightQElement = class extends HTMLElement {
291
+ };
292
+ var BlueprintPreflightElement = class extends HTMLElement {
293
+ connectedCallback() {
294
+ if (this.dataset.bpRendered) return;
295
+ this.dataset.bpRendered = "1";
296
+ const doc = this.ownerDocument;
297
+ const title = this.getAttribute("title") ?? "Before drafting";
298
+ const draftName = this.getAttribute("draft") ?? "section";
299
+ const hint = this.getAttribute("hint") ?? "Answer all questions to unlock · choices stay editable";
300
+ const reconsider = this.getAttribute("reconsider") ?? "Reset answers";
301
+ const scopeId = nextId("bp-preflight");
302
+ const questions = [...this.querySelectorAll(":scope > bp-preflight-q")];
303
+ const form = el(doc, "form");
304
+ const panel = el(doc, "div", "bp-preflight");
305
+ panel.dataset.bpPreflight = scopeId;
306
+ const bar = el(doc, "div", "bp-preflight__bar");
307
+ bar.append(label(doc, title));
308
+ const count = el(doc, "span", "bp-preflight__count");
309
+ count.textContent = `${questions.length} required`;
310
+ bar.append(count);
311
+ panel.append(bar);
312
+ const list = el(doc, "div", "bp-preflight__questions");
313
+ const gateSelectors = [];
314
+ for (const qNode of questions) {
315
+ const qName = qNode.getAttribute("name") ?? nextId("q");
316
+ const kind = (qNode.getAttribute("kind") || "one").toLowerCase();
317
+ const prompt = qNode.getAttribute("prompt") ?? "";
318
+ const answers = [...qNode.querySelectorAll(":scope > bp-preflight-a")];
319
+ const q = el(doc, "div", "bp-preflight__q");
320
+ const promptEl = el(doc, "p", "bp-preflight__prompt");
321
+ promptEl.append(doc.createTextNode(prompt));
322
+ const kindEl = el(doc, "span", "bp-preflight__kind");
323
+ kindEl.textContent = kind === "many" ? "· choose any" : "· choose one";
324
+ promptEl.append(kindEl);
325
+ const resolved = el(doc, "span", "bp-choice__tag bp-choice__tag--ink bp-preflight__resolved");
326
+ resolved.textContent = "✓ Resolved";
327
+ promptEl.append(resolved);
328
+ q.append(promptEl);
329
+ const chips = el(doc, "div", "bp-preflight__chips");
330
+ for (const aNode of answers) {
331
+ const chip = el(doc, "label", "bp-preflight__chip");
332
+ const input = doc.createElement("input");
333
+ input.type = kind === "many" ? "checkbox" : "radio";
334
+ input.name = qName;
335
+ input.value = aNode.getAttribute("value") ?? aNode.textContent?.trim() ?? "";
336
+ chip.append(input, doc.createTextNode(aNode.textContent?.trim() ?? ""));
337
+ chips.append(chip);
338
+ }
339
+ q.append(chips);
340
+ const rationale = extractRationale(qNode);
341
+ if (rationale) q.append(rationale);
342
+ list.append(q);
343
+ gateSelectors.push(`[name="${qName}"]:checked`);
344
+ }
345
+ panel.append(list);
346
+ const gateWrap = el(doc, "div");
347
+ gateWrap.style.padding = "var(--bp-space-3)";
348
+ const gate = el(doc, "div", "bp-preflight__gate");
349
+ const locked = el(doc, "div", "bp-preflight__gate-locked");
350
+ const lockedTag = el(doc, "span", "bp-choice__tag");
351
+ lockedTag.textContent = `⌧ Section fenced — resolve the ${questions.length} decisions above to draft`;
352
+ locked.append(lockedTag);
353
+ const ready = el(doc, "div", "bp-preflight__gate-ready");
354
+ const readyP = el(doc, "p");
355
+ readyP.style.margin = "0 0 var(--bp-space-1)";
356
+ readyP.textContent = "All decisions resolved.";
357
+ const draftBtn = el(doc, "span", "bp-preflight__draft");
358
+ draftBtn.textContent = `Draft “${draftName}” →`;
359
+ ready.append(readyP, draftBtn);
360
+ gate.append(locked, ready);
361
+ gateWrap.append(gate);
362
+ panel.append(gateWrap);
363
+ const style = el(doc, "style");
364
+ const gateRule = gateSelectors.map((sel) => `:has(${sel})`).join("");
365
+ style.textContent = `[data-bp-preflight="${scopeId}"]${gateRule}{--bp-preflight-ready:1}[data-bp-preflight="${scopeId}"]${gateRule}{border-color:var(--bp-ink-line)}[data-bp-preflight="${scopeId}"]${gateRule} .bp-preflight__gate{background-image:none;border-style:solid}[data-bp-preflight="${scopeId}"]${gateRule} .bp-preflight__gate-locked{display:none}[data-bp-preflight="${scopeId}"]${gateRule} .bp-preflight__gate-ready{display:block}`;
366
+ panel.prepend(style);
367
+ const footer = el(doc, "div", "bp-preflight__footer");
368
+ const hintEl = el(doc, "span", "bp-choice__hint");
369
+ hintEl.textContent = hint;
370
+ const reset = el(doc, "button", "bp-choice__reset");
371
+ reset.type = "reset";
372
+ reset.textContent = reconsider;
373
+ footer.append(hintEl, reset);
374
+ form.append(panel, footer);
375
+ this.replaceChildren(form);
376
+ }
377
+ };
378
+ var BlueprintChoiceRecordRowElement = class extends HTMLElement {
379
+ connectedCallback() {
380
+ if (this.dataset.bpRendered) return;
381
+ this.dataset.bpRendered = "1";
382
+ const doc = this.ownerDocument;
383
+ const rowLabel = this.getAttribute("label") ?? "";
384
+ const value = this.getAttribute("value") ?? "";
385
+ const alts = this.getAttribute("alts") ?? "";
386
+ const dl = el(doc, "dl", "bp-choice-record__row");
387
+ const dt = el(doc, "dt");
388
+ dt.textContent = rowLabel;
389
+ const dd = el(doc, "dd");
390
+ dd.textContent = value;
391
+ dl.append(dt, dd);
392
+ if (alts) {
393
+ const tag = el(doc, "span", "bp-choice__tag bp-choice__tag--out");
394
+ tag.textContent = alts;
395
+ dl.append(tag);
396
+ }
397
+ const considered = this.querySelector(':scope > [slot="considered"]');
398
+ const nodes = [dl];
399
+ if (considered) {
400
+ const details = el(doc, "details", "bp-choice__compare");
401
+ const summary = el(doc, "summary");
402
+ summary.textContent = "Show the options considered";
403
+ details.append(summary);
404
+ const body = el(doc, "div", "bp-choice__compare-body");
405
+ body.append(considered.cloneNode(true));
406
+ details.append(body);
407
+ nodes.push(details);
408
+ }
409
+ this.replaceChildren(...nodes);
410
+ }
411
+ };
412
+ var BlueprintChoiceRecordElement = class extends HTMLElement {
413
+ connectedCallback() {
414
+ if (this.dataset.bpRendered) return;
415
+ this.dataset.bpRendered = "1";
416
+ const wrap = this.ownerDocument.createElement("div");
417
+ wrap.className = "bp-choice-record";
418
+ wrap.append(...this.childNodes);
419
+ this.replaceChildren(wrap);
420
+ }
421
+ };
422
+ function define(name, ctor) {
423
+ if (typeof customElements !== "undefined" && !customElements.get(name)) {
424
+ customElements.define(name, ctor);
425
+ }
426
+ }
427
+ function registerBlueprintChoiceElements() {
428
+ define("bp-rationale", BlueprintRationaleElement);
429
+ define("bp-choice-option", BlueprintChoiceOptionElement);
430
+ define("bp-choice", BlueprintChoiceElement);
431
+ define("bp-preflight-a", BlueprintPreflightAElement);
432
+ define("bp-preflight-q", BlueprintPreflightQElement);
433
+ define("bp-preflight", BlueprintPreflightElement);
434
+ define("bp-choice-record-row", BlueprintChoiceRecordRowElement);
435
+ define("bp-choice-record", BlueprintChoiceRecordElement);
436
+ }
437
+ registerBlueprintChoiceElements();
438
+
439
+ // blueprint.js
440
+ var SIDEBAR_COLLAPSE_ICON = [
441
+ "M19 21L5 21C3.89543 21 3 20.1046 3 19L3 5C3 3.89543 3.89543 3 5 3L19 3C20.1046 3 21 3.89543 21 5L21 19C21 20.1046 20.1046 21 19 21Z",
442
+ "M7.25 10L5.5 12L7.25 14",
443
+ "M9.5 21V3"
444
+ ];
445
+ var SIDEBAR_EXPAND_ICON = [
446
+ "M19 21L5 21C3.89543 21 3 20.1046 3 19L3 5C3 3.89543 3.89543 3 5 3L19 3C20.1046 3 21 3.89543 21 5L21 19C21 20.1046 20.1046 21 19 21Z",
447
+ "M9.5 21V3",
448
+ "M5.5 10L7.25 12L5.5 14"
449
+ ];
450
+ var SEARCH_ICON = [
451
+ "M3 11C3 15.4183 6.58172 19 11 19C13.213 19 15.2161 18.1015 16.6644 16.6493C18.1077 15.2022 19 13.2053 19 11C19 6.58172 15.4183 3 11 3C6.58172 3 3 6.58172 3 11Z",
452
+ "M17 17L21 21"
453
+ ];
454
+ var DOC_CHEVRON_ICON = "M6 9L12 15L18 9";
455
+ var THEME_MOON_ICON = "M3 11.5066C3 16.7497 7.25034 21 12.4934 21C16.2209 21 19.4466 18.8518 21 15.7259C12.4934 15.7259 8.27411 11.5066 8.27411 3C5.14821 4.55344 3 7.77915 3 11.5066Z";
456
+ var THEME_BULB_ICON = [
457
+ "M9 18H15",
458
+ "M10 21H14",
459
+ "M9.00082 15C9.00098 13 8.50098 12.5 7.50082 11.5C6.50067 10.5 6.02422 9.48689 6.00082 8C5.95284 4.95029 8.00067 3 12.0008 3C16.001 3 18.0488 4.95029 18.0008 8C17.9774 9.48689 17.5007 10.5 16.5008 11.5C15.501 12.5 15.001 13 15.0008 15"
460
+ ];
461
+ var BLUEPRINT_LOGO_OUTLINE = "M7.3 19H12.7C14.3802 19 15.2202 19 15.862 18.673C16.4265 18.3854 16.8854 17.9265 17.173 17.362C17.5 16.7202 17.5 15.8802 17.5 14.2V9.1075C17.5 8.3982 17.5 8.04355 17.4222 7.70852C17.3531 7.41144 17.2392 7.12663 17.0843 6.86389C16.9096 6.56761 16.665 6.31078 16.1759 5.79716L13.0259 2.48966C12.5028 1.94045 12.2413 1.66585 11.9324 1.46921C11.6585 1.29489 11.3582 1.16618 11.0431 1.08809C10.6876 1 10.3084 1 9.55 1H7.3C5.61984 1 4.77976 1 4.13803 1.32698C3.57354 1.6146 3.1146 2.07354 2.82698 2.63803C2.5 3.27976 2.5 4.11984 2.5 5.8V14.2C2.5 15.8802 2.5 16.7202 2.82698 17.362C3.1146 17.9265 3.57354 18.3854 4.13803 18.673C4.77976 19 5.61984 19 7.3 19Z";
462
+ var BLUEPRINT_LOGO_FILL = "M16 16H5V6L16 16ZM6.5 14.5H11.5L6.5 9.5V14.5Z";
463
+ function metaSelector({ name, property } = {}) {
464
+ if (property) return `meta[property="${property}"]`;
465
+ if (name) return `meta[name="${name}"]`;
466
+ return null;
467
+ }
468
+ function readMetaContent(doc, target) {
469
+ const selector = metaSelector(target);
470
+ return selector ? doc.querySelector(selector)?.content?.trim() ?? "" : "";
471
+ }
472
+ function readMetaContentAll(doc, target) {
473
+ const selector = metaSelector(target);
474
+ if (!selector) return [];
475
+ return [...doc.querySelectorAll(selector)].map((meta) => meta.content?.trim()).filter(Boolean);
476
+ }
477
+ function readMetaJson(doc, name) {
478
+ const raw = readMetaContent(doc, { name });
479
+ if (!raw) return null;
480
+ try {
481
+ return JSON.parse(raw);
482
+ } catch {
483
+ return null;
484
+ }
485
+ }
486
+ function formatPublishedDate(iso) {
487
+ if (!iso) return null;
488
+ const day = iso.includes("T") ? iso.split("T")[0] : iso;
489
+ const date = new Date(day.includes("T") ? day : `${day}T12:00:00`);
490
+ if (Number.isNaN(date.getTime())) return null;
491
+ return {
492
+ iso: day,
493
+ label: date.toLocaleDateString(void 0, {
494
+ day: "numeric",
495
+ month: "long",
496
+ year: "numeric",
497
+ timeZone: "UTC"
498
+ })
499
+ };
500
+ }
501
+ function pageBasename(href, win) {
502
+ try {
503
+ return new URL(href, win.location.href).pathname.split("/").pop() || "index.html";
504
+ } catch {
505
+ return href.split("/").pop() || "index.html";
506
+ }
507
+ }
508
+ function isCurrentHref(href, win) {
509
+ const current = win.location.pathname.split("/").pop() || "index.html";
510
+ return pageBasename(href, win) === current;
511
+ }
512
+ function createChromeSvg(doc, pathD, size = 16) {
513
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
514
+ svg.setAttribute("viewBox", "0 0 24 24");
515
+ svg.setAttribute("fill", "none");
516
+ svg.setAttribute("stroke", "currentColor");
517
+ svg.setAttribute("stroke-width", "1.5");
518
+ svg.setAttribute("stroke-linecap", "round");
519
+ svg.setAttribute("stroke-linejoin", "round");
520
+ svg.setAttribute("width", String(size));
521
+ svg.setAttribute("height", String(size));
522
+ svg.setAttribute("aria-hidden", "true");
523
+ for (const d of Array.isArray(pathD) ? pathD : [pathD]) {
524
+ const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
525
+ path.setAttribute("d", d);
526
+ svg.append(path);
527
+ }
528
+ return svg;
529
+ }
530
+ function createBlueprintLogo(doc) {
531
+ const svg = createChromeSvg(doc, BLUEPRINT_LOGO_OUTLINE, 20);
532
+ svg.setAttribute("viewBox", "0 0 20 20");
533
+ svg.classList.add("site-nav__logo");
534
+ const fill = doc.createElementNS("http://www.w3.org/2000/svg", "path");
535
+ fill.setAttribute("d", BLUEPRINT_LOGO_FILL);
536
+ fill.setAttribute("fill", "currentColor");
537
+ fill.setAttribute("stroke", "none");
538
+ svg.append(fill);
539
+ return svg;
540
+ }
541
+ function injectScrollProgress(doc) {
542
+ if (doc.querySelector(".scroll-progress")) return null;
543
+ const bar = doc.createElement("div");
544
+ bar.className = "scroll-progress";
545
+ bar.setAttribute("aria-hidden", "true");
546
+ return bar;
547
+ }
548
+ function injectSiteNav(doc, win) {
549
+ if (doc.querySelector(".site-nav")) return null;
550
+ const links = readMetaJson(doc, "bp:site-nav");
551
+ if (!links?.length) return null;
552
+ const nav = doc.createElement("nav");
553
+ nav.className = "site-nav";
554
+ nav.setAttribute("aria-label", "Site");
555
+ const brandLabel = readMetaContent(doc, { property: "og:site_name" }) || doc.title.split("—")[0]?.trim() || "Home";
556
+ const brandHref = readMetaContent(doc, { name: "bp:site-home" }) || "index.html";
557
+ const brand = doc.createElement("a");
558
+ brand.className = "site-nav__brand";
559
+ brand.href = brandHref;
560
+ brand.setAttribute("aria-label", `${brandLabel} home`);
561
+ const logo = createBlueprintLogo(doc);
562
+ const wordmark = doc.createElement("span");
563
+ wordmark.className = "site-nav__wordmark";
564
+ wordmark.textContent = brandLabel;
565
+ brand.append(logo, wordmark);
566
+ const list = doc.createElement("ul");
567
+ list.className = "site-nav__links";
568
+ for (const item of links) {
569
+ const li = doc.createElement("li");
570
+ const link = doc.createElement("a");
571
+ link.href = item.href;
572
+ link.textContent = item.label;
573
+ if (item.current || isCurrentHref(item.href, win)) {
574
+ link.setAttribute("aria-current", "page");
575
+ }
576
+ li.append(link);
577
+ list.append(li);
578
+ }
579
+ nav.append(brand, list);
580
+ if (readMetaContent(doc, { name: "bp:site-theme" }) === "nav") {
581
+ const theme = doc.createElement("button");
582
+ theme.className = "toggle site-nav__theme";
583
+ theme.type = "button";
584
+ theme.id = "themeToggle";
585
+ theme.setAttribute("aria-label", "Toggle light and dark theme");
586
+ theme.textContent = "Theme";
587
+ nav.append(theme);
588
+ }
589
+ return nav;
590
+ }
591
+ function injectSidebarToggle(doc) {
592
+ if (doc.querySelector(".sidebar-toggle")) return null;
593
+ const toggle = doc.createElement("button");
594
+ toggle.className = "sidebar-toggle";
595
+ toggle.type = "button";
596
+ toggle.setAttribute("aria-controls", "bp-contents-rail");
597
+ toggle.setAttribute("aria-expanded", "true");
598
+ toggle.setAttribute("aria-label", "Hide contents sidebar");
599
+ const icons = doc.createElement("span");
600
+ icons.className = "sidebar-toggle__icons";
601
+ icons.setAttribute("aria-hidden", "true");
602
+ const collapse = doc.createElement("span");
603
+ collapse.className = "sidebar-toggle__icon sidebar-toggle__icon--collapse bp-transition-opacity bp-duration-slow bp-ease-in-out";
604
+ collapse.append(createChromeSvg(doc, SIDEBAR_COLLAPSE_ICON));
605
+ const expand = doc.createElement("span");
606
+ expand.className = "sidebar-toggle__icon sidebar-toggle__icon--expand bp-transition-opacity bp-duration-slow bp-ease-in-out";
607
+ expand.append(createChromeSvg(doc, SIDEBAR_EXPAND_ICON));
608
+ icons.append(collapse, expand);
609
+ toggle.append(icons);
610
+ return toggle;
611
+ }
612
+ function buildDocSwitcher(doc, win) {
613
+ const collections = readMetaJson(doc, "bp:collections");
614
+ if (!collections?.length) return null;
615
+ const details = doc.createElement("details");
616
+ details.className = "doc-switcher";
617
+ const summary = doc.createElement("summary");
618
+ summary.className = "doc-switcher__current sidebar-field";
619
+ summary.setAttribute("aria-label", "Switch blueprint collection");
620
+ const current = collections.find((item) => item.current || isCurrentHref(item.href, win)) ?? collections[0];
621
+ const collectionMeta = readMetaContent(doc, { name: "bp:collection-meta" });
622
+ const label2 = doc.createElement("span");
623
+ label2.className = "doc-switcher__label";
624
+ const title = doc.createElement("span");
625
+ title.className = "doc-switcher__title";
626
+ title.textContent = current.title;
627
+ const meta = doc.createElement("span");
628
+ meta.className = "doc-switcher__meta";
629
+ meta.textContent = collectionMeta || current.desc || "";
630
+ label2.append(title);
631
+ if (meta.textContent) label2.append(meta);
632
+ const chevron = doc.createElement("span");
633
+ chevron.className = "doc-switcher__chevron bp-transition-transform bp-duration-normal bp-ease-in-out";
634
+ chevron.setAttribute("aria-hidden", "true");
635
+ chevron.append(createChromeSvg(doc, DOC_CHEVRON_ICON));
636
+ summary.append(label2, chevron);
637
+ const menu = doc.createElement("div");
638
+ menu.className = "doc-switcher__menu";
639
+ menu.setAttribute("role", "menu");
640
+ for (const item of collections) {
641
+ const link = doc.createElement("a");
642
+ link.href = item.href;
643
+ link.setAttribute("role", "menuitem");
644
+ if (item.current || isCurrentHref(item.href, win)) {
645
+ link.className = "is-current";
646
+ link.setAttribute("aria-current", "page");
647
+ }
648
+ const option = doc.createElement("span");
649
+ option.className = "doc-switcher__option";
650
+ const name = doc.createElement("span");
651
+ name.className = "doc-switcher__name";
652
+ name.textContent = item.title;
653
+ option.append(name);
654
+ if (item.desc) {
655
+ const desc = doc.createElement("span");
656
+ desc.className = "doc-switcher__desc";
657
+ desc.textContent = item.desc;
658
+ option.append(desc);
659
+ }
660
+ const go = doc.createElement("span");
661
+ go.className = "doc-switcher__go bp-transition-opacity";
662
+ go.setAttribute("aria-hidden", "true");
663
+ go.append(createChromeSvg(doc, DOC_CHEVRON_ICON, 14));
664
+ link.append(option, go);
665
+ menu.append(link);
666
+ }
667
+ details.append(summary, menu);
668
+ return details;
669
+ }
670
+ function buildSidebarFooter(doc) {
671
+ const footer = doc.createElement("div");
672
+ footer.className = "sidebar-footer";
673
+ const theme = doc.createElement("button");
674
+ theme.className = "theme-toggle";
675
+ theme.type = "button";
676
+ theme.setAttribute("role", "switch");
677
+ theme.setAttribute("aria-checked", "false");
678
+ theme.setAttribute("aria-label", "Switch to dark theme");
679
+ const thumb = doc.createElement("span");
680
+ thumb.className = "theme-toggle__thumb bp-transition-transform bp-duration-normal bp-ease-in-out";
681
+ thumb.setAttribute("aria-hidden", "true");
682
+ const moon = doc.createElement("span");
683
+ moon.className = "theme-toggle__icon theme-toggle__icon--moon";
684
+ moon.setAttribute("aria-hidden", "true");
685
+ moon.append(createChromeSvg(doc, THEME_MOON_ICON, 14));
686
+ const bulb = doc.createElement("span");
687
+ bulb.className = "theme-toggle__icon theme-toggle__icon--bulb";
688
+ bulb.setAttribute("aria-hidden", "true");
689
+ bulb.append(createChromeSvg(doc, THEME_BULB_ICON, 14));
690
+ theme.append(thumb, moon, bulb);
691
+ const meta = doc.createElement("div");
692
+ meta.className = "sidebar-meta";
693
+ const authors = readMetaContentAll(doc, { property: "article:author" });
694
+ const published = formatPublishedDate(
695
+ readMetaContent(doc, { property: "article:published_time" })
696
+ );
697
+ if (authors.length) {
698
+ const item = doc.createElement("div");
699
+ item.className = "sidebar-meta__item";
700
+ const label2 = doc.createElement("p");
701
+ label2.className = "bp-meta";
702
+ label2.textContent = authors.length > 1 ? "Authors" : "Author";
703
+ const value = doc.createElement("p");
704
+ value.className = "bp-note";
705
+ value.textContent = authors.join(", ");
706
+ item.append(label2, value);
707
+ meta.append(item);
708
+ }
709
+ if (published) {
710
+ const item = doc.createElement("div");
711
+ item.className = "sidebar-meta__item";
712
+ const label2 = doc.createElement("p");
713
+ label2.className = "bp-meta";
714
+ label2.textContent = "Date";
715
+ const value = doc.createElement("p");
716
+ value.className = "bp-note";
717
+ const time = doc.createElement("time");
718
+ time.dateTime = published.iso;
719
+ time.textContent = published.label;
720
+ value.append(time);
721
+ item.append(label2, value);
722
+ meta.append(item);
723
+ }
724
+ footer.append(theme);
725
+ if (meta.childNodes.length) footer.append(meta);
726
+ return footer;
727
+ }
728
+ function injectSidebar(doc, win) {
729
+ if (doc.querySelector(".bp-sidebar")) return null;
730
+ const nav = doc.createElement("nav");
731
+ nav.className = "bp-sidebar";
732
+ nav.id = "bp-contents-rail";
733
+ nav.setAttribute("aria-label", "Contents");
734
+ const panel = doc.createElement("div");
735
+ panel.className = "bp-sidebar__panel";
736
+ const search = doc.createElement("div");
737
+ search.className = "sidebar-search";
738
+ const searchField = doc.createElement("button");
739
+ searchField.type = "button";
740
+ searchField.className = "sidebar-search__field sidebar-field bp-transition-colors bp-ease";
741
+ searchField.setAttribute("aria-haspopup", "dialog");
742
+ searchField.setAttribute("aria-keyshortcuts", "Meta+K Control+K");
743
+ searchField.setAttribute("aria-label", "Search (⌘K)");
744
+ const searchIcon = createChromeSvg(doc, SEARCH_ICON, 14);
745
+ searchIcon.classList.add("sidebar-search__icon");
746
+ const searchLabel = doc.createElement("span");
747
+ searchLabel.className = "sidebar-search__label";
748
+ searchLabel.textContent = "Search";
749
+ const searchHint = doc.createElement("span");
750
+ searchHint.className = "sidebar-search__hint";
751
+ searchHint.setAttribute("aria-hidden", "true");
752
+ const searchHintCmd = doc.createElement("kbd");
753
+ searchHintCmd.textContent = "⌘";
754
+ const searchHintKey = doc.createElement("kbd");
755
+ searchHintKey.textContent = "K";
756
+ searchHint.append(searchHintCmd, searchHintKey);
757
+ searchField.append(searchIcon, searchLabel, searchHint);
758
+ search.append(searchField);
759
+ const switcher = buildDocSwitcher(doc, win);
760
+ const tocSwitcher = doc.createElement("details");
761
+ tocSwitcher.className = "bp-toc-switcher";
762
+ tocSwitcher.open = true;
763
+ const tocSummary = doc.createElement("summary");
764
+ tocSummary.className = "bp-toc-switcher__current sidebar-field";
765
+ tocSummary.setAttribute("aria-label", "Jump to section");
766
+ const tocLabel = doc.createElement("span");
767
+ tocLabel.className = "bp-toc-switcher__label";
768
+ const tocTitle = doc.createElement("span");
769
+ tocTitle.className = "bp-toc-switcher__title";
770
+ tocTitle.textContent = "Contents";
771
+ tocLabel.append(tocTitle);
772
+ const tocChevron = doc.createElement("span");
773
+ tocChevron.className = "bp-toc-switcher__chevron bp-transition-transform bp-duration-normal bp-ease-in-out";
774
+ tocChevron.setAttribute("aria-hidden", "true");
775
+ tocChevron.append(createChromeSvg(doc, DOC_CHEVRON_ICON));
776
+ tocSummary.append(tocLabel, tocChevron);
777
+ const list = doc.createElement("ul");
778
+ tocSwitcher.append(tocSummary, list);
779
+ const mobileHead = doc.createElement("div");
780
+ mobileHead.className = "sidebar-mobile-head";
781
+ mobileHead.append(tocSwitcher, search);
782
+ panel.append(mobileHead);
783
+ if (switcher) panel.append(switcher);
784
+ nav.append(panel, buildSidebarFooter(doc));
785
+ return nav;
786
+ }
787
+ function syncThemeSwitch(doc, button) {
788
+ const dark = doc.documentElement.dataset.obviousTheme === "dark";
789
+ button.setAttribute("aria-checked", String(dark));
790
+ button.setAttribute(
791
+ "aria-label",
792
+ dark ? "Switch to light theme" : "Switch to dark theme"
793
+ );
794
+ }
795
+ var THEME_KEY = "bp-theme";
796
+ function readStoredTheme(win) {
797
+ try {
798
+ const value = win.localStorage.getItem(THEME_KEY);
799
+ return value === "dark" || value === "light" ? value : null;
800
+ } catch {
801
+ return null;
802
+ }
803
+ }
804
+ function writeStoredTheme(win, value) {
805
+ try {
806
+ win.localStorage.setItem(THEME_KEY, value);
807
+ } catch {
808
+ }
809
+ }
810
+ function systemPrefersDark(win) {
811
+ try {
812
+ return win.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
813
+ } catch {
814
+ return false;
815
+ }
816
+ }
817
+ function followsSystemTheme(doc) {
818
+ return readMetaContent(doc, { name: "bp:theme" }).toLowerCase() === "auto";
819
+ }
820
+ function themePrefersReducedMotion(win) {
821
+ try {
822
+ return win.matchMedia("(prefers-reduced-motion: reduce)").matches;
823
+ } catch {
824
+ return false;
825
+ }
826
+ }
827
+ function setTheme(doc, theme) {
828
+ doc.documentElement.dataset.obviousTheme = theme;
829
+ for (const button of doc.querySelectorAll(".theme-toggle")) {
830
+ syncThemeSwitch(doc, button);
831
+ }
832
+ }
833
+ function setThemeAnimated(doc, win, theme) {
834
+ const apply = () => setTheme(doc, theme);
835
+ if (themePrefersReducedMotion(win) || typeof doc.startViewTransition !== "function") {
836
+ apply();
837
+ return;
838
+ }
839
+ const root = doc.documentElement;
840
+ let applied = false;
841
+ const clearSwitching = () => {
842
+ delete root.dataset.bpThemeSwitching;
843
+ };
844
+ const runUpdate = () => {
845
+ applied = true;
846
+ apply();
847
+ };
848
+ root.dataset.bpThemeSwitching = "";
849
+ try {
850
+ let transition;
851
+ try {
852
+ transition = doc.startViewTransition({ update: runUpdate, types: ["bp-theme"] });
853
+ } catch {
854
+ transition = doc.startViewTransition(runUpdate);
855
+ }
856
+ if (!applied) runUpdate();
857
+ transition?.ready?.catch(clearSwitching);
858
+ if (transition?.finished) {
859
+ transition.finished.finally(clearSwitching).catch(clearSwitching);
860
+ } else {
861
+ clearSwitching();
862
+ }
863
+ } catch {
864
+ clearSwitching();
865
+ apply();
866
+ }
867
+ }
868
+ function resolveInitialTheme(doc, win) {
869
+ const stored = readStoredTheme(win);
870
+ if (stored) {
871
+ setTheme(doc, stored);
872
+ return;
873
+ }
874
+ if (followsSystemTheme(doc)) {
875
+ setTheme(doc, systemPrefersDark(win) ? "dark" : "light");
876
+ }
877
+ }
878
+ function wireThemeToggle(doc, win) {
879
+ const root = doc.documentElement;
880
+ const toggleTheme = () => {
881
+ const next = root.dataset.obviousTheme === "dark" ? "light" : "dark";
882
+ setThemeAnimated(doc, win, next);
883
+ writeStoredTheme(win, next);
884
+ };
885
+ for (const button of doc.querySelectorAll(".theme-toggle")) {
886
+ if (button.dataset.bpThemeWired) continue;
887
+ button.dataset.bpThemeWired = "true";
888
+ syncThemeSwitch(doc, button);
889
+ button.addEventListener("click", toggleTheme);
890
+ }
891
+ const navTheme = doc.getElementById("themeToggle");
892
+ if (navTheme && !navTheme.dataset.bpThemeWired) {
893
+ navTheme.dataset.bpThemeWired = "true";
894
+ navTheme.addEventListener("click", toggleTheme);
895
+ }
896
+ if (followsSystemTheme(doc) && win.matchMedia) {
897
+ const query = win.matchMedia("(prefers-color-scheme: dark)");
898
+ query.addEventListener?.("change", (event) => {
899
+ if (readStoredTheme(win)) return;
900
+ setThemeAnimated(doc, win, event.matches ? "dark" : "light");
901
+ });
902
+ }
903
+ }
904
+ function wireSidebarCollapse(doc, win) {
905
+ const root = doc.documentElement;
906
+ const toggle = doc.querySelector(".sidebar-toggle");
907
+ if (!toggle || toggle.dataset.bpSidebarWired) return;
908
+ toggle.dataset.bpSidebarWired = "true";
909
+ const KEY = "bp-sidebar";
910
+ const read = () => {
911
+ try {
912
+ return win.localStorage.getItem(KEY);
913
+ } catch {
914
+ return null;
915
+ }
916
+ };
917
+ const write = (value) => {
918
+ try {
919
+ win.localStorage.setItem(KEY, value);
920
+ } catch {
921
+ }
922
+ };
923
+ const apply = (collapsed) => {
924
+ root.dataset.sidebar = collapsed ? "collapsed" : "open";
925
+ toggle.setAttribute("aria-expanded", String(!collapsed));
926
+ toggle.setAttribute(
927
+ "aria-label",
928
+ collapsed ? "Show contents sidebar" : "Hide contents sidebar"
929
+ );
930
+ };
931
+ apply(read() === "collapsed");
932
+ win.requestAnimationFrame(() => {
933
+ root.classList.add("bp-sidebar-animate");
934
+ });
935
+ toggle.addEventListener("click", () => {
936
+ const collapsed = root.dataset.sidebar !== "collapsed";
937
+ apply(collapsed);
938
+ write(collapsed ? "collapsed" : "open");
939
+ });
940
+ }
941
+ function wireSearchPalette(doc, win) {
942
+ const trigger = doc.querySelector(".sidebar-search__field");
943
+ if (!trigger || trigger.dataset.bpSearchWired) return;
944
+ trigger.dataset.bpSearchWired = "true";
945
+ const isMacLike = /Mac|iPhone|iPad/.test(win.navigator?.platform || "");
946
+ if (!isMacLike) {
947
+ const cmd = trigger.querySelector(".sidebar-search__hint kbd");
948
+ if (cmd) cmd.textContent = "Ctrl";
949
+ trigger.setAttribute("aria-label", "Search (Ctrl+K)");
950
+ }
951
+ let overlay = null;
952
+ let panel = null;
953
+ let input = null;
954
+ let results = null;
955
+ let actionDefs = [];
956
+ let entries = [];
957
+ let visible = [];
958
+ let selected = 0;
959
+ let returnFocus = null;
960
+ let prevOverflow = "";
961
+ const collectEntries = () => {
962
+ const list = doc.querySelector(".bp-sidebar .bp-sidebar__panel .bp-toc-switcher > ul") ?? doc.querySelector(".bp-sidebar .bp-sidebar__panel > ul") ?? doc.querySelector(".bp-toc > ul");
963
+ if (!list) return [];
964
+ const out = [];
965
+ let group = "";
966
+ for (const li of list.children) {
967
+ if (li.classList.contains("bp-nav-group")) {
968
+ group = (li.textContent || "").trim();
969
+ continue;
970
+ }
971
+ const link = li.querySelector(':scope > a[href^="#"]');
972
+ if (!link) continue;
973
+ const title = (link.textContent || "").trim();
974
+ out.push({ href: link.getAttribute("href") || "", title, crumb: group });
975
+ for (const sub of li.querySelectorAll(':scope > ul > li > a[href^="#"]')) {
976
+ out.push({
977
+ href: sub.getAttribute("href") || "",
978
+ title: (sub.textContent || "").trim(),
979
+ crumb: [group, title].filter(Boolean).join(" › ")
980
+ });
981
+ }
982
+ }
983
+ return out;
984
+ };
985
+ const modGlyph = isMacLike ? "⌘" : "⌃";
986
+ const matchesShortcut = (event, key) => {
987
+ const mod = isMacLike ? event.metaKey : event.ctrlKey;
988
+ if (!mod || !event.shiftKey) return false;
989
+ return event.key.toLowerCase() === key.toLowerCase();
990
+ };
991
+ const buildActionDefs = () => {
992
+ const defs = [];
993
+ if (doc.querySelector(".theme-toggle")) {
994
+ defs.push({
995
+ label: "Toggle theme",
996
+ icon: THEME_MOON_ICON,
997
+ chip: `${modGlyph}⇧L`,
998
+ shortcut: "l",
999
+ run: () => doc.querySelector(".theme-toggle")?.click()
1000
+ });
1001
+ }
1002
+ if (doc.querySelector(".sidebar-toggle")) {
1003
+ defs.push({
1004
+ label: "Toggle sidebar",
1005
+ icon: SIDEBAR_COLLAPSE_ICON,
1006
+ chip: `${modGlyph}⇧B`,
1007
+ shortcut: "b",
1008
+ run: () => doc.querySelector(".sidebar-toggle")?.click()
1009
+ });
1010
+ }
1011
+ return defs;
1012
+ };
1013
+ const buildAction = (def) => {
1014
+ const button = doc.createElement("button");
1015
+ button.type = "button";
1016
+ button.className = "docs-search__action bp-transition-colors bp-ease";
1017
+ const icon = createChromeSvg(doc, def.icon, 16);
1018
+ icon.classList.add("docs-search__action-icon");
1019
+ const label2 = doc.createElement("span");
1020
+ label2.className = "docs-search__action-label";
1021
+ label2.textContent = def.label;
1022
+ button.append(icon, label2);
1023
+ if (def.chip) {
1024
+ const chip = doc.createElement("kbd");
1025
+ chip.className = "docs-search__chip";
1026
+ for (const glyph of Array.from(def.chip)) {
1027
+ const key = doc.createElement("span");
1028
+ key.textContent = glyph;
1029
+ chip.append(key);
1030
+ }
1031
+ button.append(chip);
1032
+ }
1033
+ button.addEventListener("click", () => def.run(button));
1034
+ def.button = button;
1035
+ return button;
1036
+ };
1037
+ const highlight = (text, q) => {
1038
+ if (!q) return [doc.createTextNode(text)];
1039
+ const idx = text.toLowerCase().indexOf(q);
1040
+ if (idx < 0) return [doc.createTextNode(text)];
1041
+ const mark = doc.createElement("mark");
1042
+ mark.textContent = text.slice(idx, idx + q.length);
1043
+ return [
1044
+ doc.createTextNode(text.slice(0, idx)),
1045
+ mark,
1046
+ doc.createTextNode(text.slice(idx + q.length))
1047
+ ];
1048
+ };
1049
+ const buildRow = (entry, index, q) => {
1050
+ const row = doc.createElement("a");
1051
+ row.className = "docs-search__row";
1052
+ row.id = `docs-search-row-${index}`;
1053
+ row.setAttribute("role", "option");
1054
+ row.href = entry.href;
1055
+ row.dataset.index = String(index);
1056
+ row.addEventListener("click", () => close());
1057
+ row.addEventListener("mousemove", () => {
1058
+ if (selected !== index) {
1059
+ selected = index;
1060
+ applySelection();
1061
+ }
1062
+ });
1063
+ const mainCol = doc.createElement("span");
1064
+ mainCol.className = "docs-search__row-main";
1065
+ const iconBox = doc.createElement("span");
1066
+ iconBox.className = "docs-search__icon-box";
1067
+ iconBox.setAttribute("aria-hidden", "true");
1068
+ iconBox.textContent = "#";
1069
+ mainCol.append(iconBox);
1070
+ if (entry.crumb) {
1071
+ const crumb = doc.createElement("span");
1072
+ crumb.className = "docs-search__crumb";
1073
+ crumb.textContent = entry.crumb;
1074
+ mainCol.append(crumb);
1075
+ }
1076
+ const title = doc.createElement("span");
1077
+ title.className = "docs-search__title";
1078
+ title.append(...highlight(entry.title, q));
1079
+ mainCol.append(title);
1080
+ const enter = doc.createElement("kbd");
1081
+ enter.className = "docs-search__enter";
1082
+ enter.textContent = "↵";
1083
+ row.append(mainCol, enter);
1084
+ return row;
1085
+ };
1086
+ const buildEmpty = () => {
1087
+ const wrap = doc.createElement("div");
1088
+ wrap.className = "docs-search__empty";
1089
+ const center = doc.createElement("div");
1090
+ center.className = "docs-search__empty-center";
1091
+ const copy = doc.createElement("div");
1092
+ copy.className = "docs-search__empty-copy";
1093
+ const title = doc.createElement("p");
1094
+ title.className = "docs-search__empty-title";
1095
+ title.textContent = "No results found";
1096
+ const note = doc.createElement("p");
1097
+ note.className = "docs-search__empty-note";
1098
+ note.textContent = "Try searching with different keywords";
1099
+ copy.append(title, note);
1100
+ center.append(copy);
1101
+ wrap.append(center);
1102
+ return wrap;
1103
+ };
1104
+ const applySelection = () => {
1105
+ const rows = results.querySelectorAll('.docs-search__row[role="option"]');
1106
+ let activeId = "";
1107
+ rows.forEach((row, index) => {
1108
+ const isSel = index === selected;
1109
+ row.setAttribute("aria-selected", String(isSel));
1110
+ if (isSel) {
1111
+ activeId = row.id;
1112
+ row.scrollIntoView({ block: "nearest" });
1113
+ }
1114
+ });
1115
+ if (activeId) input.setAttribute("aria-activedescendant", activeId);
1116
+ else input.removeAttribute("aria-activedescendant");
1117
+ };
1118
+ const move = (delta) => {
1119
+ if (!visible.length) return;
1120
+ selected = (selected + delta + visible.length) % visible.length;
1121
+ applySelection();
1122
+ };
1123
+ const render = (query) => {
1124
+ const q = (query || "").trim().toLowerCase();
1125
+ visible = q ? entries.filter((entry) => entry.title.toLowerCase().includes(q)) : entries;
1126
+ selected = 0;
1127
+ results.replaceChildren();
1128
+ if (visible.length === 0) {
1129
+ results.append(buildEmpty());
1130
+ input.removeAttribute("aria-activedescendant");
1131
+ return;
1132
+ }
1133
+ const group = doc.createElement("div");
1134
+ group.className = "docs-search__group";
1135
+ group.append(doc.createTextNode(q ? "Results" : "On this page"));
1136
+ const count = doc.createElement("span");
1137
+ count.className = "docs-search__group-count";
1138
+ count.textContent = String(visible.length);
1139
+ group.append(count);
1140
+ results.append(group);
1141
+ visible.forEach((entry, index) => results.append(buildRow(entry, index, q)));
1142
+ applySelection();
1143
+ };
1144
+ const onKeydown = (event) => {
1145
+ const action = actionDefs.find((def) => matchesShortcut(event, def.shortcut));
1146
+ if (action) {
1147
+ event.preventDefault();
1148
+ action.run(action.button);
1149
+ return;
1150
+ }
1151
+ if (event.key === "Escape") {
1152
+ event.preventDefault();
1153
+ close();
1154
+ } else if (event.key === "ArrowDown") {
1155
+ event.preventDefault();
1156
+ move(1);
1157
+ } else if (event.key === "ArrowUp") {
1158
+ event.preventDefault();
1159
+ move(-1);
1160
+ } else if (event.key === "Enter") {
1161
+ if (event.target?.closest?.(".docs-search__action")) return;
1162
+ const row = results.querySelector(
1163
+ `.docs-search__row[data-index="${selected}"]`
1164
+ );
1165
+ if (row) {
1166
+ event.preventDefault();
1167
+ row.click();
1168
+ }
1169
+ } else if (event.key === "Tab") {
1170
+ const focusable = panel.querySelectorAll(
1171
+ 'a[href], button, input, [tabindex]:not([tabindex="-1"])'
1172
+ );
1173
+ const first = focusable[0];
1174
+ const last = focusable[focusable.length - 1];
1175
+ if (first && last && (event.shiftKey ? doc.activeElement === first : doc.activeElement === last)) {
1176
+ event.preventDefault();
1177
+ (event.shiftKey ? last : first).focus();
1178
+ }
1179
+ }
1180
+ };
1181
+ const build = () => {
1182
+ overlay = doc.createElement("div");
1183
+ overlay.className = "docs-search-overlay";
1184
+ overlay.addEventListener("click", (event) => {
1185
+ if (event.target === overlay) close();
1186
+ });
1187
+ panel = doc.createElement("div");
1188
+ panel.className = "docs-search";
1189
+ panel.setAttribute("role", "dialog");
1190
+ panel.setAttribute("aria-modal", "true");
1191
+ panel.setAttribute("aria-label", "Search");
1192
+ const field = doc.createElement("div");
1193
+ field.className = "docs-search__field";
1194
+ const icon = createChromeSvg(doc, SEARCH_ICON, 16);
1195
+ icon.classList.add("docs-search__icon");
1196
+ input = doc.createElement("input");
1197
+ input.className = "docs-search__input";
1198
+ input.type = "search";
1199
+ input.placeholder = "Search the blueprint…";
1200
+ input.setAttribute("role", "combobox");
1201
+ input.setAttribute("aria-autocomplete", "list");
1202
+ input.setAttribute("aria-controls", "docs-search-listbox");
1203
+ input.setAttribute("aria-expanded", "false");
1204
+ input.setAttribute("aria-label", "Search the blueprint");
1205
+ input.autocomplete = "off";
1206
+ input.spellcheck = false;
1207
+ const esc = doc.createElement("span");
1208
+ esc.className = "docs-search__esc";
1209
+ esc.textContent = "esc";
1210
+ field.append(icon, input, esc);
1211
+ results = doc.createElement("div");
1212
+ results.className = "docs-search__results";
1213
+ results.id = "docs-search-listbox";
1214
+ results.setAttribute("role", "listbox");
1215
+ results.setAttribute("aria-label", "Search results");
1216
+ const foot = doc.createElement("div");
1217
+ foot.className = "docs-search__foot";
1218
+ const actions = doc.createElement("div");
1219
+ actions.className = "docs-search__actions";
1220
+ actionDefs = buildActionDefs();
1221
+ for (const def of actionDefs) actions.append(buildAction(def));
1222
+ foot.append(actions);
1223
+ panel.append(field, results, foot);
1224
+ overlay.append(panel);
1225
+ (doc.body || doc.documentElement).append(overlay);
1226
+ input.addEventListener("input", () => render(input.value));
1227
+ };
1228
+ const open = () => {
1229
+ if (!overlay) build();
1230
+ if (overlay.classList.contains("is-open")) return;
1231
+ entries = collectEntries();
1232
+ returnFocus = doc.activeElement;
1233
+ prevOverflow = doc.documentElement.style.overflow;
1234
+ doc.documentElement.style.overflow = "hidden";
1235
+ input.value = "";
1236
+ render("");
1237
+ overlay.classList.add("is-open");
1238
+ input.setAttribute("aria-expanded", "true");
1239
+ doc.addEventListener("keydown", onKeydown);
1240
+ input.focus({ preventScroll: true });
1241
+ win.requestAnimationFrame(() => input.focus({ preventScroll: true }));
1242
+ };
1243
+ function close() {
1244
+ if (!overlay || !overlay.classList.contains("is-open")) return;
1245
+ overlay.classList.remove("is-open");
1246
+ input.setAttribute("aria-expanded", "false");
1247
+ input.removeAttribute("aria-activedescendant");
1248
+ doc.documentElement.style.overflow = prevOverflow ?? "";
1249
+ doc.removeEventListener("keydown", onKeydown);
1250
+ if (returnFocus && typeof returnFocus.focus === "function") returnFocus.focus();
1251
+ }
1252
+ trigger.addEventListener("click", open);
1253
+ doc.addEventListener("keydown", (event) => {
1254
+ if ((event.metaKey || event.ctrlKey) && (event.key === "k" || event.key === "K")) {
1255
+ event.preventDefault();
1256
+ if (overlay && overlay.classList.contains("is-open")) close();
1257
+ else open();
1258
+ }
1259
+ });
1260
+ }
1261
+ function injectDocumentChrome(doc, win = doc.defaultView, navigationTree = []) {
1262
+ if (doc.body?.dataset.bpChromeReady === "true") return;
1263
+ const main = doc.querySelector("main");
1264
+ if (!main) return;
1265
+ const hasNavigation = navigationTree.length > 0;
1266
+ const sidebar = hasNavigation ? injectSidebar(doc, win) : null;
1267
+ const chrome = [
1268
+ injectSiteNav(doc, win),
1269
+ hasNavigation ? injectScrollProgress(doc) : null,
1270
+ sidebar ? injectSidebarToggle(doc) : null,
1271
+ sidebar
1272
+ ].filter(Boolean);
1273
+ if (chrome.length > 0) {
1274
+ main.before(...chrome);
1275
+ }
1276
+ if (sidebar) {
1277
+ doc.documentElement.dataset.bpDocument = "";
1278
+ }
1279
+ wireThemeToggle(doc, win);
1280
+ wireSidebarCollapse(doc, win);
1281
+ doc.body.dataset.bpChromeReady = "true";
1282
+ }
1283
+ var CITE_PANE_CLOSE_ICON = "M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426";
1284
+ function buildCitePane(doc) {
1285
+ const port = doc.createElement("div");
1286
+ port.className = "bp-cite-pane-port";
1287
+ const pane = doc.createElement("div");
1288
+ pane.className = "bp-cite-pane";
1289
+ pane.setAttribute("role", "complementary");
1290
+ pane.setAttribute("aria-label", "Cited source");
1291
+ pane.tabIndex = -1;
1292
+ const head = doc.createElement("div");
1293
+ head.className = "bp-cite-pane__head";
1294
+ const heading = doc.createElement("div");
1295
+ heading.className = "bp-cite-pane__heading";
1296
+ const eyebrow = doc.createElement("span");
1297
+ eyebrow.className = "bp-cite-pane__eyebrow";
1298
+ const loc = doc.createElement("button");
1299
+ loc.type = "button";
1300
+ loc.className = "bp-cite-pane__loc";
1301
+ heading.append(eyebrow, loc);
1302
+ const close = doc.createElement("button");
1303
+ close.type = "button";
1304
+ close.className = "bp-cite-pane__close";
1305
+ close.setAttribute("aria-label", "Close cited source");
1306
+ close.append(createChromeSvg(doc, CITE_PANE_CLOSE_ICON));
1307
+ head.append(heading, close);
1308
+ const body = doc.createElement("div");
1309
+ body.className = "bp-cite-pane__body";
1310
+ const swap = doc.createElement("div");
1311
+ swap.className = "bp-cite-pane__swap";
1312
+ const source = doc.createElement("div");
1313
+ source.className = "bp-cite-pane__source";
1314
+ swap.append(source);
1315
+ body.append(swap);
1316
+ pane.append(head, body);
1317
+ port.append(pane);
1318
+ doc.body.append(port);
1319
+ return { port, pane, eyebrow, loc, source, swap, close };
1320
+ }
1321
+ function wireCitePane(doc, win) {
1322
+ const root = doc.documentElement;
1323
+ root.dataset.bpCitePane = "";
1324
+ let ui = null;
1325
+ let lastTrigger = null;
1326
+ let citeEl = null;
1327
+ const reduced = () => themePrefersReducedMotion(win);
1328
+ const resolveScroller = () => {
1329
+ const main = doc.querySelector("main");
1330
+ return main && root.dataset.bpDocument !== void 0 && win.getComputedStyle(main).position === "fixed" ? main : null;
1331
+ };
1332
+ const scrollBy = (scroller, dy) => {
1333
+ if (!dy) return;
1334
+ if (scroller) scroller.scrollTop += dy;
1335
+ else win.scrollBy(0, dy);
1336
+ };
1337
+ const pinCitation = (anchor) => {
1338
+ if (!anchor || !doc.contains(anchor)) return;
1339
+ const scroller = resolveScroller();
1340
+ const anchorTop = anchor.getBoundingClientRect().top;
1341
+ const durationMs = reduced() ? 0 : (parseFloat(
1342
+ win.getComputedStyle(root).getPropertyValue("--bp-duration-slow")
1343
+ ) || 300) + 60;
1344
+ const start = win.performance.now();
1345
+ const keep = () => {
1346
+ scrollBy(scroller, anchor.getBoundingClientRect().top - anchorTop);
1347
+ if (win.performance.now() - start < durationMs) {
1348
+ win.requestAnimationFrame(keep);
1349
+ }
1350
+ };
1351
+ win.requestAnimationFrame(keep);
1352
+ };
1353
+ const ensure = () => {
1354
+ if (ui) return ui;
1355
+ ui = buildCitePane(doc);
1356
+ ui.close.addEventListener("click", () => closePane());
1357
+ ui.loc.addEventListener("click", () => {
1358
+ if (citeEl && doc.contains(citeEl)) {
1359
+ citeEl.scrollIntoView({
1360
+ behavior: reduced() ? "auto" : "smooth",
1361
+ block: "center"
1362
+ });
1363
+ }
1364
+ });
1365
+ return ui;
1366
+ };
1367
+ const paint = (detail) => {
1368
+ ui.eyebrow.textContent = detail.language || "Cited source";
1369
+ ui.loc.textContent = detail.loc || "";
1370
+ ui.loc.hidden = !detail.loc;
1371
+ ui.source.replaceChildren();
1372
+ if (detail.source instanceof Node) {
1373
+ const node = detail.source.cloneNode(true);
1374
+ node.removeAttribute?.("tabindex");
1375
+ ui.source.append(node);
1376
+ } else {
1377
+ const pre = doc.createElement("pre");
1378
+ const code = doc.createElement("code");
1379
+ code.textContent = detail.code || "";
1380
+ pre.append(code);
1381
+ ui.source.append(pre);
1382
+ }
1383
+ };
1384
+ const openPane = (detail, trigger, el2) => {
1385
+ ensure();
1386
+ const wasOpen = ui.pane.classList.contains("is-open");
1387
+ if (lastTrigger && lastTrigger !== trigger) {
1388
+ lastTrigger.setAttribute("aria-expanded", "false");
1389
+ }
1390
+ if (trigger) lastTrigger = trigger;
1391
+ citeEl = el2 || null;
1392
+ if (wasOpen && !reduced()) {
1393
+ ui.swap.classList.add("is-leaving");
1394
+ const swapMs = (parseFloat(
1395
+ win.getComputedStyle(root).getPropertyValue("--bp-duration-normal")
1396
+ ) || 200) * 0.9;
1397
+ win.setTimeout(() => {
1398
+ paint(detail);
1399
+ win.requestAnimationFrame(() => ui.swap.classList.remove("is-leaving"));
1400
+ }, swapMs);
1401
+ } else {
1402
+ paint(detail);
1403
+ }
1404
+ ui.pane.classList.add("is-open");
1405
+ if (trigger) trigger.setAttribute("aria-expanded", "true");
1406
+ if (!wasOpen) {
1407
+ pinCitation(citeEl || trigger);
1408
+ win.requestAnimationFrame(() => ui.pane.focus({ preventScroll: true }));
1409
+ }
1410
+ };
1411
+ const closePane = () => {
1412
+ if (!ui || !ui.pane.classList.contains("is-open")) return;
1413
+ ui.pane.classList.remove("is-open");
1414
+ const trigger = lastTrigger;
1415
+ const anchor = citeEl || trigger;
1416
+ lastTrigger = null;
1417
+ citeEl = null;
1418
+ pinCitation(anchor);
1419
+ if (trigger) {
1420
+ trigger.setAttribute("aria-expanded", "false");
1421
+ if (doc.contains(trigger)) trigger.focus({ preventScroll: true });
1422
+ }
1423
+ };
1424
+ doc.addEventListener("bp-cite-open", (event) => {
1425
+ const detail = event.detail || {};
1426
+ const el2 = event.target;
1427
+ const trigger = el2 && el2.querySelector && el2.querySelector(".bp-cite-trigger") || el2;
1428
+ openPane(detail, trigger, el2);
1429
+ });
1430
+ doc.addEventListener("keydown", (event) => {
1431
+ if (event.key === "Escape" && ui && ui.pane.classList.contains("is-open")) {
1432
+ event.stopPropagation();
1433
+ closePane();
1434
+ }
1435
+ });
1436
+ }
1437
+ registerBlueprintChoiceElements();
1438
+ function initializeBlueprintRuntime(doc = document, win = window) {
1439
+ const root = doc.documentElement;
1440
+ if (root.dataset.blueprintRuntime === "ready") return;
1441
+ root.dataset.blueprintRuntime = "ready";
1442
+ resolveInitialTheme(doc, win);
1443
+ const navigationTree = ensureDocumentNavTree(doc);
1444
+ injectDocumentChrome(doc, win, navigationTree);
1445
+ generateNavigationFromHeadings(doc, navigationTree);
1446
+ wireSectionHeadingLinks(doc, win);
1447
+ wireSearchPalette(doc, win);
1448
+ wireCitePane(doc, win);
1449
+ wireFigureDiagramSize(doc);
1450
+ const main = doc.querySelector("main");
1451
+ const resolveScroller = () => main && root.dataset.bpDocument !== void 0 && win.getComputedStyle(main).position === "fixed" ? main : null;
1452
+ let shellScroller = resolveScroller();
1453
+ const scrollTop = () => shellScroller ? shellScroller.scrollTop : win.scrollY;
1454
+ const scrollHeight = () => shellScroller ? shellScroller.scrollHeight : root.scrollHeight;
1455
+ const clientHeight = () => shellScroller ? shellScroller.clientHeight : win.innerHeight;
1456
+ const progress = doc.querySelector(".scroll-progress");
1457
+ const isPrimaryNavLink = (link) => {
1458
+ const listItem = link.closest("li");
1459
+ const list = listItem?.parentElement;
1460
+ if (!list || !listItem) return true;
1461
+ const container = link.closest(".bp-sidebar, .bp-toc, .sidebar");
1462
+ if (!container) return true;
1463
+ const topList = container.querySelector(":scope > .bp-sidebar__panel .bp-toc-switcher > ul") ?? container.querySelector(":scope > .bp-sidebar__panel > ul") ?? container.querySelector(":scope > ul");
1464
+ return list === topList;
1465
+ };
1466
+ const entries = [...doc.querySelectorAll('.bp-sidebar a[href^="#"], .bp-toc a[href^="#"], .sidebar a[href^="#"]')].filter(isPrimaryNavLink).map((link) => ({
1467
+ link,
1468
+ section: doc.getElementById(decodeURIComponent(link.hash.slice(1)))
1469
+ })).filter(({ section }) => section);
1470
+ const sections = [...new Set(entries.map(({ section }) => section))].sort(
1471
+ (a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
1472
+ );
1473
+ let scheduled = false;
1474
+ let scrollingTo = null;
1475
+ const tocSwitchers = [...doc.querySelectorAll(".bp-toc-switcher")];
1476
+ const tocSwitcherTitles = [...doc.querySelectorAll(".bp-toc-switcher__title")];
1477
+ const railColumnQuery = win.matchMedia("(min-width: 861px)");
1478
+ const syncTocSwitcherOpen = () => {
1479
+ for (const sw of tocSwitchers) sw.open = railColumnQuery.matches;
1480
+ };
1481
+ syncTocSwitcherOpen();
1482
+ railColumnQuery.addEventListener("change", syncTocSwitcherOpen);
1483
+ const placeMarker = (link) => {
1484
+ const container = link.closest(".bp-sidebar, .bp-toc, .sidebar");
1485
+ const list = container?.querySelector(":scope > ul") ?? container?.querySelector("ul");
1486
+ if (!list) return;
1487
+ list.style.setProperty("--bp-toc-y", `${link.offsetTop}px`);
1488
+ list.style.setProperty("--bp-toc-h", `${link.offsetHeight}px`);
1489
+ };
1490
+ const revealInRail = (link) => {
1491
+ const panel = link.closest(".bp-sidebar__panel");
1492
+ if (!panel || panel.scrollHeight <= panel.clientHeight) return;
1493
+ const style = win.getComputedStyle(panel);
1494
+ const padTop = Number.parseFloat(style.scrollPaddingTop) || 0;
1495
+ const padBottom = Number.parseFloat(style.scrollPaddingBottom) || 0;
1496
+ const panelRect = panel.getBoundingClientRect();
1497
+ const linkRect = link.getBoundingClientRect();
1498
+ const safeTop = panelRect.top + padTop;
1499
+ const safeBottom = panelRect.bottom - padBottom;
1500
+ let delta = 0;
1501
+ if (linkRect.top < safeTop) delta = linkRect.top - safeTop;
1502
+ else if (linkRect.bottom > safeBottom) delta = linkRect.bottom - safeBottom;
1503
+ if (delta === 0) return;
1504
+ panel.scrollBy({
1505
+ top: delta,
1506
+ behavior: prefersReducedMotion() ? "auto" : "smooth"
1507
+ });
1508
+ };
1509
+ let lastCurrent = null;
1510
+ const setCurrent = (section) => {
1511
+ const changed = section !== lastCurrent;
1512
+ let activeLabel = "";
1513
+ for (const item of entries) {
1514
+ if (section && item.section === section) {
1515
+ item.link.setAttribute("aria-current", "location");
1516
+ placeMarker(item.link);
1517
+ if (changed) revealInRail(item.link);
1518
+ if (!activeLabel) activeLabel = item.link.textContent.trim();
1519
+ } else {
1520
+ item.link.removeAttribute("aria-current");
1521
+ }
1522
+ }
1523
+ lastCurrent = section;
1524
+ if (changed) {
1525
+ const summaryLabel = activeLabel || "Contents";
1526
+ for (const title of tocSwitcherTitles) title.textContent = summaryLabel;
1527
+ }
1528
+ };
1529
+ const mobileHead = doc.querySelector(".sidebar-mobile-head");
1530
+ const contentsRail = doc.querySelector("#bp-contents-rail");
1531
+ const syncHeadDivider = () => {
1532
+ if (!mobileHead || !contentsRail) return;
1533
+ if (win.getComputedStyle(mobileHead).position !== "fixed") {
1534
+ mobileHead.removeAttribute("data-bp-head-stuck");
1535
+ return;
1536
+ }
1537
+ const stuck = contentsRail.getBoundingClientRect().bottom <= mobileHead.getBoundingClientRect().bottom + 0.5;
1538
+ mobileHead.toggleAttribute("data-bp-head-stuck", stuck);
1539
+ };
1540
+ const update = () => {
1541
+ scheduled = false;
1542
+ syncHeadDivider();
1543
+ if (progress) {
1544
+ const max = scrollHeight() - clientHeight();
1545
+ progress.style.transform = `scaleX(${max > 0 ? scrollTop() / max : 0})`;
1546
+ }
1547
+ if (sections.length === 0) return;
1548
+ const threshold = win.innerHeight * 0.35;
1549
+ if (scrollingTo) {
1550
+ const rect = scrollingTo.getBoundingClientRect();
1551
+ const top = rect.top;
1552
+ const visible = rect.bottom > 0 && top < win.innerHeight;
1553
+ if (top <= threshold && top >= -8) {
1554
+ scrollingTo = null;
1555
+ } else if (visible) {
1556
+ setCurrent(scrollingTo);
1557
+ return;
1558
+ } else {
1559
+ scrollingTo = null;
1560
+ }
1561
+ }
1562
+ if (scrollTop() + clientHeight() >= scrollHeight() - 2) {
1563
+ setCurrent(sections.at(-1));
1564
+ return;
1565
+ }
1566
+ setCurrent(
1567
+ sections.reduce(
1568
+ (current, section) => section.getBoundingClientRect().top <= threshold ? section : current,
1569
+ sections[0]
1570
+ )
1571
+ );
1572
+ };
1573
+ const scheduleUpdate = () => {
1574
+ if (scheduled) return;
1575
+ scheduled = true;
1576
+ win.requestAnimationFrame(update);
1577
+ };
1578
+ const prefersReducedMotion = () => themePrefersReducedMotion(win);
1579
+ const updateFromHash = () => {
1580
+ const match = entries.find(({ link }) => link.hash === win.location.hash);
1581
+ if (match) {
1582
+ scrollingTo = match.section;
1583
+ setCurrent(match.section);
1584
+ const top = match.section.getBoundingClientRect().top;
1585
+ const threshold = win.innerHeight * 0.35;
1586
+ if (top > threshold || top < -8) {
1587
+ match.section.scrollIntoView({ block: "start" });
1588
+ }
1589
+ scheduleUpdate();
1590
+ return;
1591
+ }
1592
+ scrollingTo = null;
1593
+ scheduleUpdate();
1594
+ };
1595
+ const navLinkSelector = '.bp-sidebar a[href^="#"], .bp-toc a[href^="#"], .sidebar a[href^="#"], .bp-contents a[href^="#"]';
1596
+ const scrollToSection = (section) => {
1597
+ scrollingTo = section;
1598
+ section.scrollIntoView({
1599
+ behavior: prefersReducedMotion() ? "auto" : "smooth",
1600
+ block: "start"
1601
+ });
1602
+ const finish = () => {
1603
+ if (scrollingTo === section) scrollingTo = null;
1604
+ };
1605
+ if ("onscrollend" in win) {
1606
+ const scrollTarget = shellScroller ?? win;
1607
+ scrollTarget.addEventListener("scrollend", finish, { once: true });
1608
+ } else {
1609
+ win.setTimeout(finish, prefersReducedMotion() ? 0 : 800);
1610
+ }
1611
+ scheduleUpdate();
1612
+ };
1613
+ doc.addEventListener("click", (event) => {
1614
+ const link = event.target.closest?.(navLinkSelector);
1615
+ if (!link?.hash) return;
1616
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
1617
+ return;
1618
+ }
1619
+ const section = doc.getElementById(decodeURIComponent(link.hash.slice(1)));
1620
+ if (!section) return;
1621
+ event.preventDefault();
1622
+ if (win.location.hash !== link.hash) win.history.pushState(null, "", link.hash);
1623
+ if (entries.some(({ section: target }) => target === section)) setCurrent(section);
1624
+ if (!railColumnQuery.matches) {
1625
+ for (const sw of tocSwitchers) sw.open = false;
1626
+ }
1627
+ scrollToSection(section);
1628
+ });
1629
+ let scrollSource = shellScroller ?? win;
1630
+ scrollSource.addEventListener("scroll", scheduleUpdate, { passive: true });
1631
+ const syncScrollSource = () => {
1632
+ shellScroller = resolveScroller();
1633
+ const nextSource = shellScroller ?? win;
1634
+ if (nextSource !== scrollSource) {
1635
+ scrollSource.removeEventListener("scroll", scheduleUpdate, { passive: true });
1636
+ nextSource.addEventListener("scroll", scheduleUpdate, { passive: true });
1637
+ scrollSource = nextSource;
1638
+ }
1639
+ };
1640
+ win.addEventListener("hashchange", updateFromHash);
1641
+ win.addEventListener("popstate", updateFromHash);
1642
+ win.addEventListener("resize", () => {
1643
+ syncScrollSource();
1644
+ scheduleUpdate();
1645
+ }, { passive: true });
1646
+ updateFromHash();
1647
+ }
1648
+ var HEADING_LINK_ICON = [
1649
+ "M14 11.9976C14 9.5059 11.683 7 8.85714 7C8.52241 7 7.41904 7.00001 7.14286 7.00001C4.30254 7.00001 2 9.23752 2 11.9976C2 14.376 3.70973 16.3664 6 16.8714C6.36756 16.9525 6.75006 16.9952 7.14286 16.9952",
1650
+ "M10 11.9976C10 14.4893 12.317 16.9952 15.1429 16.9952C15.4776 16.9952 16.581 16.9952 16.8571 16.9952C19.6975 16.9952 22 14.7577 22 11.9976C22 9.6192 20.2903 7.62884 18 7.12383C17.6324 7.04278 17.2499 6.99999 16.8571 6.99999"
1651
+ ];
1652
+ var HEADING_COPY_ICON = [
1653
+ "M19.4 20H9.6C9.26863 20 9 19.7314 9 19.4V9.6C9 9.26863 9.26863 9 9.6 9H19.4C19.7314 9 20 9.26863 20 9.6V19.4C20 19.7314 19.7314 20 19.4 20Z",
1654
+ "M15 9V4.6C15 4.26863 14.7314 4 14.4 4H4.6C4.26863 4 4 4.26863 4 4.6V14.4C4 14.7314 4.26863 15 4.6 15H9"
1655
+ ];
1656
+ var HEADING_CHECK_ICON = "M5 13L9 17L19 7";
1657
+ var headingLinkTimers = /* @__PURE__ */ new WeakMap();
1658
+ function createHeadingPixelIcon(doc, name, pathD) {
1659
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
1660
+ svg.setAttribute("viewBox", "0 0 24 24");
1661
+ svg.setAttribute("width", "16");
1662
+ svg.setAttribute("height", "16");
1663
+ svg.setAttribute("fill", "none");
1664
+ svg.setAttribute("stroke", "currentColor");
1665
+ svg.setAttribute("stroke-width", "1.5");
1666
+ svg.setAttribute("stroke-linecap", "round");
1667
+ svg.setAttribute("stroke-linejoin", "round");
1668
+ svg.setAttribute("aria-hidden", "true");
1669
+ svg.classList.add("bp-heading-link__icon", `bp-heading-link__icon--${name}`);
1670
+ for (const d of Array.isArray(pathD) ? pathD : [pathD]) {
1671
+ const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
1672
+ path.setAttribute("d", d);
1673
+ svg.append(path);
1674
+ }
1675
+ return svg;
1676
+ }
1677
+ function createHeadingLinkButton(doc, label2) {
1678
+ const button = doc.createElement("button");
1679
+ button.type = "button";
1680
+ button.className = "bp-heading-link";
1681
+ button.setAttribute("aria-label", `Copy link to ${label2}`);
1682
+ const icons = doc.createElement("span");
1683
+ icons.className = "bp-heading-link__icons";
1684
+ icons.append(
1685
+ createHeadingPixelIcon(doc, "link", HEADING_LINK_ICON),
1686
+ createHeadingPixelIcon(doc, "copy", HEADING_COPY_ICON),
1687
+ createHeadingPixelIcon(doc, "check", HEADING_CHECK_ICON)
1688
+ );
1689
+ button.append(icons);
1690
+ return button;
1691
+ }
1692
+ async function copySectionUrl(url) {
1693
+ if (!globalThis.isSecureContext || !navigator.clipboard?.writeText) {
1694
+ throw new Error("The Clipboard API requires a secure context");
1695
+ }
1696
+ await navigator.clipboard.writeText(url);
1697
+ }
1698
+ function showHeadingLinkState(button, state, label2) {
1699
+ const previousTimer = headingLinkTimers.get(button);
1700
+ if (previousTimer) clearTimeout(previousTimer);
1701
+ const heading = button.closest("h1, h2, h3, h4, h5, h6");
1702
+ if (state === "idle") {
1703
+ delete button.dataset.bpCopyState;
1704
+ button.setAttribute("aria-label", `Copy link to ${label2}`);
1705
+ headingLinkTimers.delete(button);
1706
+ if (heading && !heading.matches(":hover") && !heading.matches(":focus-within")) {
1707
+ delete heading.dataset.headingLink;
1708
+ }
1709
+ return;
1710
+ }
1711
+ button.dataset.bpCopyState = state;
1712
+ button.setAttribute(
1713
+ "aria-label",
1714
+ state === "copied" ? "Link copied" : "Copy failed"
1715
+ );
1716
+ if (heading) heading.dataset.headingLink = "visible";
1717
+ headingLinkTimers.set(
1718
+ button,
1719
+ setTimeout(() => showHeadingLinkState(button, "idle", label2), 1800)
1720
+ );
1721
+ }
1722
+ function wireFigureDiagramSize(doc) {
1723
+ for (const svg of doc.querySelectorAll("figure svg[viewBox]")) {
1724
+ const { width } = svg.viewBox.baseVal;
1725
+ if (width > 0) svg.style.setProperty("--bp-diagram-w", `${width}px`);
1726
+ }
1727
+ }
1728
+ var SIDEBAR_HEADING_SELECTOR = "h2[data-sidebar], h3[data-sidebar], h4[data-sidebar], h5[data-sidebar], h6[data-sidebar]";
1729
+ var NAV_CONTENT_ROOT = "main, [data-bp-nav-root]";
1730
+ function slugifyHeadingId(value) {
1731
+ return value.trim().toLowerCase().replace(/['']/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1732
+ }
1733
+ function isSectionLeadHeading(heading) {
1734
+ const section = heading.closest("section");
1735
+ if (!section) return false;
1736
+ return heading === section.querySelector(":scope > h2:first-child") || heading === section.querySelector(":scope > hgroup:first-child > h2");
1737
+ }
1738
+ function ensureUniqueHeadingId(doc, base, heading) {
1739
+ let candidate = base || "section";
1740
+ let suffix = 2;
1741
+ while (doc.getElementById(candidate) && doc.getElementById(candidate) !== heading) {
1742
+ candidate = `${base}-${suffix}`;
1743
+ suffix += 1;
1744
+ }
1745
+ return candidate;
1746
+ }
1747
+ function resolveHeadingId(doc, heading) {
1748
+ if (heading.id) return heading.id;
1749
+ const section = heading.closest("section[id]");
1750
+ if (section && isSectionLeadHeading(heading)) {
1751
+ return section.id;
1752
+ }
1753
+ const base = slugifyHeadingId(
1754
+ heading.getAttribute("data-sidebar") || heading.textContent || "section"
1755
+ );
1756
+ heading.id = ensureUniqueHeadingId(doc, base, heading);
1757
+ return heading.id;
1758
+ }
1759
+ function getSidebarLabel(heading) {
1760
+ return heading.getAttribute("data-sidebar")?.trim() || heading.textContent.trim();
1761
+ }
1762
+ function findNavMasthead(root) {
1763
+ return root.querySelector(":scope > header");
1764
+ }
1765
+ function getGroupLabel(header) {
1766
+ const explicit = header.getAttribute("data-sidebar")?.trim();
1767
+ if (explicit) return explicit;
1768
+ const heading = header.querySelector("h1, h2, h3, h4, h5, h6");
1769
+ if (heading?.textContent.trim()) return heading.textContent.trim();
1770
+ const eyebrow = header.querySelector("bp-eyebrow");
1771
+ if (eyebrow?.textContent.trim()) return eyebrow.textContent.trim();
1772
+ return header.textContent.trim() || "Section";
1773
+ }
1774
+ function resolveGroupId(doc, header) {
1775
+ if (header.id) return header.id;
1776
+ const base = slugifyHeadingId(getGroupLabel(header));
1777
+ header.id = ensureUniqueHeadingId(doc, base, header);
1778
+ return header.id;
1779
+ }
1780
+ function collectNavNodes(doc) {
1781
+ const root = doc.querySelector(NAV_CONTENT_ROOT) ?? doc.body;
1782
+ const masthead = findNavMasthead(root);
1783
+ const selector = `:scope > header, ${SIDEBAR_HEADING_SELECTOR}`;
1784
+ return [...root.querySelectorAll(selector)].filter((node) => node !== masthead);
1785
+ }
1786
+ function buildSidebarTree(nodes, doc) {
1787
+ const tree = [];
1788
+ let currentGroup = null;
1789
+ let currentPrimary = null;
1790
+ for (const node of nodes) {
1791
+ if (node.tagName === "HEADER") {
1792
+ currentGroup = {
1793
+ type: "group",
1794
+ label: getGroupLabel(node),
1795
+ id: resolveGroupId(doc, node),
1796
+ children: []
1797
+ };
1798
+ currentPrimary = null;
1799
+ tree.push(currentGroup);
1800
+ continue;
1801
+ }
1802
+ const level = Number(node.tagName.slice(1));
1803
+ const item = {
1804
+ type: "section",
1805
+ heading: node,
1806
+ label: getSidebarLabel(node),
1807
+ id: node.id || resolveHeadingId(doc, node),
1808
+ children: []
1809
+ };
1810
+ if (level === 2) {
1811
+ currentPrimary = item;
1812
+ (currentGroup ? currentGroup.children : tree).push(item);
1813
+ continue;
1814
+ }
1815
+ if (currentPrimary) {
1816
+ currentPrimary.children.push(item);
1817
+ continue;
1818
+ }
1819
+ ;
1820
+ (currentGroup ? currentGroup.children : tree).push(item);
1821
+ }
1822
+ return tree;
1823
+ }
1824
+ function createNavLink(doc, { id, label: label2 }) {
1825
+ const link = doc.createElement("a");
1826
+ link.href = `#${id}`;
1827
+ link.textContent = label2;
1828
+ return link;
1829
+ }
1830
+ function createNavGroupRow(doc, group, labelClass) {
1831
+ const li = doc.createElement("li");
1832
+ li.className = "bp-nav-group";
1833
+ const label2 = doc.createElement("span");
1834
+ label2.className = labelClass;
1835
+ label2.textContent = group.label;
1836
+ li.append(label2);
1837
+ return li;
1838
+ }
1839
+ function renderNavTree(doc, tree) {
1840
+ const fragment = doc.createDocumentFragment();
1841
+ const appendSection = (section) => {
1842
+ const li = doc.createElement("li");
1843
+ li.append(createNavLink(doc, section));
1844
+ if (section.children?.length > 0) {
1845
+ const subList = doc.createElement("ul");
1846
+ for (const child of section.children) {
1847
+ const subItem = doc.createElement("li");
1848
+ subItem.append(createNavLink(doc, child));
1849
+ subList.append(subItem);
1850
+ }
1851
+ li.append(subList);
1852
+ }
1853
+ fragment.append(li);
1854
+ };
1855
+ for (const item of tree) {
1856
+ if (item.type === "group") {
1857
+ fragment.append(createNavGroupRow(doc, item, "bp-nav-group__label"));
1858
+ item.children.forEach(appendSection);
1859
+ continue;
1860
+ }
1861
+ appendSection(item);
1862
+ }
1863
+ return fragment;
1864
+ }
1865
+ function renderContentsTree(doc, tree) {
1866
+ const fragment = doc.createDocumentFragment();
1867
+ const appendSection = (section) => {
1868
+ const li = doc.createElement("li");
1869
+ li.append(createNavLink(doc, section));
1870
+ fragment.append(li);
1871
+ };
1872
+ for (const item of tree) {
1873
+ if (item.type === "group") {
1874
+ const li = createNavGroupRow(doc, item, "bp-contents-group__label");
1875
+ li.className = "bp-contents-group";
1876
+ fragment.append(li);
1877
+ item.children.forEach(appendSection);
1878
+ continue;
1879
+ }
1880
+ appendSection(item);
1881
+ }
1882
+ return fragment;
1883
+ }
1884
+ function findSidebarNavLists(doc) {
1885
+ const lists = [];
1886
+ for (const nav of doc.querySelectorAll(".bp-sidebar:not([data-bp-nav-manual])")) {
1887
+ const list = nav.querySelector(":scope > .bp-sidebar__panel .bp-toc-switcher > ul") ?? nav.querySelector(":scope > .bp-sidebar__panel > ul") ?? nav.querySelector(":scope > ul");
1888
+ if (list) lists.push(list);
1889
+ }
1890
+ for (const nav of doc.querySelectorAll(".bp-toc:not([data-bp-nav-manual])")) {
1891
+ const list = nav.querySelector(":scope > ul");
1892
+ if (list) lists.push(list);
1893
+ }
1894
+ return lists;
1895
+ }
1896
+ function ensureDocumentNavTree(doc) {
1897
+ const nodes = collectNavNodes(doc);
1898
+ if (nodes.length === 0) return [];
1899
+ for (const node of nodes) {
1900
+ if (node.tagName === "HEADER") resolveGroupId(doc, node);
1901
+ else resolveHeadingId(doc, node);
1902
+ }
1903
+ return buildSidebarTree(nodes, doc);
1904
+ }
1905
+ function populateBpTocElement(element, doc, tree) {
1906
+ if (element.dataset.bpTocReady === "true") return;
1907
+ if (!tree?.length) return;
1908
+ element.dataset.bpTocReady = "true";
1909
+ const label2 = element.getAttribute("label")?.trim() || element.textContent.trim() || "Contents";
1910
+ const ariaLabel = element.getAttribute("aria-label")?.trim() || label2;
1911
+ const nav = doc.createElement("nav");
1912
+ nav.className = "bp-contents";
1913
+ nav.setAttribute("aria-label", ariaLabel);
1914
+ const eyebrow = doc.createElement("p");
1915
+ eyebrow.className = "bp-eyebrow";
1916
+ eyebrow.textContent = label2;
1917
+ const list = doc.createElement("ol");
1918
+ list.append(renderContentsTree(doc, tree));
1919
+ nav.append(eyebrow, list);
1920
+ element.replaceChildren(nav);
1921
+ }
1922
+ function populateAllBpToc(doc, tree) {
1923
+ for (const element of doc.querySelectorAll("bp-toc")) {
1924
+ populateBpTocElement(element, doc, tree);
1925
+ }
1926
+ }
1927
+ function generateNavigationFromHeadings(doc, tree = ensureDocumentNavTree(doc)) {
1928
+ if (tree.length === 0) return;
1929
+ const navFragment = renderNavTree(doc, tree);
1930
+ for (const list of findSidebarNavLists(doc)) {
1931
+ list.replaceChildren(navFragment.cloneNode(true));
1932
+ }
1933
+ populateAllBpToc(doc, tree);
1934
+ }
1935
+ function formatHeadingEyebrow(number) {
1936
+ return String(number).padStart(2, "0");
1937
+ }
1938
+ function wireSectionHeadingLinks(doc, win = doc.defaultView) {
1939
+ const root = doc.querySelector(NAV_CONTENT_ROOT) ?? doc.body;
1940
+ let primaryNumber = 0;
1941
+ for (const heading of root.querySelectorAll(SIDEBAR_HEADING_SELECTOR)) {
1942
+ if (heading.querySelector(":scope > .bp-heading-row")) continue;
1943
+ const level = Number(heading.tagName.slice(1));
1944
+ const label2 = getSidebarLabel(heading);
1945
+ const titleNodes = [...heading.childNodes];
1946
+ const id = resolveHeadingId(doc, heading);
1947
+ heading.replaceChildren();
1948
+ if (level === 2) {
1949
+ primaryNumber += 1;
1950
+ const eyebrow = doc.createElement("span");
1951
+ eyebrow.className = "bp-heading-eyebrow";
1952
+ eyebrow.textContent = formatHeadingEyebrow(primaryNumber);
1953
+ heading.append(eyebrow);
1954
+ }
1955
+ const row = doc.createElement("span");
1956
+ row.className = "bp-heading-row";
1957
+ const button = createHeadingLinkButton(doc, label2);
1958
+ const title = doc.createElement("span");
1959
+ title.className = "bp-heading-title";
1960
+ title.append(...titleNodes);
1961
+ row.append(button, title);
1962
+ heading.append(row);
1963
+ let hideTimer = 0;
1964
+ const showLink = () => {
1965
+ win.clearTimeout(hideTimer);
1966
+ heading.dataset.headingLink = "visible";
1967
+ };
1968
+ const scheduleHide = (event) => {
1969
+ const next = event?.relatedTarget;
1970
+ if (next instanceof Node && heading.contains(next)) return;
1971
+ hideTimer = win.setTimeout(() => {
1972
+ if (button.dataset.bpCopyState) return;
1973
+ if (heading.matches(":hover") || button.matches(":hover")) return;
1974
+ delete heading.dataset.headingLink;
1975
+ }, 120);
1976
+ };
1977
+ heading.addEventListener("mouseenter", showLink);
1978
+ heading.addEventListener("mouseleave", scheduleHide);
1979
+ button.addEventListener("mouseenter", showLink);
1980
+ button.addEventListener("mouseleave", scheduleHide);
1981
+ heading.addEventListener("focusin", showLink);
1982
+ heading.addEventListener("focusout", (event) => {
1983
+ const next = event.relatedTarget;
1984
+ if (next instanceof Node && heading.contains(next)) return;
1985
+ if (!button.dataset.bpCopyState) scheduleHide(event);
1986
+ });
1987
+ button.addEventListener("click", (event) => {
1988
+ event.preventDefault();
1989
+ const url = new URL(`#${id}`, win?.location.href ?? "").href;
1990
+ copySectionUrl(url).then(() => showHeadingLinkState(button, "copied", label2)).catch(() => showHeadingLinkState(button, "error", label2));
1991
+ });
1992
+ }
1993
+ }
1994
+ var CALLOUT_ICONS = {
1995
+ locked: "M5 8h14v2H5zm0 12h14v2H5zM3 10h2v10H3zm16 0h2v10h-2zM7 4h2v4H7zm2-2h6v2H9zm6 2h2v4h-2z",
1996
+ invariant: "M4 2h16v2H4zM2 4h2v10H2zm18 0h2v10h-2zM4 14h2v2H4zm2 2h2v2H6zm4 4h4v2h-4zm10-6h-2v2h2zm-2 2h-2v2h2zm-2 2h-2v2h2zm-6 0H8v2h2z",
1997
+ ref: "M4 2h16v2H4zm2 5h2v2H6zm4 0h8v2h-8zm-4 4h2v2H6zm4 0h8v2h-8zm-4 4h2v2H6zm4 0h8v2h-8zm-6 5h16v2H4zM2 4h2v16H2zm18 0h2v16h-2z"
1998
+ };
1999
+ var CALLOUT_TYPES = {
2000
+ locked: { mod: "locked", label: "Locked", icon: "locked" },
2001
+ invariant: { mod: "invariant", label: "Invariant", icon: "invariant" },
2002
+ ref: { mod: "ref", label: "Reference", icon: "ref" },
2003
+ reference: { mod: "ref", label: "Reference", icon: "ref" }
2004
+ };
2005
+ var CALLOUT_BLOCK = /* @__PURE__ */ new Set([
2006
+ "P",
2007
+ "UL",
2008
+ "OL",
2009
+ "DL",
2010
+ "DIV",
2011
+ "PRE",
2012
+ "BLOCKQUOTE",
2013
+ "TABLE",
2014
+ "FIGURE",
2015
+ "SECTION",
2016
+ "H1",
2017
+ "H2",
2018
+ "H3",
2019
+ "H4",
2020
+ "H5",
2021
+ "H6",
2022
+ "BP-SOURCE",
2023
+ "BP-CITE"
2024
+ ]);
2025
+ function createCalloutIcon(doc, pathD) {
2026
+ const svg = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
2027
+ svg.setAttribute("viewBox", "0 0 24 24");
2028
+ svg.setAttribute("fill", "currentColor");
2029
+ svg.setAttribute("aria-hidden", "true");
2030
+ const path = doc.createElementNS("http://www.w3.org/2000/svg", "path");
2031
+ path.setAttribute("d", pathD);
2032
+ svg.append(path);
2033
+ return svg;
2034
+ }
2035
+ var BlueprintTocElement = class extends HTMLElement {
2036
+ connectedCallback() {
2037
+ const doc = this.ownerDocument;
2038
+ if (doc.documentElement.dataset.blueprintRuntime !== "ready") return;
2039
+ populateBpTocElement(this, doc, ensureDocumentNavTree(doc));
2040
+ }
2041
+ };
2042
+ var BlueprintCalloutElement = class extends HTMLElement {
2043
+ #rendered = false;
2044
+ connectedCallback() {
2045
+ if (this.#rendered) return;
2046
+ this.#rendered = true;
2047
+ const doc = this.ownerDocument;
2048
+ const key = (this.getAttribute("type") || "locked").toLowerCase();
2049
+ const spec = CALLOUT_TYPES[key] ?? CALLOUT_TYPES.locked;
2050
+ const label2 = this.getAttribute("label") ?? spec.label;
2051
+ const aside = doc.createElement("aside");
2052
+ aside.className = `bp-callout bp-callout--${spec.mod}`;
2053
+ const tag = doc.createElement("span");
2054
+ tag.className = "bp-ctag";
2055
+ const iconPath = CALLOUT_ICONS[spec.icon];
2056
+ if (iconPath && this.getAttribute("icon") !== "none") {
2057
+ tag.append(createCalloutIcon(doc, iconPath));
2058
+ }
2059
+ if (label2) tag.append(doc.createTextNode(label2));
2060
+ aside.append(tag);
2061
+ const body = [...this.childNodes];
2062
+ const hasBlock = body.some(
2063
+ (node) => node.nodeType === 1 && CALLOUT_BLOCK.has(node.tagName)
2064
+ );
2065
+ if (hasBlock) {
2066
+ aside.append(...body);
2067
+ } else {
2068
+ const p = doc.createElement("p");
2069
+ p.append(...body);
2070
+ if (p.childNodes.length) aside.append(p);
2071
+ }
2072
+ this.replaceChildren(aside);
2073
+ }
2074
+ };
2075
+ var MOCK_EXPAND_ICON = [
2076
+ "M9 9L4 4M4 4V8M4 4H8",
2077
+ "M15 9L20 4M20 4V8M20 4H16",
2078
+ "M9 15L4 20M4 20V16M4 20H8",
2079
+ "M15 15L20 20M20 20V16M20 20H16"
2080
+ ];
2081
+ var MOCK_COLLAPSE_ICON = [
2082
+ "M20 20L15 15M15 15V19M15 15H19",
2083
+ "M4 20L9 15M9 15V19M9 15H5",
2084
+ "M20 4L15 9M15 9V5M15 9H19",
2085
+ "M4 4L9 9M9 9V5M9 9H5"
2086
+ ];
2087
+ var BlueprintMockElement = class extends HTMLElement {
2088
+ #rendered = false;
2089
+ #ro = null;
2090
+ #wrap = null;
2091
+ #stage = null;
2092
+ #canvas = null;
2093
+ #btn = null;
2094
+ #naturalWidth = 0;
2095
+ #onKeydown = null;
2096
+ #returnFocus = null;
2097
+ #prevOverflow = "";
2098
+ connectedCallback() {
2099
+ if (this.#rendered) return;
2100
+ this.#rendered = true;
2101
+ this.dataset.bpRendered = "1";
2102
+ const doc = this.ownerDocument;
2103
+ const label2 = this.getAttribute("label") || "";
2104
+ const vw = Number.parseInt(this.getAttribute("viewport") || "", 10);
2105
+ this.#naturalWidth = Number.isFinite(vw) && vw > 0 ? vw : 0;
2106
+ const expandable = this.getAttribute("expandable") !== "false";
2107
+ const wrap = doc.createElement("div");
2108
+ wrap.className = "bp-mock";
2109
+ wrap.setAttribute("role", "group");
2110
+ if (label2) wrap.setAttribute("aria-label", `Mockup: ${label2}`);
2111
+ const frame = doc.createElement("div");
2112
+ frame.className = "bp-mock__frame";
2113
+ const bar = doc.createElement("div");
2114
+ bar.className = "bp-mock__bar";
2115
+ const ticks = doc.createElement("span");
2116
+ ticks.className = "bp-mock__ticks";
2117
+ ticks.setAttribute("aria-hidden", "true");
2118
+ ticks.append(doc.createElement("i"), doc.createElement("i"), doc.createElement("i"));
2119
+ bar.append(ticks);
2120
+ const cap = doc.createElement("span");
2121
+ cap.className = "bp-mock__label";
2122
+ cap.textContent = label2;
2123
+ bar.append(cap);
2124
+ if (expandable) {
2125
+ const btn = doc.createElement("button");
2126
+ btn.type = "button";
2127
+ btn.className = "bp-mock__btn";
2128
+ btn.setAttribute("aria-expanded", "false");
2129
+ btn.setAttribute("aria-label", "Expand mockup");
2130
+ btn.append(createChromeSvg(doc, MOCK_EXPAND_ICON, 14));
2131
+ btn.addEventListener("click", () => this.#toggle());
2132
+ bar.append(btn);
2133
+ this.#btn = btn;
2134
+ }
2135
+ const stage = doc.createElement("div");
2136
+ stage.className = "bp-mock__stage";
2137
+ const canvas = doc.createElement("div");
2138
+ canvas.className = "bp-mock__canvas";
2139
+ if (this.#naturalWidth) canvas.style.width = `${this.#naturalWidth}px`;
2140
+ canvas.append(...this.childNodes);
2141
+ stage.append(canvas);
2142
+ frame.append(bar, stage);
2143
+ wrap.append(frame);
2144
+ this.replaceChildren(wrap);
2145
+ this.#wrap = wrap;
2146
+ this.#stage = stage;
2147
+ this.#canvas = canvas;
2148
+ if (this.#naturalWidth && typeof ResizeObserver !== "undefined") {
2149
+ this.#ro = new ResizeObserver(() => this.#relayout());
2150
+ this.#ro.observe(stage);
2151
+ this.#ro.observe(canvas);
2152
+ this.#relayout();
2153
+ }
2154
+ }
2155
+ disconnectedCallback() {
2156
+ this.#ro?.disconnect();
2157
+ if (this.#onKeydown) this.ownerDocument.removeEventListener("keydown", this.#onKeydown);
2158
+ }
2159
+ // Scale the fixed-width canvas down to whatever width the stage offers,
2160
+ // then collapse the stage to the scaled height so there is no dead space.
2161
+ #relayout() {
2162
+ if (!this.#naturalWidth || !this.#stage) return;
2163
+ const avail = this.#stage.clientWidth;
2164
+ if (!avail) return;
2165
+ const scale = Math.min(1, avail / this.#naturalWidth);
2166
+ this.#canvas.style.setProperty("--bp-mock-scale", String(scale));
2167
+ this.#stage.style.height = `${Math.round(this.#canvas.offsetHeight * scale)}px`;
2168
+ }
2169
+ #toggle() {
2170
+ const doc = this.ownerDocument;
2171
+ const expanded = this.#wrap.classList.toggle("is-expanded");
2172
+ if (this.#btn) {
2173
+ this.#btn.setAttribute("aria-expanded", String(expanded));
2174
+ this.#btn.setAttribute("aria-label", expanded ? "Collapse mockup" : "Expand mockup");
2175
+ this.#btn.replaceChildren(
2176
+ createChromeSvg(doc, expanded ? MOCK_COLLAPSE_ICON : MOCK_EXPAND_ICON, 14)
2177
+ );
2178
+ }
2179
+ if (expanded) {
2180
+ this.#returnFocus = doc.activeElement;
2181
+ this.#prevOverflow = doc.documentElement.style.overflow;
2182
+ doc.documentElement.style.overflow = "hidden";
2183
+ this.#onKeydown = (event) => {
2184
+ if (event.key === "Escape") this.#toggle();
2185
+ };
2186
+ doc.addEventListener("keydown", this.#onKeydown);
2187
+ this.#btn?.focus();
2188
+ } else {
2189
+ doc.documentElement.style.overflow = this.#prevOverflow ?? "";
2190
+ if (this.#onKeydown) {
2191
+ doc.removeEventListener("keydown", this.#onKeydown);
2192
+ this.#onKeydown = null;
2193
+ }
2194
+ if (this.#returnFocus && typeof this.#returnFocus.focus === "function") {
2195
+ this.#returnFocus.focus();
2196
+ }
2197
+ }
2198
+ if (typeof requestAnimationFrame === "function") {
2199
+ requestAnimationFrame(() => this.#relayout());
2200
+ } else {
2201
+ this.#relayout();
2202
+ }
2203
+ }
2204
+ };
2205
+ var GALLERY_ZOOM_ICON = [
2206
+ "M8 11H11M14 11H11M11 11V8M11 11V14",
2207
+ "M17 17L21 21",
2208
+ "M3 11C3 15.4183 6.58172 19 11 19C13.213 19 15.2161 18.1015 16.6644 16.6493C18.1077 15.2022 19 13.2053 19 11C19 6.58172 15.4183 3 11 3C6.58172 3 3 6.58172 3 11Z"
2209
+ ];
2210
+ var GALLERY_CLOSE_ICON = "M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426";
2211
+ var GALLERY_PREV_ICON = "M15 6L9 12L15 18";
2212
+ var GALLERY_NEXT_ICON = "M9 6L15 12L9 18";
2213
+ var BlueprintGalleryElement = class extends HTMLElement {
2214
+ #rendered = false;
2215
+ /** @type {{ src: string, alt: string, caption: string }[]} */
2216
+ #slides = [];
2217
+ #label = "";
2218
+ #box = null;
2219
+ #boxImg = null;
2220
+ #boxCaption = null;
2221
+ #boxCounter = null;
2222
+ #boxKicker = null;
2223
+ #index = 0;
2224
+ #onKeydown = null;
2225
+ #returnFocus = null;
2226
+ #prevOverflow = "";
2227
+ connectedCallback() {
2228
+ if (this.#rendered) return;
2229
+ this.#rendered = true;
2230
+ this.dataset.bpRendered = "1";
2231
+ const doc = this.ownerDocument;
2232
+ this.#label = this.getAttribute("label") || "";
2233
+ for (const img of this.querySelectorAll("img")) {
2234
+ const figure = img.closest("figure");
2235
+ const cap = figure?.querySelector("figcaption");
2236
+ const caption = (cap?.textContent || img.getAttribute("alt") || "").trim();
2237
+ this.#slides.push({
2238
+ src: img.getAttribute("src") || "",
2239
+ alt: (img.getAttribute("alt") || caption || "").trim(),
2240
+ caption
2241
+ });
2242
+ }
2243
+ const wrap = doc.createElement("div");
2244
+ wrap.className = "bp-gallery";
2245
+ wrap.setAttribute("role", "group");
2246
+ if (this.#label) wrap.setAttribute("aria-label", `Gallery: ${this.#label}`);
2247
+ const grid = doc.createElement("div");
2248
+ grid.className = "bp-gallery__grid";
2249
+ this.#slides.forEach((slide, index) => {
2250
+ const item = doc.createElement("button");
2251
+ item.type = "button";
2252
+ item.className = "bp-gallery__item";
2253
+ item.setAttribute(
2254
+ "aria-label",
2255
+ slide.caption ? `View image: ${slide.caption}` : `View image ${index + 1}`
2256
+ );
2257
+ item.addEventListener("click", () => this.#open(index));
2258
+ const thumb = doc.createElement("span");
2259
+ thumb.className = "bp-gallery__thumb";
2260
+ const img = doc.createElement("img");
2261
+ img.className = "bp-gallery__img";
2262
+ img.src = slide.src;
2263
+ img.alt = slide.alt;
2264
+ img.loading = "lazy";
2265
+ img.decoding = "async";
2266
+ const wash = doc.createElement("span");
2267
+ wash.className = "bp-gallery__wash";
2268
+ wash.setAttribute("aria-hidden", "true");
2269
+ const zoom = doc.createElement("span");
2270
+ zoom.className = "bp-gallery__zoom";
2271
+ zoom.setAttribute("aria-hidden", "true");
2272
+ zoom.append(createChromeSvg(doc, GALLERY_ZOOM_ICON, 16));
2273
+ thumb.append(img, wash, zoom);
2274
+ item.append(thumb);
2275
+ if (slide.caption) {
2276
+ const cap = doc.createElement("span");
2277
+ cap.className = "bp-gallery__caption";
2278
+ cap.textContent = slide.caption;
2279
+ item.append(cap);
2280
+ }
2281
+ grid.append(item);
2282
+ });
2283
+ wrap.append(grid);
2284
+ this.replaceChildren(wrap);
2285
+ }
2286
+ disconnectedCallback() {
2287
+ if (this.#onKeydown) this.ownerDocument.removeEventListener("keydown", this.#onKeydown);
2288
+ this.#box?.remove();
2289
+ this.#box = null;
2290
+ }
2291
+ #ensureBox() {
2292
+ if (this.#box) return;
2293
+ const doc = this.ownerDocument;
2294
+ const box = doc.createElement("div");
2295
+ box.className = "bp-lightbox";
2296
+ box.setAttribute("role", "dialog");
2297
+ box.setAttribute("aria-modal", "true");
2298
+ box.setAttribute("aria-label", this.#label ? `${this.#label} — image viewer` : "Image viewer");
2299
+ box.addEventListener("click", (event) => {
2300
+ if (event.target === box) this.#close();
2301
+ });
2302
+ const close = doc.createElement("button");
2303
+ close.type = "button";
2304
+ close.className = "bp-lightbox__close";
2305
+ close.setAttribute("aria-label", "Close image viewer");
2306
+ close.append(createChromeSvg(doc, GALLERY_CLOSE_ICON, 18));
2307
+ close.addEventListener("click", () => this.#close());
2308
+ const prev = doc.createElement("button");
2309
+ prev.type = "button";
2310
+ prev.className = "bp-lightbox__nav bp-lightbox__nav--prev";
2311
+ prev.setAttribute("aria-label", "Previous image");
2312
+ prev.append(createChromeSvg(doc, GALLERY_PREV_ICON, 22));
2313
+ prev.addEventListener("click", () => this.#step(-1));
2314
+ const next = doc.createElement("button");
2315
+ next.type = "button";
2316
+ next.className = "bp-lightbox__nav bp-lightbox__nav--next";
2317
+ next.setAttribute("aria-label", "Next image");
2318
+ next.append(createChromeSvg(doc, GALLERY_NEXT_ICON, 22));
2319
+ next.addEventListener("click", () => this.#step(1));
2320
+ const figure = doc.createElement("figure");
2321
+ figure.className = "bp-lightbox__figure";
2322
+ const img = doc.createElement("img");
2323
+ img.className = "bp-lightbox__img";
2324
+ img.alt = "";
2325
+ const meta = doc.createElement("figcaption");
2326
+ meta.className = "bp-lightbox__meta";
2327
+ const kicker = doc.createElement("span");
2328
+ kicker.className = "bp-lightbox__kicker";
2329
+ kicker.textContent = this.#label;
2330
+ const counter = doc.createElement("span");
2331
+ counter.className = "bp-lightbox__counter";
2332
+ const caption = doc.createElement("span");
2333
+ caption.className = "bp-lightbox__caption";
2334
+ meta.append(kicker, counter, caption);
2335
+ figure.append(img, meta);
2336
+ box.append(close, prev, figure, next);
2337
+ const host = doc.body || doc.documentElement;
2338
+ host.append(box);
2339
+ this.#box = box;
2340
+ this.#boxImg = img;
2341
+ this.#boxCaption = caption;
2342
+ this.#boxCounter = counter;
2343
+ this.#boxKicker = kicker;
2344
+ }
2345
+ #render() {
2346
+ const slide = this.#slides[this.#index];
2347
+ if (!slide) return;
2348
+ this.#boxImg.src = slide.src;
2349
+ this.#boxImg.alt = slide.alt;
2350
+ this.#boxCaption.textContent = slide.caption;
2351
+ this.#boxCaption.hidden = !slide.caption;
2352
+ this.#boxKicker.hidden = !this.#label;
2353
+ const many = this.#slides.length > 1;
2354
+ this.#boxCounter.textContent = many ? `${this.#index + 1} / ${this.#slides.length}` : "";
2355
+ this.#boxCounter.hidden = !many;
2356
+ this.#box.classList.toggle("is-single", !many);
2357
+ }
2358
+ #open(index) {
2359
+ if (!this.#slides.length) return;
2360
+ this.#ensureBox();
2361
+ this.#index = index;
2362
+ this.#render();
2363
+ const doc = this.ownerDocument;
2364
+ this.#returnFocus = doc.activeElement;
2365
+ this.#prevOverflow = doc.documentElement.style.overflow;
2366
+ doc.documentElement.style.overflow = "hidden";
2367
+ this.#box.classList.add("is-open");
2368
+ this.#onKeydown = (event) => {
2369
+ if (event.key === "Escape") this.#close();
2370
+ else if (event.key === "ArrowRight") this.#step(1);
2371
+ else if (event.key === "ArrowLeft") this.#step(-1);
2372
+ };
2373
+ doc.addEventListener("keydown", this.#onKeydown);
2374
+ this.#box.querySelector(".bp-lightbox__close")?.focus();
2375
+ }
2376
+ #step(delta) {
2377
+ if (this.#slides.length < 2) return;
2378
+ const count = this.#slides.length;
2379
+ this.#index = (this.#index + delta + count) % count;
2380
+ this.#render();
2381
+ }
2382
+ #close() {
2383
+ if (!this.#box) return;
2384
+ const doc = this.ownerDocument;
2385
+ this.#box.classList.remove("is-open");
2386
+ doc.documentElement.style.overflow = this.#prevOverflow ?? "";
2387
+ if (this.#onKeydown) {
2388
+ doc.removeEventListener("keydown", this.#onKeydown);
2389
+ this.#onKeydown = null;
2390
+ }
2391
+ if (this.#returnFocus && typeof this.#returnFocus.focus === "function") {
2392
+ this.#returnFocus.focus();
2393
+ }
2394
+ }
2395
+ };
2396
+ var BlueprintSubheaderElement = class extends HTMLElement {
2397
+ };
2398
+ var BlueprintNoteElement = class extends HTMLElement {
2399
+ };
2400
+ var BlueprintEyebrowElement = class extends HTMLElement {
2401
+ };
2402
+ var TABLE_NUMERIC = /^[+-]?[$€£¥]?\s?\d[\d,]*(\.\d+)?\s?%?$/;
2403
+ var TABLE_BLANK = /^(—|–|-|n\/?a)$/i;
2404
+ var BlueprintTableElement = class extends HTMLElement {
2405
+ static get observedAttributes() {
2406
+ return ["numeric"];
2407
+ }
2408
+ #observer = null;
2409
+ #applying = false;
2410
+ connectedCallback() {
2411
+ this.#ensureScrollShell();
2412
+ this.#apply();
2413
+ if (typeof MutationObserver !== "undefined" && !this.#observer) {
2414
+ this.#observer = new MutationObserver(() => {
2415
+ if (!this.#applying) this.#apply();
2416
+ });
2417
+ this.#observer.observe(this, { childList: true, subtree: true, characterData: true });
2418
+ }
2419
+ }
2420
+ disconnectedCallback() {
2421
+ this.#observer?.disconnect();
2422
+ this.#observer = null;
2423
+ }
2424
+ attributeChangedCallback() {
2425
+ if (this.isConnected) {
2426
+ this.#ensureScrollShell();
2427
+ this.#apply();
2428
+ }
2429
+ }
2430
+ // Wrap the author's <table> in a horizontal scroll shell. Sticky header
2431
+ // cells must stick to the page scrollport, not to <bp-table> — so overflow-x
2432
+ // lives here, on .bp-table-x, instead of on the host.
2433
+ #ensureScrollShell() {
2434
+ const table = this.querySelector(":scope > table");
2435
+ if (!table) return;
2436
+ let shell = this.querySelector(":scope > .bp-table-x");
2437
+ if (!shell) {
2438
+ shell = document.createElement("div");
2439
+ shell.className = "bp-table-x";
2440
+ this.insertBefore(shell, table);
2441
+ }
2442
+ if (table.parentElement !== shell) shell.append(table);
2443
+ }
2444
+ // Resolve which 1-based columns end-align, then stamp .bp-col-num on the
2445
+ // matching cells across thead / tbody / tfoot.
2446
+ #apply() {
2447
+ const table = this.querySelector("table");
2448
+ if (!table) return;
2449
+ this.#applying = true;
2450
+ try {
2451
+ for (const el2 of this.querySelectorAll(".bp-col-num")) {
2452
+ el2.classList.remove("bp-col-num");
2453
+ }
2454
+ const attr = (this.getAttribute("numeric") || "").trim().toLowerCase();
2455
+ if (attr === "off") return;
2456
+ let columns;
2457
+ if (attr) {
2458
+ columns = new Set(
2459
+ attr.split(",").map((n) => Number.parseInt(n, 10)).filter((n) => n > 0)
2460
+ );
2461
+ if (!columns.size) columns = this.#detect(table);
2462
+ } else {
2463
+ columns = this.#detect(table);
2464
+ }
2465
+ if (columns.size) this.#mark(table, columns);
2466
+ } finally {
2467
+ this.#applying = false;
2468
+ }
2469
+ }
2470
+ // A column is numeric when every non-blank <td> in it parses as a number.
2471
+ // <th> cells (the key column) are skipped but still advance the column
2472
+ // index so it stays aligned with the header row.
2473
+ #detect(table) {
2474
+ const tally = /* @__PURE__ */ new Map();
2475
+ for (const body of table.tBodies) {
2476
+ for (const row of body.rows) {
2477
+ let col = 0;
2478
+ for (const cell of row.cells) {
2479
+ const span = cell.colSpan || 1;
2480
+ col += 1;
2481
+ if (cell.tagName === "TH") {
2482
+ col += span - 1;
2483
+ continue;
2484
+ }
2485
+ const text = cell.textContent.trim();
2486
+ if (!text || TABLE_BLANK.test(text)) continue;
2487
+ const rec = tally.get(col) || { total: 0, numeric: 0 };
2488
+ rec.total += 1;
2489
+ if (TABLE_NUMERIC.test(text)) rec.numeric += 1;
2490
+ tally.set(col, rec);
2491
+ col += span - 1;
2492
+ }
2493
+ }
2494
+ }
2495
+ const columns = /* @__PURE__ */ new Set();
2496
+ for (const [col, rec] of tally) {
2497
+ if (rec.total > 0 && rec.numeric === rec.total) columns.add(col);
2498
+ }
2499
+ return columns;
2500
+ }
2501
+ // Stamp .bp-col-num on every cell whose 1-based column index is numeric.
2502
+ #mark(table, columns) {
2503
+ const stamp = (rows) => {
2504
+ for (const row of rows) {
2505
+ let col = 0;
2506
+ for (const cell of row.cells) {
2507
+ const span = cell.colSpan || 1;
2508
+ col += 1;
2509
+ for (let i = 0; i < span; i += 1) {
2510
+ if (columns.has(col + i)) {
2511
+ cell.classList.add("bp-col-num");
2512
+ break;
2513
+ }
2514
+ }
2515
+ col += span - 1;
2516
+ }
2517
+ }
2518
+ };
2519
+ if (table.tHead) stamp(table.tHead.rows);
2520
+ for (const body of table.tBodies) stamp(body.rows);
2521
+ if (table.tFoot) stamp(table.tFoot.rows);
2522
+ }
2523
+ };
2524
+ if (typeof customElements !== "undefined" && !customElements.get("bp-toc")) {
2525
+ customElements.define("bp-toc", BlueprintTocElement);
2526
+ }
2527
+ if (typeof customElements !== "undefined" && !customElements.get("bp-callout")) {
2528
+ customElements.define("bp-callout", BlueprintCalloutElement);
2529
+ }
2530
+ if (typeof customElements !== "undefined" && !customElements.get("bp-mock")) {
2531
+ customElements.define("bp-mock", BlueprintMockElement);
2532
+ }
2533
+ if (typeof customElements !== "undefined" && !customElements.get("bp-gallery")) {
2534
+ customElements.define("bp-gallery", BlueprintGalleryElement);
2535
+ }
2536
+ if (typeof customElements !== "undefined" && !customElements.get("bp-table")) {
2537
+ customElements.define("bp-table", BlueprintTableElement);
2538
+ }
2539
+ if (typeof customElements !== "undefined") {
2540
+ for (const [name, ctor] of [
2541
+ ["bp-subheader", BlueprintSubheaderElement],
2542
+ ["bp-note", BlueprintNoteElement],
2543
+ ["bp-eyebrow", BlueprintEyebrowElement]
2544
+ ]) {
2545
+ if (!customElements.get(name)) customElements.define(name, ctor);
2546
+ }
2547
+ }
2548
+ (() => {
2549
+ const VIEW_OPTIONS = [
2550
+ ["list", "List"],
2551
+ ["gantt", "Gantt"],
2552
+ ["kanban", "Kanban"],
2553
+ ["swimlanes", "Swimlanes"]
2554
+ ];
2555
+ function derive(plan) {
2556
+ const byId = new Map(plan.tasks.map((t) => [t.id, t]));
2557
+ const waveMemo = /* @__PURE__ */ new Map();
2558
+ const startMemo = /* @__PURE__ */ new Map();
2559
+ const wave = (id, trail = /* @__PURE__ */ new Set()) => {
2560
+ if (waveMemo.has(id)) return waveMemo.get(id);
2561
+ const deps = byId.get(id)?.dependsOn ?? [];
2562
+ let w = 0;
2563
+ for (const d of deps) {
2564
+ if (!byId.has(d) || trail.has(d)) continue;
2565
+ w = Math.max(w, wave(d, /* @__PURE__ */ new Set([...trail, id])) + 1);
2566
+ }
2567
+ waveMemo.set(id, w);
2568
+ return w;
2569
+ };
2570
+ const start = (id, trail = /* @__PURE__ */ new Set()) => {
2571
+ if (startMemo.has(id)) return startMemo.get(id);
2572
+ const deps = byId.get(id)?.dependsOn ?? [];
2573
+ let s = 0;
2574
+ for (const d of deps) {
2575
+ const dep = byId.get(d);
2576
+ if (!dep || trail.has(d)) continue;
2577
+ s = Math.max(s, start(d, /* @__PURE__ */ new Set([...trail, id])) + (dep.effort ?? 1));
2578
+ }
2579
+ startMemo.set(id, s);
2580
+ return s;
2581
+ };
2582
+ return plan.tasks.map((t) => {
2583
+ const deps = t.dependsOn ?? [];
2584
+ const blockedBy = deps.filter((d) => byId.get(d)?.status !== "done");
2585
+ const ready = blockedBy.length === 0;
2586
+ const s = start(t.id);
2587
+ return {
2588
+ ...t,
2589
+ wave: wave(t.id),
2590
+ ready,
2591
+ locked: !ready && t.status !== "done",
2592
+ start: s,
2593
+ finish: s + (t.effort ?? 1),
2594
+ blockedBy
2595
+ };
2596
+ });
2597
+ }
2598
+ function phase(t) {
2599
+ if (t.status === "done") return "done";
2600
+ if (t.locked) return "blocked";
2601
+ if (t.status === "active") return "active";
2602
+ return "ready";
2603
+ }
2604
+ const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
2605
+ const TYPE_LABEL = {
2606
+ research: "Research",
2607
+ write: "Write",
2608
+ review: "Review",
2609
+ design: "Design",
2610
+ code: "Code"
2611
+ };
2612
+ const CI_LABEL = {
2613
+ running: "Tests running",
2614
+ failed: "CI failed",
2615
+ passed: "CI passed",
2616
+ approved: "Approved"
2617
+ };
2618
+ const UNIT_SUFFIX = { hours: "h", days: "d" };
2619
+ const UNIT_AXIS = { hours: "Hours", days: "Days" };
2620
+ const TYPE_ICON = {
2621
+ research: '<circle cx="7" cy="7" r="4.2"/><path d="M10 10l4 4"/>',
2622
+ write: '<path d="M3 13l8.5-8.5 2.5 2.5L5.5 15.5 2.5 16z"/><path d="M11 5l2.5 2.5"/>',
2623
+ review: '<path d="M2 8s2.4-4 6-4 6 4 6 4-2.4 4-6 4-6-4-6-4z"/><circle cx="8" cy="8" r="1.8"/>',
2624
+ design: '<path d="M8 2v12"/><path d="M8 2l4.5 10.5a5 5 0 01-9 0z"/>',
2625
+ code: '<path d="M6 4L2 8l4 4"/><path d="M10 4l4 4-4 4"/>'
2626
+ };
2627
+ const CI_ICON = {
2628
+ running: '<circle cx="8" cy="8" r="5.4" stroke-dasharray="3 3"/>',
2629
+ failed: '<circle cx="8" cy="8" r="6"/><path d="M5.5 5.5l5 5M10.5 5.5l-5 5"/>',
2630
+ passed: '<circle cx="8" cy="8" r="6"/><path d="M5 8.2l2 2 4-4.4"/>',
2631
+ approved: '<path d="M3 8.4l2.2 2.2 4.4-4.8"/><path d="M7 8.4l2.2 2.2 4.4-4.8"/>'
2632
+ };
2633
+ const AGENT_ICON = '<path d="M8 1.5v13M1.5 8h13M3.7 3.7l8.6 8.6M12.3 3.7l-8.6 8.6"/>';
2634
+ const svgMark = (paths, cls) => `<svg class="${cls}" viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
2635
+ const typeBadge = (t) => `<span class="wp-type wp-type--${t}">${svgMark(TYPE_ICON[t], "wp-type__glyph")}<span class="wp-type__label">${TYPE_LABEL[t]}</span></span>`;
2636
+ const phaseDot = (p) => `<span class="wp-dot wp-dot--${p}" role="img" aria-label="${p}"></span>`;
2637
+ function prStatus(pr, compact = false) {
2638
+ const needsGlyph = pr.ci === "running" || pr.ci === "failed";
2639
+ const diff = !compact && pr.additions != null ? `<span class="wp-pr__diff"><span class="wp-pr__add">+${pr.additions}</span> <span class="wp-pr__del">−${pr.deletions ?? 0}</span></span>` : "";
2640
+ return `<span class="wp-pr wp-pr--${pr.ci}" title="${esc(`PR #${pr.number} — ${CI_LABEL[pr.ci]}`)}">` + (needsGlyph ? svgMark(CI_ICON[pr.ci], "wp-pr__glyph") : "") + `<span class="wp-pr__num">#${pr.number}</span><span class="wp-pr__ci">${esc(CI_LABEL[pr.ci])}</span>` + diff + `</span>`;
2641
+ }
2642
+ function initials(name) {
2643
+ const parts = name.trim().split(/\s+/);
2644
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
2645
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
2646
+ }
2647
+ function ownerMark(owner) {
2648
+ if (!owner) return "";
2649
+ if (owner.kind === "agent") {
2650
+ return `<span class="wp-owner wp-owner--agent" title="${esc(owner.name)} (agent)">${svgMark(AGENT_ICON, "wp-owner__glyph")}</span>`;
2651
+ }
2652
+ return `<span class="wp-owner wp-owner--human" title="${esc(owner.name)}">${esc(initials(owner.name))}</span>`;
2653
+ }
2654
+ function waitLabel(t, byId) {
2655
+ const names = t.blockedBy.map((d) => byId.get(d)?.name ?? d);
2656
+ return `Waiting on ${names.join(", ")}`;
2657
+ }
2658
+ function waitNote(t, byId) {
2659
+ if (!t.locked) return "";
2660
+ const label2 = waitLabel(t, byId);
2661
+ return `<span class="wp-wait" title="${esc(label2)}">${esc(label2)}</span>`;
2662
+ }
2663
+ function groupByWave(tasks) {
2664
+ const map = /* @__PURE__ */ new Map();
2665
+ for (const t of tasks) {
2666
+ const arr = map.get(t.wave) ?? [];
2667
+ arr.push(t);
2668
+ map.set(t.wave, arr);
2669
+ }
2670
+ return [...map.entries()].sort((a, b) => a[0] - b[0]);
2671
+ }
2672
+ function renderList(tasks, byId) {
2673
+ const blocks = groupByWave(tasks).map(
2674
+ ([w, items]) => `
2675
+ <div class="wp-wave">
2676
+ <div class="wp-wave__head">
2677
+ <span class="wp-wave__no">Wave ${w + 1}</span>
2678
+ <span class="wp-wave__count">${items.length} task${items.length === 1 ? "" : "s"}</span>
2679
+ </div>
2680
+ <div class="wp-list">
2681
+ ${items.map((t) => {
2682
+ const p = phase(t);
2683
+ return `
2684
+ <div class="wp-row wp-row--${p}" data-locked="${t.locked}">
2685
+ ${phaseDot(p)}
2686
+ ${typeBadge(t.type)}
2687
+ <span class="wp-row__main">
2688
+ <span class="wp-row__name">${esc(t.name)}</span>
2689
+ ${waitNote(t, byId)}
2690
+ </span>
2691
+ <span class="wp-row__pr">${t.pr ? prStatus(t.pr) : ""}</span>
2692
+ ${ownerMark(t.owner)}
2693
+ </div>`;
2694
+ }).join("")}
2695
+ </div>
2696
+ </div>`
2697
+ ).join("");
2698
+ return `<div class="wp-view wp-view--list">${blocks}</div>`;
2699
+ }
2700
+ function renderKanban(tasks, byId) {
2701
+ const cols = [
2702
+ { key: "blocked", title: "Blocked" },
2703
+ { key: "ready", title: "Ready" },
2704
+ { key: "active", title: "In progress" },
2705
+ { key: "done", title: "Done" }
2706
+ ];
2707
+ const columns = cols.map(({ key, title }) => {
2708
+ const items = tasks.filter((t) => phase(t) === key);
2709
+ return `
2710
+ <div class="wp-col wp-col--${key}">
2711
+ <div class="wp-col__head">
2712
+ <span class="wp-col__title">${title}</span>
2713
+ <span class="wp-col__count">${items.length}</span>
2714
+ </div>
2715
+ <div class="wp-col__body">
2716
+ ${items.length === 0 ? '<div class="wp-col__empty">No tasks</div>' : items.map(
2717
+ (t) => `
2718
+ <div class="wp-card wp-card--${key}" data-locked="${t.locked}"${t.locked ? ` title="${esc(waitLabel(t, byId))}"` : ""}>
2719
+ <div class="wp-card__top">
2720
+ ${typeBadge(t.type)}
2721
+ ${phaseDot(phase(t))}
2722
+ </div>
2723
+ <div class="wp-card__name">${esc(t.name)}</div>
2724
+ ${t.pr ? `<div class="wp-card__pr">${prStatus(t.pr, true)}</div>` : ""}
2725
+ <div class="wp-card__foot">
2726
+ <span class="wp-card__wave">Wave ${t.wave + 1}</span>
2727
+ ${ownerMark(t.owner)}
2728
+ </div>
2729
+ </div>`
2730
+ ).join("")}
2731
+ </div>
2732
+ </div>`;
2733
+ }).join("");
2734
+ return `<div class="wp-view wp-view--kanban">${columns}</div>`;
2735
+ }
2736
+ function renderSwimlanes(tasks) {
2737
+ const order = ["research", "design", "write", "code", "review"];
2738
+ const maxWave = Math.max(0, ...tasks.map((t) => t.wave));
2739
+ const header = `<div class="wp-swim__corner">Type × wave</div>` + Array.from({ length: maxWave + 1 }, (_, w) => `<div class="wp-swim__whead">Wave ${w + 1}</div>`).join("");
2740
+ const lanes = order.filter((type) => tasks.some((t) => t.type === type)).map((type) => {
2741
+ const cells = Array.from({ length: maxWave + 1 }, (_, w) => {
2742
+ const items = tasks.filter((t) => t.type === type && t.wave === w);
2743
+ return `<div class="wp-swim__cell">${items.map((t) => {
2744
+ const p = phase(t);
2745
+ return `<div class="wp-chip wp-chip--${p}" data-locked="${t.locked}" title="${esc(t.name)}">
2746
+ <div class="wp-chip__head">
2747
+ ${phaseDot(p)}
2748
+ <span class="wp-chip__name">${esc(t.name)}</span>
2749
+ ${ownerMark(t.owner)}
2750
+ </div>
2751
+ ${t.pr ? `<div class="wp-chip__pr">${prStatus(t.pr, true)}</div>` : ""}
2752
+ </div>`;
2753
+ }).join("")}</div>`;
2754
+ }).join("");
2755
+ return `<div class="wp-swim__lanehead">${typeBadge(type)}</div>${cells}`;
2756
+ }).join("");
2757
+ return `<div class="wp-view wp-view--swim" style="--wp-waves:${maxWave + 1}">${header}${lanes}</div>`;
2758
+ }
2759
+ function renderGantt(tasks, unit) {
2760
+ const span = Math.max(1, ...tasks.map((t) => t.finish));
2761
+ const suffix = UNIT_SUFFIX[unit];
2762
+ const axis = Array.from(
2763
+ { length: span + 1 },
2764
+ (_, d) => `<span class="wp-gantt__tick" style="--at:${d}">${d}</span>`
2765
+ ).join("");
2766
+ const rows = [...tasks].sort((a, b) => a.start - b.start || a.wave - b.wave).map((t) => {
2767
+ const p = phase(t);
2768
+ const pct = (n) => n / span * 100;
2769
+ return `
2770
+ <div class="wp-gantt__row wp-gantt__row--${p}" data-locked="${t.locked}">
2771
+ <div class="wp-gantt__label">
2772
+ ${typeBadge(t.type)}
2773
+ <span class="wp-gantt__name">${esc(t.name)}</span>
2774
+ ${ownerMark(t.owner)}
2775
+ </div>
2776
+ <div class="wp-gantt__track">
2777
+ <div class="wp-gantt__bar wp-gantt__bar--${p}" style="left:${pct(t.start)}%;width:${pct(t.finish - t.start)}%">
2778
+ ${phaseDot(p)}
2779
+ <span class="wp-gantt__bar-label">${t.pr ? `#${t.pr.number}` : `${t.effort ?? 1}${suffix}`}</span>
2780
+ </div>
2781
+ </div>
2782
+ </div>`;
2783
+ }).join("");
2784
+ return `<div class="wp-view wp-view--gantt">
2785
+ <div class="wp-gantt__row wp-gantt__row--axis">
2786
+ <div class="wp-gantt__label wp-gantt__label--axis">${UNIT_AXIS[unit]} →</div>
2787
+ <div class="wp-gantt__track wp-gantt__track--axis" style="--span:${span}">${axis}</div>
2788
+ </div>
2789
+ ${rows}
2790
+ </div>`;
2791
+ }
2792
+ function summarize(tasks) {
2793
+ return {
2794
+ total: tasks.length,
2795
+ done: tasks.filter((t) => t.status === "done").length,
2796
+ active: tasks.filter((t) => phase(t) === "active").length,
2797
+ blocked: tasks.filter((t) => t.locked).length,
2798
+ waves: new Set(tasks.map((t) => t.wave)).size
2799
+ };
2800
+ }
2801
+ function renderEmpty() {
2802
+ return `<div class="wp-empty" role="status">
2803
+ <svg viewBox="0 0 48 48" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="wp-empty__art">
2804
+ <rect x="7" y="9" width="34" height="30" rx="2"/>
2805
+ <path d="M7 17h34"/>
2806
+ <path d="M13 24h10M13 30h16" stroke-dasharray="2 3"/>
2807
+ <circle cx="33" cy="29" r="6"/>
2808
+ <path d="M33 26v3l2 2"/>
2809
+ </svg>
2810
+ <div class="wp-empty__title">Waiting for a plan</div>
2811
+ <div class="wp-empty__hint">No work plan has been received yet. The host frame will post one over <span class="wp-mono">postMessage</span>.</div>
2812
+ </div>`;
2813
+ }
2814
+ const MESSAGE_TYPE = "workplan:update";
2815
+ const GLOBAL_KEY = "__WORKPLAN__";
2816
+ const MODES = /* @__PURE__ */ new Set(["list", "gantt", "kanban", "swimlanes"]);
2817
+ class BlueprintWorkplanElement extends HTMLElement {
2818
+ static get observedAttributes() {
2819
+ return ["mode", "unit", "header", "subheader"];
2820
+ }
2821
+ #plan = null;
2822
+ #root = null;
2823
+ #activeView = "list";
2824
+ #onMessage = (e) => this.#handleMessage(e);
2825
+ #onClick = (e) => this.#handleClick(e);
2826
+ set plan(value) {
2827
+ this.#plan = value;
2828
+ this.#render();
2829
+ }
2830
+ get plan() {
2831
+ return this.#plan;
2832
+ }
2833
+ get mode() {
2834
+ const m = this.getAttribute("mode");
2835
+ return MODES.has(m) ? m : null;
2836
+ }
2837
+ get unit() {
2838
+ return this.getAttribute("unit") === "days" ? "days" : "hours";
2839
+ }
2840
+ connectedCallback() {
2841
+ if (!this.#root) {
2842
+ this.#root = this.ownerDocument.createElement("div");
2843
+ this.#root.className = "bp-workplan";
2844
+ this.#root.addEventListener("click", this.#onClick);
2845
+ this.replaceChildren(this.#root);
2846
+ }
2847
+ if (!this.#plan && !this.hasAttribute("ignore-global")) {
2848
+ const fromGlobal = this.ownerDocument.defaultView?.[GLOBAL_KEY];
2849
+ if (fromGlobal) this.#plan = fromGlobal;
2850
+ }
2851
+ this.ownerDocument.defaultView?.addEventListener("message", this.#onMessage);
2852
+ this.#render();
2853
+ }
2854
+ disconnectedCallback() {
2855
+ this.ownerDocument.defaultView?.removeEventListener("message", this.#onMessage);
2856
+ }
2857
+ attributeChangedCallback() {
2858
+ this.#render();
2859
+ }
2860
+ #handleMessage(e) {
2861
+ const data = e.data;
2862
+ if (!data || data.type !== MESSAGE_TYPE || !data.plan) return;
2863
+ this.#plan = data.plan;
2864
+ this.#render();
2865
+ }
2866
+ // Delegated: the toolbar's buttons live in re-rendered markup, so we
2867
+ // listen on the persistent root and read intent off data-attributes.
2868
+ #handleClick(e) {
2869
+ const target = e.target;
2870
+ const btn = target instanceof Element ? target.closest("[data-wp-view],[data-wp-unit]") : null;
2871
+ if (!btn || !this.#root?.contains(btn)) return;
2872
+ const view = btn.getAttribute("data-wp-view");
2873
+ if (view) {
2874
+ this.#activeView = view;
2875
+ this.#render();
2876
+ return;
2877
+ }
2878
+ const unit = btn.getAttribute("data-wp-unit");
2879
+ if (unit) this.setAttribute("unit", unit);
2880
+ }
2881
+ get #view() {
2882
+ return this.mode ?? this.#activeView;
2883
+ }
2884
+ #toolbar(view, showModes) {
2885
+ const modes = showModes ? `<div class="wp-modes" role="tablist" aria-label="Choose a view">` + VIEW_OPTIONS.map(
2886
+ ([v, label2]) => `<button class="wp-modes__btn" type="button" role="tab" aria-selected="${v === view}" data-wp-view="${v}">${label2}</button>`
2887
+ ).join("") + `</div>` : "";
2888
+ const unit = view === "gantt" ? `<div class="wp-modes wp-modes--unit" role="group" aria-label="Time unit">` + ["hours", "days"].map(
2889
+ (u) => `<button class="wp-modes__btn" type="button" aria-selected="${u === this.unit}" data-wp-unit="${u}">${UNIT_AXIS[u]}</button>`
2890
+ ).join("") + `</div>` : "";
2891
+ return modes || unit ? `<div class="wp-toolbar">${modes}${unit}</div>` : "";
2892
+ }
2893
+ #render() {
2894
+ if (!this.#root) return;
2895
+ const plan = this.#plan;
2896
+ const hasData = !!plan && Array.isArray(plan.tasks) && plan.tasks.length > 0;
2897
+ const view = this.#view;
2898
+ const showModes = this.mode === null;
2899
+ const title = this.getAttribute("header") ?? plan?.title ?? "";
2900
+ const subtitle = this.getAttribute("subheader") ?? plan?.subtitle ?? "";
2901
+ let stats = "";
2902
+ let body = "";
2903
+ if (hasData) {
2904
+ this.#root.dataset.state = "ready";
2905
+ const derived = derive(plan);
2906
+ const byId = new Map(plan.tasks.map((t) => [t.id, t]));
2907
+ const counts = summarize(derived);
2908
+ stats = `<div class="wp-stats"><div class="wp-stat"><span class="wp-stat__k">Done</span><span class="wp-stat__v">${counts.done}/${counts.total}</span></div><div class="wp-stat"><span class="wp-stat__k">Active</span><span class="wp-stat__v">${counts.active}</span></div><div class="wp-stat"><span class="wp-stat__k">Blocked</span><span class="wp-stat__v">${counts.blocked}</span></div><div class="wp-stat"><span class="wp-stat__k">Waves</span><span class="wp-stat__v">${counts.waves}</span></div></div>`;
2909
+ body = view === "gantt" ? renderGantt(derived, this.unit) : view === "kanban" ? renderKanban(derived, byId) : view === "swimlanes" ? renderSwimlanes(derived) : renderList(derived, byId);
2910
+ } else {
2911
+ this.#root.dataset.state = "empty";
2912
+ body = renderEmpty();
2913
+ }
2914
+ const head = title || subtitle || stats ? `<div class="wp-head"><div class="wp-head__title">` + (title ? `<div class="wp-head__name">${esc(title)}</div>` : "") + (subtitle ? `<div class="wp-head__sub">${esc(subtitle)}</div>` : "") + `</div>` + stats + `</div>` : "";
2915
+ const toolbar = hasData ? this.#toolbar(view, showModes) : "";
2916
+ this.#root.innerHTML = head + toolbar + `<div class="wp-canvas">${body}</div>`;
2917
+ }
2918
+ }
2919
+ if (typeof customElements !== "undefined" && !customElements.get("bp-workplan")) {
2920
+ customElements.define("bp-workplan", BlueprintWorkplanElement);
2921
+ }
2922
+ })();
2923
+ if (typeof document !== "undefined") {
2924
+ if (document.readyState === "loading") {
2925
+ document.addEventListener("DOMContentLoaded", () => initializeBlueprintRuntime(), { once: true });
2926
+ } else {
2927
+ initializeBlueprintRuntime();
2928
+ }
2929
+ }
2930
+ export {
2931
+ initializeBlueprintRuntime
2932
+ };