@kitlangton/knobs 0.4.0-next.6 → 0.4.0-next.8

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-react.js CHANGED
@@ -576,13 +576,14 @@ var compareCss = `
576
576
  picked: the pill's bright copy is. */
577
577
  .knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
578
578
  .knobs-cmp-modes[hidden]{display:none}
579
- .knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
580
- transition:color calc(var(--_k-hover-out) * 1ms)}
581
- .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
579
+ /* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
580
+ .knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;white-space:nowrap;
581
+ transition:background calc(var(--_k-hover-out) * 1ms)}
582
+ .knobs-ui .knobs-cmp-mode:not([aria-checked="true"]):is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
582
583
  /* Toggles: icon buttons, the accent's wash when on. */
583
- .knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-3);cursor:pointer;
584
- transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
585
- .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
584
+ .knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;
585
+ transition:background calc(var(--_k-hover-out) * 1ms)}
586
+ .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
586
587
  .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
587
588
  .knobs-cmp-toggle svg{display:block}
588
589
  .knobs-cmp-fill{flex:1}
@@ -596,11 +597,10 @@ var compareCss = `
596
597
  .knobs-cmp-stage{position:relative;flex:1;min-height:96px;overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_ku-cmp-stage);touch-action:pan-y;outline:none}
597
598
  .knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
598
599
  .knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
599
- /* Videos: a click on the stage plays and pauses. */
600
- .knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
601
600
  /* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
602
- .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
603
- .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
601
+ /* Zoomed, it pans: a move cursor, never a hand. */
602
+ .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:move;touch-action:none}
603
+ .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:move!important}
604
604
  /* Wiping, the grip stands in for the cursor. */
605
605
  .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
606
606
  .knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
@@ -647,8 +647,8 @@ var compareCss = `
647
647
  opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
648
648
  .knobs-cmp-zoom[data-on]{opacity:1;scale:1;pointer-events:auto;transition:opacity .2s cubic-bezier(.2,.8,.2,1),scale .2s cubic-bezier(.2,.8,.2,1)}
649
649
  :where(.knobs-ui) .knobs-cmp-zoom-reset{all:revert;box-sizing:border-box;font:inherit;letter-spacing:inherit;margin:0;border:0;outline:none;box-shadow:none;
650
- display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:pointer;transition:color .15s,background .15s}
651
- :where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){color:var(--_k-fg);background:color-mix(in srgb,var(--_k-fg) 12%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
650
+ display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
651
+ :where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
652
652
  .knobs-cmp-note{position:absolute;left:50%;top:50%;translate:-50% -50%;color:var(--_k-fg-3);font-size:var(--_k-text-meta);pointer-events:none}
653
653
  .knobs-cmp-note:empty{display:none}
654
654
  .knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
@@ -666,23 +666,29 @@ var playerCss = `
666
666
  .knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
667
667
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
668
668
  :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-player,.knobs-ui.knobs-player[data-nested]{--_kp-stage:var(--k-compare-stage,transparent)}
669
- .knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage);cursor:pointer}
669
+ .knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage)}
670
670
  .knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
671
671
  .knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
672
672
  opacity:0;filter:blur(4px);transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
673
673
  .knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
674
- .knobs-player-glyph{position:absolute;left:50%;top:50%;display:grid;place-items:center;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;color:var(--_k-fg);pointer-events:none;
675
- opacity:0;filter:blur(4px);scale:.96;
676
- background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
677
- box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
678
- transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),filter calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
679
- .knobs-player-glyph svg{width:16px;height:16px}
680
- .knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
681
674
  .knobs-player-line{flex:none;min-height:0}
682
- /* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover
683
- states are set here, after it, so the player's line and every timeline share them. */
675
+ /* The timeline's own stylesheet is look.ts's; what the player's row and every timeline share
676
+ about the hand is set here, after it. No hand cursor anywhere (the arrow, or a resize where it
677
+ resizes); hover is a quiet fill (fill-hover), in at once (hover-in, 0 ms), out easing. */
678
+ .knobs-ui .knobs-tl-play,.knobs-ui.knobs-timeline :is(.knobs-tl-pin,.knobs-tl-rlabel),
679
+ .knobs-ui.knobs-timeline[data-dragging] .knobs-tl-marker :is(.knobs-tl-pin,.knobs-tl-mlabel){cursor:default}
684
680
  .knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
685
681
  .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
