@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.js CHANGED
@@ -575,13 +575,14 @@ var compareCss = `
575
575
  picked: the pill's bright copy is. */
576
576
  .knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
577
577
  .knobs-cmp-modes[hidden]{display:none}
578
- .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;
579
- transition:color calc(var(--_k-hover-out) * 1ms)}
580
- .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
578
+ /* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
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:default;white-space:nowrap;
580
+ transition:background calc(var(--_k-hover-out) * 1ms)}
581
+ .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)}
581
582
  /* Toggles: icon buttons, the accent's wash when on. */
582
- .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;
583
- transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
584
- .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)}
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-2);cursor:default;
584
+ transition:background calc(var(--_k-hover-out) * 1ms)}
585
+ .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
585
586
  .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
586
587
  .knobs-cmp-toggle svg{display:block}
587
588
  .knobs-cmp-fill{flex:1}
@@ -595,11 +596,10 @@ var compareCss = `
595
596
  .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}
596
597
  .knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
597
598
  .knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
598
- /* Videos: a click on the stage plays and pauses. */
599
- .knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
600
599
  /* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
601
- .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
602
- .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
600
+ /* Zoomed, it pans: a move cursor, never a hand. */
601
+ .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:move;touch-action:none}
602
+ .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:move!important}
603
603
  /* Wiping, the grip stands in for the cursor. */
604
604
  .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
605
605
  .knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
@@ -646,8 +646,8 @@ var compareCss = `
646
646
  opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
647
647
  .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)}
648
648
  :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;
649
- 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}
650
- :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)}
649
+ 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)}
650
+ :where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
651
651
  .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}
652
652
  .knobs-cmp-note:empty{display:none}
653
653
  .knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
@@ -665,23 +665,29 @@ var playerCss = `
665
665
  .knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
666
666
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
667
667
  :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)}
668
- .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}
668
+ .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)}
669
669
  .knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
670
670
  .knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
671
671
  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)}
672
672
  .knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
673
- .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;
674
- opacity:0;filter:blur(4px);scale:.96;
675
- 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);
676
- box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
677
- 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)}
678
- .knobs-player-glyph svg{width:16px;height:16px}
679
- .knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
680
673
  .knobs-player-line{flex:none;min-height:0}
681
- /* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover
682
- states are set here, after it, so the player's line and every timeline share them. */
674
+ /* The timeline's own stylesheet is look.ts's; what the player's row and every timeline share
675
+ about the hand is set here, after it. No hand cursor anywhere (the arrow, or a resize where it
676
+ resizes); hover is a quiet fill (fill-hover), in at once (hover-in, 0 ms), out easing. */
677
+ .knobs-ui .knobs-tl-play,.knobs-ui.knobs-timeline :is(.knobs-tl-pin,.knobs-tl-rlabel),
678
+ .knobs-ui.knobs-timeline[data-dragging] .knobs-tl-marker :is(.knobs-tl-pin,.knobs-tl-mlabel){cursor:default}
683
679
  .knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
684
680
  .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
