@kitlangton/knobs 0.4.0-next.1 → 0.4.0-next.2

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
@@ -16375,7 +16375,7 @@ var walkthroughCss = `
16375
16375
  .knobs-walkthrough a{color:inherit;text-decoration:underline;text-decoration-color:color-mix(in srgb,currentColor 35%,transparent);text-underline-offset:2px;transition:text-decoration-color .15s}
16376
16376
  .knobs-walkthrough a:hover{text-decoration-color:currentColor}
16377
16377
  .knobs-walkthrough code,.knobs-wt-slot code{font:calc(1em - 1px) var(--_k-mono);padding:1px 5px;border-radius:5px;background:var(--_k-fill)}
16378
- :where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-step){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
16378
+ :where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-step,button.knobs-wt-stitle,.knobs-wt-chapter){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
16379
16379
  background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
16380
16380
 
16381
16381
  /* ——— layout: the rail beside the column; one column where it's narrow ——— */
@@ -16434,9 +16434,9 @@ var walkthroughCss = `
16434
16434
  .knobs-wt-fi-body{max-width:var(--kw-measure);color:var(--_k-fg)}
16435
16435
  .knobs-wt-fi-q{color:var(--_k-fg-2)}
16436
16436
  .knobs-wt-fi-q::before{content:"You asked: ";color:var(--_k-fg-3)}
16437
- :where(.knobs-ui) .knobs-wt-fi-step{all:revert;box-sizing:border-box;font:var(--_k-weight) calc(var(--kw-text) - 2.5px)/1 var(--_k-mono);color:var(--_k-fg-2);background:transparent;border:0;
16437
+ :where(.knobs-ui) :is(.knobs-wt-fi-step,.knobs-wt-steplink){all:revert;box-sizing:border-box;font:var(--_k-weight) calc(var(--k-walkthrough-text,14px) - 2.5px)/1 var(--_k-mono);color:var(--_k-fg-2);background:transparent;border:0;
16438
16438
  padding:3px 6px;margin:0;border-radius:6px;cursor:pointer;outline:none;transition:background .15s,color .15s}
16439
- .knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
16439
+ .knobs-ui :is(.knobs-wt-fi-step,.knobs-wt-steplink):is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
16440
16440
 
16441
16441
  /* ——— a step's sent notes ——— */
16442
16442
  .knobs-wt-notes{display:flex;flex-direction:column}
@@ -16572,7 +16572,8 @@ var walkthroughCss = `
16572
16572
  }
16573
16573
 
16574
16574
  /* ——— media slots ——— */
16575
- .knobs-ui.knobs-wt-slot{position:relative;min-width:0;container:knobs-wt-slot / inline-size;color:var(--_k-fg);font:var(--_k-weight) var(--k-walkthrough-text,14px)/1.5 var(--_k-font)}
16575
+ .knobs-ui.knobs-wt-slot{position:relative;min-width:0;container:knobs-wt-slot / inline-size;color:var(--_k-fg);font:var(--_k-weight) var(--k-walkthrough-text,14px)/1.5 var(--_k-font);
16576
+ --kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info))}
16576
16577
  .knobs-wt-slot-label{display:block;margin:0 0 6px 2px;font:var(--_k-weight-label) calc(var(--k-walkthrough-text,14px) - 3px)/1.3 var(--_k-font);color:var(--_k-fg-2);letter-spacing:.04em;text-transform:uppercase}
16577
16578
  /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
16578
16579
  sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
@@ -16612,9 +16613,141 @@ var walkthroughCss = `
16612
16613
  .knobs-wt-text{max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
16613
16614
  .knobs-wt-hold{border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill)}
16614
16615
  .knobs-wt-failed{display:flex;align-items:center;justify-content:center;min-height:80px;color:var(--_k-fg-3);font-size:calc(var(--k-walkthrough-text,14px) - 2px)}
16616
+
16617
+ /* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
16618
+ hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
16619
+ :where(.knobs-wt-history,.knobs-wt-stack,.knobs-wt-table,.knobs-wt-chapters){--kw-t:var(--k-walkthrough-text,14px);--kw-line:calc(var(--kw-t) + 6px);--kw-rail:var(--_k-border-strong)}
16620
+ :where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
16621
+ :where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
16622
+ :where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
16623
+ :where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
16624
+ :where(.knobs-wt-slot) [data-tone="dim"]{--kw-tone:var(--_k-fg-3)}
16625
+ .knobs-wt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
16626
+ .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) calc(var(--kw-t) - 3px)/1 var(--_k-mono);
16627
+ text-decoration:none;white-space:nowrap;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
16628
+ .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)}
16629
+
16630
+ /* A history: dates down the left, a spine of tone dots joined by a hairline (broken around each
16631
+ dot), the event beside it. Inferred events take a ring for a dot and a dashed tag. */
16632
+ .knobs-wt-history{--kw-hy:calc(5px + var(--kw-line) / 2);display:flex;flex-direction:column;gap:18px;font-size:calc(var(--kw-t) - 1px);line-height:var(--kw-line);color:var(--_k-fg)}
16633
+ .knobs-wt-hgroup{display:flex;flex-direction:column;gap:6px;min-width:0}
16634
+ .knobs-wt-hhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px}
16635
+ .knobs-ui .knobs-wt-hheading{margin:0;font:var(--_k-weight-label) calc(var(--kw-t) - 3px)/1.3 var(--_k-font);letter-spacing:.04em;text-transform:uppercase;color:var(--_k-fg-2)}
16636
+ .knobs-wt-hsum{color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 2px);font-variant-numeric:tabular-nums}
16637
+ .knobs-wt-hlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
16638
+ .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}
16639
+ .knobs-wt-hwhen{grid-area:when;display:flex;flex-direction:column;min-width:0;font:var(--_k-weight) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-2)}
16640
+ .knobs-wt-hage{color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 3px);line-height:1.35}
16641
+ .knobs-wt-hspine{grid-area:spine;position:relative;align-self:stretch;margin-block:-5px}
16642
+ .knobs-wt-hspine::before,.knobs-wt-hspine::after{content:"";position:absolute;left:6.5px;width:1px;background:var(--kw-rail)}
16643
+ .knobs-wt-hspine::before{top:0;height:calc(var(--kw-hy) - 6px)}
16644
+ .knobs-wt-hspine::after{top:calc(var(--kw-hy) + 6px);bottom:0}
16645
+ .knobs-wt-hev:first-child .knobs-wt-hspine::before,.knobs-wt-hev:last-child .knobs-wt-hspine::after{content:none}
16646
+ .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))}
16647
+ .knobs-wt-hev[data-type="release"] .knobs-wt-hdot{border-radius:1.5px;rotate:45deg;scale:.9}
16648
+ .knobs-wt-hev[data-type="audit"] .knobs-wt-hdot{border-radius:1.5px}
16649
+ .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))}
16650
+ .knobs-wt-hmain{grid-area:main;display:flex;flex-direction:column;gap:2px;min-width:0}
16651
+ .knobs-wt-htitle{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
16652
+ .knobs-wt-prov{padding:0 6px;border-radius:5px;background:var(--_k-fill);color:var(--_k-fg-3);font:var(--_k-weight) calc(var(--kw-t) - 3.5px)/18px var(--_k-mono);white-space:nowrap}
16653
+ .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}
16654
+ .knobs-ui .knobs-wt-hdetail{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
16655
+ @container knobs-wt-slot (max-width:520px){
16656
+ .knobs-wt-hev{grid-template-columns:14px minmax(0,1fr);grid-template-areas:"spine when" "spine main"}
16657
+ .knobs-wt-hwhen{flex-direction:row;flex-wrap:wrap;align-items:baseline;column-gap:8px}
16658
+ }
16659
+
16660
+ /* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
16661
+ git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
16662
+ .knobs-wt-stack{--kw-spt:7px;--kw-spb:7px;--kw-sy:calc(var(--kw-spt) + var(--kw-line) / 2);display:flex;flex-direction:column;font-size:calc(var(--kw-t) - 1px);line-height:var(--kw-line);color:var(--_k-fg)}
16663
+ .knobs-wt-srow{display:grid;grid-template-columns:auto minmax(0,1fr) auto;column-gap:10px;align-items:start;margin:0 -8px;padding:var(--kw-spt) 8px var(--kw-spb);
16664
+ border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
16665
+ .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
16666
+ .knobs-wt-srow[data-go]{cursor:pointer}
16667
+ .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
16668
+ .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
16669
+ .knobs-wt-lane{position:relative;flex:none;width:16px}
16670
+ .knobs-wt-lane:is([data-rail="pass"],[data-rail="tee"],[data-down])::before{content:"";position:absolute;left:7.5px;top:0;bottom:0;width:1px;background:var(--kw-rail)}
16671
+ .knobs-wt-lane[data-down]::before{top:calc(var(--kw-sy) + 3.5px)}
16672
+ .knobs-wt-lane:is([data-rail="tee"],[data-rail="end"])::after{content:"";position:absolute;left:7.5px;top:0;width:13px;height:calc(var(--kw-sy) + .5px);border:solid var(--kw-rail);border-width:0 0 1px 1px;border-bottom-left-radius:6px}
16673
+ .knobs-wt-lane[data-rail="node"]::after{content:"";position:absolute;left:4.5px;top:calc(var(--kw-sy) - 3.5px);width:7px;height:7px;border-radius:50%;background:var(--_k-fg-3)}
16674
+ .knobs-wt-srow[data-trunk] .knobs-wt-lane[data-rail="node"]::after{background:var(--_k-fg-2)}
16675
+ .knobs-wt-trunk{font:var(--_k-weight-label) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2)}
16676
+ .knobs-wt-smain{display:flex;flex-direction:column;gap:1px;min-width:0}
16677
+ .knobs-wt-sline{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 8px}
16678
+ .knobs-wt-sord{font:var(--_k-weight) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3);white-space:nowrap}
16679
+ .knobs-ui .knobs-wt-spr{font:var(--_k-weight-label) calc(var(--kw-t) - 1.5px)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2);text-decoration:none;white-space:nowrap}
16680
+ .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
16681
+ .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
16682
+ .knobs-ui button.knobs-wt-stitle{cursor:pointer;text-decoration:underline;text-decoration-color:transparent;text-underline-offset:3px;transition:text-decoration-color calc(var(--_k-hover-out) * 1ms)}
16683
+ .knobs-ui :is(.knobs-wt-srow[data-go]:hover button.knobs-wt-stitle,button.knobs-wt-stitle:focus-visible){text-decoration-color:color-mix(in oklch,currentColor 45%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
16684
+ .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
16685
+ .knobs-wt-smeta{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 12px;font:var(--_k-weight) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3)}
16686
+ .knobs-wt-delta{display:inline-flex;gap:6px}
16687
+ .knobs-wt-add{color:var(--_k-diff-add)}
16688
+ .knobs-wt-del{color:var(--_k-diff-del)}
16689
+ .knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
16690
+ .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
16691
+ .knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
16692
+ @container knobs-wt-slot (max-width:520px){
16693
+ .knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
16694
+ .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
16695
+ .knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
16696
+ }
16697
+
16698
+ /* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
16699
+ (the top one under the head), and thin bars show on hover. */
16700
+ @property --kw-fade-l{syntax:"<number>";inherits:false;initial-value:1}
16701
+ @property --kw-fade-r{syntax:"<number>";inherits:false;initial-value:1}
16702
+ @keyframes kw-fade-l{from{--kw-fade-l:1}to{--kw-fade-l:0}}
16703
+ @keyframes kw-fade-r{from{--kw-fade-r:0}to{--kw-fade-r:1}}
16704
+ .knobs-wt-table{max-height:520px;overflow:auto;overscroll-behavior-x:contain;outline:none;scrollbar-width:thin;scrollbar-color:transparent transparent;
16705
+ transition:scrollbar-color calc(var(--_k-hover-out) * 1ms)}
16706
+ .knobs-wt-table:is(:hover,:focus-visible){scrollbar-color:color-mix(in oklch,var(--_k-fg) 28%,transparent) transparent;transition-duration:calc(var(--_k-hover-in) * 1ms)}
16707
+ .knobs-wt-table:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus);border-radius:calc(var(--_k-radius-panel) - 4px)}
16708
+ @supports (animation-timeline:scroll()){
16709
+ .knobs-wt-table{animation:knobs-fade-a linear both,knobs-fade-b linear both,kw-fade-l linear both,kw-fade-r linear both;animation-duration:auto;
16710
+ animation-timeline:scroll(self block),scroll(self block),scroll(self inline),scroll(self inline);animation-range:0 24px,calc(100% - 24px) 100%,0 8px,calc(100% - 8px) 100%;
16711
+ --kw-table-mask-y:linear-gradient(to bottom,#000 var(--kw-thead,0px),rgb(0 0 0/var(--knobs-fade-a)) var(--kw-thead,0px),#000 calc(var(--kw-thead,0px) + 20px),#000 calc(100% - 20px),rgb(0 0 0/var(--knobs-fade-b)));
16712
+ --kw-table-mask-x:linear-gradient(to right,rgb(0 0 0/var(--kw-fade-l)),#000 24px,#000 calc(100% - 24px),rgb(0 0 0/var(--kw-fade-r)));
16713
+ -webkit-mask-image:var(--kw-table-mask-y),var(--kw-table-mask-x);mask-image:var(--kw-table-mask-y),var(--kw-table-mask-x);-webkit-mask-composite:source-in;mask-composite:intersect}
16714
+ }
16715
+ .knobs-wt-table table{width:100%;margin:0;border-collapse:separate;border-spacing:0;font-size:calc(var(--kw-t) - 1.5px);line-height:1.45;font-variant-numeric:tabular-nums;color:var(--_k-fg-2)}
16716
+ .knobs-wt-table caption{caption-side:bottom;padding-top:8px;text-align:left;color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 2px)}
16717
+ .knobs-wt-table th{position:sticky;top:0;padding:0 12px 7px;text-align:left;vertical-align:bottom;font-weight:var(--_k-weight-label);font-size:calc(var(--kw-t) - 2.5px);color:var(--_k-fg-3);white-space:nowrap;
16718
+ background:var(--_k-pane-bg);box-shadow:inset 0 -1px 0 var(--_k-border)}
16719
+ .knobs-wt-table td{padding:8px 12px;vertical-align:baseline;box-shadow:inset 0 -1px 0 var(--_k-border)}
16720
+ .knobs-wt-table tr:last-child td{box-shadow:none}
16721
+ .knobs-wt-table :is(th,td):first-child{padding-left:0}
16722
+ .knobs-wt-table :is(th,td):last-child{padding-right:0}
16723
+ .knobs-wt-table td:first-child{color:var(--_k-fg)}
16724
+ .knobs-wt-table [data-align="right"]{text-align:right}
16725
+ .knobs-wt-table [data-align="center"]{text-align:center}
16726
+ .knobs-wt-table td[data-mono]{font-family:var(--_k-mono);font-size:calc(var(--kw-t) - 2.5px);color:var(--_k-fg);white-space:nowrap}
16727
+ .knobs-wt-table td[data-long]{min-width:24ch}
16728
+ .knobs-wt-table td>.knobs-wt-steplink{margin-left:4px}
16729
+ .knobs-ui .knobs-wt-table a.knobs-wt-chip{text-decoration:none}
16730
+ .knobs-wt-ctone{color:var(--kw-tone)}
16731
+
16732
+ /* Chapters: timestamp pills under a video; one highlight springs to the one playing. */
16733
+ .knobs-wt-chapters{position:relative;display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}
16734
+ .knobs-wt-chapter-mark{position:absolute;left:0;top:0;width:0;border-radius:calc(var(--_k-radius) + 2px);background:var(--_k-fill-selected);opacity:0;pointer-events:none;will-change:transform;
16735
+ transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms)}
16736
+ .knobs-wt-chapter-mark[data-on]{opacity:1}
16737
+ .knobs-ui .knobs-wt-chapter{position:relative;display:inline-flex;align-items:baseline;gap:6px;height:26px;padding:0 10px 0 8px;border-radius:calc(var(--_k-radius) + 2px);color:var(--_k-fg-2);
16738
+ font-size:calc(var(--kw-t) - 2px);line-height:26px;white-space:nowrap;cursor:pointer;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
16739
+ .knobs-ui .knobs-wt-chapter:hover{background:var(--_ku-wash);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
16740
+ .knobs-ui .knobs-wt-chapter[aria-current]{background:transparent;color:var(--_k-fg)}
16741
+ .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus)}
16742
+ .knobs-wt-chapter-time{font:var(--_k-weight) calc(var(--kw-t) - 3px)/1 var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3)}
16743
+ .knobs-wt-chapter[aria-current] .knobs-wt-chapter-time{color:var(--_k-fg-2)}
16615
16744
  @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