682
+ /* The line's row: a minimal play/pause (the filled pair, a 28px target, no fill at rest), then
683
+ the line, centred on it. */
684
+ .knobs-ui.knobs-timeline[data-variant="line"]{gap:4px;align-items:center}
685
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:flex}
686
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play{width:28px;height:28px;min-width:28px;min-height:28px;border-radius:var(--_k-radius);background:transparent;color:var(--_k-fg-2);
687
+ transition:background calc(var(--_k-hover-out) * 1ms),transform .15s var(--_ku-ease)}
688
+ .knobs-ui.knobs-timeline[data-variant="line"][data-playing] .knobs-tl-play{background:transparent;color:var(--_k-fg-2)}
689
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
690
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track{cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
691
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
686
692
  `;
687
693
 
688
694
  // src/ui/overlay-css.ts
@@ -3234,16 +3240,17 @@ var init_diff_css = __esm(() => {
3234
3240
  .knobs-diff-nav>span{min-width:3.6em;text-align:center}
3235
3241
  :where(.knobs-ui) :is(.knobs-diff-btn,.knobs-diff-choice,.knobs-diff-viewed,.knobs-diff-filter>input,.knobs-diff-filter>button){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;border-radius:0;
3236
3242
  margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
3237
- .knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;
3238
- transition:background .15s,color .15s,opacity .15s}
3239
- .knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3243
+ .knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-2);cursor:default;
3244
+ transition:background calc(var(--_k-hover-out) * 1ms),opacity .15s}
3245
+ /* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
3246
+ .knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3240
3247
  .knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
3241
3248
  .knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
3242
3249
  .knobs-diff-btn svg{width:16px;height:16px}
3243
3250
  /* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
3244
3251
  .knobs-ui .knobs-diff-viewed{flex:none;display:inline-flex;align-items:center;gap:5px;height:var(--_k-control-h);padding:0 8px 0 6px;border-radius:var(--kd-ctl);color:var(--_k-fg-2);
3245
- font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
3246
- .knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3252
+ font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:default;transition:background calc(var(--_k-hover-out) * 1ms),color .15s}
3253
+ .knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3247
3254
  .knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
3248
3255
  .knobs-diff-viewed svg{width:16px;height:16px}
3249
3256
  .knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
@@ -3262,8 +3269,8 @@ var init_diff_css = __esm(() => {
3262
3269
  choice isn't brighter for being picked. Choices are styled by class, as the copy holds them too. */
3263
3270
  .knobs-diff-seg{position:relative;flex:none;display:grid;grid-template-columns:1fr 1fr;height:var(--_k-control-h);padding:2px;border-radius:var(--kd-ctl);background:var(--_k-fill)}
3264
3271
  .knobs-ui .knobs-diff-choice{padding:0 9px;border-radius:max(0px,calc(var(--kd-ctl) - 2px));color:var(--_k-fg-2);font-size:var(--_k-text-meta);
3265
- font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
3266
- .knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3272
+ font-weight:var(--_k-weight-label);white-space:nowrap;cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
3273
+ .knobs-ui .knobs-diff-choice:not([aria-checked="true"]):is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3267
3274
  /* The diff: one layer per file or layout on show, the old fading under the new. */
3268
3275
  .knobs-diff-body{position:relative;flex:1;min-height:0}
3269
3276
  .knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
@@ -3359,8 +3366,8 @@ var init_diff_css = __esm(() => {
3359
3366
  .knobs-diff-filter svg{width:12px;height:12px;flex:none}
3360
3367
  .knobs-ui .knobs-diff-filter>input{flex:1;min-width:0;height:100%;font:var(--_k-weight) var(--_k-font-size)/1 var(--_k-font);color:var(--_k-fg);caret-color:var(--_k-accent);background:transparent}
3361
3368
  .knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
3362
- .knobs-ui .knobs-diff-filter>button{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:var(--_k-fg-3);cursor:pointer;transition:background .12s,color .12s,opacity .12s}
3363
- .knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3369
+ .knobs-ui .knobs-diff-filter>button{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:var(--_k-fg-3);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms),opacity .12s}
3370
+ .knobs-ui .knobs-diff-filter>button:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3364
3371
  .knobs-diff-filter>button svg{width:12px;height:12px}
3365
3372
  .knobs-diff-list{position:relative;flex:1;min-height:0}
3366
3373
  .knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
@@ -11216,29 +11223,6 @@ var init_compare_strip = __esm(() => {
11216
11223
  DIFF = { w: 32, h: 18 };
11217
11224
  });
11218
11225
 
11219
- // src/ui/player.ts
11220
- function playGlyph(doc) {
11221
- const element = doc.createElement("span");
11222
- element.className = "knobs-player-glyph";
11223
- element.setAttribute("aria-hidden", "true");
11224
- let timer = 0;
11225
- const win = doc.defaultView;
11226
- return {
11227
- element,
11228
- flash(playing) {
11229
- element.innerHTML = playing ? PLAY2 : PAUSE;
11230
- element.setAttribute("data-flash", "");
11231
- win.clearTimeout(timer);
11232
- timer = win.setTimeout(() => element.removeAttribute("data-flash"), FLASH_MS);
11233
- },
11234
- dispose: () => win.clearTimeout(timer)
11235
- };
11236
- }
11237
- var FLASH_MS = 450;
11238
- var init_player = __esm(() => {
11239
- init_timeline();
11240
- });
11241
-
11242
11226
  // src/ui/compare.ts
11243
11227
  function compare(container, options) {
11244
11228
  const doc = container.ownerDocument;
@@ -11357,8 +11341,7 @@ function compare(container, options) {
11357
11341
  grip.setAttribute("aria-hidden", "true");
11358
11342
  handle.append(grip);
11359
11343
  const note = el("div", "knobs-cmp-note");
11360
- const glyph = playGlyph(doc);
11361
- stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note, glyph.element);
11344
+ stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note);
11362
11345
  root.append(bar, stage);
11363
11346
  const wantsStrip = options.strip === true;
11364
11347
  let tl = bound;
@@ -11400,7 +11383,6 @@ function compare(container, options) {
11400
11383
  let drag = null;
11401
11384
  const touches = new Map;
11402
11385
  let pinch = null;
11403
- let press = null;
11404
11386
  const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
11405
11387
  const zv = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
11406
11388
  const zw = new Spring(1, ZOOM.omega, ZOOM.zeta, 0.00001);
@@ -12229,10 +12211,8 @@ function compare(container, options) {
12229
12211
  root.focus({ preventScroll: true });
12230
12212
  touches.set(e.pointerId, p);
12231
12213
  stage.setPointerCapture(e.pointerId);
12232
- press = touches.size === 1 && mode !== "wipe" && master() ? p : null;
12233
12214
  if (touches.size >= 2) {
12234
12215
  e.preventDefault();
12235
- press = null;
12236
12216
  return startPinch();
12237
12217
  }
12238
12218
  if (mode === "wipe") {
@@ -12260,8 +12240,6 @@ function compare(container, options) {
12260
12240
  const onMove = (e) => {
12261
12241
  const p = local(e);
12262
12242
  pointer = p;
12263
- if (press && Math.hypot(p.x - press.x, p.y - press.y) > 4)
12264
- press = null;
12265
12243
  if (touches.has(e.pointerId))
12266
12244
  touches.set(e.pointerId, p);
12267
12245
  if (pinch && touches.size >= 2) {
@@ -12291,11 +12269,6 @@ function compare(container, options) {
12291
12269
  drawNow();
12292
12270
  };
12293
12271
  const endDrag = (e) => {
12294
- if (press && e.type === "pointerup") {
12295
- api.toggle();
12296
- glyph.flash(isPlaying());
12297
- }
12298
- press = null;
12299
12272
  touches.delete(e.pointerId);
12300
12273
  if (touches.size < 2)
12301
12274
  pinch = null;
@@ -12457,7 +12430,6 @@ function compare(container, options) {
12457
12430
  old.el.replaceWith(s.el);
12458
12431
  old.preview.replaceWith(s.preview);
12459
12432
  sides[index] = s;
12460
- root.toggleAttribute("data-video", sides.some((x) => x.kind === "video"));
12461
12433
  note.textContent = "";
12462
12434
  setLabel(index, labelOf(index));
12463
12435
  if (near)
@@ -12472,7 +12444,6 @@ function compare(container, options) {
12472
12444
  stage.insertBefore(sides[0].preview, labels[0]);
12473
12445
  setLabel(0, labelOf(0));
12474
12446
  setLabel(1, labelOf(1));
12475
- root.toggleAttribute("data-video", sides.some((s) => s.kind === "video"));
12476
12447
  loupeBtn.setAttribute("aria-pressed", String(loupeOn));
12477
12448
  loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
12478
12449
  const modePill = modeList.length > 1 ? selectionPill(modes, {
@@ -12673,7 +12644,6 @@ function compare(container, options) {
12673
12644
  disposed = true;
12674
12645
  loop.stop();
12675
12646
  modePill?.dispose();
12676
- glyph.dispose();
12677
12647
  win.clearTimeout(releaseTimer);
12678
12648
  ro.disconnect();
12679
12649
  nearIo?.disconnect();
@@ -12714,7 +12684,6 @@ var init_compare = __esm(() => {
12714
12684
  init_icons();
12715
12685
  init_look();
12716
12686
  init_rgba();
12717
- init_player();
12718
12687
  init_selection_pill();
12719
12688
  init_timeline();
12720
12689
  init_timeline_model();
@@ -12733,6 +12702,171 @@ var init_compare = __esm(() => {
12733
12702
  };
12734
12703
  });
12735
12704
 
12705
+ // src/ui/player.ts
12706
+ function player(container, options) {
12707
+ const doc = container.ownerDocument;
12708
+ const el = (tag, cls) => {
12709
+ const n = doc.createElement(tag);
12710
+ n.className = cls;
12711
+ return n;
12712
+ };
12713
+ const look = {
12714
+ ...options.preset ? { preset: options.preset } : {},
12715
+ ...options.theme ? { theme: options.theme } : {},
12716
+ ...options.appearance ? { appearance: options.appearance } : {}
12717
+ };
12718
+ const root = el("div", "knobs-player");
12719
+ root.setAttribute("role", "group");
12720
+ root.setAttribute("aria-label", options.label ?? "Video");
12721
+ applyLook2(root, options);
12722
+ const frame = el("div", "knobs-player-frame");
12723
+ frame.style.setProperty("--kp-aspect", String(options.aspect ?? 16 / 9));
12724
+ const v = el("video", "knobs-player-media");
12725
+ v.muted = options.muted ?? true;
12726
+ v.defaultMuted = v.muted;
12727
+ v.playsInline = true;
12728
+ v.preload = "metadata";
12729
+ v.setAttribute("playsinline", "");
12730
+ if (options.poster)
12731
+ v.poster = options.poster;
12732
+ if (options.captions) {
12733
+ const track = doc.createElement("track");
12734
+ track.kind = "captions";
12735
+ track.label = "Captions";
12736
+ track.srclang = doc.documentElement.lang || "en";
12737
+ track.default = true;
12738
+ track.src = options.captions;
12739
+ v.append(track);
12740
+ }
12741
+ frame.append(v);
12742
+ const host = el("div", "knobs-player-line");
12743
+ root.append(frame, host);
12744
+ container.append(root);
12745
+ const markers = (options.chapters ?? []).map((c, i) => ({
12746
+ id: c.id ?? `c${i}`,
12747
+ time: c.start,
12748
+ label: c.label
12749
+ }));
12750
+ const tl = timeline(host, {
12751
+ ...look,
12752
+ variant: "line",
12753
+ duration: 1,
12754
+ fps: options.fps ?? 30,
12755
+ markers,
12756
+ repeat: options.loop ?? true,
12757
+ arrows: { step: 5, shift: 10 },
12758
+ label: options.label ? `${options.label} time` : "Video time",
12759
+ ...options.hints === false ? { hints: false } : {}
12760
+ });
12761
+ const onFrame = () => root.setAttribute("data-ready", "");
12762
+ let pending = null;
12763
+ const seekVideo = (t) => {
12764
+ if (v.readyState < 1)
12765
+ return;
12766
+ if (v.seeking)
12767
+ pending = t;
12768
+ else
12769
+ v.currentTime = t;
12770
+ };
12771
+ let reported = 0;
12772
+ const onSeeked = () => {
12773
+ if (pending !== null) {
12774
+ const t = pending;
12775
+ pending = null;
12776
+ v.currentTime = t;
12777
+ return;
12778
+ }
12779
+ if (!v.paused || tl.playing || Math.abs(v.currentTime - reported) < 0.1)
12780
+ return;
12781
+ reported = v.currentTime;
12782
+ options.onSeek?.(reported);
12783
+ };
12784
+ const onMeta = () => {
12785
+ onFrame();
12786
+ if (v.videoWidth)
12787
+ frame.style.setProperty("--kp-aspect", String(v.videoWidth / v.videoHeight));
12788
+ if (Number.isFinite(v.duration))
12789
+ tl.update({ duration: v.duration });
12790
+ };
12791
+ v.addEventListener("loadedmetadata", onMeta);
12792
+ v.addEventListener("loadeddata", onFrame);
12793
+ v.addEventListener("seeked", onSeeked);
12794
+ const unlisten = tl.listen({
12795
+ onSeek(t, source) {
12796
+ if (source === "play") {
12797
+ if (Math.abs(v.currentTime - t) > 0.25)
12798
+ v.currentTime = t;
12799
+ return;
12800
+ }
12801
+ if (Math.abs(v.currentTime - t) < 1 / (options.fps ?? 30))
12802
+ return;
12803
+ seekVideo(t);
12804
+ },
12805
+ onPlay(on) {
12806
+ if (on)
12807
+ v.play().catch(() => tl.pause());
12808
+ else {
12809
+ v.pause();
12810
+ reported = tl.time;
12811
+ }
12812
+ root.toggleAttribute("data-playing", on);
12813
+ options.onPlay?.(on, tl.time);
12814
+ },
12815
+ onScrub(on) {
12816
+ if (on)
12817
+ v.pause();
12818
+ }
12819
+ });
12820
+ tl.drive(() => {
12821
+ if (v.readyState < 1)
12822
+ return Number.NaN;
12823
+ if (v.seeking)
12824
+ return tl.time;
12825
+ return v.ended ? Number.POSITIVE_INFINITY : v.currentTime;
12826
+ });
12827
+ const onClick = () => tl.focus();
12828
+ frame.addEventListener("click", onClick);
12829
+ v.src = options.src;
12830
+ const api = {
12831
+ element: root,
12832
+ video: v,
12833
+ timeline: tl,
12834
+ get time() {
12835
+ return tl.time;
12836
+ },
12837
+ get playing() {
12838
+ return tl.playing;
12839
+ },
12840
+ get duration() {
12841
+ return Number.isFinite(v.duration) ? v.duration : 0;
12842
+ },
12843
+ play: () => tl.play(),
12844
+ pause: () => tl.pause(),
12845
+ toggle: () => tl.toggle(),
12846
+ seek: (t, o) => tl.seek(t, o?.glide ? { glide: true } : undefined),
12847
+ seekBy: (d) => tl.seek(Math.min(tl.duration, Math.max(0, tl.time + d)), { glide: true }),
12848
+ focus: () => tl.focus(),
12849
+ dispose() {
12850
+ unlisten();
12851
+ tl.drive(null);
12852
+ tl.dispose();
12853
+ v.pause();
12854
+ frame.removeEventListener("click", onClick);
12855
+ v.removeEventListener("loadedmetadata", onMeta);
12856
+ v.removeEventListener("loadeddata", onFrame);
12857
+ v.removeEventListener("seeked", onSeeked);
12858
+ v.removeAttribute("src");
12859
+ v.load();
12860
+ root.remove();
12861
+ }
12862
+ };
12863
+ return api;
12864
+ }
12865
+ var init_player = __esm(() => {
12866
+ init_look();
12867
+ init_timeline();
12868
+ });
12869
+
12736
12870
  // src/ui/react.tsx
12737
12871
  import {
12738
12872
  Children,
@@ -18215,25 +18349,63 @@ function normalizeWalkthrough(doc) {
18215
18349
  })),
18216
18350
  sections: withKickers(sections, doc.sections, doc.kickers),
18217
18351
  steps,
18218
- prs: knownPRs(doc, sections),
18352
+ prs: knownPRs(doc, sections, steps),
18219
18353
  ...repoOf(doc) ? { repo: repoOf(doc) } : {}
18220
18354
  };
18221
18355
  }
18222
18356
  var prNumber = (href) => /\/(?:pull|merge_requests)\/(\d+)(?:[/?#]|$)/.exec(href)?.[1] ?? null;
18223
18357
  var repoOf = (doc) => doc.repo?.trim().replace(/^https?:\/\/github\.com\//, "").replace(/\/+$/, "") || undefined;
18224
- function knownPRs(doc, sections) {
18358
+ var PR_STATES = ["merged", "open", "draft", "closed"];
18359
+ function prStatus(status) {
18360
+ const words = (status ?? "").trim().toLowerCase();
18361
+ const first = /^[a-z]+/.exec(words)?.[0] ?? "";
18362
+ const state = PR_STATES.includes(first) ? first : first === "shipped" || first === "done" ? "merged" : null;
18363
+ return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
18364
+ }
18365
+ function knownPRs(doc, sections, steps = []) {
18225
18366
  const out = {};
18226
18367
  const repo = repoOf(doc);
18227
- for (const s of sections) {
18228
- const n = s.link && prNumber(s.link.href);
18229
- if (n)
18230
- out[n] = { href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1") };
18231
- }
18368
+ const add = (n, pr) => {
18369
+ const known = out[n] ?? {};
18370
+ const next = { ...known };
18371
+ for (const [k, v] of Object.entries(pr))
18372
+ if (v !== undefined && next[k] === undefined)
18373
+ next[k] = v;
18374
+ out[n] = next;
18375
+ };
18232
18376
  for (const [k, pr] of Object.entries(doc.prs ?? {})) {
18233
18377
  const n = k.replace(/^#/, "");
18234
18378
  if (!/^\d+$/.test(n))
18235
18379
  continue;
18236
- out[n] = { ...out[n], ...pr };
18380
+ add(n, pr);
18381
+ }
18382
+ for (const step of steps)
18383
+ for (const m of step.media) {
18384
+ if (m.kind !== "stack")
18385
+ continue;
18386
+ for (const it of m.items) {
18387
+ const n = it.pr.replace(/^#/, "");
18388
+ if (!/^\d+$/.test(n))
18389
+ continue;
18390
+ const said = prStatus(it.status);
18391
+ add(n, {
18392
+ ...said.state ? { state: said.state } : {},
18393
+ ...said.state && said.checks ? { checks: true } : {},
18394
+ ...it.title ? { title: plainMarkdown(it.title) } : {},
18395
+ ...it.href ? { href: it.href } : {}
18396
+ });
18397
+ }
18398
+ }
18399
+ for (const s of sections) {
18400
+ const n = s.link && prNumber(s.link.href);
18401
+ if (!n)
18402
+ continue;
18403
+ const said = prStatus(s.status);
18404
+ add(n, {
18405
+ href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1"),
18406
+ ...said.state ? { state: said.state } : {},
18407
+ title: plainMarkdown(s.title)
18408
+ });
18237
18409
  }
18238
18410
  if (repo) {
18239
18411
  for (const n of Object.keys(out))
@@ -18242,12 +18414,17 @@ function knownPRs(doc, sections) {
18242
18414
  }
18243
18415
  return out;
18244
18416
  }
18245
- function findPR(prs, n, repo) {
18246
- const known = prs[n];
18417
+ function findPR(prs, n, repo, kind = "pr") {
18418
+ const entry = prs[n];
18419
+ const known = entry && (entry.kind ?? "pr") === kind ? entry : undefined;
18420
+ const path = kind === "issue" ? "issues" : "pull";
18421
+ const href = repo ? `https://github.com/${repo}/${path}/${n}` : undefined;
18422
+ const base = kind === "issue" ? { kind } : {};
18247
18423
  if (known)
18248
- return known;
18249
- return repo ? { href: `https://github.com/${repo}/pull/${n}` } : null;
18424
+ return known.href || !href ? { ...base, ...known } : { ...base, ...known, href };
18425
+ return href ? { ...base, href } : kind === "issue" ? { kind } : null;
18250
18426
  }