681
+ /* The line's row: a minimal play/pause (the filled pair, a 28px target, no fill at rest), then
682
+ the line, centred on it. */
683
+ .knobs-ui.knobs-timeline[data-variant="line"]{gap:4px;align-items:center}
684
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:flex}
685
+ .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);
686
+ transition:background calc(var(--_k-hover-out) * 1ms),transform .15s var(--_ku-ease)}
687
+ .knobs-ui.knobs-timeline[data-variant="line"][data-playing] .knobs-tl-play{background:transparent;color:var(--_k-fg-2)}
688
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
689
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track{cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
690
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
685
691
  `;
686
692
 
687
693
  // src/ui/overlay-css.ts
@@ -8177,196 +8183,6 @@ var init_compare_strip = __esm(() => {
8177
8183
  DIFF = { w: 32, h: 18 };
8178
8184
  });
8179
8185
 
8180
- // src/ui/player.ts
8181
- function player(container, options) {
8182
- const doc = container.ownerDocument;
8183
- const el = (tag, cls) => {
8184
- const n = doc.createElement(tag);
8185
- n.className = cls;
8186
- return n;
8187
- };
8188
- const look = {
8189
- ...options.preset ? { preset: options.preset } : {},
8190
- ...options.theme ? { theme: options.theme } : {},
8191
- ...options.appearance ? { appearance: options.appearance } : {}
8192
- };
8193
- const root = el("div", "knobs-player");
8194
- root.setAttribute("role", "group");
8195
- root.setAttribute("aria-label", options.label ?? "Video");
8196
- applyLook2(root, options);
8197
- const frame = el("div", "knobs-player-frame");
8198
- frame.style.setProperty("--kp-aspect", String(options.aspect ?? 16 / 9));
8199
- const v = el("video", "knobs-player-media");
8200
- v.muted = options.muted ?? true;
8201
- v.defaultMuted = v.muted;
8202
- v.playsInline = true;
8203
- v.preload = "metadata";
8204
- v.setAttribute("playsinline", "");
8205
- if (options.poster)
8206
- v.poster = options.poster;
8207
- if (options.captions) {
8208
- const track = doc.createElement("track");
8209
- track.kind = "captions";
8210
- track.label = "Captions";
8211
- track.srclang = doc.documentElement.lang || "en";
8212
- track.default = true;
8213
- track.src = options.captions;
8214
- v.append(track);
8215
- }
8216
- const glyph = playGlyph(doc);
8217
- frame.append(v, glyph.element);
8218
- const host = el("div", "knobs-player-line");
8219
- root.append(frame, host);
8220
- container.append(root);
8221
- const markers = (options.chapters ?? []).map((c, i) => ({
8222
- id: c.id ?? `c${i}`,
8223
- time: c.start,
8224
- label: c.label
8225
- }));
8226
- const tl = timeline(host, {
8227
- ...look,
8228
- variant: "line",
8229
- duration: 1,
8230
- fps: options.fps ?? 30,
8231
- markers,
8232
- repeat: options.loop ?? true,
8233
- arrows: { step: 5, shift: 10 },
8234
- label: options.label ? `${options.label} time` : "Video time",
8235
- ...options.hints === false ? { hints: false } : {}
8236
- });
8237
- const onFrame = () => root.setAttribute("data-ready", "");
8238
- let pending = null;
8239
- const seekVideo = (t) => {
8240
- if (v.readyState < 1)
8241
- return;
8242
- if (v.seeking)
8243
- pending = t;
8244
- else
8245
- v.currentTime = t;
8246
- };
8247
- let reported = 0;
8248
- const onSeeked = () => {
8249
- if (pending !== null) {
8250
- const t = pending;
8251
- pending = null;
8252
- v.currentTime = t;
8253
- return;
8254
- }
8255
- if (!v.paused || tl.playing || Math.abs(v.currentTime - reported) < 0.1)
8256
- return;
8257
- reported = v.currentTime;
8258
- options.onSeek?.(reported);
8259
- };
8260
- const onMeta = () => {
8261
- onFrame();
8262
- if (v.videoWidth)
8263
- frame.style.setProperty("--kp-aspect", String(v.videoWidth / v.videoHeight));
8264
- if (Number.isFinite(v.duration))
8265
- tl.update({ duration: v.duration });
8266
- };
8267
- v.addEventListener("loadedmetadata", onMeta);
8268
- v.addEventListener("loadeddata", onFrame);
8269
- v.addEventListener("seeked", onSeeked);
8270
- const unlisten = tl.listen({
8271
- onSeek(t, source) {
8272
- if (source === "play") {
8273
- if (Math.abs(v.currentTime - t) > 0.25)
8274
- v.currentTime = t;
8275
- return;
8276
- }
8277
- if (Math.abs(v.currentTime - t) < 1 / (options.fps ?? 30))
8278
- return;
8279
- seekVideo(t);
8280
- },
8281
- onPlay(on) {
8282
- if (on)
8283
- v.play().catch(() => tl.pause());
8284
- else {
8285
- v.pause();
8286
- reported = tl.time;
8287
- }
8288
- root.toggleAttribute("data-playing", on);
8289
- options.onPlay?.(on, tl.time);
8290
- },
8291
- onScrub(on) {
8292
- if (on)
8293
- v.pause();
8294
- }
8295
- });
8296
- tl.drive(() => {
8297
- if (v.readyState < 1)
8298
- return Number.NaN;
8299
- if (v.seeking)
8300
- return tl.time;
8301
- return v.ended ? Number.POSITIVE_INFINITY : v.currentTime;
8302
- });
8303
- const onClick = (e) => {
8304
- if (e.button !== 0)
8305
- return;
8306
- tl.toggle();
8307
- glyph.flash(tl.playing);
8308
- tl.focus();
8309
- };
8310
- frame.addEventListener("click", onClick);
8311
- v.src = options.src;
8312
- const api = {
8313
- element: root,
8314
- video: v,
8315
- timeline: tl,
8316
- get time() {
8317
- return tl.time;
8318
- },
8319
- get playing() {
8320
- return tl.playing;
8321
- },
8322
- get duration() {
8323
- return Number.isFinite(v.duration) ? v.duration : 0;
8324
- },
8325
- play: () => tl.play(),
8326
- pause: () => tl.pause(),
8327
- toggle: () => tl.toggle(),
8328
- seek: (t, o) => tl.seek(t, o?.glide ? { glide: true } : undefined),
8329
- seekBy: (d) => tl.seek(Math.min(tl.duration, Math.max(0, tl.time + d)), { glide: true }),
8330
- focus: () => tl.focus(),
8331
- dispose() {
8332
- unlisten();
8333
- glyph.dispose();
8334
- tl.drive(null);
8335
- tl.dispose();
8336
- v.pause();
8337
- v.removeEventListener("loadedmetadata", onMeta);
8338
- v.removeEventListener("loadeddata", onFrame);
8339
- v.removeEventListener("seeked", onSeeked);
8340
- v.removeAttribute("src");
8341
- v.load();
8342
- root.remove();
8343
- }
8344
- };
8345
- return api;
8346
- }
8347
- function playGlyph(doc) {
8348
- const element = doc.createElement("span");
8349
- element.className = "knobs-player-glyph";
8350
- element.setAttribute("aria-hidden", "true");
8351
- let timer = 0;
8352
- const win = doc.defaultView;
8353
- return {
8354
- element,
8355
- flash(playing) {
8356
- element.innerHTML = playing ? PLAY : PAUSE;
8357
- element.setAttribute("data-flash", "");
8358
- win.clearTimeout(timer);
8359
- timer = win.setTimeout(() => element.removeAttribute("data-flash"), FLASH_MS);
8360
- },
8361
- dispose: () => win.clearTimeout(timer)
8362
- };
8363
- }
8364
- var FLASH_MS = 450;
8365
- var init_player = __esm(() => {
8366
- init_look();
8367
- init_timeline();
8368
- });
8369
-
8370
8186
  // src/ui/compare.ts
8371
8187
  function compare(container, options) {
8372
8188
  const doc = container.ownerDocument;
@@ -8485,8 +8301,7 @@ function compare(container, options) {
8485
8301
  grip.setAttribute("aria-hidden", "true");
8486
8302
  handle.append(grip);
8487
8303
  const note = el("div", "knobs-cmp-note");
8488
- const glyph = playGlyph(doc);
8489
- stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note, glyph.element);
8304
+ stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note);
8490
8305
  root.append(bar, stage);
8491
8306
  const wantsStrip = options.strip === true;
8492
8307
  let tl = bound;
@@ -8528,7 +8343,6 @@ function compare(container, options) {
8528
8343
  let drag = null;
8529
8344
  const touches = new Map;
8530
8345
  let pinch = null;
8531
- let press = null;
8532
8346
  const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
8533
8347
  const zv = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
8534
8348
  const zw = new Spring(1, ZOOM.omega, ZOOM.zeta, 0.00001);
@@ -9357,10 +9171,8 @@ function compare(container, options) {
9357
9171
  root.focus({ preventScroll: true });
9358
9172
  touches.set(e.pointerId, p);
9359
9173
  stage.setPointerCapture(e.pointerId);
9360
- press = touches.size === 1 && mode !== "wipe" && master() ? p : null;
9361
9174
  if (touches.size >= 2) {
9362
9175
  e.preventDefault();
9363
- press = null;
9364
9176
  return startPinch();
9365
9177
  }
9366
9178
  if (mode === "wipe") {
@@ -9388,8 +9200,6 @@ function compare(container, options) {
9388
9200
  const onMove = (e) => {
9389
9201
  const p = local(e);
9390
9202
  pointer = p;
9391
- if (press && Math.hypot(p.x - press.x, p.y - press.y) > 4)
9392
- press = null;
9393
9203
  if (touches.has(e.pointerId))
9394
9204
  touches.set(e.pointerId, p);
9395
9205
  if (pinch && touches.size >= 2) {
@@ -9419,11 +9229,6 @@ function compare(container, options) {
9419
9229
  drawNow();
9420
9230
  };
9421
9231
  const endDrag = (e) => {
9422
- if (press && e.type === "pointerup") {
9423
- api.toggle();
9424
- glyph.flash(isPlaying());
9425
- }
9426
- press = null;
9427
9232
  touches.delete(e.pointerId);
9428
9233
  if (touches.size < 2)
9429
9234
  pinch = null;
@@ -9585,7 +9390,6 @@ function compare(container, options) {
9585
9390
  old.el.replaceWith(s.el);
9586
9391
  old.preview.replaceWith(s.preview);
9587
9392
  sides[index] = s;
9588
- root.toggleAttribute("data-video", sides.some((x) => x.kind === "video"));
9589
9393
  note.textContent = "";
9590
9394
  setLabel(index, labelOf(index));
9591
9395
  if (near)
@@ -9600,7 +9404,6 @@ function compare(container, options) {
9600
9404
  stage.insertBefore(sides[0].preview, labels[0]);
9601
9405
  setLabel(0, labelOf(0));
9602
9406
  setLabel(1, labelOf(1));
9603
- root.toggleAttribute("data-video", sides.some((s) => s.kind === "video"));
9604
9407
  loupeBtn.setAttribute("aria-pressed", String(loupeOn));
9605
9408
  loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
9606
9409
  const modePill = modeList.length > 1 ? selectionPill(modes, {
@@ -9801,7 +9604,6 @@ function compare(container, options) {
9801
9604
  disposed = true;
9802
9605
  loop.stop();
9803
9606
  modePill?.dispose();
9804
- glyph.dispose();
9805
9607
  win.clearTimeout(releaseTimer);
9806
9608
  ro.disconnect();
9807
9609
  nearIo?.disconnect();
@@ -9842,7 +9644,6 @@ var init_compare = __esm(() => {
9842
9644
  init_icons();
9843
9645
  init_look();
9844
9646
  init_rgba();
9845
- init_player();
9846
9647
  init_selection_pill();
9847
9648
  init_timeline();
9848
9649
  init_timeline_model();
@@ -9861,6 +9662,171 @@ var init_compare = __esm(() => {
9861
9662
  };
9862
9663
  });
9863
9664
 
9665
+ // src/ui/player.ts
9666
+ function player(container, options) {
9667
+ const doc = container.ownerDocument;
9668
+ const el = (tag, cls) => {
9669
+ const n = doc.createElement(tag);
9670
+ n.className = cls;
9671
+ return n;
9672
+ };
9673
+ const look = {
9674
+ ...options.preset ? { preset: options.preset } : {},
9675
+ ...options.theme ? { theme: options.theme } : {},
9676
+ ...options.appearance ? { appearance: options.appearance } : {}
9677
+ };
9678
+ const root = el("div", "knobs-player");
9679
+ root.setAttribute("role", "group");
9680
+ root.setAttribute("aria-label", options.label ?? "Video");
9681
+ applyLook2(root, options);
9682
+ const frame = el("div", "knobs-player-frame");
9683
+ frame.style.setProperty("--kp-aspect", String(options.aspect ?? 16 / 9));
9684
+ const v = el("video", "knobs-player-media");
9685
+ v.muted = options.muted ?? true;
9686
+ v.defaultMuted = v.muted;
9687
+ v.playsInline = true;
9688
+ v.preload = "metadata";
9689
+ v.setAttribute("playsinline", "");
9690
+ if (options.poster)
9691
+ v.poster = options.poster;
9692
+ if (options.captions) {
9693
+ const track = doc.createElement("track");
9694
+ track.kind = "captions";
9695
+ track.label = "Captions";
9696
+ track.srclang = doc.documentElement.lang || "en";
9697
+ track.default = true;
9698
+ track.src = options.captions;
9699
+ v.append(track);
9700
+ }
9701
+ frame.append(v);
9702
+ const host = el("div", "knobs-player-line");
9703
+ root.append(frame, host);
9704
+ container.append(root);
9705
+ const markers = (options.chapters ?? []).map((c, i) => ({
9706
+ id: c.id ?? `c${i}`,
9707
+ time: c.start,
9708
+ label: c.label
9709
+ }));
9710
+ const tl = timeline(host, {
9711
+ ...look,
9712
+ variant: "line",
9713
+ duration: 1,
9714
+ fps: options.fps ?? 30,
9715
+ markers,
9716
+ repeat: options.loop ?? true,
9717
+ arrows: { step: 5, shift: 10 },
9718
+ label: options.label ? `${options.label} time` : "Video time",
9719
+ ...options.hints === false ? { hints: false } : {}
9720
+ });
9721
+ const onFrame = () => root.setAttribute("data-ready", "");
9722
+ let pending = null;
9723
+ const seekVideo = (t) => {
9724
+ if (v.readyState < 1)
9725
+ return;
9726
+ if (v.seeking)
9727
+ pending = t;
9728
+ else
9729
+ v.currentTime = t;
9730
+ };
9731
+ let reported = 0;
9732
+ const onSeeked = () => {
9733
+ if (pending !== null) {
9734
+ const t = pending;
9735
+ pending = null;
9736
+ v.currentTime = t;
9737
+ return;
9738
+ }
9739
+ if (!v.paused || tl.playing || Math.abs(v.currentTime - reported) < 0.1)
9740
+ return;
9741
+ reported = v.currentTime;
9742
+ options.onSeek?.(reported);
9743
+ };
9744
+ const onMeta = () => {
9745
+ onFrame();
9746
+ if (v.videoWidth)
9747
+ frame.style.setProperty("--kp-aspect", String(v.videoWidth / v.videoHeight));
9748
+ if (Number.isFinite(v.duration))
9749
+ tl.update({ duration: v.duration });
9750
+ };
9751
+ v.addEventListener("loadedmetadata", onMeta);
9752
+ v.addEventListener("loadeddata", onFrame);
9753
+ v.addEventListener("seeked", onSeeked);
9754
+ const unlisten = tl.listen({
9755
+ onSeek(t, source) {
9756
+ if (source === "play") {
9757
+ if (Math.abs(v.currentTime - t) > 0.25)
9758
+ v.currentTime = t;
9759
+ return;
9760
+ }
9761
+ if (Math.abs(v.currentTime - t) < 1 / (options.fps ?? 30))
9762
+ return;
9763
+ seekVideo(t);
9764
+ },
9765
+ onPlay(on) {
9766
+ if (on)
9767
+ v.play().catch(() => tl.pause());
9768
+ else {
9769
+ v.pause();
9770
+ reported = tl.time;
9771
+ }
9772
+ root.toggleAttribute("data-playing", on);
9773
+ options.onPlay?.(on, tl.time);
9774
+ },
9775
+ onScrub(on) {
9776
+ if (on)
9777
+ v.pause();
9778
+ }
9779
+ });
9780
+ tl.drive(() => {
9781
+ if (v.readyState < 1)
9782
+ return Number.NaN;
9783
+ if (v.seeking)
9784
+ return tl.time;
9785
+ return v.ended ? Number.POSITIVE_INFINITY : v.currentTime;
9786
+ });
9787
+ const onClick = () => tl.focus();
9788
+ frame.addEventListener("click", onClick);
9789
+ v.src = options.src;
9790
+ const api = {
9791
+ element: root,
9792
+ video: v,
9793
+ timeline: tl,
9794
+ get time() {
9795
+ return tl.time;
9796
+ },
9797
+ get playing() {
9798
+ return tl.playing;
9799
+ },
9800
+ get duration() {
9801
+ return Number.isFinite(v.duration) ? v.duration : 0;
9802
+ },
9803
+ play: () => tl.play(),
9804
+ pause: () => tl.pause(),
9805
+ toggle: () => tl.toggle(),
9806
+ seek: (t, o) => tl.seek(t, o?.glide ? { glide: true } : undefined),
9807
+ seekBy: (d) => tl.seek(Math.min(tl.duration, Math.max(0, tl.time + d)), { glide: true }),
9808
+ focus: () => tl.focus(),
9809
+ dispose() {
9810
+ unlisten();
9811
+ tl.drive(null);
9812
+ tl.dispose();
9813
+ v.pause();
9814
+ frame.removeEventListener("click", onClick);
9815
+ v.removeEventListener("loadedmetadata", onMeta);
9816
+ v.removeEventListener("loadeddata", onFrame);
9817
+ v.removeEventListener("seeked", onSeeked);
9818
+ v.removeAttribute("src");
9819
+ v.load();
9820
+ root.remove();
9821
+ }
9822
+ };
9823
+ return api;
9824
+ }
9825
+ var init_player = __esm(() => {
9826
+ init_look();
9827
+ init_timeline();
9828
+ });
9829
+
9864
9830
  // src/ui/scrollbar.ts
9865
9831
  function overlayScrollbar(scroller, content) {
9866
9832
  const doc = scroller.ownerDocument;
@@ -10935,16 +10901,17 @@ var init_diff_css = __esm(() => {
10935
10901
  .knobs-diff-nav>span{min-width:3.6em;text-align:center}
10936
10902
  :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;
10937
10903
  margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
10938
- .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;
10939
- transition:background .15s,color .15s,opacity .15s}
10940
- .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)}
10904
+ .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;
10905
+ transition:background calc(var(--_k-hover-out) * 1ms),opacity .15s}
10906
+ /* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
10907
+ .knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
10941
10908
  .knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
10942
10909
  .knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
10943
10910
  .knobs-diff-btn svg{width:16px;height:16px}
10944
10911
  /* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
10945
10912
  .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);
10946
- font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
10947
- .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)}
10913
+ 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}
10914
+ .knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
10948
10915
  .knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
10949
10916
  .knobs-diff-viewed svg{width:16px;height:16px}
10950
10917
  .knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
@@ -10963,8 +10930,8 @@ var init_diff_css = __esm(() => {
10963
10930
  choice isn't brighter for being picked. Choices are styled by class, as the copy holds them too. */
10964
10931
  .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)}
10965
10932
  .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);
10966
- font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
10967
- .knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
10933
+ font-weight:var(--_k-weight-label);white-space:nowrap;cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
10934
+ .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)}
10968
10935
  /* The diff: one layer per file or layout on show, the old fading under the new. */
10969
10936
  .knobs-diff-body{position:relative;flex:1;min-height:0}
10970
10937
  .knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
@@ -11060,8 +11027,8 @@ var init_diff_css = __esm(() => {
11060
11027
  .knobs-diff-filter svg{width:12px;height:12px;flex:none}
11061
11028
  .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}
11062
11029
  .knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
11063
- .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}
11064
- .knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
11030
+ .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}
11031
+ .knobs-ui .knobs-diff-filter>button:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
11065
11032
  .knobs-diff-filter>button svg{width:12px;height:12px}
11066
11033
  .knobs-diff-list{position:relative;flex:1;min-height:0}
11067
11034
  .knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
@@ -17451,25 +17418,63 @@ function normalizeWalkthrough(doc) {
17451
17418
  })),
17452
17419
  sections: withKickers(sections, doc.sections, doc.kickers),
17453
17420
  steps,
17454
- prs: knownPRs(doc, sections),
17421
+ prs: knownPRs(doc, sections, steps),
17455
17422
  ...repoOf(doc) ? { repo: repoOf(doc) } : {}
17456
17423
  };
17457
17424
  }
17458
17425
  var prNumber = (href) => /\/(?:pull|merge_requests)\/(\d+)(?:[/?#]|$)/.exec(href)?.[1] ?? null;
17459
17426
  var repoOf = (doc) => doc.repo?.trim().replace(/^https?:\/\/github\.com\//, "").replace(/\/+$/, "") || undefined;
17460
- function knownPRs(doc, sections) {
17427
+ var PR_STATES = ["merged", "open", "draft", "closed"];
17428
+ function prStatus(status) {
17429
+ const words = (status ?? "").trim().toLowerCase();
17430
+ const first = /^[a-z]+/.exec(words)?.[0] ?? "";
17431
+ const state = PR_STATES.includes(first) ? first : first === "shipped" || first === "done" ? "merged" : null;
17432
+ return { state, checks: /\b(green|passing|passed|ci|checks? pass)/.test(words) };
17433
+ }
17434
+ function knownPRs(doc, sections, steps = []) {
17461
17435
  const out = {};
17462
17436
  const repo = repoOf(doc);
17463
- for (const s of sections) {
17464
- const n = s.link && prNumber(s.link.href);
17465
- if (n)
17466
- out[n] = { href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1") };
17467
- }
17437
+ const add = (n, pr) => {
17438
+ const known = out[n] ?? {};
17439
+ const next = { ...known };
17440
+ for (const [k, v] of Object.entries(pr))
17441
+ if (v !== undefined && next[k] === undefined)
17442
+ next[k] = v;
17443
+ out[n] = next;
17444
+ };
17468
17445
  for (const [k, pr] of Object.entries(doc.prs ?? {})) {
17469
17446
  const n = k.replace(/^#/, "");
17470
17447
  if (!/^\d+$/.test(n))
17471
17448
  continue;
17472
- out[n] = { ...out[n], ...pr };
17449
+ add(n, pr);
17450
+ }
17451
+ for (const step of steps)
17452
+ for (const m of step.media) {
17453
+ if (m.kind !== "stack")
17454
+ continue;
17455
+ for (const it of m.items) {
17456
+ const n = it.pr.replace(/^#/, "");
17457
+ if (!/^\d+$/.test(n))
17458
+ continue;
17459
+ const said = prStatus(it.status);
17460
+ add(n, {
17461
+ ...said.state ? { state: said.state } : {},
17462
+ ...said.state && said.checks ? { checks: true } : {},
17463
+ ...it.title ? { title: plainMarkdown(it.title) } : {},
17464
+ ...it.href ? { href: it.href } : {}
17465
+ });
17466
+ }
17467
+ }
17468
+ for (const s of sections) {
17469
+ const n = s.link && prNumber(s.link.href);
17470
+ if (!n)
17471
+ continue;
17472
+ const said = prStatus(s.status);
17473
+ add(n, {
17474
+ href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1"),
17475
+ ...said.state ? { state: said.state } : {},
17476
+ title: plainMarkdown(s.title)
17477
+ });
17473
17478
  }
17474
17479
  if (repo) {
17475
17480
  for (const n of Object.keys(out))
@@ -17478,12 +17483,17 @@ function knownPRs(doc, sections) {
17478
17483
  }
17479
17484
  return out;
17480
17485
  }
17481
- function findPR(prs, n, repo) {
17482
- const known = prs[n];
17486
+ function findPR(prs, n, repo, kind = "pr") {
17487
+ const entry = prs[n];
17488
+ const known = entry && (entry.kind ?? "pr") === kind ? entry : undefined;
17489
+ const path = kind === "issue" ? "issues" : "pull";
17490
+ const href = repo ? `https://github.com/${repo}/${path}/${n}` : undefined;
17491
+ const base = kind === "issue" ? { kind } : {};
17483
17492
  if (known)
17484
- return known;
17485
- return repo ? { href: `https://github.com/${repo}/pull/${n}` } : null;
17493
+ return known.href || !href ? { ...base, ...known } : { ...base, ...known, href };
17494
+ return href ? { ...base, href } : kind === "issue" ? { kind } : null;
17486
17495
  }
