@kitlangton/knobs 0.4.0-next.7 → 0.4.0-next.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui.js CHANGED
@@ -4995,7 +4995,7 @@ function planPill(state, next, flags = {}) {
4995
4995
  if (t && t.x === next.x && t.y === next.y && t.w === next.w && t.h === next.h)
4996
4996
  return { kind: "stay" };
4997
4997
  const d = Math.hypot(next.x - state.at.x, next.y - state.at.y);
4998
- return { kind: "travel", box: next, spring: pillSpring(d) };
4998
+ return { kind: "travel", box: next, spring: pillSpring(d, flags.spring) };
4999
4999
  }
5000
5000
  function selectionPill(container, options) {
5001
5001
  const doc = container.ownerDocument;
@@ -5019,7 +5019,7 @@ function selectionPill(container, options) {
5019
5019
  container.prepend(pill);
5020
5020
  if (copyOn)
5021
5021
  container.append(copy);
5022
- const { omega, zeta } = springMotion(SPRINGS.default);
5022
+ const { omega, zeta } = springMotion(options.spring ?? SPRINGS.default);
5023
5023
  const springs = [0, 0, 0, 0].map(() => new Spring(0, omega, zeta, 0.01));
5024
5024
  const [sx, sy, sw, sh] = springs;
5025
5025
  let shown = false;
@@ -5120,7 +5120,8 @@ function selectionPill(container, options) {
5120
5120
  const plan = planPill({ shown, at: shown ? at() : null, target }, next, {
5121
5121
  first,
5122
5122
  reduced: reducedMotion(win),
5123
- ...o?.snap ? { snap: true } : {}
5123
+ ...o?.snap ? { snap: true } : {},
5124
+ ...options.spring ? { spring: options.spring } : {}
5124
5125
  });
5125
5126
  first = false;
5126
5127
  for (const n of clones.values())
@@ -8341,6 +8342,7 @@ function compare(container, options) {
8341
8342
  let loupeHeld = false;
8342
8343
  let pointer = null;
8343
8344
  let drag = null;
8345
+ let tap = null;
8344
8346
  const touches = new Map;
8345
8347
  let pinch = null;
8346
8348
  const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
@@ -9172,15 +9174,18 @@ function compare(container, options) {
9172
9174
  touches.set(e.pointerId, p);
9173
9175
  stage.setPointerCapture(e.pointerId);
9174
9176
  if (touches.size >= 2) {
9177
+ tap = null;
9175
9178
  e.preventDefault();
9176
9179
  return startPinch();
9177
9180
  }
9181
+ tap = e.target === handle ? null : { id: e.pointerId, x: p.x, y: p.y };
9178
9182
  if (mode === "wipe") {
9179
9183
  const wr = wipeRect(layoutNow().L.overlay);
9180
9184
  const inside = p.x >= wr.x - SLOP && p.x <= wr.x + wr.w + SLOP && p.y >= wr.y && p.y <= wr.y + wr.h;
9181
9185
  const grab = wipeGrab(p.x, wr, wipeLine.x, SLOP);
9182
9186
  if (inside && (grab.onLine || !zoomed())) {
9183
9187
  drag = { kind: "wipe", offset: grab.offset };
9188
+ tap = null;
9184
9189
  root.dataset.dragging = "";
9185
9190
  gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
9186
9191
  e.preventDefault();
@@ -9200,6 +9205,8 @@ function compare(container, options) {
9200
9205
  const onMove = (e) => {
9201
9206
  const p = local(e);
9202
9207
  pointer = p;
9208
+ if (tap && Math.hypot(p.x - tap.x, p.y - tap.y) > TAP_SLOP)
9209
+ tap = null;
9203
9210
  if (touches.has(e.pointerId))
9204
9211
  touches.set(e.pointerId, p);
9205
9212
  if (pinch && touches.size >= 2) {
@@ -9232,6 +9239,11 @@ function compare(container, options) {
9232
9239
  touches.delete(e.pointerId);
9233
9240
  if (touches.size < 2)
9234
9241
  pinch = null;
9242
+ if (tap?.id === e.pointerId) {
9243
+ tap = null;
9244
+ if (e.type === "pointerup" && sides.some((s) => s.kind === "video"))
9245
+ api.toggle();
9246
+ }
9235
9247
  if (!drag)
9236
9248
  return;
9237
9249
  if (drag.kind === "wipe") {
@@ -9633,7 +9645,7 @@ function compare(container, options) {
9633
9645
  wake();
9634
9646
  return api;
9635
9647
  }
9636
- var COMPARE_FFMPEG = "ffmpeg -i in.mov -vf fps=30 -fps_mode cfr -c:v libx264 -preset slow -crf 18 -g 15 -keyint_min 15 -pix_fmt yuv420p -movflags +faststart -an out.mp4", LAYOUT, FADE2, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
9648
+ var COMPARE_FFMPEG = "ffmpeg -i in.mov -vf fps=30 -fps_mode cfr -c:v libx264 -preset slow -crf 18 -g 15 -keyint_min 15 -pix_fmt yuv420p -movflags +faststart -an out.mp4", LAYOUT, FADE2, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, TAP_SLOP = 4, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
9637
9649
  var init_compare = __esm(() => {
9638
9650
  init_motion();
9639
9651
  init_page_hints();
@@ -9662,6 +9674,174 @@ var init_compare = __esm(() => {
9662
9674
  };
9663
9675
  });
9664
9676
 
9677
+ // src/ui/player.ts
9678
+ function player(container, options) {
9679
+ const doc = container.ownerDocument;
9680
+ const el = (tag, cls) => {
9681
+ const n = doc.createElement(tag);
9682
+ n.className = cls;
9683
+ return n;
9684
+ };
9685
+ const look = {
9686
+ ...options.preset ? { preset: options.preset } : {},
9687
+ ...options.theme ? { theme: options.theme } : {},
9688
+ ...options.appearance ? { appearance: options.appearance } : {}
9689
+ };
9690
+ const root = el("div", "knobs-player");
9691
+ root.setAttribute("role", "group");
9692
+ root.setAttribute("aria-label", options.label ?? "Video");
9693
+ applyLook2(root, options);
9694
+ const frame = el("div", "knobs-player-frame");
9695
+ frame.style.setProperty("--kp-aspect", String(options.aspect ?? 16 / 9));
9696
+ const v = el("video", "knobs-player-media");
9697
+ v.muted = options.muted ?? true;
9698
+ v.defaultMuted = v.muted;
9699
+ v.playsInline = true;
9700
+ v.preload = "metadata";
9701
+ v.setAttribute("playsinline", "");
9702
+ if (options.poster)
9703
+ v.poster = options.poster;
9704
+ if (options.captions) {
9705
+ const track = doc.createElement("track");
9706
+ track.kind = "captions";
9707
+ track.label = "Captions";
9708
+ track.srclang = doc.documentElement.lang || "en";
9709
+ track.default = true;
9710
+ track.src = options.captions;
9711
+ v.append(track);
9712
+ }
9713
+ frame.append(v);
9714
+ const host = el("div", "knobs-player-line");
9715
+ root.append(frame, host);
9716
+ container.append(root);
9717
+ const markers = (options.chapters ?? []).map((c, i) => ({
9718
+ id: c.id ?? `c${i}`,
9719
+ time: c.start,
9720
+ label: c.label
9721
+ }));
9722
+ const tl = timeline(host, {
9723
+ ...look,
9724
+ variant: "line",
9725
+ duration: 1,
9726
+ fps: options.fps ?? 30,
9727
+ markers,
9728
+ repeat: options.loop ?? true,
9729
+ arrows: { step: 5, shift: 10 },
9730
+ label: options.label ? `${options.label} time` : "Video time",
9731
+ ...options.hints === false ? { hints: false } : {}
9732
+ });
9733
+ const onFrame = () => root.setAttribute("data-ready", "");
9734
+ let pending = null;
9735
+ const seekVideo = (t) => {
9736
+ if (v.readyState < 1)
9737
+ return;
9738
+ if (v.seeking)
9739
+ pending = t;
9740
+ else
9741
+ v.currentTime = t;
9742
+ };
9743
+ let reported = 0;
9744
+ const onSeeked = () => {
9745
+ if (pending !== null) {
9746
+ const t = pending;
9747
+ pending = null;
9748
+ v.currentTime = t;
9749
+ return;
9750
+ }
9751
+ if (!v.paused || tl.playing || Math.abs(v.currentTime - reported) < 0.1)
9752
+ return;
9753
+ reported = v.currentTime;
9754
+ options.onSeek?.(reported);
9755
+ };
9756
+ const onMeta = () => {
9757
+ onFrame();
9758
+ if (v.videoWidth)
9759
+ frame.style.setProperty("--kp-aspect", String(v.videoWidth / v.videoHeight));
9760
+ if (Number.isFinite(v.duration))
9761
+ tl.update({ duration: v.duration });
9762
+ };
9763
+ v.addEventListener("loadedmetadata", onMeta);
9764
+ v.addEventListener("loadeddata", onFrame);
9765
+ v.addEventListener("seeked", onSeeked);
9766
+ const unlisten = tl.listen({
9767
+ onSeek(t, source) {
9768
+ if (source === "play") {
9769
+ if (Math.abs(v.currentTime - t) > 0.25)
9770
+ v.currentTime = t;
9771
+ return;
9772
+ }
9773
+ if (Math.abs(v.currentTime - t) < 1 / (options.fps ?? 30))
9774
+ return;
9775
+ seekVideo(t);
9776
+ },
9777
+ onPlay(on) {
9778
+ if (on)
9779
+ v.play().catch(() => tl.pause());
9780
+ else {
9781
+ v.pause();
9782
+ reported = tl.time;
9783
+ }
9784
+ root.toggleAttribute("data-playing", on);
9785
+ options.onPlay?.(on, tl.time);
9786
+ },
9787
+ onScrub(on) {
9788
+ if (on)
9789
+ v.pause();
9790
+ }
9791
+ });
9792
+ tl.drive(() => {
9793
+ if (v.readyState < 1)
9794
+ return Number.NaN;
9795
+ if (v.seeking)
9796
+ return tl.time;
9797
+ return v.ended ? Number.POSITIVE_INFINITY : v.currentTime;
9798
+ });
9799
+ const onClick = () => {
9800
+ tl.focus();
9801
+ tl.toggle();
9802
+ };
9803
+ frame.addEventListener("click", onClick);
9804
+ v.src = options.src;
9805
+ const api = {
9806
+ element: root,
9807
+ video: v,
9808
+ timeline: tl,
9809
+ get time() {
9810
+ return tl.time;
9811
+ },
9812
+ get playing() {
9813
+ return tl.playing;
9814
+ },
9815
+ get duration() {
9816
+ return Number.isFinite(v.duration) ? v.duration : 0;
9817
+ },
9818
+ play: () => tl.play(),
9819
+ pause: () => tl.pause(),
9820
+ toggle: () => tl.toggle(),
9821
+ seek: (t, o) => tl.seek(t, o?.glide ? { glide: true } : undefined),
9822
+ seekBy: (d) => tl.seek(Math.min(tl.duration, Math.max(0, tl.time + d)), { glide: true }),
9823
+ focus: () => tl.focus(),
9824
+ dispose() {
9825
+ unlisten();
9826
+ tl.drive(null);
9827
+ tl.dispose();
9828
+ v.pause();
9829
+ frame.removeEventListener("click", onClick);
9830
+ v.removeEventListener("loadedmetadata", onMeta);
9831
+ v.removeEventListener("loadeddata", onFrame);
9832
+ v.removeEventListener("seeked", onSeeked);
9833
+ v.removeAttribute("src");
9834
+ v.load();
9835
+ root.remove();
9836
+ }
9837
+ };
9838
+ return api;
9839
+ }
9840
+ var init_player = __esm(() => {
9841
+ init_look();
9842
+ init_timeline();
9843
+ });
9844
+
9665
9845
  // src/ui/scrollbar.ts
9666
9846
  function overlayScrollbar(scroller, content) {
9667
9847
  const doc = scroller.ownerDocument;
@@ -16379,169 +16559,8 @@ init_timeline();
16379
16559
  init_timeline_model();
16380
16560
  init_compare();
16381
16561
  init_compare_strip();
16562
+ init_player();
16382
16563
 
16383
- // src/ui/player.ts
16384
- init_look();
16385
- init_timeline();
16386
- function player(container, options) {
16387
- const doc = container.ownerDocument;
16388
- const el = (tag, cls) => {
16389
- const n = doc.createElement(tag);
16390
- n.className = cls;
16391
- return n;
16392
- };
16393
- const look = {
16394
- ...options.preset ? { preset: options.preset } : {},
16395
- ...options.theme ? { theme: options.theme } : {},
16396
- ...options.appearance ? { appearance: options.appearance } : {}
16397
- };
16398
- const root = el("div", "knobs-player");
16399
- root.setAttribute("role", "group");
16400
- root.setAttribute("aria-label", options.label ?? "Video");
16401
- applyLook2(root, options);
16402
- const frame = el("div", "knobs-player-frame");
16403
- frame.style.setProperty("--kp-aspect", String(options.aspect ?? 16 / 9));
16404
- const v = el("video", "knobs-player-media");
16405
- v.muted = options.muted ?? true;
16406
- v.defaultMuted = v.muted;
16407
- v.playsInline = true;
16408
- v.preload = "metadata";
16409
- v.setAttribute("playsinline", "");
16410
- if (options.poster)
16411
- v.poster = options.poster;
16412
- if (options.captions) {
16413
- const track = doc.createElement("track");
16414
- track.kind = "captions";
16415
- track.label = "Captions";
16416
- track.srclang = doc.documentElement.lang || "en";
16417
- track.default = true;
16418
- track.src = options.captions;
16419
- v.append(track);
16420
- }
16421
- frame.append(v);
16422
- const host = el("div", "knobs-player-line");
16423
- root.append(frame, host);
16424
- container.append(root);
16425
- const markers = (options.chapters ?? []).map((c, i) => ({
16426
- id: c.id ?? `c${i}`,
16427
- time: c.start,
16428
- label: c.label
16429
- }));
16430
- const tl = timeline(host, {
16431
- ...look,
16432
- variant: "line",
16433
- duration: 1,
16434
- fps: options.fps ?? 30,
16435
- markers,
16436
- repeat: options.loop ?? true,
16437
- arrows: { step: 5, shift: 10 },
16438
- label: options.label ? `${options.label} time` : "Video time",
16439
- ...options.hints === false ? { hints: false } : {}
16440
- });
16441
- const onFrame = () => root.setAttribute("data-ready", "");
16442
- let pending = null;
16443
- const seekVideo = (t) => {
16444
- if (v.readyState < 1)
16445
- return;
16446
- if (v.seeking)
16447
- pending = t;
16448
- else
16449
- v.currentTime = t;
16450
- };
16451
- let reported = 0;
16452
- const onSeeked = () => {
16453
- if (pending !== null) {
16454
- const t = pending;
16455
- pending = null;
16456
- v.currentTime = t;
16457
- return;
16458
- }
16459
- if (!v.paused || tl.playing || Math.abs(v.currentTime - reported) < 0.1)
16460
- return;
16461
- reported = v.currentTime;
16462
- options.onSeek?.(reported);
16463
- };
16464
- const onMeta = () => {
16465
- onFrame();
16466
- if (v.videoWidth)
16467
- frame.style.setProperty("--kp-aspect", String(v.videoWidth / v.videoHeight));
16468
- if (Number.isFinite(v.duration))
16469
- tl.update({ duration: v.duration });
16470
- };
16471
- v.addEventListener("loadedmetadata", onMeta);
16472
- v.addEventListener("loadeddata", onFrame);
16473
- v.addEventListener("seeked", onSeeked);
16474
- const unlisten = tl.listen({
16475
- onSeek(t, source) {
16476
- if (source === "play") {
16477
- if (Math.abs(v.currentTime - t) > 0.25)
16478
- v.currentTime = t;
16479
- return;
16480
- }
16481
- if (Math.abs(v.currentTime - t) < 1 / (options.fps ?? 30))
16482
- return;
16483
- seekVideo(t);
16484
- },
16485
- onPlay(on) {
16486
- if (on)
16487
- v.play().catch(() => tl.pause());
16488
- else {
16489
- v.pause();
16490
- reported = tl.time;
16491
- }
16492
- root.toggleAttribute("data-playing", on);
16493
- options.onPlay?.(on, tl.time);
16494
- },
16495
- onScrub(on) {
16496
- if (on)
16497
- v.pause();
16498
- }
16499
- });
16500
- tl.drive(() => {
16501
- if (v.readyState < 1)
16502
- return Number.NaN;
16503
- if (v.seeking)
16504
- return tl.time;
16505
- return v.ended ? Number.POSITIVE_INFINITY : v.currentTime;
16506
- });
16507
- const onClick = () => tl.focus();
16508
- frame.addEventListener("click", onClick);
16509
- v.src = options.src;
16510
- const api = {
16511
- element: root,
16512
- video: v,
16513
- timeline: tl,
16514
- get time() {
16515
- return tl.time;
16516
- },
16517
- get playing() {
16518
- return tl.playing;
16519
- },
16520
- get duration() {
16521
- return Number.isFinite(v.duration) ? v.duration : 0;
16522
- },
16523
- play: () => tl.play(),
16524
- pause: () => tl.pause(),
16525
- toggle: () => tl.toggle(),
16526
- seek: (t, o) => tl.seek(t, o?.glide ? { glide: true } : undefined),
16527
- seekBy: (d) => tl.seek(Math.min(tl.duration, Math.max(0, tl.time + d)), { glide: true }),
16528
- focus: () => tl.focus(),
16529
- dispose() {
16530
- unlisten();
16531
- tl.drive(null);
16532
- tl.dispose();
16533
- v.pause();
16534
- frame.removeEventListener("click", onClick);
16535
- v.removeEventListener("loadedmetadata", onMeta);
16536
- v.removeEventListener("loadeddata", onFrame);
16537
- v.removeEventListener("seeked", onSeeked);
16538
- v.removeAttribute("src");
16539
- v.load();
16540
- root.remove();
16541
- }
16542
- };
16543
- return api;
16544
- }
16545
16564
  // src/ui/history.ts
16546
16565
  init_motion();
16547
16566
  init_glide();
@@ -16580,7 +16599,7 @@ var historyCss = `
16580
16599
  .knobs-hy-layer{position:absolute;inset:0;grid-column:2;grid-row:1/-1;pointer-events:none}
16581
16600
 
16582
16601
  /* Bands and month lines behind the lanes; today, the selection and the pointer's line over them. */
16583
- .knobs-hy-band{position:absolute;top:0;bottom:0;background:color-mix(in oklch,var(--_k-fg) 3.5%,transparent);box-shadow:inset 1px 0 0 var(--_k-border-subtle)}
16602
+ .knobs-hy-band{position:absolute;top:0;bottom:0;background:var(--_k-fill);box-shadow:inset 1px 0 0 var(--_k-border-subtle)}
16584
16603
  :is(.knobs-hy-grid,.knobs-hy-today,.knobs-hy-hover,.knobs-hy-sel){position:absolute;left:0;top:calc(var(--hy-bandrow) + var(--hy-axis));bottom:0;width:1px;margin-left:-.5px}
16585
16604
  .knobs-hy-grid{background:var(--_k-border-subtle)}
16586
16605
  .knobs-hy-today{top:calc(var(--hy-bandrow) + var(--hy-axis) - 4px);background:var(--_k-accent)}
@@ -17133,10 +17152,59 @@ function fillMedia(m, base) {
17133
17152
  };
17134
17153
  case "table":
17135
17154
  return fillTable(m);
17155
+ case "facts":
17156
+ return {
17157
+ kind: "facts",
17158
+ ...m.title ? { title: m.title } : {},
17159
+ groups: [
17160
+ ...m.facts ? [{ facts: fillFacts(m.facts) }] : [],
17161
+ ...(m.groups ?? []).map((g) => ({
17162
+ ...g.title ? { title: g.title } : {},
17163
+ facts: fillFacts(g.facts)
17164
+ }))
17165
+ ]
17166
+ };
17167
+ case "decision":
17168
+ return { kind: "decision", options: fillDecision(m.options, m.noun, base) };
17136
17169
  default:
17137
17170
  return m;
17138
17171
  }
17139
17172
  }
17173
+ function fillFacts(facts) {
17174
+ const list = Array.isArray(facts) ? facts : Object.entries(facts).map(([term, value]) => ({ term, value }));
17175
+ return list.map((f) => {
17176
+ const code = f.code === true;
17177
+ const raw = typeof f.value === "string" ? [f.value] : [...f.value];
17178
+ const value = (code ? raw.flatMap((v) => v.split(/\s*,\s*/)) : raw).map((v) => v.trim()).filter(Boolean);
17179
+ return { term: f.term, value, code };
17180
+ });
17181
+ }
17182
+ function optionColumns(count, width, min = 280, gap = 48) {
17183
+ for (let c = Math.min(count, 4);c > 1; c--)
17184
+ if (count % c === 0 && (width - gap * (c - 1)) / c >= min)
17185
+ return c;
17186
+ return 1;
17187
+ }
17188
+ function fillDecision(options, noun = "Option", base) {
17189
+ const unique = uniqueIds();
17190
+ return options.map((o, i) => {
17191
+ if ("number" in o)
17192
+ return o;
17193
+ const extra = [
17194
+ ...o.pros?.length ? [{ term: "For", value: o.pros }] : [],
17195
+ ...o.cons?.length ? [{ term: "Against", value: o.cons }] : []
17196
+ ];
17197
+ return {
17198
+ id: unique(o.id ?? slug(o.title)),
17199
+ number: `${noun} ${i + 1}`,
17200
+ title: o.title,
17201
+ ...o.summary ? { summary: o.summary } : {},
17202
+ ...o.media !== undefined ? { media: fillMedia(o.media, base) } : {},
17203
+ facts: [...o.facts ? fillFacts(o.facts) : [], ...fillFacts(extra)],
17204
+ recommended: o.recommended === true
17205
+ };
17206
+ });
17207
+ }
17140
17208
  function historyData(m) {
17141
17209
  const events = m.events ?? [];
17142
17210
  if (m.lanes || m.bands || m.states || m.axis)
@@ -17341,7 +17409,8 @@ function normalizeWalkthrough(doc) {
17341
17409
  letter: String.fromCharCode(65 + oi % 26),
17342
17410
  ...o.caption ? { caption: o.caption } : {},
17343
17411
  ...o.media !== undefined ? { media: fillMedia(o.media, doc.base) } : {},
17344
- suggested: o.suggested === true
17412
+ ...o.facts ? { facts: fillFacts(o.facts) } : {},
17413
+ suggested: o.suggested === true || o.recommended === true
17345
17414
  }));
17346
17415
  const list = step.media === undefined ? [] : Array.isArray(step.media) ? step.media : [step.media];
17347
17416
  const asked = step.ask === "verdict" ? "note" : step.ask;
@@ -17414,25 +17483,63 @@ function normalizeWalkthrough(doc) {
17414
17483
  })),
17415
17484
  sections: withKickers(sections, doc.sections, doc.kickers),
17416
17485
  steps,
17417
- prs: knownPRs(doc, sections),
17486
+ prs: knownPRs(doc, sections, steps),
17418
17487
  ...repoOf(doc) ? { repo: repoOf(doc) } : {}
17419
17488
  };
17420
17489
  }
17421
17490
  var prNumber = (href) => /\/(?:pull|merge_requests)\/(\d+)(?:[/?#]|$)/.exec(href)?.[1] ?? null;
17422
17491
  var repoOf = (doc) => doc.repo?.trim().replace(/^https?:\/\/github\.com\//, "").replace(/\/+$/, "") || undefined;
17423
- function knownPRs(doc, sections) {
17492
+ var PR_STATES = ["merged", "open", "draft", "closed"];
17493
+ function prStatus(status) {
17494
+ const words = (status ?? "").trim().toLowerCase();
17495
+ const first = /^[a-z]+/.exec(words)?.[0] ?? "";
17496
+ const state = PR_STATES.includes(first) ? first : first === "shipped" || first === "done" ? "merged" : null;
17497
+ return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
17498
+ }
17499
+ function knownPRs(doc, sections, steps = []) {
17424
17500
  const out = {};
17425
17501
  const repo = repoOf(doc);
17426
- for (const s of sections) {
17427
- const n = s.link && prNumber(s.link.href);
17428
- if (n)
17429
- out[n] = { href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1") };
17430
- }
17502
+ const add = (n, pr) => {
17503
+ const known = out[n] ?? {};
17504
+ const next = { ...known };
17505
+ for (const [k, v] of Object.entries(pr))
17506
+ if (v !== undefined && next[k] === undefined)
17507
+ next[k] = v;
17508
+ out[n] = next;
17509
+ };
17431
17510
  for (const [k, pr] of Object.entries(doc.prs ?? {})) {
17432
17511
  const n = k.replace(/^#/, "");
17433
17512
  if (!/^\d+$/.test(n))
17434
17513
  continue;
17435
- out[n] = { ...out[n], ...pr };
17514
+ add(n, pr);
17515
+ }
17516
+ for (const step of steps)
17517
+ for (const m of step.media) {
17518
+ if (m.kind !== "stack")
17519
+ continue;
17520
+ for (const it of m.items) {
17521
+ const n = it.pr.replace(/^#/, "");
17522
+ if (!/^\d+$/.test(n))
17523
+ continue;
17524
+ const said = prStatus(it.status);
17525
+ add(n, {
17526
+ ...said.state ? { state: said.state } : {},
17527
+ ...said.state && said.checks ? { checks: true } : {},
17528
+ ...it.title ? { title: plainMarkdown(it.title) } : {},
17529
+ ...it.href ? { href: it.href } : {}
17530
+ });
17531
+ }
17532
+ }
17533
+ for (const s of sections) {
17534
+ const n = s.link && prNumber(s.link.href);
17535
+ if (!n)
17536
+ continue;
17537
+ const said = prStatus(s.status);
17538
+ add(n, {
17539
+ href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1"),
17540
+ ...said.state ? { state: said.state } : {},
17541
+ title: plainMarkdown(s.title)
17542
+ });
17436
17543
  }
17437
17544
  if (repo) {
17438
17545
  for (const n of Object.keys(out))
@@ -17441,13 +17548,17 @@ function knownPRs(doc, sections) {
17441
17548
  }
17442
17549
  return out;
17443
17550
  }
17444
- function findPR(prs, n, repo) {
17445
- const known = prs[n];
17446
- const href = repo ? `https://github.com/${repo}/pull/${n}` : undefined;
17551
+ function findPR(prs, n, repo, kind = "pr") {
17552
+ const entry = prs[n];
17553
+ const known = entry && (entry.kind ?? "pr") === kind ? entry : undefined;
17554
+ const path = kind === "issue" ? "issues" : "pull";
17555
+ const href = repo ? `https://github.com/${repo}/${path}/${n}` : undefined;
17556
+ const base = kind === "issue" ? { kind } : {};
17447
17557
  if (known)
17448
- return known.href || !href ? known : { ...known, href };
17449
- return href ? { href } : null;
17558
+ return known.href || !href ? { ...base, ...known } : { ...base, ...known, href };
17559
+ return href ? { ...base, href } : kind === "issue" ? { kind } : null;
17450
17560
  }
17561
+ var issueNumber = (href) => /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/.exec(href)?.[1] ?? null;
17451
17562
  var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
17452
17563
  function withKickers(sections, source, kickers = "pr") {
17453
17564
  if (kickers === false)
@@ -21758,6 +21869,230 @@ init_motion();
21758
21869
  init_page_hints();
21759
21870
  init_icons();
21760
21871
  init_look();
21872
+
21873
+ // src/review/foundations.ts
21874
+ init_motion();
21875
+ var TRACKING = 0.03;
21876
+ var MEASURE = "66ch";
21877
+ var FONT = {
21878
+ sans: 'system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI Variable Text","Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif',
21879
+ mono: 'ui-monospace,"SF Mono",SFMono-Regular,"Cascadia Mono","JetBrains Mono",Menlo,Consolas,monospace'
21880
+ };
21881
+ var FEATURES = {
21882
+ figures: "tabular-nums",
21883
+ data: '"tnum" 1,"ss01" 1',
21884
+ code: "none"
21885
+ };
21886
+ var MOTION2 = [
21887
+ {
21888
+ name: "hover",
21889
+ spring: null,
21890
+ ms: "0 in, 120 out",
21891
+ use: "A hover's fill: on at once, off softly"
21892
+ },
21893
+ {
21894
+ name: "quick",
21895
+ spring: FADE,
21896
+ use: "Replacing a whole thing in place: fade, scale .98, blur 3 px"
21897
+ },
21898
+ { name: "close", spring: CLOSE, use: "A popover closing" },
21899
+ { name: "snappy", spring: SPRINGS.snappy, use: "Controls: a toggle, a pick, a chapter row" },
21900
+ { name: "state", spring: SPRINGS.default, use: "The selection pill, a disclosure opening" },
21901
+ {
21902
+ name: "calm",
21903
+ spring: SPRINGS.calm,
21904
+ use: "Layout: a compare's height, the page scrolling to a step"
21905
+ },
21906
+ { name: "smooth", spring: SPRINGS.smooth, use: "A step opening to the full window" }
21907
+ ];
21908
+ var foundationCss = `
21909
+ :where(.knobs-walkthrough,.knobs-wt-slot,.knobs-review){
21910
+ --_kr-track:calc((var(--_k-type-body) - 1em) * ${TRACKING});
21911
+ --_kr-measure:var(--k-walkthrough-measure,${MEASURE});
21912
+ --_kr-block:calc(var(--_k-space-title) * 2);
21913
+ --_kr-data:${FEATURES.data};
21914
+ letter-spacing:var(--_kr-track);font-variant-numeric:${FEATURES.figures};font-optical-sizing:auto;font-kerning:normal}
21915
+ :where(.knobs-walkthrough,.knobs-wt-slot,.knobs-review) :where(*){letter-spacing:var(--_kr-track)}
21916
+ :where(.knobs-walkthrough,.knobs-wt-slot,.knobs-review) :where(code,pre,kbd,samp,.knobs-wt-mono){letter-spacing:0;font-variant-ligatures:${FEATURES.code}}
21917
+ `;
21918
+
21919
+ // src/themes/review.ts
21920
+ init_define();
21921
+
21922
+ // src/themes/void.ts
21923
+ init_define();
21924
+ init_knobs();
21925
+ var voidTheme = defineTheme({
21926
+ name: "void",
21927
+ extends: knobs,
21928
+ gray: { hue: 0, chroma: 0 },
21929
+ accent: { dark: "#ffffff", light: "#1c1c1c" },
21930
+ material: "flat",
21931
+ tokens: {
21932
+ dark: {
21933
+ "bg-page": "#000000",
21934
+ "bg-inset": "#0a0a0a",
21935
+ bg: "#000000",
21936
+ "bg-raised": "#1c1c1e",
21937
+ "bg-overlay": "#2c2c2e",
21938
+ fg: "#ededed",
21939
+ "fg-2": "#cecece",
21940
+ "fg-3": "#989898",
21941
+ "fg-4": "#7a7a7a",
21942
+ "compare-stage": "#000000"
21943
+ }
21944
+ }
21945
+ });
21946
+
21947
+ // src/themes/review.ts
21948
+ var reviewTheme = defineTheme({
21949
+ name: "review",
21950
+ extends: voidTheme,
21951
+ font: FONT,
21952
+ tokens: {
21953
+ dark: {
21954
+ fill: "#ededed0f",
21955
+ "fill-hover": "#ededed1a",
21956
+ "fill-selected": "#ededed24",
21957
+ "border-subtle": "#ededed47",
21958
+ border: "#ededed4f",
21959
+ "border-strong": "#585858"
21960
+ },
21961
+ light: {
21962
+ "bg-page": "#ffffff",
21963
+ "bg-inset": "#f7f7f7",
21964
+ bg: "#ffffff",
21965
+ "bg-raised": "#ffffff",
21966
+ "bg-overlay": "#ffffff",
21967
+ fill: "#1919190a",
21968
+ "fill-hover": "#19191912",
21969
+ "fill-selected": "#1919191c",
21970
+ "border-subtle": "#19191917",
21971
+ border: "#1919191d",
21972
+ "border-strong": "#dcdcdc"
21973
+ }
21974
+ }
21975
+ });
21976
+
21977
+ // src/ui/review-look.ts
21978
+ var MODES2 = { dark: "dark", light: "light", auto: "system" };
21979
+ var THEMED = '[data-knobs-theme]:not([data-knobs-theme="dark"],[data-knobs-theme="light"])';
21980
+ function reviewLook(options, container) {
21981
+ const t = options.theme;
21982
+ if (typeof t === "object")
21983
+ return options;
21984
+ if (!t && container?.closest(THEMED))
21985
+ return options;
21986
+ const appearance = options.appearance ?? (t ? MODES2[t] : undefined);
21987
+ return { ...options, theme: reviewTheme, ...appearance ? { appearance } : {} };
21988
+ }
21989
+
21990
+ // src/ui/review-options.ts
21991
+ var el2 = (doc, tag, cls) => {
21992
+ const n = doc.createElement(tag);
21993
+ if (cls)
21994
+ n.className = cls;
21995
+ return n;
21996
+ };
21997
+ function renderFacts(doc, groups) {
21998
+ const box = el2(doc, "div", "knobs-wt-facts");
21999
+ for (const g of groups) {
22000
+ if (!g.facts.length && !g.title)
22001
+ continue;
22002
+ const group = el2(doc, "section", "knobs-wt-fgroup");
22003
+ if (g.title) {
22004
+ const head = el2(doc, "h3", "knobs-wt-fhead");
22005
+ head.innerHTML = inlineMarkdown(g.title);
22006
+ group.append(head);
22007
+ }
22008
+ if (g.facts.length)
22009
+ group.append(factList(doc, g.facts));
22010
+ box.append(group);
22011
+ }
22012
+ return box;
22013
+ }
22014
+ function factList(doc, facts) {
22015
+ const dl = el2(doc, "dl", "knobs-wt-fterms");
22016
+ for (const f of facts) {
22017
+ const row = el2(doc, "div", "knobs-wt-fact");
22018
+ const dt = el2(doc, "dt");
22019
+ dt.innerHTML = inlineMarkdown(f.term);
22020
+ const dd = el2(doc, "dd");
22021
+ if (f.code) {
22022
+ dd.dataset.code = "";
22023
+ for (const v of f.value) {
22024
+ const chip = el2(doc, "code");
22025
+ chip.textContent = v;
22026
+ dd.append(chip);
22027
+ }
22028
+ } else
22029
+ for (const v of f.value) {
22030
+ const line = el2(doc, "div");
22031
+ line.innerHTML = inlineMarkdown(v);
22032
+ dd.append(line);
22033
+ }
22034
+ row.append(dt, dd);
22035
+ dl.append(row);
22036
+ }
22037
+ return dl;
22038
+ }
22039
+ function optionTile(doc, o, options = {}) {
22040
+ const tile = el2(doc, "div", "knobs-wt-option");
22041
+ const head = options.head ?? el2(doc, "div");
22042
+ head.classList.add("knobs-wt-ohead");
22043
+ const kicker = el2(doc, "span", "knobs-wt-okicker");
22044
+ const n = el2(doc, "span");
22045
+ n.textContent = o.number;
22046
+ kicker.append(n);
22047
+ if (o.recommended) {
22048
+ const rec = el2(doc, "span", "knobs-wt-orec");
22049
+ rec.textContent = "Recommended";
22050
+ kicker.append(rec);
22051
+ }
22052
+ const title = el2(doc, "span", "knobs-wt-otitle");
22053
+ title.innerHTML = inlineMarkdown(o.title);
22054
+ head.append(kicker, title);
22055
+ tile.append(head);
22056
+ if (o.summary) {
22057
+ const p = el2(doc, "p", "knobs-wt-osum");
22058
+ p.innerHTML = inlineMarkdown(o.summary);
22059
+ tile.append(p);
22060
+ }
22061
+ options.media?.(tile);
22062
+ if (o.facts?.length)
22063
+ tile.append(renderFacts(doc, [{ facts: o.facts }]));
22064
+ return { tile, head, kicker };
22065
+ }
22066
+ function layoutOptions(root, tiles) {
22067
+ let cols = 0;
22068
+ const layout = () => {
22069
+ const width = root.clientWidth;
22070
+ if (!width)
22071
+ return;
22072
+ const next = optionColumns(tiles.length, width);
22073
+ if (next === cols)
22074
+ return;
22075
+ cols = next;
22076
+ root.style.setProperty("--kw-cols", String(cols));
22077
+ const rows = Math.ceil(tiles.length / cols);
22078
+ tiles.forEach((t, i) => {
22079
+ const row = Math.floor(i / cols);
22080
+ t.dataset.col = String(i % cols);
22081
+ t.dataset.row = String(row);
22082
+ t.toggleAttribute("data-end", i % cols === cols - 1);
22083
+ t.toggleAttribute("data-last-row", row === rows - 1);
22084
+ });
22085
+ };
22086
+ layout();
22087
+ const win = root.ownerDocument.defaultView;
22088
+ if (!win || typeof win.ResizeObserver !== "function")
22089
+ return () => {};
22090
+ const ro = new win.ResizeObserver(layout);
22091
+ ro.observe(root);
22092
+ return () => ro.disconnect();
22093
+ }
22094
+
22095
+ // src/ui/walkthrough.ts
21761
22096
  init_selection_pill();
21762
22097
 
21763
22098
  // src/ui/walkthrough-annotations.ts
@@ -21878,19 +22213,19 @@ init_selection_pill();
21878
22213
  var GLYPH = {
21879
22214
  caret: icon("caretRight", { small: true })
21880
22215
  };
21881
- var el2 = (doc, tag, cls) => {
22216
+ var el3 = (doc, tag, cls) => {
21882
22217
  const n = doc.createElement(tag);
21883
22218
  if (cls)
21884
22219
  n.className = cls;
21885
22220
  return n;
21886
22221
  };
21887
22222
  var text2 = (doc, tag, cls, value) => {
21888
- const n = el2(doc, tag, cls);
22223
+ const n = el3(doc, tag, cls);
21889
22224
  n.textContent = value;
21890
22225
  return n;
21891
22226
  };
21892
22227
  var prose = (doc, tag, cls, value) => {
21893
- const n = el2(doc, tag, cls);
22228
+ const n = el3(doc, tag, cls);
21894
22229
  n.innerHTML = inlineMarkdown(value);
21895
22230
  return n;
21896
22231
  };
@@ -21946,10 +22281,10 @@ function mountHistory(host, m, steps) {
21946
22281
  function mountStack(host, m, steps, findPR) {
21947
22282
  const doc = host.ownerDocument;
21948
22283
  const layout = stackLayout(m);
21949
- const root = el2(doc, "div", "knobs-wt-stack");
22284
+ const root = el3(doc, "div", "knobs-wt-stack");
21950
22285
  root.toggleAttribute("data-flat", layout.flat);
21951
22286
  const lane = (kind) => {
21952
- const s = el2(doc, "span", "knobs-wt-lane");
22287
+ const s = el3(doc, "span", "knobs-wt-lane");
21953
22288
  s.dataset.rail = kind;
21954
22289
  return s;
21955
22290
  };
@@ -21960,9 +22295,9 @@ function mountStack(host, m, steps, findPR) {
21960
22295
  };
21961
22296
  const from = layout.trunk ? 0 : 1;
21962
22297
  if (layout.trunk && layout.rows.length) {
21963
- const row = el2(doc, "div", "knobs-wt-srow");
22298
+ const row = el3(doc, "div", "knobs-wt-srow");
21964
22299
  row.dataset.trunk = "";
21965
- const lanes = el2(doc, "span", "knobs-wt-lanes");
22300
+ const lanes = el3(doc, "span", "knobs-wt-lanes");
21966
22301
  lanes.setAttribute("aria-hidden", "true");
21967
22302
  lanes.append(node(true));
21968
22303
  row.append(lanes, text2(doc, "span", "knobs-wt-trunk", layout.trunk));
@@ -21970,9 +22305,9 @@ function mountStack(host, m, steps, findPR) {
21970
22305
  }
21971
22306
  for (const r of layout.rows) {
21972
22307
  const it = r.item;
21973
- const row = el2(doc, "div", "knobs-wt-srow");
22308
+ const row = el3(doc, "div", "knobs-wt-srow");
21974
22309
  if (!layout.flat) {
21975
- const lanes = el2(doc, "span", "knobs-wt-lanes");
22310
+ const lanes = el3(doc, "span", "knobs-wt-lanes");
21976
22311
  lanes.setAttribute("aria-hidden", "true");
21977
22312
  for (let k = from;k < r.depth; k++)
21978
22313
  lanes.append(lane(r.rails[k] ? "pass" : "none"));
@@ -21981,8 +22316,8 @@ function mountStack(host, m, steps, findPR) {
21981
22316
  lanes.append(node(r.children > 0));
21982
22317
  row.append(lanes);
21983
22318
  }
21984
- const main = el2(doc, "div", "knobs-wt-smain");
21985
- const line = el2(doc, "div", "knobs-wt-sline");
22319
+ const main = el3(doc, "div", "knobs-wt-smain");
22320
+ const line = el3(doc, "div", "knobs-wt-sline");
21986
22321
  if (it.order)
21987
22322
  line.append(text2(doc, "span", "knobs-wt-sord", it.order));
21988
22323
  const known = findPR?.(it.pr.replace(/^#/, "")) ?? null;
@@ -22013,7 +22348,7 @@ function mountStack(host, m, steps, findPR) {
22013
22348
  main.append(line);
22014
22349
  if (it.summary)
22015
22350
  main.append(prose(doc, "p", "knobs-wt-ssummary", it.summary));
22016
- const meta = el2(doc, "div", "knobs-wt-smeta");
22351
+ const meta = el3(doc, "div", "knobs-wt-smeta");
22017
22352
  if (it.head) {
22018
22353
  const branch = r.offBase ? `${it.head} → ${r.offBase}` : it.head;
22019
22354
  meta.append(text2(doc, "span", "knobs-wt-sbranch", branch));
@@ -22038,7 +22373,7 @@ function mountStack(host, m, steps, findPR) {
22038
22373
  return { dispose: () => root.remove(), context: () => ({ kind: "stack" }) };
22039
22374
  }
22040
22375
  function delta(doc, d) {
22041
- const s = el2(doc, "span", "knobs-wt-delta");
22376
+ const s = el3(doc, "span", "knobs-wt-delta");
22042
22377
  if (typeof d === "string") {
22043
22378
  s.textContent = d;
22044
22379
  return s;
@@ -22078,13 +22413,13 @@ var cellAttrs = (td, c) => {
22078
22413
  function mountTable(host, m, steps) {
22079
22414
  const doc = host.ownerDocument;
22080
22415
  const win = doc.defaultView;
22081
- const box = el2(doc, "div", "knobs-wt-tablebox");
22082
- const scroller = el2(doc, "div", "knobs-wt-table");
22083
- const table = el2(doc, "table");
22416
+ const box = el3(doc, "div", "knobs-wt-tablebox");
22417
+ const scroller = el3(doc, "div", "knobs-wt-table");
22418
+ const table = el3(doc, "table");
22084
22419
  if (m.caption)
22085
22420
  table.append(text2(doc, "caption", "", m.caption));
22086
- const head = el2(doc, "thead");
22087
- const hr = el2(doc, "tr");
22421
+ const head = el3(doc, "thead");
22422
+ const hr = el3(doc, "tr");
22088
22423
  for (const c of m.columns) {
22089
22424
  const th = text2(doc, "th", "", c.label);
22090
22425
  th.scope = "col";
@@ -22092,11 +22427,11 @@ function mountTable(host, m, steps) {
22092
22427
  hr.append(th);
22093
22428
  }
22094
22429
  head.append(hr);
22095
- const body = el2(doc, "tbody");
22430
+ const body = el3(doc, "tbody");
22096
22431
  for (const r of m.rows) {
22097
- const tr = el2(doc, "tr");
22432
+ const tr = el3(doc, "tr");
22098
22433
  for (const c of m.columns) {
22099
- const td = el2(doc, "td");
22434
+ const td = el3(doc, "td");
22100
22435
  cellAttrs(td, c);
22101
22436
  const cell = r[c.key];
22102
22437
  if (cell) {
@@ -22111,24 +22446,24 @@ function mountTable(host, m, steps) {
22111
22446
  }
22112
22447
  table.append(head, body);
22113
22448
  scroller.append(table);
22114
- const stack = el2(doc, "div", "knobs-wt-tstack");
22449
+ const stack = el3(doc, "div", "knobs-wt-tstack");
22115
22450
  stack.setAttribute("role", "list");
22116
22451
  if (m.caption)
22117
22452
  stack.setAttribute("aria-label", m.caption);
22118
22453
  const primary = m.columns.find((c) => c.key === m.primary) ?? m.columns[0];
22119
22454
  for (const r of m.rows) {
22120
- const item = el2(doc, "div", "knobs-wt-trow");
22455
+ const item = el3(doc, "div", "knobs-wt-trow");
22121
22456
  item.setAttribute("role", "listitem");
22122
- const title = el2(doc, "div", "knobs-wt-ttitle");
22457
+ const title = el3(doc, "div", "knobs-wt-ttitle");
22123
22458
  if (primary) {
22124
22459
  cellAttrs(title, primary);
22125
22460
  const cell = r[primary.key];
22126
22461
  if (cell)
22127
22462
  cellContent(doc, title, cell);
22128
22463
  }
22129
- const line = el2(doc, "div", "knobs-wt-tline");
22464
+ const line = el3(doc, "div", "knobs-wt-tline");
22130
22465
  line.append(title);
22131
- const badges = el2(doc, "div", "knobs-wt-tbadges");
22466
+ const badges = el3(doc, "div", "knobs-wt-tbadges");
22132
22467
  for (const c of m.columns) {
22133
22468
  const cell = r[c.key];
22134
22469
  if (c !== primary && cell?.badge)
@@ -22137,14 +22472,14 @@ function mountTable(host, m, steps) {
22137
22472
  if (badges.childNodes.length)
22138
22473
  line.append(badges);
22139
22474
  item.append(line);
22140
- const terms = el2(doc, "dl", "knobs-wt-tterms");
22475
+ const terms = el3(doc, "dl", "knobs-wt-tterms");
22141
22476
  for (const c of m.columns) {
22142
22477
  if (c === primary)
22143
22478
  continue;
22144
22479
  const cell = r[c.key];
22145
22480
  if (!cell || cell.text === "" || cell.badge)
22146
22481
  continue;
22147
- const dd = el2(doc, "dd");
22482
+ const dd = el3(doc, "dd");
22148
22483
  cellAttrs(dd, c);
22149
22484
  dd.removeAttribute("data-align");
22150
22485
  cellContent(doc, dd, cell);
@@ -22210,12 +22545,12 @@ function mountTable(host, m, steps) {
22210
22545
  }
22211
22546
  function chapterPills(host, chapters, io) {
22212
22547
  const doc = host.ownerDocument;
22213
- const list = el2(doc, "div", "knobs-wt-chapters");
22548
+ const list = el3(doc, "div", "knobs-wt-chapters");
22214
22549
  list.setAttribute("role", "group");
22215
22550
  list.setAttribute("aria-label", "Chapters");
22216
22551
  let at = -2;
22217
22552
  const rows = chapters.map((c, i) => {
22218
- const b = el2(doc, "button", "knobs-wt-chapter");
22553
+ const b = el3(doc, "button", "knobs-wt-chapter");
22219
22554
  b.type = "button";
22220
22555
  b.append(text2(doc, "span", "knobs-wt-chapter-n", String(i + 1)));
22221
22556
  b.append(text2(doc, "span", "knobs-wt-chapter-label", c.label));
@@ -22231,7 +22566,8 @@ function chapterPills(host, chapters, io) {
22231
22566
  host.append(list);
22232
22567
  const pill = selectionPill(list, {
22233
22568
  selected: () => rows[at] ?? null,
22234
- rows: () => rows
22569
+ rows: () => rows,
22570
+ spring: SPRINGS.snappy
22235
22571
  });
22236
22572
  const select = (i) => {
22237
22573
  if (i === at)
@@ -22257,13 +22593,13 @@ function disclosure(host, options) {
22257
22593
  const doc = host.ownerDocument;
22258
22594
  const win = doc.defaultView;
22259
22595
  let open = options.open === true;
22260
- const root = el2(doc, "div", "knobs-wt-disc");
22261
- const toggle = el2(doc, "button", "knobs-wt-disc-toggle");
22596
+ const root = el3(doc, "div", "knobs-wt-disc");
22597
+ const toggle = el3(doc, "button", "knobs-wt-disc-toggle");
22262
22598
  toggle.type = "button";
22263
22599
  toggle.innerHTML = GLYPH.caret;
22264
22600
  toggle.append(text2(doc, "span", "", options.label));
22265
- const viewport = el2(doc, "div", "knobs-wt-disc-body");
22266
- const content = el2(doc, "div", "knobs-wt-disc-content");
22601
+ const viewport = el3(doc, "div", "knobs-wt-disc-body");
22602
+ const content = el3(doc, "div", "knobs-wt-disc-content");
22267
22603
  content.append(options.content);
22268
22604
  viewport.append(content);
22269
22605
  root.append(toggle, viewport);
@@ -22340,19 +22676,14 @@ var CHECK3 = "M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11
22340
22676
  var svg12 = (d, evenodd = false) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="12" height="12" fill="currentColor"${evenodd ? ' fill-rule="evenodd"' : ""} aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
22341
22677
  var prGlyph = (state) => state === "merged" ? svg12(PR_PATHS.merge) : state === "draft" ? svg12(DRAFT, true) : state === "closed" ? svg12(X) : svg12(PR_PATHS.pull);
22342
22678
  var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
22343
- function prStatus(status) {
22344
- const words = (status ?? "").trim().toLowerCase();
22345
- const first = /^[a-z]+/.exec(words)?.[0] ?? "";
22346
- const state = first in PR_STATE ? first : null;
22347
- return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
22348
- }
22679
+ var ISSUE = "M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-88a16,16,0,1,1-16-16A16,16,0,0,1,144,128Z";
22349
22680
  function prStateLabel(doc, state, checks, title) {
22350
- const s = el2(doc, "span", "knobs-wt-prstate");
22681
+ const s = el3(doc, "span", "knobs-wt-prstate");
22351
22682
  s.dataset.state = state;
22352
22683
  s.innerHTML = prGlyph(state);
22353
22684
  s.append(text2(doc, "span", "", PR_STATE[state]));
22354
22685
  if (checks) {
22355
- const c = el2(doc, "span", "knobs-wt-prchecks");
22686
+ const c = el3(doc, "span", "knobs-wt-prchecks");
22356
22687
  c.innerHTML = svg12(CHECK3);
22357
22688
  c.title = "Checks pass";
22358
22689
  c.setAttribute("aria-label", "checks pass");
@@ -22363,33 +22694,61 @@ function prStateLabel(doc, state, checks, title) {
22363
22694
  return s;
22364
22695
  }
22365
22696
  function prChip(doc, n, pr) {
22366
- const chip = el2(doc, pr.href ? "a" : "span", "knobs-wt-pr");
22697
+ const chip = el3(doc, pr.href ? "a" : "span", "knobs-wt-pr");
22367
22698
  if (pr.href) {
22368
22699
  chip.setAttribute("href", pr.href);
22369
22700
  chip.setAttribute("target", "_blank");
22370
22701
  chip.setAttribute("rel", "noreferrer");
22371
22702
  }
22703
+ const issue = pr.kind === "issue";
22704
+ if (issue)
22705
+ chip.dataset.kind = "issue";
22372
22706
  if (pr.state)
22373
22707
  chip.dataset.state = pr.state;
22374
- chip.innerHTML = prGlyph(pr.state);
22708
+ chip.innerHTML = issue ? svg12(ISSUE) : prGlyph(pr.state);
22375
22709
  chip.append(text2(doc, "span", "", `#${n}`));
22376
- const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
22377
- chip.title = tip;
22378
- chip.setAttribute("aria-label", `Pull request ${tip}`);
22710
+ if (pr.checks) {
22711
+ const c = el3(doc, "span", "knobs-wt-prchecks");
22712
+ c.innerHTML = svg12(CHECK3);
22713
+ chip.append(c);
22714
+ }
22715
+ const words = [
22716
+ `${issue ? "Issue" : "Pull request"} #${n}`,
22717
+ pr.title,
22718
+ pr.state ? PR_STATE[pr.state] : "",
22719
+ pr.checks ? "checks pass" : ""
22720
+ ].filter(Boolean);
22721
+ chip.title = words.slice(1).length ? `#${n} · ${words.slice(1).join(" · ")}` : `#${n}`;
22722
+ chip.setAttribute("aria-label", words.join(" · "));
22379
22723
  return chip;
22380
22724
  }
22381
22725
  var PR_URL = /^https?:\/\/[^/]+\/.+\/(?:pull|merge_requests)\/(\d+)\/?(?:[?#].*)?$/;
22382
- var PR_REF = /(^|[^\w&/#])#(\d{3,7})\b/g;
22726
+ var ISSUE_URL = /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/;
22727
+ var REF = /(^|[^\w&/#:])(issue:)?#(\d{3,7})\b/gi;
22728
+ var WHOLE_REF = /^(issue:)?#(\d{1,7})$/i;
22729
+ var KEEP = 'a,pre,button,.knobs-wt-pr,.knobs-wt-slot:not([data-kind="table"]),.knobs-wt-stack';
22383
22730
  function linkPRs(root, find) {
22384
22731
  const doc = root.ownerDocument;
22732
+ const chip = (n, kind, href) => prChip(doc, n, { ...kind === "issue" ? { kind } : {}, ...find(n, kind), ...href ? { href } : {} });
22385
22733
  for (const a of Array.from(root.querySelectorAll("a[href]"))) {
22386
- if (a.classList.contains("knobs-wt-pr") || a.closest(".knobs-wt-slot,button"))
22734
+ if (a.classList.contains("knobs-wt-pr") || a.parentElement?.closest(KEEP))
22735
+ continue;
22736
+ const url = a.getAttribute("href") ?? "";
22737
+ const pr = PR_URL.exec(url)?.[1];
22738
+ const n = pr ?? ISSUE_URL.exec(url)?.[1];
22739
+ if (!n)
22387
22740
  continue;
22388
- const n = PR_URL.exec(a.getAttribute("href") ?? "")?.[1];
22389
- const words = a.textContent?.trim() ?? "";
22390
- if (!n || !(words === `#${n}` || words === n || words === a.getAttribute("href")))
22741
+ const words = (a.textContent ?? "").trim().replace(/^issue:/i, "");
22742
+ if (!(words === `#${n}` || words === n || words === url))
22743
+ continue;
22744
+ a.replaceWith(chip(n, pr ? "pr" : "issue", a.href));
22745
+ }
22746
+ for (const c of Array.from(root.querySelectorAll("code"))) {
22747
+ if (c.closest("pre") || c.parentElement?.closest(KEEP))
22391
22748
  continue;
22392
- a.replaceWith(prChip(doc, n, { href: a.href, ...find(n) }));
22749
+ const m = WHOLE_REF.exec((c.textContent ?? "").trim());
22750
+ if (m)
22751
+ c.replaceWith(chip(m[2], m[1] ? "issue" : "pr"));
22393
22752
  }
22394
22753
  const walker = doc.createTreeWalker(root, 5);
22395
22754
  const hits = [];
@@ -22397,29 +22756,22 @@ function linkPRs(root, find) {
22397
22756
  if (t.nodeType !== 3)
22398
22757
  continue;
22399
22758
  const p = t.parentElement;
22400
- if (!p || p.closest("a,code,pre,button,.knobs-wt-slot,.knobs-wt-pr"))
22759
+ if (!p || p.closest(`${KEEP},code`))
22401
22760
  continue;
22402
- PR_REF.lastIndex = 0;
22403
- if (PR_REF.test(t.nodeValue ?? ""))
22761
+ REF.lastIndex = 0;
22762
+ if (REF.test(t.nodeValue ?? ""))
22404
22763
  hits.push(t);
22405
22764
  }
22406
22765
  for (const t of hits) {
22407
22766
  const value = t.nodeValue ?? "";
22408
22767
  const frag = doc.createDocumentFragment();
22409
22768
  let last = 0;
22410
- let changed = false;
22411
- PR_REF.lastIndex = 0;
22412
- for (let m = PR_REF.exec(value);m; m = PR_REF.exec(value)) {
22413
- const pr = find(m[2]);
22414
- if (!pr)
22415
- continue;
22769
+ REF.lastIndex = 0;
22770
+ for (let m = REF.exec(value);m; m = REF.exec(value)) {
22416
22771
  const at = m.index + m[1].length;
22417
- frag.append(value.slice(last, at), prChip(doc, m[2], pr));
22418
- last = at + m[2].length + 1;
22419
- changed = true;
22772
+ frag.append(value.slice(last, at), chip(m[3], m[2] ? "issue" : "pr"));
22773
+ last = m.index + m[0].length;
22420
22774
  }
22421
- if (!changed)
22422
- continue;
22423
22775
  frag.append(value.slice(last));
22424
22776
  t.replaceWith(frag);
22425
22777
  }
@@ -22438,22 +22790,26 @@ var snappy3 = springCss(SPRINGS.snappy);
22438
22790
  var calm = springCss(SPRINGS.calm);
22439
22791
  var state = springCss(SPRINGS.default);
22440
22792
  var quick = springCss(FADE);
22441
- var fade2 = "-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 1.5em),transparent)";
22442
- var walkthroughCss = `
22793
+ var fade2 = "-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 16px),transparent)";
22794
+ var walkthroughCss = `${foundationCss}
22443
22795
  :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
22444
22796
  .knobs-ui:is(.knobs-walkthrough,.knobs-wt-slot){--k-glass:0;--k-glow:0;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms}
22445
22797
  .knobs-ui.knobs-walkthrough{--kw-snappy:${snappy3.easing};--kw-snappy-ms:${snappy3.duration}ms;--kw-calm:${calm.easing};--kw-calm-ms:${calm.duration}ms;--kw-state:${state.easing};--kw-state-ms:${state.duration}ms;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms;
22446
- --kw-measure:var(--k-walkthrough-measure,68ch);--kw-width:var(--k-walkthrough-width,960px);--kw-rail-w:220px;--kw-rail-gap:calc(var(--_k-space-group) + var(--_k-space-column) / 1.5);--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
22798
+ --kw-measure:var(--_kr-measure);--kw-width:var(--k-walkthrough-width,960px);--kw-rail-w:220px;--kw-rail-gap:calc(var(--_k-space-group) + var(--_k-space-column) / 1.5);--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
22447
22799
  --kw-card:var(--_ku-pane-bg);--kw-card-ring:var(--_ku-pane-ring);--kw-under:var(--_k-bg-page);--kw-ink:var(--k-walkthrough-ink,#ff375f);--kw-strong:var(--_k-weight-label);
22448
22800
  container:knobs-wt / inline-size;display:block;width:100%;color:var(--_k-fg);font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;text-align:left;
22449
- letter-spacing:normal;-webkit-font-smoothing:antialiased}
22801
+ letter-spacing:var(--_kr-track);-webkit-font-smoothing:antialiased}
22450
22802
  .knobs-walkthrough [hidden]{display:none!important}
22803
+ /* Prose: wrapped to look set by hand, quotes hung in the margin (Safari). Data: figures that line
22804
+ up and can't be misread. */
22805
+ :where(.knobs-wt-summary,.knobs-wt-ssum,.knobs-wt-caption,.knobs-wt-more-text,.knobs-wt-fi-text,.knobs-wt-fi-answer,.knobs-wt-ocap,.knobs-wt-text,.knobs-wt-callout,.knobs-wt-ssummary,.knobs-wt-osum){text-wrap:pretty;hanging-punctuation:first allow-end last}
22806
+ :where(.knobs-wt-table table,.knobs-wt-tterms,.knobs-wt-facts,.knobs-wt-smeta,.knobs-wt-metaline,.knobs-wt-meta,.knobs-wt-pr,.knobs-wt-prstate,.knobs-wt-sord){font-feature-settings:var(--_kr-data)}
22451
22807
  /* (0,0,1): a host's element rules stop here, and every class rule below wins over it. */
22452
- :where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:inherit}
22453
- .knobs-walkthrough a{color:inherit;text-decoration:underline;text-decoration-color:color-mix(in srgb,currentColor 35%,transparent);text-underline-offset:2px;transition:text-decoration-color .15s}
22808
+ :where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:var(--_kr-track)}
22809
+ .knobs-walkthrough a{cursor:default;color:inherit;text-decoration:underline;text-decoration-color:color-mix(in srgb,currentColor 35%,transparent);text-underline-offset:2px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
22454
22810
  .knobs-walkthrough a:hover{text-decoration-color:currentColor}
22455
22811
  .knobs-walkthrough code,.knobs-wt-slot code{font:var(--_k-type-label) var(--_k-mono);padding:1px 5px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-fill)}
22456
- :where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-row,button.knobs-wt-stitle,.knobs-wt-chapter,.knobs-wt-disc-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
22812
+ :where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-row,button.knobs-wt-stitle,.knobs-wt-chapter,.knobs-wt-disc-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:var(--_kr-track);
22457
22813
  background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
22458
22814
 
22459
22815
  /* ——— layout: the contents rail sticky at the left edge (right if asked), and the column centred
@@ -22478,14 +22834,17 @@ var walkthroughCss = `
22478
22834
  .knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr){display:inline-flex;align-items:baseline;color:inherit;text-decoration:none;transition:color calc(var(--_k-hover-out) * 1ms)}
22479
22835
  .knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr):hover{color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22480
22836
  .knobs-wt-metaline svg{align-self:center;width:12px;height:12px;margin-left:2px}
22481
- /* A pull request: a small chip, its state's icon and number in a quiet tint of the state (open
22482
- the success hue, merged the violet series, draft and closed neutral), its title on hover. */
22483
- .knobs-ui .knobs-wt-pr{--kw-pr:var(--_k-fg-2);display:inline-flex;align-items:center;gap:3px;height:18px;padding:0 5px 0 4px;margin:0 1px;border-radius:calc(var(--_k-radius) / 2);vertical-align:1px;
22837
+ /* A pull request or issue: a capsule, its state's icon and number in a quiet tint of the state
22838
+ (open the success hue, merged the violet series, draft and closed faint, unknown neutral; a
22839
+ closed issue is done, so violet), a faint check where its checks pass, its title on hover. */
22840
+ .knobs-ui .knobs-wt-pr{--kw-pr:var(--_k-fg-2);display:inline-flex;align-items:center;gap:3px;height:18px;padding:0 6px 0 5px;margin:0 1px;border-radius:9px;vertical-align:1px;
22484
22841
  background:color-mix(in oklch,var(--kw-pr) 12%,transparent);color:var(--kw-pr);font:var(--_k-weight-label) var(--_k-type-small)/1 var(--_k-font);font-variant-numeric:tabular-nums;white-space:nowrap;text-decoration:none;
22485
22842
  transition:background calc(var(--_k-hover-out) * 1ms)}
22486
22843
  .knobs-ui .knobs-wt-pr[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
22487
22844
  .knobs-ui .knobs-wt-pr[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
22488
22845
  .knobs-ui .knobs-wt-pr:is([data-state="draft"],[data-state="closed"]){--kw-pr:var(--_k-fg-3)}
22846
+ .knobs-ui .knobs-wt-pr[data-kind="issue"][data-state="closed"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
22847
+ .knobs-ui .knobs-wt-pr .knobs-wt-prchecks{margin-left:0}
22489
22848
  .knobs-ui a.knobs-wt-pr:is(:hover,:focus-visible){background:color-mix(in oklch,var(--kw-pr) 22%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22490
22849
  .knobs-wt-pr svg{flex:none}
22491
22850
  .knobs-wt-metaline .knobs-wt-pr{align-self:center;margin:0}
@@ -22544,14 +22903,14 @@ var walkthroughCss = `
22544
22903
  .knobs-wt-fi-ask{max-width:var(--kw-measure);color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:pretty}
22545
22904
  .knobs-wt-fi-text{max-width:var(--kw-measure);color:var(--_k-fg);text-wrap:pretty}
22546
22905
  .knobs-wt-fi-answer{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
22547
- :where(.knobs-ui) .knobs-wt-fi-step{all:revert;box-sizing:border-box;font:inherit;color:inherit;background:transparent;border:0;margin:0;padding:0;cursor:pointer;outline:none;
22906
+ :where(.knobs-ui) .knobs-wt-fi-step{all:revert;box-sizing:border-box;font:inherit;color:inherit;background:transparent;border:0;margin:0;padding:0;cursor:default;outline:none;
22548
22907
  transition:color calc(var(--_k-hover-out) * 1ms)}
22549
22908
  .knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22550
22909
 
22551
22910
  /* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
22552
22911
  script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
22553
22912
  .knobs-wt-disc{display:flex;flex-direction:column;min-width:0}
22554
- .knobs-ui .knobs-wt-disc-toggle{display:inline-flex;align-items:center;gap:var(--_k-space-inside);width:max-content;height:20px;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);cursor:pointer;
22913
+ .knobs-ui .knobs-wt-disc-toggle{display:inline-flex;align-items:center;gap:var(--_k-space-inside);width:max-content;height:20px;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);cursor:default;
22555
22914
  border-radius:calc(var(--_k-radius) / 2);transition:color calc(var(--_k-hover-out) * 1ms)}
22556
22915
  .knobs-ui .knobs-wt-disc-toggle:is(:hover,:focus-visible),.knobs-wt-disc[data-open]>.knobs-wt-disc-toggle{color:var(--_k-fg-2);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22557
22916
  .knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
@@ -22578,7 +22937,7 @@ var walkthroughCss = `
22578
22937
  .knobs-wt-n[data-leaving]{opacity:0;filter:blur(3px);padding-block:0}
22579
22938
  .knobs-wt-n+.knobs-wt-n{box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
22580
22939
  .knobs-wt-n-meta{flex:none;min-width:44px;color:var(--_k-fg-3);font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-body) var(--_k-mono)}
22581
- .knobs-wt-n-meta[data-seek]{cursor:pointer}
22940
+ .knobs-wt-n-meta[data-seek]{cursor:default}
22582
22941
  .knobs-wt-n-meta[data-seek]:hover{color:var(--_k-fg)}
22583
22942
  .knobs-wt-n-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
22584
22943
  .knobs-wt-n-voice{height:30px;max-width:320px}
@@ -22595,7 +22954,7 @@ var walkthroughCss = `
22595
22954
  /* Collapsed: the launcher and the bar swap in place, a quick fade with a little scale and blur
22596
22955
  from the launcher's corner; the exit is the shorter. */
22597
22956
  :where(.knobs-ui) .knobs-wt-launch{all:revert;box-sizing:border-box;justify-self:end;align-self:end;display:inline-flex;align-items:center;gap:6px;height:32px;margin:0;padding:0 10px 0 9px;border:0;border-radius:var(--_k-round);
22598
- background:var(--_k-bg);color:var(--_k-fg-2);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font:var(--kw-strong) var(--_k-type-small)/1 var(--_k-font);cursor:pointer;outline:none;transform-origin:100% 100%;
22957
+ background:var(--_k-bg);color:var(--_k-fg-2);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font:var(--kw-strong) var(--_k-type-small)/1 var(--_k-font);cursor:default;outline:none;transform-origin:100% 100%;
22599
22958
  transition:color calc(var(--_k-hover-out) * 1ms),opacity var(--kw-quick-ms) var(--kw-quick),scale var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
22600
22959
  .knobs-ui .knobs-wt-launch:is(:hover,:focus-visible){color:var(--_k-fg)}
22601
22960
  .knobs-ui .knobs-wt-launch:focus-visible{box-shadow:var(--_k-focus-ring)}
@@ -22608,18 +22967,18 @@ var walkthroughCss = `
22608
22967
  box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay)}
22609
22968
  .knobs-wt-on{padding-top:3px;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);white-space:nowrap;overflow:hidden;${fade2}}
22610
22969
  .knobs-wt-row{display:flex;align-items:flex-end;gap:4px}
22611
- :where(.knobs-ui) :is(.knobs-wt-input,.knobs-wt-tool,.knobs-wt-x,.knobs-wt-back){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;
22970
+ :where(.knobs-ui) :is(.knobs-wt-input,.knobs-wt-tool,.knobs-wt-x,.knobs-wt-back){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:var(--_kr-track);background:transparent;border:0;
22612
22971
  border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
22613
22972
  .knobs-ui .knobs-wt-input{flex:1;min-width:0;height:32px;max-height:140px;padding:6px 0;resize:none;color:var(--_k-fg);caret-color:var(--_k-accent);line-height:var(--_k-leading-body);font-size:var(--_k-type-body)}
22614
22973
  .knobs-ui .knobs-wt-input::placeholder{color:var(--_k-fg-3)}
22615
22974
  .knobs-wt-attach{display:flex;gap:4px;align-items:center;align-self:center}
22616
22975
  .knobs-wt-attach:empty{display:none}
22617
22976
  .knobs-wt-chip-a{display:inline-flex;align-items:center;gap:2px;height:24px;padding:0 2px 0 9px;border-radius:var(--_k-round);background:var(--_k-fill-selected);font-size:var(--_k-type-small);white-space:nowrap}
22618
- .knobs-ui .knobs-wt-x{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;color:var(--_k-fg-3);cursor:pointer;transition:background .15s,color .15s}
22977
+ .knobs-ui .knobs-wt-x{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;color:var(--_k-fg-3);cursor:default;transition:background .15s,color .15s}
22619
22978
  .knobs-ui .knobs-wt-x:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
22620
22979
  .knobs-wt-x svg{width:12px;height:12px}
22621
22980
  .knobs-ui .knobs-wt-tool{flex:none;display:inline-flex;align-items:center;justify-content:center;gap:5px;min-width:32px;height:32px;padding:0 8px;border-radius:calc(var(--_k-radius) + 2px);
22622
- color:var(--_k-fg-2);cursor:pointer;transition:background .15s,color .15s}
22981
+ color:var(--_k-fg-2);cursor:default;transition:background .15s,color .15s}
22623
22982
  .knobs-ui .knobs-wt-tool:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
22624
22983
  .knobs-ui .knobs-wt-tool[aria-pressed="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
22625
22984
  .knobs-wt-tool svg{width:16px;height:16px}
@@ -22629,7 +22988,7 @@ var walkthroughCss = `
22629
22988
  .knobs-ui .knobs-wt-tool[data-send][data-ready]{background:var(--_k-accent);color:var(--_k-bg)}
22630
22989
  /* Back: in place over the bar for a few seconds after a note moves the page on. */
22631
22990
  .knobs-ui .knobs-wt-back{position:absolute;left:8px;bottom:calc(100% + 8px);display:inline-flex;align-items:center;gap:var(--_k-space-inside);max-width:min(360px,calc(100% - 16px));height:28px;padding:0 12px 0 9px;
22632
- border-radius:var(--_k-round);background:var(--_k-bg);color:var(--_k-fg);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font-size:var(--_k-type-small);font-weight:var(--kw-strong);cursor:pointer;
22991
+ border-radius:var(--_k-round);background:var(--_k-bg);color:var(--_k-fg);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font-size:var(--_k-type-small);font-weight:var(--kw-strong);cursor:default;
22633
22992
  opacity:0;scale:.96;filter:blur(3px);pointer-events:none;transition:opacity .11s cubic-bezier(.4,0,1,1),scale .11s cubic-bezier(.4,0,1,1),filter .11s cubic-bezier(.4,0,1,1)}
22634
22993
  .knobs-ui .knobs-wt-back[data-on]{opacity:1;scale:1;filter:none;pointer-events:auto;transition:opacity .15s cubic-bezier(.2,.8,.2,1),scale .15s cubic-bezier(.2,.8,.2,1),filter .15s cubic-bezier(.2,.8,.2,1)}
22635
22994
  .knobs-ui .knobs-wt-back:hover{background:var(--_k-fill-selected)}
@@ -22656,28 +23015,57 @@ var walkthroughCss = `
22656
23015
  .knobs-wt-step[data-full] .knobs-wt-body{flex:1;min-height:0}
22657
23016
  .knobs-wt-step[data-full] .knobs-wt-fit{height:100%;transition:none}
22658
23017
  .knobs-wt-step[data-full] .knobs-wt-frame{height:100%;aspect-ratio:auto}
23018
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"]>.knobs-wt-body{display:flex;flex-direction:column}
23019
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player{flex:1;min-height:0}
23020
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player-frame{flex:1;min-height:0;aspect-ratio:auto}
22659
23021
 
22660
- /* ——— a taste test: the options side by side, Kit's pick checked and the rest stepping back. On
22661
- the step's card, so no fill of their own ——— */
22662
- .knobs-wt-options{display:grid;grid-template-columns:repeat(var(--kw-n,2),minmax(0,1fr));gap:var(--_k-space-group) var(--_k-space-column)}
22663
- @container knobs-wt-step (max-width:640px){.knobs-wt-options{grid-template-columns:minmax(0,1fr)}}
22664
- .knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
22665
- .knobs-wt-option>:not(.knobs-wt-pick){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
22666
- .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-pick){opacity:.55}
22667
- .knobs-ui .knobs-wt-pick{display:flex;align-items:center;gap:8px;width:100%;min-height:28px;margin:0 -6px;width:calc(100% + 12px);padding:0 6px;border-radius:var(--_k-radius);cursor:pointer;color:var(--_k-fg-2);text-align:left;
22668
- transition:background calc(var(--_k-hover-out) * 1ms)}
22669
- .knobs-ui .knobs-wt-pick:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_k-fill);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22670
- .knobs-wt-letter{flex:none;font-weight:var(--kw-strong);color:var(--_k-fg)}
22671
- .knobs-wt-olabel{flex:1;min-width:0;font-weight:var(--kw-strong);color:var(--_k-fg)}
22672
- .knobs-wt-suggested{flex:none;font-size:var(--_k-type-small);color:var(--_k-fg-3)}
22673
- .knobs-wt-check{flex:none;display:flex;width:18px;height:18px;align-items:center;justify-content:center;border-radius:50%;background:var(--_k-fill);color:transparent;
22674
- transition:background var(--kw-snappy-ms) var(--kw-snappy),color var(--kw-snappy-ms) var(--kw-snappy)}
23022
+ /* ——— options: a decision, or a taste test when Kit picks. Columns that fill whole rows (4 → 4, 2
23023
+ or 1 across; 3 → 3 or 1), a hairline between neighbours and between rows, no card of their own.
23024
+ Each: a kicker ("Option 1 · Recommended", the mark a quiet word in text colour), its title, a
23025
+ sentence, its media and what changes as facts. A pick dims the rest ——— */
23026
+ .knobs-wt-options{--kw-ogap:var(--_k-space-group);display:grid;grid-template-columns:repeat(var(--kw-cols,1),minmax(0,1fr));gap:var(--kw-ogap);min-width:0}
23027
+ .knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0}
23028
+ .knobs-wt-option[data-col]:not([data-col="0"])::before{content:"";position:absolute;top:0;bottom:0;left:calc(var(--kw-ogap) / -2);width:1px;background:var(--_k-border-subtle);pointer-events:none}
23029
+ .knobs-wt-option[data-col]:not([data-row="0"])::before{top:calc(var(--kw-ogap) / -2)}
23030
+ .knobs-wt-option[data-col]:not([data-last-row])::before{bottom:calc(var(--kw-ogap) / -2)}
23031
+ .knobs-wt-option[data-col]:not([data-row="0"])::after{content:"";position:absolute;top:calc(var(--kw-ogap) / -2);left:calc(var(--kw-ogap) / -2);right:calc(var(--kw-ogap) / -2);height:1px;background:var(--_k-border-subtle);pointer-events:none}
23032
+ .knobs-wt-option[data-col="0"]::after{left:0}
23033
+ .knobs-wt-option[data-end]::after{right:0}
23034
+ .knobs-wt-option>:not(.knobs-wt-ohead){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
23035
+ .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-ohead){opacity:.55}
23036
+ .knobs-wt-ohead{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0;text-align:left}
23037
+ .knobs-wt-okicker{display:flex;align-items:center;gap:var(--_k-space-inside);min-width:0;height:var(--_k-leading-small);color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);white-space:nowrap}
23038
+ .knobs-wt-orec{color:var(--_k-fg);font-weight:var(--kw-strong)}
23039
+ .knobs-wt-orec::before{content:"·";margin-right:var(--_k-space-inside);color:var(--_k-fg-3);font-weight:var(--_k-weight)}
23040
+ .knobs-wt-otitle{color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:balance}
23041
+ .knobs-wt-osum{max-width:var(--kw-measure);color:var(--_k-fg-2)}
23042
+ /* Picking: the head is the radio, bled out so its words keep the column's edge; a fill under the
23043
+ pointer at once; a check at the kicker's end that fills when picked. */
23044
+ .knobs-ui .knobs-wt-pick{margin:-6px -8px;padding:6px 8px;border-radius:var(--_k-radius);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
23045
+ .knobs-ui .knobs-wt-pick:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
23046
+ .knobs-ui .knobs-wt-pick:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
23047
+ .knobs-wt-check{flex:none;display:flex;width:16px;height:16px;margin-left:auto;align-items:center;justify-content:center;border-radius:50%;box-shadow:inset 0 0 0 1px var(--_k-border-strong);color:transparent;
23048
+ transition:background var(--kw-snappy-ms) var(--kw-snappy),color var(--kw-snappy-ms) var(--kw-snappy),box-shadow var(--kw-snappy-ms) var(--kw-snappy)}
22675
23049
  .knobs-wt-check svg{width:12px;height:12px}
22676
- .knobs-wt-option[data-picked] .knobs-wt-check{background:var(--_k-accent);color:var(--_k-bg)}
22677
- .knobs-wt-key{display:inline-flex;align-items:center;justify-content:center;min-width:15px;height:15px;padding:0 3px;border-radius:4px;background:var(--_k-fill);color:var(--_k-fg-3);
22678
- font:var(--kw-strong) var(--_k-type-small)/1 var(--_k-font);opacity:.5;transition:opacity .15s}
22679
- .knobs-wt-step[data-current] .knobs-wt-key{opacity:1}
22680
- .knobs-wt-ocap{color:var(--_k-fg-2);max-width:var(--kw-measure)}
23050
+ .knobs-wt-option[data-picked] .knobs-wt-check{background:var(--_k-accent);box-shadow:none;color:var(--_k-on-accent)}
23051
+
23052
+ /* ——— facts: terms in a fixed gutter beside their values, sharing a baseline; groups under a
23053
+ heading, a hairline between them. Code and evidence are small mono chips. Too narrow for the
23054
+ gutter, each term sits over its value ——— */
23055
+ .knobs-wt-facts{--kw-term:144px;display:flex;flex-direction:column;gap:var(--_kr-block);min-width:0}
23056
+ .knobs-wt-fgroup{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0}
23057
+ .knobs-wt-fgroup+.knobs-wt-fgroup::before{content:"";position:absolute;left:0;right:0;top:calc(var(--_kr-block) / -2);height:1px;background:var(--_k-border-subtle);pointer-events:none}
23058
+ .knobs-wt-fhead{margin:0;color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:balance}
23059
+ .knobs-wt-fterms{display:grid;grid-template-columns:var(--kw-term) minmax(0,1fr);gap:var(--_k-space-sibling) var(--_kr-block);align-items:baseline;margin:0;min-width:0}
23060
+ .knobs-wt-fact{display:contents}
23061
+ .knobs-wt-fterms dt{margin:0;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);text-wrap:balance}
23062
+ .knobs-wt-fterms dd{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0;margin:0;color:var(--_k-fg-2);overflow-wrap:anywhere;text-wrap:pretty}
23063
+ .knobs-wt-fterms dd[data-code]{flex-flow:row wrap}
23064
+ .knobs-wt-fterms dd[data-code]>code{padding:2px 6px;line-height:var(--_k-leading-label);color:var(--_k-fg)}
23065
+ @container knobs-wt-slot (max-width:400px){
23066
+ .knobs-wt-fterms{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
23067
+ .knobs-wt-fact{display:flex;flex-direction:column;gap:var(--_k-space-inside)}
23068
+ }
22681
23069
 
22682
23070
  /* ——— the rail: progress, contents, export ——— */
22683
23071
  .knobs-wt-rail{position:sticky;top:var(--_k-pad-page);display:flex;flex-direction:column;gap:var(--_k-space-title);max-height:calc(100vh - var(--_k-pad-page) * 2);min-height:0;font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
@@ -22687,17 +23075,18 @@ var walkthroughCss = `
22687
23075
  .knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
22688
23076
  .knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
22689
23077
  .knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
22690
- /* The contents: every row on one text edge and in one colour, title rows (32 high, the control
22691
- radius) and the steps under them (20 high, half of it), bled out so their text sits on the
22692
- rail's edge; the selection pill slides to the current step, and base rows never brighten. */
22693
- .knobs-wt-toc{position:relative;flex:1 1 auto;min-height:0;overflow:auto;scrollbar-width:none;display:flex;flex-direction:column;gap:var(--_k-space-sibling);margin:0 calc(var(--_k-space-sibling) * -1);padding:0 0 8px}
23078
+ /* The contents: every row on one text edge and in one colour, title rows (28 high, the control
23079
+ radius) and the steps under them (20 high, half of it), bled 6 px out so their text sits on the
23080
+ rail's edge and the first title's words on the column's top line; a narrow fade where a title
23081
+ runs out of room; the selection pill slides to the current step, and base rows never brighten. */
23082
+ .knobs-wt-toc{position:relative;flex:1 1 auto;min-height:0;overflow:auto;scrollbar-width:none;display:flex;flex-direction:column;gap:var(--_k-space-sibling);margin:-4px -6px 0;padding:0 0 8px}
22694
23083
  .knobs-wt-toc::-webkit-scrollbar{display:none}
22695
23084
  .knobs-wt-toc-group{display:flex;flex-direction:column;min-width:0}
22696
- .knobs-ui .knobs-wt-toc-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;flex:none;width:100%;height:32px;
22697
- padding:0 var(--_k-space-sibling);border-radius:var(--_k-radius);color:var(--_k-fg-2);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight);text-align:left;white-space:nowrap;cursor:pointer;
22698
- transition:color calc(var(--_k-hover-out) * 1ms)}
23085
+ .knobs-ui .knobs-wt-toc-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;flex:none;width:100%;height:28px;
23086
+ padding:0 6px;border-radius:var(--_k-radius);color:var(--_k-fg-2);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight);text-align:left;white-space:nowrap;cursor:default;
23087
+ transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
22699
23088
  .knobs-ui .knobs-wt-toc-sub{height:20px;border-radius:calc(var(--_k-radius) / 2);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight)}
22700
- .knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
23089
+ .knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22701
23090
  .knobs-ui .knobs-wt-toc-row:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
22702
23091
  .knobs-wt-toc-end{display:flex;align-items:center;font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight);color:var(--_k-fg-3)}
22703
23092
  .knobs-wt-toc-label{min-width:0;overflow:hidden;${fade2}}
@@ -22709,7 +23098,7 @@ var walkthroughCss = `
22709
23098
  .knobs-wt-toc-glyph[data-done]{background:var(--kw-tone)}
22710
23099
  .knobs-wt-toc-glyph[data-tone="none"]{box-shadow:inset 0 0 0 1px var(--_k-border)}
22711
23100
  .knobs-wt-actions{display:flex;flex-wrap:wrap;gap:4px}
22712
- .knobs-ui .knobs-wt-btn{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
23101
+ .knobs-ui .knobs-wt-btn{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;white-space:nowrap;
22713
23102
  transition:background .15s,color .15s}
22714
23103
  .knobs-ui .knobs-wt-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
22715
23104
  .knobs-ui .knobs-wt-btn[data-primary]{background:var(--_k-fill-selected);color:var(--_k-fg)}
@@ -22730,11 +23119,11 @@ var walkthroughCss = `
22730
23119
  set apart by space, hairlines and their own colours. On a card (.knobs-surface) code drops its
22731
23120
  well for hairlines, so nothing draws a fill and a ring of its own there ——— */
22732
23121
  .knobs-ui.knobs-wt-slot{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0;container:knobs-wt-slot / inline-size;color:var(--_k-fg);
22733
- font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;--kw-measure:var(--k-walkthrough-measure,68ch);
23122
+ font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;--kw-measure:var(--_kr-measure);
22734
23123
  --kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));--kw-frame-r:var(--_k-radius-panel)}
22735
23124
  .knobs-surface .knobs-wt-slot{--kw-frame-r:calc(var(--_k-radius-panel) - 4px)}
22736
23125
  /* A slot's label: sentence case as written, small and faint, never a heading. */
22737
- .knobs-wt-slot-label{display:block;min-width:0;overflow:hidden;white-space:nowrap;font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);letter-spacing:normal;text-transform:none;color:var(--_k-fg-3);${fade2}}
23126
+ .knobs-wt-slot-label{display:block;min-width:0;overflow:hidden;white-space:nowrap;font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);text-transform:none;color:var(--_k-fg-3);${fade2}}
22738
23127
  .knobs-wt-body{min-width:0}
22739
23128
  /* Media mounting after the first paint, and a picture still decoding, come into focus: a fade with
22740
23129
  a small blur on the quick spring, from the top; at rest, nothing. */
@@ -22743,21 +23132,25 @@ var walkthroughCss = `
22743
23132
  /* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
22744
23133
  slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
22745
23134
  .knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
22746
- /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
22747
- sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
23135
+ /* A compare is as tall as its two pictures side by side at this width, in every mode (Wipe fuses
23136
+ them at one side's size, so nothing grows or re-measures), plus its mode bar, the line and gaps
22748
23137
  (92 px), and the frame strip with its gap (158 px). */
22749
23138
  .knobs-wt-fit{width:100%;height:calc(100cqw / 2 / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
22750
- .knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
22751
23139
  .knobs-wt-fit[data-strip]{--kw-chrome:158px}
22752
23140
  .knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
22753
23141
  .knobs-wt-frame{position:relative;width:100%;overflow:hidden;border-radius:var(--kw-frame-r);background:var(--_k-fill);aspect-ratio:var(--kw-aspect,16/9)}
22754
23142
  .knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
22755
23143
  .knobs-wt-frame[data-natural]{aspect-ratio:auto;background:transparent}
22756
23144
  .knobs-wt-frame[data-natural]>img{position:static;height:auto}
22757
- /* Diff, code and terminal: one plain container (no glass): a flat inset fill, a hairline ring, the
22758
- control radius and room inside, so a diff's buttons and the code under them can breathe. */
22759
- .knobs-wt-slot[data-kind="diff"]>.knobs-wt-body{padding:var(--_k-space-sibling) var(--_k-pad-card) var(--_k-pad-card);border-radius:var(--_k-radius);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle)}
22760
- .knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle);color:var(--_k-fg);
23145
+ /* Diff, code and terminal: one plain well, drawn by its hairline alone (a fill and a ring would be
23146
+ two edges for one box), the panel radius and room inside, so a diff's buttons and the code under
23147
+ them can breathe. */
23148
+ .knobs-wt-slot[data-kind="diff"]>.knobs-wt-body{padding:var(--_k-pad-card);border-radius:var(--_k-radius-panel);box-shadow:inset 0 0 0 1px var(--_k-border-subtle)}
23149
+ /* The diff inside it: its header row 28 high on the container's padding, a hairline under it, and
23150
+ no line of its own under the code (the container's ring closes it). */
23151
+ .knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"]){--kd-head:28px;--kd-x:0px}
23152
+ .knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"])::after{display:none}
23153
+ .knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius-panel);box-shadow:inset 0 0 0 1px var(--_k-border-subtle);color:var(--_k-fg);
22761
23154
  font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
22762
23155
  .knobs-surface :is(.knobs-wt-code,.knobs-wt-slot[data-kind="diff"]>.knobs-wt-body){padding-inline:0;border-radius:0;background:transparent;box-shadow:inset 0 1px 0 var(--_k-border-subtle),inset 0 -1px 0 var(--_k-border-subtle)}
22763
23156
  .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
@@ -22766,17 +23159,17 @@ var walkthroughCss = `
22766
23159
  .knobs-wt-out{color:var(--_k-fg-2)}
22767
23160
  .knobs-wt-metric{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
22768
23161
  .knobs-wt-mlabel{color:var(--_k-fg-2)}
22769
- .knobs-wt-mvalue{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--_k-space-sibling);font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
23162
+ .knobs-wt-mvalue{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--_k-space-sibling);font-feature-settings:var(--_kr-data)}
22770
23163
  .knobs-wt-mfrom{color:var(--_k-fg-3)}
22771
23164
  .knobs-wt-mto{font-size:var(--_k-type-title);line-height:var(--_k-leading-title);font-weight:var(--_k-weight-label)}
22772
23165
  .knobs-wt-mof{color:var(--_k-fg-3)}
22773
23166
  .knobs-wt-mdelta{--kw-tone:var(--_k-fg-2);font-size:var(--_k-type-small);font-weight:var(--_k-weight-label);color:var(--kw-tone)}
22774
23167
  .knobs-wt-mdelta[data-tone="good"]{--kw-tone:var(--kw-good)}
22775
23168
  .knobs-wt-mdelta[data-tone="bad"]{--kw-tone:var(--_k-danger)}
22776
- .knobs-wt-mtrack{position:relative;height:4px;border-radius:2px;background:color-mix(in srgb,var(--_k-fg) 8%,transparent)}
23169
+ .knobs-wt-mtrack{position:relative;height:4px;border-radius:2px;background:var(--_k-fill-selected)}
22777
23170
  .knobs-wt-mtrack>span{position:absolute;top:0;bottom:0;left:0;border-radius:inherit}
22778
23171
  .knobs-wt-mtrack>.knobs-wt-mafter{background:var(--_k-accent)}
22779
- .knobs-wt-mtrack>.knobs-wt-mbefore{background:color-mix(in srgb,var(--_k-fg) 30%,transparent)}
23172
+ .knobs-wt-mtrack>.knobs-wt-mbefore{background:var(--_k-fg-4)}
22780
23173
  .knobs-wt-callout{--kw-tone:var(--kw-info);display:flex;gap:var(--_k-space-sibling);max-width:var(--kw-measure);color:var(--_k-fg)}
22781
23174
  .knobs-wt-callout::before{content:"";flex:none;width:6px;height:6px;margin-top:7px;border-radius:50%;background:var(--kw-tone)}
22782
23175
  .knobs-wt-callout[data-tone="good"]{--kw-tone:var(--kw-good)}
@@ -22803,7 +23196,7 @@ var walkthroughCss = `
22803
23196
  .knobs-wt-mark:is([data-shape="dot"],[data-shape="arrow"]) .knobs-wt-mark-label{left:12px;bottom:auto;top:10px}
22804
23197
  .knobs-wt-mark[data-shape="arrow"] .knobs-wt-mark-label{left:auto;right:30px;top:auto;bottom:30px}
22805
23198
  .knobs-ui .knobs-wt-anno-toggle{all:revert;box-sizing:border-box;position:absolute;top:8px;right:8px;display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;margin:0;
22806
- border-radius:var(--_k-radius);background:color-mix(in srgb,var(--_k-bg) 72%,transparent);color:var(--_k-fg-2);cursor:pointer;opacity:.6;outline:none;
23199
+ border-radius:var(--_k-radius);background:color-mix(in srgb,var(--_k-bg) 72%,transparent);color:var(--_k-fg-2);cursor:default;opacity:.6;outline:none;
22807
23200
  transition:opacity calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
22808
23201
  .knobs-ui .knobs-wt-anno-toggle:is(:hover,:focus-visible){opacity:1;color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22809
23202
  .knobs-ui .knobs-wt-anno-toggle:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
@@ -22824,7 +23217,7 @@ var walkthroughCss = `
22824
23217
  .knobs-wt-srow{display:grid;grid-template-columns:auto minmax(0,1fr) auto;column-gap:10px;align-items:start;margin:0 -8px;padding:var(--kw-spt) 8px var(--kw-spb);
22825
23218
  border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
22826
23219
  .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
22827
- .knobs-wt-srow[data-go]{cursor:pointer}
23220
+ .knobs-wt-srow[data-go]{cursor:default}
22828
23221
  .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22829
23222
  .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
22830
23223
  .knobs-wt-lane{position:relative;flex:none;width:16px}
@@ -22840,9 +23233,9 @@ var walkthroughCss = `
22840
23233
  .knobs-ui .knobs-wt-spr{font:var(--_k-weight-label) var(--_k-type-label)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2);text-decoration:none;white-space:nowrap}
22841
23234
  .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
22842
23235
  .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
22843
- .knobs-ui button.knobs-wt-stitle{cursor:pointer;text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
23236
+ .knobs-ui button.knobs-wt-stitle{cursor:default;text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
22844
23237
  .knobs-ui :is(.knobs-wt-srow[data-go]:hover button.knobs-wt-stitle,button.knobs-wt-stitle:focus-visible){text-decoration-color:color-mix(in oklch,currentColor 45%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22845
- .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
23238
+ .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--kw-measure);color:var(--_k-fg-2)}
22846
23239
  .knobs-wt-smeta{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 12px;font:var(--_k-weight) var(--_k-type-small)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3)}
22847
23240
  .knobs-wt-delta{display:inline-flex;gap:6px}
22848
23241
  .knobs-wt-add{color:var(--_k-diff-add)}
@@ -22872,7 +23265,7 @@ var walkthroughCss = `
22872
23265
  .knobs-wt-tablebox{min-width:0}
22873
23266
  /* A row with a step goes there: the whole row, a wash under the pointer, no arrow, so rows with and
22874
23267
  without one line up. */
22875
- .knobs-wt-tablebox [data-go]{cursor:pointer;outline:none}
23268
+ .knobs-wt-tablebox [data-go]{cursor:default;outline:none}
22876
23269
  .knobs-wt-tablebox tr[data-go]>td,.knobs-wt-trow[data-go]{transition:background calc(var(--_k-hover-out) * 1ms)}
22877
23270
  .knobs-wt-tablebox tr[data-go]:is(:hover,:focus-visible)>td,.knobs-wt-trow[data-go]:is(:hover,:focus-visible){background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22878
23271
  .knobs-wt-tablebox [data-go]:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
@@ -22928,13 +23321,18 @@ var walkthroughCss = `
22928
23321
  .knobs-wt-body:has(>.knobs-wt-chapters){display:flex;flex-direction:column;gap:var(--_k-space-title)}
22929
23322
  .knobs-wt-chapters{position:relative;display:flex;flex-direction:column;margin:0 calc(var(--_k-space-sibling) * -1)}
22930
23323
  .knobs-ui .knobs-wt-chapter{position:relative;display:grid;grid-template-columns:2ch minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;height:32px;padding:0 var(--_k-space-sibling);
22931
- border-radius:var(--_k-radius);color:var(--_k-fg-2);text-align:left;white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
22932
- .knobs-ui .knobs-wt-chapter:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
23324
+ border-radius:var(--_k-radius);color:var(--_k-fg-2);text-align:left;white-space:nowrap;cursor:default;transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
23325
+ .knobs-ui .knobs-wt-chapter:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22933
23326
  .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
22934
23327
  .knobs-wt-chapter-n{font-size:var(--_k-type-small);color:var(--_k-fg-3)}
22935
23328
  .knobs-wt-chapter-label{min-width:0;overflow:hidden;${fade2}}
22936
23329
  .knobs-wt-chapter-time{font:var(--_k-weight) var(--_k-type-small)/1 var(--_k-mono);color:var(--_k-fg-3)}
22937
23330
  .knobs-pick-copy :is(.knobs-wt-chapter-n,.knobs-wt-chapter-time){color:var(--_k-fg-2)}
23331
+ /* Every hover in the walkthrough lands at once (hover-in, 0 ms) and eases out (hover-out): links,
23332
+ buttons, chips, rail, chapter, table and stack rows (its own; the pieces inside keep theirs). */
23333
+ :is(.knobs-walkthrough,.knobs-wt-slot) :is(a,button,[data-go])[class*="knobs-wt-"]:is(:hover,:focus-visible),
23334
+ .knobs-walkthrough a:not([class]):hover,
23335
+ :is(.knobs-walkthrough,.knobs-wt-slot) :is(.knobs-wt-srow,tr)[data-go]:is(:hover,:focus-visible) :is(td,.knobs-wt-stitle){transition-duration:calc(var(--_k-hover-in) * 1ms)}
22938
23336
  @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
22939
23337
  @media(prefers-reduced-motion:reduce){.knobs-wt-disc-content{transition:opacity .15s}}
22940
23338
  `;
@@ -23035,7 +23433,6 @@ async function mountCompare(host, m, look, alive, onEvent) {
23035
23433
  fit.style.setProperty("--kw-aspect", String(known ?? DEFAULT_ASPECT));
23036
23434
  const width = host.getBoundingClientRect().width;
23037
23435
  const mode = m.mode ?? (width && width < 560 ? "wipe" : "side");
23038
- fit.toggleAttribute("data-overlay", mode !== "side");
23039
23436
  host.append(fit);
23040
23437
  const [aspect, lib] = await Promise.all([
23041
23438
  known ? Promise.resolve(known) : probeAspect(doc, after.src),
@@ -23054,12 +23451,11 @@ async function mountCompare(host, m, look, alive, onEvent) {
23054
23451
  before: side(before, "Before"),
23055
23452
  after: side(after, "After"),
23056
23453
  mode,
23057
- timeline: true,
23454
+ timeline: mediaKind(before.src) === "video" || mediaKind(after.src) === "video",
23058
23455
  lazy: false,
23059
23456
  ...aspect ? { aspect } : {},
23060
23457
  ...m.strip ? { strip: true } : {},
23061
23458
  onMode: (next) => {
23062
- fit.toggleAttribute("data-overlay", next !== "side");
23063
23459
  onEvent?.({ what: "mode", detail: next });
23064
23460
  const layer = marks?.element;
23065
23461
  if (layer) {
@@ -23083,6 +23479,12 @@ async function mountCompare(host, m, look, alive, onEvent) {
23083
23479
  }
23084
23480
  });
23085
23481
  const stage = fit.querySelector(".knobs-cmp-stage");
23482
+ if (stage)
23483
+ doc.defaultView?.requestAnimationFrame(() => {
23484
+ const chrome = fit.clientHeight - stage.clientHeight;
23485
+ if (alive() && stage.clientHeight > 0 && chrome > 0)
23486
+ fit.style.setProperty("--kw-chrome", `${Math.round(chrome)}px`);
23487
+ });
23086
23488
  if (m.annotations?.length && stage) {
23087
23489
  const shape = { w: aspect ?? DEFAULT_ASPECT, h: 1 };
23088
23490
  marks = annotationLayer(stage, m.annotations, () => {
@@ -23158,45 +23560,44 @@ async function mountCompare(host, m, look, alive, onEvent) {
23158
23560
  var round2 = (v) => Math.round(v * 100) / 100;
23159
23561
  var MARK_GLIDE = 450;
23160
23562
  var zoomedIn = (z) => z.w < 0.999 || z.h < 0.999;
23161
- function mountVideo(host, m, onEvent) {
23563
+ async function mountVideo(host, m, look, alive, onEvent) {
23162
23564
  const doc = host.ownerDocument;
23163
- const frame = div(doc, "knobs-wt-frame");
23565
+ await Promise.resolve().then(() => init_player());
23566
+ if (!alive())
23567
+ return { dispose() {} };
23164
23568
  const aspect = parseAspect(m.aspect);
23165
- if (aspect)
23166
- frame.style.setProperty("--kw-aspect", String(aspect));
23167
- const v = doc.createElement("video");
23168
- v.controls = true;
23169
- v.loop = true;
23170
- v.muted = true;
23171
- v.playsInline = true;
23172
- v.preload = "metadata";
23173
- if (m.poster)
23174
- v.poster = m.poster;
23175
- if (!aspect)
23176
- v.addEventListener("loadedmetadata", () => v.videoWidth && frame.style.setProperty("--kw-aspect", String(v.videoWidth / v.videoHeight)), { once: true });
23177
- if (m.captions) {
23178
- const track = doc.createElement("track");
23179
- track.kind = "captions";
23180
- track.label = "Captions";
23181
- track.srclang = doc.documentElement.lang || "en";
23182
- track.default = true;
23183
- track.src = m.captions;
23184
- v.append(track);
23185
- }
23186
- v.src = m.src;
23187
- v.addEventListener("play", () => onEvent?.({ what: "play", time: round2(v.currentTime) }));
23188
- v.addEventListener("pause", () => onEvent?.({ what: "pause", time: round2(v.currentTime) }));
23189
- v.addEventListener("seeked", () => {
23190
- if (v.paused)
23191
- onEvent?.({ what: "seek", time: round2(v.currentTime) });
23569
+ let pills = null;
23570
+ let marking = null;
23571
+ const p = player(host, {
23572
+ ...look,
23573
+ src: m.src,
23574
+ ...m.poster ? { poster: m.poster } : {},
23575
+ ...m.captions ? { captions: m.captions } : {},
23576
+ ...aspect ? { aspect } : {},
23577
+ ...m.label ? { label: m.label } : {},
23578
+ ...m.chapters?.length ? {
23579
+ chapters: m.chapters.map((c) => ({
23580
+ label: c.label,
23581
+ start: c.start,
23582
+ ...c.id ? { id: c.id } : {}
23583
+ }))
23584
+ } : {},
23585
+ onPlay: (on, time) => {
23586
+ marking?.kick();
23587
+ onEvent?.({ what: on ? "play" : "pause", time: round2(time) });
23588
+ },
23589
+ onSeek: (time) => {
23590
+ pills?.sync();
23591
+ marking?.kick();
23592
+ onEvent?.({ what: "seek", time: round2(time) });
23593
+ }
23192
23594
  });
23193
- frame.append(v);
23194
- host.append(frame);
23195
- const pills = m.chapters?.length ? chapterPills(host, m.chapters, {
23196
- now: () => v.currentTime,
23595
+ const v = p.video;
23596
+ pills = m.chapters?.length ? chapterPills(host, m.chapters, {
23597
+ now: () => p.time,
23197
23598
  seek: (t) => {
23198
- v.currentTime = t;
23199
- v.play().catch(() => {});
23599
+ p.seek(t, { glide: true });
23600
+ p.play();
23200
23601
  },
23201
23602
  onEvent
23202
23603
  }) : null;
@@ -23204,36 +23605,33 @@ function mountVideo(host, m, onEvent) {
23204
23605
  if (pills)
23205
23606
  for (const type of ["timeupdate", "seeking", "seeked"])
23206
23607
  v.addEventListener(type, sync);
23608
+ const frame = p.element.querySelector(".knobs-player-frame") ?? p.element;
23207
23609
  const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
23208
23610
  rects: [
23209
23611
  contain({ w: frame.clientWidth, h: frame.clientHeight }, { w: v.videoWidth, h: v.videoHeight })
23210
23612
  ],
23211
- time: v.currentTime
23613
+ time: p.time
23212
23614
  })) : null;
23213
- const marking = marks ? follow(doc.defaultView, () => !v.paused, () => marks.update()) : null;
23214
- if (marking)
23215
- for (const type of ["play", "seeked", "loadedmetadata", "timeupdate"])
23216
- v.addEventListener(type, () => marking.kick());
23615
+ if (marks) {
23616
+ marking = follow(doc.defaultView, () => p.playing, () => marks.update());
23617
+ for (const type of ["seeked", "loadedmetadata", "timeupdate"])
23618
+ v.addEventListener(type, () => marking?.kick());
23619
+ }
23217
23620
  return {
23218
- pause: () => v.pause(),
23219
- seekBy: (d) => {
23220
- v.currentTime = Math.max(0, Math.min(v.duration || Infinity, v.currentTime + d));
23221
- },
23621
+ pause: () => p.pause(),
23622
+ seekBy: (d) => p.seekBy(d),
23222
23623
  context: () => ({
23223
23624
  kind: "video",
23224
- time: round2(v.currentTime),
23225
- ...Number.isFinite(v.duration) ? { duration: round2(v.duration) } : {},
23226
- playing: !v.paused,
23625
+ time: round2(p.time),
23626
+ ...p.duration ? { duration: round2(p.duration) } : {},
23627
+ playing: p.playing,
23227
23628
  ...pills?.current ? { chapter: pills.current.label } : {}
23228
23629
  }),
23229
23630
  dispose: () => {
23230
23631
  marking?.stop();
23231
23632
  marks?.dispose();
23232
23633
  pills?.dispose();
23233
- v.pause();
23234
- v.removeAttribute("src");
23235
- v.load();
23236
- frame.remove();
23634
+ p.dispose();
23237
23635
  }
23238
23636
  };
23239
23637
  }
@@ -23416,12 +23814,39 @@ function mountProse(host, m) {
23416
23814
  host.append(el);
23417
23815
  return { dispose: () => el.remove() };
23418
23816
  }
23817
+ function mountFacts(host, m) {
23818
+ const box = renderFacts(host.ownerDocument, m.groups);
23819
+ host.append(box);
23820
+ return { dispose: () => box.remove() };
23821
+ }
23822
+ function mountDecision(host, m, slot) {
23823
+ const root = div(host.ownerDocument, "knobs-wt-options");
23824
+ const slots = [];
23825
+ const tiles = m.options.map((o) => optionTile(host.ownerDocument, o, {
23826
+ media: (tile) => {
23827
+ if (o.media)
23828
+ slots.push(slot(tile, o.media));
23829
+ }
23830
+ }).tile);
23831
+ root.append(...tiles);
23832
+ host.append(root);
23833
+ const unlayout = layoutOptions(root, tiles);
23834
+ return {
23835
+ pause: () => slots.forEach((s) => s.pause()),
23836
+ dispose: () => {
23837
+ unlayout();
23838
+ slots.forEach((s) => s.dispose());
23839
+ root.remove();
23840
+ }
23841
+ };
23842
+ }
23419
23843
  var HEAVY = new Set(["compare", "video", "diff", "mount"]);
23420
23844
  var RELEASES = new Set(["compare", "video"]);
23421
- var labelOf3 = (m) => ("label" in m) && m.kind !== "metric" ? m.label : undefined;
23422
- function mediaSlot(container, options) {
23845
+ var labelOf3 = (m) => m.kind === "facts" ? m.title : ("label" in m) && m.kind !== "metric" ? m.label : undefined;
23846
+ function mediaSlot(container, given) {
23423
23847
  const doc = container.ownerDocument;
23424
23848
  injectWalkthroughStyles(doc);
23849
+ const options = reviewLook(given, container);
23425
23850
  const look = {
23426
23851
  ...options.preset ? { preset: options.preset } : {},
23427
23852
  ...options.theme ? { theme: options.theme } : {}
@@ -23431,7 +23856,7 @@ function mediaSlot(container, options) {
23431
23856
  root.dataset.kind = m.kind;
23432
23857
  applyLook2(root, options);
23433
23858
  const label = labelOf3(m);
23434
- if (label && (m.kind === "video" || m.kind === "image" || m.kind === "code" || m.kind === "terminal")) {
23859
+ if (label && (m.kind === "video" || m.kind === "image" || m.kind === "code" || m.kind === "terminal" || m.kind === "facts")) {
23435
23860
  const l = doc.createElement("span");
23436
23861
  l.className = "knobs-wt-slot-label";
23437
23862
  l.textContent = label;
@@ -23494,7 +23919,7 @@ function mediaSlot(container, options) {
23494
23919
  case "diff":
23495
23920
  return void mountDiff(host, m, look, alive, options.onEvent).then(done, fail("diff"));
23496
23921
  case "video":
23497
- return done(mountVideo(host, m, options.onEvent));
23922
+ return void mountVideo(host, m, look, alive, options.onEvent).then(done, fail("video"));
23498
23923
  case "image":
23499
23924
  return done(mountImage(host, m));
23500
23925
  case "code":
@@ -23511,6 +23936,16 @@ function mediaSlot(container, options) {
23511
23936
  return done(mountStack(host, m, options.steps, options.findPR));
23512
23937
  case "table":
23513
23938
  return done(mountTable(host, m, options.steps));
23939
+ case "facts":
23940
+ return done(mountFacts(host, m));
23941
+ case "decision":
23942
+ return done(mountDecision(host, m, (tile, media) => mediaSlot(tile, {
23943
+ ...look,
23944
+ media,
23945
+ ...options.lazy === false ? { lazy: false } : {},
23946
+ ...options.steps ? { steps: options.steps } : {},
23947
+ ...options.onEvent ? { onEvent: options.onEvent } : {}
23948
+ })));
23514
23949
  case "mount": {
23515
23950
  const cleanup = m.mount(host);
23516
23951
  return done({ dispose: () => cleanup ? cleanup() : host.textContent = "" });
@@ -23563,16 +23998,35 @@ function mediaSlot(container, options) {
23563
23998
  };
23564
23999
  }
23565
24000
  var CHECK4 = icon("check", { small: true });
24001
+ function facts(container, options) {
24002
+ const { title, facts: list, groups, ...look } = options;
24003
+ return mediaSlot(container, {
24004
+ ...look,
24005
+ media: {
24006
+ kind: "facts",
24007
+ ...title ? { title } : {},
24008
+ ...list ? { facts: list } : {},
24009
+ ...groups ? { groups } : {}
24010
+ }
24011
+ });
24012
+ }
24013
+ function decision(container, options) {
24014
+ const { options: list, noun, ...rest } = options;
24015
+ return mediaSlot(container, {
24016
+ ...rest,
24017
+ media: { kind: "decision", options: list, ...noun ? { noun } : {} }
24018
+ });
24019
+ }
23566
24020
 
23567
24021
  // src/ui/walkthrough.ts
23568
- var el3 = (doc, tag, cls) => {
24022
+ var el4 = (doc, tag, cls) => {
23569
24023
  const n = doc.createElement(tag);
23570
24024
  if (cls)
23571
24025
  n.className = cls;
23572
24026
  return n;
23573
24027
  };
23574
24028
  var prose2 = (doc, tag, cls, text) => {
23575
- const n = el3(doc, tag, cls);
24029
+ const n = el4(doc, tag, cls);
23576
24030
  n.innerHTML = inlineMarkdown(text);
23577
24031
  return n;
23578
24032
  };
@@ -23597,9 +24051,10 @@ var DOCK_GAP = 12;
23597
24051
  var CALM2 = springMotion(SPRINGS.calm);
23598
24052
  var SMOOTH = springMotion(SPRINGS.smooth);
23599
24053
  var BACK_MS = 4500;
23600
- function tasteTest(container, options) {
24054
+ function tasteTest(container, given) {
23601
24055
  const doc = container.ownerDocument;
23602
24056
  injectWalkthroughStyles(doc);
24057
+ const options = reviewLook(given, container);
23603
24058
  const look = lookOf2(options);
23604
24059
  const list = options.options.map((o, i) => ("letter" in o) ? o : {
23605
24060
  id: o.id ?? String.fromCharCode(97 + i),
@@ -23607,66 +24062,55 @@ function tasteTest(container, options) {
23607
24062
  letter: String.fromCharCode(65 + i),
23608
24063
  ...o.caption ? { caption: o.caption } : {},
23609
24064
  ...o.media !== undefined ? { media: fillMedia(o.media) } : {},
23610
- suggested: o.suggested === true
24065
+ ...o.facts ? { facts: fillFacts(o.facts) } : {},
24066
+ suggested: o.suggested === true || o.recommended === true
23611
24067
  });
23612
24068
  const pickable = options.pickable !== false;
23613
24069
  let picked = pickable ? options.picked ?? null : null;
23614
- const root = el3(doc, "div", "knobs-wt-options");
24070
+ const root = el4(doc, "div", "knobs-wt-options");
23615
24071
  if (pickable) {
23616
24072
  root.setAttribute("role", "radiogroup");
23617
24073
  root.setAttribute("aria-label", "Options");
23618
24074
  } else
23619
24075
  root.dataset.static = "";
23620
- root.style.setProperty("--kw-n", String(Math.min(list.length, 3)));
23621
24076
  if (!container.closest(".knobs-ui"))
23622
24077
  applyLook2(root, options);
23623
24078
  const slots = [];
23624
24079
  const tiles = list.map((o, i) => {
23625
- const tile = el3(doc, "div", "knobs-wt-option");
23626
- if (!pickable) {
23627
- const name = el3(doc, "span", "knobs-wt-slot-label");
23628
- name.textContent = o.label;
23629
- tile.append(name);
23630
- }
23631
- const pick = el3(doc, "button", "knobs-wt-pick");
23632
- pick.type = "button";
23633
- pick.setAttribute("role", "radio");
23634
- if (i < 9)
23635
- pick.setAttribute("aria-keyshortcuts", String(i + 1));
23636
- const letter = el3(doc, "span", "knobs-wt-letter");
23637
- letter.textContent = o.letter;
23638
- const name = el3(doc, "span", "knobs-wt-olabel");
23639
- name.textContent = o.label;
23640
- pick.append(letter, name);
23641
- if (o.suggested) {
23642
- const s = el3(doc, "span", "knobs-wt-suggested");
23643
- s.textContent = "Suggested";
23644
- pick.append(s);
23645
- }
23646
- if (i < 9) {
23647
- const key = el3(doc, "span", "knobs-wt-key");
23648
- key.textContent = String(i + 1);
23649
- pick.append(key);
23650
- }
23651
- const check = el3(doc, "span", "knobs-wt-check");
23652
- check.innerHTML = CHECK4;
23653
- pick.append(check);
23654
- pick.addEventListener("click", () => {
23655
- const next = picked === o.id ? null : o.id;
23656
- api.pick(next);
23657
- options.onPick?.(next);
24080
+ const button = pickable ? el4(doc, "button", "knobs-wt-pick") : undefined;
24081
+ const { tile, kicker } = optionTile(doc, {
24082
+ number: `Option ${i + 1}`,
24083
+ title: o.label,
24084
+ ...o.caption ? { summary: o.caption } : {},
24085
+ recommended: o.suggested,
24086
+ ...o.facts ? { facts: o.facts } : {}
24087
+ }, {
24088
+ ...button ? { head: button } : {},
24089
+ media: (t) => {
24090
+ if (o.media)
24091
+ slots.push(mediaSlot(t, {
24092
+ ...look,
24093
+ media: o.media,
24094
+ ...options.lazy === false ? { lazy: false } : {},
24095
+ ...options.steps ? { steps: options.steps } : {}
24096
+ }));
24097
+ }
23658
24098
  });
23659
- if (pickable)
23660
- tile.append(pick);
23661
- if (o.media)
23662
- slots.push(mediaSlot(tile, {
23663
- ...look,
23664
- media: o.media,
23665
- ...options.lazy === false ? { lazy: false } : {},
23666
- ...options.steps ? { steps: options.steps } : {}
23667
- }));
23668
- if (o.caption)
23669
- tile.append(prose2(doc, "p", "knobs-wt-ocap", o.caption));
24099
+ const pick = button ?? el4(doc, "button");
24100
+ if (button) {
24101
+ button.type = "button";
24102
+ button.setAttribute("role", "radio");
24103
+ if (i < 9)
24104
+ button.setAttribute("aria-keyshortcuts", String(i + 1));
24105
+ const check = el4(doc, "span", "knobs-wt-check");
24106
+ check.innerHTML = CHECK4;
24107
+ kicker.append(check);
24108
+ button.addEventListener("click", () => {
24109
+ const next = picked === o.id ? null : o.id;
24110
+ api.pick(next);
24111
+ options.onPick?.(next);
24112
+ });
24113
+ }
23670
24114
  root.append(tile);
23671
24115
  return { tile, pick };
23672
24116
  });
@@ -23680,6 +24124,7 @@ function tasteTest(container, options) {
23680
24124
  };
23681
24125
  sync();
23682
24126
  container.append(root);
24127
+ const unlayout = layoutOptions(root, tiles.map((t) => t.tile));
23683
24128
  const api = {
23684
24129
  element: root,
23685
24130
  get picked() {
@@ -23692,6 +24137,7 @@ function tasteTest(container, options) {
23692
24137
  },
23693
24138
  pause: () => slots.forEach((s) => s.pause()),
23694
24139
  dispose() {
24140
+ unlayout();
23695
24141
  slots.forEach((s) => s.dispose());
23696
24142
  root.remove();
23697
24143
  }
@@ -23708,13 +24154,14 @@ function scrollerOf(el) {
23708
24154
  }
23709
24155
  return doc.scrollingElement ?? doc.documentElement;
23710
24156
  }
23711
- function walkthrough(container, options) {
24157
+ function walkthrough(container, given) {
23712
24158
  const doc = container.ownerDocument;
23713
24159
  const win = doc.defaultView;
23714
24160
  injectWalkthroughStyles(doc);
24161
+ const options = reviewLook(given, container);
23715
24162
  const look = lookOf2(options);
23716
24163
  const storage = options.storage === undefined ? browserStorage() : options.storage;
23717
- const root = el3(doc, "div", "knobs-walkthrough");
24164
+ const root = el4(doc, "div", "knobs-walkthrough");
23718
24165
  applyLook2(root, options);
23719
24166
  container.append(root);
23720
24167
  let page = normalizeWalkthrough(options.doc);
@@ -23927,7 +24374,7 @@ function walkthrough(container, options) {
23927
24374
  return true;
23928
24375
  return false;
23929
24376
  };
23930
- const scrim = el3(doc, "div", "knobs-wt-scrim");
24377
+ const scrim = el4(doc, "div", "knobs-wt-scrim");
23931
24378
  scrim.addEventListener("click", () => setFull(false));
23932
24379
  const fullRect = () => ({
23933
24380
  x: DOCK_INSET,
@@ -23977,7 +24424,7 @@ function walkthrough(container, options) {
23977
24424
  }
23978
24425
  if (!full) {
23979
24426
  const r = v.card.getBoundingClientRect();
23980
- const hold = el3(doc, "div", "knobs-wt-hold-card");
24427
+ const hold = el4(doc, "div", "knobs-wt-hold-card");
23981
24428
  hold.style.height = `${r.height}px`;
23982
24429
  v.card.before(hold);
23983
24430
  v.card.dataset.full = "";
@@ -24127,7 +24574,7 @@ function walkthrough(container, options) {
24127
24574
  toast("Couldn't reach the microphone", { tone: "warn", look, doc });
24128
24575
  }
24129
24576
  };
24130
- const backPill = el3(doc, "button", "knobs-wt-back");
24577
+ const backPill = el4(doc, "button", "knobs-wt-back");
24131
24578
  backPill.type = "button";
24132
24579
  let backTo = null;
24133
24580
  let backTimer = 0;
@@ -24147,7 +24594,7 @@ function walkthrough(container, options) {
24147
24594
  return;
24148
24595
  backTo = key;
24149
24596
  backPill.innerHTML = ICON.back;
24150
- const words = el3(doc, "span", "knobs-wt-back-label");
24597
+ const words = el4(doc, "span", "knobs-wt-back-label");
24151
24598
  words.textContent = `Back to ${plainMarkdown(v.step.title)}`;
24152
24599
  backPill.append(words);
24153
24600
  backPill.dataset.on = "";
@@ -24155,16 +24602,16 @@ function walkthrough(container, options) {
24155
24602
  backTimer = win.setTimeout(hideBack, BACK_MS);
24156
24603
  };
24157
24604
  const bar = (() => {
24158
- const dock = el3(doc, "div", "knobs-wt-dock");
24159
- const box = el3(doc, "div", "knobs-wt-notebar");
24160
- const on = el3(doc, "div", "knobs-wt-on");
24161
- const row = el3(doc, "div", "knobs-wt-row");
24162
- const input = el3(doc, "textarea", "knobs-wt-input");
24605
+ const dock = el4(doc, "div", "knobs-wt-dock");
24606
+ const box = el4(doc, "div", "knobs-wt-notebar");
24607
+ const on = el4(doc, "div", "knobs-wt-on");
24608
+ const row = el4(doc, "div", "knobs-wt-row");
24609
+ const input = el4(doc, "textarea", "knobs-wt-input");
24163
24610
  input.rows = 1;
24164
24611
  input.setAttribute("aria-label", "Note");
24165
- const attach = el3(doc, "div", "knobs-wt-attach");
24612
+ const attach = el4(doc, "div", "knobs-wt-attach");
24166
24613
  const btn = (icon, label, run) => {
24167
- const b = el3(doc, "button", "knobs-wt-tool");
24614
+ const b = el4(doc, "button", "knobs-wt-tool");
24168
24615
  b.type = "button";
24169
24616
  b.innerHTML = icon;
24170
24617
  b.setAttribute("aria-label", label);
@@ -24175,17 +24622,17 @@ function walkthrough(container, options) {
24175
24622
  const fullBtn = btn(ICON.full, "Full window · f", () => setFull(!full));
24176
24623
  const penBtn = btn(ICON.pen, "Draw on the media", () => setInk(!ink.on));
24177
24624
  const micBtn = btn(ICON.mic, "Record a voice note", () => void toggleVoice());
24178
- const micTime = el3(doc, "span", "knobs-wt-rec");
24625
+ const micTime = el4(doc, "span", "knobs-wt-rec");
24179
24626
  micBtn.append(micTime);
24180
24627
  const sendBtn = btn(ICON.send, "Send · Enter", () => send());
24181
24628
  sendBtn.dataset.send = "";
24182
24629
  row.append(input, attach, fullBtn, penBtn, micBtn, sendBtn);
24183
24630
  box.append(on, row);
24184
- const launch = el3(doc, "button", "knobs-wt-launch");
24631
+ const launch = el4(doc, "button", "knobs-wt-launch");
24185
24632
  launch.type = "button";
24186
24633
  launch.innerHTML = ICON.pen;
24187
24634
  launch.append("Note");
24188
- const kbd = el3(doc, "kbd");
24635
+ const kbd = el4(doc, "kbd");
24189
24636
  kbd.textContent = "n";
24190
24637
  launch.append(kbd);
24191
24638
  launch.setAttribute("aria-label", "Write a note (n)");
@@ -24237,9 +24684,9 @@ function walkthrough(container, options) {
24237
24684
  }
24238
24685
  });
24239
24686
  const chip = (text, clear) => {
24240
- const c = el3(doc, "span", "knobs-wt-chip-a");
24687
+ const c = el4(doc, "span", "knobs-wt-chip-a");
24241
24688
  c.append(text);
24242
- const x = el3(doc, "button", "knobs-wt-x");
24689
+ const x = el4(doc, "button", "knobs-wt-x");
24243
24690
  x.type = "button";
24244
24691
  x.innerHTML = ICON.close;
24245
24692
  x.setAttribute("aria-label", `Remove ${text}`);
@@ -24388,9 +24835,9 @@ function walkthrough(container, options) {
24388
24835
  }
24389
24836
  };
24390
24837
  const renderNote = (v, n, entering = false) => {
24391
- const row = el3(doc, "div", "knobs-wt-n");
24838
+ const row = el4(doc, "div", "knobs-wt-n");
24392
24839
  row.dataset.id = n.id;
24393
- const meta = el3(doc, "span", "knobs-wt-n-meta");
24840
+ const meta = el4(doc, "span", "knobs-wt-n-meta");
24394
24841
  const m = n.context.media;
24395
24842
  meta.textContent = m?.time !== undefined ? formatTime(m.time) : m?.file ? m.file.split("/").pop() : "·";
24396
24843
  if (m?.time !== undefined) {
@@ -24406,11 +24853,11 @@ function walkthrough(container, options) {
24406
24853
  });
24407
24854
  }
24408
24855
  row.append(meta);
24409
- const body = el3(doc, "div", "knobs-wt-n-body");
24856
+ const body = el4(doc, "div", "knobs-wt-n-body");
24410
24857
  if (n.text)
24411
24858
  body.append(prose2(doc, "p", "knobs-wt-n-text", n.text));
24412
24859
  if (n.voice) {
24413
- const a = el3(doc, "audio", "knobs-wt-n-voice");
24860
+ const a = el4(doc, "audio", "knobs-wt-n-voice");
24414
24861
  a.controls = true;
24415
24862
  a.preload = "none";
24416
24863
  a.src = n.voice.url;
@@ -24430,7 +24877,7 @@ function walkthrough(container, options) {
24430
24877
  body.append(thumb);
24431
24878
  }
24432
24879
  row.append(body);
24433
- const x = el3(doc, "button", "knobs-wt-x");
24880
+ const x = el4(doc, "button", "knobs-wt-x");
24434
24881
  x.type = "button";
24435
24882
  x.innerHTML = ICON.close;
24436
24883
  x.setAttribute("aria-label", "Delete this note");
@@ -24470,22 +24917,23 @@ function walkthrough(container, options) {
24470
24917
  row.addEventListener("transitionend", done, { once: true });
24471
24918
  win.setTimeout(done, 600);
24472
24919
  };
24473
- const metaLine = (cls) => el3(doc, "div", `knobs-wt-metaline ${cls}`);
24920
+ const metaLine = (cls) => el4(doc, "div", `knobs-wt-metaline ${cls}`);
24474
24921
  const metaText = (line, text, cls) => {
24475
- const s = el3(doc, "span", cls);
24922
+ const s = el4(doc, "span", cls);
24476
24923
  s.textContent = text;
24477
24924
  line.append(s);
24478
24925
  return s;
24479
24926
  };
24480
- const findPRIn = (n) => findPR(page.prs, n, page.repo);
24927
+ const findPRIn = (n, kind = "pr") => findPR(page.prs, n, page.repo, kind);
24481
24928
  const metaLink = (line, text, href) => {
24482
24929
  const n = prNumber(href);
24483
- if (n) {
24484
- const chip = prChip(doc, n, { href, ...findPRIn(n) });
24930
+ const issue = n ? null : issueNumber(href);
24931
+ if (n || issue) {
24932
+ const chip = n ? prChip(doc, n, { ...findPRIn(n), href }) : prChip(doc, issue, { ...findPRIn(issue, "issue"), href });
24485
24933
  line.append(chip);
24486
24934
  return chip;
24487
24935
  }
24488
- const a = el3(doc, "a");
24936
+ const a = el4(doc, "a");
24489
24937
  a.href = href;
24490
24938
  a.target = "_blank";
24491
24939
  a.rel = "noreferrer";
@@ -24495,9 +24943,9 @@ function walkthrough(container, options) {
24495
24943
  return a;
24496
24944
  };
24497
24945
  const feedItem = (f, now) => {
24498
- const li = el3(doc, "li", "knobs-wt-fi");
24946
+ const li = el4(doc, "li", "knobs-wt-fi");
24499
24947
  li.dataset.kind = f.kind;
24500
- const q = el3(doc, "div", "knobs-wt-fi-q");
24948
+ const q = el4(doc, "div", "knobs-wt-fi-q");
24501
24949
  const meta = metaLine("knobs-wt-fi-meta");
24502
24950
  if (f.at !== null) {
24503
24951
  const when = metaText(meta, ago(f.at, now));
@@ -24506,7 +24954,7 @@ function walkthrough(container, options) {
24506
24954
  const step = f.step ? page.steps.find((s) => s.key === f.step) : undefined;
24507
24955
  if (step) {
24508
24956
  const section = page.sections.find((s) => s.id === step.section);
24509
- const b = el3(doc, "button", "knobs-wt-fi-step");
24957
+ const b = el4(doc, "button", "knobs-wt-fi-step");
24510
24958
  b.type = "button";
24511
24959
  b.textContent = section?.link?.label ?? plainMarkdown(step.title);
24512
24960
  b.title = `Go to “${plainMarkdown(step.title)}”`;
@@ -24528,19 +24976,19 @@ function walkthrough(container, options) {
24528
24976
  q.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
24529
24977
  li.append(q);
24530
24978
  if (f.q || f.trail.length) {
24531
- const a = el3(doc, "div", "knobs-wt-fi-a");
24979
+ const a = el4(doc, "div", "knobs-wt-fi-a");
24532
24980
  if (f.q)
24533
24981
  a.append(prose2(doc, "div", "knobs-wt-fi-answer", f.text));
24534
24982
  if (f.trail.length) {
24535
- const list = el3(doc, "ol", "knobs-wt-trail");
24983
+ const list = el4(doc, "ol", "knobs-wt-trail");
24536
24984
  for (const line of f.trail) {
24537
24985
  const i = line.indexOf(" · ");
24538
- const row = el3(doc, "li");
24539
- const words = el3(doc, "span", "knobs-wt-trail-text");
24986
+ const row = el4(doc, "li");
24987
+ const words = el4(doc, "span", "knobs-wt-trail-text");
24540
24988
  words.textContent = i < 0 ? line : line.slice(i + 3);
24541
24989
  row.append(words);
24542
24990
  if (i >= 0) {
24543
- const at = el3(doc, "span", "knobs-wt-trail-at");
24991
+ const at = el4(doc, "span", "knobs-wt-trail-at");
24544
24992
  at.textContent = line.slice(0, i);
24545
24993
  row.append(at);
24546
24994
  }
@@ -24574,11 +25022,11 @@ function walkthrough(container, options) {
24574
25022
  root.dataset.rail = options.rail === false ? "none" : options.rail === "right" ? "right" : "left";
24575
25023
  root.toggleAttribute("data-cards", options.cards === true);
24576
25024
  root.toggleAttribute("data-review", review);
24577
- const layout = el3(doc, "div", "knobs-wt-layout");
24578
- const main = el3(doc, "div", "knobs-wt-main");
24579
- const flow = el3(doc, "div", "knobs-wt-flow");
24580
- const head = el3(doc, "header", "knobs-wt-head");
24581
- const h1 = el3(doc, "h1", "knobs-wt-title");
25025
+ const layout = el4(doc, "div", "knobs-wt-layout");
25026
+ const main = el4(doc, "div", "knobs-wt-main");
25027
+ const flow = el4(doc, "div", "knobs-wt-flow");
25028
+ const head = el4(doc, "header", "knobs-wt-head");
25029
+ const h1 = el4(doc, "h1", "knobs-wt-title");
24582
25030
  h1.textContent = page.title;
24583
25031
  head.append(h1);
24584
25032
  if (page.summary)
@@ -24586,13 +25034,13 @@ function walkthrough(container, options) {
24586
25034
  if (page.more)
24587
25035
  head.append(more(page.more));
24588
25036
  if (page.meta.length) {
24589
- const meta = el3(doc, "div", "knobs-wt-meta");
25037
+ const meta = el4(doc, "div", "knobs-wt-meta");
24590
25038
  for (const m of page.meta) {
24591
- const s = el3(doc, "span");
25039
+ const s = el4(doc, "span");
24592
25040
  s.append(m.label);
24593
- const b = el3(doc, "b");
25041
+ const b = el4(doc, "b");
24594
25042
  if (m.href) {
24595
- const a = el3(doc, "a");
25043
+ const a = el4(doc, "a");
24596
25044
  a.href = m.href;
24597
25045
  a.target = "_blank";
24598
25046
  a.rel = "noreferrer";
@@ -24608,14 +25056,14 @@ function walkthrough(container, options) {
24608
25056
  flow.append(head);
24609
25057
  let feedEl = null;
24610
25058
  if (page.feed.length) {
24611
- feedEl = el3(doc, "section", "knobs-wt-feed");
25059
+ feedEl = el4(doc, "section", "knobs-wt-feed");
24612
25060
  feedEl.id = "wt-latest";
24613
25061
  feedEl.setAttribute("aria-label", "Updates and answers");
24614
- const fh = el3(doc, "header", "knobs-wt-feed-head");
24615
- const h = el3(doc, "h2", "knobs-wt-feed-title");
25062
+ const fh = el4(doc, "header", "knobs-wt-feed-head");
25063
+ const h = el4(doc, "h2", "knobs-wt-feed-title");
24616
25064
  h.textContent = page.feed.some((f) => f.kind === "qa") ? "Updates & Q&A" : "Updates";
24617
25065
  fh.append(h);
24618
- const list = el3(doc, "ol", "knobs-wt-feed-list");
25066
+ const list = el4(doc, "ol", "knobs-wt-feed-list");
24619
25067
  const now = Date.now();
24620
25068
  for (const f of page.feed)
24621
25069
  list.append(feedItem(f, now));
@@ -24625,21 +25073,21 @@ function walkthrough(container, options) {
24625
25073
  let rail = null;
24626
25074
  count = fill = tocEl = null;
24627
25075
  if (options.rail !== false) {
24628
- rail = el3(doc, "aside", "knobs-wt-rail");
25076
+ rail = el4(doc, "aside", "knobs-wt-rail");
24629
25077
  rail.setAttribute("aria-label", "Contents");
24630
- tocEl = el3(doc, "nav", "knobs-wt-toc");
25078
+ tocEl = el4(doc, "nav", "knobs-wt-toc");
24631
25079
  tocEl.setAttribute("aria-label", "Contents");
24632
25080
  if (feedEl) {
24633
- const group = el3(doc, "div", "knobs-wt-toc-group");
25081
+ const group = el4(doc, "div", "knobs-wt-toc-group");
24634
25082
  const target = feedEl;
24635
25083
  group.append(tocRow("Latest", false, () => target.scrollIntoView({ block: "start", behavior: reduced() ? "auto" : "smooth" })));
24636
25084
  tocEl.append(group);
24637
25085
  }
24638
25086
  if (review) {
24639
- const progress = el3(doc, "div", "knobs-wt-progress");
24640
- count = el3(doc, "div", "knobs-wt-count");
24641
- const meter = el3(doc, "div", "knobs-wt-bar");
24642
- fill = el3(doc, "div", "knobs-wt-fill");
25087
+ const progress = el4(doc, "div", "knobs-wt-progress");
25088
+ count = el4(doc, "div", "knobs-wt-count");
25089
+ const meter = el4(doc, "div", "knobs-wt-bar");
25090
+ fill = el4(doc, "div", "knobs-wt-fill");
24643
25091
  meter.append(fill);
24644
25092
  progress.append(count, meter);
24645
25093
  rail.append(progress);
@@ -24648,15 +25096,15 @@ function walkthrough(container, options) {
24648
25096
  if (review)
24649
25097
  rail.append(actions(true));
24650
25098
  if (review && options.keys !== false) {
24651
- const keys = el3(doc, "div", "knobs-wt-keys");
25099
+ const keys = el4(doc, "div", "knobs-wt-keys");
24652
25100
  keys.innerHTML = "<kbd>j</kbd> <kbd>k</kbd> steps · <kbd>←</kbd> <kbd>→</kbd> seek · <kbd>f</kbd> full · <kbd>n</kbd> note" + (page.steps.some((s) => s.ask === "pick") ? " · <kbd>1</kbd>–<kbd>9</kbd> pick" : "");
24653
25101
  rail.append(keys);
24654
25102
  }
24655
25103
  }
24656
25104
  for (const section of page.sections) {
24657
- const sec = el3(doc, "section", "knobs-wt-section");
25105
+ const sec = el4(doc, "section", "knobs-wt-section");
24658
25106
  sec.id = `wt-${section.id}`;
24659
- const sh = el3(doc, "header", "knobs-wt-shead");
25107
+ const sh = el4(doc, "header", "knobs-wt-shead");
24660
25108
  const status = review ? section.status : undefined;
24661
25109
  if (section.kicker || section.link || status) {
24662
25110
  const meta = metaLine("knobs-wt-kicker");
@@ -24670,10 +25118,10 @@ function walkthrough(container, options) {
24670
25118
  }
24671
25119
  sh.append(meta);
24672
25120
  }
24673
- const h2 = el3(doc, "h2", "knobs-wt-heading");
25121
+ const h2 = el4(doc, "h2", "knobs-wt-heading");
24674
25122
  h2.innerHTML = inlineMarkdown(section.title);
24675
25123
  sh.append(h2);
24676
- const intro = el3(doc, "div", "knobs-wt-sintro");
25124
+ const intro = el4(doc, "div", "knobs-wt-sintro");
24677
25125
  intro.append(sh);
24678
25126
  if (section.summary)
24679
25127
  intro.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
@@ -24682,7 +25130,7 @@ function walkthrough(container, options) {
24682
25130
  sec.append(intro);
24683
25131
  let group = null;
24684
25132
  if (tocEl) {
24685
- group = el3(doc, "div", "knobs-wt-toc-group");
25133
+ group = el4(doc, "div", "knobs-wt-toc-group");
24686
25134
  const first = section.steps[0]?.key;
24687
25135
  group.append(tocRow(plainMarkdown(section.title), false, () => first ? go(first) : undefined, section.id));
24688
25136
  tocEl.append(group);
@@ -24710,7 +25158,8 @@ function walkthrough(container, options) {
24710
25158
  if (tocEl)
24711
25159
  pill = selectionPill(tocEl, {
24712
25160
  selected: () => viewOf(current)?.toc ?? null,
24713
- rows: () => tocRows
25161
+ rows: () => tocRows,
25162
+ spring: SPRINGS.snappy
24714
25163
  });
24715
25164
  syncProgress();
24716
25165
  current = null;
@@ -24724,8 +25173,8 @@ function walkthrough(container, options) {
24724
25173
  }));
24725
25174
  };
24726
25175
  const more = (text) => {
24727
- const box = el3(doc, "div", "knobs-wt-more");
24728
- const body = el3(doc, "div", "knobs-wt-more-text");
25176
+ const box = el4(doc, "div", "knobs-wt-more");
25177
+ const body = el4(doc, "div", "knobs-wt-more-text");
24729
25178
  for (const para of text.split(/\n\s*\n/))
24730
25179
  if (para.trim())
24731
25180
  body.append(prose2(doc, "p", "", para.trim()));
@@ -24733,25 +25182,25 @@ function walkthrough(container, options) {
24733
25182
  return box;
24734
25183
  };
24735
25184
  const tocRow = (label, sub, run, section) => {
24736
- const b = el3(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
25185
+ const b = el4(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
24737
25186
  b.type = "button";
24738
25187
  if (section)
24739
25188
  b.dataset.section = section;
24740
- const t = el3(doc, "span", "knobs-wt-toc-label");
25189
+ const t = el4(doc, "span", "knobs-wt-toc-label");
24741
25190
  t.textContent = label;
24742
25191
  b.title = label;
24743
25192
  b.append(t);
24744
25193
  if (review)
24745
- b.append(el3(doc, "span", "knobs-wt-toc-end"));
25194
+ b.append(el4(doc, "span", "knobs-wt-toc-end"));
24746
25195
  b.addEventListener("mousedown", (ev) => ev.preventDefault());
24747
25196
  b.addEventListener("click", run);
24748
25197
  tocRows.push(b);
24749
25198
  return b;
24750
25199
  };
24751
25200
  const actions = (rail) => {
24752
- const row = el3(doc, "div", "knobs-wt-actions");
25201
+ const row = el4(doc, "div", "knobs-wt-actions");
24753
25202
  const btn = (text, run, primary = false) => {
24754
- const b = el3(doc, "button", "knobs-wt-btn");
25203
+ const b = el4(doc, "button", "knobs-wt-btn");
24755
25204
  b.type = "button";
24756
25205
  b.textContent = text;
24757
25206
  if (primary)
@@ -24789,13 +25238,13 @@ function walkthrough(container, options) {
24789
25238
  }
24790
25239
  };
24791
25240
  const stepView = (step, group) => {
24792
- const card = el3(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
25241
+ const card = el4(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
24793
25242
  card.id = `wt-${step.key.replace("/", "--")}`;
24794
25243
  card.tabIndex = -1;
24795
25244
  card.dataset.key = step.key;
24796
25245
  card.setAttribute("aria-label", plainMarkdown(step.title));
24797
- const head = el3(doc, "header", "knobs-wt-step-head");
24798
- const h3 = el3(doc, "h3", "knobs-wt-stepTitle");
25246
+ const head = el4(doc, "header", "knobs-wt-step-head");
25247
+ const h3 = el4(doc, "h3", "knobs-wt-stepTitle");
24799
25248
  h3.innerHTML = inlineMarkdown(step.title);
24800
25249
  head.append(h3);
24801
25250
  if (step.caption)
@@ -24807,7 +25256,7 @@ function walkthrough(container, options) {
24807
25256
  const onEvent = (e) => record({ ...e, step: step.key });
24808
25257
  let media = null;
24809
25258
  if (step.media.length) {
24810
- media = el3(doc, "div", "knobs-wt-media");
25259
+ media = el4(doc, "div", "knobs-wt-media");
24811
25260
  media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
24812
25261
  for (const m of step.media)
24813
25262
  slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks, findPR: findPRIn }));
@@ -24827,7 +25276,7 @@ function walkthrough(container, options) {
24827
25276
  });
24828
25277
  media ??= taste.element;
24829
25278
  }
24830
- const notes = el3(doc, "div", "knobs-wt-notes");
25279
+ const notes = el4(doc, "div", "knobs-wt-notes");
24831
25280
  if (review)
24832
25281
  card.append(notes);
24833
25282
  card.addEventListener("focusin", () => {
@@ -24841,7 +25290,7 @@ function walkthrough(container, options) {
24841
25290
  if (group) {
24842
25291
  toc = tocRow(plainMarkdown(step.title), true, () => go(step.key));
24843
25292
  if (review) {
24844
- glyph = el3(doc, "span", "knobs-wt-toc-glyph");
25293
+ glyph = el4(doc, "span", "knobs-wt-toc-glyph");
24845
25294
  toc.querySelector(".knobs-wt-toc-end").append(glyph);
24846
25295
  }
24847
25296
  group.append(toc);
@@ -25045,6 +25494,7 @@ export {
25045
25494
  contextMenu,
25046
25495
  countChanges,
25047
25496
  currentMarker,
25497
+ decision,
25048
25498
  diffActivity,
25049
25499
  diffBar,
25050
25500
  diffCounts,
@@ -25057,6 +25507,7 @@ export {
25057
25507
  digitPick,
25058
25508
  drag as dragDivider,
25059
25509
  dropTarget,
25510
+ facts,
25060
25511
  feedbackMarkdown,
25061
25512
  fileKey,
25062
25513
  filesUnder,