18427
+ var issueNumber = (href) => /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/.exec(href)?.[1] ?? null;
18251
18428
  var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
18252
18429
  function withKickers(sections, source, kickers = "pr") {
18253
18430
  if (kickers === false)
@@ -19211,7 +19388,7 @@ function mountHistory(host, m, steps) {
19211
19388
  context: () => ({ kind: "history" })
19212
19389
  };
19213
19390
  }
19214
- function mountStack(host, m, steps) {
19391
+ function mountStack(host, m, steps, findPR) {
19215
19392
  const doc = host.ownerDocument;
19216
19393
  const layout = stackLayout(m);
19217
19394
  const root = el2(doc, "div", "knobs-wt-stack");
@@ -19253,7 +19430,10 @@ function mountStack(host, m, steps) {
19253
19430
  const line = el2(doc, "div", "knobs-wt-sline");
19254
19431
  if (it.order)
19255
19432
  line.append(text2(doc, "span", "knobs-wt-sord", it.order));
19256
- line.append(link(doc, "knobs-wt-spr", it.pr, it.href));
19433
+ const known = findPR?.(it.pr.replace(/^#/, "")) ?? null;
19434
+ const pr = link(doc, "knobs-wt-spr", it.pr, it.href ?? known?.href);
19435
+ pr.addEventListener("click", (e) => e.stopPropagation());
19436
+ line.append(pr);
19257
19437
  const target = it.step ? steps?.find(it.step) : null;
19258
19438
  if (target && steps) {
19259
19439
  const title = text2(doc, "button", "knobs-wt-stitle", it.title);
@@ -19288,7 +19468,11 @@ function mountStack(host, m, steps) {
19288
19468
  if (meta.childNodes.length)
19289
19469
  main.append(meta);
19290
19470
  row.append(main);
19291
- if (it.status) {
19471
+ const said = prStatus(it.status);
19472
+ const state = known?.state ?? said.state;
19473
+ if (state)
19474
+ row.append(prStateLabel(doc, state, said.checks, it.status));
19475
+ else if (it.status) {
19292
19476
  const chip = text2(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
19293
19477
  chip.dataset.tone = it.tone ?? "dim";
19294
19478
  row.append(chip);
@@ -19595,8 +19779,29 @@ var PR_PATHS = {
19595
19779
  pull: "M104,64A32,32,0,1,0,64,95v66a32,32,0,1,0,16,0V95A32.06,32.06,0,0,0,104,64ZM56,64A16,16,0,1,1,72,80,16,16,0,0,1,56,64ZM88,192a16,16,0,1,1-16-16A16,16,0,0,1,88,192Zm120-31V110.63a23.85,23.85,0,0,0-7-17L163.31,56H192a8,8,0,0,0,0-16H144a8,8,0,0,0-8,8V96a8,8,0,0,0,16,0V67.31L189.66,105a8,8,0,0,1,2.34,5.66V161a32,32,0,1,0,16,0Zm-8,47a16,16,0,1,1,16-16A16,16,0,0,1,200,208Z",
19596
19780
  merge: "M208,112a32.05,32.05,0,0,0-30.69,23l-42.21-6a8,8,0,0,1-4.95-2.71L94.43,84.55A32,32,0,1,0,72,87v82a32,32,0,1,0,16,0V101.63l30,35a24,24,0,0,0,14.83,8.14l44,6.28A32,32,0,1,0,208,112ZM64,56A16,16,0,1,1,80,72,16,16,0,0,1,64,56ZM96,200a16,16,0,1,1-16-16A16,16,0,0,1,96,200Zm112-40a16,16,0,1,1,16-16A16,16,0,0,1,208,160Z"
19597
19781
  };
19598
- var prGlyph = (merged) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="12" height="12" fill="currentColor" aria-hidden="true" focusable="false"><path d="${merged ? PR_PATHS.merge : PR_PATHS.pull}"/></svg>`;
19782
+ var DRAFT = "M104,64A32,32,0,1,0,64,95v66a32,32,0,1,0,16,0V95A32.06,32.06,0,0,0,104,64ZM56,64A16,16,0,1,1,72,80,16,16,0,0,1,56,64ZM88,192a16,16,0,1,1-16-16A16,16,0,0,1,88,192ZM232,192a32,32,0,1,1-32-32A32,32,0,0,1,232,192ZM216,192a16,16,0,1,0-16,16A16,16,0,0,0,216,192ZM212,120a12,12,0,1,1-12-12A12,12,0,0,1,212,120ZM212,60a12,12,0,1,1-12-12A12,12,0,0,1,212,60Z";
19783
+ var X = "M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z";
19784
+ var CHECK3 = "M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z";
19785
+ 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>`;
19786
+ var prGlyph = (state) => state === "merged" ? svg12(PR_PATHS.merge) : state === "draft" ? svg12(DRAFT, true) : state === "closed" ? svg12(X) : svg12(PR_PATHS.pull);
19599
19787
  var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
19788
+ 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";
19789
+ function prStateLabel(doc, state, checks, title) {
19790
+ const s = el2(doc, "span", "knobs-wt-prstate");
19791
+ s.dataset.state = state;
19792
+ s.innerHTML = prGlyph(state);
19793
+ s.append(text2(doc, "span", "", PR_STATE[state]));
19794
+ if (checks) {
19795
+ const c = el2(doc, "span", "knobs-wt-prchecks");
19796
+ c.innerHTML = svg12(CHECK3);
19797
+ c.title = "Checks pass";
19798
+ c.setAttribute("aria-label", "checks pass");
19799
+ s.append(c);
19800
+ }
19801
+ if (title)
19802
+ s.title = title;
19803
+ return s;
19804
+ }
19600
19805
  function prChip(doc, n, pr) {
19601
19806
  const chip = el2(doc, pr.href ? "a" : "span", "knobs-wt-pr");
19602
19807
  if (pr.href) {
@@ -19604,27 +19809,55 @@ function prChip(doc, n, pr) {
19604
19809
  chip.setAttribute("target", "_blank");
19605
19810
  chip.setAttribute("rel", "noreferrer");
19606
19811
  }
19812
+ const issue = pr.kind === "issue";
19813
+ if (issue)
19814
+ chip.dataset.kind = "issue";
19607
19815
  if (pr.state)
19608
19816
  chip.dataset.state = pr.state;
19609
- chip.innerHTML = prGlyph(pr.state === "merged");
19817
+ chip.innerHTML = issue ? svg12(ISSUE) : prGlyph(pr.state);
19610
19818
  chip.append(text2(doc, "span", "", `#${n}`));
19611
- const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
19612
- chip.title = tip;
19613
- chip.setAttribute("aria-label", `Pull request ${tip}`);
19819
+ if (pr.checks) {
19820
+ const c = el2(doc, "span", "knobs-wt-prchecks");
19821
+ c.innerHTML = svg12(CHECK3);
19822
+ chip.append(c);
19823
+ }
19824
+ const words = [
19825
+ `${issue ? "Issue" : "Pull request"} #${n}`,
19826
+ pr.title,
19827
+ pr.state ? PR_STATE[pr.state] : "",
19828
+ pr.checks ? "checks pass" : ""
19829
+ ].filter(Boolean);
19830
+ chip.title = words.slice(1).length ? `#${n} · ${words.slice(1).join(" · ")}` : `#${n}`;
19831
+ chip.setAttribute("aria-label", words.join(" · "));
19614
19832
  return chip;
19615
19833
  }
19616
19834
  var PR_URL = /^https?:\/\/[^/]+\/.+\/(?:pull|merge_requests)\/(\d+)\/?(?:[?#].*)?$/;
19617
- var PR_REF = /(^|[^\w&/#])#(\d{3,7})\b/g;
19835
+ var ISSUE_URL = /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/;
19836
+ var REF = /(^|[^\w&/#:])(issue:)?#(\d{3,7})\b/gi;
19837
+ var WHOLE_REF = /^(issue:)?#(\d{1,7})$/i;
19838
+ var KEEP = 'a,pre,button,.knobs-wt-pr,.knobs-wt-slot:not([data-kind="table"]),.knobs-wt-stack';
19618
19839
  function linkPRs(root, find) {
19619
19840
  const doc = root.ownerDocument;
19841
+ const chip = (n, kind, href) => prChip(doc, n, { ...kind === "issue" ? { kind } : {}, ...find(n, kind), ...href ? { href } : {} });
19620
19842
  for (const a of Array.from(root.querySelectorAll("a[href]"))) {
19621
- if (a.classList.contains("knobs-wt-pr") || a.closest(".knobs-wt-slot,button"))
19843
+ if (a.classList.contains("knobs-wt-pr") || a.parentElement?.closest(KEEP))
19844
+ continue;
19845
+ const url = a.getAttribute("href") ?? "";
19846
+ const pr = PR_URL.exec(url)?.[1];
19847
+ const n = pr ?? ISSUE_URL.exec(url)?.[1];
19848
+ if (!n)
19622
19849
  continue;
19623
- const n = PR_URL.exec(a.getAttribute("href") ?? "")?.[1];
19624
- const words = a.textContent?.trim() ?? "";
19625
- if (!n || !(words === `#${n}` || words === n || words === a.getAttribute("href")))
19850
+ const words = (a.textContent ?? "").trim().replace(/^issue:/i, "");
19851
+ if (!(words === `#${n}` || words === n || words === url))
19626
19852
  continue;
19627
- a.replaceWith(prChip(doc, n, { href: a.href, ...find(n) }));
19853
+ a.replaceWith(chip(n, pr ? "pr" : "issue", a.href));
19854
+ }
19855
+ for (const c of Array.from(root.querySelectorAll("code"))) {
19856
+ if (c.closest("pre") || c.parentElement?.closest(KEEP))
19857
+ continue;
19858
+ const m = WHOLE_REF.exec((c.textContent ?? "").trim());
19859
+ if (m)
19860
+ c.replaceWith(chip(m[2], m[1] ? "issue" : "pr"));
19628
19861
  }
19629
19862
  const walker = doc.createTreeWalker(root, 5);
19630
19863
  const hits = [];
@@ -19632,29 +19865,22 @@ function linkPRs(root, find) {
19632
19865
  if (t.nodeType !== 3)
19633
19866
  continue;
19634
19867
  const p = t.parentElement;
19635
- if (!p || p.closest("a,code,pre,button,.knobs-wt-slot,.knobs-wt-pr"))
19868
+ if (!p || p.closest(`${KEEP},code`))
19636
19869
  continue;
19637
- PR_REF.lastIndex = 0;
19638
- if (PR_REF.test(t.nodeValue ?? ""))
19870
+ REF.lastIndex = 0;
19871
+ if (REF.test(t.nodeValue ?? ""))
19639
19872
  hits.push(t);
19640
19873
  }
19641
19874
  for (const t of hits) {
19642
19875
  const value = t.nodeValue ?? "";
19643
19876
  const frag = doc.createDocumentFragment();
19644
19877
  let last = 0;
19645
- let changed = false;
19646
- PR_REF.lastIndex = 0;
19647
- for (let m = PR_REF.exec(value);m; m = PR_REF.exec(value)) {
19648
- const pr = find(m[2]);
19649
- if (!pr)
19650
- continue;
19878
+ REF.lastIndex = 0;
19879
+ for (let m = REF.exec(value);m; m = REF.exec(value)) {
19651
19880
  const at = m.index + m[1].length;
19652
- frag.append(value.slice(last, at), prChip(doc, m[2], pr));
19653
- last = at + m[2].length + 1;
19654
- changed = true;
19881
+ frag.append(value.slice(last, at), chip(m[3], m[2] ? "issue" : "pr"));
19882
+ last = m.index + m[0].length;
19655
19883
  }
19656
- if (!changed)
19657
- continue;
19658
19884
  frag.append(value.slice(last));
19659
19885
  t.replaceWith(frag);
19660
19886
  }
@@ -19673,7 +19899,7 @@ var snappy3 = springCss(SPRINGS.snappy);
19673
19899
  var calm = springCss(SPRINGS.calm);
19674
19900
  var state = springCss(SPRINGS.default);
19675
19901
  var quick = springCss(FADE);
19676
- 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)";
19902
+ 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)";
19677
19903
  var walkthroughCss = `
19678
19904
  :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
19679
19905
  .knobs-ui:is(.knobs-walkthrough,.knobs-wt-slot){--k-glass:0;--k-glow:0;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms}
@@ -19685,7 +19911,7 @@ var walkthroughCss = `
19685
19911
  .knobs-walkthrough [hidden]{display:none!important}
19686
19912
  /* (0,0,1): a host's element rules stop here, and every class rule below wins over it. */
19687
19913
  :where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:inherit}
19688
- .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}
19914
+ .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)}
19689
19915
  .knobs-walkthrough a:hover{text-decoration-color:currentColor}
19690
19916
  .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)}
19691
19917
  :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;
@@ -19713,14 +19939,17 @@ var walkthroughCss = `
19713
19939
  .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)}
19714
19940
  .knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr):hover{color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19715
19941
  .knobs-wt-metaline svg{align-self:center;width:12px;height:12px;margin-left:2px}
19716
- /* A pull request: a small chip, its state's icon and number in a quiet tint of the state (open
19717
- the success hue, merged the violet series, draft and closed neutral), its title on hover. */
19718
- .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;
19942
+ /* A pull request or issue: a capsule, its state's icon and number in a quiet tint of the state
19943
+ (open the success hue, merged the violet series, draft and closed faint, unknown neutral; a
19944
+ closed issue is done, so violet), a faint check where its checks pass, its title on hover. */
19945
+ .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;
19719
19946
  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;
19720
19947
  transition:background calc(var(--_k-hover-out) * 1ms)}
19721
19948
  .knobs-ui .knobs-wt-pr[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
19722
19949
  .knobs-ui .knobs-wt-pr[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
19723
19950
  .knobs-ui .knobs-wt-pr:is([data-state="draft"],[data-state="closed"]){--kw-pr:var(--_k-fg-3)}
19951
+ .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))}
19952
+ .knobs-ui .knobs-wt-pr .knobs-wt-prchecks{margin-left:0}
19724
19953
  .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)}
19725
19954
  .knobs-wt-pr svg{flex:none}
19726
19955
  .knobs-wt-metaline .knobs-wt-pr{align-self:center;margin:0}
@@ -19779,14 +20008,14 @@ var walkthroughCss = `
19779
20008
  .knobs-wt-fi-ask{max-width:var(--kw-measure);color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:pretty}
19780
20009
  .knobs-wt-fi-text{max-width:var(--kw-measure);color:var(--_k-fg);text-wrap:pretty}
19781
20010
  .knobs-wt-fi-answer{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
19782
- :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;
20011
+ :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;
19783
20012
  transition:color calc(var(--_k-hover-out) * 1ms)}
19784
20013
  .knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19785
20014
 
19786
20015
  /* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
19787
20016
  script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
19788
20017
  .knobs-wt-disc{display:flex;flex-direction:column;min-width:0}
19789
- .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;
20018
+ .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;
19790
20019
  border-radius:calc(var(--_k-radius) / 2);transition:color calc(var(--_k-hover-out) * 1ms)}
19791
20020
  .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)}
19792
20021
  .knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
@@ -19813,7 +20042,7 @@ var walkthroughCss = `
19813
20042
  .knobs-wt-n[data-leaving]{opacity:0;filter:blur(3px);padding-block:0}
19814
20043
  .knobs-wt-n+.knobs-wt-n{box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
19815
20044
  .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)}
19816
- .knobs-wt-n-meta[data-seek]{cursor:pointer}
20045
+ .knobs-wt-n-meta[data-seek]{cursor:default}
19817
20046
  .knobs-wt-n-meta[data-seek]:hover{color:var(--_k-fg)}
19818
20047
  .knobs-wt-n-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
19819
20048
  .knobs-wt-n-voice{height:30px;max-width:320px}
@@ -19830,7 +20059,7 @@ var walkthroughCss = `
19830
20059
  /* Collapsed: the launcher and the bar swap in place, a quick fade with a little scale and blur
19831
20060
  from the launcher's corner; the exit is the shorter. */
19832
20061
  :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);
19833
- 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%;
20062
+ 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%;
19834
20063
  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)}
19835
20064
  .knobs-ui .knobs-wt-launch:is(:hover,:focus-visible){color:var(--_k-fg)}
19836
20065
  .knobs-ui .knobs-wt-launch:focus-visible{box-shadow:var(--_k-focus-ring)}
@@ -19850,11 +20079,11 @@ var walkthroughCss = `
19850
20079
  .knobs-wt-attach{display:flex;gap:4px;align-items:center;align-self:center}
19851
20080
  .knobs-wt-attach:empty{display:none}
19852
20081
  .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}
19853
- .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}
20082
+ .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}
19854
20083
  .knobs-ui .knobs-wt-x:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19855
20084
  .knobs-wt-x svg{width:12px;height:12px}
19856
20085
  .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);
19857
- color:var(--_k-fg-2);cursor:pointer;transition:background .15s,color .15s}
20086
+ color:var(--_k-fg-2);cursor:default;transition:background .15s,color .15s}
19858
20087
  .knobs-ui .knobs-wt-tool:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19859
20088
  .knobs-ui .knobs-wt-tool[aria-pressed="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
19860
20089
  .knobs-wt-tool svg{width:16px;height:16px}
@@ -19864,7 +20093,7 @@ var walkthroughCss = `
19864
20093
  .knobs-ui .knobs-wt-tool[data-send][data-ready]{background:var(--_k-accent);color:var(--_k-bg)}
19865
20094
  /* Back: in place over the bar for a few seconds after a note moves the page on. */
19866
20095
  .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;
19867
- 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;
20096
+ 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;
19868
20097
  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)}
19869
20098
  .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)}
19870
20099
  .knobs-ui .knobs-wt-back:hover{background:var(--_k-fill-selected)}
@@ -19891,6 +20120,9 @@ var walkthroughCss = `
19891
20120
  .knobs-wt-step[data-full] .knobs-wt-body{flex:1;min-height:0}
19892
20121
  .knobs-wt-step[data-full] .knobs-wt-fit{height:100%;transition:none}
19893
20122
  .knobs-wt-step[data-full] .knobs-wt-frame{height:100%;aspect-ratio:auto}
20123
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"]>.knobs-wt-body{display:flex;flex-direction:column}
20124
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player{flex:1;min-height:0}
20125
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player-frame{flex:1;min-height:0;aspect-ratio:auto}
19894
20126
 
19895
20127
  /* ——— a taste test: the options side by side, Kit's pick checked and the rest stepping back. On
19896
20128
  the step's card, so no fill of their own ——— */
@@ -19899,7 +20131,7 @@ var walkthroughCss = `
19899
20131
  .knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
19900
20132
  .knobs-wt-option>:not(.knobs-wt-pick){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
19901
20133
  .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-pick){opacity:.55}
19902
- .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;
20134
+ .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:default;color:var(--_k-fg-2);text-align:left;
19903
20135
  transition:background calc(var(--_k-hover-out) * 1ms)}
19904
20136
  .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)}
19905
20137
  .knobs-wt-letter{flex:none;font-weight:var(--kw-strong);color:var(--_k-fg)}
@@ -19922,17 +20154,18 @@ var walkthroughCss = `
19922
20154
  .knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
19923
20155
  .knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
19924
20156
  .knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
19925
- /* The contents: every row on one text edge and in one colour, title rows (32 high, the control
19926
- radius) and the steps under them (20 high, half of it), bled out so their text sits on the
19927
- rail's edge; the selection pill slides to the current step, and base rows never brighten. */
19928
- .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}
20157
+ /* The contents: every row on one text edge and in one colour, title rows (28 high, the control
20158
+ radius) and the steps under them (20 high, half of it), bled 6 px out so their text sits on the
20159
+ rail's edge and the first title's words on the column's top line; a narrow fade where a title
20160
+ runs out of room; the selection pill slides to the current step, and base rows never brighten. */
20161
+ .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}
19929
20162
  .knobs-wt-toc::-webkit-scrollbar{display:none}
19930
20163
  .knobs-wt-toc-group{display:flex;flex-direction:column;min-width:0}
19931
- .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;
19932
- 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;
19933
- transition:color calc(var(--_k-hover-out) * 1ms)}
20164
+ .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;
20165
+ 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;
20166
+ transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
19934
20167
  .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)}
19935
- .knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20168
+ .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)}
19936
20169
  .knobs-ui .knobs-wt-toc-row:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
19937
20170
  .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)}
19938
20171
  .knobs-wt-toc-label{min-width:0;overflow:hidden;${fade2}}
@@ -19944,7 +20177,7 @@ var walkthroughCss = `
19944
20177
  .knobs-wt-toc-glyph[data-done]{background:var(--kw-tone)}
19945
20178
  .knobs-wt-toc-glyph[data-tone="none"]{box-shadow:inset 0 0 0 1px var(--_k-border)}
19946
20179
  .knobs-wt-actions{display:flex;flex-wrap:wrap;gap:4px}
19947
- .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;
20180
+ .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;
19948
20181
  transition:background .15s,color .15s}