17496
+ var issueNumber = (href) => /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/.exec(href)?.[1] ?? null;
17487
17497
  var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
17488
17498
  function withKickers(sections, source, kickers = "pr") {
17489
17499
  if (kickers === false)
@@ -21979,7 +21989,7 @@ function mountHistory(host, m, steps) {
21979
21989
  context: () => ({ kind: "history" })
21980
21990
  };
21981
21991
  }
21982
- function mountStack(host, m, steps) {
21992
+ function mountStack(host, m, steps, findPR) {
21983
21993
  const doc = host.ownerDocument;
21984
21994
  const layout = stackLayout(m);
21985
21995
  const root = el2(doc, "div", "knobs-wt-stack");
@@ -22021,7 +22031,10 @@ function mountStack(host, m, steps) {
22021
22031
  const line = el2(doc, "div", "knobs-wt-sline");
22022
22032
  if (it.order)
22023
22033
  line.append(text2(doc, "span", "knobs-wt-sord", it.order));
22024
- line.append(link(doc, "knobs-wt-spr", it.pr, it.href));
22034
+ const known = findPR?.(it.pr.replace(/^#/, "")) ?? null;
22035
+ const pr = link(doc, "knobs-wt-spr", it.pr, it.href ?? known?.href);
22036
+ pr.addEventListener("click", (e) => e.stopPropagation());
22037
+ line.append(pr);
22025
22038
  const target = it.step ? steps?.find(it.step) : null;
22026
22039
  if (target && steps) {
22027
22040
  const title = text2(doc, "button", "knobs-wt-stitle", it.title);
@@ -22056,7 +22069,11 @@ function mountStack(host, m, steps) {
22056
22069
  if (meta.childNodes.length)
22057
22070
  main.append(meta);
22058
22071
  row.append(main);
22059
- if (it.status) {
22072
+ const said = prStatus(it.status);
22073
+ const state = known?.state ?? said.state;
22074
+ if (state)
22075
+ row.append(prStateLabel(doc, state, said.checks, it.status));
22076
+ else if (it.status) {
22060
22077
  const chip = text2(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
22061
22078
  chip.dataset.tone = it.tone ?? "dim";
22062
22079
  row.append(chip);
@@ -22363,8 +22380,29 @@ var PR_PATHS = {
22363
22380
  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",
22364
22381
  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"
22365
22382
  };
22366
- 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>`;
22383
+ 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";
22384
+ 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";
22385
+ 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";
22386
+ 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>`;
22387
+ var prGlyph = (state) => state === "merged" ? svg12(PR_PATHS.merge) : state === "draft" ? svg12(DRAFT, true) : state === "closed" ? svg12(X) : svg12(PR_PATHS.pull);
22367
22388
  var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
22389
+ 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";
22390
+ function prStateLabel(doc, state, checks, title) {
22391
+ const s = el2(doc, "span", "knobs-wt-prstate");
22392
+ s.dataset.state = state;
22393
+ s.innerHTML = prGlyph(state);
22394
+ s.append(text2(doc, "span", "", PR_STATE[state]));
22395
+ if (checks) {
22396
+ const c = el2(doc, "span", "knobs-wt-prchecks");
22397
+ c.innerHTML = svg12(CHECK3);
22398
+ c.title = "Checks pass";
22399
+ c.setAttribute("aria-label", "checks pass");
22400
+ s.append(c);
22401
+ }
22402
+ if (title)
22403
+ s.title = title;
22404
+ return s;
22405
+ }
22368
22406
  function prChip(doc, n, pr) {
22369
22407
  const chip = el2(doc, pr.href ? "a" : "span", "knobs-wt-pr");
22370
22408
  if (pr.href) {
@@ -22372,27 +22410,55 @@ function prChip(doc, n, pr) {
22372
22410
  chip.setAttribute("target", "_blank");
22373
22411
  chip.setAttribute("rel", "noreferrer");
22374
22412
  }
22413
+ const issue = pr.kind === "issue";
22414
+ if (issue)
22415
+ chip.dataset.kind = "issue";
22375
22416
  if (pr.state)
22376
22417
  chip.dataset.state = pr.state;
22377
- chip.innerHTML = prGlyph(pr.state === "merged");
22418
+ chip.innerHTML = issue ? svg12(ISSUE) : prGlyph(pr.state);
22378
22419
  chip.append(text2(doc, "span", "", `#${n}`));
22379
- const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
22380
- chip.title = tip;
22381
- chip.setAttribute("aria-label", `Pull request ${tip}`);
22420
+ if (pr.checks) {
22421
+ const c = el2(doc, "span", "knobs-wt-prchecks");
22422
+ c.innerHTML = svg12(CHECK3);
22423
+ chip.append(c);
22424
+ }
22425
+ const words = [
22426
+ `${issue ? "Issue" : "Pull request"} #${n}`,
22427
+ pr.title,
22428
+ pr.state ? PR_STATE[pr.state] : "",
22429
+ pr.checks ? "checks pass" : ""
22430
+ ].filter(Boolean);
22431
+ chip.title = words.slice(1).length ? `#${n} · ${words.slice(1).join(" · ")}` : `#${n}`;
22432
+ chip.setAttribute("aria-label", words.join(" · "));
22382
22433
  return chip;
22383
22434
  }
22384
22435
  var PR_URL = /^https?:\/\/[^/]+\/.+\/(?:pull|merge_requests)\/(\d+)\/?(?:[?#].*)?$/;
22385
- var PR_REF = /(^|[^\w&/#])#(\d{3,7})\b/g;
22436
+ var ISSUE_URL = /^https?:\/\/[^/]+\/.+\/issues\/(\d+)\/?(?:[?#].*)?$/;
22437
+ var REF = /(^|[^\w&/#:])(issue:)?#(\d{3,7})\b/gi;
22438
+ var WHOLE_REF = /^(issue:)?#(\d{1,7})$/i;
22439
+ var KEEP = 'a,pre,button,.knobs-wt-pr,.knobs-wt-slot:not([data-kind="table"]),.knobs-wt-stack';
22386
22440
  function linkPRs(root, find) {
22387
22441
  const doc = root.ownerDocument;
22442
+ const chip = (n, kind, href) => prChip(doc, n, { ...kind === "issue" ? { kind } : {}, ...find(n, kind), ...href ? { href } : {} });
22388
22443
  for (const a of Array.from(root.querySelectorAll("a[href]"))) {
22389
- if (a.classList.contains("knobs-wt-pr") || a.closest(".knobs-wt-slot,button"))
22444
+ if (a.classList.contains("knobs-wt-pr") || a.parentElement?.closest(KEEP))
22445
+ continue;
22446
+ const url = a.getAttribute("href") ?? "";
22447
+ const pr = PR_URL.exec(url)?.[1];
22448
+ const n = pr ?? ISSUE_URL.exec(url)?.[1];
22449
+ if (!n)
22390
22450
  continue;
22391
- const n = PR_URL.exec(a.getAttribute("href") ?? "")?.[1];
22392
- const words = a.textContent?.trim() ?? "";
22393
- if (!n || !(words === `#${n}` || words === n || words === a.getAttribute("href")))
22451
+ const words = (a.textContent ?? "").trim().replace(/^issue:/i, "");
22452
+ if (!(words === `#${n}` || words === n || words === url))
22394
22453
  continue;
22395
- a.replaceWith(prChip(doc, n, { href: a.href, ...find(n) }));
22454
+ a.replaceWith(chip(n, pr ? "pr" : "issue", a.href));
22455
+ }
22456
+ for (const c of Array.from(root.querySelectorAll("code"))) {
22457
+ if (c.closest("pre") || c.parentElement?.closest(KEEP))
22458
+ continue;
22459
+ const m = WHOLE_REF.exec((c.textContent ?? "").trim());
22460
+ if (m)
22461
+ c.replaceWith(chip(m[2], m[1] ? "issue" : "pr"));
22396
22462
  }
22397
22463
  const walker = doc.createTreeWalker(root, 5);
22398
22464
  const hits = [];
@@ -22400,29 +22466,22 @@ function linkPRs(root, find) {
22400
22466
  if (t.nodeType !== 3)
22401
22467
  continue;
22402
22468
  const p = t.parentElement;
22403
- if (!p || p.closest("a,code,pre,button,.knobs-wt-slot,.knobs-wt-pr"))
22469
+ if (!p || p.closest(`${KEEP},code`))
22404
22470
  continue;
22405
- PR_REF.lastIndex = 0;
22406
- if (PR_REF.test(t.nodeValue ?? ""))
22471
+ REF.lastIndex = 0;
22472
+ if (REF.test(t.nodeValue ?? ""))
22407
22473
  hits.push(t);
22408
22474
  }
22409
22475
  for (const t of hits) {
22410
22476
  const value = t.nodeValue ?? "";
22411
22477
  const frag = doc.createDocumentFragment();
22412
22478
  let last = 0;
22413
- let changed = false;
22414
- PR_REF.lastIndex = 0;
22415
- for (let m = PR_REF.exec(value);m; m = PR_REF.exec(value)) {
22416
- const pr = find(m[2]);
22417
- if (!pr)
22418
- continue;
22479
+ REF.lastIndex = 0;
22480
+ for (let m = REF.exec(value);m; m = REF.exec(value)) {
22419
22481
  const at = m.index + m[1].length;
22420
- frag.append(value.slice(last, at), prChip(doc, m[2], pr));
22421
- last = at + m[2].length + 1;
22422
- changed = true;
22482
+ frag.append(value.slice(last, at), chip(m[3], m[2] ? "issue" : "pr"));
22483
+ last = m.index + m[0].length;
22423
22484
  }
22424
- if (!changed)
22425
- continue;
22426
22485
  frag.append(value.slice(last));
22427
22486
  t.replaceWith(frag);
22428
22487
  }
@@ -22441,7 +22500,7 @@ var snappy3 = springCss(SPRINGS.snappy);
22441
22500
  var calm = springCss(SPRINGS.calm);
22442
22501
  var state = springCss(SPRINGS.default);
22443
22502
  var quick = springCss(FADE);
22444
- 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)";
22503
+ 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)";
22445
22504
  var walkthroughCss = `
22446
22505
  :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
22447
22506
  .knobs-ui:is(.knobs-walkthrough,.knobs-wt-slot){--k-glass:0;--k-glow:0;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms}
@@ -22453,7 +22512,7 @@ var walkthroughCss = `
22453
22512
  .knobs-walkthrough [hidden]{display:none!important}
22454
22513
  /* (0,0,1): a host's element rules stop here, and every class rule below wins over it. */
22455
22514
  :where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:inherit}
22456
- .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}
22515
+ .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)}
22457
22516
  .knobs-walkthrough a:hover{text-decoration-color:currentColor}
22458
22517
  .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)}
22459
22518
  :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;
@@ -22481,14 +22540,17 @@ var walkthroughCss = `
22481
22540
  .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)}
22482
22541
  .knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr):hover{color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22483
22542
  .knobs-wt-metaline svg{align-self:center;width:12px;height:12px;margin-left:2px}
22484
- /* A pull request: a small chip, its state's icon and number in a quiet tint of the state (open
22485
- the success hue, merged the violet series, draft and closed neutral), its title on hover. */
22486
- .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;
22543
+ /* A pull request or issue: a capsule, its state's icon and number in a quiet tint of the state
22544
+ (open the success hue, merged the violet series, draft and closed faint, unknown neutral; a
22545
+ closed issue is done, so violet), a faint check where its checks pass, its title on hover. */
22546
+ .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;
22487
22547
  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;
22488
22548
  transition:background calc(var(--_k-hover-out) * 1ms)}
22489
22549
  .knobs-ui .knobs-wt-pr[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
22490
22550
  .knobs-ui .knobs-wt-pr[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
22491
22551
  .knobs-ui .knobs-wt-pr:is([data-state="draft"],[data-state="closed"]){--kw-pr:var(--_k-fg-3)}
22552
+ .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))}
22553
+ .knobs-ui .knobs-wt-pr .knobs-wt-prchecks{margin-left:0}
22492
22554
  .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)}
22493
22555
  .knobs-wt-pr svg{flex:none}
22494
22556
  .knobs-wt-metaline .knobs-wt-pr{align-self:center;margin:0}
@@ -22547,14 +22609,14 @@ var walkthroughCss = `
22547
22609
  .knobs-wt-fi-ask{max-width:var(--kw-measure);color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:pretty}
22548
22610
  .knobs-wt-fi-text{max-width:var(--kw-measure);color:var(--_k-fg);text-wrap:pretty}
22549
22611
  .knobs-wt-fi-answer{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
22550
- :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;
22612
+ :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;
22551
22613
  transition:color calc(var(--_k-hover-out) * 1ms)}
22552
22614
  .knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22553
22615
 
22554
22616
  /* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
22555
22617
  script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
22556
22618
  .knobs-wt-disc{display:flex;flex-direction:column;min-width:0}
22557
- .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;
22619
+ .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;
22558
22620
  border-radius:calc(var(--_k-radius) / 2);transition:color calc(var(--_k-hover-out) * 1ms)}
22559
22621
  .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)}
22560
22622
  .knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
@@ -22581,7 +22643,7 @@ var walkthroughCss = `
22581
22643
  .knobs-wt-n[data-leaving]{opacity:0;filter:blur(3px);padding-block:0}
22582
22644
  .knobs-wt-n+.knobs-wt-n{box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
22583
22645
  .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)}
22584
- .knobs-wt-n-meta[data-seek]{cursor:pointer}
22646
+ .knobs-wt-n-meta[data-seek]{cursor:default}
22585
22647
  .knobs-wt-n-meta[data-seek]:hover{color:var(--_k-fg)}
22586
22648
  .knobs-wt-n-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
22587
22649
  .knobs-wt-n-voice{height:30px;max-width:320px}
@@ -22598,7 +22660,7 @@ var walkthroughCss = `
22598
22660
  /* Collapsed: the launcher and the bar swap in place, a quick fade with a little scale and blur
22599
22661
  from the launcher's corner; the exit is the shorter. */
22600
22662
  :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);
22601
- 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%;
22663
+ 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%;
22602
22664
  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)}
22603
22665
  .knobs-ui .knobs-wt-launch:is(:hover,:focus-visible){color:var(--_k-fg)}
22604
22666
  .knobs-ui .knobs-wt-launch:focus-visible{box-shadow:var(--_k-focus-ring)}
@@ -22618,11 +22680,11 @@ var walkthroughCss = `
22618
22680
  .knobs-wt-attach{display:flex;gap:4px;align-items:center;align-self:center}
22619
22681
  .knobs-wt-attach:empty{display:none}
22620
22682
  .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}
22621
- .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}
22683
+ .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}
22622
22684
  .knobs-ui .knobs-wt-x:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
22623
22685
  .knobs-wt-x svg{width:12px;height:12px}
22624
22686
  .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);
22625
- color:var(--_k-fg-2);cursor:pointer;transition:background .15s,color .15s}
22687
+ color:var(--_k-fg-2);cursor:default;transition:background .15s,color .15s}
22626
22688
  .knobs-ui .knobs-wt-tool:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
22627
22689
  .knobs-ui .knobs-wt-tool[aria-pressed="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
22628
22690
  .knobs-wt-tool svg{width:16px;height:16px}
@@ -22632,7 +22694,7 @@ var walkthroughCss = `
22632
22694
  .knobs-ui .knobs-wt-tool[data-send][data-ready]{background:var(--_k-accent);color:var(--_k-bg)}
22633
22695
  /* Back: in place over the bar for a few seconds after a note moves the page on. */
22634
22696
  .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;
22635
- 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;
22697
+ 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;
22636
22698
  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)}
22637
22699
  .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)}
22638
22700
  .knobs-ui .knobs-wt-back:hover{background:var(--_k-fill-selected)}
@@ -22659,6 +22721,9 @@ var walkthroughCss = `
22659
22721
  .knobs-wt-step[data-full] .knobs-wt-body{flex:1;min-height:0}
22660
22722
  .knobs-wt-step[data-full] .knobs-wt-fit{height:100%;transition:none}
22661
22723
  .knobs-wt-step[data-full] .knobs-wt-frame{height:100%;aspect-ratio:auto}
22724
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"]>.knobs-wt-body{display:flex;flex-direction:column}
22725
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player{flex:1;min-height:0}
22726
+ .knobs-wt-step[data-full] .knobs-wt-slot[data-kind="video"] .knobs-player-frame{flex:1;min-height:0;aspect-ratio:auto}
22662
22727
 
22663
22728
  /* ——— a taste test: the options side by side, Kit's pick checked and the rest stepping back. On
22664
22729
  the step's card, so no fill of their own ——— */
@@ -22667,7 +22732,7 @@ var walkthroughCss = `
22667
22732
  .knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
22668
22733
  .knobs-wt-option>:not(.knobs-wt-pick){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
22669
22734
  .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-pick){opacity:.55}
22670
- .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;
22735
+ .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;
22671
22736
  transition:background calc(var(--_k-hover-out) * 1ms)}
22672
22737
  .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)}
22673
22738
  .knobs-wt-letter{flex:none;font-weight:var(--kw-strong);color:var(--_k-fg)}
@@ -22690,17 +22755,18 @@ var walkthroughCss = `
22690
22755
  .knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
22691
22756
  .knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
22692
22757
  .knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
22693
- /* The contents: every row on one text edge and in one colour, title rows (32 high, the control
22694
- radius) and the steps under them (20 high, half of it), bled out so their text sits on the
22695
- rail's edge; the selection pill slides to the current step, and base rows never brighten. */
22696
- .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}
22758
+ /* The contents: every row on one text edge and in one colour, title rows (28 high, the control
22759
+ radius) and the steps under them (20 high, half of it), bled 6 px out so their text sits on the
22760
+ rail's edge and the first title's words on the column's top line; a narrow fade where a title
22761
+ runs out of room; the selection pill slides to the current step, and base rows never brighten. */
22762
+ .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}
22697
22763
  .knobs-wt-toc::-webkit-scrollbar{display:none}
22698
22764
  .knobs-wt-toc-group{display:flex;flex-direction:column;min-width:0}
22699
- .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;
22700
- 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;
22701
- transition:color calc(var(--_k-hover-out) * 1ms)}
22765
+ .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;
22766
+ 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;
22767
+ transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
22702
22768
  .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)}
22703
- .knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22769
+ .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)}
22704
22770
  .knobs-ui .knobs-wt-toc-row:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
22705
22771
  .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)}
22706
22772
  .knobs-wt-toc-label{min-width:0;overflow:hidden;${fade2}}
@@ -22712,7 +22778,7 @@ var walkthroughCss = `
22712
22778
  .knobs-wt-toc-glyph[data-done]{background:var(--kw-tone)}
22713
22779
  .knobs-wt-toc-glyph[data-tone="none"]{box-shadow:inset 0 0 0 1px var(--_k-border)}
22714
22780
  .knobs-wt-actions{display:flex;flex-wrap:wrap;gap:4px}
22715
- .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;
22781
+ .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;
22716
22782
  transition:background .15s,color .15s}
22717
22783
  .knobs-ui .knobs-wt-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
22718
22784
  .knobs-ui .knobs-wt-btn[data-primary]{background:var(--_k-fill-selected);color:var(--_k-fg)}
@@ -22746,11 +22812,10 @@ var walkthroughCss = `
22746
22812
  /* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
22747
22813
  slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
22748
22814
  .knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
22749
- /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
22750
- sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
22815
+ /* A compare is as tall as its two pictures side by side at this width, in every mode (Wipe fuses
22816
+ them at one side's size, so nothing grows or re-measures), plus its mode bar, the line and gaps
22751
22817
  (92 px), and the frame strip with its gap (158 px). */
22752
22818
  .knobs-wt-fit{width:100%;height:calc(100cqw / 2 / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
22753
- .knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
22754
22819
  .knobs-wt-fit[data-strip]{--kw-chrome:158px}
22755
22820
  .knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
22756
22821
  .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)}
@@ -22759,8 +22824,12 @@ var walkthroughCss = `
22759
22824
  .knobs-wt-frame[data-natural]>img{position:static;height:auto}
22760
22825
  /* Diff, code and terminal: one plain container (no glass): a flat inset fill, a hairline ring, the
22761
22826
  control radius and room inside, so a diff's buttons and the code under them can breathe. */
22762
- .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)}
22763
- .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);
22827
+ .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)}
22828
+ /* The diff inside it: its header row 28 high on the container's padding, a hairline under it, and
22829
+ no line of its own under the code (the container's ring closes it). */
22830
+ .knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"]){--kd-head:28px;--kd-x:0px}
22831
+ .knobs-wt-slot[data-kind="diff"] .knobs-ui.knobs-diff:not([data-on="pane"])::after{display:none}
22832
+ .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);
22764
22833
  font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