16616
16745
  `;
16617
16746
 
16747
+ // src/ui/walkthrough-blocks.ts
16748
+ init_motion();
16749
+ init_look();
16750
+
16618
16751
  // src/ui/walkthrough-model.ts
16619
16752
  var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
16620
16753
  function slug(text) {
@@ -16656,20 +16789,207 @@ function fillMedia(m, base) {
16656
16789
  if (typeof m === "string")
16657
16790
  return mediaOf(resolveSrc(m, base));
16658
16791
  switch (m.kind) {
16659
- case "compare":
16660
- return { ...m, before: source2(m.before, base), after: source2(m.after, base) };
16661
- case "video":
16792
+ case "compare": {
16793
+ const { chapters, ...rest } = m;
16662
16794
  return {
16663
- ...m,
16795
+ ...rest,
16796
+ before: source2(m.before, base),
16797
+ after: source2(m.after, base),
16798
+ ...chapters?.length ? { chapters: fillChapters(chapters) } : {}
16799
+ };
16800
+ }
16801
+ case "video": {
16802
+ const { chapters, captionsSrc, captions, ...rest } = m;
16803
+ const vtt = captions ?? captionsSrc;
16804
+ return {
16805
+ ...rest,
16664
16806
  src: resolveSrc(m.src, base),
16665
- ...m.poster ? { poster: resolveSrc(m.poster, base) } : {}
16807
+ ...m.poster ? { poster: resolveSrc(m.poster, base) } : {},
16808
+ ...vtt ? { captions: resolveSrc(vtt, base) } : {},
16809
+ ...chapters?.length ? { chapters: fillChapters(chapters) } : {}
16666
16810
  };
16811
+ }
16667
16812
  case "image":
16668
16813
  return { ...m, src: resolveSrc(m.src, base) };
16814
+ case "history":
16815
+ return { kind: "history", groups: fillHistory(m) };
16816
+ case "stack":
16817
+ return {
16818
+ kind: "stack",
16819
+ ...m.base ? { base: m.base } : {},
16820
+ items: m.items.map(fillStackItem)
16821
+ };
16822
+ case "table":
16823
+ return fillTable(m);
16669
16824
  default:
16670
16825
  return m;
16671
16826
  }
16672
16827
  }
16828
+ function fillHistory(m) {
16829
+ const groups = [];
16830
+ if (m.events?.length)
16831
+ groups.push({ events: m.events });
16832
+ for (const g of m.groups ?? [])
16833
+ groups.push({
16834
+ ...g.heading ? { heading: g.heading } : {},
16835
+ ...g.summary ? { summary: g.summary } : {},
16836
+ events: g.events ?? g.rows ?? []
16837
+ });
16838
+ return groups;
16839
+ }
16840
+ function parseBranch(branch) {
16841
+ if (!branch?.trim())
16842
+ return {};
16843
+ const [head, base] = branch.split(/\s*(?:→|->|=>|⇒)\s*/, 2).map((s) => s.trim());
16844
+ return { ...head ? { head } : {}, ...base ? { base } : {} };
16845
+ }
16846
+ function parseDelta(delta) {
16847
+ if (delta === undefined || typeof delta === "object")
16848
+ return delta;
16849
+ const m = /\+\s*([\d,_]+)[^\d+]*?[-−–]\s*([\d,_]+)/.exec(delta);
16850
+ if (!m)
16851
+ return delta.trim() || undefined;
16852
+ const n = (s) => Number(s.replace(/[,_]/g, ""));
16853
+ return { add: n(m[1]), del: n(m[2]) };
16854
+ }
16855
+ function fillStackItem(it) {
16856
+ const parsed = parseBranch(it.branch);
16857
+ const head = parsed.head ?? it.head;
16858
+ const base = parsed.base ?? it.base;
16859
+ const delta = parseDelta(it.delta);
16860
+ const step = it.step ?? it.stepId;
16861
+ const pr = String(it.pr).trim();
16862
+ return {
16863
+ pr: /^\d+$/.test(pr) ? `#${pr}` : pr,
16864
+ ...it.href ? { href: it.href } : {},
16865
+ title: it.title,
16866
+ ...head ? { head } : {},
16867
+ ...base ? { base } : {},
16868
+ ...delta !== undefined ? { delta } : {},
16869
+ ...it.status ? { status: it.status } : {},
16870
+ ...it.tone ? { tone: it.tone } : {},
16871
+ ...it.summary ? { summary: it.summary } : {},
16872
+ ...step ? { step } : {},
16873
+ ...it.order !== undefined && it.order !== "" ? { order: String(it.order) } : {}
16874
+ };
16875
+ }
16876
+ function stackLayout(m) {
16877
+ const items = m.items;
16878
+ const byHead = new Map;
16879
+ items.forEach((it, i) => it.head && !byHead.has(it.head) && byHead.set(it.head, i));
16880
+ let trunk = m.base ?? null;
16881
+ if (!trunk) {
16882
+ const tally = new Map;
16883
+ for (const it of items)
16884
+ if (it.base && !byHead.has(it.base))
16885
+ tally.set(it.base, (tally.get(it.base) ?? 0) + 1);
16886
+ let best = 0;
16887
+ for (const [b, n] of tally)
16888
+ if (n > best)
16889
+ [trunk, best] = [b, n];
16890
+ }
16891
+ const parentOf = items.map((it, i) => {
16892
+ const p = it.base === undefined ? undefined : byHead.get(it.base);
16893
+ return p === undefined || p === i ? null : p;
16894
+ });
16895
+ items.forEach((_, i) => {
16896
+ const seen = new Set;
16897
+ for (let n = i;n !== null; n = parentOf[n]) {
16898
+ if (seen.has(n)) {
16899
+ const loop = [n];
16900
+ for (let k = parentOf[n];k !== n && k !== null; k = parentOf[k])
16901
+ loop.push(k);
16902
+ parentOf[Math.min(...loop)] = null;
16903
+ break;
16904
+ }
16905
+ seen.add(n);
16906
+ }
16907
+ });
16908
+ const kids = items.map(() => []);
16909
+ const roots = [];
16910
+ parentOf.forEach((p, i) => (p === null ? roots : kids[p]).push(i));
16911
+ const rows = [];
16912
+ const walk = (list, depth, rails) => list.forEach((i, n) => {
16913
+ const it = items[i];
16914
+ const last = n === list.length - 1;
16915
+ const off = parentOf[i] === null && it.base && it.base !== trunk ? it.base : undefined;
16916
+ rows.push({
16917
+ item: it,
16918
+ index: i,
16919
+ depth,
16920
+ parent: parentOf[i],
16921
+ last,
16922
+ rails,
16923
+ children: kids[i].length,
16924
+ ...off ? { offBase: off } : {}
16925
+ });
16926
+ walk(kids[i], depth + 1, [...rails, !last]);
16927
+ });
16928
+ walk(roots, 0, []);
16929
+ return { trunk, rows, flat: !trunk && rows.every((r) => r.depth === 0 && !r.children) };
16930
+ }
16931
+ var isNum = (c) => typeof c === "number" || typeof c === "object" && typeof c.text === "number";
16932
+ function fillCell(c) {
16933
+ if (typeof c !== "object")
16934
+ return { text: String(c) };
16935
+ return { ...c, text: String(c.text) };
16936
+ }
16937
+ function fillTable(m) {
16938
+ const wrapped = !m.columns.some((c) => c.key === "cells");
16939
+ const raw = m.rows.map((r) => {
16940
+ const keys = Object.keys(r);
16941
+ const cells = r.cells;
16942
+ return wrapped && keys.length === 1 && keys[0] === "cells" && cells && typeof cells === "object" ? cells : r;
16943
+ });
16944
+ const columns = m.columns.map((c) => {
16945
+ const present = raw.map((r) => r[c.key]).filter((v) => v !== undefined && v !== "");
16946
+ const numeric = present.length > 0 && present.every(isNum);
16947
+ return { ...c, align: c.align ?? (numeric ? "right" : "left") };
16948
+ });
16949
+ const rows = raw.map((r) => {
16950
+ const out = {};
16951
+ for (const [k, v] of Object.entries(r))
16952
+ if (v !== undefined && v !== null)
16953
+ out[k] = fillCell(v);
16954
+ return out;
16955
+ });
16956
+ return { kind: "table", columns, rows, ...m.caption ? { caption: m.caption } : {} };
16957
+ }
16958
+ function fillChapters(chapters) {
16959
+ const id = uniqueIds();
16960
+ const starts = chapters.map((c) => c.start).sort((a, b) => a - b);
16961
+ return chapters.map((c) => {
16962
+ const next = starts.find((s) => s > c.start);
16963
+ const end = c.end ?? (c.duration !== undefined ? Math.round((c.start + c.duration) * 1000) / 1000 : next);
16964
+ return {
16965
+ id: id(c.id ?? slug(c.label)),
16966
+ label: c.label,
16967
+ start: c.start,
16968
+ ...end !== undefined && end > c.start ? { end } : {}
16969
+ };
16970
+ });
16971
+ }
16972
+ function chapterAt(chapters, time) {
16973
+ let at = -1;
16974
+ chapters.forEach((c, i) => {
16975
+ if (time >= c.start && time < (c.end ?? Infinity) && (at < 0 || c.start >= chapters[at].start))
16976
+ at = i;
16977
+ });
16978
+ return at;
16979
+ }
16980
+ function clock(seconds) {
16981
+ const t = Math.max(0, Math.floor(seconds));
16982
+ const h = Math.floor(t / 3600);
16983
+ const m = Math.floor(t % 3600 / 60);
16984
+ const s = String(t % 60).padStart(2, "0");
16985
+ return h ? `${h}:${String(m).padStart(2, "0")}:${s}` : `${m}:${s}`;
16986
+ }
16987
+ function findStep(steps, ref) {
16988
+ if (ref === undefined)
16989
+ return;
16990
+ const r = ref.trim().replace(/^#/, "");
16991
+ return steps.find((s) => s.key === r || s.id === r || s.number === r);
16992
+ }
16673
16993
  function linkLabel(href) {
16674
16994
  const pr = /\/(?:pull|issues)\/(\d+)/.exec(href);
16675
16995
  if (pr)
@@ -16726,7 +17046,7 @@ function normalizeWalkthrough(doc) {
16726
17046
  label: k,
16727
17047
  value
16728
17048
  }));