19949
20182
  .knobs-ui .knobs-wt-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19950
20183
  .knobs-ui .knobs-wt-btn[data-primary]{background:var(--_k-fill-selected);color:var(--_k-fg)}
@@ -19978,11 +20211,10 @@ var walkthroughCss = `
19978
20211
  /* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
19979
20212
  slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
19980
20213
  .knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
19981
- /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
19982
- sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
20214
+ /* A compare is as tall as its two pictures side by side at this width, in every mode (Wipe fuses
20215
+ them at one side's size, so nothing grows or re-measures), plus its mode bar, the line and gaps
19983
20216
  (92 px), and the frame strip with its gap (158 px). */
19984
20217
  .knobs-wt-fit{width:100%;height:calc(100cqw / 2 / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
19985
- .knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
19986
20218
  .knobs-wt-fit[data-strip]{--kw-chrome:158px}
19987
20219
  .knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
19988
20220
  .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)}
@@ -19991,8 +20223,12 @@ var walkthroughCss = `
19991
20223
  .knobs-wt-frame[data-natural]>img{position:static;height:auto}
19992
20224
  /* Diff, code and terminal: one plain container (no glass): a flat inset fill, a hairline ring, the
19993
20225
  control radius and room inside, so a diff's buttons and the code under them can breathe. */