22765
22834
  .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)}
22766
22835
  .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
@@ -22806,7 +22875,7 @@ var walkthroughCss = `
22806
22875
  .knobs-wt-mark:is([data-shape="dot"],[data-shape="arrow"]) .knobs-wt-mark-label{left:12px;bottom:auto;top:10px}
22807
22876
  .knobs-wt-mark[data-shape="arrow"] .knobs-wt-mark-label{left:auto;right:30px;top:auto;bottom:30px}
22808
22877
  .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;
22809
- 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;
22878
+ 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;
22810
22879
  transition:opacity calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
22811
22880
  .knobs-ui .knobs-wt-anno-toggle:is(:hover,:focus-visible){opacity:1;color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22812
22881
  .knobs-ui .knobs-wt-anno-toggle:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
@@ -22827,7 +22896,7 @@ var walkthroughCss = `
22827
22896
  .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);
22828
22897
  border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
22829
22898
  .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
22830
- .knobs-wt-srow[data-go]{cursor:pointer}
22899
+ .knobs-wt-srow[data-go]{cursor:default}
22831
22900
  .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
22832
22901
  .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
22833
22902
  .knobs-wt-lane{position:relative;flex:none;width:16px}
@@ -22843,7 +22912,7 @@ var walkthroughCss = `
22843
22912
  .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}
22844
22913
  .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
22845
22914
  .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
22846
- .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)}
22915
+ .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)}
22847
22916
  .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)}
22848
22917
  .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
22849
22918
  .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)}
@@ -22851,12 +22920,21 @@ var walkthroughCss = `
22851
22920
  .knobs-wt-add{color:var(--_k-diff-add)}
