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

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-solid.js CHANGED
@@ -577,11 +577,11 @@ var compareCss = `
577
577
  .knobs-cmp-modes[hidden]{display:none}
578
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
579
  transition:color calc(var(--_k-hover-out) * 1ms)}
580
- .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
580
+ .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
581
581
  /* Toggles: icon buttons, the accent's wash when on. */
582
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
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)}
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)}
585
585
  .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
586
586
  .knobs-cmp-toggle svg{display:block}
587
587
  .knobs-cmp-fill{flex:1}
@@ -647,7 +647,7 @@ var compareCss = `
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
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)}
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)}
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}
@@ -678,6 +678,10 @@ var playerCss = `
678
678
  .knobs-player-glyph svg{width:16px;height:16px}
679
679
  .knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
680
680
  .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. */
683
+ .knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
684
+ .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
681
685
  `;
682
686
 
683
687
  // src/ui/overlay-css.ts
@@ -3231,14 +3235,14 @@ var init_diff_css = __esm(() => {
3231
3235
  margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
3232
3236
  .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;
3233
3237
  transition:background .15s,color .15s,opacity .15s}
3234
- .knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
3238
+ .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)}
3235
3239
  .knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
3236
3240
  .knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
3237
3241
  .knobs-diff-btn svg{width:16px;height:16px}
3238
3242
  /* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
3239
3243
  .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);
3240
3244
  font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
3241
- .knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
3245
+ .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)}
3242
3246
  .knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
3243
3247
  .knobs-diff-viewed svg{width:16px;height:16px}
3244
3248
  .knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
@@ -3258,7 +3262,7 @@ var init_diff_css = __esm(() => {
3258
3262
  .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)}
3259
3263
  .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);
3260
3264
  font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
3261
- .knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg)}
3265
+ .knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3262
3266
  /* The diff: one layer per file or layout on show, the old fading under the new. */
3263
3267
  .knobs-diff-body{position:relative;flex:1;min-height:0}
3264
3268
  .knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