19994
- .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)}
19995
- .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);
20226
+ .knobs-wt-slot[data-kind="diff"]>.knobs-wt-body{padding:var(--_k-pad-card);border-radius:var(--_k-radius-panel);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle)}
20227
+ /* The diff inside it: its header row 28 high on the container's padding, a hairline under it, and
20228
+ no line of its own under the code (the container's ring closes it). */
20229
+ .knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"]){--kd-head:28px;--kd-x:0px}
20230
+ .knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"])::after{display:none}
20231
+ .knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius-panel);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle);color:var(--_k-fg);
19996
20232
  font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
19997
20233
  .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)}
19998
20234
  .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
@@ -20038,7 +20274,7 @@ var walkthroughCss = `
20038
20274
  .knobs-wt-mark:is([data-shape="dot"],[data-shape="arrow"]) .knobs-wt-mark-label{left:12px;bottom:auto;top:10px}
20039
20275
  .knobs-wt-mark[data-shape="arrow"] .knobs-wt-mark-label{left:auto;right:30px;top:auto;bottom:30px}
20040
20276
  .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;
20041
- 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;
20277
+ 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;
20042
20278
  transition:opacity calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
20043
20279
  .knobs-ui .knobs-wt-anno-toggle:is(:hover,:focus-visible){opacity:1;color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20044
20280
  .knobs-ui .knobs-wt-anno-toggle:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
@@ -20059,7 +20295,7 @@ var walkthroughCss = `
20059
20295
  .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);