16729
- const stepRef = (ref) => ref === undefined ? null : steps.find((s) => s.key === ref || s.id === ref || s.number === ref)?.key ?? null;
17049
+ const stepRef = (ref) => findStep(steps, ref)?.key ?? null;
16730
17050
  const feed = sortFeed([
16731
17051
  ...(doc.updates ?? []).map((u) => ({ kind: "update", at: parseAt(u.at), text: u.text, step: stepRef(u.step) })),
16732
17052
  ...(doc.qa ?? []).map((x) => ({
@@ -16893,6 +17213,8 @@ function describeMedia(m) {
16893
17213
  parts.push(`${m.kind} at ${formatTime(m.time)}`);
16894
17214
  else
16895
17215
  parts.push(m.kind);
17216
+ if (m.chapter)
17217
+ parts[0] += ` in “${m.chapter}”`;
16896
17218
  if (m.loop)
16897
17219
  parts.push(`loop ${formatTime(m.loop.start)}–${formatTime(m.loop.end)}`);
16898
17220
  if (m.zoom)
@@ -17052,6 +17374,388 @@ function inlineMarkdown(text) {
17052
17374
  return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`);
17053
17375
  }
17054
17376
 
17377
+ // src/ui/walkthrough-blocks.ts
17378
+ var el = (doc, tag, cls) => {
17379
+ const n = doc.createElement(tag);
17380
+ if (cls)
17381
+ n.className = cls;
17382
+ return n;
17383
+ };
17384
+ var text = (doc, tag, cls, value) => {
17385
+ const n = el(doc, tag, cls);
17386
+ n.textContent = value;
17387
+ return n;
17388
+ };
17389
+ var prose = (doc, tag, cls, value) => {
17390
+ const n = el(doc, tag, cls);
17391
+ n.innerHTML = inlineMarkdown(value);
17392
+ return n;
17393
+ };
17394
+ var link = (doc, cls, label, href) => {
17395
+ if (!href)
17396
+ return text(doc, "span", cls, label);
17397
+ const a = text(doc, "a", cls, label);
17398
+ a.href = href;
17399
+ a.target = "_blank";
17400
+ a.rel = "noreferrer";
17401
+ return a;
17402
+ };
17403
+ function stepButton(doc, ref, steps) {
17404
+ const s = ref && steps?.find(ref);
17405
+ if (!s || !steps)
17406
+ return null;
17407
+ const b = text(doc, "button", "knobs-wt-steplink", s.number);
17408
+ b.type = "button";
17409
+ b.title = `Go to ${s.number} ${s.title}`;
17410
+ b.setAttribute("aria-label", b.title);
17411
+ b.addEventListener("click", (e) => {
17412
+ e.stopPropagation();
17413
+ steps.go(s.key);
17414
+ });
17415
+ return b;
17416
+ }
17417
+ var tags = (doc, list) => {
17418
+ if (!list?.length)
17419
+ return null;
17420
+ const row = el(doc, "div", "knobs-wt-tags");
17421
+ for (const e of list)
17422
+ row.append(link(doc, "knobs-wt-tag", e.label, e.href));
17423
+ return row;
17424
+ };
17425
+ function mountHistory(host, m, steps) {
17426
+ const doc = host.ownerDocument;
17427
+ const root = el(doc, "div", "knobs-wt-history");
17428
+ const longest = Math.max(6, ...m.groups.flatMap((g) => g.events.flatMap((e) => [e.date.length, e.age?.length ?? 0])));
17429
+ root.style.setProperty("--kw-hw", `${Math.min(16, longest + 1)}ch`);
17430
+ for (const g of m.groups) {
17431
+ const group = el(doc, "section", "knobs-wt-hgroup");
17432
+ if (g.heading || g.summary) {
17433
+ const head = el(doc, "header", "knobs-wt-hhead");
17434
+ if (g.heading)
17435
+ head.append(text(doc, "h4", "knobs-wt-hheading", g.heading));
17436
+ if (g.summary)
17437
+ head.append(text(doc, "span", "knobs-wt-hsum", g.summary));
17438
+ group.append(head);
17439
+ }
17440
+ const list = el(doc, "ol", "knobs-wt-hlist");
17441
+ for (const e of g.events)
17442
+ list.append(historyEvent(doc, e, steps));
17443
+ group.append(list);
17444
+ root.append(group);
17445
+ }
17446
+ host.append(root);
17447
+ return { dispose: () => root.remove(), context: () => ({ kind: "history" }) };
17448
+ }
17449
+ function historyEvent(doc, e, steps) {
17450
+ const li = el(doc, "li", "knobs-wt-hev");
17451
+ if (e.tone)
17452
+ li.dataset.tone = e.tone;
17453
+ if (e.type)
17454
+ li.dataset.type = e.type;
17455
+ if (e.provenance)
17456
+ li.dataset.provenance = e.provenance;
17457
+ const when = el(doc, "div", "knobs-wt-hwhen");
17458
+ when.append(text(doc, "time", "knobs-wt-hdate", e.date));
17459
+ if (e.age)
17460
+ when.append(text(doc, "span", "knobs-wt-hage", e.age));
17461
+ const spine = el(doc, "span", "knobs-wt-hspine");
17462
+ spine.setAttribute("aria-hidden", "true");
17463
+ spine.append(el(doc, "span", "knobs-wt-hdot"));
17464
+ const main = el(doc, "div", "knobs-wt-hmain");
17465
+ const title = el(doc, "div", "knobs-wt-htitle");
17466
+ title.append(text(doc, "span", "knobs-wt-htext", e.title));
17467
+ if (e.provenance || e.source) {
17468
+ const p = text(doc, "span", "knobs-wt-prov", [e.provenance, e.source].filter(Boolean).join(" · "));
17469
+ if (e.provenance === "inferred")
17470
+ p.title = "Inferred from behaviour, not seen in the record";
17471
+ else if (e.provenance === "verified")
17472
+ p.title = "Seen in the record";
17473
+ title.append(p);
17474
+ }
17475
+ const go = stepButton(doc, e.step, steps);
17476
+ if (go)
17477
+ title.append(go);
17478
+ main.append(title);
17479
+ if (e.detail)
17480
+ main.append(prose(doc, "p", "knobs-wt-hdetail", e.detail));
17481
+ const t = tags(doc, e.evidence);
17482
+ if (t)
17483
+ main.append(t);
17484
+ li.append(when, spine, main);
17485
+ return li;
17486
+ }
17487
+ function mountStack(host, m, steps) {
17488
+ const doc = host.ownerDocument;
17489
+ const layout = stackLayout(m);
17490
+ const root = el(doc, "div", "knobs-wt-stack");
17491
+ root.toggleAttribute("data-flat", layout.flat);
17492
+ const lane = (kind) => {
17493
+ const s = el(doc, "span", "knobs-wt-lane");
17494
+ s.dataset.rail = kind;
17495
+ return s;
17496
+ };
17497
+ const node = (down) => {
17498
+ const s = lane("node");
17499
+ s.toggleAttribute("data-down", down);
17500
+ return s;
17501
+ };
17502
+ const from = layout.trunk ? 0 : 1;
17503
+ if (layout.trunk && layout.rows.length) {
17504
+ const row = el(doc, "div", "knobs-wt-srow");
17505
+ row.dataset.trunk = "";
17506
+ const lanes = el(doc, "span", "knobs-wt-lanes");
17507
+ lanes.setAttribute("aria-hidden", "true");
17508
+ lanes.append(node(true));
17509
+ row.append(lanes, text(doc, "span", "knobs-wt-trunk", layout.trunk));
17510
+ root.append(row);
17511
+ }
17512
+ for (const r of layout.rows) {
17513
+ const it = r.item;
17514
+ const row = el(doc, "div", "knobs-wt-srow");
17515
+ if (!layout.flat) {
17516
+ const lanes = el(doc, "span", "knobs-wt-lanes");
17517
+ lanes.setAttribute("aria-hidden", "true");
17518
+ for (let k = from;k < r.depth; k++)
17519
+ lanes.append(lane(r.rails[k] ? "pass" : "none"));
17520
+ if (r.depth >= from)
17521
+ lanes.append(lane(r.last ? "end" : "tee"));
17522
+ lanes.append(node(r.children > 0));
17523
+ row.append(lanes);
17524
+ }
17525
+ const main = el(doc, "div", "knobs-wt-smain");
17526
+ const line = el(doc, "div", "knobs-wt-sline");
17527
+ if (it.order)
17528
+ line.append(text(doc, "span", "knobs-wt-sord", it.order));
17529
+ line.append(link(doc, "knobs-wt-spr", it.pr, it.href));
17530
+ const target = it.step ? steps?.find(it.step) : null;
17531
+ if (target && steps) {
17532
+ const title = text(doc, "button", "knobs-wt-stitle", it.title);
17533
+ title.type = "button";
17534
+ title.title = `Go to ${target.number} ${target.title}`;
17535
+ title.addEventListener("click", (e) => {
17536
+ e.stopPropagation();
17537
+ steps.go(target.key);
17538
+ });
17539
+ line.append(title);
17540
+ const go = stepButton(doc, it.step, steps);
17541
+ if (go)
17542
+ line.append(go);
17543
+ row.dataset.go = "";
17544
+ row.addEventListener("click", (e) => {
17545
+ const t = e.target;
17546
+ if (t?.closest("a,button"))
17547
+ return;
17548
+ if (doc.getSelection()?.toString())
17549
+ return;
17550
+ steps.go(target.key);
17551
+ });
17552
+ } else
17553
+ line.append(text(doc, "span", "knobs-wt-stitle", it.title));
17554
+ main.append(line);
17555
+ if (it.summary)
17556
+ main.append(prose(doc, "p", "knobs-wt-ssummary", it.summary));
17557
+ const meta = el(doc, "div", "knobs-wt-smeta");
17558
+ if (it.head) {
17559
+ const branch = r.offBase ? `${it.head} → ${r.offBase}` : it.head;
17560
+ meta.append(text(doc, "span", "knobs-wt-sbranch", branch));
17561
+ }
17562
+ if (it.delta !== undefined)
17563
+ meta.append(delta(doc, it.delta));
17564
+ if (meta.childNodes.length)
17565
+ main.append(meta);
17566
+ row.append(main);
17567
+ if (it.status) {
17568
+ const chip = text(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
17569
+ chip.dataset.tone = it.tone ?? "dim";
17570
+ row.append(chip);
17571
+ }
17572
+ root.append(row);
17573
+ }
17574
+ host.append(root);
17575
+ return { dispose: () => root.remove(), context: () => ({ kind: "stack" }) };
17576
+ }
17577
+ function delta(doc, d) {
17578
+ const s = el(doc, "span", "knobs-wt-delta");
17579
+ if (typeof d === "string") {
17580
+ s.textContent = d;
17581
+ return s;
17582
+ }
17583
+ const n = (v) => v.toLocaleString("en-US");
17584
+ s.append(text(doc, "span", "knobs-wt-add", `+${n(d.add)}`));
17585
+ s.append(text(doc, "span", "knobs-wt-del", `−${n(d.del)}`));
17586
+ s.setAttribute("aria-label", `${d.add} added, ${d.del} removed`);
17587
+ return s;
17588
+ }
17589
+ function mountTable(host, m, steps) {
17590
+ const doc = host.ownerDocument;
17591
+ const win = doc.defaultView;
17592
+ const scroller = el(doc, "div", "knobs-wt-table");
17593
+ const table = el(doc, "table");
17594
+ if (m.caption)
17595
+ table.append(text(doc, "caption", "", m.caption));
17596
+ const head = el(doc, "thead");
17597
+ const hr = el(doc, "tr");
17598
+ for (const c of m.columns) {
17599
+ const th = text(doc, "th", "", c.label);
17600
+ th.scope = "col";
17601
+ th.dataset.align = c.align;
17602
+ hr.append(th);
17603
+ }
17604
+ head.append(hr);
17605
+ const body = el(doc, "tbody");
17606
+ for (const r of m.rows) {
17607
+ const tr = el(doc, "tr");
17608
+ for (const c of m.columns) {
17609
+ const td = el(doc, "td");
17610
+ td.dataset.align = c.align;
17611
+ if (c.mono)
17612
+ td.dataset.mono = "";
17613
+ const cell = r[c.key];
17614
+ if (cell) {
17615
+ if (cell.text.length > 40 && !c.mono && !cell.code)
17616
+ td.dataset.long = "";
17617
+ let content;
17618
+ if (cell.badge) {
17619
+ content = link(doc, "knobs-wt-chip", cell.text, cell.href);
17620
+ content.dataset.tone = cell.badge;
17621
+ } else {
17622
+ const inner = cell.code ? text(doc, "code", "", cell.text) : null;
17623
+ if (cell.href) {
17624
+ content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
17625
+ if (inner)
17626
+ content.append(inner);
17627
+ } else
17628
+ content = inner ?? text(doc, "span", "", cell.text);
17629
+ if (cell.tone) {
17630
+ content.classList.add("knobs-wt-ctone");
17631
+ content.dataset.tone = cell.tone;
17632
+ }
17633
+ }
17634
+ td.append(content);
17635
+ const go = stepButton(doc, cell.step, steps);
17636
+ if (go)
17637
+ td.append(go);
17638
+ }
17639
+ tr.append(td);
17640
+ }
17641
+ body.append(tr);
17642
+ }
17643
+ table.append(head, body);
17644
+ scroller.append(table);
17645
+ host.append(scroller);
17646
+ const measure = () => {
17647
+ scroller.style.setProperty("--kw-thead", `${head.offsetHeight}px`);
17648
+ const scrolls = scroller.scrollWidth > scroller.clientWidth + 1 || scroller.scrollHeight > scroller.clientHeight + 1;
17649
+ if (scrolls) {
17650
+ scroller.tabIndex = 0;
17651
+ scroller.setAttribute("role", "region");
17652
+ scroller.setAttribute("aria-label", m.caption ?? "Table");
17653
+ } else {
17654
+ scroller.removeAttribute("tabindex");
17655
+ scroller.removeAttribute("role");
17656
+ scroller.removeAttribute("aria-label");
17657
+ }
17658
+ };
17659
+ const ro = win && "ResizeObserver" in win ? new win.ResizeObserver(measure) : null;
17660
+ ro?.observe(scroller);
17661
+ measure();
17662
+ return {
17663
+ dispose: () => {
17664
+ ro?.disconnect();
17665
+ scroller.remove();
17666
+ },
17667
+ context: () => ({ kind: "table" })
17668
+ };
17669
+ }
17670
+ var grid = (v) => Math.round(v * 64) / 64;
17671
+ function chapterPills(host, chapters, io) {
17672
+ const doc = host.ownerDocument;
17673
+ const win = doc.defaultView;
17674
+ const bar = el(doc, "div", "knobs-wt-chapters");
17675
+ bar.setAttribute("role", "group");
17676
+ bar.setAttribute("aria-label", "Chapters");
17677
+ const mark = el(doc, "span", "knobs-wt-chapter-mark");
17678
+ mark.setAttribute("aria-hidden", "true");
17679
+ bar.append(mark);
17680
+ const pills = chapters.map((c) => {
17681
+ const b = el(doc, "button", "knobs-wt-chapter");
17682
+ b.type = "button";
17683
+ b.append(text(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
17684
+ b.append(text(doc, "span", "knobs-wt-chapter-label", c.label));
17685
+ b.addEventListener("click", () => {
17686
+ io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
17687
+ io.seek(c.start);
17688
+ sync();
17689
+ });
17690
+ bar.append(b);
17691
+ return b;
17692
+ });
17693
+ host.append(bar);
17694
+ const x = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
17695
+ const y = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
17696
+ const w = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
17697
+ const draw = () => {
17698
+ const left = grid(x.x);
17699
+ mark.style.transform = `translate(${left}px,${grid(y.x)}px)`;
17700
+ mark.style.width = `${grid(x.x + w.x) - left}px`;
17701
+ };
17702
+ const loop = frameLoop(win, (dt) => {
17703
+ x.step(dt);
17704
+ y.step(dt);
17705
+ w.step(dt);
17706
+ draw();
17707
+ return !x.resting || !y.resting || !w.resting;
17708
+ });
17709
+ let at = -2;
17710
+ let shown = false;
17711
+ const place = (snap) => {
17712
+ const p = pills[at];
17713
+ if (!p) {
17714
+ mark.removeAttribute("data-on");
17715
+ pills.forEach((b) => b.removeAttribute("aria-current"));
17716
+ shown = false;
17717
+ return;
17718
+ }
17719
+ pills.forEach((b, i) => i === at ? b.setAttribute("aria-current", "true") : b.removeAttribute("aria-current"));
17720
+ const to = [p.offsetLeft, p.offsetTop, p.offsetWidth];
17721
+ if (snap || !shown || reducedMotion(win)) {
17722
+ x.snap(to[0]);
17723
+ y.snap(to[1]);
17724
+ w.snap(to[2]);
17725
+ draw();
17726
+ } else {
17727
+ x.set(to[0]);
17728
+ y.set(to[1]);
17729
+ w.set(to[2]);
17730
+ loop.wake();
17731
+ }
17732
+ mark.style.height = `${p.offsetHeight}px`;
17733
+ mark.dataset.on = "";
17734
+ shown = true;
17735
+ };
17736
+ const sync = () => {
17737
+ const next = chapterAt(chapters, io.now());
17738
+ if (next === at)
17739
+ return;
17740
+ at = next;
17741
+ place(false);
17742
+ };
17743
+ sync();
17744
+ const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => place(true)) : null;
17745
+ ro?.observe(bar);
17746
+ return {
17747
+ sync,
17748
+ get current() {
17749
+ return chapters[at] ?? null;
17750
+ },
17751
+ dispose() {
17752
+ loop.stop();
17753
+ ro?.disconnect();
17754
+ bar.remove();
17755
+ }
17756
+ };
17757
+ }
17758
+
17055
17759
  // src/ui/walkthrough-media.ts
17056
17760
  function injectWalkthroughStyles(doc) {
17057
17761
  if (doc.querySelector("style[data-knobs-ui-walkthrough]"))
@@ -17184,8 +17888,25 @@ async function mountCompare(host, m, look, alive, onEvent) {
17184
17888
  onEvent?.({ what: "loop", detail: lastLoop = loopKey });
17185
17889
  }
17186
17890
  });
17891
+ const chapters = m.chapters?.length ? m.chapters : null;
17892
+ if (chapters)
17893
+ c.timeline?.update({
17894
+ markers: chapters.map((ch) => ({ id: ch.id, time: ch.start, label: ch.label }))
17895
+ });
17896
+ const pills = chapters ? chapterPills(host, chapters, {
17897
+ now: () => c.time,
17898
+ seek: (t) => {
17899
+ c.seek(t);
17900
+ c.play();
17901
+ },
17902
+ onEvent
17903
+ }) : null;
17187
17904
  const unlisten = c.timeline?.listen({
17188
- onSeek: (time, source) => source !== "play" && onEvent?.({ what: "seek", time: round2(time) }),
17905
+ onSeek: (time, source) => {
17906
+ pills?.sync();
17907
+ if (source !== "play")
17908
+ onEvent?.({ what: "seek", time: round2(time) });
17909
+ },
17189
17910
  onPlay: (on) => onEvent?.({ what: on ? "play" : "pause", time: round2(c.time) })
17190
17911
  });
17191
17912
  requestAnimationFrame(() => requestAnimationFrame(() => fit.dataset.settled = ""));
@@ -17207,12 +17928,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
17207
17928
  duration: round2(c.duration),
17208
17929
  playing: c.playing,
17209
17930
  mode: c.mode,
17931
+ ...pills?.current ? { chapter: pills.current.label } : {},
17210
17932
  ...loop ? { loop: { start: round2(loop.start), end: round2(loop.end) } } : {},
17211
17933
  ...zoom ? { zoom: { u: round2(zoom.u), v: round2(zoom.v), w: round2(zoom.w), h: round2(zoom.h) } } : {}
17212
17934
  };
17213
17935
  },
17214
17936
  dispose: () => {
17215
17937
  unlisten?.();
17938
+ pills?.dispose();
17216
17939
  c.dispose();
17217
17940
  fit.remove();
17218
17941
  }
@@ -17236,6 +17959,15 @@ function mountVideo(host, m, onEvent) {
17236
17959
  v.poster = m.poster;
17237
17960
  if (!aspect)
17238
17961
  v.addEventListener("loadedmetadata", () => v.videoWidth && frame.style.setProperty("--kw-aspect", String(v.videoWidth / v.videoHeight)), { once: true });
17962
+ if (m.captions) {
17963
+ const track = doc.createElement("track");
17964
+ track.kind = "captions";
17965
+ track.label = "Captions";
17966
+ track.srclang = doc.documentElement.lang || "en";
17967
+ track.default = true;
17968
+ track.src = m.captions;
17969
+ v.append(track);
17970
+ }
17239
17971
  v.src = m.src;
17240
17972
  v.addEventListener("play", () => onEvent?.({ what: "play", time: round2(v.currentTime) }));
17241
17973
  v.addEventListener("pause", () => onEvent?.({ what: "pause", time: round2(v.currentTime) }));
@@ -17245,6 +17977,18 @@ function mountVideo(host, m, onEvent) {
17245
17977
  });
17246
17978
  frame.append(v);
17247
17979
  host.append(frame);
17980
+ const pills = m.chapters?.length ? chapterPills(host, m.chapters, {
17981
+ now: () => v.currentTime,
17982
+ seek: (t) => {
17983
+ v.currentTime = t;
17984
+ v.play().catch(() => {});
17985
+ },
17986
+ onEvent
17987
+ }) : null;
17988
+ const sync = () => pills?.sync();
17989
+ if (pills)
17990
+ for (const type of ["timeupdate", "seeking", "seeked"])
17991
+ v.addEventListener(type, sync);
17248
17992
  return {
17249
17993
  pause: () => v.pause(),
17250
17994
  seekBy: (d) => {
@@ -17254,9 +17998,11 @@ function mountVideo(host, m, onEvent) {
17254
17998
  kind: "video",
17255
17999
  time: round2(v.currentTime),
17256
18000
  ...Number.isFinite(v.duration) ? { duration: round2(v.duration) } : {},
17257
- playing: !v.paused
18001
+ playing: !v.paused,
18002
+ ...pills?.current ? { chapter: pills.current.label } : {}
17258
18003
  }),
17259
18004
  dispose: () => {
18005
+ pills?.dispose();
17260
18006
  v.pause();
17261
18007
  v.removeAttribute("src");
17262
18008
  v.load();
@@ -17480,6 +18226,12 @@ function mediaSlot(container, options) {
17480
18226
  case "callout":
17481
18227
  case "text":
17482
18228
  return done(mountProse(host, m));
18229
+ case "history":
18230
+ return done(mountHistory(host, m, options.steps));
18231
+ case "stack":
18232
+ return done(mountStack(host, m, options.steps));
18233
+ case "table":
18234
+ return done(mountTable(host, m, options.steps));
17483
18235
  case "mount": {
17484
18236
  const cleanup = m.mount(host);
17485
18237
  return done({ dispose: () => cleanup ? cleanup() : host.textContent = "" });
@@ -17534,14 +18286,14 @@ function mediaSlot(container, options) {
17534
18286
  var CHECK3 = svg('<path d="m3.5 8.4 3 3 6-6.6"/>');
17535
18287
 
17536
18288
  // src/ui/walkthrough.ts
17537
- var el = (doc, tag, cls) => {
18289
+ var el2 = (doc, tag, cls) => {
17538
18290
  const n = doc.createElement(tag);
17539
18291
  if (cls)
17540
18292
  n.className = cls;
17541
18293
  return n;
17542
18294
  };
17543
- var prose = (doc, tag, cls, text) => {
17544
- const n = el(doc, tag, cls);
18295
+ var prose2 = (doc, tag, cls, text) => {
18296
+ const n = el2(doc, tag, cls);
17545
18297
  n.innerHTML = inlineMarkdown(text);
17546
18298
  return n;
17547
18299
  };
@@ -17549,7 +18301,7 @@ var lookOf2 = (o) => ({
17549
18301
  ...o.preset ? { preset: o.preset } : {},
17550
18302
  ...o.theme ? { theme: o.theme } : {}
17551
18303
  });
17552
- var grid = (v) => Math.round(v * 64) / 64;
18304
+ var grid2 = (v) => Math.round(v * 64) / 64;
17553
18305
  var ICON = {
17554
18306
  full: svg('<path d="M3 6.5V3h3.5M13 6.5V3H9.5M3 9.5V13h3.5M13 9.5V13H9.5"/>'),
17555
18307
  exit: svg('<path d="M6.5 3v3.5H3M9.5 3v3.5H13M6.5 13V9.5H3M9.5 13V9.5H13"/>'),
@@ -17575,7 +18327,7 @@ function tasteTest(container, options) {
17575
18327
  suggested: o.suggested === true
17576
18328
  });
17577
18329
  let picked = options.picked ?? null;
17578
- const root = el(doc, "div", "knobs-wt-options");
18330
+ const root = el2(doc, "div", "knobs-wt-options");
17579
18331
  root.setAttribute("role", "radiogroup");
17580
18332
  root.setAttribute("aria-label", "Options");
17581
18333
  root.style.setProperty("--kw-n", String(Math.min(list.length, 3)));
@@ -17583,28 +18335,28 @@ function tasteTest(container, options) {
17583
18335
  applyLook2(root, options);
17584
18336
  const slots = [];
17585
18337
  const tiles = list.map((o, i) => {
17586
- const tile = el(doc, "div", "knobs-wt-option");
17587
- const pick = el(doc, "button", "knobs-wt-pick");
18338
+ const tile = el2(doc, "div", "knobs-wt-option");
18339
+ const pick = el2(doc, "button", "knobs-wt-pick");
17588
18340
  pick.type = "button";
17589
18341
  pick.setAttribute("role", "radio");
17590
18342
  if (i < 9)
17591
18343
  pick.setAttribute("aria-keyshortcuts", String(i + 1));
17592
- const letter = el(doc, "span", "knobs-wt-letter");
18344
+ const letter = el2(doc, "span", "knobs-wt-letter");
17593
18345
  letter.textContent = o.letter;
17594
- const name = el(doc, "span", "knobs-wt-olabel");
18346
+ const name = el2(doc, "span", "knobs-wt-olabel");
17595
18347
  name.textContent = o.label;
17596
18348
  pick.append(letter, name);
17597
18349
  if (o.suggested) {
17598
- const s = el(doc, "span", "knobs-wt-suggested");
18350
+ const s = el2(doc, "span", "knobs-wt-suggested");
17599
18351
  s.textContent = "Suggested";
17600
18352
  pick.append(s);
17601
18353
  }
17602
18354
  if (i < 9) {
17603
- const key = el(doc, "span", "knobs-wt-key");
18355
+ const key = el2(doc, "span", "knobs-wt-key");
17604
18356
  key.textContent = String(i + 1);
17605
18357
  pick.append(key);
17606
18358
  }
17607
- const check = el(doc, "span", "knobs-wt-check");
18359
+ const check = el2(doc, "span", "knobs-wt-check");
17608
18360
  check.innerHTML = CHECK3;
17609
18361
  pick.append(check);
17610
18362
  pick.addEventListener("click", () => {
@@ -17617,10 +18369,11 @@ function tasteTest(container, options) {
17617
18369
  slots.push(mediaSlot(tile, {
17618
18370
  ...look,
17619
18371
  media: o.media,
17620
- ...options.lazy === false ? { lazy: false } : {}
18372
+ ...options.lazy === false ? { lazy: false } : {},
18373
+ ...options.steps ? { steps: options.steps } : {}
17621
18374
  }));
17622
18375
  if (o.caption)
17623
- tile.append(prose(doc, "p", "knobs-wt-ocap", o.caption));
18376
+ tile.append(prose2(doc, "p", "knobs-wt-ocap", o.caption));
17624
18377
  root.append(tile);
17625
18378
  return { tile, pick };
17626
18379
  });
@@ -17668,7 +18421,7 @@ function walkthrough(container, options) {
17668
18421
  injectWalkthroughStyles(doc);
17669
18422
  const look = lookOf2(options);
17670
18423
  const storage = options.storage === undefined ? browserStorage() : options.storage;
17671
- const root = el(doc, "div", "knobs-walkthrough");
18424
+ const root = el2(doc, "div", "knobs-walkthrough");
17672
18425
  applyLook2(root, options);
17673
18426
  container.append(root);
17674
18427
  let page = normalizeWalkthrough(options.doc);
@@ -17728,8 +18481,8 @@ function walkthrough(container, options) {
17728
18481
  if (mark) {
17729
18482
  pillY.step(dt);
17730
18483
  pillH.step(dt);
17731
- mark.style.transform = `translateY(${grid(pillY.x)}px)`;
17732
- mark.style.height = `${grid(pillY.x + pillH.x) - grid(pillY.x)}px`;
18484
+ mark.style.transform = `translateY(${grid2(pillY.x)}px)`;
18485
+ mark.style.height = `${grid2(pillY.x + pillH.x) - grid2(pillY.x)}px`;
17733
18486
  more ||= !pillY.resting || !pillH.resting;
17734
18487
  }
17735
18488
  if (scroll.to && scroll.el) {
@@ -17864,6 +18617,10 @@ function walkthrough(container, options) {
17864
18617
  scrollToCard(v);
17865
18618
  v.card.focus({ preventScroll: true });
17866
18619
  };
18620
+ const stepLinks = {
18621
+ find: (ref) => findStep(page.steps, ref) ?? null,
18622
+ go: (key) => go(key)
18623
+ };
17867
18624
  const slotsOf = (v) => v ? [...v.slots, ...v.taste?.slots ?? []] : [];
17868
18625
  const mediaContext = (v) => {
17869
18626
  const slots = slotsOf(v);
@@ -17878,17 +18635,17 @@ function walkthrough(container, options) {
17878
18635
  return true;
17879
18636
  return false;
17880
18637
  };
17881
- const scrim = el(doc, "div", "knobs-wt-scrim");
18638
+ const scrim = el2(doc, "div", "knobs-wt-scrim");
17882
18639
  scrim.addEventListener("click", () => setFull(false));
17883
18640
  const fullRect = () => ({ x: 12, y: 12, w: win.innerWidth - 24, h: win.innerHeight - 24 });
17884
18641
  const placeFull = (f) => {
17885
18642
  const a = f.hold.getBoundingClientRect();
17886
18643
  const b = fullRect();
17887
18644
  const k = reduced() ? Math.round(f.p.target) : f.p.x;
17888
- const left = grid(a.left + (b.x - a.left) * k);
17889
- const top = grid(a.top + (b.y - a.top) * k);
17890
- const right = grid(a.right + (b.x + b.w - a.right) * k);
17891
- const bottom = grid(a.bottom + (b.y + b.h - a.bottom) * k);
18645
+ const left = grid2(a.left + (b.x - a.left) * k);
18646
+ const top = grid2(a.top + (b.y - a.top) * k);
18647
+ const right = grid2(a.right + (b.x + b.w - a.right) * k);
18648
+ const bottom = grid2(a.bottom + (b.y + b.h - a.bottom) * k);
17892
18649
  const s = f.v.card.style;
17893
18650
  s.left = `${left}px`;
17894
18651
  s.top = `${top}px`;
@@ -17923,7 +18680,7 @@ function walkthrough(container, options) {
17923
18680
  }
17924
18681
  if (!full) {
17925
18682
  const r = v.card.getBoundingClientRect();
17926
- const hold = el(doc, "div", "knobs-wt-hold-card");
18683
+ const hold = el2(doc, "div", "knobs-wt-hold-card");
17927
18684
  hold.style.height = `${r.height}px`;
17928
18685
  v.card.before(hold);
17929
18686
  v.card.dataset.full = "";
@@ -18071,7 +18828,7 @@ function walkthrough(container, options) {
18071
18828
  toast("Couldn't reach the microphone", { tone: "warn", look, doc });
18072
18829
  }
18073
18830
  };
18074
- const backPill = el(doc, "button", "knobs-wt-back");
18831
+ const backPill = el2(doc, "button", "knobs-wt-back");
18075
18832
  backPill.type = "button";
18076
18833
  let backTo = null;
18077
18834
  let backTimer = 0;
@@ -18096,16 +18853,16 @@ function walkthrough(container, options) {
18096
18853
  backTimer = win.setTimeout(hideBack, BACK_MS);
18097
18854
  };
18098
18855
  const bar = (() => {
18099
- const dock = el(doc, "div", "knobs-wt-dock");
18100
- const box = el(doc, "div", "knobs-wt-notebar");
18101
- const on = el(doc, "div", "knobs-wt-on");
18102
- const row = el(doc, "div", "knobs-wt-row");
18103
- const input = el(doc, "textarea", "knobs-wt-input");
18856
+ const dock = el2(doc, "div", "knobs-wt-dock");
18857
+ const box = el2(doc, "div", "knobs-wt-notebar");
18858
+ const on = el2(doc, "div", "knobs-wt-on");
18859
+ const row = el2(doc, "div", "knobs-wt-row");
18860
+ const input = el2(doc, "textarea", "knobs-wt-input");
18104
18861
  input.rows = 1;
18105
18862
  input.setAttribute("aria-label", "Note");
18106
- const attach = el(doc, "div", "knobs-wt-attach");
18863
+ const attach = el2(doc, "div", "knobs-wt-attach");
18107
18864
  const btn = (icon, label, run) => {
18108
- const b = el(doc, "button", "knobs-wt-tool");
18865
+ const b = el2(doc, "button", "knobs-wt-tool");
18109
18866
  b.type = "button";
18110
18867
  b.innerHTML = icon;
18111
18868
  b.setAttribute("aria-label", label);
@@ -18116,7 +18873,7 @@ function walkthrough(container, options) {
18116
18873
  const fullBtn = btn(ICON.full, "Full window · f", () => setFull(!full));
18117
18874
  const penBtn = btn(ICON.pen, "Draw on the media", () => setInk(!ink.on));
18118
18875
  const micBtn = btn(ICON.mic, "Record a voice note", () => void toggleVoice());
18119
- const micTime = el(doc, "span", "knobs-wt-rec");
18876
+ const micTime = el2(doc, "span", "knobs-wt-rec");
18120
18877
  micBtn.append(micTime);
18121
18878
  const sendBtn = btn(ICON.send, "Send · Enter", () => send());
18122
18879
  sendBtn.dataset.send = "";
@@ -18142,9 +18899,9 @@ function walkthrough(container, options) {
18142
18899
  }
18143
18900
  });
18144
18901
  const chip = (text, clear) => {
18145
- const c = el(doc, "span", "knobs-wt-chip-a");
18902
+ const c = el2(doc, "span", "knobs-wt-chip-a");
18146
18903
  c.append(text);
18147
- const x = el(doc, "button", "knobs-wt-x");
18904
+ const x = el2(doc, "button", "knobs-wt-x");
18148
18905
  x.type = "button";
18149
18906
  x.innerHTML = ICON.close;
18150
18907
  x.setAttribute("aria-label", `Remove ${text}`);
@@ -18257,9 +19014,9 @@ function walkthrough(container, options) {
18257
19014
  }
18258
19015
  };
18259
19016
  const renderNote = (v, n, entering = false) => {
18260
- const row = el(doc, "div", "knobs-wt-n");
19017
+ const row = el2(doc, "div", "knobs-wt-n");
18261
19018
  row.dataset.id = n.id;
18262
- const meta = el(doc, "span", "knobs-wt-n-meta");
19019
+ const meta = el2(doc, "span", "knobs-wt-n-meta");
18263
19020
  const m = n.context.media;
18264
19021
  meta.textContent = m?.time !== undefined ? formatTime(m.time) : m?.file ? m.file.split("/").pop() : "·";
18265
19022
  if (m?.time !== undefined) {
@@ -18275,11 +19032,11 @@ function walkthrough(container, options) {
18275
19032
  });
18276
19033
  }
18277
19034
  row.append(meta);
18278
- const body = el(doc, "div", "knobs-wt-n-body");
19035
+ const body = el2(doc, "div", "knobs-wt-n-body");
18279
19036
  if (n.text)
18280
- body.append(prose(doc, "p", "knobs-wt-n-text", n.text));
19037
+ body.append(prose2(doc, "p", "knobs-wt-n-text", n.text));
18281
19038
  if (n.voice) {
18282
- const a = el(doc, "audio", "knobs-wt-n-voice");
19039
+ const a = el2(doc, "audio", "knobs-wt-n-voice");
18283
19040
  a.controls = true;
18284
19041
  a.preload = "none";
18285
19042
  a.src = n.voice.url;
@@ -18299,7 +19056,7 @@ function walkthrough(container, options) {
18299
19056
  body.append(thumb);
18300
19057
  }
18301
19058
  row.append(body);
18302
- const x = el(doc, "button", "knobs-wt-x");
19059
+ const x = el2(doc, "button", "knobs-wt-x");
18303
19060
  x.type = "button";
18304
19061
  x.innerHTML = ICON.close;
18305
19062
  x.setAttribute("aria-label", "Delete this note");
@@ -18353,22 +19110,22 @@ function walkthrough(container, options) {
18353
19110
  pillPlaced = false;
18354
19111
  root.textContent = "";
18355
19112
  root.removeAttribute("data-settled");
18356
- const layout = el(doc, "div", "knobs-wt-layout");
18357
- const main = el(doc, "div", "knobs-wt-main");
18358
- const head = el(doc, "header", "knobs-wt-head");
18359
- const h1 = el(doc, "h1", "knobs-wt-title");
19113
+ const layout = el2(doc, "div", "knobs-wt-layout");
19114
+ const main = el2(doc, "div", "knobs-wt-main");
19115
+ const head = el2(doc, "header", "knobs-wt-head");
19116
+ const h1 = el2(doc, "h1", "knobs-wt-title");
18360
19117
  h1.textContent = page.title;
18361
19118
  head.append(h1);
18362
19119
  if (page.summary)
18363
- head.append(prose(doc, "p", "knobs-wt-summary", page.summary));
19120
+ head.append(prose2(doc, "p", "knobs-wt-summary", page.summary));
18364
19121
  if (page.meta.length) {
18365
- const meta = el(doc, "div", "knobs-wt-meta");
19122
+ const meta = el2(doc, "div", "knobs-wt-meta");
18366
19123
  for (const m of page.meta) {
18367
- const s = el(doc, "span");
19124
+ const s = el2(doc, "span");
18368
19125
  s.append(m.label);
18369
- const b = el(doc, "b");
19126
+ const b = el2(doc, "b");
18370
19127
  if (m.href) {
18371
- const a = el(doc, "a");
19128
+ const a = el2(doc, "a");
18372
19129
  a.href = m.href;
18373
19130
  a.target = "_blank";
18374
19131
  a.rel = "noreferrer";
@@ -18383,27 +19140,27 @@ function walkthrough(container, options) {
18383
19140
  }
18384
19141
  main.append(head);
18385
19142
  if (page.feed.length) {
18386
- const feed = el(doc, "section", "knobs-wt-feed");
19143
+ const feed = el2(doc, "section", "knobs-wt-feed");
18387
19144
  feed.setAttribute("aria-label", "Latest");
18388
- const list = el(doc, "ol", "knobs-wt-feed-list knobs-scroll-fade");
19145
+ const list = el2(doc, "ol", "knobs-wt-feed-list knobs-scroll-fade");
18389
19146
  const now = Date.now();
18390
19147
  for (const f of page.feed) {
18391
- const li = el(doc, "li", "knobs-wt-fi");
19148
+ const li = el2(doc, "li", "knobs-wt-fi");
18392
19149
  li.dataset.kind = f.kind;
18393
- const when = el(doc, "span", "knobs-wt-fi-when");
19150
+ const when = el2(doc, "span", "knobs-wt-fi-when");
18394
19151
  when.textContent = f.at === null ? "" : ago(f.at, now);
18395
19152
  if (f.at !== null)
18396
19153
  when.title = new Date(f.at).toLocaleString();
18397
- const kind = el(doc, "span", "knobs-wt-fi-kind");
19154
+ const kind = el2(doc, "span", "knobs-wt-fi-kind");
18398
19155
  kind.textContent = f.kind === "qa" ? "Answer" : "Update";
18399
- const body = el(doc, "div", "knobs-wt-fi-body");
19156
+ const body = el2(doc, "div", "knobs-wt-fi-body");
18400
19157
  if (f.q)
18401
- body.append(prose(doc, "p", "knobs-wt-fi-q", f.q));
18402
- body.append(prose(doc, "p", "knobs-wt-fi-text", f.text));
19158
+ body.append(prose2(doc, "p", "knobs-wt-fi-q", f.q));
19159
+ body.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
18403
19160
  li.append(when, kind, body);
18404
19161
  if (f.step) {
18405
19162
  const target = f.step;
18406
- const link = el(doc, "button", "knobs-wt-fi-step");
19163
+ const link = el2(doc, "button", "knobs-wt-fi-step");
18407
19164
  link.type = "button";
18408
19165
  link.textContent = page.steps.find((s) => s.key === target)?.number ?? "";
18409
19166
  link.title = "Go to the step";
@@ -18418,33 +19175,33 @@ function walkthrough(container, options) {
18418
19175
  let rail = null;
18419
19176
  count = fill = tocEl = mark = null;
18420
19177
  if (options.rail !== false) {
18421
- rail = el(doc, "aside", "knobs-wt-rail");
19178
+ rail = el2(doc, "aside", "knobs-wt-rail");
18422
19179
  rail.setAttribute("aria-label", "Progress");
18423
- const progress = el(doc, "div", "knobs-wt-progress");
18424
- count = el(doc, "div", "knobs-wt-count");
18425
- const meter = el(doc, "div", "knobs-wt-bar");
18426
- fill = el(doc, "div", "knobs-wt-fill");
19180
+ const progress = el2(doc, "div", "knobs-wt-progress");
19181
+ count = el2(doc, "div", "knobs-wt-count");
19182
+ const meter = el2(doc, "div", "knobs-wt-bar");
19183
+ fill = el2(doc, "div", "knobs-wt-fill");
18427
19184
  meter.append(fill);
18428
19185
  progress.append(count, meter);
18429
- tocEl = el(doc, "nav", "knobs-wt-toc");
19186
+ tocEl = el2(doc, "nav", "knobs-wt-toc");
18430
19187
  tocEl.setAttribute("aria-label", "Contents");
18431
- mark = el(doc, "div", "knobs-wt-toc-mark");
19188
+ mark = el2(doc, "div", "knobs-wt-toc-mark");
18432
19189
  tocEl.append(mark);
18433
19190
  rail.append(progress, tocEl, actions(true));
18434
19191
  if (options.keys !== false) {
18435
- const keys = el(doc, "div", "knobs-wt-keys");
19192
+ const keys = el2(doc, "div", "knobs-wt-keys");
18436
19193
  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" : "");
18437
19194
  rail.append(keys);
18438
19195
  }
18439
19196
  }
18440
19197
  for (const section of page.sections) {
18441
- const sec = el(doc, "section", "knobs-wt-section");
19198
+ const sec = el2(doc, "section", "knobs-wt-section");
18442
19199
  sec.id = `wt-${section.id}`;
18443
- const sh = el(doc, "header", "knobs-wt-shead");
19200
+ const sh = el2(doc, "header", "knobs-wt-shead");
18444
19201
  if (section.link || section.status) {
18445
- const kicker = el(doc, "div", "knobs-wt-kicker");
19202
+ const kicker = el2(doc, "div", "knobs-wt-kicker");
18446
19203
  if (section.link) {
18447
- const a = el(doc, "a");
19204
+ const a = el2(doc, "a");
18448
19205
  a.href = section.link.href;
18449
19206
  a.target = "_blank";
18450
19207
  a.rel = "noreferrer";
@@ -18453,21 +19210,21 @@ function walkthrough(container, options) {
18453
19210
  }
18454
19211
  if (section.status) {
18455
19212
  const s = statusLook(section.status);
18456
- const chip = el(doc, "span", "knobs-wt-chip");
19213
+ const chip = el2(doc, "span", "knobs-wt-chip");
18457
19214
  chip.dataset.tone = s.tone;
18458
19215
  chip.textContent = s.label;
18459
19216
  kicker.append(chip);
18460
19217
  }
18461
19218
  sh.append(kicker);
18462
19219
  }
18463
- const h2 = el(doc, "h2", "knobs-wt-stitle");
19220
+ const h2 = el2(doc, "h2", "knobs-wt-stitle");
18464
19221
  h2.textContent = section.title;
18465
19222
  sh.append(h2);
18466
19223
  if (section.summary)
18467
- sh.append(prose(doc, "p", "knobs-wt-ssum", section.summary));
19224
+ sh.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
18468
19225
  sec.append(sh);
18469
19226
  if (tocEl) {
18470
- const t = el(doc, "div", "knobs-wt-toc-section");
19227
+ const t = el2(doc, "div", "knobs-wt-toc-section");
18471
19228
  t.textContent = section.link ? `${section.link.label} ${section.title}` : section.title;
18472
19229
  t.title = section.title;
18473
19230
  tocEl.append(t);
@@ -18500,9 +19257,9 @@ function walkthrough(container, options) {
18500
19257
  }));
18501
19258
  };
18502
19259
  const actions = (rail) => {
18503
- const row = el(doc, "div", "knobs-wt-actions");
19260
+ const row = el2(doc, "div", "knobs-wt-actions");
18504
19261
  const btn = (text, run, primary = false) => {
18505
- const b = el(doc, "button", "knobs-wt-btn");
19262
+ const b = el2(doc, "button", "knobs-wt-btn");
18506
19263
  b.type = "button";
18507
19264
  b.textContent = text;
18508
19265
  if (primary)
@@ -18540,43 +19297,44 @@ function walkthrough(container, options) {
18540
19297
  }
18541
19298
  };
18542
19299
  const stepView = (step) => {
18543
- const card = el(doc, "article", "knobs-wt-step knobs-surface");
19300
+ const card = el2(doc, "article", "knobs-wt-step knobs-surface");
18544
19301
  card.id = `wt-${step.key.replace("/", "--")}`;
18545
19302
  card.tabIndex = -1;
18546
19303
  card.dataset.key = step.key;
18547
19304
  card.setAttribute("aria-label", `${step.number} ${step.title}`);
18548
- const head = el(doc, "header", "knobs-wt-step-head");
18549
- const num = el(doc, "span", "knobs-wt-num");
19305
+ const head = el2(doc, "header", "knobs-wt-step-head");
19306
+ const num = el2(doc, "span", "knobs-wt-num");
18550
19307
  num.textContent = step.number;
18551
- const h3 = el(doc, "h3", "knobs-wt-stepTitle");
19308
+ const h3 = el2(doc, "h3", "knobs-wt-stepTitle");
18552
19309
  h3.textContent = step.title;
18553
19310
  head.append(num, h3);
18554
19311
  card.append(head);
18555
19312
  if (step.caption)
18556
- card.append(prose(doc, "p", "knobs-wt-caption", step.caption));
19313
+ card.append(prose2(doc, "p", "knobs-wt-caption", step.caption));
18557
19314
  const slots = [];
18558
19315
  const onEvent = (e) => record({ ...e, step: step.key });
18559
19316
  let media = null;
18560
19317
  if (step.media.length) {
18561
- media = el(doc, "div", "knobs-wt-media");
19318
+ media = el2(doc, "div", "knobs-wt-media");
18562
19319
  media.style.setProperty("--kw-n", String(Math.min(step.media.length, 3)));
18563
19320
  for (const m of step.media)
18564
- slots.push(mediaSlot(media, { ...look, media: m, onEvent }));
19321
+ slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
18565
19322
  card.append(media);
18566
19323
  }
18567
19324
  let taste = null;
18568
19325
  if (step.question)
18569
- card.append(prose(doc, "p", "knobs-wt-question", step.question));
19326
+ card.append(prose2(doc, "p", "knobs-wt-question", step.question));
18570
19327
  if (step.ask === "pick") {
18571
19328
  taste = tasteTest(card, {
18572
19329
  ...look,
18573
19330
  options: step.options,
18574
19331
  picked: state.steps[step.key]?.pick ?? null,
18575
- onPick: (id) => pick(step.key, id)
19332
+ onPick: (id) => pick(step.key, id),
19333
+ steps: stepLinks
18576
19334
  });
18577
19335
  media ??= taste.element;
18578
19336
  }
18579
- const notes = el(doc, "div", "knobs-wt-notes");
19337
+ const notes = el2(doc, "div", "knobs-wt-notes");
18580
19338
  card.append(notes);
18581
19339
  card.addEventListener("focusin", () => {
18582
19340
  if (scroll.to)
@@ -18587,10 +19345,10 @@ function walkthrough(container, options) {
18587
19345
  let toc = null;
18588
19346
  let glyph = null;
18589
19347
  if (tocEl) {
18590
- toc = el(doc, "button", "knobs-wt-toc-step");
19348
+ toc = el2(doc, "button", "knobs-wt-toc-step");
18591
19349
  toc.type = "button";
18592
- glyph = el(doc, "span", "knobs-wt-toc-glyph");
18593
- const label = el(doc, "span", "knobs-wt-toc-label");
19350
+ glyph = el2(doc, "span", "knobs-wt-toc-glyph");
19351
+ const label = el2(doc, "span", "knobs-wt-toc-label");
18594
19352
  label.textContent = step.title;
18595
19353
  toc.title = `${step.number} ${step.title}`;
18596
19354
  toc.append(glyph, label);