22852
22921
  .knobs-wt-del{color:var(--_k-diff-del)}
22853
22922
  .knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
22923
+ /* A PR's state the GitHub way: its glyph and word, open and merged quietly tinted (as PR chips),
22924
+ draft and closed faint; no fill, no dot. A check after it where the checks pass, faint too. One
22925
+ width for every row, so the glyphs and words line up. */
22926
+ .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);
22927
+ font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);white-space:nowrap}
22928
+ .knobs-ui .knobs-wt-prstate[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
22929
+ .knobs-ui .knobs-wt-prstate[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
22930
+ .knobs-wt-prstate svg{flex:none}
22931
+ .knobs-ui .knobs-wt-prchecks{display:inline-flex;margin-left:2px;color:var(--_k-fg-3)}
22854
22932
  .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
22855
22933
  .knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
22856
22934
  @container knobs-wt-slot (max-width:520px){
22857
22935
  .knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
22858
22936
  .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
22859
- .knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
22937
+ :is(.knobs-wt-sstatus,.knobs-wt-prstate){grid-column:-2;justify-self:start;margin-top:4px}
22860
22938
  }
22861
22939
 
22862
22940
  /* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
@@ -22866,7 +22944,7 @@ var walkthroughCss = `
22866
22944
  .knobs-wt-tablebox{min-width:0}
22867
22945
  /* A row with a step goes there: the whole row, a wash under the pointer, no arrow, so rows with and
22868
22946
  without one line up. */
22869
- .knobs-wt-tablebox [data-go]{cursor:pointer;outline:none}
22947
+ .knobs-wt-tablebox [data-go]{cursor:default;outline:none}
22870
22948
  .knobs-wt-tablebox tr[data-go]>td,.knobs-wt-trow[data-go]{transition:background calc(var(--_k-hover-out) * 1ms)}
22871
22949
  .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)}