20060
20296
  border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
20061
20297
  .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
20062
- .knobs-wt-srow[data-go]{cursor:pointer}
20298
+ .knobs-wt-srow[data-go]{cursor:default}
20063
20299
  .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20064
20300
  .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
20065
20301
  .knobs-wt-lane{position:relative;flex:none;width:16px}
@@ -20075,7 +20311,7 @@ var walkthroughCss = `
20075
20311
  .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}
20076
20312
  .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
20077
20313
  .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
20078
- .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)}
20314
+ .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)}
20079
20315
  .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)}
20080
20316
  .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
20081
20317
  .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)}
@@ -20083,12 +20319,21 @@ var walkthroughCss = `
20083
20319
  .knobs-wt-add{color:var(--_k-diff-add)}
20084
20320
  .knobs-wt-del{color:var(--_k-diff-del)}
20085
20321
  .knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
20322
+ /* A PR's state the GitHub way: its glyph and word, open and merged quietly tinted (as PR chips),
20323
+ draft and closed faint; no fill, no dot. A check after it where the checks pass, faint too. One
20324
+ width for every row, so the glyphs and words line up. */
20325
+ .knobs-ui .knobs-wt-prstate{--kw-pr:var(--_k-fg-3);display:inline-flex;align-items:center;gap:4px;min-width:calc(7ch + 32px);height:var(--kw-line);color:var(--kw-pr);
20326
+ font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);white-space:nowrap}
20327
+ .knobs-ui .knobs-wt-prstate[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
20328
+ .knobs-ui .knobs-wt-prstate[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
20329
+ .knobs-wt-prstate svg{flex:none}
20330
+ .knobs-ui .knobs-wt-prchecks{display:inline-flex;margin-left:2px;color:var(--_k-fg-3)}
20086
20331
  .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
20087
20332
  .knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
20088
20333
  @container knobs-wt-slot (max-width:520px){
20089
20334
  .knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
20090
20335
  .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
20091
- .knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
20336
+ :is(.knobs-wt-sstatus,.knobs-wt-prstate){grid-column:-2;justify-self:start;margin-top:4px}
20092
20337
  }
20093
20338
 
20094
20339
  /* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
@@ -20098,7 +20343,7 @@ var walkthroughCss = `
20098
20343
  .knobs-wt-tablebox{min-width:0}
20099
20344
  /* A row with a step goes there: the whole row, a wash under the pointer, no arrow, so rows with and
20100
20345
  without one line up. */
20101
- .knobs-wt-tablebox [data-go]{cursor:pointer;outline:none}
20346
+ .knobs-wt-tablebox [data-go]{cursor:default;outline:none}
20102
20347
  .knobs-wt-tablebox tr[data-go]>td,.knobs-wt-trow[data-go]{transition:background calc(var(--_k-hover-out) * 1ms)}
20103
20348
  .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)}