@@ -3279,8 +3283,8 @@ var init_diff_css = __esm(() => {
3279
3283
  .knobs-diff-hbar{position:absolute;height:5px;pointer-events:none}
3280
3284
  .knobs-diff-hbar>i{position:absolute;left:0;top:0;height:5px;border-radius:3px;background:color-mix(in srgb,var(--_k-fg) 30%,transparent);opacity:0;pointer-events:none;
3281
3285
  transition:opacity .3s,background .15s}
3282
- .knobs-diff-body:hover .knobs-diff-hbar>i,.knobs-diff-hbar:is([data-active],[data-dragging])>i{opacity:1;pointer-events:auto;transition-duration:.12s,.15s}
3283
- .knobs-diff-hbar>i:hover,.knobs-diff-hbar[data-dragging]>i{background:color-mix(in srgb,var(--_k-fg) 48%,transparent)}
3286
+ .knobs-diff-body:hover .knobs-diff-hbar>i,.knobs-diff-hbar:is([data-active],[data-dragging])>i{opacity:1;pointer-events:auto;transition-duration:calc(var(--_k-hover-in) * 1ms)}
3287
+ .knobs-diff-hbar>i:hover,.knobs-diff-hbar[data-dragging]>i{background:color-mix(in srgb,var(--_k-fg) 48%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3284
3288
  /* stack: every file down one scroller, each header sticking until the next pushes it off. It's
3285
3289
  flush (square, no card around its file), opaque in the code's own colour, with a hairline
3286
3290
  under it only while it's stuck; a gap, not a line, parts one file from the next. */
@@ -3355,7 +3359,7 @@ var init_diff_css = __esm(() => {
3355
3359
  .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}
3356
3360
  .knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
3357
3361
  .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}
3358
- .knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg)}
3362
+ .knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
3359
3363
  .knobs-diff-filter>button svg{width:12px;height:12px}
3360
3364
  .knobs-diff-list{position:relative;flex:1;min-height:0}
3361
3365
  .knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
@@ -9769,7 +9773,8 @@ function fuseFrame(L, before, after, k, wipe) {
9769
9773
  const feather = Math.min(contact, 1 + FUSE.feather * Math.sin(Math.PI * t));
9770
9774
  const overlap = before.x + before.w - after.x;
9771
9775
  const seam = t > 0.001 && overlap >= 0 ? Math.min(1, Math.max(0, overlap / FUSE.seamFade)) * Math.min(1, t * 4) : 0;
9772
- return { cut, feather, seam, blur: t < 0.995 ? FUSE.blur * (1 - t) : 0 };
9776
+ const blur = t > 0.005 && t < 0.995 ? FUSE.blur * Math.sin(Math.PI * t) : 0;
9777
+ return { cut, feather, seam, blur };
9773
9778
  }
9774
9779
  function clampZoom(r) {
9775
9780
  const w = Math.min(1, Math.max(1 / ZOOM_MAX, r.w));
@@ -9856,7 +9861,10 @@ function stripPeaks(diffs, o = {}) {
9856
9861
  });
9857
9862
  return out;
9858
9863
  }
9859
- var ALL_MODES, MODE_KEYS, modeKey = (mode) => MODE_KEYS[mode].toUpperCase(), VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE, FUSE, labelFold = (fold, mark, blur = 3) => {
9864
+ var ALL_MODES, MODE_KEYS, modeKey = (mode) => MODE_KEYS[mode].toUpperCase(), VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE, FUSE, fuseCorners = (r, k) => {
9865
+ const inner = r * Math.min(1, Math.max(0, k));
9866
+ return { before: { left: r, right: inner }, after: { left: inner, right: r } };
9867
+ }, labelFold = (fold, mark, blur = 3) => {
9860
9868
  const f = Math.min(1, Math.max(0, fold));
9861
9869
  const open = 1 - f;
9862
9870
  return {
@@ -9887,7 +9895,7 @@ var init_compare_model = __esm(() => {
9887
9895
  MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
9888
9896
  VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
9889
9897
  SEEK_IDLE = { seeking: false, pending: null };
9890
- FUSE = { feather: 24, blur: 4, seamFade: 48 };
9898
+ FUSE = { feather: 32, blur: 8, seamFade: 48 };
9891
9899
  ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
9892
9900
  });
9893
9901
 
@@ -10620,8 +10628,8 @@ async function createCompareGpu(canvas, onLost) {
10620
10628
  onLost();
10621
10629
  };
10622
10630
  lostListeners.add(lost);
10623
- const uniforms = device.createBuffer({ size: 176, usage: 64 | 8 });
10624
- const data = new Float32Array(44);
10631
+ const uniforms = device.createBuffer({ size: 192, usage: 64 | 8 });
10632
+ const data = new Float32Array(48);
10625
10633
  const make = (w, h) => device.createTexture({
10626
10634
  size: [Math.max(1, w), Math.max(1, h)],
10627
10635
  format: "rgba8unorm",
@@ -10694,6 +10702,7 @@ async function createCompareGpu(canvas, onLost) {
10694
10702
  data.set([f.preview[0] ? 1 : 0, f.preview[1] ? 1 : 0, 0, 0], 32);
10695
10703
  r(36, f.beforeClip);
10696
10704
  r(40, f.afterClip);
10705
+ data.set(f.radii, 44);
10697
10706
  device.queue.writeBuffer(uniforms, 0, data);
10698
10707
  const encoder = device.createCommandEncoder();
10699
10708
  const pass = encoder.beginRenderPass({
@@ -10735,6 +10744,7 @@ struct U {
10735
10744
  loupe: vec4f, // x, y, r, zoom (0: none)
10736
10745
  src: vec4f, // Before, After from their previews
10737
10746
  ca: vec4f, cb: vec4f, // where each side may draw
10747
+ radii: vec4f, // the clips' corners: Before left, right; After left, right
10738
10748
  }
10739
10749
  @group(0) @binding(0) var<uniform> u: U;
10740
10750
  @group(0) @binding(1) var A: texture_2d<f32>;
@@ -10756,8 +10766,18 @@ fn cover(p: vec2f, r: vec4f) -> f32 {
10756
10766
  return clamp(d + 0.5, 0.0, 1.0);
10757
10767
  }
10758
10768
 
10759
- // A side's colour where rect r maps it to p, and its coverage inside clip c there.
10760
- fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, near: bool, ready: f32) -> vec4f {
10769
+ // Coverage of rect r at p with its left corners rounded rl and its right ones rr.
10770
+ fn coverRound(p: vec2f, r: vec4f, rl: f32, rr: f32) -> f32 {
10771
+ let h = r.zw * 0.5;
10772
+ let q0 = p - (r.xy + h);
10773
+ let rad = min(select(rl, rr, q0.x > 0.0), min(h.x, h.y));
10774
+ let q = abs(q0) - h + vec2f(rad);
10775
+ let d = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rad;
10776
+ return clamp(0.5 - d, 0.0, 1.0);
10777
+ }
10778
+
10779
+ // A side's colour where rect r maps it to p, and its coverage inside clip c (corners rc) there.
10780
+ fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, rc: vec2f, near: bool, ready: f32) -> vec4f {
10761
10781
  let uv = clamp((p - r.xy) / max(r.zw, vec2f(1e-3)), vec2f(0.0), vec2f(1.0));
10762
10782
  var c: vec3f;
10763
10783
  if (near) {
@@ -10766,16 +10786,16 @@ fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, near: bool, ready: f3
10766
10786
  } else {
10767
10787
  c = textureSampleLevel(t, smp, uv, 0.0).rgb;
10768
10788
  }
10769
- return vec4f(c, min(cover(p, r), cover(p, c0)) * ready);
10789
+ return vec4f(c, min(cover(p, r), coverRound(p, c0, rc.x, rc.y)) * ready);
10770
10790
  }
10771
10791
 
10772
10792
  fn takeA(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
10773
- if (u.src.x > 0.5) { return take(PA, p, r, c0, near, 1.0); }
10774
- return take(A, p, r, c0, near, u.diff.z);
10793
+ if (u.src.x > 0.5) { return take(PA, p, r, c0, u.radii.xy, near, 1.0); }
10794
+ return take(A, p, r, c0, u.radii.xy, near, u.diff.z);
10775
10795
  }
10776
10796
  fn takeB(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
10777
- if (u.src.y > 0.5) { return take(PB, p, r, c0, near, 1.0); }
10778
- return take(B, p, r, c0, near, u.diff.w);
10797
+ if (u.src.y > 0.5) { return take(PB, p, r, c0, u.radii.zw, near, 1.0); }
10798
+ return take(B, p, r, c0, u.radii.zw, near, u.diff.w);
10779
10799
  }
10780
10800
 
10781
10801
  fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
@@ -11391,6 +11411,7 @@ function compare(container, options) {
11391
11411
  let stageRect = null;
11392
11412
  let accent = [0.49, 0.81, 0.61];
11393
11413
  let labelW = [0, 0];
11414
+ let frameRadius = 0;
11394
11415
  let dirty = true;
11395
11416
  let disposed = false;
11396
11417
  let near = options.lazy === false;
@@ -11911,6 +11932,7 @@ function compare(container, options) {
11911
11932
  const wr = wipeRect(r.L.overlay);
11912
11933
  const k = Math.min(1, Math.max(0, lay.x));
11913
11934
  const fu = fuseFrame(r.L, r.before, r.after, k, wipeLine.x);
11935
+ const corners = fuseCorners(frameRadius, k);
11914
11936
  const dw = differenceWeight();
11915
11937
  const fusing = 1 - dw;
11916
11938
  folded = k * fusing;
@@ -11967,6 +11989,7 @@ function compare(container, options) {
11967
11989
  if (renderer === "dom") {
11968
11990
  const cut = mode === "difference" ? null : fu;
11969
11991
  const place = (n, rect, clip, before) => {
11992
+ const round = before ? corners.before : corners.after;
11970
11993
  const st = n.style;
11971
11994
  st.transform = `translate(${rect.x.toFixed(2)}px,${rect.y.toFixed(2)}px)`;
11972
11995
  st.width = `${rect.w.toFixed(2)}px`;
@@ -11976,7 +11999,9 @@ function compare(container, options) {
11976
11999
  const l = Math.max(0, clip.x - rect.x);
11977
12000
  const b = Math.max(0, rect.y + rect.h - (clip.y + clip.h));
11978
12001
  const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, edge));
11979
- st.clipPath = t + l + b + right > 0.01 ? `inset(${t.toFixed(2)}px ${right.toFixed(2)}px ${b.toFixed(2)}px ${l.toFixed(2)}px)` : "";
12002
+ const rr = edge < clip.x + clip.w - 0.5 ? 0 : round.right;
12003
+ const rl = round.left;
12004
+ st.clipPath = t + l + b + right + rl + rr > 0.01 ? `inset(${t.toFixed(2)}px ${right.toFixed(2)}px ${b.toFixed(2)}px ${l.toFixed(2)}px round ${rl.toFixed(2)}px ${rr.toFixed(2)}px ${rr.toFixed(2)}px ${rl.toFixed(2)}px)` : "";
11980
12005
  const mask = before && cut && cut.feather > 0.5 ? `linear-gradient(to right,#000 ${(cut.cut - cut.feather - rect.x).toFixed(2)}px,transparent ${(edge - rect.x).toFixed(2)}px)` : "";
11981
12006
  st.maskImage = mask;
11982
12007
  st.setProperty("-webkit-mask-image", mask);
@@ -12014,7 +12039,13 @@ function compare(container, options) {
12014
12039
  gain: 1,
12015
12040
  accent,
12016
12041
  loupe: loupeShown() && pointer ? { x: pointer.x * dpr, y: pointer.y * dpr, r: LOUPE.r * dpr, zoom: LOUPE.zoom } : null,
12017
- preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)]
12042
+ preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)],
12043
+ radii: [
12044
+ corners.before.left * dpr,
12045
+ corners.before.right * dpr,
12046
+ corners.after.left * dpr,
12047
+ corners.after.right * dpr
12048
+ ]
12018
12049
  });
12019
12050
  };
12020
12051
  const drawNow = () => {
@@ -12106,6 +12137,7 @@ function compare(container, options) {
12106
12137
  accent = [c[0], c[1], c[2]];
12107
12138
  const unfold = MARK.w - labelFold(folded, MARK.w).width + MARK.gap * folded;
12108
12139
  labelW = [labels[0].offsetWidth + unfold, labels[1].offsetWidth + unfold];
12140
+ frameRadius = Number.parseFloat(win.getComputedStyle(stage).borderTopLeftRadius) || 0;
12109
12141
  requestDraw();
12110
12142
  };
12111
12143
  const measure = () => {
@@ -17198,11 +17230,629 @@ function toast(message, options = {}) {
17198
17230
  };
17199
17231
  }
17200
17232
 
17233
+ // src/ui/walkthrough-annotations.ts
17234
+ var EYE = "M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.47,133.47,0,0,1,25,128,133.33,133.33,0,0,1,48.07,97.25C70.33,75.19,97.22,64,128,64s57.67,11.19,79.93,33.25A133.46,133.46,0,0,1,231.05,128C223.84,141.46,192.43,192,128,192Zm0-112a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Z";
17235
+ var EYE_OFF = "M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.57A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z";
17236
+ var glyph = (d) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="16" height="16" fill="currentColor" aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
17237
+ var ARROW = '<svg class="knobs-wt-anno-arrow" viewBox="0 0 28 28" width="28" height="28" aria-hidden="true"><path d="M3 3 L24 24 M24 24 L24 13 M24 24 L13 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
17238
+ var shapeOf = (a) => a.shape ?? (a.w !== undefined && a.h !== undefined ? "box" : "dot");
17239
+ var showsAt = (a, time) => !a.at || time === undefined || time >= a.at[0] && time <= a.at[1];
17240
+ function placeMark(a, rect, view = ALL) {
17241
+ const w = a.w ?? 0;
17242
+ const h = a.h ?? 0;
17243
+ const x = (a.x - view.u) / view.w;
17244
+ const y = (a.y - view.v) / view.h;
17245
+ const ww = w / view.w;
17246
+ const hh = h / view.h;
17247
+ if (x + ww < 0 || y + hh < 0 || x > 1 || y > 1)
17248
+ return null;
17249
+ return { x: rect.x + x * rect.w, y: rect.y + y * rect.h, w: ww * rect.w, h: hh * rect.h };
17250
+ }
17251
+ var ALL = { u: 0, v: 0, w: 1, h: 1 };
17252
+ var scopeOf = (el) => el.closest(".knobs-walkthrough") ?? el.closest(".knobs-wt-slot") ?? el;
17253
+ var annotationsOn = (scope) => scope.dataset.annotations !== "off";
17254
+ function setAnnotations(scope, on) {
17255
+ if (on)
17256
+ delete scope.dataset.annotations;
17257
+ else
17258
+ scope.dataset.annotations = "off";
17259
+ for (const b of Array.from(scope.querySelectorAll(".knobs-wt-anno-toggle")))
17260
+ syncToggle(b, on);
17261
+ }
17262
+ function syncToggle(b, on) {
17263
+ b.setAttribute("aria-pressed", String(on));
17264
+ b.title = on ? "Hide annotations (a)" : "Show annotations (a)";
17265
+ b.innerHTML = glyph(on ? EYE : EYE_OFF);
17266
+ }
17267
+ function annotationLayer(host, list, place, corner = host) {
17268
+ const doc = host.ownerDocument;
17269
+ const layer = doc.createElement("div");
17270
+ layer.className = "knobs-wt-anno";
17271
+ layer.setAttribute("aria-hidden", "true");
17272
+ host.append(layer);
17273
+ const toggle = doc.createElement("button");
17274
+ toggle.type = "button";
17275
+ toggle.className = "knobs-wt-anno-toggle";
17276
+ toggle.setAttribute("aria-label", "Annotations");
17277
+ syncToggle(toggle, annotationsOn(scopeOf(host)));
17278
+ toggle.addEventListener("click", (e) => {
17279
+ e.stopPropagation();
17280
+ const scope = scopeOf(host);
17281
+ setAnnotations(scope, !annotationsOn(scope));
17282
+ });
17283
+ corner.append(toggle);
17284
+ let marks = [];
17285
+ const make = (a) => {
17286
+ const m = doc.createElement("div");
17287
+ const shape = shapeOf(a);
17288
+ m.className = "knobs-wt-mark";
17289
+ m.dataset.shape = shape;
17290
+ m.dataset.hidden = "";
17291
+ if (shape === "arrow")
17292
+ m.innerHTML = ARROW;
17293
+ if (a.label) {
17294
+ const l = doc.createElement("span");
17295
+ l.className = "knobs-wt-mark-label";
17296
+ l.textContent = a.label;
17297
+ m.append(l);
17298
+ }
17299
+ layer.append(m);
17300
+ return m;
17301
+ };
17302
+ let settled = false;
17303
+ const update = () => {
17304
+ const p = place();
17305
+ if (marks.length !== p.rects.length) {
17306
+ for (const row of marks)
17307
+ for (const m of row)
17308
+ m.remove();
17309
+ marks = p.rects.map(() => list.map(make));
17310
+ }
17311
+ p.rects.forEach((rect, i) => {
17312
+ list.forEach((a, j) => {
17313
+ const m = marks[i][j];
17314
+ const box = placeMark(a, rect, p.view);
17315
+ const show = box !== null && showsAt(a, p.time);
17316
+ if (box) {
17317
+ m.style.transform = `translate(${box.x.toFixed(2)}px,${box.y.toFixed(2)}px)`;
17318
+ m.style.width = `${box.w.toFixed(2)}px`;
17319
+ m.style.height = `${box.h.toFixed(2)}px`;
17320
+ }
17321
+ m.toggleAttribute("data-hidden", !show);
17322
+ });
17323
+ });
17324
+ if (!settled) {
17325
+ settled = true;
17326
+ requestAnimationFrame(() => requestAnimationFrame(() => layer.dataset.settled = ""));
17327
+ }
17328
+ };
17329
+ const ro = new ResizeObserver(() => update());
17330
+ ro.observe(host);
17331
+ update();
17332
+ return {
17333
+ element: layer,
17334
+ update,
17335
+ dispose() {
17336
+ ro.disconnect();
17337
+ layer.remove();
17338
+ toggle.remove();
17339
+ }
17340
+ };
17341
+ }
17342
+
17201
17343
  // src/ui/walkthrough-blocks.ts
17202
17344
  init_motion();
17203
- init_icons();
17345
+
17346
+ // src/ui/history.ts
17347
+ init_motion();
17348
+ init_glide();
17349
+
17350
+ // src/ui/history-css.ts
17351
+ var historyCss = `
17352
+ :where(.knobs-history,.knobs-history *){box-sizing:border-box}
17353
+ .knobs-ui.knobs-history{--hy-gutter:128px;--hy-lane:24px;--hy-bar:16px;--hy-axis:20px;--hy-bandrow:0px;--hy-c:var(--_k-fg-3);
17354
+ --hy-fade:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);
17355
+ position:relative;display:flex;flex-direction:column;width:100%;min-width:0;outline:none;color:var(--_k-fg);
17356
+ font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";
17357
+ text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased;user-select:none;-webkit-user-select:none}
17358
+ .knobs-history[data-bands]{--hy-bandrow:var(--_k-leading-small)}
17359
+ .knobs-history[data-narrow]{--hy-gutter:0px}
17360
+ .knobs-history [hidden]{display:none!important}
17361
+ :where(.knobs-ui) :is(.knobs-hy-ev,.knobs-hy-span,.knobs-hy-jump,.knobs-hy-more){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
17362
+ background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
17363
+ .knobs-history [data-tone="good"]{--hy-c:var(--_k-ok)}
17364
+ .knobs-history [data-tone="warn"]{--hy-c:var(--_k-warn)}
17365
+ .knobs-history [data-tone="bad"]{--hy-c:var(--_k-danger)}
17366
+ .knobs-history [data-tone="info"]{--hy-c:var(--_k-info)}
17367
+ .knobs-history [data-tone="dim"]{--hy-c:var(--_k-fg-3)}
17368
+
17369
+ /* ——— the chart: lane labels beside the plot (above each lane where it's narrow) ——— */
17370
+ .knobs-hy-chart{position:relative;display:grid;grid-template-columns:var(--hy-gutter) minmax(0,1fr)}
17371
+ .knobs-hy-axis{position:relative;grid-column:2;grid-row:1;height:calc(var(--hy-bandrow) + var(--hy-axis))}
17372
+ :is(.knobs-hy-tick,.knobs-hy-now,.knobs-hy-blabel){position:absolute;white-space:nowrap;font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
17373
+ .knobs-hy-tick{bottom:2px;padding-left:4px}
17374
+ .knobs-hy-now{bottom:2px;color:var(--_k-accent-fg);font-weight:var(--_k-weight-label)}
17375
+ .knobs-hy-blabel{top:0;padding-left:4px;overflow:hidden;color:var(--_k-fg-2)}
17376
+ .knobs-hy-label{grid-column:1;align-self:center;min-width:0;margin-right:var(--_k-space-title);overflow:hidden;white-space:nowrap;
17377
+ font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
17378
+ .knobs-history[data-narrow] .knobs-hy-label{grid-column:2;align-self:end;margin:0}
17379
+ :is(.knobs-hy-label,.knobs-hy-blabel)[data-overflow]{-webkit-mask-image:var(--hy-fade);mask-image:var(--hy-fade)}
17380
+ .knobs-hy-track{position:relative;grid-column:2;height:var(--hy-lane)}
17381
+ .knobs-hy-layer{position:absolute;inset:0;grid-column:2;grid-row:1/-1;pointer-events:none}
17382
+
17383
+ /* Bands and month lines behind the lanes; today, the selection and the pointer's line over them. */
17384
+ .knobs-hy-band{position:absolute;top:0;bottom:0;background:color-mix(in oklch,var(--_k-fg) 3.5%,transparent);box-shadow:inset 1px 0 0 var(--_k-border-subtle)}
17385
+ :is(.knobs-hy-grid,.knobs-hy-today,.knobs-hy-hover,.knobs-hy-sel){position:absolute;left:0;top:calc(var(--hy-bandrow) + var(--hy-axis));bottom:0;width:1px;margin-left:-.5px}
17386
+ .knobs-hy-grid{background:var(--_k-border-subtle)}
17387
+ .knobs-hy-today{top:calc(var(--hy-bandrow) + var(--hy-axis) - 4px);background:var(--_k-accent)}
17388
+ .knobs-hy-hover{background:color-mix(in oklch,var(--_k-fg) 32%,transparent);opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
17389
+ .knobs-hy-hover[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
17390
+ .knobs-hy-sel{background:var(--_k-fg-2);opacity:0}
17391
+ .knobs-hy-sel[data-on]{opacity:.6}
17392
+
17393
+ /* Spans: a soft bar in their state's tone, inferred ones hatched; the tag at the end. */
17394
+ .knobs-hy-span{position:absolute;top:calc((var(--hy-lane) - var(--hy-bar)) / 2);height:var(--hy-bar);border-radius:var(--_k-radius-sm);cursor:pointer;
17395
+ background:color-mix(in oklch,var(--hy-c) 24%,transparent);transition:background calc(var(--_k-hover-out) * 1ms),box-shadow calc(var(--_k-hover-out) * 1ms)}
17396
+ .knobs-hy-span[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 34%,transparent) 0 2px,color-mix(in oklch,var(--hy-c) 9%,transparent) 2px 6px)}
17397
+ .knobs-hy-span[data-hover]{background-color:color-mix(in oklch,var(--hy-c) 34%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
17398
+ .knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 1px var(--hy-c)}
17399
+ .knobs-hy-span[data-clip-start]{border-top-left-radius:0;border-bottom-left-radius:0;-webkit-mask-image:linear-gradient(to right,transparent,#000 16px);mask-image:linear-gradient(to right,transparent,#000 16px)}
17400
+ .knobs-hy-span[data-clip-end]{border-top-right-radius:0;border-bottom-right-radius:0;-webkit-mask-image:linear-gradient(to left,transparent,#000 16px);mask-image:linear-gradient(to left,transparent,#000 16px)}
17401
+ .knobs-hy-tag{position:absolute;top:0;right:6px;font-size:var(--_k-type-small);line-height:var(--hy-bar);white-space:nowrap;pointer-events:none;
17402
+ color:color-mix(in oklch,var(--hy-c) 55%,var(--_k-fg))}
17403
+ .knobs-hy-tag[data-place="after"]{right:auto;left:calc(100% + 6px)}
17404
+ .knobs-hy-tag[data-place="none"]{visibility:hidden}
17405
+
17406
+ /* Event marks (their kind named in the tooltip): a dot for a commit, a diamond for a release, a cross for a deletion, a square for a
17407
+ channel's switch, a triangle for a finding; inferred ones hollow. */
17408
+ .knobs-hy-ev{position:absolute;top:0;width:16px;height:var(--hy-lane);margin-left:-8px;cursor:pointer;--hy-c:var(--_k-fg-2);--hy-s:9px}
17409
+ .knobs-hy-ev::before{content:"";position:absolute;left:50%;top:50%;width:var(--hy-s);height:var(--hy-s);margin:calc(var(--hy-s) / -2) 0 0 calc(var(--hy-s) / -2);
17410
+ border-radius:50%;background:var(--hy-c);transition:scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
17411
+ .knobs-hy-ev[data-range]{width:calc(var(--hy-w) + 16px)}
17412
+ .knobs-hy-ev[data-range]::before{left:calc(8px - var(--hy-s) / 2);right:calc(8px - var(--hy-s) / 2);width:auto;margin-left:0;border-radius:var(--_k-round)}
17413
+ .knobs-hy-ev[data-type="release"]{--hy-s:8px}
17414
+ .knobs-hy-ev[data-type="release"]::before{border-radius:1.5px;rotate:45deg}
17415
+ .knobs-hy-ev[data-type="channel"]{--hy-s:8px}
17416
+ .knobs-hy-ev[data-type="channel"]::before{border-radius:2px}
17417
+ .knobs-hy-ev[data-type="audit"]{--hy-s:10px}
17418
+ .knobs-hy-ev[data-type="audit"]::before{border-radius:0;clip-path:polygon(50% 4%,100% 92%,0 92%)}
17419
+ .knobs-hy-ev[data-type="deletion"]::before{border-radius:0;rotate:45deg;
17420
+ background:linear-gradient(var(--hy-c),var(--hy-c)) center/100% 1.75px no-repeat,linear-gradient(var(--hy-c),var(--hy-c)) center/1.75px 100% no-repeat}
17421
+ .knobs-hy-ev[data-inferred]:not([data-type="audit"],[data-type="deletion"])::before{background:transparent;box-shadow:inset 0 0 0 1.5px var(--hy-c)}
17422
+ .knobs-hy-ev[data-inferred]:is([data-type="audit"],[data-type="deletion"])::before{opacity:.5}
17423
+ /* Held-back marks (commits, by default) come up while the pointer is on their lane, and stay with
17424
+ "Show commits" or while selected. */
17425
+ .knobs-hy-ev[data-quiet]{opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
17426
+ .knobs-hy-track:hover .knobs-hy-ev[data-quiet]{opacity:.6;transition-duration:calc(var(--_k-motion-snappy-ms) * 1ms)}
17427
+ :is(.knobs-history[data-all] .knobs-hy-ev[data-quiet],.knobs-hy-ev[data-quiet]:is([data-selected],[data-hover])){opacity:1}
17428
+ .knobs-hy-ev[data-hover]::before{scale:1.25}
17429
+ .knobs-hy-ev[data-selected]::before{scale:1.3}
17430
+ .knobs-hy-ev[data-selected]::after{content:"";position:absolute;left:50%;top:50%;width:calc(var(--hy-s) + 9px);height:calc(var(--hy-s) + 9px);
17431
+ margin:calc((var(--hy-s) + 9px) / -2) 0 0 calc((var(--hy-s) + 9px) / -2);border-radius:50%;box-shadow:0 0 0 1.5px var(--hy-c)}
17432
+ .knobs-hy-ev[data-range][data-selected]::after{left:3px;right:3px;width:auto;margin-left:0;border-radius:var(--_k-round)}
17433
+ .knobs-history:focus-visible .knobs-hy-ev[data-selected]::after{box-shadow:0 0 0 2px var(--_k-focus)}
17434
+ .knobs-history:focus-visible .knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 2px var(--_k-focus)}
17435
+
17436
+ /* ——— under the chart: "Show commits", and a legend only when inferred and verified both appear ——— */
17437
+ .knobs-hy-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--_k-space-inside) var(--_k-space-title);margin-top:var(--_k-space-sibling);padding-left:var(--hy-gutter);
17438
+ font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
17439
+ .knobs-hy-legend{display:flex;flex-wrap:wrap;gap:var(--_k-space-inside) var(--_k-space-title)}
17440
+ .knobs-ui .knobs-hy-more{height:20px;margin-left:-6px;padding:0 6px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-3);cursor:pointer;
17441
+ transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
17442
+ .knobs-ui .knobs-hy-more:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
17443
+ .knobs-hy-key{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
17444
+ .knobs-hy-swatch{flex:none;width:14px;height:8px;border-radius:2px;background:color-mix(in oklch,var(--hy-c) 34%,transparent)}
17445
+ .knobs-hy-swatch[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 60%,transparent) 0 1.5px,transparent 1.5px 4px)}
17446
+
17447
+ /* ——— the detail: one area under the chart, its height on a spring ——— */
17448
+ .knobs-hy-detail{position:relative;overflow:hidden;user-select:text;-webkit-user-select:text}
17449
+ .knobs-hy-detail[data-moving]{-webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent)}
17450
+ .knobs-hy-dc{padding:var(--_k-space-title) 0 0 var(--hy-gutter);transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
17451
+ .knobs-hy-dc:is([data-enter],[data-leave]){opacity:0}
17452
+ .knobs-hy-dc[data-leave]{position:absolute;left:0;right:0;top:0;transition-duration:calc(var(--_k-motion-snappy-ms) * .8ms)}
17453
+ .knobs-hy-dmeta{display:flex;flex-wrap:wrap;gap:0 var(--_k-space-sibling);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
17454
+ .knobs-hy-dmeta>[data-tone]{color:var(--hy-c)}
17455
+ .knobs-hy-dtitle{margin-top:var(--_k-space-inside);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight-title);color:var(--_k-fg);text-wrap:balance}
17456
+ .knobs-hy-dtext{margin:var(--_k-space-inside) 0 0;max-width:68ch;font-size:var(--_k-type-body);line-height:var(--_k-leading-body);color:var(--_k-fg-2);text-wrap:pretty}
17457
+ .knobs-hy-dtext code{font:calc(1em - 1px) var(--_k-mono);padding:1px 4px;border-radius:var(--_k-radius-sm);background:var(--_k-fill)}
17458
+ .knobs-hy-dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:var(--_k-space-sibling)}
17459
+ .knobs-hy-dfoot:empty{display:none}
17460
+ .knobs-hy-evidence{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--_k-radius-sm);background:var(--_k-fill);color:var(--_k-fg-2);
17461
+ font:var(--_k-type-small)/1 var(--_k-mono);text-decoration:none;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
17462
+ a.knobs-hy-evidence:hover{background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
17463
+ .knobs-ui .knobs-hy-jump{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px 0 8px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-2);cursor:pointer;
17464
+ font-size:var(--_k-type-small);transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
17465
+ .knobs-ui .knobs-hy-jump:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
17466
+ .knobs-hy-jump svg{width:12px;height:12px}
17467
+
17468
+ /* ——— the pointer's tooltip: title, date, one line ——— */
17469
+ .knobs-hy-tip{position:absolute;left:0;top:0;max-width:280px;padding:6px 8px;border-radius:var(--_k-radius-md);background:var(--_k-bg-overlay);
17470
+ box-shadow:var(--_k-shadow-overlay),0 0 0 1px var(--_k-border);pointer-events:none;opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
17471
+ .knobs-hy-tip[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
17472
+ .knobs-hy-tt{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight-label);color:var(--_k-fg);text-wrap:balance}
17473
+ :is(.knobs-hy-td,.knobs-hy-tl){font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
17474
+ .knobs-hy-tl{margin-top:2px;color:var(--_k-fg-2);text-wrap:pretty}
17475
+ .knobs-hy-tl:empty{display:none}
17476
+
17477
+ @media(prefers-reduced-motion:reduce){
17478
+ .knobs-history :where(*,::before,::after){transition-property:opacity,background,color,box-shadow!important}
17479
+ .knobs-hy-dc{transition-duration:calc(var(--_k-hover-out) * 1ms)!important}
17480
+ }
17481
+ `;
17482
+
17483
+ // src/ui/history-model.ts
17484
+ var MS_DAY = 86400000;
17485
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
17486
+ var dayOf = (y, m, d) => Math.floor(Date.UTC(y, m, d) / MS_DAY);
17487
+ var parts = (day) => {
17488
+ const t = new Date(day * MS_DAY);
17489
+ return { y: t.getUTCFullYear(), m: t.getUTCMonth(), d: t.getUTCDate() };
17490
+ };
17491
+ var todayDay = (now = Date.now()) => {
17492
+ const t = new Date(now);
17493
+ return dayOf(t.getFullYear(), t.getMonth(), t.getDate());
17494
+ };
17495
+ var monthIndex = (name) => MONTHS.findIndex((m) => m.toLowerCase() === name.slice(0, 3).toLowerCase());
17496
+ function parseDay(text, year) {
17497
+ const s = text.trim();
17498
+ const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(s);
17499
+ if (iso) {
17500
+ const y = Number(iso[1]);
17501
+ const day = dayOf(y, Number(iso[2]) - 1, Number(iso[3]));
17502
+ return { from: day, to: day, year: y };
17503
+ }
17504
+ const m = /^([A-Za-z]{3,9})\.?\s+(\d{1,2})(?:\s*[–—-]\s*(?:([A-Za-z]{3,9})\.?\s+)?(\d{1,2}))?(?:,?\s+(\d{4}))?$/.exec(s);
17505
+ if (!m)
17506
+ return null;
17507
+ const m1 = monthIndex(m[1]);
17508
+ const m2 = m[3] ? monthIndex(m[3]) : m1;
17509
+ if (m1 < 0 || m2 < 0)
17510
+ return null;
17511
+ const written = m[5] ? Number(m[5]) : undefined;
17512
+ const y = written ?? year;
17513
+ if (y === undefined)
17514
+ return null;
17515
+ const from = dayOf(y, m1, Number(m[2]));
17516
+ let to = m[4] ? dayOf(y, m2, Number(m[4])) : from;
17517
+ if (to < from)
17518
+ to = dayOf(y + 1, m2, Number(m[4]));
17519
+ return { from, to, ...written !== undefined ? { year: written } : {} };
17520
+ }
17521
+ function formatDay(day) {
17522
+ const { y, m, d } = parts(day);
17523
+ return `${MONTHS[m]} ${d}, ${y}`;
17524
+ }
17525
+ function formatRange(from, to, open = false) {
17526
+ if (open)
17527
+ return `${formatDay(from)} – today`;
17528
+ if (to <= from)
17529
+ return formatDay(from);
17530
+ const a = parts(from);
17531
+ const b = parts(to);
17532
+ if (a.y === b.y && a.m === b.m)
17533
+ return `${MONTHS[a.m]} ${a.d}–${b.d}, ${a.y}`;
17534
+ if (a.y === b.y)
17535
+ return `${MONTHS[a.m]} ${a.d} – ${MONTHS[b.m]} ${b.d}, ${a.y}`;
17536
+ return `${formatDay(from)} – ${formatDay(to)}`;
17537
+ }
17538
+ var formatDays = (n) => `${n} ${n === 1 ? "day" : "days"}`;
17539
+ function guessTone(state) {
17540
+ const s = state.toLowerCase();
17541
+ if (/broken|bad|fail|missing|deleted|down/.test(s))
17542
+ return "bad";
17543
+ if (/warn|redirect|partial|beta|next|pre/.test(s))
17544
+ return "warn";
17545
+ if (/work|ok|good|fixed|live|green/.test(s))
17546
+ return "good";
17547
+ return "dim";
17548
+ }
17549
+ var bandLabel = (heading) => heading.replace(/^\s*\d+\s*[·.:)\-–]\s*/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
17550
+ function fromGroups(groups, today) {
17551
+ const events = [];
17552
+ const bands = [];
17553
+ const all = groups.flatMap((g) => g.events ?? g.rows ?? []);
17554
+ let year = all.map((e) => parseDay(e.date, 0)?.year).find((y) => y !== undefined) ?? parts(today).y;
17555
+ for (const g of groups) {
17556
+ let lo = Infinity;
17557
+ let hi = -Infinity;
17558
+ for (const e of g.events ?? g.rows ?? []) {
17559
+ const d = parseDay(e.date, year);
17560
+ if (!d)
17561
+ continue;
17562
+ if (d.year !== undefined)
17563
+ year = d.year;
17564
+ lo = Math.min(lo, d.from);
17565
+ hi = Math.max(hi, d.to);
17566
+ events.push({
17567
+ ...e,
17568
+ date: iso(d.from),
17569
+ ...d.to > d.from ? { to: iso(d.to) } : {},
17570
+ lane: e.lane ?? "code"
17571
+ });
17572
+ }
17573
+ const label = g.heading ? bandLabel(g.heading) : "";
17574
+ if (label && lo <= hi)
17575
+ bands.push({ from: iso(lo), to: iso(hi), label });
17576
+ }
17577
+ return { events, bands };
17578
+ }
17579
+ function iso(day) {
17580
+ const { y, m, d } = parts(day);
17581
+ return `${y}-${String(m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
17582
+ }
17583
+ var startOfMonth = (day) => {
17584
+ const { y, m } = parts(day);
17585
+ return dayOf(y, m, 1);
17586
+ };
17587
+ var TYPE_TONE = { deletion: "bad" };
17588
+ function normalizeHistory(data, now = todayDay()) {
17589
+ const today = data.axis?.today && parseDay(data.axis.today, parts(now).y)?.from || now;
17590
+ const converted = data.groups?.length ? fromGroups(data.groups, today) : { events: [], bands: [] };
17591
+ const rawEvents = [...data.events ?? [], ...converted.events];
17592
+ const rawBands = [...data.bands ?? [], ...converted.bands];
17593
+ const year = parts(today).y;
17594
+ const day = (s) => s ? parseDay(s, year) ?? null : null;
17595
+ const states = new Map;
17596
+ const stateOf = (key) => {
17597
+ const known = states.get(key);
17598
+ if (known)
17599
+ return known;
17600
+ const def = data.states?.[key];
17601
+ const s = { key, label: def?.label ?? key, tone: def?.tone ?? guessTone(key) };
17602
+ states.set(key, s);
17603
+ return s;
17604
+ };
17605
+ const items = new Map;
17606
+ const laneOrder = [];
17607
+ const lanes = new Map;
17608
+ const lane = (id, label = id === "code" ? "Code" : id) => {
17609
+ let l = lanes.get(id);
17610
+ if (!l) {
17611
+ l = { id, label, spans: [], events: [] };
17612
+ lanes.set(id, l);
17613
+ laneOrder.push(id);
17614
+ }
17615
+ return l;
17616
+ };
17617
+ if (rawEvents.some((e) => !e.lane || e.lane === "code") && !data.lanes?.some((l) => l.id === "code"))
17618
+ lane("code");
17619
+ let inferred = false;
17620
+ for (const l of data.lanes ?? []) {
17621
+ const into = lane(l.id, l.label);
17622
+ (l.spans ?? []).forEach((s, i) => {
17623
+ const a = day(s.from);
17624
+ if (!a)
17625
+ return;
17626
+ const open = !s.to;
17627
+ const b = open ? null : day(s.to);
17628
+ const to = open ? today : b?.to ?? a.to;
17629
+ const st = stateOf(s.state);
17630
+ const days = Math.max(0, to - a.from);
17631
+ const own = s.label ?? data.states?.[s.state]?.tag ?? "{d}d";
17632
+ const tags = own === "" ? [] : [...new Set([own, "{d}d"])].map((t) => t.replace(/\{d\}/g, String(days)));
17633
+ const id = s.id ?? `${l.id}/${i}`;
17634
+ const item = {
17635
+ id,
17636
+ kind: "span",
17637
+ lane: l.id,
17638
+ from: a.from,
17639
+ to,
17640
+ open,
17641
+ title: s.title ?? `${l.label} · ${st.label}`,
17642
+ ...s.detail ? { detail: s.detail } : {},
17643
+ tone: st.tone,
17644
+ inferred: s.provenance === "inferred",
17645
+ ...s.provenance ? { provenance: s.provenance } : {},
17646
+ ...s.source ? { source: s.source } : {},
17647
+ evidence: s.evidence ?? [],
17648
+ ...s.step ? { step: s.step } : {},
17649
+ state: s.state,
17650
+ tags
17651
+ };
17652
+ if (item.inferred)
17653
+ inferred = true;
17654
+ into.spans.push(item);
17655
+ items.set(id, item);
17656
+ });
17657
+ }
17658
+ const types = [];
17659
+ const events = [];
17660
+ rawEvents.forEach((e, index) => {
17661
+ const a = day(e.date);
17662
+ if (!a)
17663
+ return;
17664
+ const to = Math.max(a.to, day(e.to)?.to ?? a.to);
17665
+ const into = lane(e.lane ?? "code");
17666
+ const id = e.id ?? `e${index}`;
17667
+ const item = {
17668
+ id,
17669
+ kind: "event",
17670
+ lane: into.id,
17671
+ from: a.from,
17672
+ to,
17673
+ open: false,
17674
+ title: e.title,
17675
+ ...e.detail ? { detail: e.detail } : {},
17676
+ tone: e.tone ?? TYPE_TONE[e.type ?? ""] ?? "dim",
17677
+ ...e.type ? { type: e.type } : {},
17678
+ inferred: e.provenance === "inferred",
17679
+ ...e.provenance ? { provenance: e.provenance } : {},
17680
+ ...e.source ? { source: e.source } : {},
17681
+ evidence: e.evidence ?? [],
17682
+ ...e.step ? { step: e.step } : {},
17683
+ tags: []
17684
+ };
17685
+ if (item.inferred)
17686
+ inferred = true;
17687
+ if (e.type && !types.includes(e.type))
17688
+ types.push(e.type);
17689
+ into.events.push(item);
17690
+ items.set(id, item);
17691
+ events.push({ item, index });
17692
+ });
17693
+ const bands = [];
17694
+ for (const b of rawBands) {
17695
+ const a = day(b.from);
17696
+ if (!a)
17697
+ continue;
17698
+ const z = b.to ? day(b.to) : null;
17699
+ bands.push({ from: a.from, to: z ? z.to : today, label: b.label });
17700
+ }
17701
+ bands.sort((x, y) => x.from - y.from);
17702
+ const all = [...items.values()].flatMap((i) => [i.from, i.to]);
17703
+ for (const b of bands)
17704
+ all.push(b.from, b.to);
17705
+ const hasOpen = [...items.values()].some((i) => i.open);
17706
+ const lo = all.length ? Math.min(...all) : today - 90;
17707
+ let hi = all.length ? Math.max(...all) : today;
17708
+ if (hasOpen || data.axis?.today)
17709
+ hi = Math.max(hi, today);
17710
+ const from = day(data.axis?.from)?.from ?? startOfMonth(lo);
17711
+ const to = day(data.axis?.to)?.to ?? hi + Math.max(6, Math.round((hi - from) * 0.05));
17712
+ const laneIndex = (id) => laneOrder.indexOf(id);
17713
+ const order = eventOrder(events.map((e) => ({ ...e, laneIndex: laneIndex(e.item.lane) })));
17714
+ for (const l of lanes.values())
17715
+ l.events.sort((a, b) => a.from - b.from);
17716
+ return {
17717
+ from,
17718
+ to: Math.max(to, from + 1),
17719
+ today,
17720
+ lanes: laneOrder.map((id) => lanes.get(id)),
17721
+ events: order,
17722
+ bands,
17723
+ states: [...states.values()],
17724
+ types,
17725
+ inferred,
17726
+ verified: [...items.values()].some((i) => i.provenance === "verified"),
17727
+ items
17728
+ };
17729
+ }
17730
+ var DEFAULT_MARKS = ["release", "deletion"];
17731
+ function quietEvents(events, marks) {
17732
+ if (marks === "all")
17733
+ return new Set;
17734
+ const quiet = events.filter((e) => e.type && !marks.includes(e.type));
17735
+ return quiet.length === events.length ? new Set : new Set(quiet.map((e) => e.id));
17736
+ }
17737
+ var PLURALS = {
17738
+ commit: "commits",
17739
+ release: "releases",
17740
+ deletion: "deletions",
17741
+ channel: "channel switches",
17742
+ audit: "findings"
17743
+ };
17744
+ function quietLabel(events, quiet) {
17745
+ const types = [];
17746
+ for (const e of events)
17747
+ if (quiet.has(e.id) && e.type && !types.includes(e.type))
17748
+ types.push(e.type);
17749
+ const names = types.map((t) => PLURALS[t] ?? `${t}s`);
17750
+ return names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}` : names[0] ?? "";
17751
+ }
17752
+ function eventOrder(events) {
17753
+ return [...events].sort((a, b) => a.item.from - b.item.from || a.laneIndex - b.laneIndex || a.index - b.index).map((e) => e.item);
17754
+ }
17755
+ function stepEvent(order, current, dir, at) {
17756
+ if (!order.length)
17757
+ return null;
17758
+ const i = current === null ? -1 : order.findIndex((e) => e.id === current);
17759
+ if (i < 0 && at !== undefined) {
17760
+ const next = dir > 0 ? order.find((e) => e.from >= at) : [...order].reverse().find((e) => e.from <= at);
17761
+ return next ?? (dir > 0 ? order[order.length - 1] : order[0]);
17762
+ }
17763
+ if (i < 0)
17764
+ return dir > 0 ? order[0] : order[order.length - 1];
17765
+ return order[Math.max(0, Math.min(order.length - 1, i + dir))];
17766
+ }
17767
+ function dayScale(from, to, width) {
17768
+ const span = Math.max(1, to - from);
17769
+ return {
17770
+ x: (day) => (day - from) / span * width,
17771
+ day: (x) => from + x / Math.max(1, width) * span,
17772
+ f: (day) => (day - from) / span
17773
+ };
17774
+ }
17775
+ var STEPS = [1, 2, 3, 6, 12];
17776
+ function monthTicks(from, to, width, minGap = 44) {
17777
+ const perMonth = width / Math.max(1, to - from) * 30.44;
17778
+ const step = STEPS.find((s) => s * perMonth >= minGap) ?? 12;
17779
+ const ticks = [];
17780
+ const { y } = parts(from);
17781
+ let { m } = parts(from);
17782
+ let d = dayOf(y, m, 1);
17783
+ if (d < from) {
17784
+ m++;
17785
+ d = dayOf(y, m, 1);
17786
+ }
17787
+ while (d <= to) {
17788
+ const p = parts(d);
17789
+ if (p.m % step === 0)
17790
+ ticks.push({ day: d, label: p.m === 0 ? String(p.y) : MONTHS[p.m] });
17791
+ m++;
17792
+ d = dayOf(y, m, 1);
17793
+ }
17794
+ return ticks;
17795
+ }
17796
+ function fitLabels(labels, width, reserved = [], gap = 8) {
17797
+ let end = -Infinity;
17798
+ return labels.map(({ x, w }) => {
17799
+ const x1 = x + w;
17800
+ if (x < end + gap || x1 > width)
17801
+ return false;
17802
+ if (reserved.some((r) => x < r.x1 + gap && x1 > r.x0 - gap))
17803
+ return false;
17804
+ end = x1;
17805
+ return true;
17806
+ });
17807
+ }
17808
+ function clipSpan(from, to, axisFrom, axisTo) {
17809
+ if (to < axisFrom || from > axisTo)
17810
+ return null;
17811
+ return {
17812
+ from: Math.max(from, axisFrom),
17813
+ to: Math.min(to, axisTo),
17814
+ clipStart: from < axisFrom,
17815
+ clipEnd: to > axisTo
17816
+ };
17817
+ }
17818
+ function fitTag(widths, spanWidth, roomAfter, pad = 6) {
17819
+ for (let i = 0;i < widths.length; i++)
17820
+ if (widths[i] + pad * 2 <= spanWidth)
17821
+ return { index: i, place: "inside" };
17822
+ for (let i = 0;i < widths.length; i++)
17823
+ if (widths[i] + pad <= roomAfter)
17824
+ return { index: i, place: "after" };
17825
+ return null;
17826
+ }
17827
+ function bandRoom(bands, x) {
17828
+ return bands.map((b, i) => {
17829
+ const next = bands.slice(i + 1).find((n) => n.from > b.from);
17830
+ const end = Math.min(b.to, next ? next.from : Infinity);
17831
+ return Math.max(0, x(end) - x(b.from));
17832
+ });
17833
+ }
17834
+ function nearestEvent(events, day, tolerance) {
17835
+ let best = null;
17836
+ let gap = tolerance;
17837
+ for (const e of events) {
17838
+ const d = day < e.from ? e.from - day : day > e.to ? day - e.to : 0;
17839
+ if (d <= gap) {
17840
+ best = e;
17841
+ gap = d;
17842
+ }
17843
+ }
17844
+ return best;
17845
+ }
17846
+ function firstSentence(text) {
17847
+ if (!text)
17848
+ return "";
17849
+ const plain = text.replace(/`([^`]*)`/g, "$1").replace(/\*\*?([^*]+)\*\*?/g, "$1").trim();
17850
+ const m = /^.*?[.!?](?=\s|$)/.exec(plain);
17851
+ return (m ? m[0] : plain).trim();
17852
+ }
17853
+
17854
+ // src/ui/history.ts
17204
17855
  init_look();
17205
- init_selection_pill();
17206
17856
 
17207
17857
  // src/ui/walkthrough-model.ts
17208
17858
  var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
@@ -17268,7 +17918,14 @@ function fillMedia(m, base) {
17268
17918
  case "image":
17269
17919
  return { ...m, src: resolveSrc(m.src, base) };
17270
17920
  case "history":
17271
- return { kind: "history", groups: fillHistory(m) };
17921
+ if ("data" in m)
17922
+ return m;
17923
+ return {
17924
+ kind: "history",
17925
+ data: historyData(m),
17926
+ ...m.label ? { label: m.label } : {},
17927
+ ...m.marks ? { marks: m.marks } : {}
17928
+ };
17272
17929
  case "stack":
17273
17930
  return {
17274
17931
  kind: "stack",
@@ -17281,17 +17938,26 @@ function fillMedia(m, base) {
17281
17938
  return m;
17282
17939
  }
17283
17940
  }
17284
- function fillHistory(m) {
17941
+ function historyData(m) {
17942
+ const events = m.events ?? [];
17943
+ if (m.lanes || m.bands || m.states || m.axis)
17944
+ return {
17945
+ ...m.axis ? { axis: m.axis } : {},
17946
+ ...m.lanes ? { lanes: m.lanes } : {},
17947
+ ...events.length ? { events } : {},
17948
+ ...m.bands ? { bands: m.bands } : {},
17949
+ ...m.states ? { states: m.states } : {}
17950
+ };
17285
17951
  const groups = [];
17286
- if (m.events?.length)
17287
- groups.push({ events: m.events });
17952
+ if (events.length)
17953
+ groups.push({ events });
17288
17954
  for (const g of m.groups ?? [])
17289
17955
  groups.push({
17290
17956
  ...g.heading ? { heading: g.heading } : {},
17291
17957
  ...g.summary ? { summary: g.summary } : {},
17292
17958
  events: g.events ?? g.rows ?? []
17293
17959
  });
17294
- return groups;
17960
+ return { groups };
17295
17961
  }
17296
17962
  function parseBranch(branch) {
17297
17963
  if (!branch?.trim())
@@ -17910,11 +18576,18 @@ function inlineMarkdown(text) {
17910
18576
  return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`);
17911
18577
  }
17912
18578
 
17913
- // src/ui/walkthrough-blocks.ts
17914
- var GLYPH = {
17915
- go: icon("caretRight", { small: true }),
17916
- caret: icon("caretRight", { small: true })
18579
+ // src/ui/history.ts
18580
+ var HEIGHT2 = springMotion(SPRINGS.default);
18581
+ var GO = svg('<path d="M5 5h6v6M11 5l-6 6"/>');
18582
+ var TYPE_NAMES = {
18583
+ commit: "Commit",
18584
+ release: "Release",
18585
+ deletion: "Deletion",
18586
+ channel: "Channel switch",
18587
+ audit: "Finding"
17917
18588
  };
18589
+ var typeName = (t) => TYPE_NAMES[t] ?? t.charAt(0).toUpperCase() + t.slice(1);
18590
+ var REACH = 8;
17918
18591
  var el = (doc, tag, cls) => {
17919
18592
  const n = doc.createElement(tag);
17920
18593
  if (cls)
@@ -17926,112 +18599,625 @@ var text = (doc, tag, cls, value) => {
17926
18599
  n.textContent = value;
17927
18600
  return n;
17928
18601
  };
18602
+ function injectHistoryStyles(doc) {
18603
+ if (doc.querySelector("style[data-knobs-history]"))
18604
+ return;
18605
+ const style = doc.createElement("style");
18606
+ style.dataset.knobsHistory = "";
18607
+ style.textContent = aliased(historyCss);
18608
+ doc.head.append(style);
18609
+ }
18610
+ var when = (it) => it.kind === "span" ? `${formatRange(it.from, it.to, it.open)} · ${formatDays(it.to - it.from)}` : formatRange(it.from, it.to);
18611
+ function history(container, options) {
18612
+ const doc = container.ownerDocument;
18613
+ const win = doc.defaultView;
18614
+ const root = el(doc, "div", "knobs-history");
18615
+ applyLook2(root, options);
18616
+ injectHistoryStyles(doc);
18617
+ root.tabIndex = 0;
18618
+ root.setAttribute("role", "group");
18619
+ root.setAttribute("aria-label", options.label ?? "History");
18620
+ root.setAttribute("aria-roledescription", "timeline");
18621
+ const chart = el(doc, "div", "knobs-hy-chart");
18622
+ const foot = el(doc, "div", "knobs-hy-foot");
18623
+ const detail = el(doc, "div", "knobs-hy-detail");
18624
+ detail.setAttribute("aria-live", "polite");
18625
+ const tip = el(doc, "div", "knobs-hy-tip");
18626
+ tip.setAttribute("aria-hidden", "true");
18627
+ const tipTitle = el(doc, "div", "knobs-hy-tt");
18628
+ const tipDate = el(doc, "div", "knobs-hy-td");
18629
+ const tipLine = el(doc, "div", "knobs-hy-tl");
18630
+ tip.append(tipTitle, tipDate, tipLine);
18631
+ root.append(chart, foot, detail, tip);
18632
+ container.append(root);
18633
+ let model = normalizeHistory(options.data);
18634
+ let selected = options.selected && model.items.has(options.selected) ? options.selected : null;
18635
+ let disposed = false;
18636
+ let quiet = quietEvents(model.events, options.marks ?? DEFAULT_MARKS);
18637
+ let showAll = false;
18638
+ const walked = () => showAll ? model.events : model.events.filter((e) => !quiet.has(e.id));
18639
+ let nodes = new Map;
18640
+ let tracks = new Map;
18641
+ let over = null;
18642
+ let hoverLine = null;
18643
+ let selLine = null;
18644
+ let scale = dayScale(model.from, model.to, 1);
18645
+ let drawnWidth = -1;
18646
+ let hovered = null;
18647
+ let tipKey = "";
18648
+ const fontOf = (n) => {
18649
+ const c = win.getComputedStyle(n);
18650
+ return `${c.fontStyle} ${c.fontWeight} ${c.fontSize} ${c.fontFamily}`;
18651
+ };
18652
+ let measureCtx;
18653
+ const measure = (s, font) => {
18654
+ if (measureCtx === undefined)
18655
+ measureCtx = doc.createElement("canvas").getContext("2d");
18656
+ if (!measureCtx)
18657
+ return s.length * 6.4;
18658
+ measureCtx.font = font;
18659
+ return measureCtx.measureText(s).width;
18660
+ };
18661
+ function drawFoot() {
18662
+ foot.textContent = "";
18663
+ root.toggleAttribute("data-all", showAll);
18664
+ if (quiet.size) {
18665
+ const t = el(doc, "button", "knobs-hy-more");
18666
+ t.type = "button";
18667
+ t.setAttribute("aria-pressed", String(showAll));
18668
+ t.textContent = `${showAll ? "Hide" : "Show"} ${quietLabel(model.events, quiet)}`;
18669
+ t.addEventListener("click", () => {
18670
+ showAll = !showAll;
18671
+ if (!showAll && selected && quiet.has(selected))
18672
+ select(null, true, true);
18673
+ drawFoot();
18674
+ });
18675
+ foot.append(t);
18676
+ }
18677
+ if (model.inferred && model.verified) {
18678
+ const legend = el(doc, "span", "knobs-hy-legend");
18679
+ for (const [label, inferred] of [
18680
+ ["Verified", false],
18681
+ ["Inferred", true]
18682
+ ]) {
18683
+ const k = el(doc, "span", "knobs-hy-key");
18684
+ const sw = el(doc, "span", "knobs-hy-swatch");
18685
+ sw.dataset.tone = "dim";
18686
+ if (inferred)
18687
+ sw.dataset.inferred = "";
18688
+ k.append(sw, text(doc, "span", "", label));
18689
+ legend.append(k);
18690
+ }
18691
+ foot.append(legend);
18692
+ }
18693
+ foot.hidden = !foot.childElementCount;
18694
+ }
18695
+ function draw() {
18696
+ const width = root.clientWidth;
18697
+ drawnWidth = width;
18698
+ const narrow = width > 0 && width < (options.narrow ?? 560);
18699
+ root.toggleAttribute("data-narrow", narrow);
18700
+ root.toggleAttribute("data-bands", model.bands.length > 0);
18701
+ hideHover();
18702
+ chart.textContent = "";
18703
+ nodes = new Map;
18704
+ tracks = new Map;
18705
+ let gutter = 0;
18706
+ if (!narrow) {
18707
+ const probe = el(doc, "div", "knobs-hy-label");
18708
+ chart.append(probe);
18709
+ const font = fontOf(probe);
18710
+ const gap = parseFloat(win.getComputedStyle(probe).marginRight) || 0;
18711
+ probe.remove();
18712
+ const longest = Math.max(0, ...model.lanes.map((l) => measure(l.label, font)));
18713
+ gutter = Math.round(Math.min(Math.max(longest + gap + 2, 64), Math.max(96, width * 0.3), 200));
18714
+ }
18715
+ root.style.setProperty("--hy-gutter", `${gutter}px`);
18716
+ const plotW = Math.max(1, width - gutter);
18717
+ scale = dayScale(model.from, model.to, plotW);
18718
+ const pct = (d) => `${(scale.f(d) * 100).toFixed(4)}%`;
18719
+ const rows = ["auto"];
18720
+ for (let i = 0;i < model.lanes.length; i++)
18721
+ rows.push(...narrow ? ["auto", "var(--hy-lane)"] : ["var(--hy-lane)"]);
18722
+ chart.style.gridTemplateRows = rows.join(" ");
18723
+ const behind = el(doc, "div", "knobs-hy-layer");
18724
+ behind.setAttribute("aria-hidden", "true");
18725
+ for (const b of model.bands) {
18726
+ const c = clipSpan(b.from, b.to, model.from, model.to);
18727
+ if (!c)
18728
+ continue;
18729
+ const band = el(doc, "div", "knobs-hy-band");
18730
+ band.style.left = pct(c.from);
18731
+ band.style.width = pct(model.from + (c.to - c.from));
18732
+ behind.append(band);
18733
+ }
18734
+ const ticks = monthTicks(model.from, model.to, plotW);
18735
+ for (const t of ticks) {
18736
+ const line = el(doc, "div", "knobs-hy-grid");
18737
+ line.style.left = pct(t.day);
18738
+ behind.append(line);
18739
+ }
18740
+ chart.append(behind);
18741
+ const axis = el(doc, "div", "knobs-hy-axis");
18742
+ axis.setAttribute("aria-hidden", "true");
18743
+ const tickEls = ticks.map((t) => {
18744
+ const n = text(doc, "span", "knobs-hy-tick", t.label);
18745
+ n.style.left = pct(t.day);
18746
+ axis.append(n);
18747
+ return n;
18748
+ });
18749
+ const showToday = model.today >= model.from && model.today <= model.to;
18750
+ const now = showToday ? text(doc, "span", "knobs-hy-now", "Today") : null;
18751
+ if (now)
18752
+ axis.append(now);
18753
+ const room = bandRoom(model.bands, scale.x);
18754
+ const bandEls = model.bands.map((b, i) => {
18755
+ const n = text(doc, "span", "knobs-hy-blabel", b.label);
18756
+ n.style.left = pct(Math.max(b.from, model.from));
18757
+ n.style.maxWidth = `${Math.max(0, room[i])}px`;
18758
+ axis.append(n);
18759
+ return n;
18760
+ });
18761
+ chart.append(axis);
18762
+ model.lanes.forEach((lane, i) => {
18763
+ const row = narrow ? 2 + i * 2 : 2 + i;
18764
+ const label = text(doc, "div", "knobs-hy-label", lane.label);
18765
+ label.style.gridRow = String(row);
18766
+ label.title = lane.label;
18767
+ const track = el(doc, "div", "knobs-hy-track");
18768
+ track.style.gridRow = String(narrow ? row + 1 : row);
18769
+ track.dataset.lane = lane.id;
18770
+ tracks.set(lane.id, track);
18771
+ for (const s of lane.spans) {
18772
+ const c = clipSpan(s.from, s.to, model.from, model.to);
18773
+ if (!c)
18774
+ continue;
18775
+ const b = el(doc, "button", "knobs-hy-span");
18776
+ b.type = "button";
18777
+ b.tabIndex = -1;
18778
+ b.dataset.id = s.id;
18779
+ b.dataset.tone = s.tone;
18780
+ if (s.inferred)
18781
+ b.dataset.inferred = "";
18782
+ if (c.clipStart)
18783
+ b.dataset.clipStart = "";
18784
+ if (c.clipEnd)
18785
+ b.dataset.clipEnd = "";
18786
+ b.style.left = pct(c.from);
18787
+ b.style.width = pct(model.from + (c.to - c.from));
18788
+ b.setAttribute("aria-label", `${s.title}, ${when(s)}`);
18789
+ if (s.tags.length)
18790
+ b.append(text(doc, "span", "knobs-hy-tag", s.tags[0]));
18791
+ track.append(b);
18792
+ nodes.set(s.id, b);
18793
+ }
18794
+ for (const e of lane.events) {
18795
+ if (e.to < model.from || e.from > model.to)
18796
+ continue;
18797
+ const b = el(doc, "button", "knobs-hy-ev");
18798
+ b.type = "button";
18799
+ b.tabIndex = -1;
18800
+ b.dataset.id = e.id;
18801
+ b.dataset.tone = e.tone;
18802
+ if (e.type)
18803
+ b.dataset.type = e.type;
18804
+ if (e.inferred)
18805
+ b.dataset.inferred = "";
18806
+ if (quiet.has(e.id))
18807
+ b.dataset.quiet = "";
18808
+ b.style.left = pct(Math.max(e.from, model.from));
18809
+ if (e.to > e.from) {
18810
+ b.dataset.range = "";
18811
+ b.style.setProperty("--hy-w", `${scale.x(model.from + (e.to - e.from)).toFixed(2)}px`);
18812
+ }
18813
+ b.setAttribute("aria-label", `${when(e)}: ${e.title}`);
18814
+ track.append(b);
18815
+ nodes.set(e.id, b);
18816
+ }
18817
+ chart.append(label, track);
18818
+ });
18819
+ over = el(doc, "div", "knobs-hy-layer");
18820
+ over.setAttribute("aria-hidden", "true");
18821
+ if (showToday) {
18822
+ const today = el(doc, "div", "knobs-hy-today");
18823
+ today.style.left = pct(model.today);
18824
+ over.append(today);
18825
+ }
18826
+ selLine = el(doc, "div", "knobs-hy-sel");
18827
+ hoverLine = el(doc, "div", "knobs-hy-hover");
18828
+ over.append(selLine, hoverLine);
18829
+ chart.append(over);
18830
+ const reserved = [];
18831
+ if (now) {
18832
+ const w = now.offsetWidth;
18833
+ const x0 = Math.max(0, Math.min(plotW - w, scale.x(model.today) - w / 2));
18834
+ now.style.left = `${x0}px`;
18835
+ reserved.push({ x0, x1: x0 + w });
18836
+ }
18837
+ const fits = fitLabels(ticks.map((t, i) => ({ x: scale.x(t.day), w: tickEls[i].offsetWidth })), plotW, reserved);
18838
+ tickEls.forEach((n, i) => n.hidden = !fits[i]);
18839
+ for (const n of bandEls)
18840
+ n.toggleAttribute("data-overflow", n.scrollWidth > n.clientWidth + 1);
18841
+ for (const n of chart.querySelectorAll(".knobs-hy-label"))
18842
+ n.toggleAttribute("data-overflow", n.scrollWidth > n.clientWidth + 1);
18843
+ for (const lane of model.lanes) {
18844
+ const spans = lane.spans.map((s) => ({ s, c: clipSpan(s.from, s.to, model.from, model.to) })).filter((x) => x.c).sort((a, b) => a.c.from - b.c.from);
18845
+ spans.forEach(({ s, c }, i) => {
18846
+ const b = nodes.get(s.id);
18847
+ const tag = b?.querySelector(".knobs-hy-tag");
18848
+ if (!b || !tag)
18849
+ return;
18850
+ const font = fontOf(tag);
18851
+ const widths = s.tags.map((t) => measure(t, font));
18852
+ const next = spans[i + 1]?.c;
18853
+ const spanW = scale.x(c.to) - scale.x(c.from);
18854
+ const after = (next ? scale.x(next.from) : plotW) - scale.x(c.to);
18855
+ const fit = fitTag(widths, spanW, after);
18856
+ tag.textContent = fit ? s.tags[fit.index] : s.tags[0] ?? "";
18857
+ tag.dataset.place = fit ? fit.place : "none";
18858
+ });
18859
+ }
18860
+ paintSelection();
18861
+ }
18862
+ const h = new Spring(0, HEIGHT2.omega, HEIGHT2.zeta, 0.25);
18863
+ let goal = 0;
18864
+ let content = null;
18865
+ const loop = frameLoop(win, (dt) => {
18866
+ h.step(dt);
18867
+ const moving = !h.resting;
18868
+ detail.style.height = moving ? `${Math.max(0, h.x).toFixed(2)}px` : goal > 0 ? "" : "0px";
18869
+ detail.toggleAttribute("data-moving", moving);
18870
+ return moving;
18871
+ });
18872
+ function buildDetail(it) {
18873
+ const c = el(doc, "div", "knobs-hy-dc");
18874
+ const meta = el(doc, "div", "knobs-hy-dmeta");
18875
+ meta.append(text(doc, "time", "", when(it)));
18876
+ const lane = model.lanes.find((l) => l.id === it.lane);
18877
+ if (lane && (model.lanes.length > 1 || lane.id !== "code"))
18878
+ meta.append(text(doc, "span", "", lane.label));
18879
+ if (it.type)
18880
+ meta.append(text(doc, "span", "", typeName(it.type)));
18881
+ if (it.provenance || it.source) {
18882
+ const p = text(doc, "span", "", [it.provenance, it.source].filter(Boolean).join(" · "));
18883
+ if (it.inferred) {
18884
+ p.dataset.tone = "warn";
18885
+ p.title = "Worked out from behaviour, not seen in the record";
18886
+ }
18887
+ meta.append(p);
18888
+ }
18889
+ c.append(meta, text(doc, "div", "knobs-hy-dtitle", it.title));
18890
+ if (it.detail) {
18891
+ const p = el(doc, "p", "knobs-hy-dtext");
18892
+ p.innerHTML = inlineMarkdown(it.detail);
18893
+ c.append(p);
18894
+ }
18895
+ const foot = el(doc, "div", "knobs-hy-dfoot");
18896
+ for (const e of it.evidence) {
18897
+ const tag = text(doc, e.href ? "a" : "span", "knobs-hy-evidence", e.label);
18898
+ if (tag instanceof HTMLAnchorElement && e.href) {
18899
+ tag.href = e.href;
18900
+ tag.target = "_blank";
18901
+ tag.rel = "noreferrer";
18902
+ }
18903
+ foot.append(tag);
18904
+ }
18905
+ if (it.step && options.onJump) {
18906
+ const go = el(doc, "button", "knobs-hy-jump");
18907
+ go.type = "button";
18908
+ go.append(text(doc, "span", "", "Go to step"));
18909
+ go.insertAdjacentHTML("beforeend", GO);
18910
+ const step = it.step;
18911
+ go.addEventListener("click", () => options.onJump?.(step));
18912
+ foot.append(go);
18913
+ }
18914
+ c.append(foot);
18915
+ return c;
18916
+ }
18917
+ function showDetail(it, animate) {
18918
+ const from = detail.offsetHeight;
18919
+ const old = content;
18920
+ const next = it ? buildDetail(it) : null;
18921
+ if (old) {
18922
+ if (animate) {
18923
+ old.dataset.leave = "";
18924
+ const drop = () => old.remove();
18925
+ old.addEventListener("transitionend", drop, { once: true });
18926
+ win.setTimeout(drop, 400);
18927
+ } else
18928
+ old.remove();
18929
+ }
18930
+ content = next;
18931
+ if (next) {
18932
+ detail.append(next);
18933
+ if (animate) {
18934
+ next.dataset.enter = "";
18935
+ next.offsetHeight;
18936
+ delete next.dataset.enter;
18937
+ }
18938
+ }
18939
+ goal = next ? next.offsetHeight : 0;
18940
+ if (!animate || reducedMotion(win)) {
18941
+ loop.stop();
18942
+ h.snap(goal);
18943
+ detail.style.height = goal > 0 ? "" : "0px";
18944
+ detail.removeAttribute("data-moving");
18945
+ return;
18946
+ }
18947
+ if (h.resting)
18948
+ h.snap(from);
18949
+ h.set(goal);
18950
+ loop.wake();
18951
+ }
18952
+ function paintSelection() {
18953
+ for (const [id, n] of nodes)
18954
+ n.toggleAttribute("data-selected", id === selected);
18955
+ const it = selected ? model.items.get(selected) : undefined;
18956
+ if (selLine) {
18957
+ const on = it?.kind === "event";
18958
+ selLine.toggleAttribute("data-on", on);
18959
+ if (on)
18960
+ selLine.style.left = `${(scale.f(it.from) * 100).toFixed(4)}%`;
18961
+ }
18962
+ }
18963
+ function select(id, animate, notify) {
18964
+ const next = id && model.items.has(id) ? id : null;
18965
+ if (next === selected)
18966
+ return;
18967
+ selected = next;
18968
+ paintSelection();
18969
+ showDetail(next ? model.items.get(next) : null, animate);
18970
+ if (notify)
18971
+ options.onSelect?.(next);
18972
+ }
18973
+ function itemAt(e) {
18974
+ if (!over)
18975
+ return null;
18976
+ const r = over.getBoundingClientRect();
18977
+ if (!r.width)
18978
+ return null;
18979
+ const x = Math.max(0, Math.min(r.width, e.clientX - r.left));
18980
+ const target = e.target instanceof Element ? e.target : null;
18981
+ const hit = target?.closest("[data-id]");
18982
+ if (hit && chart.contains(hit))
18983
+ return { it: model.items.get(hit.dataset.id) ?? null, x };
18984
+ const laneId = target?.closest(".knobs-hy-track")?.dataset.lane;
18985
+ const lane = laneId ? model.lanes.find((l) => l.id === laneId) : undefined;
18986
+ if (lane) {
18987
+ const reach = REACH / r.width * (model.to - model.from);
18988
+ const near = nearestEvent(lane.events, scale.day(x), reach);
18989
+ if (near)
18990
+ return { it: near, x };
18991
+ }
18992
+ return { it: null, x };
18993
+ }
18994
+ function hideHover() {
18995
+ hoverLine?.removeAttribute("data-on");
18996
+ tip.removeAttribute("data-on");
18997
+ hovered?.removeAttribute("data-hover");
18998
+ hovered = null;
18999
+ tipKey = "";
19000
+ }
19001
+ function hover(e) {
19002
+ const at = itemAt(e);
19003
+ if (!at || !over || !hoverLine)
19004
+ return hideHover();
19005
+ const { it, x } = at;
19006
+ const lineX = it?.kind === "event" ? scale.x(it.from) : x;
19007
+ hoverLine.style.left = `${lineX.toFixed(2)}px`;
19008
+ hoverLine.setAttribute("data-on", "");
19009
+ const node = it ? nodes.get(it.id) ?? null : null;
19010
+ if (node !== hovered) {
19011
+ hovered?.removeAttribute("data-hover");
19012
+ node?.setAttribute("data-hover", "");
19013
+ hovered = node;
19014
+ }
19015
+ const key = it ? it.id : `d${Math.round(scale.day(x))}`;
19016
+ if (key !== tipKey) {
19017
+ tipKey = key;
19018
+ tipTitle.textContent = it ? it.title : formatDay(Math.round(scale.day(x)));
19019
+ tipDate.textContent = it ? [when(it), it.type ? typeName(it.type) : ""].filter(Boolean).join(" · ") : "";
19020
+ tipDate.hidden = !it;
19021
+ tipLine.textContent = it ? firstSentence(it.detail) : "";
19022
+ }
19023
+ const rr = root.getBoundingClientRect();
19024
+ const or = over.getBoundingClientRect();
19025
+ const laneId = it?.lane ?? (e.target instanceof Element ? e.target.closest(".knobs-hy-track")?.dataset.lane : undefined);
19026
+ const lr = laneId && tracks.get(laneId)?.getBoundingClientRect() || or;
19027
+ const tw = tip.offsetWidth;
19028
+ const th = tip.offsetHeight;
19029
+ const left = Math.max(0, Math.min(rr.width - tw, or.left - rr.left + lineX - tw / 2));
19030
+ let top = lr.top - rr.top - th - 6;
19031
+ if (top + rr.top < 0)
19032
+ top = lr.bottom - rr.top + 6;
19033
+ tip.style.transform = `translate3d(${Math.round(left)}px,${Math.round(top)}px,0)`;
19034
+ tip.setAttribute("data-on", "");
19035
+ }
19036
+ chart.addEventListener("pointermove", (e) => {
19037
+ if (e.pointerType === "touch")
19038
+ return;
19039
+ hover(e);
19040
+ });
19041
+ chart.addEventListener("pointerleave", hideHover);
19042
+ chart.addEventListener("mousedown", (e) => {
19043
+ if (!(e.target instanceof Element) || !e.target.closest("button"))
19044
+ return;
19045
+ e.preventDefault();
19046
+ root.focus({ preventScroll: true });
19047
+ });
19048
+ chart.addEventListener("click", (e) => {
19049
+ const at = itemAt(e);
19050
+ const id = at?.it?.id ?? null;
19051
+ select(id !== null && id === selected ? null : id, true, true);
19052
+ });
19053
+ root.addEventListener("keydown", (e) => {
19054
+ const onChart = e.target instanceof Node && chart.contains(e.target);
19055
+ if (e.target !== root && !onChart || e.metaKey || e.ctrlKey || e.altKey)
19056
+ return;
19057
+ let next;
19058
+ const order = walked();
19059
+ const at = selected ? model.items.get(selected)?.from : undefined;
19060
+ if (e.key === "ArrowRight")
19061
+ next = stepEvent(order, selected, 1, at);
19062
+ else if (e.key === "ArrowLeft")
19063
+ next = stepEvent(order, selected, -1, at);
19064
+ else if (e.key === "Home")
19065
+ next = order[0] ?? null;
19066
+ else if (e.key === "End")
19067
+ next = order[order.length - 1] ?? null;
19068
+ else if (e.key === "Escape" && selected)
19069
+ next = null;
19070
+ else if (e.key === "Enter" && selected) {
19071
+ const step = model.items.get(selected)?.step;
19072
+ if (!step || !options.onJump)
19073
+ return;
19074
+ options.onJump(step);
19075
+ e.preventDefault();
19076
+ e.stopPropagation();
19077
+ return;
19078
+ } else
19079
+ return;
19080
+ e.preventDefault();
19081
+ e.stopPropagation();
19082
+ hideHover();
19083
+ select(next?.id ?? null, true, true);
19084
+ });
19085
+ let raf = 0;
19086
+ const ro = new ResizeObserver(() => {
19087
+ if (raf || disposed)
19088
+ return;
19089
+ raf = win.requestAnimationFrame(() => {
19090
+ raf = 0;
19091
+ if (!disposed && root.clientWidth !== drawnWidth)
19092
+ draw();
19093
+ });
19094
+ });
19095
+ ro.observe(root);
19096
+ const restyle = () => draw();
19097
+ root.addEventListener("knobs-restyle", restyle);
19098
+ drawFoot();
19099
+ draw();
19100
+ detail.style.height = "0px";
19101
+ if (selected)
19102
+ showDetail(model.items.get(selected), false);
19103
+ const dispose = () => {
19104
+ if (disposed)
19105
+ return;
19106
+ disposed = true;
19107
+ ro.disconnect();
19108
+ if (raf)
19109
+ win.cancelAnimationFrame(raf);
19110
+ loop.stop();
19111
+ root.removeEventListener("knobs-restyle", restyle);
19112
+ root.remove();
19113
+ };
19114
+ return {
19115
+ element: root,
19116
+ get model() {
19117
+ return model;
19118
+ },
19119
+ get selected() {
19120
+ return selected;
19121
+ },
19122
+ select(id) {
19123
+ select(id, true, false);
19124
+ },
19125
+ set(data) {
19126
+ model = normalizeHistory(data);
19127
+ const keep = selected && model.items.has(selected) ? selected : null;
19128
+ selected = keep;
19129
+ quiet = quietEvents(model.events, options.marks ?? DEFAULT_MARKS);
19130
+ drawFoot();
19131
+ draw();
19132
+ showDetail(keep ? model.items.get(keep) : null, false);
19133
+ },
19134
+ focus() {
19135
+ root.focus();
19136
+ },
19137
+ dispose,
19138
+ destroy: dispose
19139
+ };
19140
+ }
19141
+
19142
+ // src/ui/walkthrough-blocks.ts
19143
+ init_icons();
19144
+ init_look();
19145
+ init_selection_pill();
19146
+ var GLYPH = {
19147
+ caret: icon("caretRight", { small: true })
19148
+ };
19149
+ var el2 = (doc, tag, cls) => {
19150
+ const n = doc.createElement(tag);
19151
+ if (cls)
19152
+ n.className = cls;
19153
+ return n;
19154
+ };
19155
+ var text2 = (doc, tag, cls, value) => {
19156
+ const n = el2(doc, tag, cls);
19157
+ n.textContent = value;
19158
+ return n;
19159
+ };
17929
19160
  var prose = (doc, tag, cls, value) => {
17930
- const n = el(doc, tag, cls);
19161
+ const n = el2(doc, tag, cls);
17931
19162
  n.innerHTML = inlineMarkdown(value);
17932
19163
  return n;
17933
19164
  };
17934
19165
  var link = (doc, cls, label, href) => {
17935
19166
  if (!href)
17936
- return text(doc, "span", cls, label);
17937
- const a = text(doc, "a", cls, label);
19167
+ return text2(doc, "span", cls, label);
19168
+ const a = text2(doc, "a", cls, label);
17938
19169
  a.href = href;
17939
19170
  a.target = "_blank";
17940
19171
  a.rel = "noreferrer";
17941
19172
  return a;
17942
19173
  };
17943
- function stepButton(doc, ref, steps) {
19174
+ function rowLink(row, ref, steps) {
17944
19175
  const s = ref && steps?.find(ref);
17945
19176
  if (!s || !steps)
17946
- return null;
17947
- const b = el(doc, "button", "knobs-wt-steplink");
17948
- b.innerHTML = GLYPH.go;
17949
- b.type = "button";
17950
- b.title = `Go to “${s.title}”`;
17951
- b.setAttribute("aria-label", b.title);
17952
- b.addEventListener("click", (e) => {
17953
- e.stopPropagation();
19177
+ return;
19178
+ const doc = row.ownerDocument;
19179
+ row.dataset.go = "";
19180
+ row.tabIndex = 0;
19181
+ row.title = `Go to “${plainMarkdown(s.title)}”`;
19182
+ row.addEventListener("click", (e) => {
19183
+ if (e.target?.closest("a,button"))
19184
+ return;
19185
+ if (doc.getSelection()?.toString())
19186
+ return;
19187
+ steps.go(s.key);
19188
+ });
19189
+ row.addEventListener("keydown", (e) => {
19190
+ if (e.target !== row || e.key !== "Enter" && e.key !== " ")
19191
+ return;
19192
+ e.preventDefault();
17954
19193
  steps.go(s.key);
17955
19194
  });
17956
- return b;
17957
19195
  }
17958
- var tags = (doc, list) => {
17959
- if (!list?.length)
17960
- return null;
17961
- const row = el(doc, "div", "knobs-wt-tags");
17962
- for (const e of list)
17963
- row.append(link(doc, "knobs-wt-tag", e.label, e.href));
17964
- return row;
17965
- };
17966
19196
  function mountHistory(host, m, steps) {
17967
- const doc = host.ownerDocument;
17968
- const root = el(doc, "div", "knobs-wt-history");
17969
- const longest = Math.max(6, ...m.groups.flatMap((g) => g.events.flatMap((e) => [e.date.length, e.age?.length ?? 0])));
17970
- root.style.setProperty("--kw-hw", `${Math.min(16, longest + 1)}ch`);
17971
- for (const g of m.groups) {
17972
- const group = el(doc, "section", "knobs-wt-hgroup");
17973
- if (g.heading || g.summary) {
17974
- const head = el(doc, "header", "knobs-wt-hhead");
17975
- if (g.heading)
17976
- head.append(text(doc, "h4", "knobs-wt-hheading", g.heading));
17977
- if (g.summary)
17978
- head.append(text(doc, "span", "knobs-wt-hsum", g.summary));
17979
- group.append(head);
17980
- }
17981
- const list = el(doc, "ol", "knobs-wt-hlist");
17982
- for (const e of g.events)
17983
- list.append(historyEvent(doc, e, steps));
17984
- group.append(list);
17985
- root.append(group);
17986
- }
17987
- host.append(root);
17988
- return { dispose: () => root.remove(), context: () => ({ kind: "history" }) };
17989
- }
17990
- function historyEvent(doc, e, steps) {
17991
- const li = el(doc, "li", "knobs-wt-hev");
17992
- if (e.tone)
17993
- li.dataset.tone = e.tone;
17994
- if (e.type)
17995
- li.dataset.type = e.type;
17996
- if (e.provenance)
17997
- li.dataset.provenance = e.provenance;
17998
- const when = el(doc, "div", "knobs-wt-hwhen");
17999
- when.append(text(doc, "time", "knobs-wt-hdate", e.date));
18000
- if (e.age)
18001
- when.append(text(doc, "span", "knobs-wt-hage", e.age));
18002
- const spine = el(doc, "span", "knobs-wt-hspine");
18003
- spine.setAttribute("aria-hidden", "true");
18004
- spine.append(el(doc, "span", "knobs-wt-hdot"));
18005
- const main = el(doc, "div", "knobs-wt-hmain");
18006
- const title = el(doc, "div", "knobs-wt-htitle");
18007
- title.append(text(doc, "span", "knobs-wt-htext", e.title));
18008
- if (e.provenance || e.source) {
18009
- const p = text(doc, "span", "knobs-wt-prov", [e.provenance, e.source].filter(Boolean).join(" · "));
18010
- if (e.provenance === "inferred")
18011
- p.title = "Inferred from behaviour, not seen in the record";
18012
- else if (e.provenance === "verified")
18013
- p.title = "Seen in the record";
18014
- title.append(p);
18015
- }
18016
- const go = stepButton(doc, e.step, steps);
18017
- if (go)
18018
- title.append(go);
18019
- main.append(title);
18020
- if (e.detail)
18021
- main.append(prose(doc, "p", "knobs-wt-hdetail", e.detail));
18022
- const t = tags(doc, e.evidence);
18023
- if (t)
18024
- main.append(t);
18025
- li.append(when, spine, main);
18026
- return li;
19197
+ const h = history(host, {
19198
+ data: m.data,
19199
+ ...m.label ? { label: m.label } : {},
19200
+ ...m.marks ? { marks: m.marks } : {},
19201
+ ...steps ? {
19202
+ onJump: (ref) => {
19203
+ const s = steps.find(ref);
19204
+ if (s)
19205
+ steps.go(s.key);
19206
+ }
19207
+ } : {}
19208
+ });
19209
+ return {
19210
+ dispose: () => h.dispose(),
19211
+ context: () => ({ kind: "history" })
19212
+ };
18027
19213
  }
18028
19214
  function mountStack(host, m, steps) {
18029
19215
  const doc = host.ownerDocument;
18030
19216
  const layout = stackLayout(m);
18031
- const root = el(doc, "div", "knobs-wt-stack");
19217
+ const root = el2(doc, "div", "knobs-wt-stack");
18032
19218
  root.toggleAttribute("data-flat", layout.flat);
18033
19219
  const lane = (kind) => {
18034
- const s = el(doc, "span", "knobs-wt-lane");
19220
+ const s = el2(doc, "span", "knobs-wt-lane");
18035
19221
  s.dataset.rail = kind;
18036
19222
  return s;
18037
19223
  };
@@ -18042,19 +19228,19 @@ function mountStack(host, m, steps) {
18042
19228
  };
18043
19229
  const from = layout.trunk ? 0 : 1;
18044
19230
  if (layout.trunk && layout.rows.length) {
18045
- const row = el(doc, "div", "knobs-wt-srow");
19231
+ const row = el2(doc, "div", "knobs-wt-srow");
18046
19232
  row.dataset.trunk = "";
18047
- const lanes = el(doc, "span", "knobs-wt-lanes");
19233
+ const lanes = el2(doc, "span", "knobs-wt-lanes");
18048
19234
  lanes.setAttribute("aria-hidden", "true");
18049
19235
  lanes.append(node(true));
18050
- row.append(lanes, text(doc, "span", "knobs-wt-trunk", layout.trunk));
19236
+ row.append(lanes, text2(doc, "span", "knobs-wt-trunk", layout.trunk));
18051
19237
  root.append(row);
18052
19238
  }
18053
19239
  for (const r of layout.rows) {
18054
19240
  const it = r.item;
18055
- const row = el(doc, "div", "knobs-wt-srow");
19241
+ const row = el2(doc, "div", "knobs-wt-srow");
18056
19242
  if (!layout.flat) {
18057
- const lanes = el(doc, "span", "knobs-wt-lanes");
19243
+ const lanes = el2(doc, "span", "knobs-wt-lanes");
18058
19244
  lanes.setAttribute("aria-hidden", "true");
18059
19245
  for (let k = from;k < r.depth; k++)
18060
19246
  lanes.append(lane(r.rails[k] ? "pass" : "none"));
@@ -18063,24 +19249,21 @@ function mountStack(host, m, steps) {
18063
19249
  lanes.append(node(r.children > 0));
18064
19250
  row.append(lanes);
18065
19251
  }
18066
- const main = el(doc, "div", "knobs-wt-smain");
18067
- const line = el(doc, "div", "knobs-wt-sline");
19252
+ const main = el2(doc, "div", "knobs-wt-smain");
19253
+ const line = el2(doc, "div", "knobs-wt-sline");
18068
19254
  if (it.order)
18069
- line.append(text(doc, "span", "knobs-wt-sord", it.order));
19255
+ line.append(text2(doc, "span", "knobs-wt-sord", it.order));
18070
19256
  line.append(link(doc, "knobs-wt-spr", it.pr, it.href));
18071
19257
  const target = it.step ? steps?.find(it.step) : null;
18072
19258
  if (target && steps) {
18073
- const title = text(doc, "button", "knobs-wt-stitle", it.title);
19259
+ const title = text2(doc, "button", "knobs-wt-stitle", it.title);
18074
19260
  title.type = "button";
18075
- title.title = `Go to “${target.title}”`;
19261
+ title.title = `Go to “${plainMarkdown(target.title)}”`;
18076
19262
  title.addEventListener("click", (e) => {
18077
19263
  e.stopPropagation();
18078
19264
  steps.go(target.key);
18079
19265
  });
18080
19266
  line.append(title);
18081
- const go = stepButton(doc, it.step, steps);
18082
- if (go)
18083
- line.append(go);
18084
19267
  row.dataset.go = "";
18085
19268
  row.addEventListener("click", (e) => {
18086
19269
  const t = e.target;
@@ -18091,14 +19274,14 @@ function mountStack(host, m, steps) {
18091
19274
  steps.go(target.key);
18092
19275
  });
18093
19276
  } else
18094
- line.append(text(doc, "span", "knobs-wt-stitle", it.title));
19277
+ line.append(text2(doc, "span", "knobs-wt-stitle", it.title));
18095
19278
  main.append(line);
18096
19279
  if (it.summary)
18097
19280
  main.append(prose(doc, "p", "knobs-wt-ssummary", it.summary));
18098
- const meta = el(doc, "div", "knobs-wt-smeta");
19281
+ const meta = el2(doc, "div", "knobs-wt-smeta");
18099
19282
  if (it.head) {
18100
19283
  const branch = r.offBase ? `${it.head} → ${r.offBase}` : it.head;
18101
- meta.append(text(doc, "span", "knobs-wt-sbranch", branch));
19284
+ meta.append(text2(doc, "span", "knobs-wt-sbranch", branch));
18102
19285
  }
18103
19286
  if (it.delta !== undefined)
18104
19287
  meta.append(delta(doc, it.delta));
@@ -18106,7 +19289,7 @@ function mountStack(host, m, steps) {
18106
19289
  main.append(meta);
18107
19290
  row.append(main);
18108
19291
  if (it.status) {
18109
- const chip = text(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
19292
+ const chip = text2(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
18110
19293
  chip.dataset.tone = it.tone ?? "dim";
18111
19294
  row.append(chip);
18112
19295
  }
@@ -18116,40 +19299,38 @@ function mountStack(host, m, steps) {
18116
19299
  return { dispose: () => root.remove(), context: () => ({ kind: "stack" }) };
18117
19300
  }
18118
19301
  function delta(doc, d) {
18119
- const s = el(doc, "span", "knobs-wt-delta");
19302
+ const s = el2(doc, "span", "knobs-wt-delta");
18120
19303
  if (typeof d === "string") {
18121
19304
  s.textContent = d;
18122
19305
  return s;
18123
19306
  }
18124
19307
  const n = (v) => v.toLocaleString("en-US");
18125
- s.append(text(doc, "span", "knobs-wt-add", `+${n(d.add)}`));
18126
- s.append(text(doc, "span", "knobs-wt-del", `−${n(d.del)}`));
19308
+ s.append(text2(doc, "span", "knobs-wt-add", `+${n(d.add)}`));
19309
+ s.append(text2(doc, "span", "knobs-wt-del", `−${n(d.del)}`));
18127
19310
  s.setAttribute("aria-label", `${d.add} added, ${d.del} removed`);
18128
19311
  return s;
18129
19312
  }
18130
- function cellContent(doc, into, cell, steps) {
19313
+ function cellContent(doc, into, cell) {
18131
19314
  let content;
18132
19315
  if (cell.badge) {
18133
19316
  content = link(doc, "knobs-wt-chip", cell.text, cell.href);
18134
19317
  content.dataset.tone = cell.badge;
18135
19318
  } else {
18136
- const inner = cell.code ? text(doc, "code", "", cell.text) : null;
19319
+ const inner = cell.code ? text2(doc, "code", "", cell.text) : null;
18137
19320
  if (cell.href) {
18138
19321
  content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
18139
19322
  if (inner)
18140
19323
  content.append(inner);
18141
19324
  } else
18142
- content = inner ?? text(doc, "span", "", cell.text);
19325
+ content = inner ?? text2(doc, "span", "", cell.text);
18143
19326
  if (cell.tone) {
18144
19327
  content.classList.add("knobs-wt-ctone");
18145
19328
  content.dataset.tone = cell.tone;
18146
19329
  }
18147
19330
  }
18148
19331
  into.append(content);
18149
- const go = stepButton(doc, cell.step, steps);
18150
- if (go)
18151
- into.append(go);
18152
19332
  }
19333
+ var rowStep = (r, columns) => columns.map((c) => r[c.key]?.step).find((step) => step !== undefined);
18153
19334
  var cellAttrs = (td, c) => {
18154
19335
  td.dataset.align = c.align;
18155
19336
  if (c.mono)
@@ -18158,83 +19339,85 @@ var cellAttrs = (td, c) => {
18158
19339
  function mountTable(host, m, steps) {
18159
19340
  const doc = host.ownerDocument;
18160
19341
  const win = doc.defaultView;
18161
- const box = el(doc, "div", "knobs-wt-tablebox");
18162
- const scroller = el(doc, "div", "knobs-wt-table");
18163
- const table = el(doc, "table");
19342
+ const box = el2(doc, "div", "knobs-wt-tablebox");
19343
+ const scroller = el2(doc, "div", "knobs-wt-table");
19344
+ const table = el2(doc, "table");
18164
19345
  if (m.caption)
18165
- table.append(text(doc, "caption", "", m.caption));
18166
- const head = el(doc, "thead");
18167
- const hr = el(doc, "tr");
19346
+ table.append(text2(doc, "caption", "", m.caption));
19347
+ const head = el2(doc, "thead");
19348
+ const hr = el2(doc, "tr");
18168
19349
  for (const c of m.columns) {
18169
- const th = text(doc, "th", "", c.label);
19350
+ const th = text2(doc, "th", "", c.label);
18170
19351
  th.scope = "col";
18171
19352
  th.dataset.align = c.align;
18172
19353
  hr.append(th);
18173
19354
  }
18174
19355
  head.append(hr);
18175
- const body = el(doc, "tbody");
19356
+ const body = el2(doc, "tbody");
18176
19357
  for (const r of m.rows) {
18177
- const tr = el(doc, "tr");
19358
+ const tr = el2(doc, "tr");
18178
19359
  for (const c of m.columns) {
18179
- const td = el(doc, "td");
19360
+ const td = el2(doc, "td");
18180
19361
  cellAttrs(td, c);
18181
19362
  const cell = r[c.key];
18182
19363
  if (cell) {
18183
19364
  if (cell.text.length > 40 && !c.mono && !cell.code)
18184
19365
  td.dataset.long = "";
18185
- cellContent(doc, td, cell, steps);
19366
+ cellContent(doc, td, cell);
18186
19367
  }
18187
19368
  tr.append(td);
18188
19369
  }
19370
+ rowLink(tr, rowStep(r, m.columns), steps);
18189
19371
  body.append(tr);
18190
19372
  }
18191
19373
  table.append(head, body);
18192
19374
  scroller.append(table);
18193
- const stack = el(doc, "div", "knobs-wt-tstack");
19375
+ const stack = el2(doc, "div", "knobs-wt-tstack");
18194
19376
  stack.setAttribute("role", "list");
18195
19377
  if (m.caption)
18196
19378
  stack.setAttribute("aria-label", m.caption);
18197
19379
  const primary = m.columns.find((c) => c.key === m.primary) ?? m.columns[0];
18198
19380
  for (const r of m.rows) {
18199
- const item = el(doc, "div", "knobs-wt-trow");
19381
+ const item = el2(doc, "div", "knobs-wt-trow");
18200
19382
  item.setAttribute("role", "listitem");
18201
- const title = el(doc, "div", "knobs-wt-ttitle");
19383
+ const title = el2(doc, "div", "knobs-wt-ttitle");
18202
19384
  if (primary) {
18203
19385
  cellAttrs(title, primary);
18204
19386
  const cell = r[primary.key];
18205
19387
  if (cell)
18206
- cellContent(doc, title, cell, steps);
19388
+ cellContent(doc, title, cell);
18207
19389
  }
18208
- const line = el(doc, "div", "knobs-wt-tline");
19390
+ const line = el2(doc, "div", "knobs-wt-tline");
18209
19391
  line.append(title);
18210
- const badges = el(doc, "div", "knobs-wt-tbadges");
19392
+ const badges = el2(doc, "div", "knobs-wt-tbadges");
18211
19393
  for (const c of m.columns) {
18212
19394
  const cell = r[c.key];
18213
19395
  if (c !== primary && cell?.badge)
18214
- cellContent(doc, badges, cell, steps);
19396
+ cellContent(doc, badges, cell);
18215
19397
  }
18216
19398
  if (badges.childNodes.length)
18217
19399
  line.append(badges);
18218
19400
  item.append(line);
18219
- const terms = el(doc, "dl", "knobs-wt-tterms");
19401
+ const terms = el2(doc, "dl", "knobs-wt-tterms");
18220
19402
  for (const c of m.columns) {
18221
19403
  if (c === primary)
18222
19404
  continue;
18223
19405
  const cell = r[c.key];
18224
19406
  if (!cell || cell.text === "" || cell.badge)
18225
19407
  continue;
18226
- const dd = el(doc, "dd");
19408
+ const dd = el2(doc, "dd");
18227
19409
  cellAttrs(dd, c);
18228
19410
  dd.removeAttribute("data-align");
18229
- cellContent(doc, dd, cell, steps);
18230
- terms.append(text(doc, "dt", "", c.label), dd);
19411
+ cellContent(doc, dd, cell);
19412
+ terms.append(text2(doc, "dt", "", c.label), dd);
18231
19413
  }
18232
19414
  if (terms.childNodes.length)
18233
19415
  item.append(terms);
19416
+ rowLink(item, rowStep(r, m.columns), steps);
18234
19417
  stack.append(item);
18235
19418
  }
18236
19419
  if (m.caption)
18237
- stack.append(text(doc, "p", "knobs-wt-tcaption", m.caption));
19420
+ stack.append(text2(doc, "p", "knobs-wt-tcaption", m.caption));
18238
19421
  box.append(scroller, stack);
18239
19422
  host.append(box);
18240
19423
  let natural = 0;
@@ -18288,16 +19471,16 @@ function mountTable(host, m, steps) {
18288
19471
  }
18289
19472
  function chapterPills(host, chapters, io) {
18290
19473
  const doc = host.ownerDocument;
18291
- const list = el(doc, "div", "knobs-wt-chapters");
19474
+ const list = el2(doc, "div", "knobs-wt-chapters");
18292
19475
  list.setAttribute("role", "group");
18293
19476
  list.setAttribute("aria-label", "Chapters");
18294
19477
  let at = -2;
18295
19478
  const rows = chapters.map((c, i) => {
18296
- const b = el(doc, "button", "knobs-wt-chapter");
19479
+ const b = el2(doc, "button", "knobs-wt-chapter");
18297
19480
  b.type = "button";
18298
- b.append(text(doc, "span", "knobs-wt-chapter-n", String(i + 1)));
18299
- b.append(text(doc, "span", "knobs-wt-chapter-label", c.label));
18300
- b.append(text(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
19481
+ b.append(text2(doc, "span", "knobs-wt-chapter-n", String(i + 1)));
19482
+ b.append(text2(doc, "span", "knobs-wt-chapter-label", c.label));
19483
+ b.append(text2(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
18301
19484
  b.addEventListener("click", () => {
18302
19485
  io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
18303
19486
  io.seek(c.start);
@@ -18335,15 +19518,13 @@ function disclosure(host, options) {
18335
19518
  const doc = host.ownerDocument;
18336
19519
  const win = doc.defaultView;
18337
19520
  let open = options.open === true;
18338
- const root = el(doc, "div", "knobs-wt-disc");
18339
- const toggle = el(doc, "button", "knobs-wt-disc-toggle");
19521
+ const root = el2(doc, "div", "knobs-wt-disc");
19522
+ const toggle = el2(doc, "button", "knobs-wt-disc-toggle");
18340
19523
  toggle.type = "button";
18341
19524
  toggle.innerHTML = GLYPH.caret;
18342
- toggle.append(text(doc, "span", "", options.label));
18343
- if (options.count !== undefined)
18344
- toggle.append(text(doc, "span", "knobs-wt-disc-count", String(options.count)));
18345
- const viewport = el(doc, "div", "knobs-wt-disc-body");
18346
- const content = el(doc, "div", "knobs-wt-disc-content");
19525
+ toggle.append(text2(doc, "span", "", options.label));
19526
+ const viewport = el2(doc, "div", "knobs-wt-disc-body");
19527
+ const content = el2(doc, "div", "knobs-wt-disc-content");
18347
19528
  content.append(options.content);
18348
19529
  viewport.append(content);
18349
19530
  root.append(toggle, viewport);
@@ -18355,7 +19536,9 @@ function disclosure(host, options) {
18355
19536
  const k = full > 0 ? Math.max(0, Math.min(1, h.x / full)) : open ? 1 : 0;
18356
19537
  viewport.style.height = !moving && open ? "" : `${Math.max(0, h.x).toFixed(2)}px`;
18357
19538
  viewport.toggleAttribute("data-moving", moving);
18358
- content.style.opacity = (open ? Math.min(1, k * 1.25) : Math.max(0, k * 1.5 - 0.5)).toFixed(3);
19539
+ const o = open ? Math.min(1, k * 1.25) : Math.max(0, k * 1.5 - 0.5);
19540
+ content.style.opacity = o.toFixed(3);
19541
+ content.style.filter = o >= 1 ? "" : `blur(${((1 - o) * 3).toFixed(2)}px)`;
18359
19542
  };
18360
19543
  const loop = frameLoop(win, (dt) => {
18361
19544
  h.step(dt);
@@ -18391,6 +19574,7 @@ function disclosure(host, options) {
18391
19574
  h.snap(open ? full : 0);
18392
19575
  viewport.style.height = open ? "" : "0px";
18393
19576
  content.style.opacity = open ? "1" : "0";
19577
+ content.style.filter = "";
18394
19578
  return;
18395
19579
  }
18396
19580
  if (h.resting)
@@ -18414,7 +19598,7 @@ var PR_PATHS = {
18414
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>`;
18415
19599
  var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
18416
19600
  function prChip(doc, n, pr) {
18417
- const chip = el(doc, pr.href ? "a" : "span", "knobs-wt-pr");
19601
+ const chip = el2(doc, pr.href ? "a" : "span", "knobs-wt-pr");
18418
19602
  if (pr.href) {
18419
19603
  chip.setAttribute("href", pr.href);
18420
19604
  chip.setAttribute("target", "_blank");
@@ -18423,7 +19607,7 @@ function prChip(doc, n, pr) {
18423
19607
  if (pr.state)
18424
19608
  chip.dataset.state = pr.state;
18425
19609
  chip.innerHTML = prGlyph(pr.state === "merged");
18426
- chip.append(text(doc, "span", "", `#${n}`));
19610
+ chip.append(text2(doc, "span", "", `#${n}`));
18427
19611
  const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
18428
19612
  chip.title = tip;
18429
19613
  chip.setAttribute("aria-label", `Pull request ${tip}`);
@@ -18479,6 +19663,7 @@ function linkPRs(root, find) {
18479
19663
  // src/ui/walkthrough-media.ts
18480
19664
  init_compare_model();
18481
19665
  init_glide();
19666
+ init_motion();
18482
19667
  init_icons();
18483
19668
  init_look();
18484
19669
 
@@ -18491,8 +19676,9 @@ var quick = springCss(FADE);
18491
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)";
18492
19677
  var walkthroughCss = `
18493
19678
  :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
19679
+ .knobs-ui:is(.knobs-walkthrough,.knobs-wt-slot){--k-glass:0;--k-glow:0;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms}
18494
19680
  .knobs-ui.knobs-walkthrough{--kw-snappy:${snappy3.easing};--kw-snappy-ms:${snappy3.duration}ms;--kw-calm:${calm.easing};--kw-calm-ms:${calm.duration}ms;--kw-state:${state.easing};--kw-state-ms:${state.duration}ms;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms;
18495
- --kw-measure:var(--k-walkthrough-measure,68ch);--kw-width:var(--k-walkthrough-width,960px);--kw-rail-w:220px;--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
19681
+ --kw-measure:var(--k-walkthrough-measure,68ch);--kw-width:var(--k-walkthrough-width,960px);--kw-rail-w:220px;--kw-rail-gap:calc(var(--_k-space-group) + var(--_k-space-column) / 1.5);--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
18496
19682
  --kw-card:var(--_ku-pane-bg);--kw-card-ring:var(--_ku-pane-ring);--kw-under:var(--_k-bg-page);--kw-ink:var(--k-walkthrough-ink,#ff375f);--kw-strong:var(--_k-weight-label);
18497
19683
  container:knobs-wt / inline-size;display:block;width:100%;color:var(--_k-fg);font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;text-align:left;
18498
19684
  letter-spacing:normal;-webkit-font-smoothing:antialiased}
@@ -18508,7 +19694,7 @@ var walkthroughCss = `
18508
19694
  /* ——— layout: the contents rail sticky at the left edge (right if asked), and the column centred
18509
19695
  in the room beside it, up to --kw-width: media take the column, prose stops at the measure.
18510
19696
  One column where it's narrow ——— */
18511
- .knobs-wt-layout{display:grid;grid-template-columns:var(--kw-rail-w) minmax(0,1fr);gap:var(--_k-space-column);align-items:start}
19697
+ .knobs-wt-layout{display:grid;grid-template-columns:var(--kw-rail-w) minmax(0,1fr);gap:var(--kw-rail-gap);align-items:start}
18512
19698
  .knobs-walkthrough[data-rail="right"] .knobs-wt-layout{grid-template-columns:minmax(0,1fr) var(--kw-rail-w)}
18513
19699
  .knobs-walkthrough[data-rail="right"] .knobs-wt-main{grid-column:1;grid-row:1}
18514
19700
  .knobs-walkthrough[data-rail="right"] .knobs-wt-rail{grid-column:2;grid-row:1}
@@ -18586,7 +19772,6 @@ var walkthroughCss = `
18586
19772
  .knobs-wt-feed{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0;scroll-margin-top:16px}
18587
19773
  .knobs-wt-feed-head{display:flex;align-items:baseline;gap:var(--_k-space-sibling)}
18588
19774
  .knobs-wt-feed-title{font-weight:var(--kw-strong)!important}
18589
- .knobs-wt-feed-count{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
18590
19775
  .knobs-wt-feed-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0}
18591
19776
  .knobs-wt-fi{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
18592
19777
  .knobs-wt-fi+.knobs-wt-fi::before{content:"";position:absolute;left:0;right:0;top:calc(var(--_k-space-group) / -2);height:1px;background:var(--_k-border-subtle)}
@@ -18597,10 +19782,6 @@ var walkthroughCss = `
18597
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;
18598
19783
  transition:color calc(var(--_k-hover-out) * 1ms)}
18599
19784
  .knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18600
- :where(.knobs-ui) .knobs-wt-steplink{all:revert;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin:0 0 0 2px;padding:0;vertical-align:-5px;border:0;
18601
- border-radius:calc(var(--_k-radius) / 2);background:transparent;color:var(--_k-fg-3);cursor:pointer;outline:none;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
18602
- .knobs-ui .knobs-wt-steplink:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18603
- .knobs-wt-steplink svg{width:12px;height:12px}
18604
19785
 
18605
19786
  /* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
18606
19787
  script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
@@ -18611,7 +19792,6 @@ var walkthroughCss = `
18611
19792
  .knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
18612
19793
  .knobs-wt-disc-toggle svg{flex:none;width:12px;height:12px;transition:rotate var(--kw-state-ms) var(--kw-state)}
18613
19794
  .knobs-wt-disc[data-open]>.knobs-wt-disc-toggle svg{rotate:90deg}
18614
- .knobs-wt-disc-count{color:var(--_k-fg-3)}
18615
19795
  .knobs-wt-disc-body{overflow:hidden}
18616
19796
  .knobs-wt-disc[data-open]>.knobs-wt-disc-body:not([data-moving]){overflow:visible}
18617
19797
  .knobs-wt-disc-body[data-moving]{-webkit-mask-image:linear-gradient(#000 calc(100% - 16px),transparent);mask-image:linear-gradient(#000 calc(100% - 16px),transparent)}
@@ -18791,6 +19971,10 @@ var walkthroughCss = `
18791
19971
  /* A slot's label: sentence case as written, small and faint, never a heading. */
18792
19972
  .knobs-wt-slot-label{display:block;min-width:0;overflow:hidden;white-space:nowrap;font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);letter-spacing:normal;text-transform:none;color:var(--_k-fg-3);${fade2}}
18793
19973
  .knobs-wt-body{min-width:0}
19974
+ /* Media mounting after the first paint, and a picture still decoding, come into focus: a fade with
19975
+ a small blur on the quick spring, from the top; at rest, nothing. */
19976
+ .knobs-wt-body,.knobs-wt-frame>img{transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
19977
+ .knobs-wt-body[data-entering],.knobs-wt-frame[data-loading]>img{opacity:0;filter:blur(3px);transition:none}
18794
19978
  /* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
18795
19979
  slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
18796
19980
  .knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
@@ -18805,9 +19989,12 @@ var walkthroughCss = `
18805
19989
  .knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
18806
19990
  .knobs-wt-frame[data-natural]{aspect-ratio:auto;background:transparent}
18807
19991
  .knobs-wt-frame[data-natural]>img{position:static;height:auto}
18808
- .knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--kw-frame-r);background:var(--_k-fill);color:var(--_k-fg);
19992
+ /* Diff, code and terminal: one plain container (no glass): a flat inset fill, a hairline ring, the
19993
+ 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);
18809
19996
  font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
18810
- .knobs-surface .knobs-wt-code{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)}
19997
+ .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)}
18811
19998
  .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
18812
19999
  .knobs-wt-prompt{color:var(--_k-fg-3);user-select:none;-webkit-user-select:none}
18813
20000
  .knobs-wt-cmd{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
@@ -18834,48 +20021,37 @@ var walkthroughCss = `
18834
20021
  .knobs-wt-hold{border-radius:var(--kw-frame-r);background:var(--_k-fill)}
18835
20022
  .knobs-wt-failed{display:flex;align-items:center;justify-content:center;min-height:80px;color:var(--_k-fg-3);font-size:var(--_k-type-small)}
18836
20023
 
20024
+ /* Annotations: a calm ring round a box, a dot or an arrow at a point, a small label beside it;
20025
+ each fades in and out with a small blur on the quick spring as its time comes and goes, and a
20026
+ page's switch (the eye, or a) hides them all. Placed in px by script, 1:1 with the picture;
20027
+ last in their box, so they sit over the picture (and under a compare's grip and labels). */
20028
+ .knobs-wt-anno{position:absolute;inset:0;pointer-events:none;overflow:hidden;transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
20029
+ .knobs-wt-anno:not([data-settled]),.knobs-wt-anno:not([data-settled]) *{transition:none!important}
20030
+ [data-annotations="off"] .knobs-wt-anno,.knobs-wt-anno[data-moving]{opacity:0;filter:blur(3px)}
20031
+ .knobs-wt-mark{--kw-mark:var(--_k-accent);position:absolute;left:0;top:0;color:var(--kw-mark);transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
20032
+ .knobs-wt-mark[data-hidden]{opacity:0;filter:blur(3px)}
20033
+ .knobs-wt-mark[data-shape="box"]{border-radius:6px;box-shadow:0 0 0 1.5px var(--kw-mark),0 0 0 4px color-mix(in srgb,var(--_k-bg) 45%,transparent)}
20034
+ .knobs-wt-mark[data-shape="dot"]::before{content:"";position:absolute;left:-6px;top:-6px;width:12px;height:12px;border-radius:50%;background:var(--kw-mark);box-shadow:0 0 0 3px color-mix(in srgb,var(--kw-mark) 30%,transparent)}
20035
+ .knobs-wt-anno-arrow{position:absolute;right:0;bottom:0;filter:drop-shadow(0 0 2px var(--_k-bg))}
20036
+ .knobs-wt-mark-label{position:absolute;left:0;bottom:calc(100% + 6px);max-width:240px;padding:2px 6px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-bg-overlay);color:var(--_k-fg);
20037
+ font:var(--_k-weight-label) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);white-space:nowrap;overflow:hidden;box-shadow:var(--_k-shadow-overlay);${fade2}}
20038
+ .knobs-wt-mark:is([data-shape="dot"],[data-shape="arrow"]) .knobs-wt-mark-label{left:12px;bottom:auto;top:10px}
20039
+ .knobs-wt-mark[data-shape="arrow"] .knobs-wt-mark-label{left:auto;right:30px;top:auto;bottom:30px}
20040
+ .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;
20042
+ transition:opacity calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
20043
+ .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
+ .knobs-ui .knobs-wt-anno-toggle:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
20045
+ .knobs-cmp-stage>.knobs-wt-anno-toggle{top:auto;bottom:8px}
20046
+
18837
20047
  /* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
18838
20048
  hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
18839
- :where(.knobs-wt-history,.knobs-wt-stack,.knobs-wt-tablebox,.knobs-wt-chapters){--kw-line:var(--_k-leading-body);--kw-rail:var(--_k-border-strong)}
20049
+ :where(.knobs-wt-stack,.knobs-wt-tablebox,.knobs-wt-chapters){--kw-line:var(--_k-leading-body);--kw-rail:var(--_k-border-strong)}
18840
20050
  :where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
18841
20051
  :where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
18842
20052
  :where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
18843
20053
  :where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
18844
20054
  :where(.knobs-wt-slot) [data-tone="dim"]{--kw-tone:var(--_k-fg-3)}
18845
- .knobs-wt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
18846
- .knobs-ui .knobs-wt-tag{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:5px;background:var(--_k-fill);color:var(--_k-fg-2);font:var(--_k-weight) var(--_k-type-small)/1 var(--_k-mono);
18847
- text-decoration:none;white-space:nowrap;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
18848
- .knobs-ui a.knobs-wt-tag:is(:hover,:focus-visible){background:var(--_k-fill-selected);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18849
-
18850
- /* A history: dates down the left, a spine of tone dots joined by a hairline (broken around each
18851
- dot), the event beside it. Inferred events take a ring for a dot and a dashed tag. */
18852
- .knobs-wt-history{--kw-hy:calc(5px + var(--kw-line) / 2);display:flex;flex-direction:column;gap:18px;font-size:var(--_k-type-body);line-height:var(--kw-line);color:var(--_k-fg)}
18853
- .knobs-wt-hgroup{display:flex;flex-direction:column;gap:6px;min-width:0}
18854
- .knobs-wt-hhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px}
18855
- .knobs-ui .knobs-wt-hheading{margin:0;font:var(--_k-weight-label) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);letter-spacing:.04em;text-transform:uppercase;color:var(--_k-fg-2)}
18856
- .knobs-wt-hsum{color:var(--_k-fg-3);font-size:var(--_k-type-small);font-variant-numeric:tabular-nums}
18857
- .knobs-wt-hlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
18858
- .knobs-wt-hev{display:grid;grid-template-columns:var(--kw-hw,11ch) 14px minmax(0,1fr);grid-template-areas:"when spine main";column-gap:10px;align-items:baseline;padding:5px 0}
18859
- .knobs-wt-hwhen{grid-area:when;display:flex;flex-direction:column;min-width:0;font:var(--_k-weight) var(--_k-type-label)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-2)}
18860
- .knobs-wt-hage{color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
18861
- .knobs-wt-hspine{grid-area:spine;position:relative;align-self:stretch;margin-block:-5px}
18862
- .knobs-wt-hspine::before,.knobs-wt-hspine::after{content:"";position:absolute;left:6.5px;width:1px;background:var(--kw-rail)}
18863
- .knobs-wt-hspine::before{top:0;height:calc(var(--kw-hy) - 6px)}
18864
- .knobs-wt-hspine::after{top:calc(var(--kw-hy) + 6px);bottom:0}
18865
- .knobs-wt-hev:first-child .knobs-wt-hspine::before,.knobs-wt-hev:last-child .knobs-wt-hspine::after{content:none}
18866
- .knobs-wt-hdot{position:absolute;left:3.5px;top:calc(var(--kw-hy) - 3.5px);width:7px;height:7px;border-radius:50%;background:var(--kw-tone,var(--_k-fg-3))}
18867
- .knobs-wt-hev[data-type="release"] .knobs-wt-hdot{border-radius:1.5px;rotate:45deg;scale:.9}
18868
- .knobs-wt-hev[data-type="audit"] .knobs-wt-hdot{border-radius:1.5px}
18869
- .knobs-wt-hev[data-provenance="inferred"] .knobs-wt-hdot{background:transparent;box-shadow:inset 0 0 0 1.5px var(--kw-tone,var(--_k-fg-3))}
18870
- .knobs-wt-hmain{grid-area:main;display:flex;flex-direction:column;gap:2px;min-width:0}
18871
- .knobs-wt-htitle{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
18872
- .knobs-wt-prov{padding:0 6px;border-radius:5px;background:var(--_k-fill);color:var(--_k-fg-3);font:var(--_k-weight) var(--_k-type-small)/18px var(--_k-mono);white-space:nowrap}
18873
- .knobs-wt-hev[data-provenance="inferred"] .knobs-wt-prov{background:transparent;color:var(--_k-warn);outline:1px dashed color-mix(in oklch,var(--_k-warn) 50%,transparent);outline-offset:-1px}
18874
- .knobs-ui .knobs-wt-hdetail{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
18875
- @container knobs-wt-slot (max-width:520px){
18876
- .knobs-wt-hev{grid-template-columns:14px minmax(0,1fr);grid-template-areas:"spine when" "spine main"}
18877
- .knobs-wt-hwhen{flex-direction:row;flex-wrap:wrap;align-items:baseline;column-gap:8px}
18878
- }
18879
20055
 
18880
20056
  /* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
18881
20057
  git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
@@ -18920,6 +20096,12 @@ var walkthroughCss = `
18920
20096
  /* Too narrow for its columns, each row becomes a block: its primary cell titling the rest as term
18921
20097
  and value, the blocks parted by hairlines. Scrolling sideways is the last resort (stack: never). */
18922
20098
  .knobs-wt-tablebox{min-width:0}
20099
+ /* A row with a step goes there: the whole row, a wash under the pointer, no arrow, so rows with and
20100
+ without one line up. */
20101
+ .knobs-wt-tablebox [data-go]{cursor:pointer;outline:none}
20102
+ .knobs-wt-tablebox tr[data-go]>td,.knobs-wt-trow[data-go]{transition:background calc(var(--_k-hover-out) * 1ms)}
20103
+ .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
+ .knobs-wt-tablebox [data-go]:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
18923
20105
  .knobs-wt-tablebox[data-measure] table{width:min-content}
18924
20106
  .knobs-wt-tstack{display:none;flex-direction:column;min-width:0}
18925
20107
  .knobs-wt-tablebox[data-stacked]>.knobs-wt-table{display:none}
@@ -19091,6 +20273,8 @@ async function mountCompare(host, m, look, alive, onEvent) {
19091
20273
  fit.style.setProperty("--kw-aspect", String(aspect));
19092
20274
  let lastZoom = "all";
19093
20275
  let lastLoop = "off";
20276
+ let marks = null;
20277
+ let marking = null;
19094
20278
  const c = compare(fit, {
19095
20279
  ...look,
19096
20280
  before: side(before, "Before"),
@@ -19103,6 +20287,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
19103
20287
  onMode: (next) => {
19104
20288
  fit.toggleAttribute("data-overlay", next !== "side");
19105
20289
  onEvent?.({ what: "mode", detail: next });
20290
+ const layer = marks?.element;
20291
+ if (layer) {
20292
+ layer.dataset.moving = "";
20293
+ setTimeout(() => {
20294
+ delete layer.dataset.moving;
20295
+ marks?.update();
20296
+ }, MARK_GLIDE);
20297
+ }
19106
20298
  },
19107
20299
  onView: (view) => {
19108
20300
  const z = view.zoom;
@@ -19113,8 +20305,23 @@ async function mountCompare(host, m, look, alive, onEvent) {
19113
20305
  const loopKey = l ? `${formatTime(l.start)}–${formatTime(l.end)}` : "off";
19114
20306
  if (loopKey !== lastLoop)
19115
20307
  onEvent?.({ what: "loop", detail: lastLoop = loopKey });
20308
+ marking?.kick(MARK_GLIDE);
19116
20309
  }
19117
20310
  });
20311
+ const stage = fit.querySelector(".knobs-cmp-stage");
20312
+ if (m.annotations?.length && stage) {
20313
+ const shape = { w: aspect ?? DEFAULT_ASPECT, h: 1 };
20314
+ marks = annotationLayer(stage, m.annotations, () => {
20315
+ const l = compareLayout({ x: 0, y: 0, w: stage.clientWidth, h: stage.clientHeight }, shape, shape);
20316
+ return {
20317
+ rects: c.mode === "side" ? [l.side.before, l.side.after] : [l.overlay.after],
20318
+ view: c.zoom,
20319
+ time: c.time
20320
+ };
20321
+ });
20322
+ const layer = marks;
20323
+ marking = follow(doc.defaultView, () => c.playing, () => layer.update());
20324
+ }
19118
20325
  const chapters = m.chapters?.length ? m.chapters : null;
19119
20326
  if (chapters)
19120
20327
  c.timeline?.update({
@@ -19131,10 +20338,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
19131
20338
  const unlisten = c.timeline?.listen({
19132
20339
  onSeek: (time, source) => {
19133
20340
  pills?.sync();
20341
+ marking?.kick();
19134
20342
  if (source !== "play")
19135
20343
  onEvent?.({ what: "seek", time: round2(time) });
19136
20344
  },
19137
- onPlay: (on) => onEvent?.({ what: on ? "play" : "pause", time: round2(c.time) })
20345
+ onPlay: (on) => {
20346
+ marking?.kick();
20347
+ onEvent?.({ what: on ? "play" : "pause", time: round2(c.time) });
20348
+ }
19138
20349
  });
19139
20350
  requestAnimationFrame(() => requestAnimationFrame(() => fit.dataset.settled = ""));
19140
20351
  return {
@@ -19161,6 +20372,8 @@ async function mountCompare(host, m, look, alive, onEvent) {
19161
20372
  };
19162
20373
  },
19163
20374
  dispose: () => {
20375
+ marking?.stop();
20376
+ marks?.dispose();
19164
20377
  unlisten?.();
19165
20378
  pills?.dispose();
19166
20379
  c.dispose();
@@ -19169,6 +20382,7 @@ async function mountCompare(host, m, look, alive, onEvent) {
19169
20382
  };
19170
20383
  }
19171
20384
  var round2 = (v) => Math.round(v * 100) / 100;
20385
+ var MARK_GLIDE = 450;
19172
20386
  var zoomedIn = (z) => z.w < 0.999 || z.h < 0.999;
19173
20387
  function mountVideo(host, m, onEvent) {
19174
20388
  const doc = host.ownerDocument;
@@ -19216,6 +20430,16 @@ function mountVideo(host, m, onEvent) {
19216
20430
  if (pills)
19217
20431
  for (const type of ["timeupdate", "seeking", "seeked"])
19218
20432
  v.addEventListener(type, sync);
20433
+ const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
20434
+ rects: [
20435
+ contain({ w: frame.clientWidth, h: frame.clientHeight }, { w: v.videoWidth, h: v.videoHeight })
20436
+ ],
20437
+ time: v.currentTime
20438
+ })) : 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());
19219
20443
  return {
19220
20444
  pause: () => v.pause(),
19221
20445
  seekBy: (d) => {
@@ -19229,6 +20453,8 @@ function mountVideo(host, m, onEvent) {
19229
20453
  ...pills?.current ? { chapter: pills.current.label } : {}
19230
20454
  }),
19231
20455
  dispose: () => {
20456
+ marking?.stop();
20457
+ marks?.dispose();
19232
20458
  pills?.dispose();
19233
20459
  v.pause();
19234
20460
  v.removeAttribute("src");
@@ -19246,9 +20472,52 @@ function mountImage(host, m) {
19246
20472
  img.decoding = "async";
19247
20473
  img.alt = m.alt ?? m.label ?? "";
19248
20474
  img.src = m.src;
20475
+ if (!img.complete) {
20476
+ frame.dataset.loading = "";
20477
+ img.addEventListener("load", () => delete frame.dataset.loading, { once: true });
20478
+ img.addEventListener("error", () => delete frame.dataset.loading, { once: true });
20479
+ }
19249
20480
  frame.append(img);
19250
20481
  host.append(frame);
19251
- return { dispose: () => frame.remove() };
20482
+ const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
20483
+ rects: [{ x: 0, y: 0, w: frame.clientWidth, h: frame.clientHeight }]
20484
+ })) : null;
20485
+ if (marks)
20486
+ img.addEventListener("load", () => marks.update(), { once: true });
20487
+ return {
20488
+ dispose: () => {
20489
+ marks?.dispose();
20490
+ frame.remove();
20491
+ }
20492
+ };
20493
+ }
20494
+ function contain(box, natural) {
20495
+ if (!(natural.w > 0 && natural.h > 0))
20496
+ return { x: 0, y: 0, w: box.w, h: box.h };
20497
+ const k = Math.min(box.w / natural.w, box.h / natural.h);
20498
+ const w = natural.w * k;
20499
+ const h = natural.h * k;
20500
+ return { x: (box.w - w) / 2, y: (box.h - h) / 2, w, h };
20501
+ }
20502
+ function follow(win, on, fn) {
20503
+ let raf = 0;
20504
+ let until = 0;
20505
+ const tick = () => {
20506
+ fn();
20507
+ raf = on() || performance.now() < until ? win.requestAnimationFrame(tick) : 0;
20508
+ };
20509
+ return {
20510
+ kick(ms = 0) {
20511
+ until = Math.max(until, performance.now() + ms);
20512
+ if (!raf)
20513
+ raf = win.requestAnimationFrame(tick);
20514
+ },
20515
+ stop() {
20516
+ if (raf)
20517
+ win.cancelAnimationFrame(raf);
20518
+ raf = 0;
20519
+ }
20520
+ };
19252
20521
  }
19253
20522
  async function mountDiff(host, m, look, alive, onEvent) {
19254
20523
  const lib = await Promise.resolve().then(() => (init_diff(), {}));
@@ -19402,6 +20671,14 @@ function mediaSlot(container, options) {
19402
20671
  let pending = false;
19403
20672
  let disposed = false;
19404
20673
  let generation = 0;
20674
+ const born = performance.now();
20675
+ const enter = () => {
20676
+ const win = doc.defaultView;
20677
+ if (!win || performance.now() - born < 120 || reducedMotion(win))
20678
+ return;
20679
+ host.dataset.entering = "";
20680
+ win.requestAnimationFrame(() => win.requestAnimationFrame(() => delete host.dataset.entering));
20681
+ };
19405
20682
  const hold = (px) => {
19406
20683
  host.style.minHeight = px === null ? "" : `${px}px`;
19407
20684
  host.classList.toggle("knobs-wt-hold", px !== null);
@@ -19421,6 +20698,7 @@ function mediaSlot(container, options) {
19421
20698
  if (!alive())
19422
20699
  return r.dispose();
19423
20700
  mounted = r;
20701
+ enter();
19424
20702
  if (filled)
19425
20703
  r.fill?.(true);
19426
20704
  if (r.ready)
@@ -19513,14 +20791,14 @@ function mediaSlot(container, options) {
19513
20791
  var CHECK3 = icon("check", { small: true });
19514
20792
 
19515
20793
  // src/ui/walkthrough.ts
19516
- var el2 = (doc, tag, cls) => {
20794
+ var el3 = (doc, tag, cls) => {
19517
20795
  const n = doc.createElement(tag);
19518
20796
  if (cls)
19519
20797
  n.className = cls;
19520
20798
  return n;
19521
20799
  };
19522
20800
  var prose2 = (doc, tag, cls, text) => {
19523
- const n = el2(doc, tag, cls);
20801
+ const n = el3(doc, tag, cls);
19524
20802
  n.innerHTML = inlineMarkdown(text);
19525
20803
  return n;
19526
20804
  };
@@ -19559,7 +20837,7 @@ function tasteTest(container, options) {
19559
20837
  });
19560
20838
  const pickable = options.pickable !== false;
19561
20839
  let picked = pickable ? options.picked ?? null : null;
19562
- const root = el2(doc, "div", "knobs-wt-options");
20840
+ const root = el3(doc, "div", "knobs-wt-options");
19563
20841
  if (pickable) {
19564
20842
  root.setAttribute("role", "radiogroup");
19565
20843
  root.setAttribute("aria-label", "Options");
@@ -19570,33 +20848,33 @@ function tasteTest(container, options) {
19570
20848
  applyLook2(root, options);
19571
20849
  const slots = [];
19572
20850
  const tiles = list.map((o, i) => {
19573
- const tile = el2(doc, "div", "knobs-wt-option");
20851
+ const tile = el3(doc, "div", "knobs-wt-option");
19574
20852
  if (!pickable) {
19575
- const name = el2(doc, "span", "knobs-wt-slot-label");
20853
+ const name = el3(doc, "span", "knobs-wt-slot-label");
19576
20854
  name.textContent = o.label;
19577
20855
  tile.append(name);
19578
20856
  }
19579
- const pick = el2(doc, "button", "knobs-wt-pick");
20857
+ const pick = el3(doc, "button", "knobs-wt-pick");
19580
20858
  pick.type = "button";
19581
20859
  pick.setAttribute("role", "radio");
19582
20860
  if (i < 9)
19583
20861
  pick.setAttribute("aria-keyshortcuts", String(i + 1));
19584
- const letter = el2(doc, "span", "knobs-wt-letter");
20862
+ const letter = el3(doc, "span", "knobs-wt-letter");
19585
20863
  letter.textContent = o.letter;
19586
- const name = el2(doc, "span", "knobs-wt-olabel");
20864
+ const name = el3(doc, "span", "knobs-wt-olabel");
19587
20865
  name.textContent = o.label;
19588
20866
  pick.append(letter, name);
19589
20867
  if (o.suggested) {
19590
- const s = el2(doc, "span", "knobs-wt-suggested");
20868
+ const s = el3(doc, "span", "knobs-wt-suggested");
19591
20869
  s.textContent = "Suggested";
19592
20870
  pick.append(s);
19593
20871
  }
19594
20872
  if (i < 9) {
19595
- const key = el2(doc, "span", "knobs-wt-key");
20873
+ const key = el3(doc, "span", "knobs-wt-key");
19596
20874
  key.textContent = String(i + 1);
19597
20875
  pick.append(key);
19598
20876
  }
19599
- const check = el2(doc, "span", "knobs-wt-check");
20877
+ const check = el3(doc, "span", "knobs-wt-check");
19600
20878
  check.innerHTML = CHECK3;
19601
20879
  pick.append(check);
19602
20880
  pick.addEventListener("click", () => {
@@ -19662,7 +20940,7 @@ function walkthrough(container, options) {
19662
20940
  injectWalkthroughStyles(doc);
19663
20941
  const look = lookOf2(options);
19664
20942
  const storage = options.storage === undefined ? browserStorage() : options.storage;
19665
- const root = el2(doc, "div", "knobs-walkthrough");
20943
+ const root = el3(doc, "div", "knobs-walkthrough");
19666
20944
  applyLook2(root, options);
19667
20945
  container.append(root);
19668
20946
  let page = normalizeWalkthrough(options.doc);
@@ -19875,7 +21153,7 @@ function walkthrough(container, options) {
19875
21153
  return true;
19876
21154
  return false;
19877
21155
  };
19878
- const scrim = el2(doc, "div", "knobs-wt-scrim");
21156
+ const scrim = el3(doc, "div", "knobs-wt-scrim");
19879
21157
  scrim.addEventListener("click", () => setFull(false));
19880
21158
  const fullRect = () => ({
19881
21159
  x: DOCK_INSET,
@@ -19925,7 +21203,7 @@ function walkthrough(container, options) {
19925
21203
  }
19926
21204
  if (!full) {
19927
21205
  const r = v.card.getBoundingClientRect();
19928
- const hold = el2(doc, "div", "knobs-wt-hold-card");
21206
+ const hold = el3(doc, "div", "knobs-wt-hold-card");
19929
21207
  hold.style.height = `${r.height}px`;
19930
21208
  v.card.before(hold);
19931
21209
  v.card.dataset.full = "";
@@ -20075,7 +21353,7 @@ function walkthrough(container, options) {
20075
21353
  toast("Couldn't reach the microphone", { tone: "warn", look, doc });
20076
21354
  }
20077
21355
  };
20078
- const backPill = el2(doc, "button", "knobs-wt-back");
21356
+ const backPill = el3(doc, "button", "knobs-wt-back");
20079
21357
  backPill.type = "button";
20080
21358
  let backTo = null;
20081
21359
  let backTimer = 0;
@@ -20095,7 +21373,7 @@ function walkthrough(container, options) {
20095
21373
  return;
20096
21374
  backTo = key;
20097
21375
  backPill.innerHTML = ICON.back;
20098
- const words = el2(doc, "span", "knobs-wt-back-label");
21376
+ const words = el3(doc, "span", "knobs-wt-back-label");
20099
21377
  words.textContent = `Back to ${plainMarkdown(v.step.title)}`;
20100
21378
  backPill.append(words);
20101
21379
  backPill.dataset.on = "";
@@ -20103,16 +21381,16 @@ function walkthrough(container, options) {
20103
21381
  backTimer = win.setTimeout(hideBack, BACK_MS);
20104
21382
  };
20105
21383
  const bar = (() => {
20106
- const dock = el2(doc, "div", "knobs-wt-dock");
20107
- const box = el2(doc, "div", "knobs-wt-notebar");
20108
- const on = el2(doc, "div", "knobs-wt-on");
20109
- const row = el2(doc, "div", "knobs-wt-row");
20110
- const input = el2(doc, "textarea", "knobs-wt-input");
21384
+ const dock = el3(doc, "div", "knobs-wt-dock");
21385
+ const box = el3(doc, "div", "knobs-wt-notebar");
21386
+ const on = el3(doc, "div", "knobs-wt-on");
21387
+ const row = el3(doc, "div", "knobs-wt-row");
21388
+ const input = el3(doc, "textarea", "knobs-wt-input");
20111
21389
  input.rows = 1;
20112
21390
  input.setAttribute("aria-label", "Note");
20113
- const attach = el2(doc, "div", "knobs-wt-attach");
21391
+ const attach = el3(doc, "div", "knobs-wt-attach");
20114
21392
  const btn = (icon, label, run) => {
20115
- const b = el2(doc, "button", "knobs-wt-tool");
21393
+ const b = el3(doc, "button", "knobs-wt-tool");
20116
21394
  b.type = "button";
20117
21395
  b.innerHTML = icon;
20118
21396
  b.setAttribute("aria-label", label);
@@ -20123,17 +21401,17 @@ function walkthrough(container, options) {
20123
21401
  const fullBtn = btn(ICON.full, "Full window · f", () => setFull(!full));
20124
21402
  const penBtn = btn(ICON.pen, "Draw on the media", () => setInk(!ink.on));
20125
21403
  const micBtn = btn(ICON.mic, "Record a voice note", () => void toggleVoice());
20126
- const micTime = el2(doc, "span", "knobs-wt-rec");
21404
+ const micTime = el3(doc, "span", "knobs-wt-rec");
20127
21405
  micBtn.append(micTime);
20128
21406
  const sendBtn = btn(ICON.send, "Send · Enter", () => send());
20129
21407
  sendBtn.dataset.send = "";
20130
21408
  row.append(input, attach, fullBtn, penBtn, micBtn, sendBtn);
20131
21409
  box.append(on, row);
20132
- const launch = el2(doc, "button", "knobs-wt-launch");
21410
+ const launch = el3(doc, "button", "knobs-wt-launch");
20133
21411
  launch.type = "button";
20134
21412
  launch.innerHTML = ICON.pen;
20135
21413
  launch.append("Note");
20136
- const kbd = el2(doc, "kbd");
21414
+ const kbd = el3(doc, "kbd");
20137
21415
  kbd.textContent = "n";
20138
21416
  launch.append(kbd);
20139
21417
  launch.setAttribute("aria-label", "Write a note (n)");
@@ -20185,9 +21463,9 @@ function walkthrough(container, options) {
20185
21463
  }
20186
21464
  });
20187
21465
  const chip = (text, clear) => {
20188
- const c = el2(doc, "span", "knobs-wt-chip-a");
21466
+ const c = el3(doc, "span", "knobs-wt-chip-a");
20189
21467
  c.append(text);
20190
- const x = el2(doc, "button", "knobs-wt-x");
21468
+ const x = el3(doc, "button", "knobs-wt-x");
20191
21469
  x.type = "button";
20192
21470
  x.innerHTML = ICON.close;
20193
21471
  x.setAttribute("aria-label", `Remove ${text}`);
@@ -20336,9 +21614,9 @@ function walkthrough(container, options) {
20336
21614
  }
20337
21615
  };
20338
21616
  const renderNote = (v, n, entering = false) => {
20339
- const row = el2(doc, "div", "knobs-wt-n");
21617
+ const row = el3(doc, "div", "knobs-wt-n");
20340
21618
  row.dataset.id = n.id;
20341
- const meta = el2(doc, "span", "knobs-wt-n-meta");
21619
+ const meta = el3(doc, "span", "knobs-wt-n-meta");
20342
21620
  const m = n.context.media;
20343
21621
  meta.textContent = m?.time !== undefined ? formatTime(m.time) : m?.file ? m.file.split("/").pop() : "·";
20344
21622
  if (m?.time !== undefined) {
@@ -20354,11 +21632,11 @@ function walkthrough(container, options) {
20354
21632
  });
20355
21633
  }
20356
21634
  row.append(meta);
20357
- const body = el2(doc, "div", "knobs-wt-n-body");
21635
+ const body = el3(doc, "div", "knobs-wt-n-body");
20358
21636
  if (n.text)
20359
21637
  body.append(prose2(doc, "p", "knobs-wt-n-text", n.text));
20360
21638
  if (n.voice) {
20361
- const a = el2(doc, "audio", "knobs-wt-n-voice");
21639
+ const a = el3(doc, "audio", "knobs-wt-n-voice");
20362
21640
  a.controls = true;
20363
21641
  a.preload = "none";
20364
21642
  a.src = n.voice.url;
@@ -20378,7 +21656,7 @@ function walkthrough(container, options) {
20378
21656
  body.append(thumb);
20379
21657
  }
20380
21658
  row.append(body);
20381
- const x = el2(doc, "button", "knobs-wt-x");
21659
+ const x = el3(doc, "button", "knobs-wt-x");
20382
21660
  x.type = "button";
20383
21661
  x.innerHTML = ICON.close;
20384
21662
  x.setAttribute("aria-label", "Delete this note");
@@ -20418,9 +21696,9 @@ function walkthrough(container, options) {
20418
21696
  row.addEventListener("transitionend", done, { once: true });
20419
21697
  win.setTimeout(done, 600);
20420
21698
  };
20421
- const metaLine = (cls) => el2(doc, "div", `knobs-wt-metaline ${cls}`);
21699
+ const metaLine = (cls) => el3(doc, "div", `knobs-wt-metaline ${cls}`);
20422
21700
  const metaText = (line, text, cls) => {
20423
- const s = el2(doc, "span", cls);
21701
+ const s = el3(doc, "span", cls);
20424
21702
  s.textContent = text;
20425
21703
  line.append(s);
20426
21704
  return s;
@@ -20433,7 +21711,7 @@ function walkthrough(container, options) {
20433
21711
  line.append(chip);
20434
21712
  return chip;
20435
21713
  }
20436
- const a = el2(doc, "a");
21714
+ const a = el3(doc, "a");
20437
21715
  a.href = href;
20438
21716
  a.target = "_blank";
20439
21717
  a.rel = "noreferrer";
@@ -20443,58 +21721,58 @@ function walkthrough(container, options) {
20443
21721
  return a;
20444
21722
  };
20445
21723
  const feedItem = (f, now) => {
20446
- const li = el2(doc, "li", "knobs-wt-fi");
21724
+ const li = el3(doc, "li", "knobs-wt-fi");
20447
21725
  li.dataset.kind = f.kind;
20448
- const q = el2(doc, "div", "knobs-wt-fi-q");
21726
+ const q = el3(doc, "div", "knobs-wt-fi-q");
20449
21727
  const meta = metaLine("knobs-wt-fi-meta");
20450
21728
  if (f.at !== null) {
20451
21729
  const when = metaText(meta, ago(f.at, now));
20452
21730
  when.title = new Date(f.at).toLocaleString();
20453
- } else
20454
- metaText(meta, f.kind === "qa" ? "Answer" : "Update");
21731
+ }
20455
21732
  const step = f.step ? page.steps.find((s) => s.key === f.step) : undefined;
20456
21733
  if (step) {
20457
21734
  const section = page.sections.find((s) => s.id === step.section);
20458
- const b = el2(doc, "button", "knobs-wt-fi-step");
21735
+ const b = el3(doc, "button", "knobs-wt-fi-step");
20459
21736
  b.type = "button";
20460
21737
  b.textContent = section?.link?.label ?? plainMarkdown(step.title);
20461
21738
  b.title = `Go to “${plainMarkdown(step.title)}”`;
20462
21739
  b.addEventListener("click", () => go(step.key));
20463
21740
  meta.append(b);
20464
21741
  }
20465
- if (f.status) {
21742
+ if (f.status && review) {
20466
21743
  const id = f.status.toLowerCase();
20467
21744
  const shipped = /^(ship|shipped|merged|done)$/.test(id);
20468
21745
  const changes = /^(changes?|changes queued)$/.test(id);
20469
21746
  const s = metaText(meta, shipped ? "Shipped" : changes ? "Changes queued" : f.status);
20470
21747
  s.dataset.status = shipped ? "on" : changes ? "warn" : "";
20471
21748
  }
20472
- q.append(meta);
21749
+ if (meta.childNodes.length)
21750
+ q.append(meta);
20473
21751
  if (f.q)
20474
21752
  q.append(prose2(doc, "p", "knobs-wt-fi-ask", f.q));
20475
21753
  else
20476
21754
  q.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
20477
21755
  li.append(q);
20478
21756
  if (f.q || f.trail.length) {
20479
- const a = el2(doc, "div", "knobs-wt-fi-a");
21757
+ const a = el3(doc, "div", "knobs-wt-fi-a");
20480
21758
  if (f.q)
20481
21759
  a.append(prose2(doc, "div", "knobs-wt-fi-answer", f.text));
20482
21760
  if (f.trail.length) {
20483
- const list = el2(doc, "ol", "knobs-wt-trail");
21761
+ const list = el3(doc, "ol", "knobs-wt-trail");
20484
21762
  for (const line of f.trail) {
20485
21763
  const i = line.indexOf(" · ");
20486
- const row = el2(doc, "li");
20487
- const words = el2(doc, "span", "knobs-wt-trail-text");
21764
+ const row = el3(doc, "li");
21765
+ const words = el3(doc, "span", "knobs-wt-trail-text");
20488
21766
  words.textContent = i < 0 ? line : line.slice(i + 3);
20489
21767
  row.append(words);
20490
21768
  if (i >= 0) {
20491
- const at = el2(doc, "span", "knobs-wt-trail-at");
21769
+ const at = el3(doc, "span", "knobs-wt-trail-at");
20492
21770
  at.textContent = line.slice(0, i);
20493
21771
  row.append(at);
20494
21772
  }
20495
21773
  list.append(row);
20496
21774
  }
20497
- discs.push(disclosure(a, { label: "Context", count: f.trail.length, content: list }));
21775
+ discs.push(disclosure(a, { label: "Context", content: list }));
20498
21776
  }
20499
21777
  li.append(a);
20500
21778
  }
@@ -20522,11 +21800,11 @@ function walkthrough(container, options) {
20522
21800
  root.dataset.rail = options.rail === false ? "none" : options.rail === "right" ? "right" : "left";
20523
21801
  root.toggleAttribute("data-cards", options.cards === true);
20524
21802
  root.toggleAttribute("data-review", review);
20525
- const layout = el2(doc, "div", "knobs-wt-layout");
20526
- const main = el2(doc, "div", "knobs-wt-main");
20527
- const flow = el2(doc, "div", "knobs-wt-flow");
20528
- const head = el2(doc, "header", "knobs-wt-head");
20529
- const h1 = el2(doc, "h1", "knobs-wt-title");
21803
+ const layout = el3(doc, "div", "knobs-wt-layout");
21804
+ const main = el3(doc, "div", "knobs-wt-main");
21805
+ const flow = el3(doc, "div", "knobs-wt-flow");
21806
+ const head = el3(doc, "header", "knobs-wt-head");
21807
+ const h1 = el3(doc, "h1", "knobs-wt-title");
20530
21808
  h1.textContent = page.title;
20531
21809
  head.append(h1);
20532
21810
  if (page.summary)
@@ -20534,13 +21812,13 @@ function walkthrough(container, options) {
20534
21812
  if (page.more)
20535
21813
  head.append(more(page.more));
20536
21814
  if (page.meta.length) {
20537
- const meta = el2(doc, "div", "knobs-wt-meta");
21815
+ const meta = el3(doc, "div", "knobs-wt-meta");
20538
21816
  for (const m of page.meta) {
20539
- const s = el2(doc, "span");
21817
+ const s = el3(doc, "span");
20540
21818
  s.append(m.label);
20541
- const b = el2(doc, "b");
21819
+ const b = el3(doc, "b");
20542
21820
  if (m.href) {
20543
- const a = el2(doc, "a");
21821
+ const a = el3(doc, "a");
20544
21822
  a.href = m.href;
20545
21823
  a.target = "_blank";
20546
21824
  a.rel = "noreferrer";
@@ -20556,16 +21834,14 @@ function walkthrough(container, options) {
20556
21834
  flow.append(head);
20557
21835
  let feedEl = null;
20558
21836
  if (page.feed.length) {
20559
- feedEl = el2(doc, "section", "knobs-wt-feed");
21837
+ feedEl = el3(doc, "section", "knobs-wt-feed");
20560
21838
  feedEl.id = "wt-latest";
20561
21839
  feedEl.setAttribute("aria-label", "Updates and answers");
20562
- const fh = el2(doc, "header", "knobs-wt-feed-head");
20563
- const h = el2(doc, "h2", "knobs-wt-feed-title");
21840
+ const fh = el3(doc, "header", "knobs-wt-feed-head");
21841
+ const h = el3(doc, "h2", "knobs-wt-feed-title");
20564
21842
  h.textContent = page.feed.some((f) => f.kind === "qa") ? "Updates & Q&A" : "Updates";
20565
- const n = el2(doc, "span", "knobs-wt-feed-count");
20566
- n.textContent = String(page.feed.length);
20567
- fh.append(h, n);
20568
- const list = el2(doc, "ol", "knobs-wt-feed-list");
21843
+ fh.append(h);
21844
+ const list = el3(doc, "ol", "knobs-wt-feed-list");
20569
21845
  const now = Date.now();
20570
21846
  for (const f of page.feed)
20571
21847
  list.append(feedItem(f, now));
@@ -20575,21 +21851,21 @@ function walkthrough(container, options) {
20575
21851
  let rail = null;
20576
21852
  count = fill = tocEl = null;
20577
21853
  if (options.rail !== false) {
20578
- rail = el2(doc, "aside", "knobs-wt-rail");
21854
+ rail = el3(doc, "aside", "knobs-wt-rail");
20579
21855
  rail.setAttribute("aria-label", "Contents");
20580
- tocEl = el2(doc, "nav", "knobs-wt-toc");
21856
+ tocEl = el3(doc, "nav", "knobs-wt-toc");
20581
21857
  tocEl.setAttribute("aria-label", "Contents");
20582
21858
  if (feedEl) {
20583
- const group = el2(doc, "div", "knobs-wt-toc-group");
21859
+ const group = el3(doc, "div", "knobs-wt-toc-group");
20584
21860
  const target = feedEl;
20585
21861
  group.append(tocRow("Latest", false, () => target.scrollIntoView({ block: "start", behavior: reduced() ? "auto" : "smooth" })));
20586
21862
  tocEl.append(group);
20587
21863
  }
20588
21864
  if (review) {
20589
- const progress = el2(doc, "div", "knobs-wt-progress");
20590
- count = el2(doc, "div", "knobs-wt-count");
20591
- const meter = el2(doc, "div", "knobs-wt-bar");
20592
- fill = el2(doc, "div", "knobs-wt-fill");
21865
+ const progress = el3(doc, "div", "knobs-wt-progress");
21866
+ count = el3(doc, "div", "knobs-wt-count");
21867
+ const meter = el3(doc, "div", "knobs-wt-bar");
21868
+ fill = el3(doc, "div", "knobs-wt-fill");
20593
21869
  meter.append(fill);
20594
21870
  progress.append(count, meter);
20595
21871
  rail.append(progress);
@@ -20598,15 +21874,15 @@ function walkthrough(container, options) {
20598
21874
  if (review)
20599
21875
  rail.append(actions(true));
20600
21876
  if (review && options.keys !== false) {
20601
- const keys = el2(doc, "div", "knobs-wt-keys");
21877
+ const keys = el3(doc, "div", "knobs-wt-keys");
20602
21878
  keys.innerHTML = "<kbd>j</kbd> <kbd>k</kbd> steps · <kbd>←</kbd> <kbd>→</kbd> seek · <kbd>f</kbd> full · <kbd>n</kbd> note" + (page.steps.some((s) => s.ask === "pick") ? " · <kbd>1</kbd>–<kbd>9</kbd> pick" : "");
20603
21879
  rail.append(keys);
20604
21880
  }
20605
21881
  }
20606
21882
  for (const section of page.sections) {
20607
- const sec = el2(doc, "section", "knobs-wt-section");
21883
+ const sec = el3(doc, "section", "knobs-wt-section");
20608
21884
  sec.id = `wt-${section.id}`;
20609
- const sh = el2(doc, "header", "knobs-wt-shead");
21885
+ const sh = el3(doc, "header", "knobs-wt-shead");
20610
21886
  const status = review ? section.status : undefined;
20611
21887
  if (section.kicker || section.link || status) {
20612
21888
  const meta = metaLine("knobs-wt-kicker");
@@ -20620,10 +21896,10 @@ function walkthrough(container, options) {
20620
21896
  }
20621
21897
  sh.append(meta);
20622
21898
  }
20623
- const h2 = el2(doc, "h2", "knobs-wt-heading");
21899
+ const h2 = el3(doc, "h2", "knobs-wt-heading");
20624
21900
  h2.innerHTML = inlineMarkdown(section.title);
20625
21901
  sh.append(h2);
20626
- const intro = el2(doc, "div", "knobs-wt-sintro");
21902
+ const intro = el3(doc, "div", "knobs-wt-sintro");
20627
21903
  intro.append(sh);
20628
21904
  if (section.summary)
20629
21905
  intro.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
@@ -20632,7 +21908,7 @@ function walkthrough(container, options) {
20632
21908
  sec.append(intro);
20633
21909
  let group = null;
20634
21910
  if (tocEl) {
20635
- group = el2(doc, "div", "knobs-wt-toc-group");
21911
+ group = el3(doc, "div", "knobs-wt-toc-group");
20636
21912
  const first = section.steps[0]?.key;
20637
21913
  group.append(tocRow(plainMarkdown(section.title), false, () => first ? go(first) : undefined, section.id));
20638
21914
  tocEl.append(group);
@@ -20674,8 +21950,8 @@ function walkthrough(container, options) {
20674
21950
  }));
20675
21951
  };
20676
21952
  const more = (text) => {
20677
- const box = el2(doc, "div", "knobs-wt-more");
20678
- const body = el2(doc, "div", "knobs-wt-more-text");
21953
+ const box = el3(doc, "div", "knobs-wt-more");
21954
+ const body = el3(doc, "div", "knobs-wt-more-text");
20679
21955
  for (const para of text.split(/\n\s*\n/))
20680
21956
  if (para.trim())
20681
21957
  body.append(prose2(doc, "p", "", para.trim()));
@@ -20683,24 +21959,25 @@ function walkthrough(container, options) {
20683
21959
  return box;
20684
21960
  };
20685
21961
  const tocRow = (label, sub, run, section) => {
20686
- const b = el2(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
21962
+ const b = el3(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
20687
21963
  b.type = "button";
20688
21964
  if (section)
20689
21965
  b.dataset.section = section;
20690
- const t = el2(doc, "span", "knobs-wt-toc-label");
21966
+ const t = el3(doc, "span", "knobs-wt-toc-label");
20691
21967
  t.textContent = label;
20692
- const e = el2(doc, "span", "knobs-wt-toc-end");
20693
21968
  b.title = label;
20694
- b.append(t, e);
21969
+ b.append(t);
21970
+ if (review)
21971
+ b.append(el3(doc, "span", "knobs-wt-toc-end"));
20695
21972
  b.addEventListener("mousedown", (ev) => ev.preventDefault());
20696
21973
  b.addEventListener("click", run);
20697
21974
  tocRows.push(b);
20698
21975
  return b;
20699
21976
  };
20700
21977
  const actions = (rail) => {
20701
- const row = el2(doc, "div", "knobs-wt-actions");
21978
+ const row = el3(doc, "div", "knobs-wt-actions");
20702
21979
  const btn = (text, run, primary = false) => {
20703
- const b = el2(doc, "button", "knobs-wt-btn");
21980
+ const b = el3(doc, "button", "knobs-wt-btn");
20704
21981
  b.type = "button";
20705
21982
  b.textContent = text;
20706
21983
  if (primary)
@@ -20738,13 +22015,13 @@ function walkthrough(container, options) {
20738
22015
  }
20739
22016
  };
20740
22017
  const stepView = (step, group) => {
20741
- const card = el2(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
22018
+ const card = el3(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
20742
22019
  card.id = `wt-${step.key.replace("/", "--")}`;
20743
22020
  card.tabIndex = -1;
20744
22021
  card.dataset.key = step.key;
20745
22022
  card.setAttribute("aria-label", plainMarkdown(step.title));
20746
- const head = el2(doc, "header", "knobs-wt-step-head");
20747
- const h3 = el2(doc, "h3", "knobs-wt-stepTitle");
22023
+ const head = el3(doc, "header", "knobs-wt-step-head");
22024
+ const h3 = el3(doc, "h3", "knobs-wt-stepTitle");
20748
22025
  h3.innerHTML = inlineMarkdown(step.title);
20749
22026
  head.append(h3);
20750
22027
  if (step.caption)
@@ -20756,7 +22033,7 @@ function walkthrough(container, options) {
20756
22033
  const onEvent = (e) => record({ ...e, step: step.key });
20757
22034
  let media = null;
20758
22035
  if (step.media.length) {
20759
- media = el2(doc, "div", "knobs-wt-media");
22036
+ media = el3(doc, "div", "knobs-wt-media");
20760
22037
  media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
20761
22038
  for (const m of step.media)
20762
22039
  slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
@@ -20776,7 +22053,7 @@ function walkthrough(container, options) {
20776
22053
  });
20777
22054
  media ??= taste.element;
20778
22055
  }
20779
- const notes = el2(doc, "div", "knobs-wt-notes");
22056
+ const notes = el3(doc, "div", "knobs-wt-notes");
20780
22057
  if (review)
20781
22058
  card.append(notes);
20782
22059
  card.addEventListener("focusin", () => {
@@ -20790,7 +22067,7 @@ function walkthrough(container, options) {
20790
22067
  if (group) {
20791
22068
  toc = tocRow(plainMarkdown(step.title), true, () => go(step.key));
20792
22069
  if (review) {
20793
- glyph = el2(doc, "span", "knobs-wt-toc-glyph");
22070
+ glyph = el3(doc, "span", "knobs-wt-toc-glyph");
20794
22071
  toc.querySelector(".knobs-wt-toc-end").append(glyph);
20795
22072
  }
20796
22073
  group.append(toc);
@@ -20864,6 +22141,10 @@ function walkthrough(container, options) {
20864
22141
  claim();
20865
22142
  return;
20866
22143
  }
22144
+ if (key === "a" && !e.shiftKey && root.querySelector(".knobs-wt-anno")) {
22145
+ claim();
22146
+ return setAnnotations(root, !annotationsOn(root));
22147
+ }
20867
22148
  if (key === "n" && noteMode !== "hidden") {
20868
22149
  claim();
20869
22150
  bar.setOpen(true, true);
@@ -20962,7 +22243,7 @@ function walkthrough(container, options) {
20962
22243
  }
20963
22244
 
20964
22245
  // src/ui/solid.ts
20965
- var follow = (read, apply) => {
22246
+ var follow2 = (read, apply) => {
20966
22247
  const changes = propChanges(untrack(read));
20967
22248
  createEffect(() => {
20968
22249
  const changed = changes(read());
@@ -21010,7 +22291,7 @@ function Tree(props) {
21010
22291
  }
21011
22292
  }));
21012
22293
  untrack(() => props.onReady?.(t));
21013
- follow(() => ({ items: props.items, filter: props.filter, selection: props.selection }), (c) => {
22294
+ follow2(() => ({ items: props.items, filter: props.filter, selection: props.selection }), (c) => {
21014
22295
  if ("items" in c)
21015
22296
  t.update(c.items);
21016
22297
  if ("filter" in c)
@@ -21046,7 +22327,7 @@ function CodeDiff(props) {
21046
22327
  onViewed: props.onViewed && ((viewed, file) => props.onViewed?.(viewed, file))
21047
22328
  }));
21048
22329
  untrack(() => props.onReady?.(d));
21049
- follow(diffProps(props), (c) => {
22330
+ follow2(diffProps(props), (c) => {
21050
22331
  if (c.height)
21051
22332
  host.style.height = c.height === "auto" ? "" : "100%";
21052
22333
  d.update(c);
@@ -21069,7 +22350,7 @@ function DiffReview(props) {
21069
22350
  onViewed: (viewed, file) => props.onViewed?.(viewed, file)
21070
22351
  }));
21071
22352
  untrack(() => props.onReady?.(r));
21072
- follow(diffProps(props), (c) => {
22353
+ follow2(diffProps(props), (c) => {
21073
22354
  if (c.height)
21074
22355
  host.style.height = c.height === "auto" ? "" : "100%";
21075
22356
  r.update(c);
@@ -21091,7 +22372,7 @@ function Timeline(props) {
21091
22372
  onCross: (m) => props.onCross?.(m)
21092
22373
  }));
21093
22374
  untrack(() => props.onReady?.(t));
21094
- follow(() => ({
22375
+ follow2(() => ({
21095
22376
  duration: props.duration,
21096
22377
  fps: props.fps,
21097
22378
  units: props.units,
@@ -21120,7 +22401,7 @@ function Compare(props) {
21120
22401
  onView: (view) => props.onView?.(view)
21121
22402
  }));
21122
22403
  untrack(() => props.onReady?.(c));
21123
- follow(() => ({
22404
+ follow2(() => ({
21124
22405
  before: props.before,
21125
22406
  after: props.after,
21126
22407
  mode: props.mode,
@@ -21143,7 +22424,7 @@ function CompareStrip(props) {
21143
22424
  host.style.height = "100%";
21144
22425
  const s = untrack(() => compareStrip(host, { ...props, onPick: (time) => props.onPick?.(time) }));
21145
22426
  untrack(() => props.onReady?.(s));
21146
- follow(() => ({ before: props.before.src, after: props.after.src }), (changed) => s.update({
22427
+ follow2(() => ({ before: props.before.src, after: props.after.src }), (changed) => s.update({
21147
22428
  ..."before" in changed ? { before: props.before } : {},
21148
22429
  ..."after" in changed ? { after: props.after } : {}
21149
22430
  }));
@@ -21163,7 +22444,7 @@ function Bento(props) {
21163
22444
  onActive: (id) => props.onActive?.(id)
21164
22445
  }));
21165
22446
  untrack(() => props.onReady?.(b));
21166
- follow(() => ({ items: props.items, picks: props.picks }), (c) => {
22447
+ follow2(() => ({ items: props.items, picks: props.picks }), (c) => {
21167
22448
  if ("items" in c)
21168
22449
  b.update(c.items);
21169
22450
  if (c.picks)
@@ -21185,7 +22466,7 @@ function Activity(props) {
21185
22466
  onPlay: props.onPlay && ((id, event) => props.onPlay?.(id, event))
21186
22467
  }));
21187
22468
  untrack(() => props.onReady?.(a));
21188
- follow(() => ({ events: props.events, newest: props.newest, selected: props.selected }), (c) => {
22469
+ follow2(() => ({ events: props.events, newest: props.newest, selected: props.selected }), (c) => {
21189
22470
  if (c.events || c.newest)
21190
22471
  a.update({ events: c.events, newest: c.newest });
21191
22472
  if (c.selected !== undefined && c.selected !== a.selected)
@@ -21214,7 +22495,7 @@ function OptionPicker(props) {
21214
22495
  }
21215
22496
  }));
21216
22497
  untrack(() => props.onReady?.(p));
21217
- follow(() => ({ options: props.options, selected: props.selected, chosen: props.chosen }), (c) => {
22498
+ follow2(() => ({ options: props.options, selected: props.selected, chosen: props.chosen }), (c) => {
21218
22499
  if ("options" in c)
21219
22500
  p.set(c.options);
21220
22501
  if (c.selected != null)
@@ -21231,7 +22512,7 @@ function Walkthrough(props) {
21231
22512
  const host = document.createElement("div");
21232
22513
  const w = untrack(() => walkthrough(host, { ...props, onFeedback: (feedback) => props.onFeedback?.(feedback) }));
21233
22514
  untrack(() => props.onReady?.(w));
21234
- follow(() => ({ doc: props.doc }), (c) => c.doc && w.set(c.doc));
22515
+ follow2(() => ({ doc: props.doc }), (c) => c.doc && w.set(c.doc));
21235
22516
  onCleanup(() => w.dispose());
21236
22517
  return host;
21237
22518
  }
@@ -21241,7 +22522,7 @@ function TasteTest(props) {
21241
22522
  const host = document.createElement("div");
21242
22523
  const t = untrack(() => tasteTest(host, { ...props, onPick: (id) => props.onPick?.(id) }));
21243
22524
  untrack(() => props.onReady?.(t));
21244
- follow(() => ({ picked: props.picked }), (c) => ("picked" in c) && t.pick(c.picked ?? null));
22525
+ follow2(() => ({ picked: props.picked }), (c) => ("picked" in c) && t.pick(c.picked ?? null));
21245
22526
  onCleanup(() => t.dispose());
21246
22527
  return host;
21247
22528
  }