22872
22950
  .knobs-wt-tablebox [data-go]:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
@@ -22922,13 +23000,18 @@ var walkthroughCss = `
22922
23000
  .knobs-wt-body:has(>.knobs-wt-chapters){display:flex;flex-direction:column;gap:var(--_k-space-title)}
22923
23001
  .knobs-wt-chapters{position:relative;display:flex;flex-direction:column;margin:0 calc(var(--_k-space-sibling) * -1)}
22924
23002
  .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);
22925
- 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)}
22926
- .knobs-ui .knobs-wt-chapter:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
23003
+ 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)}
23004
+ .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)}
22927
23005
  .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
22928
23006
  .knobs-wt-chapter-n{font-size:var(--_k-type-small);color:var(--_k-fg-3)}
22929
23007
  .knobs-wt-chapter-label{min-width:0;overflow:hidden;${fade2}}
22930
23008
  .knobs-wt-chapter-time{font:var(--_k-weight) var(--_k-type-small)/1 var(--_k-mono);color:var(--_k-fg-3)}
22931
23009
  .knobs-pick-copy :is(.knobs-wt-chapter-n,.knobs-wt-chapter-time){color:var(--_k-fg-2)}
23010
+ /* Every hover in the walkthrough lands at once (hover-in, 0 ms) and eases out (hover-out): links,
23011
+ buttons, chips, rail, chapter, table and stack rows (its own; the pieces inside keep theirs). */
23012
+ :is(.knobs-walkthrough,.knobs-wt-slot) :is(a,button,[data-go])[class*="knobs-wt-"]:is(:hover,:focus-visible),
23013
+ .knobs-walkthrough a:not([class]):hover,
23014
+ :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)}
22932
23015
  @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
22933
23016
  @media(prefers-reduced-motion:reduce){.knobs-wt-disc-content{transition:opacity .15s}}
22934
23017
  `;