20104
20349
  .knobs-wt-tablebox [data-go]:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
@@ -20154,13 +20399,18 @@ var walkthroughCss = `
20154
20399
  .knobs-wt-body:has(>.knobs-wt-chapters){display:flex;flex-direction:column;gap:var(--_k-space-title)}
20155
20400
  .knobs-wt-chapters{position:relative;display:flex;flex-direction:column;margin:0 calc(var(--_k-space-sibling) * -1)}
20156
20401
  .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);
20157
- 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)}
20158
- .knobs-ui .knobs-wt-chapter:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20402
+ 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)}
20403
+ .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)}
20159
20404
  .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
20160
20405
  .knobs-wt-chapter-n{font-size:var(--_k-type-small);color:var(--_k-fg-3)}
20161
20406
  .knobs-wt-chapter-label{min-width:0;overflow:hidden;${fade2}}
20162
20407
  .knobs-wt-chapter-time{font:var(--_k-weight) var(--_k-type-small)/1 var(--_k-mono);color:var(--_k-fg-3)}
20163
20408
  .knobs-pick-copy :is(.knobs-wt-chapter-n,.knobs-wt-chapter-time){color:var(--_k-fg-2)}
20409
+ /* Every hover in the walkthrough lands at once (hover-in, 0 ms) and eases out (hover-out): links,
20410
+ buttons, chips, rail, chapter, table and stack rows (its own; the pieces inside keep theirs). */
20411
+ :is(.knobs-walkthrough,.knobs-wt-slot) :is(a,button,[data-go])[class*="knobs-wt-"]:is(:hover,:focus-visible),
20412
+ .knobs-walkthrough a:not([class]):hover,
20413
+ :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)}
20164
20414
  @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
20165
20415
  @media(prefers-reduced-motion:reduce){.knobs-wt-disc-content{transition:opacity .15s}}
20166
20416
  `;