@@ -23029,7 +23112,6 @@ async function mountCompare(host, m, look, alive, onEvent) {
23029
23112
  fit.style.setProperty("--kw-aspect", String(known ?? DEFAULT_ASPECT));
23030
23113
  const width = host.getBoundingClientRect().width;
23031
23114
  const mode = m.mode ?? (width && width < 560 ? "wipe" : "side");
23032
- fit.toggleAttribute("data-overlay", mode !== "side");
23033
23115
  host.append(fit);
23034
23116
  const [aspect, lib] = await Promise.all([
23035
23117
  known ? Promise.resolve(known) : probeAspect(doc, after.src),
@@ -23053,7 +23135,6 @@ async function mountCompare(host, m, look, alive, onEvent) {
23053
23135
  ...aspect ? { aspect } : {},
23054
23136
  ...m.strip ? { strip: true } : {},
23055
23137
  onMode: (next) => {
23056
- fit.toggleAttribute("data-overlay", next !== "side");
23057
23138
  onEvent?.({ what: "mode", detail: next });
23058
23139
  const layer = marks?.element;
23059
23140
  if (layer) {
@@ -23077,6 +23158,12 @@ async function mountCompare(host, m, look, alive, onEvent) {
23077
23158
  }
23078
23159
  });
23079
23160
  const stage = fit.querySelector(".knobs-cmp-stage");
23161
+ if (stage)
23162
+ doc.defaultView?.requestAnimationFrame(() => {
23163
+ const chrome = fit.clientHeight - stage.clientHeight;
23164
+ if (alive() && stage.clientHeight > 0 && chrome > 0)
23165
+ fit.style.setProperty("--kw-chrome", `${Math.round(chrome)}px`);
23166
+ });
23080
23167
  if (m.annotations?.length && stage) {
23081
23168
  const shape = { w: aspect ?? DEFAULT_ASPECT, h: 1 };
23082
23169
  marks = annotationLayer(stage, m.annotations, () => {
@@ -23152,45 +23239,38 @@ async function mountCompare(host, m, look, alive, onEvent) {
23152
23239
  var round2 = (v) => Math.round(v * 100) / 100;
23153
23240
  var MARK_GLIDE = 450;
23154
23241
  var zoomedIn = (z) => z.w < 0.999 || z.h < 0.999;
23155
- function mountVideo(host, m, onEvent) {
23242
+ async function mountVideo(host, m, look, alive, onEvent) {
23156
23243
  const doc = host.ownerDocument;
23157
- const frame = div(doc, "knobs-wt-frame");
23244
+ await Promise.resolve().then(() => init_player());
23245
+ if (!alive())
23246
+ return { dispose() {} };
23158
23247
  const aspect = parseAspect(m.aspect);
23159
- if (aspect)
23160
- frame.style.setProperty("--kw-aspect", String(aspect));
23161
- const v = doc.createElement("video");
23162
- v.controls = true;
23163
- v.loop = true;
23164
- v.muted = true;
23165
- v.playsInline = true;
23166
- v.preload = "metadata";
23167
- if (m.poster)
23168
- v.poster = m.poster;
23169
- if (!aspect)
23170
- v.addEventListener("loadedmetadata", () => v.videoWidth && frame.style.setProperty("--kw-aspect", String(v.videoWidth / v.videoHeight)), { once: true });
23171
- if (m.captions) {
23172
- const track = doc.createElement("track");
23173
- track.kind = "captions";
23174
- track.label = "Captions";
23175
- track.srclang = doc.documentElement.lang || "en";
23176
- track.default = true;
23177
- track.src = m.captions;
23178
- v.append(track);
23179
- }
23180
- v.src = m.src;
23181
- v.addEventListener("play", () => onEvent?.({ what: "play", time: round2(v.currentTime) }));
23182
- v.addEventListener("pause", () => onEvent?.({ what: "pause", time: round2(v.currentTime) }));
23183
- v.addEventListener("seeked", () => {
23184
- if (v.paused)
23185
- onEvent?.({ what: "seek", time: round2(v.currentTime) });
23248
+ let pills = null;
23249
+ let marking = null;
23250
+ const p = player(host, {
23251
+ ...look,
23252
+ src: m.src,
23253
+ ...m.poster ? { poster: m.poster } : {},
23254
+ ...m.captions ? { captions: m.captions } : {},
23255
+ ...aspect ? { aspect } : {},
23256
+ ...m.label ? { label: m.label } : {},
23257
+ ...m.chapters?.length ? { chapters: m.chapters.map((c) => ({ label: c.label, start: c.start, ...c.id ? { id: c.id } : {} })) } : {},
23258
+ onPlay: (on, time) => {
23259
+ marking?.kick();
23260
+ onEvent?.({ what: on ? "play" : "pause", time: round2(time) });
23261
+ },
23262
+ onSeek: (time) => {
23263
+ pills?.sync();
23264
+ marking?.kick();
23265
+ onEvent?.({ what: "seek", time: round2(time) });
23266
+ }
23186
23267
  });
23187
- frame.append(v);
23188
- host.append(frame);
23189
- const pills = m.chapters?.length ? chapterPills(host, m.chapters, {
23190
- now: () => v.currentTime,
23268
+ const v = p.video;
23269
+ pills = m.chapters?.length ? chapterPills(host, m.chapters, {
23270
+ now: () => p.time,
23191
23271
  seek: (t) => {
23192
- v.currentTime = t;
23193
- v.play().catch(() => {});
23272
+ p.seek(t, { glide: true });
23273
+ p.play();
23194
23274
  },
23195
23275
  onEvent
23196
23276
  }) : null;
@@ -23198,36 +23278,33 @@ function mountVideo(host, m, onEvent) {
23198
23278
  if (pills)
23199
23279
  for (const type of ["timeupdate", "seeking", "seeked"])
23200
23280
  v.addEventListener(type, sync);
23281
+ const frame = p.element.querySelector(".knobs-player-frame") ?? p.element;
23201
23282
  const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
23202
23283
  rects: [
23203
23284
  contain({ w: frame.clientWidth, h: frame.clientHeight }, { w: v.videoWidth, h: v.videoHeight })
23204
23285
  ],
23205
- time: v.currentTime
23286
+ time: p.time
23206
23287
  })) : null;
23207
- const marking = marks ? follow(doc.defaultView, () => !v.paused, () => marks.update()) : null;
23208
- if (marking)
23209
- for (const type of ["play", "seeked", "loadedmetadata", "timeupdate"])
23210
- v.addEventListener(type, () => marking.kick());
23288
+ if (marks) {
23289
+ marking = follow(doc.defaultView, () => p.playing, () => marks.update());
23290
+ for (const type of ["seeked", "loadedmetadata", "timeupdate"])
23291
+ v.addEventListener(type, () => marking?.kick());
23292
+ }
23211
23293
  return {
23212
- pause: () => v.pause(),
23213
- seekBy: (d) => {
23214
- v.currentTime = Math.max(0, Math.min(v.duration || Infinity, v.currentTime + d));
23215
- },
23294
+ pause: () => p.pause(),
23295
+ seekBy: (d) => p.seekBy(d),
23216
23296
  context: () => ({
23217
23297
  kind: "video",
23218
- time: round2(v.currentTime),
23219
- ...Number.isFinite(v.duration) ? { duration: round2(v.duration) } : {},
23220
- playing: !v.paused,
23298
+ time: round2(p.time),
23299
+ ...p.duration ? { duration: round2(p.duration) } : {},
23300
+ playing: p.playing,
23221
23301
  ...pills?.current ? { chapter: pills.current.label } : {}
23222
23302
  }),
23223
23303
  dispose: () => {
23224
23304
  marking?.stop();
23225
23305
  marks?.dispose();
23226
23306
  pills?.dispose();
23227
- v.pause();
23228
- v.removeAttribute("src");
23229
- v.load();
23230
- frame.remove();
23307
+ p.dispose();
23231
23308
  }
23232
23309
  };
23233
23310
  }
@@ -23488,7 +23565,7 @@ function mediaSlot(container, options) {
23488
23565
  case "diff":
23489
23566
  return void mountDiff(host, m, look, alive, options.onEvent).then(done, fail("diff"));
23490
23567
  case "video":
23491
- return done(mountVideo(host, m, options.onEvent));
23568
+ return void mountVideo(host, m, look, alive, options.onEvent).then(done, fail("video"));
23492
23569
  case "image":
23493
23570
  return done(mountImage(host, m));
23494
23571
  case "code":
@@ -23502,7 +23579,7 @@ function mediaSlot(container, options) {
23502
23579
  case "history":
23503
23580
  return done(mountHistory(host, m, options.steps));
23504
23581
  case "stack":
23505
- return done(mountStack(host, m, options.steps));
23582
+ return done(mountStack(host, m, options.steps, options.findPR));
23506
23583
  case "table":
23507
23584
  return done(mountTable(host, m, options.steps));
23508
23585
  case "mount": {
@@ -23556,7 +23633,7 @@ function mediaSlot(container, options) {
23556
23633
  }
23557
23634
  };
23558
23635
  }
23559
- var CHECK3 = icon("check", { small: true });
23636
+ var CHECK4 = icon("check", { small: true });
23560
23637
 
23561
23638
  // src/ui/walkthrough.ts
23562
23639
  var el3 = (doc, tag, cls) => {
@@ -23643,7 +23720,7 @@ function tasteTest(container, options) {
23643
23720
  pick.append(key);
23644
23721
  }
23645
23722
  const check = el3(doc, "span", "knobs-wt-check");
23646
- check.innerHTML = CHECK3;
23723
+ check.innerHTML = CHECK4;
23647
23724
  pick.append(check);
23648
23725
  pick.addEventListener("click", () => {
23649
23726
  const next = picked === o.id ? null : o.id;
@@ -24471,11 +24548,12 @@ function walkthrough(container, options) {
24471
24548
  line.append(s);
24472
24549
  return s;
24473
24550
  };
24474
- const findPRIn = (n) => findPR(page.prs, n, page.repo);
24551
+ const findPRIn = (n, kind = "pr") => findPR(page.prs, n, page.repo, kind);
24475
24552
  const metaLink = (line, text, href) => {
24476
24553
  const n = prNumber(href);
24477
- if (n) {
24478
- const chip = prChip(doc, n, { href, ...findPRIn(n) });
24554
+ const issue = n ? null : issueNumber(href);
24555
+ if (n || issue) {
24556
+ const chip = n ? prChip(doc, n, { ...findPRIn(n), href }) : prChip(doc, issue, { ...findPRIn(issue, "issue"), href });
24479
24557
  line.append(chip);
24480
24558
  return chip;
24481
24559
  }
@@ -24804,7 +24882,7 @@ function walkthrough(container, options) {
24804
24882
  media = el3(doc, "div", "knobs-wt-media");
24805
24883
  media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
24806
24884
  for (const m of step.media)
24807
- slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
24885
+ slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks, findPR: findPRIn }));
24808
24886
  card.append(media);
24809
24887
  }
24810
24888
  let taste = null;