@@ -20261,7 +20511,6 @@ async function mountCompare(host, m, look, alive, onEvent) {
20261
20511
  fit.style.setProperty("--kw-aspect", String(known ?? DEFAULT_ASPECT));
20262
20512
  const width = host.getBoundingClientRect().width;
20263
20513
  const mode = m.mode ?? (width && width < 560 ? "wipe" : "side");
20264
- fit.toggleAttribute("data-overlay", mode !== "side");
20265
20514
  host.append(fit);
20266
20515
  const [aspect, lib] = await Promise.all([
20267
20516
  known ? Promise.resolve(known) : probeAspect(doc, after.src),
@@ -20285,7 +20534,6 @@ async function mountCompare(host, m, look, alive, onEvent) {
20285
20534
  ...aspect ? { aspect } : {},
20286
20535
  ...m.strip ? { strip: true } : {},
20287
20536
  onMode: (next) => {
20288
- fit.toggleAttribute("data-overlay", next !== "side");
20289
20537
  onEvent?.({ what: "mode", detail: next });
20290
20538
  const layer = marks?.element;
20291
20539
  if (layer) {
@@ -20309,6 +20557,12 @@ async function mountCompare(host, m, look, alive, onEvent) {
20309
20557
  }
20310
20558
  });
20311
20559
  const stage = fit.querySelector(".knobs-cmp-stage");
20560
+ if (stage)
20561
+ doc.defaultView?.requestAnimationFrame(() => {
20562
+ const chrome = fit.clientHeight - stage.clientHeight;
20563
+ if (alive() && stage.clientHeight > 0 && chrome > 0)
20564
+ fit.style.setProperty("--kw-chrome", `${Math.round(chrome)}px`);
20565
+ });
20312
20566
  if (m.annotations?.length && stage) {
20313
20567
  const shape = { w: aspect ?? DEFAULT_ASPECT, h: 1 };
20314
20568
  marks = annotationLayer(stage, m.annotations, () => {
@@ -20384,45 +20638,38 @@ async function mountCompare(host, m, look, alive, onEvent) {
20384
20638
  var round2 = (v) => Math.round(v * 100) / 100;
20385
20639
  var MARK_GLIDE = 450;
20386
20640
  var zoomedIn = (z) => z.w < 0.999 || z.h < 0.999;
20387
- function mountVideo(host, m, onEvent) {
20641
+ async function mountVideo(host, m, look, alive, onEvent) {
20388
20642
  const doc = host.ownerDocument;
20389
- const frame = div(doc, "knobs-wt-frame");
20643
+ await Promise.resolve().then(() => init_player());
20644
+ if (!alive())
20645
+ return { dispose() {} };
20390
20646
  const aspect = parseAspect(m.aspect);
20391
- if (aspect)
20392
- frame.style.setProperty("--kw-aspect", String(aspect));
20393
- const v = doc.createElement("video");
20394
- v.controls = true;
20395
- v.loop = true;
20396
- v.muted = true;
20397
- v.playsInline = true;
20398
- v.preload = "metadata";
20399
- if (m.poster)
20400
- v.poster = m.poster;
20401
- if (!aspect)
20402
- v.addEventListener("loadedmetadata", () => v.videoWidth && frame.style.setProperty("--kw-aspect", String(v.videoWidth / v.videoHeight)), { once: true });
20403
- if (m.captions) {
20404
- const track = doc.createElement("track");
20405
- track.kind = "captions";
20406
- track.label = "Captions";
20407
- track.srclang = doc.documentElement.lang || "en";
20408
- track.default = true;
20409
- track.src = m.captions;
20410
- v.append(track);
20411
- }
20412
- v.src = m.src;
20413
- v.addEventListener("play", () => onEvent?.({ what: "play", time: round2(v.currentTime) }));
20414
- v.addEventListener("pause", () => onEvent?.({ what: "pause", time: round2(v.currentTime) }));
20415
- v.addEventListener("seeked", () => {
20416
- if (v.paused)
20417
- onEvent?.({ what: "seek", time: round2(v.currentTime) });
20647
+ let pills = null;
20648
+ let marking = null;
20649
+ const p = player(host, {
20650
+ ...look,
20651
+ src: m.src,
20652
+ ...m.poster ? { poster: m.poster } : {},
20653
+ ...m.captions ? { captions: m.captions } : {},
20654
+ ...aspect ? { aspect } : {},
20655
+ ...m.label ? { label: m.label } : {},
20656
+ ...m.chapters?.length ? { chapters: m.chapters.map((c) => ({ label: c.label, start: c.start, ...c.id ? { id: c.id } : {} })) } : {},
20657
+ onPlay: (on, time) => {
20658
+ marking?.kick();
20659
+ onEvent?.({ what: on ? "play" : "pause", time: round2(time) });
20660
+ },
20661
+ onSeek: (time) => {
20662
+ pills?.sync();
20663
+ marking?.kick();
20664
+ onEvent?.({ what: "seek", time: round2(time) });
20665
+ }
20418
20666
  });
20419
- frame.append(v);
20420
- host.append(frame);
20421
- const pills = m.chapters?.length ? chapterPills(host, m.chapters, {
20422
- now: () => v.currentTime,
20667
+ const v = p.video;
20668
+ pills = m.chapters?.length ? chapterPills(host, m.chapters, {
20669
+ now: () => p.time,
20423
20670
  seek: (t) => {
20424
- v.currentTime = t;
20425
- v.play().catch(() => {});
20671
+ p.seek(t, { glide: true });
20672
+ p.play();
20426
20673
  },
20427
20674
  onEvent
20428
20675
  }) : null;
@@ -20430,36 +20677,33 @@ function mountVideo(host, m, onEvent) {
20430
20677
  if (pills)
20431
20678
  for (const type of ["timeupdate", "seeking", "seeked"])
20432
20679
  v.addEventListener(type, sync);
20680
+ const frame = p.element.querySelector(".knobs-player-frame") ?? p.element;
20433
20681
  const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
20434
20682
  rects: [
20435
20683
  contain({ w: frame.clientWidth, h: frame.clientHeight }, { w: v.videoWidth, h: v.videoHeight })
20436
20684
  ],
20437
- time: v.currentTime
20685
+ time: p.time
20438
20686
  })) : null;
20439
- const marking = marks ? follow(doc.defaultView, () => !v.paused, () => marks.update()) : null;
20440
- if (marking)
20441
- for (const type of ["play", "seeked", "loadedmetadata", "timeupdate"])
20442
- v.addEventListener(type, () => marking.kick());
20687
+ if (marks) {
20688
+ marking = follow(doc.defaultView, () => p.playing, () => marks.update());
20689
+ for (const type of ["seeked", "loadedmetadata", "timeupdate"])
20690
+ v.addEventListener(type, () => marking?.kick());
20691
+ }
20443
20692
  return {
20444
- pause: () => v.pause(),
20445
- seekBy: (d) => {
20446
- v.currentTime = Math.max(0, Math.min(v.duration || Infinity, v.currentTime + d));
20447
- },
20693
+ pause: () => p.pause(),
20694
+ seekBy: (d) => p.seekBy(d),
20448
20695
  context: () => ({
20449
20696
  kind: "video",
20450
- time: round2(v.currentTime),
20451
- ...Number.isFinite(v.duration) ? { duration: round2(v.duration) } : {},
20452
- playing: !v.paused,
20697
+ time: round2(p.time),
20698
+ ...p.duration ? { duration: round2(p.duration) } : {},
20699
+ playing: p.playing,
20453
20700
  ...pills?.current ? { chapter: pills.current.label } : {}
20454
20701
  }),
20455
20702
  dispose: () => {
20456
20703
  marking?.stop();
20457
20704
  marks?.dispose();
20458
20705
  pills?.dispose();
20459
- v.pause();
20460
- v.removeAttribute("src");
20461
- v.load();
20462
- frame.remove();
20706
+ p.dispose();
20463
20707
  }
20464
20708
  };
20465
20709
  }
@@ -20720,7 +20964,7 @@ function mediaSlot(container, options) {
20720
20964
  case "diff":
20721
20965
  return void mountDiff(host, m, look, alive, options.onEvent).then(done, fail("diff"));
20722
20966
  case "video":
20723
- return done(mountVideo(host, m, options.onEvent));
20967
+ return void mountVideo(host, m, look, alive, options.onEvent).then(done, fail("video"));
20724
20968
  case "image":
20725
20969
  return done(mountImage(host, m));
20726
20970
  case "code":
@@ -20734,7 +20978,7 @@ function mediaSlot(container, options) {
20734
20978
  case "history":
20735
20979
  return done(mountHistory(host, m, options.steps));
20736
20980
  case "stack":
20737
- return done(mountStack(host, m, options.steps));
20981
+ return done(mountStack(host, m, options.steps, options.findPR));
20738
20982
  case "table":
20739
20983
  return done(mountTable(host, m, options.steps));
20740
20984
  case "mount": {
@@ -20788,7 +21032,7 @@ function mediaSlot(container, options) {
20788
21032
  }
20789
21033
  };
20790
21034
  }
20791
- var CHECK3 = icon("check", { small: true });
21035
+ var CHECK4 = icon("check", { small: true });
20792
21036
 
20793
21037
  // src/ui/walkthrough.ts
20794
21038
  var el3 = (doc, tag, cls) => {
@@ -20875,7 +21119,7 @@ function tasteTest(container, options) {
20875
21119
  pick.append(key);
20876
21120
  }
20877
21121
  const check = el3(doc, "span", "knobs-wt-check");
20878
- check.innerHTML = CHECK3;
21122
+ check.innerHTML = CHECK4;
20879
21123
  pick.append(check);
20880
21124
  pick.addEventListener("click", () => {
20881
21125
  const next = picked === o.id ? null : o.id;
@@ -21703,11 +21947,12 @@ function walkthrough(container, options) {
21703
21947
  line.append(s);
21704
21948
  return s;
21705
21949
  };
21706
- const findPRIn = (n) => findPR(page.prs, n, page.repo);
21950
+ const findPRIn = (n, kind = "pr") => findPR(page.prs, n, page.repo, kind);
21707
21951
  const metaLink = (line, text, href) => {
21708
21952
  const n = prNumber(href);
21709
- if (n) {
21710
- const chip = prChip(doc, n, { href, ...findPRIn(n) });
21953
+ const issue = n ? null : issueNumber(href);
21954
+ if (n || issue) {
21955
+ const chip = n ? prChip(doc, n, { ...findPRIn(n), href }) : prChip(doc, issue, { ...findPRIn(issue, "issue"), href });
21711
21956
  line.append(chip);
21712
21957
  return chip;
21713
21958
  }
@@ -22036,7 +22281,7 @@ function walkthrough(container, options) {
22036
22281
  media = el3(doc, "div", "knobs-wt-media");
22037
22282
  media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
22038
22283
  for (const m of step.media)
22039
- slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
22284
+ slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks, findPR: findPRIn }));
22040
22285
  card.append(media);
22041
22286
  }
22042
22287
  let taste = null;