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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui-react.js CHANGED
@@ -596,6 +596,8 @@ var compareCss = `
596
596
  .knobs-cmp-stage{position:relative;flex:1;min-height:96px;overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_ku-cmp-stage);touch-action:pan-y;outline:none}
597
597
  .knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
598
598
  .knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
599
+ /* Videos: a click on the stage plays and pauses. */
600
+ .knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
599
601
  /* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
600
602
  .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
601
603
  .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
@@ -605,14 +607,28 @@ var compareCss = `
605
607
  .knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
606
608
  .knobs-cmp-media{position:absolute;left:0;top:0;display:block;max-width:none;margin:0;padding:0;border:0;object-fit:fill;transform-origin:0 0;pointer-events:none;will-change:transform}
607
609
  .knobs-compare[data-renderer="gpu"] .knobs-cmp-media{opacity:0}
610
+ /* The media comes in with its first frames: a fade and a 4px blur on the default spring. */
611
+ .knobs-cmp-canvas,.knobs-compare[data-renderer="dom"] .knobs-cmp-media{transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
612
+ .knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}
608
613
  /* Glass: what's behind shows through, blurred, on the knobs tokens. */
609
614
  .knobs-cmp-label,.knobs-cmp-zoom{background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
610
615
  box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim),0 2px 10px rgba(0,0,0,calc(.18 * var(--_k-shadow-k,1)))}
611
- .knobs-cmp-label{position:absolute;left:0;top:0;padding:1px 7px;border-radius:999px;font-size:var(--_k-text-meta);line-height:16px;color:var(--_k-fg-2);white-space:nowrap;pointer-events:none;will-change:transform;transition:color calc(var(--_k-hover-out) * 1ms)}
612
- /* The wipe: a hairline down the line with a raised grip at the pointer's height; a wider strip
613
- takes the hand and the keys (a slider). It fades with the mode; wiping, line and grip brighten. */
614
- .knobs-cmp-handle{cursor:ew-resize;position:absolute;left:-12px;top:0;width:24px;height:0;opacity:0;pointer-events:none;outline:none;will-change:transform;transition:opacity .18s cubic-bezier(.4,0,1,1)}
615
- .knobs-cmp-handle[data-on]{opacity:1;pointer-events:auto;transition:opacity .22s cubic-bezier(.2,.8,.2,1)}
616
+ /* ✗ Before · ✓ After: Before quieter (fg-3), After in full text. Neutral: no colour of its own. */
617
+ .knobs-cmp-label{position:absolute;left:0;top:0;display:flex;align-items:center;gap:4px;padding:1px 7px 1px 5px;border-radius:999px;font-size:var(--_k-text-meta);line-height:16px;font-weight:var(--_k-weight-label);
618
+ white-space:nowrap;pointer-events:none;will-change:transform}
619
+ .knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
620
+ .knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
621
+ /* Fusing, the mark folds away and the label is its name. */
622
+ .knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}
623
+ .knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}
624
+ /* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */
625
+ .knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;
626
+ -webkit-mask-image:linear-gradient(to right,transparent,#000 35%,#000 65%,transparent);mask-image:linear-gradient(to right,transparent,#000 35%,#000 65%,transparent)}
627
+ /* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height
628
+ while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the
629
+ fuse; wiping, line and grip brighten. */
630
+ .knobs-cmp-handle{cursor:ew-resize;position:absolute;left:-12px;top:0;width:24px;height:0;opacity:0;pointer-events:none;outline:none;will-change:transform,opacity}
631
+ .knobs-cmp-handle[data-on]{pointer-events:auto}
616
632
  .knobs-cmp-handle::before{content:"";position:absolute;top:0;bottom:0;left:11.5px;width:1px;background:color-mix(in srgb,var(--_ku-cmp-line) 72%,transparent);box-shadow:0 0 0 .5px rgba(0,0,0,.28),0 0 6px rgba(0,0,0,.25)}
617
633
  .knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
618
634
  .knobs-cmp-grip{position:absolute;top:0;left:4px;width:16px;height:28px;border-radius:min(8px,calc(var(--_k-radius) * 999));background:var(--_k-bg-overlay);
@@ -644,6 +660,27 @@ var compareCss = `
644
660
  @media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}
645
661
  `;
646
662
 
663
+ // src/ui/player-css.ts
664
+ var playerCss = `
665
+ :where(.knobs-player,.knobs-player *){box-sizing:border-box}
666
+ .knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
667
+ font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
668
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-player,.knobs-ui.knobs-player[data-nested]{--_kp-stage:var(--k-compare-stage,transparent)}
669
+ .knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage);cursor:pointer}
670
+ .knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
671
+ .knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
672
+ opacity:0;filter:blur(4px);transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
673
+ .knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
674
+ .knobs-player-glyph{position:absolute;left:50%;top:50%;display:grid;place-items:center;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;color:var(--_k-fg);pointer-events:none;
675
+ opacity:0;filter:blur(4px);scale:.96;
676
+ background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
677
+ box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
678
+ transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),filter calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
679
+ .knobs-player-glyph svg{width:16px;height:16px}
680
+ .knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
681
+ .knobs-player-line{flex:none;min-height:0}
682
+ `;
683
+
647
684
  // src/ui/overlay-css.ts
648
685
  var overlayCss = `
649
686
  .knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}
@@ -1332,6 +1369,22 @@ var init_schema = __esm(() => {
1332
1369
  source: css("var(--k-warn)"),
1333
1370
  doc: "Modified lines."
1334
1371
  },
1372
+ {
1373
+ name: "before",
1374
+ tier: "semantic",
1375
+ type: "color",
1376
+ group: "Data",
1377
+ source: css("var(--k-fg-3)"),
1378
+ doc: "Before in a comparison, and only Before: its label and side of the seam. Neutral unless a theme gives it a colour."
1379
+ },
1380
+ {
1381
+ name: "after",
1382
+ tier: "semantic",
1383
+ type: "color",
1384
+ group: "Data",
1385
+ source: css("var(--k-fg)"),
1386
+ doc: "After in a comparison, and only After."
1387
+ },
1335
1388
  {
1336
1389
  name: "series-1",
1337
1390
  tier: "semantic",
@@ -1422,6 +1475,150 @@ var init_schema = __esm(() => {
1422
1475
  source: value("288px"),
1423
1476
  doc: "Panel width."
1424
1477
  },
1478
+ {
1479
+ name: "space-inside",
1480
+ tier: "semantic",
1481
+ type: "length",
1482
+ group: "Space",
1483
+ source: css("var(--k-unit)"),
1484
+ doc: "Parts of one thing: an icon and its label, a meta line and its title."
1485
+ },
1486
+ {
1487
+ name: "space-sibling",
1488
+ tier: "semantic",
1489
+ type: "length",
1490
+ group: "Space",
1491
+ source: css("calc(var(--k-unit) * 2)"),
1492
+ doc: "Siblings in a group: buttons in a row, a list item's lines."
1493
+ },
1494
+ {
1495
+ name: "space-title",
1496
+ tier: "semantic",
1497
+ type: "length",
1498
+ group: "Space",
1499
+ source: css("calc(var(--k-unit) * 3)"),
1500
+ doc: "A heading and what it heads."
1501
+ },
1502
+ {
1503
+ name: "space-group",
1504
+ tier: "semantic",
1505
+ type: "length",
1506
+ group: "Space",
1507
+ source: css("calc(var(--k-unit) * 12)"),
1508
+ doc: "Groups inside a section (prose, video, diff), stacked media, feed items (a hairline in the middle)."
1509
+ },
1510
+ {
1511
+ name: "space-section",
1512
+ tier: "semantic",
1513
+ type: "length",
1514
+ group: "Space",
1515
+ source: css("calc(var(--k-unit) * 24)"),
1516
+ doc: "Sections of a page (the intro, the updates, each PR), a hairline in the middle."
1517
+ },
1518
+ {
1519
+ name: "space-column",
1520
+ tier: "semantic",
1521
+ type: "length",
1522
+ group: "Space",
1523
+ source: css("calc(var(--k-unit) * 6)"),
1524
+ doc: "Columns: a contents rail and the page, media side by side."
1525
+ },
1526
+ {
1527
+ name: "pad-page",
1528
+ tier: "semantic",
1529
+ type: "length",
1530
+ group: "Space",
1531
+ source: css("calc(var(--k-unit) * 6)"),
1532
+ doc: "A page's edge; 16px under 640px wide."
1533
+ },
1534
+ {
1535
+ name: "pad-card",
1536
+ tier: "semantic",
1537
+ type: "length",
1538
+ group: "Space",
1539
+ source: css("calc(var(--k-unit) * 3)"),
1540
+ doc: "Inside a card or popover."
1541
+ },
1542
+ {
1543
+ name: "type-heading",
1544
+ tier: "semantic",
1545
+ type: "length",
1546
+ group: "Type",
1547
+ source: value("20px"),
1548
+ doc: "A page's title, weight 500."
1549
+ },
1550
+ {
1551
+ name: "leading-heading",
1552
+ tier: "semantic",
1553
+ type: "length",
1554
+ group: "Type",
1555
+ source: value("28px"),
1556
+ doc: "Line height for type-heading."
1557
+ },
1558
+ {
1559
+ name: "type-title",
1560
+ tier: "semantic",
1561
+ type: "length",
1562
+ group: "Type",
1563
+ source: value("16px"),
1564
+ doc: "Section titles (under their kicker), weight 500."
1565
+ },
1566
+ {
1567
+ name: "leading-title",
1568
+ tier: "semantic",
1569
+ type: "length",
1570
+ group: "Type",
1571
+ source: value("24px"),
1572
+ doc: "Line height for type-title."
1573
+ },
1574
+ {
1575
+ name: "type-body",
1576
+ tier: "semantic",
1577
+ type: "length",
1578
+ group: "Type",
1579
+ source: value("13px"),
1580
+ doc: "Prose, questions, list rows, step titles; 400, or 500 for emphasis."
1581
+ },
1582
+ {
1583
+ name: "leading-body",
1584
+ tier: "semantic",
1585
+ type: "length",
1586
+ group: "Type",
1587
+ source: value("20px"),
1588
+ doc: "Line height for type-body."
1589
+ },
1590
+ {
1591
+ name: "type-label",
1592
+ tier: "semantic",
1593
+ type: "length",
1594
+ group: "Type",
1595
+ source: value("12px"),
1596
+ doc: "Mono inside body text: code and paths."
1597
+ },
1598
+ {
1599
+ name: "leading-label",
1600
+ tier: "semantic",
1601
+ type: "length",
1602
+ group: "Type",
1603
+ source: value("16px"),
1604
+ doc: "Line height for type-label."
1605
+ },
1606
+ {
1607
+ name: "type-small",
1608
+ tier: "semantic",
1609
+ type: "length",
1610
+ group: "Type",
1611
+ source: value("11px"),
1612
+ doc: "Meta, controls, keys, timestamps; mono inside labels."
1613
+ },
1614
+ {
1615
+ name: "leading-small",
1616
+ tier: "semantic",
1617
+ type: "length",
1618
+ group: "Type",
1619
+ source: value("16px"),
1620
+ doc: "Line height for type-small."
1621
+ },
1425
1622
  {
1426
1623
  name: "font-size",
1427
1624
  tier: "semantic",
@@ -2386,16 +2583,18 @@ function injectTokens(doc) {
2386
2583
  style.textContent = rootCss;
2387
2584
  doc.head.append(style);
2388
2585
  }
2389
- var ROOT_CLASS = "knobs-root", extras, spring, rootCss;
2586
+ var ROOT_CLASS = "knobs-root", extras, narrow, spring, rootCss;
2390
2587
  var init_root = __esm(() => {
2391
2588
  init_knobs();
2392
2589
  init_css();
2393
2590
  init_glide();
2394
2591
  init_motion();
2395
2592
  extras = `:where(.${ROOT_CLASS}){--_k-shadow-k:calc(var(--k-shadow-strength,calc(.48 - .32 * var(--_k-light,0))) / .48);--_k-glass-tone:var(--k-glass-tone,calc(.5 + .58 * var(--_k-light,0)));--_k-fill-base:var(--k-fill-base,var(--_k-fill-selected));--_k-fill-hover-mix:var(--k-fill-hover-mix,var(--k-fill-rest-mix,20%));--_k-reveal-ease:var(--k-ease,cubic-bezier(.2,.8,.2,1))}`;
2593
+ narrow = `@media(max-width:639.98px){:where(.${ROOT_CLASS}){--_k-pad-page:var(--k-pad-page,calc(var(--_k-unit) * 4))}}`;
2396
2594
  spring = `:where(:root,:host){--k-fill-ease:${DEFAULT_LINEAR.easing};--k-fill-ms:${DEFAULT_LINEAR.ms}}`;
2397
2595
  rootCss = `${tokensCss(knobs)}
2398
2596
  ${extras}
2597
+ ${narrow}
2399
2598
  ${spring}
2400
2599
  ${DRIVERS}
2401
2600
  ${motionCss}`;
@@ -2606,6 +2805,12 @@ var css2 = `
2606
2805
  --_ku-pane-bg and --_ku-pane-ring so this reaches them. */
2607
2806
  :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui:not(.knobs-surface),.knobs-ui[data-nested]{--_ku-gap:var(--k-ui-gap,0px);--_ku-pane-bg:transparent;--_ku-pane-ring:none;
2608
2807
  --_ku-divider:var(--_k-split-gutter);--_kd-bg:transparent}
2808
+ /* What draws its box without those: a picker's card keeps its fill as the highlight but drops its
2809
+ ring; a timeline's track drops its well for a hairline under the lane; an inline panel sheds its
2810
+ card (--k-chrome 0). A full-window step is the one raised exception (it is the surface). */
2811
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-picker-card,.knobs-ui[data-nested] .knobs-picker-card{box-shadow:none}
2812
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-tl-track,.knobs-ui[data-nested] .knobs-tl-track{background:transparent;border-radius:0;box-shadow:inset 0 -1px 0 var(--_k-border-subtle)}
2813
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-panel.knobs-inline:not(.knobs-page){--k-chrome:0}
2609
2814
  /* Only the pieces' own elements take their type and resets: what you put in a pane is yours. */
2610
2815
  .knobs-ui{box-sizing:border-box}
2611
2816
  :where(.knobs-split>*,.knobs-pane-clip,.knobs-pane-inner,.knobs-pane-body,.knobs-pane-head,.knobs-pane-head *,.knobs-pane-rail,.knobs-pane-rail *,.knobs-tree,.knobs-tree *,.knobs-tree-ghost){box-sizing:border-box}
@@ -2831,8 +3036,6 @@ var css2 = `
2831
3036
  .knobs-tl-hover{position:absolute;left:0;top:0;bottom:0;width:1px;margin-left:-.5px;background:var(--_k-border-strong);transform:translateX(-100px);pointer-events:none;opacity:0;transition:opacity .15s}
2832
3037
  .knobs-tl-track:hover .knobs-tl-hover{opacity:1}
2833
3038
  .knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
2834
- @media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
2835
- @media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
2836
3039
  .knobs-tl-tag{position:absolute;left:0;bottom:2px;font-size:var(--_k-text-meta);line-height:16px;color:var(--_k-fg);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";white-space:nowrap;pointer-events:none;opacity:0}
2837
3040
  .knobs-tl-tag[data-on]{opacity:1}
2838
3041
  /* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
@@ -2848,7 +3051,26 @@ var css2 = `
2848
3051
  .knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
2849
3052
  .knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
2850
3053
  .knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
2851
- `, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + selectionPillCss) + rolling_css_default, lookDefaults, tune = (s, m) => {
3054
+ /* The line (variant: "line"): a player's progress under its media. One 2px line, fg-3 with fg
3055
+ filling it, a segment per chapter; it thickens under the pointer and while scrubbing. The
3056
+ chapter and time under the pointer float over it; nothing else shows. */
3057
+ .knobs-ui.knobs-timeline[data-variant="line"]{height:auto;min-height:0;padding:0;gap:0}
3058
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:none}
3059
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track{height:16px;overflow:visible;border-radius:var(--_k-radius);background:transparent;box-shadow:none;cursor:pointer}
3060
+ .knobs-timeline[data-variant="line"] :is(.knobs-tl-ruler,.knobs-tl-lane,.knobs-tl-hover,.knobs-tl-head,.knobs-tl-rlabel){display:none}
3061
+ .knobs-tl-segs{position:absolute;left:0;right:0;top:50%;height:2px;margin-top:-1px;pointer-events:none;transition:scale calc(var(--_k-hover-out) * 1ms) var(--_ku-ease)}
3062
+ .knobs-tl-seg{position:absolute;left:0;top:0;height:100%;overflow:hidden;border-radius:1px;background:var(--_k-fg-3);will-change:transform}
3063
+ .knobs-tl-seg-fill{position:absolute;inset:0;background:var(--_k-fg);transform-origin:0 0;will-change:transform}
3064
+ .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs,.knobs-timeline[data-variant="line"][data-scrubbing] .knobs-tl-segs{scale:1 2}
3065
+ .knobs-timeline[data-variant="line"] .knobs-tl-range{top:3px;bottom:3px;border-radius:3px}
3066
+ .knobs-timeline[data-variant="line"]:focus-visible .knobs-tl-track,.knobs-timeline[data-variant="line"][data-keyboard]:focus .knobs-tl-track{background:transparent;box-shadow:0 0 0 2px color-mix(in srgb,var(--_k-accent) 60%,transparent)}
3067
+ .knobs-timeline[data-variant="line"] .knobs-tl-tag{top:auto;bottom:calc(100% + 2px);display:flex;gap:6px;padding:0 6px;border-radius:calc(var(--_k-radius) * 999);background:color-mix(in srgb,var(--_k-bg) 72%,transparent);
3068
+ -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:inset 0 0 0 .5px var(--_k-border)}
3069
+ .knobs-tl-tag-label{color:var(--_k-fg-2)}
3070
+ .knobs-tl-tag-label:empty{display:none}
3071
+ @media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
3072
+ @media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
3073
+ `, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + playerCss + selectionPillCss) + rolling_css_default, lookDefaults, tune = (s, m) => {
2852
3074
  s.omega = m.omega;
2853
3075
  s.zeta = m.zeta;
2854
3076
  return s;
@@ -2971,19 +3193,19 @@ var init_diff_css = __esm(() => {
2971
3193
  @property --kd-fb{syntax:"<length>";inherits:false;initial-value:0px}
2972
3194
  :where(.knobs-ui){--kd-add:var(--_k-diff-add);--kd-del:var(--_k-diff-del);--kd-mod:var(--_k-diff-mod);--kd-move:var(--k-diff-move,var(--_k-info))}
2973
3195
  :where(.knobs-diff,.knobs-diff *,.knobs-diff-files,.knobs-diff-files *){box-sizing:border-box}
2974
- .knobs-ui.knobs-diff{--kd-bg:var(--k-diff-bg,var(--_kd-bg,var(--_k-bg)));--kd-solid:var(--kd-fill,var(--kd-bg));--kd-line:calc(var(--_k-font-size) * 1.5);
3196
+ .knobs-ui.knobs-diff{--kd-bg:var(--k-diff-bg,var(--_kd-bg,transparent));--kd-solid:var(--kd-fill,var(--kd-bg));--kd-line:calc(var(--_k-font-size) * 1.5);
2975
3197
  --kd-i:var(--k-diff-inset,8px);--kd-x:var(--kd-i);--kd-inner:max(3px,calc(var(--_ku-pane-radius) - var(--kd-i)));--kd-ctl:var(--kd-inner);
2976
3198
  --kd-head:calc(var(--_k-control-h) + 2 * var(--kd-i));--kd-fade:var(--k-diff-fade,20px);--kd-stick:0px;position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;
2977
3199
  background:var(--kd-bg);border-radius:var(--_ku-pane-radius);font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);text-align:left;
2978
3200
  letter-spacing:normal;word-spacing:normal;text-transform:none;-webkit-font-smoothing:antialiased}
2979
3201
  .knobs-ui.knobs-diff[data-height="auto"]{height:auto}
2980
- .knobs-diff::after{content:"";position:absolute;inset:0;z-index:3;border-radius:inherit;box-shadow:var(--_ku-pane-ring);pointer-events:none}
3202
+ .knobs-ui.knobs-diff:not([data-on="pane"])::after{content:"";position:absolute;inset:0;z-index:3;box-shadow:inset 0 -1px 0 var(--_k-border-subtle);pointer-events:none}
2981
3203
  .knobs-diff [hidden],.knobs-diff-files [hidden]{display:none!important}
2982
3204
  .knobs-diff svg{display:block;flex:none}
2983
- .knobs-ui.knobs-diff[data-on="surface"]{--kd-x:0px;--kd-head:var(--_k-control-h);--kd-ctl:var(--_k-radius)}
2984
- /* The header's hairline shows only once the code has scrolled under it. */
3205
+ .knobs-ui.knobs-diff:not([data-on="pane"]){--kd-x:0px;--kd-head:calc(var(--_k-control-h) + var(--kd-i));--kd-ctl:var(--_k-radius)}
3206
+ /* A hairline under the header, firmer once the code has scrolled under it. */
2985
3207
  .knobs-diff-head{position:relative;z-index:1;flex:none;display:flex;align-items:center;gap:8px;height:var(--kd-head);padding:0 var(--kd-x);
2986
- box-shadow:inset 0 -1px transparent;transition:box-shadow .15s;user-select:none;-webkit-user-select:none}
3208
+ box-shadow:inset 0 -1px var(--_k-border-subtle);transition:box-shadow .15s;user-select:none;-webkit-user-select:none}
2987
3209
  .knobs-diff[data-scrolled]>.knobs-diff-head{box-shadow:inset 0 -1px var(--_k-border)}
2988
3210
  .knobs-diff-path{flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap}
2989
3211
  .knobs-diff-dir,.knobs-diff-from{color:var(--_k-fg-3)}
@@ -3021,6 +3243,15 @@ var init_diff_css = __esm(() => {
3021
3243
  .knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
3022
3244
  .knobs-diff-viewed svg{width:16px;height:16px}
3023
3245
  .knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
3246
+ /* Narrow, the header gives up words before anything is cut off at its end: Viewed keeps its
3247
+ check, then the counts' bar goes, then the counts; the path fades at its end throughout. */
3248
+ .knobs-diff-head,.knobs-diff-part-head{container-type:inline-size}
3249
+ @container (max-width:520px){
3250
+ .knobs-diff-viewed-label{display:none}
3251
+ .knobs-ui .knobs-diff-viewed{width:var(--_k-control-h);padding:0;justify-content:center}
3252
+ .knobs-diff-counts .knobs-diff-bar{display:none}
3253
+ }
3254
+ @container (max-width:400px){.knobs-diff-counts{display:none}.knobs-diff-nav>span{min-width:2.6em}}
3024
3255
  .knobs-diff-path{transition:opacity .2s}
3025
3256
  .knobs-diff[data-viewed]>.knobs-diff-head .knobs-diff-path,.knobs-diff-part[data-viewed] .knobs-diff-path{opacity:.6}
3026
3257
  /* Split | Unified: the selection pill on the one picked, its bright copy the picked one's text; a
@@ -3085,24 +3316,26 @@ var init_diff_css = __esm(() => {
3085
3316
  --diffs-bg-deletion-number-override:color-mix(in srgb,var(--kd-del) 55%,var(--kd-solid));
3086
3317
  --diffs-bg-addition-emphasis-override:color-mix(in srgb,var(--kd-add) 22%,transparent);
3087
3318
  --diffs-bg-deletion-emphasis-override:color-mix(in srgb,var(--kd-del) 22%,transparent);
3088
- --diffs-bg-separator-override:color-mix(in srgb,var(--_k-fg) 4.5%,var(--kd-solid));
3089
- --diffs-bg-context-override:color-mix(in srgb,var(--_k-fg) 3%,var(--kd-solid));
3090
- --diffs-bg-buffer-override:color-mix(in srgb,var(--_k-fg) 3.5%,var(--kd-solid));
3319
+ /* Unchanged lines and the folds between hunks sit on the void; only a split's missing lines
3320
+ are faintly filled. */
3321
+ --diffs-bg-separator-override:var(--kd-solid);
3322
+ --diffs-bg-context-override:var(--kd-solid);
3323
+ --diffs-bg-buffer-override:color-mix(in srgb,var(--_k-fg) 2.5%,var(--kd-solid));
3091
3324
  --diffs-bg-selection-override:var(--_k-accent);
3092
3325
  --diffs-fg-number-override:var(--_k-fg-3);
3093
3326
  }
3094
- /* diffReview: one card, the file list a drawer at its left (its width springs; the list inside
3095
- is pinned to its right edge, so it slides out from under the diff), parted from the diff by a
3096
- hairline. The diff in it draws no card of its own. */
3097
- .knobs-ui.knobs-diff-review{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;border-radius:var(--_ku-pane-radius);background:var(--_ku-pane-bg);
3327
+ /* diffReview, on the void as the diff is: the file list a drawer at its left (its width springs;
3328
+ the list inside is pinned to its right edge, so it slides out from under the diff), parted
3329
+ from the diff by a hairline; one hairline runs under both headers and one under it all. */
3330
+ .knobs-ui.knobs-diff-review{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;
3098
3331
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased}
3099
- .knobs-diff-review::after{content:"";position:absolute;inset:0;z-index:5;border-radius:inherit;box-shadow:var(--_ku-pane-ring);pointer-events:none}
3332
+ .knobs-diff-review::after{content:"";position:absolute;inset:0;z-index:5;box-shadow:inset 0 -1px 0 var(--_k-border-subtle);pointer-events:none}
3100
3333
  :where(.knobs-diff-review,.knobs-diff-review *){box-sizing:border-box}
3101
- .knobs-diff-drawer{position:relative;flex:none;width:0;height:100%;overflow:hidden;box-shadow:inset -1px 0 var(--_k-border)}
3334
+ .knobs-diff-drawer{position:relative;flex:none;width:0;height:100%;overflow:hidden;box-shadow:inset -1px 0 var(--_k-border-subtle)}
3102
3335
  .knobs-diff-drawer[data-shut]{visibility:hidden}
3103
3336
  .knobs-diff-drawer-in{position:absolute;top:0;bottom:0;right:0;display:flex;flex-direction:column}
3104
3337
  .knobs-diff-drawer-head{flex:none;display:flex;align-items:center;gap:6px;height:calc(var(--_k-control-h) + 2 * var(--k-diff-inset,8px));padding:0 var(--k-diff-inset,8px) 0 calc(var(--_ku-row-x) + 8px);
3105
- user-select:none;-webkit-user-select:none}
3338
+ box-shadow:inset 0 -1px var(--_k-border-subtle);user-select:none;-webkit-user-select:none}
3106
3339
  .knobs-diff-drawer-title{font-weight:var(--_k-weight-title)}
3107
3340
  .knobs-diff-drawer-count{color:var(--_k-fg-3);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums}
3108
3341
  .knobs-diff-drawer-in>.knobs-diff-files{flex:1;min-height:0;height:auto}
@@ -3114,10 +3347,11 @@ var init_diff_css = __esm(() => {
3114
3347
  .knobs-ui .knobs-diff-toggle[aria-pressed]:not(:hover,:focus-visible){background:transparent}
3115
3348
  /* diffReview's file list: a filter over a compact tree, a status letter, the counts and their
3116
3349
  bar on each file; one highlight glides to the file on show. Viewed files dim, with a tick. */
3117
- .knobs-diff-files{--k-row:var(--k-diff-row,28px);display:flex;flex-direction:column;height:100%;min-height:0;padding-top:2px}
3350
+ .knobs-diff-files{--k-row:var(--k-diff-row,28px);display:flex;flex-direction:column;height:100%;min-height:0;padding-top:4px}
3118
3351
  .knobs-diff-filter{position:relative;flex:none;display:flex;align-items:center;gap:6px;height:26px;margin:0 var(--_ku-row-x) 4px;padding:0 4px 0 8px;
3119
- border-radius:max(6px,var(--_ku-row-radius));background:var(--_ku-wash);color:var(--_k-fg-3);box-shadow:inset 0 0 0 1px transparent;transition:background .15s,box-shadow .15s}
3120
- .knobs-diff-filter:focus-within{background:var(--_ku-wash-hi);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_k-accent) 45%,transparent)}
3352
+ border-radius:max(6px,var(--_ku-row-radius));color:var(--_k-fg-3);box-shadow:inset 0 0 0 1px transparent;transition:box-shadow .15s}
3353
+ /* The filter is a line of text, no box; focused, a hairline ring. */
3354
+ .knobs-diff-filter:focus-within{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_k-accent) 45%,transparent)}
3121
3355
  .knobs-diff-filter svg{width:12px;height:12px;flex:none}
3122
3356
  .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}
3123
3357
  .knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
@@ -6042,7 +6276,10 @@ function codeDiff(container, options) {
6042
6276
  const viewedToggle = () => {
6043
6277
  const b = make("button", "knobs-diff-viewed");
6044
6278
  b.type = "button";
6045
- b.append(make("span", ""), "Viewed");
6279
+ const label = make("span", "knobs-diff-viewed-label");
6280
+ label.textContent = "Viewed";
6281
+ b.append(make("span", ""), label);
6282
+ b.setAttribute("aria-label", "Viewed");
6046
6283
  return b;
6047
6284
  };
6048
6285
  const paintViewed = (b, on) => {
@@ -8329,6 +8566,17 @@ function currentMarker(markers, t, fps) {
8329
8566
  best = m;
8330
8567
  return best;
8331
8568
  }
8569
+ function minutes(t) {
8570
+ const s = Math.max(0, Math.floor(t + 0.000001));
8571
+ const h = Math.floor(s / 3600);
8572
+ const m = Math.floor(s % 3600 / 60);
8573
+ const ss = String(s % 60).padStart(2, "0");
8574
+ return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
8575
+ }
8576
+ function chapterBounds(times, duration) {
8577
+ const inside = times.filter((t) => t > 0.000001 && t < duration - 0.000001);
8578
+ return [0, ...[...new Set(inside)].sort((a, b) => a - b), duration];
8579
+ }
8332
8580
  var SECOND_STEPS, FRAME_STEPS, toFrame = (t, fps) => Math.round(t * fps), frameTime = (t, fps) => Math.round(t * fps) / fps, round = (t) => Math.round(t * 1e6) / 1e6, CATCH_UP;
8333
8581
  var init_timeline_model = __esm(() => {
8334
8582
  SECOND_STEPS = [0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600];
@@ -8346,7 +8594,8 @@ function timeline(container, options) {
8346
8594
  let speed = options.speed ?? 1;
8347
8595
  let repeat = options.repeat ?? true;
8348
8596
  const arrows = options.arrows ?? "frames";
8349
- const lane = options.variant === "lane";
8597
+ const line = options.variant === "line";
8598
+ const lane = line || options.variant === "lane";
8350
8599
  let time = Math.min(duration, Math.max(0, options.time ?? 0));
8351
8600
  let playing = false;
8352
8601
  let markers = [...options.markers ?? []];
@@ -8381,7 +8630,7 @@ function timeline(container, options) {
8381
8630
  root.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight , . Home End [ ] I O M");
8382
8631
  applyLook2(root, options);
8383
8632
  if (lane)
8384
- root.dataset.variant = "lane";
8633
+ root.dataset.variant = line ? "line" : "lane";
8385
8634
  const transport = el("div", "knobs-tl-transport");
8386
8635
  const playBtn = el("button", "knobs-tl-play");
8387
8636
  playBtn.type = "button";
@@ -8394,6 +8643,10 @@ function timeline(container, options) {
8394
8643
  const hover = el("div", "knobs-tl-hover");
8395
8644
  const tag = el("span", "knobs-tl-tag");
8396
8645
  tag.setAttribute("aria-hidden", "true");
8646
+ const tagLabel = el("span", "knobs-tl-tag-label");
8647
+ const tagTime = el("span", "knobs-tl-tag-time");
8648
+ tag.append(tagLabel, tagTime);
8649
+ const segs = el("div", "knobs-tl-segs");
8397
8650
  const head = el("div", "knobs-tl-head");
8398
8651
  head.id = `${id}-head`;
8399
8652
  head.setAttribute("role", "slider");
@@ -8401,11 +8654,13 @@ function timeline(container, options) {
8401
8654
  head.setAttribute("aria-valuemin", "0");
8402
8655
  head.setAttribute("aria-valuemax", String(duration));
8403
8656
  track.append(canvas, markerLane, hover, head, tag);
8657
+ if (line)
8658
+ track.insertBefore(segs, markerLane);
8404
8659
  root.append(transport, track);
8405
8660
  container.append(root);
8406
8661
  let width = 1;
8407
8662
  let height = 1;
8408
- const fitView = () => clampView({ start: 0, span: duration }, duration, { pad: 0.02 });
8663
+ const fitView = () => clampView({ start: 0, span: duration }, duration, { pad: line ? 0 : 0.02 });
8409
8664
  let target = fitView();
8410
8665
  const vStart = new Spring(target.start, INSTANT2.omega, 1, 0.00001);
8411
8666
  const vSpan = new Spring(target.span, INSTANT2.omega, 1, 0.00001);
@@ -8414,13 +8669,13 @@ function timeline(container, options) {
8414
8669
  const xOf = (t) => (t - vStart.x) * pxPerSec();
8415
8670
  const tOf = (x) => vStart.x + x / pxPerSec();
8416
8671
  const minSpan = () => Math.max(4 / fps, 0.05);
8417
- const retarget = (next, motion = INSTANT2) => {
8418
- target = clampView(next, duration, { pad: 0.04, minSpan: minSpan() });
8672
+ const retarget = (next, motion = INSTANT2, hand = false) => {
8673
+ target = clampView(next, duration, { pad: line ? 0 : 0.04, minSpan: minSpan() });
8419
8674
  for (const s of [vStart, vSpan]) {
8420
8675
  s.omega = motion.omega;
8421
8676
  s.zeta = motion.zeta;
8422
8677
  }
8423
- if (reducedMotion(win) && motion === CALM) {
8678
+ if (hand || reducedMotion(win) && motion === CALM) {
8424
8679
  vStart.snap(target.start);
8425
8680
  vSpan.snap(target.span);
8426
8681
  } else {
@@ -8446,6 +8701,8 @@ function timeline(container, options) {
8446
8701
  };
8447
8702
  let drawn = "";
8448
8703
  const drawRuler = () => {
8704
+ if (line)
8705
+ return;
8449
8706
  const v = view();
8450
8707
  const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${height}:${units}:${fps}`;
8451
8708
  if (key === drawn)
@@ -8533,6 +8790,8 @@ function timeline(container, options) {
8533
8790
  n.title = `${m.label ? `${m.label} · ` : ""}${clock(m.time)}`;
8534
8791
  }
8535
8792
  placeMarkers();
8793
+ if (line)
8794
+ syncSegments();
8536
8795
  };
8537
8796
  const placeMarkers = () => {
8538
8797
  for (const m of markers) {
@@ -8541,6 +8800,36 @@ function timeline(container, options) {
8541
8800
  n.style.transform = `translateX(${xOf(m.time).toFixed(2)}px)`;
8542
8801
  }
8543
8802
  };
8803
+ let bounds = [];
8804
+ const segEls = [];
8805
+ const syncSegments = () => {
8806
+ bounds = chapterBounds(markers.map((m) => m.time), duration);
8807
+ while (segEls.length > bounds.length - 1)
8808
+ segEls.pop().el.remove();
8809
+ while (segEls.length < bounds.length - 1) {
8810
+ const n = el("i", "knobs-tl-seg");
8811
+ const fill = el("b", "knobs-tl-seg-fill");
8812
+ n.append(fill);
8813
+ segs.append(n);
8814
+ segEls.push({ el: n, fill });
8815
+ }
8816
+ placeSegments();
8817
+ };
8818
+ const placeSegments = () => {
8819
+ if (!line)
8820
+ return;
8821
+ const last = segEls.length - 1;
8822
+ segEls.forEach((s, i) => {
8823
+ const a = bounds[i];
8824
+ const b = bounds[i + 1];
8825
+ const x0 = xOf(a) + (i > 0 ? SEG_GAP / 2 : 0);
8826
+ const x1 = xOf(b) - (i < last ? SEG_GAP / 2 : 0);
8827
+ s.el.style.transform = `translateX(${x0.toFixed(2)}px)`;
8828
+ s.el.style.width = `${Math.max(0, x1 - x0).toFixed(2)}px`;
8829
+ const k = Math.min(1, Math.max(0, (shownHead - a) / Math.max(0.000000001, b - a)));
8830
+ s.fill.style.transform = `scaleX(${k.toFixed(4)})`;
8831
+ });
8832
+ };
8544
8833
  const syncRanges = () => {
8545
8834
  const keep = new Set(ranges.map((r) => r.id));
8546
8835
  for (const [k, n] of rangeEls)
@@ -8557,7 +8846,7 @@ function timeline(container, options) {
8557
8846
  b.dataset.edge = "end";
8558
8847
  const label = el("span", "knobs-tl-rlabel");
8559
8848
  n.append(label, a, b);
8560
- track.insertBefore(n, markerLane);
8849
+ track.insertBefore(n, line ? segs : markerLane);
8561
8850
  rangeEls.set(r.id, n);
8562
8851
  if (built)
8563
8852
  enter(n);
@@ -8609,6 +8898,7 @@ function timeline(container, options) {
8609
8898
  let currentId = null;
8610
8899
  const renderHead = () => {
8611
8900
  head.style.transform = `translateX(${xOf(shownHead).toFixed(2)}px)`;
8901
+ placeSegments();
8612
8902
  head.setAttribute("aria-valuenow", time.toFixed(3));
8613
8903
  head.setAttribute("aria-valuetext", `${time.toFixed(2)} seconds, frame ${toFrame(time, fps)}`);
8614
8904
  const cur = currentMarker(markers, time, fps)?.id ?? null;
@@ -8872,14 +9162,25 @@ function timeline(container, options) {
8872
9162
  setTime(s.t, "scrub", s.shown, press);
8873
9163
  root.toggleAttribute("data-stuck", s.stop !== null);
8874
9164
  };
9165
+ let pointerX = null;
9166
+ const zoomAtHand = (factor) => zoom(factor, pointerX === null ? time : target.start + pointerX / width * target.span);
8875
9167
  const showTag = (x) => {
8876
9168
  const at = Math.min(duration, Math.max(0, tOf(x)));
8877
- const text = `${at.toFixed(2)} s`;
8878
- if (tag.textContent !== text)
8879
- tag.textContent = text;
9169
+ const text = line ? minutes(at) : `${at.toFixed(2)} s`;
9170
+ if (tagTime.textContent !== text)
9171
+ tagTime.textContent = text;
9172
+ const label = line ? currentMarker(markers, at, fps)?.label ?? "" : "";
9173
+ if (tagLabel.textContent !== label)
9174
+ tagLabel.textContent = label;
8880
9175
  const cx = Math.min(width, Math.max(0, x));
8881
- const flip = cx > width - tag.offsetWidth - 12;
8882
- tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
9176
+ if (line) {
9177
+ const w = tag.offsetWidth;
9178
+ const left = Math.min(Math.max(0, width - w), Math.max(0, cx - w / 2));
9179
+ tag.style.transform = `translateX(${left.toFixed(1)}px)`;
9180
+ } else {
9181
+ const flip = cx > width - tag.offsetWidth - 12;
9182
+ tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
9183
+ }
8883
9184
  tag.dataset.on = "";
8884
9185
  };
8885
9186
  const hideTag = () => delete tag.dataset.on;
@@ -8940,6 +9241,7 @@ function timeline(container, options) {
8940
9241
  });
8941
9242
  track.addEventListener("pointermove", (e) => {
8942
9243
  const x = localX(e);
9244
+ pointerX = x;
8943
9245
  hover.style.transform = `translateX(${x.toFixed(1)}px)`;
8944
9246
  if (!drag || drag.kind === "scrub")
8945
9247
  showTag(x);
@@ -9013,6 +9315,7 @@ function timeline(container, options) {
9013
9315
  track.addEventListener("pointercancel", endDrag);
9014
9316
  track.addEventListener("lostpointercapture", endDrag);
9015
9317
  track.addEventListener("pointerleave", () => {
9318
+ pointerX = null;
9016
9319
  if (drag)
9017
9320
  return;
9018
9321
  hover.style.transform = "translateX(-100px)";
@@ -9020,7 +9323,7 @@ function timeline(container, options) {
9020
9323
  });
9021
9324
  track.addEventListener("dblclick", (e) => {
9022
9325
  if (lane)
9023
- return;
9326
+ return fit();
9024
9327
  const markerEl = e.target.closest(".knobs-tl-marker");
9025
9328
  if (markerEl)
9026
9329
  return rename(markerEl.dataset.id);
@@ -9031,18 +9334,40 @@ function timeline(container, options) {
9031
9334
  rename(m.id);
9032
9335
  });
9033
9336
  track.addEventListener("wheel", (e) => {
9034
- if (lane)
9337
+ const zooming = e.ctrlKey || e.metaKey;
9338
+ if (zooming && pinching)
9339
+ return e.preventDefault();
9340
+ const sideways = Math.abs(e.deltaX) > Math.abs(e.deltaY);
9341
+ if (lane && !zooming && !sideways)
9035
9342
  return;
9036
9343
  e.preventDefault();
9037
9344
  const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? height : 1;
9038
- if (e.ctrlKey || e.metaKey) {
9345
+ if (zooming) {
9039
9346
  const at = target.start + localX(e) / width * target.span;
9040
- retarget(zoomView(target, Math.exp(-e.deltaY * scale * (e.ctrlKey ? 0.01 : 0.002)), at));
9347
+ const k = Math.exp(-e.deltaY * scale * (e.ctrlKey ? 0.01 : 0.002));
9348
+ retarget(zoomView(target, k, at), INSTANT2, lane);
9041
9349
  } else {
9042
- const d = (Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY) * scale;
9043
- retarget({ ...target, start: target.start + d / width * target.span });
9350
+ const d = (sideways ? e.deltaX : e.deltaY) * scale;
9351
+ retarget({ ...target, start: target.start + d / width * target.span }, INSTANT2, lane);
9044
9352
  }
9045
9353
  }, { passive: false });
9354
+ let pinchScale = 1;
9355
+ let pinching = false;
9356
+ const onGesture = (e) => {
9357
+ const g = e;
9358
+ e.preventDefault();
9359
+ if (e.type === "gesturestart")
9360
+ pinchScale = 1;
9361
+ pinching = e.type !== "gestureend";
9362
+ if (e.type !== "gesturechange")
9363
+ return;
9364
+ const s = g.scale ?? 1;
9365
+ const x = g.clientX !== undefined ? localX({ clientX: g.clientX }) : width / 2;
9366
+ retarget(zoomView(target, s / pinchScale, target.start + x / width * target.span), INSTANT2, true);
9367
+ pinchScale = s;
9368
+ };
9369
+ for (const type of ["gesturestart", "gesturechange", "gestureend"])
9370
+ track.addEventListener(type, onGesture);
9046
9371
  root.addEventListener("keydown", (e) => {
9047
9372
  if (e.target !== root || e.metaKey || e.ctrlKey) {
9048
9373
  if ((e.metaKey || e.ctrlKey) && (e.key === "=" || e.key === "-" || e.key === "0") && e.target === root) {
@@ -9050,7 +9375,7 @@ function timeline(container, options) {
9050
9375
  if (e.key === "0")
9051
9376
  fit();
9052
9377
  else
9053
- zoom(e.key === "=" ? 1.5 : 1 / 1.5);
9378
+ zoomAtHand(e.key === "=" ? 1.5 : 1 / 1.5);
9054
9379
  }
9055
9380
  return;
9056
9381
  }
@@ -9132,18 +9457,12 @@ function timeline(container, options) {
9132
9457
  return;
9133
9458
  case "=":
9134
9459
  case "+":
9135
- if (lane)
9136
- return;
9137
9460
  claim();
9138
- return zoom(1.5);
9461
+ return zoomAtHand(1.5);
9139
9462
  case "-":
9140
- if (lane)
9141
- return;
9142
9463
  claim();
9143
- return zoom(1 / 1.5);
9464
+ return zoomAtHand(1 / 1.5);
9144
9465
  case "0":
9145
- if (lane)
9146
- return;
9147
9466
  claim();
9148
9467
  return fit();
9149
9468
  }
@@ -9359,7 +9678,7 @@ function timeline(container, options) {
9359
9678
  };
9360
9679
  return api;
9361
9680
  }
9362
- var RULER = 22, INSTANT2, CALM, GLIDE, uid2 = 0, PLAY2 = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5 3.6v8.8c0 .5.5.8.9.5l6.6-4.4c.4-.3.4-.8 0-1L5.9 3.1c-.4-.3-.9 0-.9.5Z" fill="currentColor"/></svg>', PAUSE = '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="4" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/><rect x="9.2" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/></svg>';
9681
+ var RULER = 22, SEG_GAP = 3, INSTANT2, CALM, GLIDE, uid2 = 0, PLAY2 = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5 3.6v8.8c0 .5.5.8.9.5l6.6-4.4c.4-.3.4-.8 0-1L5.9 3.1c-.4-.3-.9 0-.9.5Z" fill="currentColor"/></svg>', PAUSE = '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="4" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/><rect x="9.2" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/></svg>';
9363
9682
  var init_timeline = __esm(() => {
9364
9683
  init_rgba();
9365
9684
  init_motion();
@@ -9431,15 +9750,28 @@ function compareLayout(box, before, after) {
9431
9750
  const shape = after.w > 0 && after.h > 0 ? after : before;
9432
9751
  const pair = letterbox({ w: shape.w * 2, h: shape.h }, box);
9433
9752
  const half = pair.w / 2;
9753
+ const fused = { x: pair.x + half / 2, y: pair.y, w: half, h: pair.h };
9434
9754
  return {
9435
9755
  side: {
9436
9756
  before: letterbox(before, { x: pair.x, y: pair.y, w: half, h: pair.h }),
9437
9757
  after: letterbox(after, { x: pair.x + half, y: pair.y, w: half, h: pair.h }),
9438
9758
  split: pair.x + half
9439
9759
  },
9440
- overlay: { before: letterbox(before, box), after: letterbox(after, box) }
9760
+ overlay: { before: letterbox(before, fused), after: letterbox(after, fused) }
9441
9761
  };
9442
9762
  }
9763
+ function fuseFrame(L, before, after, k, wipe) {
9764
+ const t = Math.min(1, Math.max(0, k));
9765
+ const o = L.overlay;
9766
+ const left = Math.min(o.before.x, o.after.x);
9767
+ const line = wipeX({ x: left, y: 0, w: Math.max(o.before.x + o.before.w, o.after.x + o.after.w) - left, h: 0 }, wipe);
9768
+ const cut = L.side.split + (line - L.side.split) * t;
9769
+ const contact = Math.max(0, Math.min(cut - after.x, before.x + before.w - cut));
9770
+ const feather = Math.min(contact, 1 + FUSE.feather * Math.sin(Math.PI * t));
9771
+ const overlap = before.x + before.w - after.x;
9772
+ const seam = t > 0.001 && overlap >= 0 ? Math.min(1, Math.max(0, overlap / FUSE.seamFade)) * Math.min(1, t * 4) : 0;
9773
+ return { cut, feather, seam, blur: t < 0.995 ? FUSE.blur * (1 - t) : 0 };
9774
+ }
9443
9775
  function clampZoom(r) {
9444
9776
  const w = Math.min(1, Math.max(1 / ZOOM_MAX, r.w));
9445
9777
  const u = Math.min(1 - w, Math.max(0, r.u));
@@ -9525,7 +9857,15 @@ function stripPeaks(diffs, o = {}) {
9525
9857
  });
9526
9858
  return out;
9527
9859
  }
9528
- 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, splitHeight = (w, media) => media.w > 0 && media.h > 0 ? w / 2 * (media.h / media.w) : w / 2 * (9 / 16), ZOOM_FIT, ZOOM_MAX = 16, panBy = (r, dx, dy, base) => clampZoom({
9860
+ 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) => {
9861
+ const f = Math.min(1, Math.max(0, fold));
9862
+ const open = 1 - f;
9863
+ return {
9864
+ width: mark * open,
9865
+ opacity: Math.min(1, Math.max(0, (open - 0.2) / 0.8)),
9866
+ blur: blur * Math.min(1, Math.max(0, (f - 0.25) / 0.75))
9867
+ };
9868
+ }, splitHeight = (w, media) => media.w > 0 && media.h > 0 ? w / 2 * (media.h / media.w) : w / 2 * (9 / 16), ZOOM_FIT, ZOOM_MAX = 16, panBy = (r, dx, dy, base) => clampZoom({
9529
9869
  u: r.u - dx / Math.max(1, base.w) * r.w,
9530
9870
  v: r.v - dy / Math.max(1, base.h) * r.h,
9531
9871
  w: r.w
@@ -9548,6 +9888,7 @@ var init_compare_model = __esm(() => {
9548
9888
  MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
9549
9889
  VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
9550
9890
  SEEK_IDLE = { seeking: false, pending: null };
9891
+ FUSE = { feather: 24, blur: 4, seamFade: 48 };
9551
9892
  ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
9552
9893
  });
9553
9894
 
@@ -10442,11 +10783,6 @@ fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
10442
10783
 
10443
10784
  fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
10444
10785
  switch mode {
10445
- case 1u: {
10446
- let s = max(u.wipe.y, 0.5);
10447
- let m = smoothstep(u.wipe.x - s, u.wipe.x + s, p.x);
10448
- return pm(vec4f(mix(a.rgb, b.rgb, m), mix(a.a, b.a, m)));
10449
- }
10450
10786
  case 2u: {
10451
10787
  let d = abs(a.rgb - b.rgb);
10452
10788
  let h = smoothstep(u.diff.x, u.diff.x + 0.06, max(d.r, max(d.g, d.b)) * u.diff.y) * min(a.a, b.a);
@@ -10455,8 +10791,10 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
10455
10791
  }
10456
10792
  default: {}
10457
10793
  }
10458
- // Side by side: each side in its own place, Before over After where they meet.
10459
- return vec4f(a.rgb * a.a + b.rgb * b.a * (1.0 - a.a), a.a + b.a * (1.0 - a.a));
10794
+ // Side by side, fusing and wiping: Before over After, cut at the seam with its feather.
10795
+ let s = max(u.wipe.y, 0.5);
10796
+ let ka = a.a * (1.0 - smoothstep(u.wipe.x - s, u.wipe.x + s, p.x));
10797
+ return vec4f(a.rgb * ka + b.rgb * b.a * (1.0 - ka), ka + b.a * (1.0 - ka));
10460
10798
  }
10461
10799
 
10462
10800
  @fragment fn fs(@builtin(position) q: vec4f) -> @location(0) vec4f {
@@ -10858,6 +11196,29 @@ var init_compare_strip = __esm(() => {
10858
11196
  DIFF = { w: 32, h: 18 };
10859
11197
  });
10860
11198
 
11199
+ // src/ui/player.ts
11200
+ function playGlyph(doc) {
11201
+ const element = doc.createElement("span");
11202
+ element.className = "knobs-player-glyph";
11203
+ element.setAttribute("aria-hidden", "true");
11204
+ let timer = 0;
11205
+ const win = doc.defaultView;
11206
+ return {
11207
+ element,
11208
+ flash(playing) {
11209
+ element.innerHTML = playing ? PLAY2 : PAUSE;
11210
+ element.setAttribute("data-flash", "");
11211
+ win.clearTimeout(timer);
11212
+ timer = win.setTimeout(() => element.removeAttribute("data-flash"), FLASH_MS);
11213
+ },
11214
+ dispose: () => win.clearTimeout(timer)
11215
+ };
11216
+ }
11217
+ var FLASH_MS = 450;
11218
+ var init_player = __esm(() => {
11219
+ init_timeline();
11220
+ });
11221
+
10861
11222
  // src/ui/compare.ts
10862
11223
  function compare(container, options) {
10863
11224
  const doc = container.ownerDocument;
@@ -10954,6 +11315,19 @@ function compare(container, options) {
10954
11315
  const labels = [el("div", "knobs-cmp-label"), el("div", "knobs-cmp-label")];
10955
11316
  labels[0].dataset.side = "before";
10956
11317
  labels[1].dataset.side = "after";
11318
+ const setLabel = (index, text) => {
11319
+ const l = labels[index];
11320
+ if (!l.firstElementChild) {
11321
+ const mark = el("span", "knobs-cmp-mark");
11322
+ mark.innerHTML = icon(index === 0 ? "x" : "check", { small: true });
11323
+ l.append(mark, el("span", "knobs-cmp-name"));
11324
+ }
11325
+ const name = l.lastElementChild;
11326
+ if (name.textContent !== text)
11327
+ name.textContent = text;
11328
+ };
11329
+ const seam = el("div", "knobs-cmp-seam");
11330
+ seam.setAttribute("aria-hidden", "true");
10957
11331
  const handle = el("div", "knobs-cmp-handle");
10958
11332
  handle.setAttribute("role", "slider");
10959
11333
  handle.setAttribute("aria-label", "Wipe");
@@ -10963,7 +11337,8 @@ function compare(container, options) {
10963
11337
  grip.setAttribute("aria-hidden", "true");
10964
11338
  handle.append(grip);
10965
11339
  const note = el("div", "knobs-cmp-note");
10966
- stage.append(canvas, split, labels[0], labels[1], handle, zoomPill, note);
11340
+ const glyph = playGlyph(doc);
11341
+ stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note, glyph.element);
10967
11342
  root.append(bar, stage);
10968
11343
  const wantsStrip = options.strip === true;
10969
11344
  let tl = bound;
@@ -10978,7 +11353,7 @@ function compare(container, options) {
10978
11353
  root.append(host);
10979
11354
  tl = timeline(host, {
10980
11355
  ...look,
10981
- variant: "lane",
11356
+ variant: "line",
10982
11357
  duration: 1,
10983
11358
  fps: fps(),
10984
11359
  arrows: { step: 0.5, shift: 2 },
@@ -10999,12 +11374,13 @@ function compare(container, options) {
10999
11374
  let toMode = mode;
11000
11375
  const wipeLine = new Spring(wipe, WIPE_CATCH.omega, WIPE_CATCH.zeta, 0.0001);
11001
11376
  let wipeCatching = false;
11002
- let gripY = null;
11377
+ const gripAt = new Spring(0.5, LAYOUT2.omega, LAYOUT2.zeta, 0.0001);
11003
11378
  let loupeHeld = false;
11004
11379
  let pointer = null;
11005
11380
  let drag = null;
11006
11381
  const touches = new Map;
11007
11382
  let pinch = null;
11383
+ let press = null;
11008
11384
  const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
11009
11385
  const zv = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
11010
11386
  const zw = new Spring(1, ZOOM.omega, ZOOM.zeta, 0.00001);
@@ -11291,6 +11667,8 @@ function compare(container, options) {
11291
11667
  for (const s of sides) {
11292
11668
  const st = s.el.style;
11293
11669
  st.clipPath = "";
11670
+ st.maskImage = "";
11671
+ st.setProperty("-webkit-mask-image", "");
11294
11672
  st.mixBlendMode = "";
11295
11673
  st.opacity = "";
11296
11674
  s.shown = null;
@@ -11517,43 +11895,65 @@ function compare(container, options) {
11517
11895
  viewSent = key;
11518
11896
  options.onView({ zoom: clampZoom(z), loop: l });
11519
11897
  };
11520
- const labelsIn = (m, r, wr, wx) => {
11521
- if (m === "wipe") {
11522
- const at = seamLabels(wx, wr.x, wr.x + wr.w, labelW[0], labelW[1], INSET);
11523
- return [at.before, at.after];
11524
- }
11525
- const ax = r.after.x + INSET + (r.after.w - labelW[1] - INSET * 2) * lay.x;
11526
- return [
11527
- { x: r.before.x + INSET, opacity: 1 },
11528
- { x: ax, opacity: 1 }
11529
- ];
11898
+ const differenceWeight = () => {
11899
+ const f = Math.min(1, Math.max(0, fade.x));
11900
+ return (fromMode === "difference" ? 1 - f : 0) + (toMode === "difference" ? f : 0);
11530
11901
  };
11531
11902
  const placeGrip = (wr) => {
11532
- const gy = gripTop(gripY ?? wr.y + wr.h / 2, wr, GRIP.h) - wr.y;
11903
+ const gy = gripTop(wr.y + gripAt.x * wr.h, wr, GRIP.h) - wr.y;
11533
11904
  grip.style.transform = `translate3d(0,${gy.toFixed(2)}px,0)`;
11534
11905
  };
11906
+ let folded = 0;
11535
11907
  const draw = () => {
11536
11908
  dirty = false;
11909
+ if (!root.hasAttribute("data-ready") && sides.every((x) => x.shown || x.failed))
11910
+ root.setAttribute("data-ready", "");
11537
11911
  const r = layoutNow();
11538
11912
  const wr = wipeRect(r.L.overlay);
11539
- const wx = wipeX(wr, wipeLine.x);
11540
- const k = lay.x;
11541
- const a = labelsIn(fromMode, r, wr, wx);
11542
- const b = labelsIn(toMode, r, wr, wx);
11543
- const f = Math.min(1, Math.max(0, fade.x));
11913
+ const k = Math.min(1, Math.max(0, lay.x));
11914
+ const fu = fuseFrame(r.L, r.before, r.after, k, wipeLine.x);
11915
+ const dw = differenceWeight();
11916
+ const fusing = 1 - dw;
11917
+ folded = k * fusing;
11918
+ const fold = labelFold(folded, MARK.w);
11919
+ const w = labelW.map((x) => x - (MARK.w - fold.width) - MARK.gap * folded);
11920
+ const ride = seamLabels(fu.cut, r.before.x, r.after.x + r.after.w, w[0], w[1], INSET);
11921
+ const apart = [r.before.x + INSET, r.after.x + INSET];
11922
+ const corner = [r.before.x + INSET, r.after.x + r.after.w - labelW[1] - INSET];
11923
+ const rides = [ride.before, ride.after];
11544
11924
  const ys = [r.before.y + INSET, r.after.y + INSET];
11545
11925
  for (const i of [0, 1]) {
11546
- const x = a[i].x + (b[i].x - a[i].x) * f;
11547
- labels[i].style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
11548
- labels[i].style.opacity = (a[i].opacity + (b[i].opacity - a[i].opacity) * f).toFixed(3);
11926
+ const fused = apart[i] + (rides[i].x - apart[i]) * k;
11927
+ const x = fused + (corner[i] - fused) * dw;
11928
+ const opacity = (1 - k + k * rides[i].opacity) * fusing + dw;
11929
+ const l = labels[i];
11930
+ l.style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
11931
+ l.style.opacity = opacity.toFixed(3);
11932
+ const mark = l.firstElementChild;
11933
+ if (mark) {
11934
+ mark.style.width = `${fold.width.toFixed(2)}px`;
11935
+ mark.style.marginRight = `${(-MARK.gap * folded).toFixed(2)}px`;
11936
+ mark.style.opacity = fold.opacity.toFixed(3);
11937
+ mark.style.filter = fold.blur > 0.05 ? `blur(${fold.blur.toFixed(2)}px)` : "";
11938
+ }
11549
11939
  }
11550
11940
  const top = Math.min(r.L.side.before.y, r.L.side.after.y);
11551
11941
  split.style.transform = `translate(${r.L.side.split.toFixed(2)}px,${top.toFixed(2)}px)`;
11552
11942
  split.style.height = `${Math.max(0, r.L.side.after.h).toFixed(2)}px`;
11553
11943
  split.style.opacity = String(Math.max(0, 1 - k * 2));
11944
+ const seamOn = fu.seam * fusing;
11945
+ const blur = fu.blur > 0.01 ? `blur(${fu.blur.toFixed(2)}px)` : "none";
11946
+ seam.style.transform = `translate(${fu.cut.toFixed(2)}px,${wr.y.toFixed(2)}px) translateX(-50%)`;
11947
+ seam.style.width = `${(8 + 2 * fu.feather).toFixed(2)}px`;
11948
+ seam.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
11949
+ seam.style.opacity = (blur === "none" ? 0 : seamOn).toFixed(3);
11950
+ seam.style.backdropFilter = blur;
11951
+ seam.style.setProperty("-webkit-backdrop-filter", blur);
11554
11952
  handle.toggleAttribute("data-on", mode === "wipe");
11555
- handle.style.transform = `translate(${wx.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
11953
+ handle.style.transform = `translate(${fu.cut.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
11556
11954
  handle.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
11955
+ handle.style.opacity = seamOn.toFixed(3);
11956
+ handle.style.filter = blur;
11557
11957
  placeGrip(wr);
11558
11958
  handle.setAttribute("aria-valuenow", String(Math.round(wipe * 100)));
11559
11959
  handle.setAttribute("aria-valuetext", `${Math.round(wipe * 100)}% Before`);
@@ -11566,24 +11966,27 @@ function compare(container, options) {
11566
11966
  if (pending)
11567
11967
  return;
11568
11968
  if (renderer === "dom") {
11569
- const place = (n, rect, clip, cutAt) => {
11969
+ const cut = mode === "difference" ? null : fu;
11970
+ const place = (n, rect, clip, before) => {
11570
11971
  const st = n.style;
11571
11972
  st.transform = `translate(${rect.x.toFixed(2)}px,${rect.y.toFixed(2)}px)`;
11572
11973
  st.width = `${rect.w.toFixed(2)}px`;
11573
11974
  st.height = `${rect.h.toFixed(2)}px`;
11975
+ const edge = before && cut ? cut.cut + cut.feather : Number.POSITIVE_INFINITY;
11574
11976
  const t = Math.max(0, clip.y - rect.y);
11575
11977
  const l = Math.max(0, clip.x - rect.x);
11576
11978
  const b = Math.max(0, rect.y + rect.h - (clip.y + clip.h));
11577
- const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, cutAt));
11979
+ const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, edge));
11578
11980
  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)` : "";
11981
+ 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)` : "";
11982
+ st.maskImage = mask;
11983
+ st.setProperty("-webkit-mask-image", mask);
11579
11984
  };
11580
- const cut = mode === "wipe" ? wx : Number.POSITIVE_INFINITY;
11581
11985
  for (const s of sides) {
11582
11986
  const rect = s.index === 0 ? r.zBefore : r.zAfter;
11583
11987
  const clip = s.index === 0 ? r.before : r.after;
11584
- const at = s.index === 0 ? cut : Number.POSITIVE_INFINITY;
11585
- place(s.el, rect, clip, at);
11586
- place(s.preview, rect, clip, at);
11988
+ place(s.el, rect, clip, s.index === 0);
11989
+ place(s.preview, rect, clip, s.index === 0);
11587
11990
  if (s.index !== 0)
11588
11991
  continue;
11589
11992
  for (const n of [s.el, s.preview])
@@ -11603,8 +12006,8 @@ function compare(container, options) {
11603
12006
  width: Math.max(1, Math.round(stageW * dpr)),
11604
12007
  height: Math.max(1, Math.round(stageH * dpr)),
11605
12008
  dpr,
11606
- wipeX: wx * dpr,
11607
- soft: 0.75 * dpr,
12009
+ wipeX: fu.cut * dpr,
12010
+ soft: Math.max(0.75, fu.feather) * dpr,
11608
12011
  from: MODE_INDEX[fromMode],
11609
12012
  to: MODE_INDEX[toMode],
11610
12013
  mix: Math.min(1, Math.max(0, fade.x)),
@@ -11631,6 +12034,7 @@ function compare(container, options) {
11631
12034
  zu.step(dt);
11632
12035
  zv.step(dt);
11633
12036
  zw.step(dt);
12037
+ gripAt.step(dt);
11634
12038
  const playing = isPlaying();
11635
12039
  if (playing) {
11636
12040
  const m = master();
@@ -11661,11 +12065,9 @@ function compare(container, options) {
11661
12065
  pushFrame(s);
11662
12066
  }
11663
12067
  }
11664
- const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching;
12068
+ const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching || !gripAt.resting;
11665
12069
  if (moving)
11666
12070
  dirty = true;
11667
- if (auto && !lay.resting)
11668
- fitHeight();
11669
12071
  if (dirty)
11670
12072
  draw();
11671
12073
  return playing || moving;
@@ -11691,13 +12093,7 @@ function compare(container, options) {
11691
12093
  zw.set(c.w);
11692
12094
  requestDraw();
11693
12095
  };
11694
- const autoHeight = (w) => {
11695
- const shape = sizeOf(sides[1]);
11696
- const side = splitHeight(w, shape);
11697
- const over = w * shape.h / shape.w;
11698
- const h = side + (over - side) * lay.x;
11699
- return Math.max(60, Math.min(win.innerHeight * 0.8, h));
11700
- };
12096
+ const autoHeight = (w) => Math.max(60, Math.min(win.innerHeight * 0.8, splitHeight(w, sizeOf(sides[1]))));
11701
12097
  const fitHeight = () => {
11702
12098
  const h = autoHeight(stageW);
11703
12099
  if (Math.abs(h - stageH) < 0.25)
@@ -11709,7 +12105,8 @@ function compare(container, options) {
11709
12105
  const readColors = () => {
11710
12106
  const c = tokenColor(win.getComputedStyle(root), "--_ku-cmp-diff", [0.49, 0.81, 0.61, 1]);
11711
12107
  accent = [c[0], c[1], c[2]];
11712
- labelW = [labels[0].offsetWidth, labels[1].offsetWidth];
12108
+ const unfold = MARK.w - labelFold(folded, MARK.w).width + MARK.gap * folded;
12109
+ labelW = [labels[0].offsetWidth + unfold, labels[1].offsetWidth + unfold];
11713
12110
  requestDraw();
11714
12111
  };
11715
12112
  const measure = () => {
@@ -11800,8 +12197,10 @@ function compare(container, options) {
11800
12197
  root.focus({ preventScroll: true });
11801
12198
  touches.set(e.pointerId, p);
11802
12199
  stage.setPointerCapture(e.pointerId);
12200
+ press = touches.size === 1 && mode !== "wipe" && master() ? p : null;
11803
12201
  if (touches.size >= 2) {
11804
12202
  e.preventDefault();
12203
+ press = null;
11805
12204
  return startPinch();
11806
12205
  }
11807
12206
  if (mode === "wipe") {
@@ -11811,7 +12210,7 @@ function compare(container, options) {
11811
12210
  if (inside && (grab.onLine || !zoomed())) {
11812
12211
  drag = { kind: "wipe", offset: grab.offset };
11813
12212
  root.dataset.dragging = "";
11814
- gripY = p.y;
12213
+ gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
11815
12214
  e.preventDefault();
11816
12215
  if (!grab.onLine)
11817
12216
  setWipe(wipeAt(p.x, wr), true, true);
@@ -11829,6 +12228,8 @@ function compare(container, options) {
11829
12228
  const onMove = (e) => {
11830
12229
  const p = local(e);
11831
12230
  pointer = p;
12231
+ if (press && Math.hypot(p.x - press.x, p.y - press.y) > 4)
12232
+ press = null;
11832
12233
  if (touches.has(e.pointerId))
11833
12234
  touches.set(e.pointerId, p);
11834
12235
  if (pinch && touches.size >= 2) {
@@ -11842,14 +12243,12 @@ function compare(container, options) {
11842
12243
  pinch = { d, x: mid.x, y: mid.y };
11843
12244
  return handZoom(zoomAbout(panned, k, q.pu, q.pv));
11844
12245
  }
11845
- if (mode === "wipe" && drag?.kind !== "pan") {
11846
- gripY = p.y;
11847
- if (!drag)
11848
- placeGrip(wipeRect(layoutNow().L.overlay));
11849
- }
11850
- if (drag?.kind === "wipe")
11851
- setWipe(wipeAt(p.x, wipeRect(layoutNow().L.overlay), drag.offset));
11852
- else if (drag?.kind === "pan") {
12246
+ if (drag?.kind === "wipe") {
12247
+ const wr = wipeRect(layoutNow().L.overlay);
12248
+ gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
12249
+ setWipe(wipeAt(p.x, wr, drag.offset));
12250
+ placeGrip(wr);
12251
+ } else if (drag?.kind === "pan") {
11853
12252
  const dx = p.x - drag.x;
11854
12253
  const dy = p.y - drag.y;
11855
12254
  drag.x = p.x;
@@ -11860,11 +12259,23 @@ function compare(container, options) {
11860
12259
  drawNow();
11861
12260
  };
11862
12261
  const endDrag = (e) => {
12262
+ if (press && e.type === "pointerup") {
12263
+ api.toggle();
12264
+ glyph.flash(isPlaying());
12265
+ }
12266
+ press = null;
11863
12267
  touches.delete(e.pointerId);
11864
12268
  if (touches.size < 2)
11865
12269
  pinch = null;
11866
12270
  if (!drag)
11867
12271
  return;
12272
+ if (drag.kind === "wipe") {
12273
+ if (reducedMotion(win))
12274
+ gripAt.snap(0.5);
12275
+ else
12276
+ gripAt.set(0.5);
12277
+ requestDraw();
12278
+ }
11868
12279
  drag = null;
11869
12280
  delete root.dataset.dragging;
11870
12281
  delete root.dataset.panning;
@@ -12014,8 +12425,9 @@ function compare(container, options) {
12014
12425
  old.el.replaceWith(s.el);
12015
12426
  old.preview.replaceWith(s.preview);
12016
12427
  sides[index] = s;
12428
+ root.toggleAttribute("data-video", sides.some((x) => x.kind === "video"));
12017
12429
  note.textContent = "";
12018
- labels[index].textContent = labelOf(index);
12430
+ setLabel(index, labelOf(index));
12019
12431
  if (near)
12020
12432
  loadSide(s);
12021
12433
  readColors();
@@ -12026,8 +12438,9 @@ function compare(container, options) {
12026
12438
  stage.insertBefore(sides[1].preview, labels[0]);
12027
12439
  stage.insertBefore(sides[0].el, labels[0]);
12028
12440
  stage.insertBefore(sides[0].preview, labels[0]);
12029
- labels[0].textContent = labelOf(0);
12030
- labels[1].textContent = labelOf(1);
12441
+ setLabel(0, labelOf(0));
12442
+ setLabel(1, labelOf(1));
12443
+ root.toggleAttribute("data-video", sides.some((s) => s.kind === "video"));
12031
12444
  loupeBtn.setAttribute("aria-pressed", String(loupeOn));
12032
12445
  loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
12033
12446
  const modePill = modeList.length > 1 ? selectionPill(modes, {
@@ -12167,11 +12580,11 @@ function compare(container, options) {
12167
12580
  if (before)
12168
12581
  replace(0, before);
12169
12582
  else if (next.before)
12170
- labels[0].textContent = next.before.label ?? "Before";
12583
+ setLabel(0, next.before.label ?? "Before");
12171
12584
  if (after)
12172
12585
  replace(1, after);
12173
12586
  else if (next.after)
12174
- labels[1].textContent = next.after.label ?? "After";
12587
+ setLabel(1, next.after.label ?? "After");
12175
12588
  if (before || after)
12176
12589
  strip?.update({ ...before ? { before } : {}, ...after ? { after } : {} });
12177
12590
  if (next.before || next.after)
@@ -12228,6 +12641,7 @@ function compare(container, options) {
12228
12641
  disposed = true;
12229
12642
  loop.stop();
12230
12643
  modePill?.dispose();
12644
+ glyph.dispose();
12231
12645
  win.clearTimeout(releaseTimer);
12232
12646
  ro.disconnect();
12233
12647
  nearIo?.disconnect();
@@ -12257,7 +12671,7 @@ function compare(container, options) {
12257
12671
  wake();
12258
12672
  return api;
12259
12673
  }
12260
- var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
12674
+ var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
12261
12675
  var init_compare = __esm(() => {
12262
12676
  init_motion();
12263
12677
  init_page_hints();
@@ -12268,15 +12682,17 @@ var init_compare = __esm(() => {
12268
12682
  init_icons();
12269
12683
  init_look();
12270
12684
  init_rgba();
12685
+ init_player();
12271
12686
  init_selection_pill();
12272
12687
  init_timeline();
12273
12688
  init_timeline_model();
12274
- LAYOUT2 = springMotion({ duration: 0.35, bounce: 0 });
12689
+ LAYOUT2 = springMotion(SPRINGS.default);
12275
12690
  FADE6 = springMotion(SPRINGS.snappy);
12276
12691
  FADE_REDUCED = springMotion(FADE);
12277
12692
  ZOOM = springMotion({ duration: 0.35, bounce: 0 });
12278
12693
  WIPE_CATCH = springMotion(CATCH_UP);
12279
12694
  GRIP = { w: 16, h: 28 };
12695
+ MARK = { w: 12, gap: 4 };
12280
12696
  LOUPE = { r: 88, zoom: 4 };
12281
12697
  MODE_LABEL = {
12282
12698
  side: { short: "Side", long: "Side by side" },
@@ -16714,7 +17130,9 @@ function confirm(options) {
16714
17130
  }
16715
17131
 
16716
17132
  // src/ui/walkthrough.ts
17133
+ init_icons();
16717
17134
  init_look();
17135
+ init_selection_pill();
16718
17136
 
16719
17137
  // src/ui/toast.ts
16720
17138
  init_look();
@@ -16780,399 +17198,11 @@ function toast(message, options = {}) {
16780
17198
  };
16781
17199
  }
16782
17200
 
16783
- // src/ui/walkthrough-media.ts
16784
- init_compare_model();
16785
- init_glide();
16786
- init_look();
16787
-
16788
- // src/ui/walkthrough-css.ts
17201
+ // src/ui/walkthrough-blocks.ts
16789
17202
  init_motion();
16790
- var snappy3 = springCss(SPRINGS.snappy);
16791
- var calm = springCss(SPRINGS.calm);
16792
- var walkthroughCss = `
16793
- :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
16794
- .knobs-ui.knobs-walkthrough{--kw-snappy:${snappy3.easing};--kw-snappy-ms:${snappy3.duration}ms;--kw-calm:${calm.easing};--kw-calm-ms:${calm.duration}ms;
16795
- --kw-text:var(--k-walkthrough-text,14px);--kw-measure:var(--k-walkthrough-measure,68ch);--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
16796
- --kw-card:var(--_ku-pane-bg);--kw-card-ring:var(--_ku-pane-ring);--kw-pad:16px;--kw-ink:var(--k-walkthrough-ink,#ff375f);
16797
- container:knobs-wt / inline-size;display:block;width:100%;color:var(--_k-fg);font:var(--_k-weight) var(--kw-text)/1.5 var(--_k-font);text-align:left;
16798
- letter-spacing:normal;-webkit-font-smoothing:antialiased}
16799
- .knobs-walkthrough [hidden]{display:none!important}
16800
- .knobs-walkthrough :is(h1,h2,h3,p){margin:0;font:inherit;letter-spacing:inherit}
16801
- .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}
16802
- .knobs-walkthrough a:hover{text-decoration-color:currentColor}
16803
- .knobs-walkthrough code,.knobs-wt-slot code{font:calc(1em - 1px) var(--_k-mono);padding:1px 5px;border-radius:5px;background:var(--_k-fill)}
16804
- :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;
16805
- background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
16806
-
16807
- /* ——— layout: the rail beside the column; one column where it's narrow ——— */
16808
- .knobs-wt-layout{display:grid;grid-template-columns:200px minmax(0,1fr);gap:40px;align-items:start}
16809
- .knobs-wt-main{display:flex;flex-direction:column;gap:56px;min-width:0;max-width:1120px;padding-bottom:40vh}
16810
- @container knobs-wt (max-width:880px){.knobs-wt-layout{grid-template-columns:minmax(0,1fr);gap:0}.knobs-wt-main{gap:44px}}
16811
-
16812
- /* ——— the page's head ——— */
16813
- .knobs-wt-head{display:flex;flex-direction:column;gap:10px}
16814
- .knobs-wt-title{font-size:calc(var(--kw-text) + 10px)!important;font-weight:var(--_k-weight-title)!important;line-height:1.2;letter-spacing:-.01em!important}
16815
- .knobs-wt-summary{max-width:var(--kw-measure);color:var(--_k-fg-2)}
16816
- .knobs-wt-meta{display:flex;flex-wrap:wrap;gap:6px 16px;color:var(--_k-fg-3);font-size:calc(var(--kw-text) - 2px)}
16817
- .knobs-wt-meta>span{display:inline-flex;gap:6px;white-space:nowrap}
16818
- .knobs-wt-meta b{font-weight:var(--_k-weight);color:var(--_k-fg-2);font-family:var(--_k-mono);font-size:calc(1em - .5px)}
16819
-
16820
- /* ——— a section: a PR or topic ——— */
16821
- .knobs-wt-section{display:flex;flex-direction:column;gap:14px;min-width:0}
16822
- .knobs-wt-shead{display:flex;flex-direction:column;gap:6px;scroll-margin-top:16px}
16823
- .knobs-wt-kicker{display:flex;align-items:center;gap:10px;font-size:calc(var(--kw-text) - 2px);color:var(--_k-fg-3)}
16824
- .knobs-wt-kicker a{font-family:var(--_k-mono);color:var(--_k-fg-2);text-decoration:none}
16825
- .knobs-wt-kicker a:hover{color:var(--_k-fg)}
16826
- .knobs-wt-stitle{font-size:calc(var(--kw-text) + 4px)!important;font-weight:var(--_k-weight-title)!important;line-height:1.3}
16827
- .knobs-wt-ssum{max-width:var(--kw-measure);color:var(--_k-fg-2)}
16828
- .knobs-wt-chip{display:inline-flex;align-items:center;gap:6px;height:20px;padding:0 8px;border-radius:999px;font-size:calc(var(--kw-text) - 3px);font-weight:var(--_k-weight-label);white-space:nowrap;
16829
- --kw-tone:var(--_k-fg-2);color:var(--kw-tone);background:color-mix(in srgb,var(--kw-tone) 14%,transparent)}
16830
- .knobs-wt-chip::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
16831
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="good"]{--kw-tone:var(--kw-good)}
16832
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="warn"]{--kw-tone:var(--_k-warn)}
16833
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="bad"]{--kw-tone:var(--_k-danger)}
16834
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="info"]{--kw-tone:var(--kw-info)}
16835
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="dim"]{--kw-tone:var(--_k-fg-2)}
16836
-
16837
- /* ——— a step: one card ——— */
16838
- .knobs-wt-step{position:relative;display:flex;flex-direction:column;gap:12px;padding:var(--kw-pad);border-radius:var(--_k-radius-panel);background:var(--kw-card);box-shadow:var(--kw-card-ring);
16839
- scroll-margin-top:16px;outline:none;container:knobs-wt-step / inline-size}
16840
- .knobs-walkthrough[data-frame="flat"] .knobs-wt-step{box-shadow:inset 0 0 0 1px var(--_k-border)}
16841
- .knobs-wt-step-head{display:flex;align-items:baseline;gap:10px;min-width:0}
16842
- .knobs-wt-num{flex:none;font:var(--_k-weight) calc(var(--kw-text) - 2px)/1 var(--_k-mono);color:var(--_k-fg-3);font-variant-numeric:tabular-nums}
16843
- .knobs-wt-stepTitle{flex:1;min-width:0;font-weight:var(--_k-weight-title)!important}
16844
- .knobs-wt-caption{max-width:var(--kw-measure);color:var(--_k-fg-2);margin-top:-6px!important}
16845
- .knobs-wt-question{font-weight:var(--_k-weight-label)!important}
16846
- /* The step Kit is on: its number lights, and its choices show their keys. */
16847
- .knobs-wt-step[data-current] .knobs-wt-num{color:var(--_k-fg)}
16848
- .knobs-wt-media{display:grid;grid-template-columns:repeat(var(--kw-n,1),minmax(0,1fr));gap:12px;min-width:0}
16849
- @container knobs-wt-step (max-width:640px){.knobs-wt-media{grid-template-columns:minmax(0,1fr)}}
16850
-
16851
- /* ——— the feed: the agent's updates and answers, newest first, compact ——— */
16852
- .knobs-wt-feed{margin-top:-24px}
16853
- .knobs-wt-feed-list{list-style:none;margin:0;padding:0;max-height:260px;overflow:auto;scrollbar-width:none;display:flex;flex-direction:column}
16854
- .knobs-wt-feed-list::-webkit-scrollbar{display:none}
16855
- .knobs-wt-fi{display:grid;grid-template-columns:64px 56px minmax(0,1fr) auto;gap:10px;align-items:baseline;padding:6px 0;font-size:calc(var(--kw-text) - 1px)}
16856
- .knobs-wt-fi+.knobs-wt-fi{box-shadow:inset 0 1px 0 var(--_k-border)}
16857
- .knobs-wt-fi-when{color:var(--_k-fg-3);font-size:calc(var(--kw-text) - 2.5px);font-variant-numeric:tabular-nums;white-space:nowrap}
16858
- .knobs-wt-fi-kind{color:var(--_k-fg-2);font-size:calc(var(--kw-text) - 2.5px);font-weight:var(--_k-weight-label)}
16859
- .knobs-wt-fi[data-kind="qa"] .knobs-wt-fi-kind{color:var(--kw-info)}
16860
- .knobs-wt-fi-body{max-width:var(--kw-measure);color:var(--_k-fg)}
16861
- .knobs-wt-fi-q{color:var(--_k-fg-2)}
16862
- .knobs-wt-fi-q::before{content:"You asked: ";color:var(--_k-fg-3)}
16863
- :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;
16864
- padding:3px 6px;margin:0;border-radius:6px;cursor:pointer;outline:none;transition:background .15s,color .15s}
16865
- .knobs-ui :is(.knobs-wt-fi-step,.knobs-wt-steplink):is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
16866
-
16867
- /* ——— a step's sent notes ——— */
16868
- .knobs-wt-notes{display:flex;flex-direction:column}
16869
- .knobs-wt-notes:empty{display:none}
16870
- .knobs-wt-n{display:flex;align-items:flex-start;gap:10px;padding:6px 0;overflow:hidden;
16871
- transition:height var(--kw-snappy-ms) var(--kw-snappy),opacity var(--kw-snappy-ms) var(--kw-snappy),filter var(--kw-snappy-ms) var(--kw-snappy)}
16872
- .knobs-wt-n[data-entering]{opacity:0;filter:blur(3px)}
16873
- .knobs-wt-n[data-leaving]{opacity:0;filter:blur(3px);padding-block:0}
16874
- .knobs-wt-n+.knobs-wt-n{box-shadow:inset 0 1px 0 var(--_k-border)}
16875
- .knobs-wt-n-meta{flex:none;min-width:44px;padding-top:2px;color:var(--_k-fg-3);font:var(--_k-weight) calc(var(--kw-text) - 2.5px)/1.4 var(--_k-mono);font-variant-numeric:tabular-nums}
16876
- .knobs-wt-n-meta[data-seek]{cursor:pointer}
16877
- .knobs-wt-n-meta[data-seek]:hover{color:var(--_k-fg)}
16878
- .knobs-wt-n-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px;font-size:calc(var(--kw-text) - 1px)}
16879
- .knobs-wt-n-voice{height:30px;max-width:320px}
16880
- .knobs-wt-n-ink{display:block;width:120px;max-height:80px;border-radius:6px;background:var(--_k-fill)}
16881
- .knobs-wt-n-ink path{fill:none;stroke:var(--kw-ink);stroke-width:2px;vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round}
16882
-
16883
- /* ——— the note bar, docked at the bottom of the column ——— */
16884
- .knobs-wt-dock{position:sticky;bottom:12px;z-index:40;margin-top:-24px}
16885
- .knobs-wt-notebar{display:flex;flex-direction:column;gap:2px;padding:6px 6px 6px 14px;border-radius:calc(var(--_k-radius-panel) + 2px);background:var(--_k-bg);
16886
- box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay)}
16887
- .knobs-wt-on{padding-top:3px;color:var(--_k-fg-3);font-size:calc(var(--kw-text) - 2.5px);white-space:nowrap;overflow:hidden;
16888
- -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)}
16889
- .knobs-wt-row{display:flex;align-items:flex-end;gap:4px}
16890
- :where(.knobs-ui) :is(.knobs-wt-input,.knobs-wt-tool,.knobs-wt-x,.knobs-wt-back){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;
16891
- border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
16892
- .knobs-ui .knobs-wt-input{flex:1;min-width:0;height:32px;max-height:140px;padding:6px 0;resize:none;color:var(--_k-fg);caret-color:var(--_k-accent);line-height:20px;font-size:var(--kw-text)}
16893
- .knobs-ui .knobs-wt-input::placeholder{color:var(--_k-fg-3)}
16894
- .knobs-wt-attach{display:flex;gap:4px;align-items:center;align-self:center}
16895
- .knobs-wt-attach:empty{display:none}
16896
- .knobs-wt-chip-a{display:inline-flex;align-items:center;gap:2px;height:24px;padding:0 2px 0 9px;border-radius:999px;background:var(--_k-fill-selected);font-size:calc(var(--kw-text) - 2.5px);white-space:nowrap}
16897
- .knobs-ui .knobs-wt-x{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;color:var(--_k-fg-3);cursor:pointer;transition:background .15s,color .15s}
16898
- .knobs-ui .knobs-wt-x:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
16899
- .knobs-wt-x svg{width:10px;height:10px}
16900
- .knobs-ui .knobs-wt-tool{flex:none;display:inline-flex;align-items:center;justify-content:center;gap:5px;min-width:32px;height:32px;padding:0 8px;border-radius:calc(var(--_k-radius) + 2px);
16901
- color:var(--_k-fg-2);cursor:pointer;transition:background .15s,color .15s}
16902
- .knobs-ui .knobs-wt-tool:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
16903
- .knobs-ui .knobs-wt-tool[aria-pressed="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
16904
- .knobs-wt-tool svg{width:15px;height:15px}
16905
- .knobs-wt-rec{font-size:calc(var(--kw-text) - 2.5px);font-variant-numeric:tabular-nums}
16906
- .knobs-wt-rec:empty{display:none}
16907
- .knobs-ui .knobs-wt-tool[data-send]{color:var(--_k-fg-3)}
16908
- .knobs-ui .knobs-wt-tool[data-send][data-ready]{background:var(--_k-accent);color:var(--_k-bg)}
16909
- /* Back: in place over the bar for a few seconds after a note moves the page on. */
16910
- .knobs-ui .knobs-wt-back{position:absolute;left:8px;bottom:calc(100% + 8px);height:28px;padding:0 12px;border-radius:999px;background:var(--_k-bg);color:var(--_k-fg);
16911
- box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font-size:calc(var(--kw-text) - 1.5px);font-weight:var(--_k-weight-label);cursor:pointer;
16912
- opacity:0;scale:.96;filter:blur(3px);pointer-events:none;transition:opacity .11s cubic-bezier(.4,0,1,1),scale .11s cubic-bezier(.4,0,1,1),filter .11s cubic-bezier(.4,0,1,1)}
16913
- .knobs-ui .knobs-wt-back[data-on]{opacity:1;scale:1;filter:none;pointer-events:auto;transition:opacity .15s cubic-bezier(.2,.8,.2,1),scale .15s cubic-bezier(.2,.8,.2,1),filter .15s cubic-bezier(.2,.8,.2,1)}
16914
- .knobs-ui .knobs-wt-back:hover{background:var(--_k-fill-selected)}
16915
-
16916
- /* ——— ink over a step's media ——— */
16917
- .knobs-wt-media,.knobs-wt-options{position:relative}
16918
- .knobs-wt-ink{position:absolute;inset:0;z-index:5;width:100%;height:100%;overflow:visible;pointer-events:none;touch-action:none}
16919
- .knobs-wt-ink[data-on]{pointer-events:auto;cursor:crosshair}
16920
- .knobs-wt-ink path{fill:none;stroke:var(--kw-ink);stroke-width:3px;vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round}
16921
-
16922
- /* ——— full-window: the step over the page, its media filling it; its place held in the page ——— */
16923
- .knobs-wt-scrim{position:fixed;inset:0;z-index:30;background:color-mix(in srgb,var(--_k-bg-page) 82%,transparent);opacity:0;pointer-events:none}
16924
- .knobs-wt-scrim[data-on]{pointer-events:auto}
16925
- .knobs-wt-hold-card{flex:none}
16926
- .knobs-wt-step[data-full]{position:fixed;z-index:35;margin:0;overflow:hidden;overscroll-behavior:contain;padding-bottom:84px;box-shadow:var(--kw-card-ring),var(--_k-shadow-overlay);background:var(--_k-bg)}
16927
- .knobs-wt-step[data-full]>:not(.knobs-wt-media){flex:none}
16928
- .knobs-wt-step[data-full] .knobs-wt-notes{display:none}
16929
- .knobs-wt-step[data-full]>.knobs-wt-media{flex:1;min-height:0;grid-auto-rows:minmax(0,1fr)}
16930
- .knobs-wt-step[data-full] .knobs-wt-slot{display:flex;flex-direction:column;min-height:0}
16931
- .knobs-wt-step[data-full] .knobs-wt-body{flex:1;min-height:0}
16932
- .knobs-wt-step[data-full] .knobs-wt-fit{height:100%;transition:none}
16933
- .knobs-wt-step[data-full] .knobs-wt-frame{height:100%;aspect-ratio:auto}
16934
-
16935
- /* ——— a taste test: the options side by side, Kit's pick lit and the rest stepping back ——— */
16936
- .knobs-wt-options{display:grid;grid-template-columns:repeat(var(--kw-n,2),minmax(0,1fr));gap:10px}
16937
- @container knobs-wt-step (max-width:640px){.knobs-wt-options{grid-template-columns:minmax(0,1fr)}}
16938
- .knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:8px;min-width:0;padding:8px;border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_ku-wash);
16939
- transition:background var(--kw-snappy-ms) var(--kw-snappy)}
16940
- .knobs-wt-option[data-picked]{background:color-mix(in srgb,var(--_k-accent) 12%,var(--_ku-wash))}
16941
- .knobs-wt-option>:not(.knobs-wt-pick){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
16942
- .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-pick){opacity:.55}
16943
- .knobs-ui .knobs-wt-pick{display:flex;align-items:center;gap:8px;width:100%;min-height:28px;padding:0 4px;border-radius:calc(var(--_k-radius) - 2px);cursor:pointer;color:var(--_k-fg-2);text-align:left}
16944
- .knobs-ui .knobs-wt-pick:is(:hover,:focus-visible){color:var(--_k-fg)}
16945
- .knobs-wt-letter{flex:none;font:var(--_k-weight-title) calc(var(--kw-text) - 1px)/1 var(--_k-font);color:var(--_k-fg)}
16946
- .knobs-wt-olabel{flex:1;min-width:0;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
16947
- .knobs-wt-suggested{flex:none;font-size:calc(var(--kw-text) - 3px);color:var(--_k-fg-3)}
16948
- .knobs-wt-check{flex:none;display:flex;width:18px;height:18px;align-items:center;justify-content:center;border-radius:50%;background:var(--_k-fill);color:transparent;
16949
- transition:background var(--kw-snappy-ms) var(--kw-snappy),color var(--kw-snappy-ms) var(--kw-snappy)}
16950
- .knobs-wt-check svg{width:12px;height:12px}
16951
- .knobs-wt-option[data-picked] .knobs-wt-check{background:var(--_k-accent);color:var(--_k-bg)}
16952
- .knobs-wt-key{display:inline-flex;align-items:center;justify-content:center;min-width:15px;height:15px;padding:0 3px;border-radius:4px;background:var(--_k-fill);color:var(--_k-fg-3);
16953
- font:var(--_k-weight-label) calc(var(--kw-text) - 4px)/1 var(--_k-font);font-variant-numeric:tabular-nums;opacity:.5;transition:opacity .15s}
16954
- .knobs-wt-step[data-current] .knobs-wt-key{opacity:1}
16955
- .knobs-wt-ocap{color:var(--_k-fg-2);font-size:calc(var(--kw-text) - 1px);padding:0 4px}
16956
-
16957
- /* ——— the rail: progress, contents, export ——— */
16958
- .knobs-wt-rail{position:sticky;top:16px;display:flex;flex-direction:column;gap:14px;max-height:calc(100vh - 32px);min-height:0;font-size:calc(var(--kw-text) - 1.5px)}
16959
- .knobs-wt-progress{display:flex;flex-direction:column;gap:7px}
16960
- .knobs-wt-count{display:flex;align-items:baseline;justify-content:space-between;color:var(--_k-fg-2);font-variant-numeric:tabular-nums}
16961
- .knobs-wt-count b{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
16962
- .knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
16963
- .knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
16964
- .knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
16965
- /* The contents: no box, one text spine for sections and steps (the steps' dots hang in the
16966
- gutter left of it), and one pill behind the current step that springs between rows. */
16967
- .knobs-wt-toc{position:relative;flex:1 1 auto;min-height:0;overflow:auto;scrollbar-width:none;margin:0 -8px;padding:0 0 8px}
16968
- .knobs-wt-toc::-webkit-scrollbar{display:none}
16969
- .knobs-wt-toc-section{display:block;padding:12px 8px 4px 26px;color:var(--_k-fg-3);font-size:calc(var(--kw-text) - 3px);font-weight:var(--_k-weight-label);white-space:nowrap;overflow:hidden;
16970
- -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)}
16971
- .knobs-wt-toc-section:first-of-type{padding-top:2px}
16972
- .knobs-wt-toc-mark{position:absolute;left:0;right:0;top:0;height:0;border-radius:calc(var(--_k-radius) - 1px);background:var(--_ku-wash-hi);pointer-events:none;opacity:0;will-change:transform}
16973
- .knobs-wt-toc-mark[data-on]{opacity:1}
16974
- .knobs-walkthrough[data-settled] .knobs-wt-toc-mark{transition:opacity .15s}
16975
- .knobs-ui .knobs-wt-toc-step{position:relative;display:flex;align-items:center;width:100%;text-align:left;height:26px;padding:0 8px 0 26px;color:var(--_k-fg-2);cursor:pointer;white-space:nowrap}
16976
- .knobs-ui .knobs-wt-toc-step:is(:hover,:focus-visible){color:var(--_k-fg)}
16977
- .knobs-wt-toc-step[aria-current="step"]{color:var(--_k-fg)}
16978
- .knobs-wt-toc-label{flex:1;min-width:0;overflow:hidden;-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)}
16979
- /* A step's state: an open ring, a filled dot once answered, nothing to answer a faint ring. */
16980
- .knobs-wt-toc-glyph{--kw-tone:var(--_k-fg-3);position:absolute;left:11px;top:50%;width:7px;height:7px;margin-top:-3.5px;border-radius:50%;box-shadow:inset 0 0 0 1.5px var(--kw-tone);
16981
- transition:background var(--kw-snappy-ms) var(--kw-snappy),box-shadow var(--kw-snappy-ms) var(--kw-snappy)}
16982
- .knobs-wt-toc-glyph[data-tone="accent"]{--kw-tone:var(--_k-accent)}
16983
- .knobs-wt-toc-glyph[data-done]{background:var(--kw-tone)}
16984
- .knobs-wt-toc-glyph[data-tone="none"]{box-shadow:inset 0 0 0 1px var(--_k-border)}
16985
- .knobs-wt-actions{display:flex;flex-wrap:wrap;gap:4px}
16986
- .knobs-ui .knobs-wt-btn{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
16987
- transition:background .15s,color .15s}
16988
- .knobs-ui .knobs-wt-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
16989
- .knobs-ui .knobs-wt-btn[data-primary]{background:var(--_k-fill-selected);color:var(--_k-fg)}
16990
- .knobs-ui .knobs-wt-btn[data-primary]:is(:hover,:focus-visible){background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
16991
- .knobs-wt-keys{color:var(--_k-fg-3);font-size:calc(var(--kw-text) - 3px);line-height:1.6}
16992
- .knobs-wt-keys kbd{font:inherit;color:var(--_k-fg-2)}
16993
- /* Narrow: the rail becomes a bar along the top, with the progress and the export only. */
16994
- @container knobs-wt (max-width:880px){
16995
- .knobs-wt-rail{z-index:4;flex-direction:row;align-items:center;gap:12px;max-height:none;margin:0 calc(var(--kw-pad) * -1) 20px;padding:8px var(--kw-pad);top:0;background:var(--_k-bg-page)}
16996
- .knobs-wt-progress{flex:1}.knobs-wt-toc,.knobs-wt-keys{display:none}
16997
- .knobs-wt-step,.knobs-wt-shead{scroll-margin-top:60px}
16998
- }
16999
-
17000
- /* ——— media slots ——— */
17001
- .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);
17002
- --kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info))}
17003
- .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}
17004
- /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
17005
- sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
17006
- (92 px), and the frame strip with its gap (158 px). */
17007
- .knobs-wt-fit{width:100%;height:calc(100cqw / 2 / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
17008
- .knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
17009
- .knobs-wt-fit[data-strip]{--kw-chrome:158px}
17010
- .knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
17011
- .knobs-wt-frame{position:relative;width:100%;overflow:hidden;border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill);aspect-ratio:var(--kw-aspect,16/9)}
17012
- .knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
17013
- .knobs-wt-frame[data-natural]{aspect-ratio:auto}
17014
- .knobs-wt-frame[data-natural]>img{position:static;height:auto}
17015
- .knobs-wt-code{margin:0;padding:12px 14px;max-height:420px;overflow:auto;border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill);color:var(--_k-fg);
17016
- font:calc(var(--k-walkthrough-text,14px) - 2px)/1.55 var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
17017
- .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
17018
- .knobs-wt-prompt{color:var(--_k-fg-3);user-select:none;-webkit-user-select:none}
17019
- .knobs-wt-cmd{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
17020
- .knobs-wt-out{color:var(--_k-fg-2)}
17021
- .knobs-wt-metric{display:flex;flex-direction:column;gap:8px;padding:12px 14px;border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill)}
17022
- .knobs-wt-mlabel{color:var(--_k-fg-2);font-size:calc(var(--k-walkthrough-text,14px) - 2px)}
17023
- .knobs-wt-mvalue{display:flex;align-items:baseline;flex-wrap:wrap;gap:8px;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
17024
- .knobs-wt-mfrom{color:var(--_k-fg-3);font-size:calc(var(--k-walkthrough-text,14px) + 2px)}
17025
- .knobs-wt-mto{font-size:calc(var(--k-walkthrough-text,14px) + 12px);font-weight:var(--_k-weight-title);line-height:1}
17026
- .knobs-wt-mof{color:var(--_k-fg-3)}
17027
- .knobs-wt-mdelta{--kw-tone:var(--_k-fg-2);padding:1px 7px;border-radius:999px;font-size:calc(var(--k-walkthrough-text,14px) - 3px);font-weight:var(--_k-weight-label);color:var(--kw-tone);background:color-mix(in srgb,var(--kw-tone) 14%,transparent)}
17028
- .knobs-wt-mdelta[data-tone="good"]{--kw-tone:var(--kw-good)}
17029
- .knobs-wt-mdelta[data-tone="bad"]{--kw-tone:var(--_k-danger)}
17030
- .knobs-wt-mtrack{position:relative;height:4px;border-radius:2px;background:color-mix(in srgb,var(--_k-fg) 8%,transparent)}
17031
- .knobs-wt-mtrack>span{position:absolute;top:0;bottom:0;left:0;border-radius:inherit}
17032
- .knobs-wt-mtrack>.knobs-wt-mafter{background:var(--_k-accent)}
17033
- .knobs-wt-mtrack>.knobs-wt-mbefore{background:color-mix(in srgb,var(--_k-fg) 30%,transparent)}
17034
- .knobs-wt-callout{--kw-tone:var(--kw-info);display:flex;gap:10px;padding:10px 14px;border-radius:calc(var(--_k-radius-panel) - 4px);background:color-mix(in srgb,var(--kw-tone) 11%,transparent);color:var(--_k-fg)}
17035
- .knobs-wt-callout::before{content:"";flex:none;width:6px;height:6px;margin-top:.62em;border-radius:50%;background:var(--kw-tone)}
17036
- .knobs-wt-callout[data-tone="good"]{--kw-tone:var(--kw-good)}
17037
- .knobs-wt-callout[data-tone="warn"]{--kw-tone:var(--_k-warn)}
17038
- .knobs-wt-callout[data-tone="bad"]{--kw-tone:var(--_k-danger)}
17039
- .knobs-wt-text{max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
17040
- .knobs-wt-hold{border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill)}
17041
- .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)}
17042
-
17043
- /* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
17044
- hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
17045
- :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)}
17046
- :where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
17047
- :where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
17048
- :where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
17049
- :where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
17050
- :where(.knobs-wt-slot) [data-tone="dim"]{--kw-tone:var(--_k-fg-3)}
17051
- .knobs-wt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
17052
- .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);
17053
- text-decoration:none;white-space:nowrap;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
17054
- .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)}
17055
-
17056
- /* A history: dates down the left, a spine of tone dots joined by a hairline (broken around each
17057
- dot), the event beside it. Inferred events take a ring for a dot and a dashed tag. */
17058
- .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)}
17059
- .knobs-wt-hgroup{display:flex;flex-direction:column;gap:6px;min-width:0}
17060
- .knobs-wt-hhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px}
17061
- .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)}
17062
- .knobs-wt-hsum{color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 2px);font-variant-numeric:tabular-nums}
17063
- .knobs-wt-hlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
17064
- .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}
17065
- .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)}
17066
- .knobs-wt-hage{color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 3px);line-height:1.35}
17067
- .knobs-wt-hspine{grid-area:spine;position:relative;align-self:stretch;margin-block:-5px}
17068
- .knobs-wt-hspine::before,.knobs-wt-hspine::after{content:"";position:absolute;left:6.5px;width:1px;background:var(--kw-rail)}
17069
- .knobs-wt-hspine::before{top:0;height:calc(var(--kw-hy) - 6px)}
17070
- .knobs-wt-hspine::after{top:calc(var(--kw-hy) + 6px);bottom:0}
17071
- .knobs-wt-hev:first-child .knobs-wt-hspine::before,.knobs-wt-hev:last-child .knobs-wt-hspine::after{content:none}
17072
- .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))}
17073
- .knobs-wt-hev[data-type="release"] .knobs-wt-hdot{border-radius:1.5px;rotate:45deg;scale:.9}
17074
- .knobs-wt-hev[data-type="audit"] .knobs-wt-hdot{border-radius:1.5px}
17075
- .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))}
17076
- .knobs-wt-hmain{grid-area:main;display:flex;flex-direction:column;gap:2px;min-width:0}
17077
- .knobs-wt-htitle{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
17078
- .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}
17079
- .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}
17080
- .knobs-ui .knobs-wt-hdetail{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
17081
- @container knobs-wt-slot (max-width:520px){
17082
- .knobs-wt-hev{grid-template-columns:14px minmax(0,1fr);grid-template-areas:"spine when" "spine main"}
17083
- .knobs-wt-hwhen{flex-direction:row;flex-wrap:wrap;align-items:baseline;column-gap:8px}
17084
- }
17085
-
17086
- /* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
17087
- git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
17088
- .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)}
17089
- .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);
17090
- border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
17091
- .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
17092
- .knobs-wt-srow[data-go]{cursor:pointer}
17093
- .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
17094
- .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
17095
- .knobs-wt-lane{position:relative;flex:none;width:16px}
17096
- .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)}
17097
- .knobs-wt-lane[data-down]::before{top:calc(var(--kw-sy) + 3.5px)}
17098
- .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}
17099
- .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)}
17100
- .knobs-wt-srow[data-trunk] .knobs-wt-lane[data-rail="node"]::after{background:var(--_k-fg-2)}
17101
- .knobs-wt-trunk{font:var(--_k-weight-label) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2)}
17102
- .knobs-wt-smain{display:flex;flex-direction:column;gap:1px;min-width:0}
17103
- .knobs-wt-sline{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 8px}
17104
- .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}
17105
- .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}
17106
- .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
17107
- .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
17108
- .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)}
17109
- .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)}
17110
- .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
17111
- .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)}
17112
- .knobs-wt-delta{display:inline-flex;gap:6px}
17113
- .knobs-wt-add{color:var(--_k-diff-add)}
17114
- .knobs-wt-del{color:var(--_k-diff-del)}
17115
- .knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
17116
- .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
17117
- .knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
17118
- @container knobs-wt-slot (max-width:520px){
17119
- .knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
17120
- .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
17121
- .knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
17122
- }
17123
-
17124
- /* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
17125
- (the top one under the head), and thin bars show on hover. */
17126
- @property --kw-fade-l{syntax:"<number>";inherits:false;initial-value:1}
17127
- @property --kw-fade-r{syntax:"<number>";inherits:false;initial-value:1}
17128
- @keyframes kw-fade-l{from{--kw-fade-l:1}to{--kw-fade-l:0}}
17129
- @keyframes kw-fade-r{from{--kw-fade-r:0}to{--kw-fade-r:1}}
17130
- .knobs-wt-table{max-height:520px;overflow:auto;overscroll-behavior-x:contain;outline:none;scrollbar-width:thin;scrollbar-color:transparent transparent;
17131
- transition:scrollbar-color calc(var(--_k-hover-out) * 1ms)}
17132
- .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)}
17133
- .knobs-wt-table:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus);border-radius:calc(var(--_k-radius-panel) - 4px)}
17134
- @supports (animation-timeline:scroll()){
17135
- .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;
17136
- 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%;
17137
- --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)));
17138
- --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)));
17139
- -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}
17140
- }
17141
- .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)}
17142
- .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)}
17143
- .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;
17144
- background:var(--_k-pane-bg);box-shadow:inset 0 -1px 0 var(--_k-border)}
17145
- .knobs-wt-table td{padding:8px 12px;vertical-align:baseline;box-shadow:inset 0 -1px 0 var(--_k-border)}
17146
- .knobs-wt-table tr:last-child td{box-shadow:none}
17147
- .knobs-wt-table :is(th,td):first-child{padding-left:0}
17148
- .knobs-wt-table :is(th,td):last-child{padding-right:0}
17149
- .knobs-wt-table td:first-child{color:var(--_k-fg)}
17150
- .knobs-wt-table [data-align="right"]{text-align:right}
17151
- .knobs-wt-table [data-align="center"]{text-align:center}
17152
- .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}
17153
- .knobs-wt-table td[data-long]{min-width:24ch}
17154
- .knobs-wt-table td>.knobs-wt-steplink{margin-left:4px}
17155
- .knobs-ui .knobs-wt-table a.knobs-wt-chip{text-decoration:none}
17156
- .knobs-wt-ctone{color:var(--kw-tone)}
17157
-
17158
- /* Chapters: timestamp pills under a video; one highlight springs to the one playing. */
17159
- .knobs-wt-chapters{position:relative;display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}
17160
- .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;
17161
- transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms)}
17162
- .knobs-wt-chapter-mark[data-on]{opacity:1}
17163
- .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);
17164
- 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)}
17165
- .knobs-ui .knobs-wt-chapter:hover{background:var(--_ku-wash);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
17166
- .knobs-ui .knobs-wt-chapter[aria-current]{background:transparent;color:var(--_k-fg)}
17167
- .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus)}
17168
- .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)}
17169
- .knobs-wt-chapter[aria-current] .knobs-wt-chapter-time{color:var(--_k-fg-2)}
17170
- @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
17171
- `;
17172
-
17173
- // src/ui/walkthrough-blocks.ts
17174
- init_motion();
17175
- init_look();
17203
+ init_icons();
17204
+ init_look();
17205
+ init_selection_pill();
17176
17206
 
17177
17207
  // src/ui/walkthrough-model.ts
17178
17208
  var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
@@ -17379,7 +17409,15 @@ function fillTable(m) {
17379
17409
  out[k] = fillCell(v);
17380
17410
  return out;
17381
17411
  });
17382
- return { kind: "table", columns, rows, ...m.caption ? { caption: m.caption } : {} };
17412
+ const primary = columns.find((c) => c.key === m.primary)?.key ?? columns[0]?.key ?? "";
17413
+ return {
17414
+ kind: "table",
17415
+ columns,
17416
+ rows,
17417
+ ...m.caption ? { caption: m.caption } : {},
17418
+ stack: m.stack ?? "auto",
17419
+ primary
17420
+ };
17383
17421
  }
17384
17422
  function fillChapters(chapters) {
17385
17423
  const id = uniqueIds();
@@ -17452,6 +17490,8 @@ function normalizeWalkthrough(doc) {
17452
17490
  ...step.caption ? { caption: step.caption } : {},
17453
17491
  ...step.question ? { question: step.question } : {},
17454
17492
  media: list.map((m) => fillMedia(m, doc.base)),
17493
+ columns: Math.max(1, Math.min(3, Math.round(step.columns ?? 1))),
17494
+ ...step.more?.trim() ? { more: step.more.trim() } : {},
17455
17495
  options,
17456
17496
  ask
17457
17497
  };
@@ -17463,8 +17503,10 @@ function normalizeWalkthrough(doc) {
17463
17503
  id,
17464
17504
  title: s.title,
17465
17505
  ...link ? { link } : {},
17506
+ ...typeof s.kicker === "string" && s.kicker.trim() ? { kicker: s.kicker.trim() } : {},
17466
17507
  ...s.status ? { status: s.status } : {},
17467
17508
  ...s.summary ? { summary: s.summary } : {},
17509
+ ...s.more?.trim() ? { more: s.more.trim() } : {},
17468
17510
  steps: own
17469
17511
  };
17470
17512
  });
@@ -17473,30 +17515,95 @@ function normalizeWalkthrough(doc) {
17473
17515
  value
17474
17516
  }));
17475
17517
  const stepRef = (ref) => findStep(steps, ref)?.key ?? null;
17518
+ const extra = (x) => ({
17519
+ ...x.status?.trim() ? { status: x.status.trim() } : {},
17520
+ trail: (x.trail ?? []).filter((t) => typeof t === "string" && t.trim())
17521
+ });
17476
17522
  const feed = sortFeed([
17477
- ...(doc.updates ?? []).map((u) => ({ kind: "update", at: parseAt(u.at), text: u.text, step: stepRef(u.step) })),
17523
+ ...(doc.updates ?? []).map((u) => ({
17524
+ kind: "update",
17525
+ at: parseAt(u.at),
17526
+ text: u.text,
17527
+ step: stepRef(u.step),
17528
+ ...extra(u)
17529
+ })),
17478
17530
  ...(doc.qa ?? []).map((x) => ({
17479
17531
  kind: "qa",
17480
17532
  at: parseAt(x.at),
17481
17533
  text: x.a,
17482
17534
  q: x.q,
17483
- step: stepRef(x.step)
17535
+ step: stepRef(x.step),
17536
+ ...extra(x)
17484
17537
  }))
17485
17538
  ]);
17486
17539
  return {
17487
17540
  id: doc.id,
17488
17541
  title: doc.title,
17489
17542
  ...doc.summary ? { summary: doc.summary } : {},
17543
+ ...doc.more?.trim() ? { more: doc.more.trim() } : {},
17490
17544
  meta,
17491
17545
  feed,
17492
17546
  verdicts: ((doc.verdicts?.length) ? doc.verdicts : DEFAULT_VERDICTS).map((v) => ({
17493
17547
  id: slug(v),
17494
17548
  label: label(v)
17495
17549
  })),
17496
- sections,
17497
- steps
17550
+ sections: withKickers(sections, doc.sections, doc.kickers),
17551
+ steps,
17552
+ prs: knownPRs(doc, sections),
17553
+ ...repoOf(doc) ? { repo: repoOf(doc) } : {}
17498
17554
  };
17499
17555
  }
17556
+ var prNumber = (href) => /\/(?:pull|merge_requests)\/(\d+)(?:[/?#]|$)/.exec(href)?.[1] ?? null;
17557
+ var repoOf = (doc) => doc.repo?.trim().replace(/^https?:\/\/github\.com\//, "").replace(/\/+$/, "") || undefined;
17558
+ function knownPRs(doc, sections) {
17559
+ const out = {};
17560
+ const repo = repoOf(doc);
17561
+ for (const s of sections) {
17562
+ const n = s.link && prNumber(s.link.href);
17563
+ if (n)
17564
+ out[n] = { href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1") };
17565
+ }
17566
+ for (const [k, pr] of Object.entries(doc.prs ?? {})) {
17567
+ const n = k.replace(/^#/, "");
17568
+ if (!/^\d+$/.test(n))
17569
+ continue;
17570
+ out[n] = { ...out[n], ...pr };
17571
+ }
17572
+ if (repo) {
17573
+ for (const n of Object.keys(out))
17574
+ if (!out[n].href)
17575
+ out[n] = { ...out[n], href: `https://github.com/${repo}/pull/${n}` };
17576
+ }
17577
+ return out;
17578
+ }
17579
+ function findPR(prs, n, repo) {
17580
+ const known = prs[n];
17581
+ if (known)
17582
+ return known;
17583
+ return repo ? { href: `https://github.com/${repo}/pull/${n}` } : null;
17584
+ }
17585
+ var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
17586
+ function withKickers(sections, source, kickers = "pr") {
17587
+ if (kickers === false)
17588
+ return [...sections];
17589
+ const noun = typeof kickers === "object" ? kickers.noun.trim() : "PR";
17590
+ const counted = typeof kickers === "object" ? sections : sections.filter((s) => s.link && isPullRequest(s.link.href));
17591
+ return sections.map((s, i) => {
17592
+ const own = source[i]?.kicker;
17593
+ if (own !== undefined || s.kicker)
17594
+ return s;
17595
+ const at = counted.indexOf(s);
17596
+ return at < 0 ? s : { ...s, kicker: `${noun} ${at + 1} of ${counted.length}`.trim() };
17597
+ });
17598
+ }
17599
+ function tableStacks(stack, room, natural) {
17600
+ if (stack !== "auto")
17601
+ return stack === "always";
17602
+ return room > 0 && natural > room + 1;
17603
+ }
17604
+ function noteBarReserve(bar, inset, gap) {
17605
+ return Math.max(0, Math.ceil(bar)) + inset + gap;
17606
+ }
17500
17607
  function parseAt(at) {
17501
17608
  if (!at)
17502
17609
  return null;
@@ -17781,6 +17888,9 @@ function feedbackMarkdown(fb, verdicts) {
17781
17888
  `;
17782
17889
  }
17783
17890
  var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
17891
+ function plainMarkdown(text) {
17892
+ return text.replace(/\[([^\]\n]+)\]\([^)\s]+\)/g, "$1").replace(/`([^`\n]+)`/g, "$1").replace(/\*\*([^*\n]+)\*\*/g, "$1").replace(/(^|[^*\w])\*([^*\n]+)\*(?!\w)/g, "$1$2").replace(/\s+/g, " ").trim();
17893
+ }
17784
17894
  function inlineMarkdown(text) {
17785
17895
  const codes = [];
17786
17896
  let s = text.replace(/`([^`\n]+)`/g, (_, c) => {
@@ -17801,6 +17911,10 @@ function inlineMarkdown(text) {
17801
17911
  }
17802
17912
 
17803
17913
  // src/ui/walkthrough-blocks.ts
17914
+ var GLYPH = {
17915
+ go: icon("caretRight", { small: true }),
17916
+ caret: icon("caretRight", { small: true })
17917
+ };
17804
17918
  var el = (doc, tag, cls) => {
17805
17919
  const n = doc.createElement(tag);
17806
17920
  if (cls)
@@ -17830,9 +17944,10 @@ function stepButton(doc, ref, steps) {
17830
17944
  const s = ref && steps?.find(ref);
17831
17945
  if (!s || !steps)
17832
17946
  return null;
17833
- const b = text(doc, "button", "knobs-wt-steplink", s.number);
17947
+ const b = el(doc, "button", "knobs-wt-steplink");
17948
+ b.innerHTML = GLYPH.go;
17834
17949
  b.type = "button";
17835
- b.title = `Go to ${s.number} ${s.title}`;
17950
+ b.title = `Go to “${s.title}”`;
17836
17951
  b.setAttribute("aria-label", b.title);
17837
17952
  b.addEventListener("click", (e) => {
17838
17953
  e.stopPropagation();
@@ -17957,7 +18072,7 @@ function mountStack(host, m, steps) {
17957
18072
  if (target && steps) {
17958
18073
  const title = text(doc, "button", "knobs-wt-stitle", it.title);
17959
18074
  title.type = "button";
17960
- title.title = `Go to ${target.number} ${target.title}`;
18075
+ title.title = `Go to “${target.title}”`;
17961
18076
  title.addEventListener("click", (e) => {
17962
18077
  e.stopPropagation();
17963
18078
  steps.go(target.key);
@@ -18012,175 +18127,861 @@ function delta(doc, d) {
18012
18127
  s.setAttribute("aria-label", `${d.add} added, ${d.del} removed`);
18013
18128
  return s;
18014
18129
  }
18015
- function mountTable(host, m, steps) {
18016
- const doc = host.ownerDocument;
18017
- const win = doc.defaultView;
18018
- const scroller = el(doc, "div", "knobs-wt-table");
18019
- const table = el(doc, "table");
18020
- if (m.caption)
18021
- table.append(text(doc, "caption", "", m.caption));
18022
- const head = el(doc, "thead");
18023
- const hr = el(doc, "tr");
18024
- for (const c of m.columns) {
18025
- const th = text(doc, "th", "", c.label);
18026
- th.scope = "col";
18027
- th.dataset.align = c.align;
18028
- hr.append(th);
18029
- }
18030
- head.append(hr);
18031
- const body = el(doc, "tbody");
18032
- for (const r of m.rows) {
18033
- const tr = el(doc, "tr");
18034
- for (const c of m.columns) {
18035
- const td = el(doc, "td");
18036
- td.dataset.align = c.align;
18037
- if (c.mono)
18038
- td.dataset.mono = "";
18039
- const cell = r[c.key];
18040
- if (cell) {
18041
- if (cell.text.length > 40 && !c.mono && !cell.code)
18042
- td.dataset.long = "";
18043
- let content;
18044
- if (cell.badge) {
18045
- content = link(doc, "knobs-wt-chip", cell.text, cell.href);
18046
- content.dataset.tone = cell.badge;
18047
- } else {
18048
- const inner = cell.code ? text(doc, "code", "", cell.text) : null;
18049
- if (cell.href) {
18050
- content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
18051
- if (inner)
18052
- content.append(inner);
18053
- } else
18054
- content = inner ?? text(doc, "span", "", cell.text);
18055
- if (cell.tone) {
18056
- content.classList.add("knobs-wt-ctone");
18057
- content.dataset.tone = cell.tone;
18058
- }
18059
- }
18060
- td.append(content);
18061
- const go = stepButton(doc, cell.step, steps);
18062
- if (go)
18063
- td.append(go);
18064
- }
18065
- tr.append(td);
18066
- }
18067
- body.append(tr);
18068
- }
18069
- table.append(head, body);
18070
- scroller.append(table);
18071
- host.append(scroller);
18072
- const measure = () => {
18073
- scroller.style.setProperty("--kw-thead", `${head.offsetHeight}px`);
18074
- const scrolls = scroller.scrollWidth > scroller.clientWidth + 1 || scroller.scrollHeight > scroller.clientHeight + 1;
18075
- if (scrolls) {
18076
- scroller.tabIndex = 0;
18077
- scroller.setAttribute("role", "region");
18078
- scroller.setAttribute("aria-label", m.caption ?? "Table");
18079
- } else {
18080
- scroller.removeAttribute("tabindex");
18081
- scroller.removeAttribute("role");
18082
- scroller.removeAttribute("aria-label");
18083
- }
18084
- };
18085
- const ro = win && "ResizeObserver" in win ? new win.ResizeObserver(measure) : null;
18086
- ro?.observe(scroller);
18087
- measure();
18088
- return {
18089
- dispose: () => {
18090
- ro?.disconnect();
18091
- scroller.remove();
18092
- },
18093
- context: () => ({ kind: "table" })
18094
- };
18130
+ function cellContent(doc, into, cell, steps) {
18131
+ let content;
18132
+ if (cell.badge) {
18133
+ content = link(doc, "knobs-wt-chip", cell.text, cell.href);
18134
+ content.dataset.tone = cell.badge;
18135
+ } else {
18136
+ const inner = cell.code ? text(doc, "code", "", cell.text) : null;
18137
+ if (cell.href) {
18138
+ content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
18139
+ if (inner)
18140
+ content.append(inner);
18141
+ } else
18142
+ content = inner ?? text(doc, "span", "", cell.text);
18143
+ if (cell.tone) {
18144
+ content.classList.add("knobs-wt-ctone");
18145
+ content.dataset.tone = cell.tone;
18146
+ }
18147
+ }
18148
+ into.append(content);
18149
+ const go = stepButton(doc, cell.step, steps);
18150
+ if (go)
18151
+ into.append(go);
18152
+ }
18153
+ var cellAttrs = (td, c) => {
18154
+ td.dataset.align = c.align;
18155
+ if (c.mono)
18156
+ td.dataset.mono = "";
18157
+ };
18158
+ function mountTable(host, m, steps) {
18159
+ const doc = host.ownerDocument;
18160
+ 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");
18164
+ if (m.caption)
18165
+ table.append(text(doc, "caption", "", m.caption));
18166
+ const head = el(doc, "thead");
18167
+ const hr = el(doc, "tr");
18168
+ for (const c of m.columns) {
18169
+ const th = text(doc, "th", "", c.label);
18170
+ th.scope = "col";
18171
+ th.dataset.align = c.align;
18172
+ hr.append(th);
18173
+ }
18174
+ head.append(hr);
18175
+ const body = el(doc, "tbody");
18176
+ for (const r of m.rows) {
18177
+ const tr = el(doc, "tr");
18178
+ for (const c of m.columns) {
18179
+ const td = el(doc, "td");
18180
+ cellAttrs(td, c);
18181
+ const cell = r[c.key];
18182
+ if (cell) {
18183
+ if (cell.text.length > 40 && !c.mono && !cell.code)
18184
+ td.dataset.long = "";
18185
+ cellContent(doc, td, cell, steps);
18186
+ }
18187
+ tr.append(td);
18188
+ }
18189
+ body.append(tr);
18190
+ }
18191
+ table.append(head, body);
18192
+ scroller.append(table);
18193
+ const stack = el(doc, "div", "knobs-wt-tstack");
18194
+ stack.setAttribute("role", "list");
18195
+ if (m.caption)
18196
+ stack.setAttribute("aria-label", m.caption);
18197
+ const primary = m.columns.find((c) => c.key === m.primary) ?? m.columns[0];
18198
+ for (const r of m.rows) {
18199
+ const item = el(doc, "div", "knobs-wt-trow");
18200
+ item.setAttribute("role", "listitem");
18201
+ const title = el(doc, "div", "knobs-wt-ttitle");
18202
+ if (primary) {
18203
+ cellAttrs(title, primary);
18204
+ const cell = r[primary.key];
18205
+ if (cell)
18206
+ cellContent(doc, title, cell, steps);
18207
+ }
18208
+ const line = el(doc, "div", "knobs-wt-tline");
18209
+ line.append(title);
18210
+ const badges = el(doc, "div", "knobs-wt-tbadges");
18211
+ for (const c of m.columns) {
18212
+ const cell = r[c.key];
18213
+ if (c !== primary && cell?.badge)
18214
+ cellContent(doc, badges, cell, steps);
18215
+ }
18216
+ if (badges.childNodes.length)
18217
+ line.append(badges);
18218
+ item.append(line);
18219
+ const terms = el(doc, "dl", "knobs-wt-tterms");
18220
+ for (const c of m.columns) {
18221
+ if (c === primary)
18222
+ continue;
18223
+ const cell = r[c.key];
18224
+ if (!cell || cell.text === "" || cell.badge)
18225
+ continue;
18226
+ const dd = el(doc, "dd");
18227
+ cellAttrs(dd, c);
18228
+ dd.removeAttribute("data-align");
18229
+ cellContent(doc, dd, cell, steps);
18230
+ terms.append(text(doc, "dt", "", c.label), dd);
18231
+ }
18232
+ if (terms.childNodes.length)
18233
+ item.append(terms);
18234
+ stack.append(item);
18235
+ }
18236
+ if (m.caption)
18237
+ stack.append(text(doc, "p", "knobs-wt-tcaption", m.caption));
18238
+ box.append(scroller, stack);
18239
+ host.append(box);
18240
+ let natural = 0;
18241
+ const measureNatural = () => {
18242
+ if (!box.isConnected)
18243
+ return 0;
18244
+ const was = box.dataset.stacked;
18245
+ delete box.dataset.stacked;
18246
+ box.dataset.measure = "";
18247
+ const w = table.getBoundingClientRect().width;
18248
+ delete box.dataset.measure;
18249
+ if (was !== undefined)
18250
+ box.dataset.stacked = was;
18251
+ return w;
18252
+ };
18253
+ const measure = () => {
18254
+ if (!natural)
18255
+ natural = measureNatural();
18256
+ const stacked = tableStacks(m.stack, box.clientWidth, natural);
18257
+ box.toggleAttribute("data-stacked", stacked);
18258
+ scroller.style.setProperty("--kw-thead", `${head.offsetHeight}px`);
18259
+ const scrolls = !stacked && (scroller.scrollWidth > scroller.clientWidth + 1 || scroller.scrollHeight > scroller.clientHeight + 1);
18260
+ if (scrolls) {
18261
+ scroller.tabIndex = 0;
18262
+ scroller.setAttribute("role", "region");
18263
+ scroller.setAttribute("aria-label", m.caption ?? "Table");
18264
+ } else {
18265
+ scroller.removeAttribute("tabindex");
18266
+ scroller.removeAttribute("role");
18267
+ scroller.removeAttribute("aria-label");
18268
+ }
18269
+ };
18270
+ if (m.stack === "always")
18271
+ box.dataset.stacked = "";
18272
+ const ro = win && "ResizeObserver" in win ? new win.ResizeObserver(measure) : null;
18273
+ ro?.observe(box);
18274
+ measure();
18275
+ doc.fonts?.ready.then(() => {
18276
+ if (!box.isConnected)
18277
+ return;
18278
+ natural = 0;
18279
+ measure();
18280
+ });
18281
+ return {
18282
+ dispose: () => {
18283
+ ro?.disconnect();
18284
+ box.remove();
18285
+ },
18286
+ context: () => ({ kind: "table" })
18287
+ };
18288
+ }
18289
+ function chapterPills(host, chapters, io) {
18290
+ const doc = host.ownerDocument;
18291
+ const list = el(doc, "div", "knobs-wt-chapters");
18292
+ list.setAttribute("role", "group");
18293
+ list.setAttribute("aria-label", "Chapters");
18294
+ let at = -2;
18295
+ const rows = chapters.map((c, i) => {
18296
+ const b = el(doc, "button", "knobs-wt-chapter");
18297
+ 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)));
18301
+ b.addEventListener("click", () => {
18302
+ io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
18303
+ io.seek(c.start);
18304
+ select(i);
18305
+ });
18306
+ list.append(b);
18307
+ return b;
18308
+ });
18309
+ host.append(list);
18310
+ const pill = selectionPill(list, {
18311
+ selected: () => rows[at] ?? null,
18312
+ rows: () => rows
18313
+ });
18314
+ const select = (i) => {
18315
+ if (i === at)
18316
+ return;
18317
+ at = i;
18318
+ rows.forEach((b, k) => k === at ? b.setAttribute("aria-current", "true") : b.removeAttribute("aria-current"));
18319
+ pill.update();
18320
+ };
18321
+ const sync = () => select(chapterAt(chapters, io.now()));
18322
+ sync();
18323
+ return {
18324
+ sync,
18325
+ get current() {
18326
+ return chapters[at] ?? null;
18327
+ },
18328
+ dispose() {
18329
+ pill.dispose();
18330
+ list.remove();
18331
+ }
18332
+ };
18333
+ }
18334
+ function disclosure(host, options) {
18335
+ const doc = host.ownerDocument;
18336
+ const win = doc.defaultView;
18337
+ let open = options.open === true;
18338
+ const root = el(doc, "div", "knobs-wt-disc");
18339
+ const toggle = el(doc, "button", "knobs-wt-disc-toggle");
18340
+ toggle.type = "button";
18341
+ 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");
18347
+ content.append(options.content);
18348
+ viewport.append(content);
18349
+ root.append(toggle, viewport);
18350
+ host.append(root);
18351
+ const STATE = springMotion(SPRINGS.default);
18352
+ const h = new Spring(0, STATE.omega, STATE.zeta, 0.25);
18353
+ let full = 0;
18354
+ const paint = (moving) => {
18355
+ const k = full > 0 ? Math.max(0, Math.min(1, h.x / full)) : open ? 1 : 0;
18356
+ viewport.style.height = !moving && open ? "" : `${Math.max(0, h.x).toFixed(2)}px`;
18357
+ 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);
18359
+ };
18360
+ const loop = frameLoop(win, (dt) => {
18361
+ h.step(dt);
18362
+ const moving = !h.resting;
18363
+ paint(moving);
18364
+ return moving;
18365
+ });
18366
+ const sync = () => {
18367
+ root.toggleAttribute("data-open", open);
18368
+ toggle.setAttribute("aria-expanded", String(open));
18369
+ viewport.inert = !open;
18370
+ };
18371
+ sync();
18372
+ h.snap(0);
18373
+ paint(false);
18374
+ if (open) {
18375
+ viewport.style.height = "";
18376
+ content.style.opacity = "1";
18377
+ }
18378
+ const api = {
18379
+ element: root,
18380
+ get open() {
18381
+ return open;
18382
+ },
18383
+ toggle(next = !open) {
18384
+ if (next === open)
18385
+ return;
18386
+ open = next;
18387
+ sync();
18388
+ full = content.offsetHeight;
18389
+ if (reducedMotion(win)) {
18390
+ loop.stop();
18391
+ h.snap(open ? full : 0);
18392
+ viewport.style.height = open ? "" : "0px";
18393
+ content.style.opacity = open ? "1" : "0";
18394
+ return;
18395
+ }
18396
+ if (h.resting)
18397
+ h.snap(open ? 0 : full);
18398
+ h.set(open ? full : 0);
18399
+ paint(true);
18400
+ loop.wake();
18401
+ },
18402
+ dispose() {
18403
+ loop.stop();
18404
+ root.remove();
18405
+ }
18406
+ };
18407
+ toggle.addEventListener("click", () => api.toggle());
18408
+ return api;
18409
+ }
18410
+ var PR_PATHS = {
18411
+ pull: "M104,64A32,32,0,1,0,64,95v66a32,32,0,1,0,16,0V95A32.06,32.06,0,0,0,104,64ZM56,64A16,16,0,1,1,72,80,16,16,0,0,1,56,64ZM88,192a16,16,0,1,1-16-16A16,16,0,0,1,88,192Zm120-31V110.63a23.85,23.85,0,0,0-7-17L163.31,56H192a8,8,0,0,0,0-16H144a8,8,0,0,0-8,8V96a8,8,0,0,0,16,0V67.31L189.66,105a8,8,0,0,1,2.34,5.66V161a32,32,0,1,0,16,0Zm-8,47a16,16,0,1,1,16-16A16,16,0,0,1,200,208Z",
18412
+ merge: "M208,112a32.05,32.05,0,0,0-30.69,23l-42.21-6a8,8,0,0,1-4.95-2.71L94.43,84.55A32,32,0,1,0,72,87v82a32,32,0,1,0,16,0V101.63l30,35a24,24,0,0,0,14.83,8.14l44,6.28A32,32,0,1,0,208,112ZM64,56A16,16,0,1,1,80,72,16,16,0,0,1,64,56ZM96,200a16,16,0,1,1-16-16A16,16,0,0,1,96,200Zm112-40a16,16,0,1,1,16-16A16,16,0,0,1,208,160Z"
18413
+ };
18414
+ 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
+ var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
18416
+ function prChip(doc, n, pr) {
18417
+ const chip = el(doc, pr.href ? "a" : "span", "knobs-wt-pr");
18418
+ if (pr.href) {
18419
+ chip.setAttribute("href", pr.href);
18420
+ chip.setAttribute("target", "_blank");
18421
+ chip.setAttribute("rel", "noreferrer");
18422
+ }
18423
+ if (pr.state)
18424
+ chip.dataset.state = pr.state;
18425
+ chip.innerHTML = prGlyph(pr.state === "merged");
18426
+ chip.append(text(doc, "span", "", `#${n}`));
18427
+ const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
18428
+ chip.title = tip;
18429
+ chip.setAttribute("aria-label", `Pull request ${tip}`);
18430
+ return chip;
18431
+ }
18432
+ var PR_URL = /^https?:\/\/[^/]+\/.+\/(?:pull|merge_requests)\/(\d+)\/?(?:[?#].*)?$/;
18433
+ var PR_REF = /(^|[^\w&/#])#(\d{3,7})\b/g;
18434
+ function linkPRs(root, find) {
18435
+ const doc = root.ownerDocument;
18436
+ for (const a of Array.from(root.querySelectorAll("a[href]"))) {
18437
+ if (a.classList.contains("knobs-wt-pr") || a.closest(".knobs-wt-slot,button"))
18438
+ continue;
18439
+ const n = PR_URL.exec(a.getAttribute("href") ?? "")?.[1];
18440
+ const words = a.textContent?.trim() ?? "";
18441
+ if (!n || !(words === `#${n}` || words === n || words === a.getAttribute("href")))
18442
+ continue;
18443
+ a.replaceWith(prChip(doc, n, { href: a.href, ...find(n) }));
18444
+ }
18445
+ const walker = doc.createTreeWalker(root, 5);
18446
+ const hits = [];
18447
+ for (let t = walker.nextNode();t; t = walker.nextNode()) {
18448
+ if (t.nodeType !== 3)
18449
+ continue;
18450
+ const p = t.parentElement;
18451
+ if (!p || p.closest("a,code,pre,button,.knobs-wt-slot,.knobs-wt-pr"))
18452
+ continue;
18453
+ PR_REF.lastIndex = 0;
18454
+ if (PR_REF.test(t.nodeValue ?? ""))
18455
+ hits.push(t);
18456
+ }
18457
+ for (const t of hits) {
18458
+ const value = t.nodeValue ?? "";
18459
+ const frag = doc.createDocumentFragment();
18460
+ let last = 0;
18461
+ let changed = false;
18462
+ PR_REF.lastIndex = 0;
18463
+ for (let m = PR_REF.exec(value);m; m = PR_REF.exec(value)) {
18464
+ const pr = find(m[2]);
18465
+ if (!pr)
18466
+ continue;
18467
+ const at = m.index + m[1].length;
18468
+ frag.append(value.slice(last, at), prChip(doc, m[2], pr));
18469
+ last = at + m[2].length + 1;
18470
+ changed = true;
18471
+ }
18472
+ if (!changed)
18473
+ continue;
18474
+ frag.append(value.slice(last));
18475
+ t.replaceWith(frag);
18476
+ }
18477
+ }
18478
+
18479
+ // src/ui/walkthrough-media.ts
18480
+ init_compare_model();
18481
+ init_glide();
18482
+ init_icons();
18483
+ init_look();
18484
+
18485
+ // src/ui/walkthrough-css.ts
18486
+ init_motion();
18487
+ var snappy3 = springCss(SPRINGS.snappy);
18488
+ var calm = springCss(SPRINGS.calm);
18489
+ var state = springCss(SPRINGS.default);
18490
+ var quick = springCss(FADE);
18491
+ 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
+ var walkthroughCss = `
18493
+ :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
18494
+ .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));
18496
+ --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
+ 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
+ letter-spacing:normal;-webkit-font-smoothing:antialiased}
18499
+ .knobs-walkthrough [hidden]{display:none!important}
18500
+ /* (0,0,1): a host's element rules stop here, and every class rule below wins over it. */
18501
+ :where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:inherit}
18502
+ .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}
18503
+ .knobs-walkthrough a:hover{text-decoration-color:currentColor}
18504
+ .knobs-walkthrough code,.knobs-wt-slot code{font:var(--_k-type-label) var(--_k-mono);padding:1px 5px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-fill)}
18505
+ :where(.knobs-ui) :is(.knobs-wt-btn,.knobs-wt-pick,.knobs-wt-toc-row,button.knobs-wt-stitle,.knobs-wt-chapter,.knobs-wt-disc-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
18506
+ background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
18507
+
18508
+ /* ——— layout: the contents rail sticky at the left edge (right if asked), and the column centred
18509
+ in the room beside it, up to --kw-width: media take the column, prose stops at the measure.
18510
+ 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}
18512
+ .knobs-walkthrough[data-rail="right"] .knobs-wt-layout{grid-template-columns:minmax(0,1fr) var(--kw-rail-w)}
18513
+ .knobs-walkthrough[data-rail="right"] .knobs-wt-main{grid-column:1;grid-row:1}
18514
+ .knobs-walkthrough[data-rail="right"] .knobs-wt-rail{grid-column:2;grid-row:1}
18515
+ .knobs-walkthrough[data-rail="none"] .knobs-wt-layout{grid-template-columns:minmax(0,1fr)}
18516
+ .knobs-wt-main{justify-self:center;width:100%;max-width:var(--kw-width);display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0;padding-bottom:12px}
18517
+ /* The intro, the updates and each section: a section's room apart, the hairline in the middle. */
18518
+ .knobs-wt-flow{display:flex;flex-direction:column;gap:calc(var(--_k-space-section) / 2);min-width:0}
18519
+ .knobs-wt-flow>*+*{padding-top:calc(var(--_k-space-section) / 2);box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
18520
+ @container knobs-wt (max-width:960px){.knobs-walkthrough[data-rail] .knobs-wt-layout{grid-template-columns:minmax(0,1fr);gap:var(--_k-space-title)}
18521
+ .knobs-walkthrough[data-rail] .knobs-wt-layout>*{grid-column:auto;grid-row:auto}}
18522
+
18523
+ /* ——— one line of facts: small, faint, joined by ·, its trailing edge fading ——— */
18524
+ .knobs-wt-metaline{display:flex;align-items:baseline;gap:var(--_k-space-inside);min-width:0;overflow:hidden;white-space:nowrap;font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3);${fade2}}
18525
+ .knobs-wt-metaline>*{flex:none}
18526
+ .knobs-wt-metaline>*+*::before{content:"·";margin-right:var(--_k-space-inside);color:var(--_k-fg-3)}
18527
+ .knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr){display:inline-flex;align-items:baseline;color:inherit;text-decoration:none;transition:color calc(var(--_k-hover-out) * 1ms)}
18528
+ .knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr):hover{color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18529
+ .knobs-wt-metaline svg{align-self:center;width:12px;height:12px;margin-left:2px}
18530
+ /* A pull request: a small chip, its state's icon and number in a quiet tint of the state (open
18531
+ the success hue, merged the violet series, draft and closed neutral), its title on hover. */
18532
+ .knobs-ui .knobs-wt-pr{--kw-pr:var(--_k-fg-2);display:inline-flex;align-items:center;gap:3px;height:18px;padding:0 5px 0 4px;margin:0 1px;border-radius:calc(var(--_k-radius) / 2);vertical-align:1px;
18533
+ background:color-mix(in oklch,var(--kw-pr) 12%,transparent);color:var(--kw-pr);font:var(--_k-weight-label) var(--_k-type-small)/1 var(--_k-font);font-variant-numeric:tabular-nums;white-space:nowrap;text-decoration:none;
18534
+ transition:background calc(var(--_k-hover-out) * 1ms)}
18535
+ .knobs-ui .knobs-wt-pr[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
18536
+ .knobs-ui .knobs-wt-pr[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
18537
+ .knobs-ui .knobs-wt-pr:is([data-state="draft"],[data-state="closed"]){--kw-pr:var(--_k-fg-3)}
18538
+ .knobs-ui a.knobs-wt-pr:is(:hover,:focus-visible){background:color-mix(in oklch,var(--kw-pr) 22%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18539
+ .knobs-wt-pr svg{flex:none}
18540
+ .knobs-wt-metaline .knobs-wt-pr{align-self:center;margin:0}
18541
+ .knobs-wt-kicker-lead{color:var(--_k-fg);font-weight:var(--kw-strong)}
18542
+ .knobs-wt-metaline [data-status="on"]{color:var(--_k-fg)}
18543
+ .knobs-wt-metaline [data-status="warn"]{color:var(--_k-warn)}
18544
+
18545
+ /* ——— the page's head ——— */
18546
+ .knobs-wt-head{display:flex;flex-direction:column;gap:var(--_k-space-title)}
18547
+ .knobs-wt-title{font-size:var(--_k-type-heading)!important;line-height:var(--_k-leading-heading);font-weight:var(--kw-strong)!important;text-wrap:balance}
18548
+ .knobs-wt-summary{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
18549
+ .knobs-wt-meta{display:flex;flex-wrap:wrap;gap:var(--_k-space-inside) var(--_k-space-title);color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
18550
+ .knobs-wt-meta>span{display:inline-flex;gap:var(--_k-space-inside);white-space:nowrap}
18551
+ .knobs-wt-meta b{font-weight:var(--_k-weight);color:var(--_k-fg-2);font-family:var(--_k-mono)}
18552
+
18553
+ /* ——— a section: a PR or topic. Its kicker leads the meta line, its title under it (16/24, under
18554
+ the page's 20/28), both on the column's edge ——— */
18555
+ .knobs-wt-section{display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0}
18556
+ .knobs-wt-sintro{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0}
18557
+ .knobs-wt-shead{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0;scroll-margin-top:16px}
18558
+ .knobs-wt-heading{font-size:var(--_k-type-title)!important;line-height:var(--_k-leading-title);font-weight:var(--kw-strong)!important;text-wrap:balance}
18559
+ .knobs-wt-ssum{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
18560
+ .knobs-wt-chip{display:inline-flex;align-items:center;gap:6px;height:20px;padding:0 8px;border-radius:var(--_k-round);font-size:var(--_k-type-small);font-weight:var(--kw-strong);white-space:nowrap;
18561
+ --kw-tone:var(--_k-fg-2);color:var(--kw-tone);background:color-mix(in srgb,var(--kw-tone) 14%,transparent)}
18562
+ .knobs-wt-chip::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
18563
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="good"]{--kw-tone:var(--kw-good)}
18564
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="warn"]{--kw-tone:var(--_k-warn)}
18565
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="bad"]{--kw-tone:var(--_k-danger)}
18566
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="info"]{--kw-tone:var(--kw-info)}
18567
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="dim"]{--kw-tone:var(--_k-fg-2)}
18568
+
18569
+ /* ——— a step: on the page by default, a group's room between steps with a hairline in the middle;
18570
+ or (cards) one card each, the one surface its pieces sit on ——— */
18571
+ .knobs-walkthrough:not([data-cards]) .knobs-wt-step:not([data-full]){padding:0;border-radius:0;background:transparent;box-shadow:none}
18572
+ .knobs-walkthrough:not([data-cards]) .knobs-wt-step+.knobs-wt-step::before{content:"";position:absolute;left:0;right:0;top:calc(var(--_k-space-group) / -2);height:1px;background:var(--_k-border-subtle);pointer-events:none}
18573
+ .knobs-wt-step{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-title);padding:var(--_k-pad-card);border-radius:var(--_k-radius-panel);background:var(--kw-card);box-shadow:var(--kw-card-ring);
18574
+ scroll-margin-top:16px;outline:none;container:knobs-wt-step / inline-size}
18575
+ .knobs-walkthrough[data-cards]{--kw-under:var(--kw-card)}
18576
+ .knobs-walkthrough[data-cards][data-frame="flat"] .knobs-wt-step{box-shadow:inset 0 0 0 1px var(--_k-border)}
18577
+ .knobs-wt-step-head{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0}
18578
+ .knobs-wt-stepTitle{min-width:0;font-weight:var(--kw-strong)!important;text-wrap:balance}
18579
+ .knobs-wt-caption{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
18580
+ .knobs-wt-question{font-weight:var(--kw-strong)!important;max-width:var(--kw-measure)}
18581
+ .knobs-wt-media{display:grid;grid-template-columns:repeat(var(--kw-n,1),minmax(0,1fr));gap:var(--_k-space-group) var(--_k-space-column);min-width:0}
18582
+ @container knobs-wt-step (max-width:640px){.knobs-wt-media{grid-template-columns:minmax(0,1fr)}}
18583
+
18584
+ /* ——— the feed: updates and answers, newest first. Tight inside an item (4 / 8 / 4), a group's
18585
+ room between items with the hairline in the middle; no boxes ——— */
18586
+ .knobs-wt-feed{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0;scroll-margin-top:16px}
18587
+ .knobs-wt-feed-head{display:flex;align-items:baseline;gap:var(--_k-space-sibling)}
18588
+ .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
+ .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
+ .knobs-wt-fi{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
18592
+ .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)}
18593
+ .knobs-wt-fi-q,.knobs-wt-fi-a{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0}
18594
+ .knobs-wt-fi-ask{max-width:var(--kw-measure);color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:pretty}
18595
+ .knobs-wt-fi-text{max-width:var(--kw-measure);color:var(--_k-fg);text-wrap:pretty}
18596
+ .knobs-wt-fi-answer{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
18597
+ :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
+ transition:color calc(var(--_k-hover-out) * 1ms)}
18599
+ .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
+
18605
+ /* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
18606
+ script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
18607
+ .knobs-wt-disc{display:flex;flex-direction:column;min-width:0}
18608
+ .knobs-ui .knobs-wt-disc-toggle{display:inline-flex;align-items:center;gap:var(--_k-space-inside);width:max-content;height:20px;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);cursor:pointer;
18609
+ border-radius:calc(var(--_k-radius) / 2);transition:color calc(var(--_k-hover-out) * 1ms)}
18610
+ .knobs-ui .knobs-wt-disc-toggle:is(:hover,:focus-visible),.knobs-wt-disc[data-open]>.knobs-wt-disc-toggle{color:var(--_k-fg-2);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18611
+ .knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
18612
+ .knobs-wt-disc-toggle svg{flex:none;width:12px;height:12px;transition:rotate var(--kw-state-ms) var(--kw-state)}
18613
+ .knobs-wt-disc[data-open]>.knobs-wt-disc-toggle svg{rotate:90deg}
18614
+ .knobs-wt-disc-count{color:var(--_k-fg-3)}
18615
+ .knobs-wt-disc-body{overflow:hidden}
18616
+ .knobs-wt-disc[data-open]>.knobs-wt-disc-body:not([data-moving]){overflow:visible}
18617
+ .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)}
18618
+ .knobs-wt-disc-content{padding-top:var(--_k-space-inside)}
18619
+ /* "More" under a caption or summary: the rest of its words, in the same voice. */
18620
+ .knobs-wt-more-text{display:flex;flex-direction:column;gap:var(--_k-space-sibling);max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
18621
+ /* A trail sits on the toggle's label edge: faint lines, the time trailing. */
18622
+ .knobs-wt-trail{display:flex;flex-direction:column;list-style:none;padding:0 0 0 calc(12px + var(--_k-space-inside));font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3);min-width:0}
18623
+ .knobs-wt-trail li{display:flex;align-items:baseline;gap:var(--_k-space-sibling);min-width:0;white-space:nowrap}
18624
+ .knobs-wt-trail-text{flex:0 1 auto;min-width:0;overflow:hidden;${fade2}}
18625
+ .knobs-wt-trail-at{flex:none}
18626
+
18627
+ /* ——— a step's sent notes ——— */
18628
+ .knobs-wt-notes{display:flex;flex-direction:column}
18629
+ .knobs-wt-notes:empty{display:none}
18630
+ .knobs-wt-n{display:flex;align-items:flex-start;gap:var(--_k-space-sibling);padding:6px 0;overflow:hidden;
18631
+ transition:height var(--kw-snappy-ms) var(--kw-snappy),opacity var(--kw-snappy-ms) var(--kw-snappy),filter var(--kw-snappy-ms) var(--kw-snappy)}
18632
+ .knobs-wt-n[data-entering]{opacity:0;filter:blur(3px)}
18633
+ .knobs-wt-n[data-leaving]{opacity:0;filter:blur(3px);padding-block:0}
18634
+ .knobs-wt-n+.knobs-wt-n{box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
18635
+ .knobs-wt-n-meta{flex:none;min-width:44px;color:var(--_k-fg-3);font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-body) var(--_k-mono)}
18636
+ .knobs-wt-n-meta[data-seek]{cursor:pointer}
18637
+ .knobs-wt-n-meta[data-seek]:hover{color:var(--_k-fg)}
18638
+ .knobs-wt-n-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
18639
+ .knobs-wt-n-voice{height:30px;max-width:320px}
18640
+ .knobs-wt-n-ink{display:block;width:120px;max-height:80px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-fill)}
18641
+ .knobs-wt-n-ink path{fill:none;stroke:var(--kw-ink);stroke-width:2px;vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round}
18642
+
18643
+ /* ——— the note bar, docked at the bottom of the column. It holds its own place at the page's end
18644
+ (so the last step stays clear of it), and script keeps its height as the scroller's
18645
+ scroll-padding-bottom ——— */
18646
+ .knobs-wt-dock{position:sticky;bottom:12px;z-index:40;display:grid;pointer-events:none}
18647
+ .knobs-wt-dock>*{pointer-events:auto}
18648
+ .knobs-wt-dock>:is(.knobs-wt-notebar,.knobs-wt-launch){grid-area:1/1}
18649
+ .knobs-wt-dock[data-mode="docked"]>.knobs-wt-launch{display:none}
18650
+ /* Collapsed: the launcher and the bar swap in place, a quick fade with a little scale and blur
18651
+ from the launcher's corner; the exit is the shorter. */
18652
+ :where(.knobs-ui) .knobs-wt-launch{all:revert;box-sizing:border-box;justify-self:end;align-self:end;display:inline-flex;align-items:center;gap:6px;height:32px;margin:0;padding:0 10px 0 9px;border:0;border-radius:var(--_k-round);
18653
+ background:var(--_k-bg);color:var(--_k-fg-2);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font:var(--kw-strong) var(--_k-type-small)/1 var(--_k-font);cursor:pointer;outline:none;transform-origin:100% 100%;
18654
+ transition:color calc(var(--_k-hover-out) * 1ms),opacity var(--kw-quick-ms) var(--kw-quick),scale var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
18655
+ .knobs-ui .knobs-wt-launch:is(:hover,:focus-visible){color:var(--_k-fg)}
18656
+ .knobs-ui .knobs-wt-launch:focus-visible{box-shadow:var(--_k-focus-ring)}
18657
+ .knobs-wt-launch svg{width:16px;height:16px}
18658
+ .knobs-wt-launch kbd{font:inherit;font-weight:var(--_k-weight);color:var(--_k-fg-3)}
18659
+ .knobs-wt-dock[data-mode="collapsed"] .knobs-wt-notebar{transform-origin:100% 100%;transition:opacity var(--kw-quick-ms) var(--kw-quick),scale var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
18660
+ .knobs-wt-dock[data-mode="collapsed"]:not([data-open])>.knobs-wt-notebar,.knobs-wt-dock[data-mode="collapsed"][data-open]>.knobs-wt-launch{opacity:0;scale:.98;filter:blur(3px);pointer-events:none;
18661
+ transition-duration:calc(var(--kw-quick-ms) * .8)}
18662
+ .knobs-wt-notebar{display:flex;flex-direction:column;gap:2px;padding:6px 6px 6px 14px;border-radius:calc(var(--_k-radius-panel) + 2px);background:var(--_k-bg);
18663
+ box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay)}
18664
+ .knobs-wt-on{padding-top:3px;color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);white-space:nowrap;overflow:hidden;${fade2}}
18665
+ .knobs-wt-row{display:flex;align-items:flex-end;gap:4px}
18666
+ :where(.knobs-ui) :is(.knobs-wt-input,.knobs-wt-tool,.knobs-wt-x,.knobs-wt-back){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;
18667
+ border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
18668
+ .knobs-ui .knobs-wt-input{flex:1;min-width:0;height:32px;max-height:140px;padding:6px 0;resize:none;color:var(--_k-fg);caret-color:var(--_k-accent);line-height:var(--_k-leading-body);font-size:var(--_k-type-body)}
18669
+ .knobs-ui .knobs-wt-input::placeholder{color:var(--_k-fg-3)}
18670
+ .knobs-wt-attach{display:flex;gap:4px;align-items:center;align-self:center}
18671
+ .knobs-wt-attach:empty{display:none}
18672
+ .knobs-wt-chip-a{display:inline-flex;align-items:center;gap:2px;height:24px;padding:0 2px 0 9px;border-radius:var(--_k-round);background:var(--_k-fill-selected);font-size:var(--_k-type-small);white-space:nowrap}
18673
+ .knobs-ui .knobs-wt-x{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;color:var(--_k-fg-3);cursor:pointer;transition:background .15s,color .15s}
18674
+ .knobs-ui .knobs-wt-x:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
18675
+ .knobs-wt-x svg{width:12px;height:12px}
18676
+ .knobs-ui .knobs-wt-tool{flex:none;display:inline-flex;align-items:center;justify-content:center;gap:5px;min-width:32px;height:32px;padding:0 8px;border-radius:calc(var(--_k-radius) + 2px);
18677
+ color:var(--_k-fg-2);cursor:pointer;transition:background .15s,color .15s}
18678
+ .knobs-ui .knobs-wt-tool:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
18679
+ .knobs-ui .knobs-wt-tool[aria-pressed="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
18680
+ .knobs-wt-tool svg{width:16px;height:16px}
18681
+ .knobs-wt-rec{font-size:var(--_k-type-small)}
18682
+ .knobs-wt-rec:empty{display:none}
18683
+ .knobs-ui .knobs-wt-tool[data-send]{color:var(--_k-fg-3)}
18684
+ .knobs-ui .knobs-wt-tool[data-send][data-ready]{background:var(--_k-accent);color:var(--_k-bg)}
18685
+ /* Back: in place over the bar for a few seconds after a note moves the page on. */
18686
+ .knobs-ui .knobs-wt-back{position:absolute;left:8px;bottom:calc(100% + 8px);display:inline-flex;align-items:center;gap:var(--_k-space-inside);max-width:min(360px,calc(100% - 16px));height:28px;padding:0 12px 0 9px;
18687
+ border-radius:var(--_k-round);background:var(--_k-bg);color:var(--_k-fg);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);font-size:var(--_k-type-small);font-weight:var(--kw-strong);cursor:pointer;
18688
+ opacity:0;scale:.96;filter:blur(3px);pointer-events:none;transition:opacity .11s cubic-bezier(.4,0,1,1),scale .11s cubic-bezier(.4,0,1,1),filter .11s cubic-bezier(.4,0,1,1)}
18689
+ .knobs-ui .knobs-wt-back[data-on]{opacity:1;scale:1;filter:none;pointer-events:auto;transition:opacity .15s cubic-bezier(.2,.8,.2,1),scale .15s cubic-bezier(.2,.8,.2,1),filter .15s cubic-bezier(.2,.8,.2,1)}
18690
+ .knobs-ui .knobs-wt-back:hover{background:var(--_k-fill-selected)}
18691
+ .knobs-wt-back svg{flex:none;width:12px;height:12px}
18692
+ .knobs-wt-back-label{min-width:0;overflow:hidden;white-space:nowrap;${fade2}}
18693
+
18694
+ /* ——— ink over a step's media ——— */
18695
+ .knobs-wt-media,.knobs-wt-options{position:relative}
18696
+ .knobs-wt-ink{position:absolute;inset:0;z-index:5;width:100%;height:100%;overflow:visible;pointer-events:none;touch-action:none}
18697
+ .knobs-wt-ink[data-on]{pointer-events:auto;cursor:crosshair}
18698
+ .knobs-wt-ink path{fill:none;stroke:var(--kw-ink);stroke-width:3px;vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round}
18699
+
18700
+ /* ——— full-window: the step over the page above the note bar, its media filling it; its place held
18701
+ in the page. The one raised surface ——— */
18702
+ .knobs-wt-scrim{position:fixed;inset:0;z-index:30;background:color-mix(in srgb,var(--_k-bg-page) 82%,transparent);opacity:0;pointer-events:none}
18703
+ .knobs-wt-scrim[data-on]{pointer-events:auto}
18704
+ .knobs-wt-hold-card{flex:none}
18705
+ .knobs-wt-step[data-full]{position:fixed;z-index:35;margin:0;overflow:hidden;overscroll-behavior:contain;padding:var(--_k-pad-card);border-radius:var(--_k-radius-panel);box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay);background:var(--_k-bg)}
18706
+ .knobs-wt-step[data-full]::before{content:none}
18707
+ .knobs-wt-step[data-full]>:not(.knobs-wt-media){flex:none}
18708
+ .knobs-wt-step[data-full] .knobs-wt-notes{display:none}
18709
+ .knobs-wt-step[data-full]>.knobs-wt-media{flex:1;min-height:0;grid-auto-rows:minmax(0,1fr)}
18710
+ .knobs-wt-step[data-full] .knobs-wt-slot{display:flex;flex-direction:column;min-height:0}
18711
+ .knobs-wt-step[data-full] .knobs-wt-body{flex:1;min-height:0}
18712
+ .knobs-wt-step[data-full] .knobs-wt-fit{height:100%;transition:none}
18713
+ .knobs-wt-step[data-full] .knobs-wt-frame{height:100%;aspect-ratio:auto}
18714
+
18715
+ /* ——— a taste test: the options side by side, Kit's pick checked and the rest stepping back. On
18716
+ the step's card, so no fill of their own ——— */
18717
+ .knobs-wt-options{display:grid;grid-template-columns:repeat(var(--kw-n,2),minmax(0,1fr));gap:var(--_k-space-group) var(--_k-space-column)}
18718
+ @container knobs-wt-step (max-width:640px){.knobs-wt-options{grid-template-columns:minmax(0,1fr)}}
18719
+ .knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
18720
+ .knobs-wt-option>:not(.knobs-wt-pick){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
18721
+ .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-pick){opacity:.55}
18722
+ .knobs-ui .knobs-wt-pick{display:flex;align-items:center;gap:8px;width:100%;min-height:28px;margin:0 -6px;width:calc(100% + 12px);padding:0 6px;border-radius:var(--_k-radius);cursor:pointer;color:var(--_k-fg-2);text-align:left;
18723
+ transition:background calc(var(--_k-hover-out) * 1ms)}
18724
+ .knobs-ui .knobs-wt-pick:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_k-fill);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18725
+ .knobs-wt-letter{flex:none;font-weight:var(--kw-strong);color:var(--_k-fg)}
18726
+ .knobs-wt-olabel{flex:1;min-width:0;font-weight:var(--kw-strong);color:var(--_k-fg)}
18727
+ .knobs-wt-suggested{flex:none;font-size:var(--_k-type-small);color:var(--_k-fg-3)}
18728
+ .knobs-wt-check{flex:none;display:flex;width:18px;height:18px;align-items:center;justify-content:center;border-radius:50%;background:var(--_k-fill);color:transparent;
18729
+ transition:background var(--kw-snappy-ms) var(--kw-snappy),color var(--kw-snappy-ms) var(--kw-snappy)}
18730
+ .knobs-wt-check svg{width:12px;height:12px}
18731
+ .knobs-wt-option[data-picked] .knobs-wt-check{background:var(--_k-accent);color:var(--_k-bg)}
18732
+ .knobs-wt-key{display:inline-flex;align-items:center;justify-content:center;min-width:15px;height:15px;padding:0 3px;border-radius:4px;background:var(--_k-fill);color:var(--_k-fg-3);
18733
+ font:var(--kw-strong) var(--_k-type-small)/1 var(--_k-font);opacity:.5;transition:opacity .15s}
18734
+ .knobs-wt-step[data-current] .knobs-wt-key{opacity:1}
18735
+ .knobs-wt-ocap{color:var(--_k-fg-2);max-width:var(--kw-measure)}
18736
+
18737
+ /* ——— the rail: progress, contents, export ——— */
18738
+ .knobs-wt-rail{position:sticky;top:var(--_k-pad-page);display:flex;flex-direction:column;gap:var(--_k-space-title);max-height:calc(100vh - var(--_k-pad-page) * 2);min-height:0;font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
18739
+ .knobs-wt-progress{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
18740
+ .knobs-wt-count{display:flex;align-items:baseline;justify-content:space-between;color:var(--_k-fg-2)}
18741
+ .knobs-wt-count b{color:var(--_k-fg);font-weight:var(--kw-strong)}
18742
+ .knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
18743
+ .knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
18744
+ .knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
18745
+ /* The contents: every row on one text edge and in one colour, title rows (32 high, the control
18746
+ radius) and the steps under them (20 high, half of it), bled out so their text sits on the
18747
+ rail's edge; the selection pill slides to the current step, and base rows never brighten. */
18748
+ .knobs-wt-toc{position:relative;flex:1 1 auto;min-height:0;overflow:auto;scrollbar-width:none;display:flex;flex-direction:column;gap:var(--_k-space-sibling);margin:0 calc(var(--_k-space-sibling) * -1);padding:0 0 8px}
18749
+ .knobs-wt-toc::-webkit-scrollbar{display:none}
18750
+ .knobs-wt-toc-group{display:flex;flex-direction:column;min-width:0}
18751
+ .knobs-ui .knobs-wt-toc-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;flex:none;width:100%;height:32px;
18752
+ padding:0 var(--_k-space-sibling);border-radius:var(--_k-radius);color:var(--_k-fg-2);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight);text-align:left;white-space:nowrap;cursor:pointer;
18753
+ transition:color calc(var(--_k-hover-out) * 1ms)}
18754
+ .knobs-ui .knobs-wt-toc-sub{height:20px;border-radius:calc(var(--_k-radius) / 2);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight)}
18755
+ .knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18756
+ .knobs-ui .knobs-wt-toc-row:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
18757
+ .knobs-wt-toc-end{display:flex;align-items:center;font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight);color:var(--_k-fg-3)}
18758
+ .knobs-wt-toc-label{min-width:0;overflow:hidden;${fade2}}
18759
+ .knobs-pick-copy .knobs-wt-toc-end{color:var(--_k-fg-2)}
18760
+ /* A step's state: an open ring, a filled dot once answered, nothing to answer a faint ring. */
18761
+ .knobs-wt-toc-glyph{--kw-tone:var(--_k-fg-3);display:block;width:6px;height:6px;border-radius:50%;box-shadow:inset 0 0 0 1.5px var(--kw-tone);
18762
+ transition:background var(--kw-snappy-ms) var(--kw-snappy),box-shadow var(--kw-snappy-ms) var(--kw-snappy)}
18763
+ .knobs-wt-toc-glyph[data-tone="accent"]{--kw-tone:var(--_k-accent)}
18764
+ .knobs-wt-toc-glyph[data-done]{background:var(--kw-tone)}
18765
+ .knobs-wt-toc-glyph[data-tone="none"]{box-shadow:inset 0 0 0 1px var(--_k-border)}
18766
+ .knobs-wt-actions{display:flex;flex-wrap:wrap;gap:4px}
18767
+ .knobs-ui .knobs-wt-btn{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 9px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
18768
+ transition:background .15s,color .15s}
18769
+ .knobs-ui .knobs-wt-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
18770
+ .knobs-ui .knobs-wt-btn[data-primary]{background:var(--_k-fill-selected);color:var(--_k-fg)}
18771
+ .knobs-ui .knobs-wt-btn[data-primary]:is(:hover,:focus-visible){background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
18772
+ .knobs-wt-keys{color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
18773
+ .knobs-wt-keys kbd{font:inherit;color:var(--_k-fg-2)}
18774
+ /* Narrow: the contents go; with review on, the rail becomes a bar along the top with the
18775
+ progress and the export. */
18776
+ @container knobs-wt (max-width:960px){
18777
+ .knobs-wt-rail{z-index:4;flex-direction:row;align-items:center;gap:var(--_k-space-title);max-height:none;margin:0 calc(var(--_k-pad-page) * -1);padding:8px var(--_k-pad-page);top:0;background:var(--_k-bg-page)}
18778
+ .knobs-wt-progress{flex:1}.knobs-wt-toc,.knobs-wt-keys{display:none}
18779
+ .knobs-walkthrough:not([data-review]) .knobs-wt-rail{display:none}
18780
+ .knobs-wt-step,.knobs-wt-shead,.knobs-wt-feed{scroll-margin-top:60px}
18781
+ }
18782
+
18783
+ /* ——— media slots. Only a picture's frame (a quiet fill while it loads or letterboxes) and a code
18784
+ block (a well) are bounded; a metric, a callout, a table, a stack and a history sit on the page,
18785
+ set apart by space, hairlines and their own colours. On a card (.knobs-surface) code drops its
18786
+ well for hairlines, so nothing draws a fill and a ring of its own there ——— */
18787
+ .knobs-ui.knobs-wt-slot{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0;container:knobs-wt-slot / inline-size;color:var(--_k-fg);
18788
+ font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;--kw-measure:var(--k-walkthrough-measure,68ch);
18789
+ --kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));--kw-frame-r:var(--_k-radius-panel)}
18790
+ .knobs-surface .knobs-wt-slot{--kw-frame-r:calc(var(--_k-radius-panel) - 4px)}
18791
+ /* A slot's label: sentence case as written, small and faint, never a heading. */
18792
+ .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
+ .knobs-wt-body{min-width:0}
18794
+ /* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
18795
+ slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
18796
+ .knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
18797
+ /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
18798
+ sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
18799
+ (92 px), and the frame strip with its gap (158 px). */
18800
+ .knobs-wt-fit{width:100%;height:calc(100cqw / 2 / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
18801
+ .knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
18802
+ .knobs-wt-fit[data-strip]{--kw-chrome:158px}
18803
+ .knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
18804
+ .knobs-wt-frame{position:relative;width:100%;overflow:hidden;border-radius:var(--kw-frame-r);background:var(--_k-fill);aspect-ratio:var(--kw-aspect,16/9)}
18805
+ .knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
18806
+ .knobs-wt-frame[data-natural]{aspect-ratio:auto;background:transparent}
18807
+ .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);
18809
+ 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)}
18811
+ .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
18812
+ .knobs-wt-prompt{color:var(--_k-fg-3);user-select:none;-webkit-user-select:none}
18813
+ .knobs-wt-cmd{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
18814
+ .knobs-wt-out{color:var(--_k-fg-2)}
18815
+ .knobs-wt-metric{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
18816
+ .knobs-wt-mlabel{color:var(--_k-fg-2)}
18817
+ .knobs-wt-mvalue{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--_k-space-sibling);font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
18818
+ .knobs-wt-mfrom{color:var(--_k-fg-3)}
18819
+ .knobs-wt-mto{font-size:var(--_k-type-title);line-height:var(--_k-leading-title);font-weight:var(--_k-weight-label)}
18820
+ .knobs-wt-mof{color:var(--_k-fg-3)}
18821
+ .knobs-wt-mdelta{--kw-tone:var(--_k-fg-2);font-size:var(--_k-type-small);font-weight:var(--_k-weight-label);color:var(--kw-tone)}
18822
+ .knobs-wt-mdelta[data-tone="good"]{--kw-tone:var(--kw-good)}
18823
+ .knobs-wt-mdelta[data-tone="bad"]{--kw-tone:var(--_k-danger)}
18824
+ .knobs-wt-mtrack{position:relative;height:4px;border-radius:2px;background:color-mix(in srgb,var(--_k-fg) 8%,transparent)}
18825
+ .knobs-wt-mtrack>span{position:absolute;top:0;bottom:0;left:0;border-radius:inherit}
18826
+ .knobs-wt-mtrack>.knobs-wt-mafter{background:var(--_k-accent)}
18827
+ .knobs-wt-mtrack>.knobs-wt-mbefore{background:color-mix(in srgb,var(--_k-fg) 30%,transparent)}
18828
+ .knobs-wt-callout{--kw-tone:var(--kw-info);display:flex;gap:var(--_k-space-sibling);max-width:var(--kw-measure);color:var(--_k-fg)}
18829
+ .knobs-wt-callout::before{content:"";flex:none;width:6px;height:6px;margin-top:7px;border-radius:50%;background:var(--kw-tone)}
18830
+ .knobs-wt-callout[data-tone="good"]{--kw-tone:var(--kw-good)}
18831
+ .knobs-wt-callout[data-tone="warn"]{--kw-tone:var(--_k-warn)}
18832
+ .knobs-wt-callout[data-tone="bad"]{--kw-tone:var(--_k-danger)}
18833
+ .knobs-wt-text{max-width:var(--kw-measure);color:var(--_k-fg-2)}
18834
+ .knobs-wt-hold{border-radius:var(--kw-frame-r);background:var(--_k-fill)}
18835
+ .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
+
18837
+ /* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
18838
+ 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)}
18840
+ :where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
18841
+ :where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
18842
+ :where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
18843
+ :where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
18844
+ :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
+
18880
+ /* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
18881
+ git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
18882
+ .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:var(--_k-type-body);line-height:var(--kw-line);color:var(--_k-fg)}
18883
+ .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);
18884
+ border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
18885
+ .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
18886
+ .knobs-wt-srow[data-go]{cursor:pointer}
18887
+ .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18888
+ .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
18889
+ .knobs-wt-lane{position:relative;flex:none;width:16px}
18890
+ .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)}
18891
+ .knobs-wt-lane[data-down]::before{top:calc(var(--kw-sy) + 3.5px)}
18892
+ .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}
18893
+ .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)}
18894
+ .knobs-wt-srow[data-trunk] .knobs-wt-lane[data-rail="node"]::after{background:var(--_k-fg-2)}
18895
+ .knobs-wt-trunk{font:var(--_k-weight-label) var(--_k-type-label)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2)}
18896
+ .knobs-wt-smain{display:flex;flex-direction:column;gap:1px;min-width:0}
18897
+ .knobs-wt-sline{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 8px}
18898
+ .knobs-wt-sord{font:var(--_k-weight) var(--_k-type-label)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3);white-space:nowrap}
18899
+ .knobs-ui .knobs-wt-spr{font:var(--_k-weight-label) var(--_k-type-label)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2);text-decoration:none;white-space:nowrap}
18900
+ .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
18901
+ .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
18902
+ .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)}
18903
+ .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)}
18904
+ .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
18905
+ .knobs-wt-smeta{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 12px;font:var(--_k-weight) var(--_k-type-small)/var(--kw-line) var(--_k-mono);font-variant-numeric:tabular-nums;color:var(--_k-fg-3)}
18906
+ .knobs-wt-delta{display:inline-flex;gap:6px}
18907
+ .knobs-wt-add{color:var(--_k-diff-add)}
18908
+ .knobs-wt-del{color:var(--_k-diff-del)}
18909
+ .knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
18910
+ .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
18911
+ .knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
18912
+ @container knobs-wt-slot (max-width:520px){
18913
+ .knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
18914
+ .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
18915
+ .knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
18095
18916
  }
18096
- var grid2 = (v) => Math.round(v * 64) / 64;
18097
- function chapterPills(host, chapters, io) {
18098
- const doc = host.ownerDocument;
18099
- const win = doc.defaultView;
18100
- const bar = el(doc, "div", "knobs-wt-chapters");
18101
- bar.setAttribute("role", "group");
18102
- bar.setAttribute("aria-label", "Chapters");
18103
- const mark = el(doc, "span", "knobs-wt-chapter-mark");
18104
- mark.setAttribute("aria-hidden", "true");
18105
- bar.append(mark);
18106
- const pills = chapters.map((c) => {
18107
- const b = el(doc, "button", "knobs-wt-chapter");
18108
- b.type = "button";
18109
- b.append(text(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
18110
- b.append(text(doc, "span", "knobs-wt-chapter-label", c.label));
18111
- b.addEventListener("click", () => {
18112
- io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
18113
- io.seek(c.start);
18114
- sync();
18115
- });
18116
- bar.append(b);
18117
- return b;
18118
- });
18119
- host.append(bar);
18120
- const x = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
18121
- const y = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
18122
- const w = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
18123
- const draw = () => {
18124
- const left = grid2(x.x);
18125
- mark.style.transform = `translate(${left}px,${grid2(y.x)}px)`;
18126
- mark.style.width = `${grid2(x.x + w.x) - left}px`;
18127
- };
18128
- const loop = frameLoop(win, (dt) => {
18129
- x.step(dt);
18130
- y.step(dt);
18131
- w.step(dt);
18132
- draw();
18133
- return !x.resting || !y.resting || !w.resting;
18134
- });
18135
- let at = -2;
18136
- let shown = false;
18137
- const place = (snap) => {
18138
- const p = pills[at];
18139
- if (!p) {
18140
- mark.removeAttribute("data-on");
18141
- pills.forEach((b) => b.removeAttribute("aria-current"));
18142
- shown = false;
18143
- return;
18144
- }
18145
- pills.forEach((b, i) => i === at ? b.setAttribute("aria-current", "true") : b.removeAttribute("aria-current"));
18146
- const to = [p.offsetLeft, p.offsetTop, p.offsetWidth];
18147
- if (snap || !shown || reducedMotion(win)) {
18148
- x.snap(to[0]);
18149
- y.snap(to[1]);
18150
- w.snap(to[2]);
18151
- draw();
18152
- } else {
18153
- x.set(to[0]);
18154
- y.set(to[1]);
18155
- w.set(to[2]);
18156
- loop.wake();
18157
- }
18158
- mark.style.height = `${p.offsetHeight}px`;
18159
- mark.dataset.on = "";
18160
- shown = true;
18161
- };
18162
- const sync = () => {
18163
- const next = chapterAt(chapters, io.now());
18164
- if (next === at)
18165
- return;
18166
- at = next;
18167
- place(false);
18168
- };
18169
- sync();
18170
- const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => place(true)) : null;
18171
- ro?.observe(bar);
18172
- return {
18173
- sync,
18174
- get current() {
18175
- return chapters[at] ?? null;
18176
- },
18177
- dispose() {
18178
- loop.stop();
18179
- ro?.disconnect();
18180
- bar.remove();
18181
- }
18182
- };
18917
+
18918
+ /* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
18919
+ (the top one under the head), and thin bars show on hover. */
18920
+ /* Too narrow for its columns, each row becomes a block: its primary cell titling the rest as term
18921
+ and value, the blocks parted by hairlines. Scrolling sideways is the last resort (stack: never). */
18922
+ .knobs-wt-tablebox{min-width:0}
18923
+ .knobs-wt-tablebox[data-measure] table{width:min-content}
18924
+ .knobs-wt-tstack{display:none;flex-direction:column;min-width:0}
18925
+ .knobs-wt-tablebox[data-stacked]>.knobs-wt-table{display:none}
18926
+ .knobs-wt-tablebox[data-stacked]>.knobs-wt-tstack{display:flex}
18927
+ .knobs-wt-trow{display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0;padding-block:var(--_k-space-title);box-shadow:inset 0 -1px 0 var(--_k-border-subtle)}
18928
+ .knobs-wt-trow:first-child{padding-top:0}
18929
+ .knobs-wt-trow:last-of-type{padding-bottom:0;box-shadow:none}
18930
+ .knobs-wt-tline{display:flex;align-items:baseline;justify-content:space-between;gap:var(--_k-space-title);min-width:0}
18931
+ .knobs-wt-tbadges{flex:none;display:flex;align-items:center;gap:var(--_k-space-inside)}
18932
+ .knobs-wt-ttitle{flex:1 1 auto;min-width:0;color:var(--_k-fg);font-weight:var(--_k-weight-label);overflow-wrap:anywhere}
18933
+ .knobs-wt-ttitle[data-mono]{font-family:var(--_k-mono);font-size:var(--_k-type-label)}
18934
+ .knobs-wt-tterms{display:grid;grid-template-columns:minmax(6ch,max-content) minmax(0,1fr);gap:var(--_k-space-inside) var(--_k-space-title);min-width:0}
18935
+ .knobs-wt-tterms dt{color:var(--_k-fg-3);font-size:var(--_k-type-small)}
18936
+ .knobs-wt-tterms dd{min-width:0;color:var(--_k-fg-2);overflow-wrap:anywhere}
18937
+ .knobs-wt-tterms dd[data-mono]{font-family:var(--_k-mono);font-size:var(--_k-type-label);color:var(--_k-fg)}
18938
+ .knobs-wt-tcaption{padding-top:var(--_k-space-sibling);color:var(--_k-fg-3);font-size:var(--_k-type-small)}
18939
+ @property --kw-fade-l{syntax:"<number>";inherits:false;initial-value:1}
18940
+ @property --kw-fade-r{syntax:"<number>";inherits:false;initial-value:1}
18941
+ @keyframes kw-fade-l{from{--kw-fade-l:1}to{--kw-fade-l:0}}
18942
+ @keyframes kw-fade-r{from{--kw-fade-r:0}to{--kw-fade-r:1}}
18943
+ .knobs-wt-table{max-height:520px;overflow:auto;overscroll-behavior-x:contain;outline:none;scrollbar-width:thin;scrollbar-color:transparent transparent;
18944
+ transition:scrollbar-color calc(var(--_k-hover-out) * 1ms)}
18945
+ .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)}
18946
+ .knobs-wt-table:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus);border-radius:var(--kw-frame-r)}
18947
+ @supports (animation-timeline:scroll()){
18948
+ .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;
18949
+ 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%;
18950
+ --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)));
18951
+ --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)));
18952
+ -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}
18183
18953
  }
18954
+ .knobs-wt-table table{width:100%;margin:0;border-collapse:separate;border-spacing:0;font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-variant-numeric:tabular-nums;color:var(--_k-fg-2)}
18955
+ .knobs-wt-table caption{caption-side:bottom;padding-top:8px;text-align:left;color:var(--_k-fg-3);font-size:var(--_k-type-small)}
18956
+ .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:var(--_k-type-small);color:var(--_k-fg-3);white-space:nowrap;
18957
+ background:var(--kw-under,var(--_k-pane-bg));box-shadow:inset 0 -1px 0 var(--_k-border)}
18958
+ .knobs-wt-table td{padding:8px 12px;vertical-align:baseline;box-shadow:inset 0 -1px 0 var(--_k-border)}
18959
+ .knobs-wt-table tr:last-child td{box-shadow:none}
18960
+ .knobs-wt-table :is(th,td):first-child{padding-left:0}
18961
+ .knobs-wt-table :is(th,td):last-child{padding-right:0}
18962
+ .knobs-wt-table td:first-child{color:var(--_k-fg)}
18963
+ .knobs-wt-table [data-align="right"]{text-align:right}
18964
+ .knobs-wt-table [data-align="center"]{text-align:center}
18965
+ .knobs-wt-table td[data-mono]{font-family:var(--_k-mono);font-size:var(--_k-type-label);color:var(--_k-fg);white-space:nowrap}
18966
+ .knobs-wt-table td[data-long]{min-width:24ch}
18967
+ .knobs-ui :is(.knobs-wt-table,.knobs-wt-tstack) a.knobs-wt-chip{text-decoration:none}
18968
+ .knobs-wt-ctone{color:var(--kw-tone)}
18969
+
18970
+ /* Chapters: a readout list under a video or compare: number, label, start; the selection pill
18971
+ on the one playing, its rows bled out so their text sits on the media's edge. */
18972
+ .knobs-wt-body:has(>.knobs-wt-chapters){display:flex;flex-direction:column;gap:var(--_k-space-title)}
18973
+ .knobs-wt-chapters{position:relative;display:flex;flex-direction:column;margin:0 calc(var(--_k-space-sibling) * -1)}
18974
+ .knobs-ui .knobs-wt-chapter{position:relative;display:grid;grid-template-columns:2ch minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;height:32px;padding:0 var(--_k-space-sibling);
18975
+ border-radius:var(--_k-radius);color:var(--_k-fg-2);text-align:left;white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
18976
+ .knobs-ui .knobs-wt-chapter:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
18977
+ .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
18978
+ .knobs-wt-chapter-n{font-size:var(--_k-type-small);color:var(--_k-fg-3)}
18979
+ .knobs-wt-chapter-label{min-width:0;overflow:hidden;${fade2}}
18980
+ .knobs-wt-chapter-time{font:var(--_k-weight) var(--_k-type-small)/1 var(--_k-mono);color:var(--_k-fg-3)}
18981
+ .knobs-pick-copy :is(.knobs-wt-chapter-n,.knobs-wt-chapter-time){color:var(--_k-fg-2)}
18982
+ @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
18983
+ @media(prefers-reduced-motion:reduce){.knobs-wt-disc-content{transition:opacity .15s}}
18984
+ `;
18184
18985
 
18185
18986
  // src/ui/walkthrough-media.ts
18186
18987
  function injectWalkthroughStyles(doc) {
@@ -18709,7 +19510,7 @@ function mediaSlot(container, options) {
18709
19510
  }
18710
19511
  };
18711
19512
  }
18712
- var CHECK3 = svg('<path d="m3.5 8.4 3 3 6-6.6"/>');
19513
+ var CHECK3 = icon("check", { small: true });
18713
19514
 
18714
19515
  // src/ui/walkthrough.ts
18715
19516
  var el2 = (doc, tag, cls) => {
@@ -18727,16 +19528,20 @@ var lookOf2 = (o) => ({
18727
19528
  ...o.preset ? { preset: o.preset } : {},
18728
19529
  ...o.theme ? { theme: o.theme } : {}
18729
19530
  });
18730
- var grid3 = (v) => Math.round(v * 64) / 64;
19531
+ var grid2 = (v) => Math.round(v * 64) / 64;
18731
19532
  var ICON = {
18732
- full: svg('<path d="M3 6.5V3h3.5M13 6.5V3H9.5M3 9.5V13h3.5M13 9.5V13H9.5"/>'),
18733
- exit: svg('<path d="M6.5 3v3.5H3M9.5 3v3.5H13M6.5 13V9.5H3M9.5 13V9.5H13"/>'),
18734
- pen: svg('<path d="M10.5 3.2 12.8 5.5 5.8 12.5 3 13l.5-2.8z"/><path d="M9.3 4.4l2.3 2.3"/>'),
18735
- mic: svg('<rect x="6" y="2.5" width="4" height="7" rx="2"/><path d="M3.8 8a4.2 4.2 0 0 0 8.4 0M8 12.2v1.8"/>'),
19533
+ full: icon("cornersOut"),
19534
+ exit: icon("cornersIn"),
19535
+ pen: icon("pencilSimple"),
19536
+ mic: icon("microphone"),
18736
19537
  stop: svg('<rect x="4.5" y="4.5" width="7" height="7" rx="1.5" fill="currentColor" stroke="none"/>'),
18737
- send: svg('<path d="M8 13V3.5M4 7.5 8 3.5l4 4"/>'),
18738
- close: svg('<path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/>')
19538
+ send: icon("paperPlaneRight"),
19539
+ close: icon("x", { small: true }),
19540
+ back: icon("arrowLeft", { small: true }),
19541
+ out: icon("arrowUpRight", { small: true })
18739
19542
  };
19543
+ var DOCK_INSET = 12;
19544
+ var DOCK_GAP = 12;
18740
19545
  var CALM2 = springMotion(SPRINGS.calm);
18741
19546
  var SMOOTH = springMotion(SPRINGS.smooth);
18742
19547
  var BACK_MS = 4500;
@@ -18752,16 +19557,25 @@ function tasteTest(container, options) {
18752
19557
  ...o.media !== undefined ? { media: fillMedia(o.media) } : {},
18753
19558
  suggested: o.suggested === true
18754
19559
  });
18755
- let picked = options.picked ?? null;
19560
+ const pickable = options.pickable !== false;
19561
+ let picked = pickable ? options.picked ?? null : null;
18756
19562
  const root = el2(doc, "div", "knobs-wt-options");
18757
- root.setAttribute("role", "radiogroup");
18758
- root.setAttribute("aria-label", "Options");
19563
+ if (pickable) {
19564
+ root.setAttribute("role", "radiogroup");
19565
+ root.setAttribute("aria-label", "Options");
19566
+ } else
19567
+ root.dataset.static = "";
18759
19568
  root.style.setProperty("--kw-n", String(Math.min(list.length, 3)));
18760
19569
  if (!container.closest(".knobs-ui"))
18761
19570
  applyLook2(root, options);
18762
19571
  const slots = [];
18763
19572
  const tiles = list.map((o, i) => {
18764
19573
  const tile = el2(doc, "div", "knobs-wt-option");
19574
+ if (!pickable) {
19575
+ const name = el2(doc, "span", "knobs-wt-slot-label");
19576
+ name.textContent = o.label;
19577
+ tile.append(name);
19578
+ }
18765
19579
  const pick = el2(doc, "button", "knobs-wt-pick");
18766
19580
  pick.type = "button";
18767
19581
  pick.setAttribute("role", "radio");
@@ -18790,7 +19604,8 @@ function tasteTest(container, options) {
18790
19604
  api.pick(next);
18791
19605
  options.onPick?.(next);
18792
19606
  });
18793
- tile.append(pick);
19607
+ if (pickable)
19608
+ tile.append(pick);
18794
19609
  if (o.media)
18795
19610
  slots.push(mediaSlot(tile, {
18796
19611
  ...look,
@@ -18869,8 +19684,13 @@ function walkthrough(container, options) {
18869
19684
  let count = null;
18870
19685
  let fill = null;
18871
19686
  let tocEl = null;
18872
- let mark = null;
19687
+ let pill = null;
19688
+ let tocRows = [];
19689
+ let discs = [];
18873
19690
  let full = null;
19691
+ const review = options.review === true;
19692
+ const noteMode = review ? options.noteBar ?? "collapsed" : "hidden";
19693
+ let reserve = noteBarReserve(52, DOCK_INSET, DOCK_GAP);
18874
19694
  const viewOf = (key) => views.find((v) => v.step.key === key);
18875
19695
  const feedback = () => collectFeedback(page, state);
18876
19696
  const markdown = () => feedbackMarkdown(feedback(), page.verdicts);
@@ -18894,9 +19714,6 @@ function walkthrough(container, options) {
18894
19714
  else
18895
19715
  run();
18896
19716
  };
18897
- const pillY = new Spring(0, CALM2.omega, CALM2.zeta, 0.25);
18898
- const pillH = new Spring(0, CALM2.omega, CALM2.zeta, 0.25);
18899
- let pillPlaced = false;
18900
19717
  const scroll = {
18901
19718
  spring: new Spring(0, CALM2.omega, CALM2.zeta, 0.5),
18902
19719
  to: null,
@@ -18904,13 +19721,6 @@ function walkthrough(container, options) {
18904
19721
  };
18905
19722
  const motion = frameLoop(win, (dt) => {
18906
19723
  let more = false;
18907
- if (mark) {
18908
- pillY.step(dt);
18909
- pillH.step(dt);
18910
- mark.style.transform = `translateY(${grid3(pillY.x)}px)`;
18911
- mark.style.height = `${grid3(pillY.x + pillH.x) - grid3(pillY.x)}px`;
18912
- more ||= !pillY.resting || !pillH.resting;
18913
- }
18914
19724
  if (scroll.to && scroll.el) {
18915
19725
  scroll.spring.set(scrollTarget(scroll.to, scroll.el));
18916
19726
  scroll.spring.step(dt);
@@ -18974,21 +19784,12 @@ function walkthrough(container, options) {
18974
19784
  v.glyph.dataset.tone = v.step.ask === "none" ? "none" : verdict ? verdictTone(verdict, page.verdicts.findIndex((x) => x.id === verdict)) : done ? "accent" : "dim";
18975
19785
  };
18976
19786
  const placeMark = () => {
18977
- if (!mark || !tocEl)
19787
+ if (!pill || !tocEl)
18978
19788
  return;
19789
+ pill.update();
18979
19790
  const row = viewOf(current)?.toc;
18980
19791
  if (!row)
18981
- return void mark.removeAttribute("data-on");
18982
- mark.dataset.on = "";
18983
- if (!pillPlaced || reduced()) {
18984
- pillY.snap(row.offsetTop);
18985
- pillH.snap(row.offsetHeight);
18986
- pillPlaced = true;
18987
- } else {
18988
- pillY.set(row.offsetTop);
18989
- pillH.set(row.offsetHeight);
18990
- }
18991
- motion.wake();
19792
+ return;
18992
19793
  const top = row.offsetTop;
18993
19794
  const bottom = top + row.offsetHeight;
18994
19795
  if (top < tocEl.scrollTop + 8)
@@ -19005,6 +19806,9 @@ function walkthrough(container, options) {
19005
19806
  const v = viewOf(key);
19006
19807
  v?.card.setAttribute("data-current", "");
19007
19808
  v?.toc?.setAttribute("aria-current", "step");
19809
+ for (const r of tocRows)
19810
+ if (r.dataset.section)
19811
+ r.toggleAttribute("data-active", r.dataset.section === v?.step.section);
19008
19812
  if (key)
19009
19813
  record({ what: "step", step: key });
19010
19814
  placeMark();
@@ -19021,6 +19825,16 @@ function walkthrough(container, options) {
19021
19825
  else
19022
19826
  break;
19023
19827
  }
19828
+ const s = scrollerOf(root);
19829
+ if (s.scrollTop > 0 && s.scrollTop >= s.scrollHeight - s.clientHeight - 2) {
19830
+ const bottom = (s === doc.scrollingElement ? win.innerHeight : s.getBoundingClientRect().bottom) - reserve;
19831
+ for (let i = views.length - 1;i >= 0; i--) {
19832
+ if (views[i].card.getBoundingClientRect().top < bottom) {
19833
+ pick = views[i].step.key;
19834
+ break;
19835
+ }
19836
+ }
19837
+ }
19024
19838
  setCurrent(pick);
19025
19839
  };
19026
19840
  let scrollRaf = 0;
@@ -19063,15 +19877,20 @@ function walkthrough(container, options) {
19063
19877
  };
19064
19878
  const scrim = el2(doc, "div", "knobs-wt-scrim");
19065
19879
  scrim.addEventListener("click", () => setFull(false));
19066
- const fullRect = () => ({ x: 12, y: 12, w: win.innerWidth - 24, h: win.innerHeight - 24 });
19880
+ const fullRect = () => ({
19881
+ x: DOCK_INSET,
19882
+ y: DOCK_INSET,
19883
+ w: win.innerWidth - DOCK_INSET * 2,
19884
+ h: win.innerHeight - DOCK_INSET - reserve
19885
+ });
19067
19886
  const placeFull = (f) => {
19068
19887
  const a = f.hold.getBoundingClientRect();
19069
19888
  const b = fullRect();
19070
19889
  const k = reduced() ? Math.round(f.p.target) : f.p.x;
19071
- const left = grid3(a.left + (b.x - a.left) * k);
19072
- const top = grid3(a.top + (b.y - a.top) * k);
19073
- const right = grid3(a.right + (b.x + b.w - a.right) * k);
19074
- const bottom = grid3(a.bottom + (b.y + b.h - a.bottom) * k);
19890
+ const left = grid2(a.left + (b.x - a.left) * k);
19891
+ const top = grid2(a.top + (b.y - a.top) * k);
19892
+ const right = grid2(a.right + (b.x + b.w - a.right) * k);
19893
+ const bottom = grid2(a.bottom + (b.y + b.h - a.bottom) * k);
19075
19894
  const s = f.v.card.style;
19076
19895
  s.left = `${left}px`;
19077
19896
  s.top = `${top}px`;
@@ -19208,6 +20027,7 @@ function walkthrough(container, options) {
19208
20027
  ink.on = true;
19209
20028
  ink.layer.dataset.on = "";
19210
20029
  root.dataset.inking = "";
20030
+ bar.setOpen(true);
19211
20031
  bar.sync();
19212
20032
  };
19213
20033
  const voice = {
@@ -19247,6 +20067,7 @@ function walkthrough(container, options) {
19247
20067
  });
19248
20068
  voice.recorder = rec;
19249
20069
  voice.started = performance.now();
20070
+ bar.setOpen(true);
19250
20071
  rec.start();
19251
20072
  voice.timer = win.setInterval(() => bar.sync(), 250);
19252
20073
  bar.sync();
@@ -19273,7 +20094,10 @@ function walkthrough(container, options) {
19273
20094
  if (!v)
19274
20095
  return;
19275
20096
  backTo = key;
19276
- backPill.textContent = `← Back to ${v.step.number}`;
20097
+ backPill.innerHTML = ICON.back;
20098
+ const words = el2(doc, "span", "knobs-wt-back-label");
20099
+ words.textContent = `Back to ${plainMarkdown(v.step.title)}`;
20100
+ backPill.append(words);
19277
20101
  backPill.dataset.on = "";
19278
20102
  win.clearTimeout(backTimer);
19279
20103
  backTimer = win.setTimeout(hideBack, BACK_MS);
@@ -19305,7 +20129,41 @@ function walkthrough(container, options) {
19305
20129
  sendBtn.dataset.send = "";
19306
20130
  row.append(input, attach, fullBtn, penBtn, micBtn, sendBtn);
19307
20131
  box.append(on, row);
19308
- dock.append(backPill, box);
20132
+ const launch = el2(doc, "button", "knobs-wt-launch");
20133
+ launch.type = "button";
20134
+ launch.innerHTML = ICON.pen;
20135
+ launch.append("Note");
20136
+ const kbd = el2(doc, "kbd");
20137
+ kbd.textContent = "n";
20138
+ launch.append(kbd);
20139
+ launch.setAttribute("aria-label", "Write a note (n)");
20140
+ launch.addEventListener("click", () => setOpen(true, true));
20141
+ dock.dataset.mode = noteMode;
20142
+ dock.append(backPill, box, launch);
20143
+ let open = noteMode !== "collapsed";
20144
+ const setOpen = (on, focus = false) => {
20145
+ if (noteMode === "collapsed" && on !== open) {
20146
+ open = on;
20147
+ dock.toggleAttribute("data-open", on);
20148
+ box.inert = !on;
20149
+ launch.inert = on;
20150
+ syncReserve();
20151
+ }
20152
+ if (on && focus)
20153
+ input.focus({ preventScroll: true });
20154
+ };
20155
+ dock.toggleAttribute("data-open", open);
20156
+ box.inert = !open;
20157
+ launch.inert = open;
20158
+ const empty = () => !input.value.trim() && !voice.pending && !voice.recorder && !ink.on && !ink.paths.length;
20159
+ const onOutside = (e) => {
20160
+ if (open && noteMode === "collapsed" && !dock.contains(e.target) && empty())
20161
+ setOpen(false);
20162
+ };
20163
+ doc.addEventListener("pointerdown", onOutside, true);
20164
+ const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => syncReserve()) : null;
20165
+ ro?.observe(box);
20166
+ ro?.observe(launch);
19309
20167
  const fit = () => {
19310
20168
  input.style.height = "auto";
19311
20169
  input.style.height = `${Math.min(140, input.scrollHeight)}px`;
@@ -19321,6 +20179,8 @@ function walkthrough(container, options) {
19321
20179
  } else if (e.key === "Escape") {
19322
20180
  e.preventDefault();
19323
20181
  input.blur();
20182
+ if (empty())
20183
+ setOpen(false);
19324
20184
  viewOf(current)?.card.focus({ preventScroll: true });
19325
20185
  }
19326
20186
  });
@@ -19340,13 +20200,13 @@ function walkthrough(container, options) {
19340
20200
  const sync = () => {
19341
20201
  const v = viewOf(current);
19342
20202
  const ctx = describeMedia(mediaContext(v));
19343
- const where = v ? `${v.step.number} ${v.step.title}` : "General note";
20203
+ const where = v ? plainMarkdown(v.step.title) : "General note";
19344
20204
  const text = ctx ? `${where} · ${ctx}` : where;
19345
20205
  if (text !== lastOn) {
19346
20206
  on.textContent = text;
19347
20207
  lastOn = text;
19348
20208
  }
19349
- input.placeholder = v ? `Note on ${v.step.number}… (n)` : "A note on the whole page… (n)";
20209
+ input.placeholder = v ? "A note on this step… (n)" : "A note on the whole page… (n)";
19350
20210
  const attached = `${voice.pending?.url ?? ""}|${ink.paths.length}`;
19351
20211
  if (attached !== lastAttached) {
19352
20212
  lastAttached = attached;
@@ -19385,14 +20245,45 @@ function walkthrough(container, options) {
19385
20245
  };
19386
20246
  return {
19387
20247
  dock,
20248
+ box,
20249
+ launch,
19388
20250
  input,
19389
20251
  sync,
20252
+ get open() {
20253
+ return open;
20254
+ },
20255
+ setOpen,
20256
+ empty,
20257
+ unobserve: () => {
20258
+ ro?.disconnect();
20259
+ doc.removeEventListener("pointerdown", onOutside, true);
20260
+ },
19390
20261
  clear() {
19391
20262
  input.value = "";
19392
20263
  fit();
19393
20264
  }
19394
20265
  };
19395
20266
  })();
20267
+ let padded = null;
20268
+ function syncReserve() {
20269
+ if (disposed)
20270
+ return;
20271
+ const h = (bar.open ? bar.box : bar.launch).offsetHeight;
20272
+ if (noteMode === "hidden")
20273
+ reserve = 0;
20274
+ else if (h)
20275
+ reserve = noteBarReserve(h, DOCK_INSET, DOCK_GAP);
20276
+ root.style.setProperty("--kw-reserve", `${reserve}px`);
20277
+ const s = scrollerOf(root);
20278
+ if (padded?.el !== s) {
20279
+ if (padded)
20280
+ padded.el.style.scrollPaddingBottom = padded.was;
20281
+ padded = { el: s, was: s.style.scrollPaddingBottom };
20282
+ }
20283
+ s.style.scrollPaddingBottom = `${reserve}px`;
20284
+ if (full)
20285
+ placeFull(full);
20286
+ }
19396
20287
  const ticker = win.setInterval(() => !doc.hidden && bar.sync(), 500);
19397
20288
  const send = (text = bar.input.value) => {
19398
20289
  if (voice.recorder)
@@ -19428,6 +20319,10 @@ function walkthrough(container, options) {
19428
20319
  syncProgress();
19429
20320
  emit();
19430
20321
  bar.sync();
20322
+ if (noteMode === "collapsed") {
20323
+ bar.input.blur();
20324
+ bar.setOpen(false);
20325
+ }
19431
20326
  if (v && options.advance !== false) {
19432
20327
  const to = adjacentStep(views.map((x) => x.step.key), v.step.key, 1);
19433
20328
  if (to && to !== v.step.key) {
@@ -19435,7 +20330,8 @@ function walkthrough(container, options) {
19435
20330
  setFull(false);
19436
20331
  go(to);
19437
20332
  showBack(v.step.key);
19438
- bar.input.focus({ preventScroll: true });
20333
+ if (noteMode === "docked")
20334
+ bar.input.focus({ preventScroll: true });
19439
20335
  }
19440
20336
  }
19441
20337
  };
@@ -19522,28 +20418,121 @@ function walkthrough(container, options) {
19522
20418
  row.addEventListener("transitionend", done, { once: true });
19523
20419
  win.setTimeout(done, 600);
19524
20420
  };
20421
+ const metaLine = (cls) => el2(doc, "div", `knobs-wt-metaline ${cls}`);
20422
+ const metaText = (line, text, cls) => {
20423
+ const s = el2(doc, "span", cls);
20424
+ s.textContent = text;
20425
+ line.append(s);
20426
+ return s;
20427
+ };
20428
+ const findPRIn = (n) => findPR(page.prs, n, page.repo);
20429
+ const metaLink = (line, text, href) => {
20430
+ const n = prNumber(href);
20431
+ if (n) {
20432
+ const chip = prChip(doc, n, { href, ...findPRIn(n) });
20433
+ line.append(chip);
20434
+ return chip;
20435
+ }
20436
+ const a = el2(doc, "a");
20437
+ a.href = href;
20438
+ a.target = "_blank";
20439
+ a.rel = "noreferrer";
20440
+ a.textContent = text;
20441
+ a.insertAdjacentHTML("beforeend", ICON.out);
20442
+ line.append(a);
20443
+ return a;
20444
+ };
20445
+ const feedItem = (f, now) => {
20446
+ const li = el2(doc, "li", "knobs-wt-fi");
20447
+ li.dataset.kind = f.kind;
20448
+ const q = el2(doc, "div", "knobs-wt-fi-q");
20449
+ const meta = metaLine("knobs-wt-fi-meta");
20450
+ if (f.at !== null) {
20451
+ const when = metaText(meta, ago(f.at, now));
20452
+ when.title = new Date(f.at).toLocaleString();
20453
+ } else
20454
+ metaText(meta, f.kind === "qa" ? "Answer" : "Update");
20455
+ const step = f.step ? page.steps.find((s) => s.key === f.step) : undefined;
20456
+ if (step) {
20457
+ const section = page.sections.find((s) => s.id === step.section);
20458
+ const b = el2(doc, "button", "knobs-wt-fi-step");
20459
+ b.type = "button";
20460
+ b.textContent = section?.link?.label ?? plainMarkdown(step.title);
20461
+ b.title = `Go to “${plainMarkdown(step.title)}”`;
20462
+ b.addEventListener("click", () => go(step.key));
20463
+ meta.append(b);
20464
+ }
20465
+ if (f.status) {
20466
+ const id = f.status.toLowerCase();
20467
+ const shipped = /^(ship|shipped|merged|done)$/.test(id);
20468
+ const changes = /^(changes?|changes queued)$/.test(id);
20469
+ const s = metaText(meta, shipped ? "Shipped" : changes ? "Changes queued" : f.status);
20470
+ s.dataset.status = shipped ? "on" : changes ? "warn" : "";
20471
+ }
20472
+ q.append(meta);
20473
+ if (f.q)
20474
+ q.append(prose2(doc, "p", "knobs-wt-fi-ask", f.q));
20475
+ else
20476
+ q.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
20477
+ li.append(q);
20478
+ if (f.q || f.trail.length) {
20479
+ const a = el2(doc, "div", "knobs-wt-fi-a");
20480
+ if (f.q)
20481
+ a.append(prose2(doc, "div", "knobs-wt-fi-answer", f.text));
20482
+ if (f.trail.length) {
20483
+ const list = el2(doc, "ol", "knobs-wt-trail");
20484
+ for (const line of f.trail) {
20485
+ const i = line.indexOf(" · ");
20486
+ const row = el2(doc, "li");
20487
+ const words = el2(doc, "span", "knobs-wt-trail-text");
20488
+ words.textContent = i < 0 ? line : line.slice(i + 3);
20489
+ row.append(words);
20490
+ if (i >= 0) {
20491
+ const at = el2(doc, "span", "knobs-wt-trail-at");
20492
+ at.textContent = line.slice(0, i);
20493
+ row.append(at);
20494
+ }
20495
+ list.append(row);
20496
+ }
20497
+ discs.push(disclosure(a, { label: "Context", count: f.trail.length, content: list }));
20498
+ }
20499
+ li.append(a);
20500
+ }
20501
+ return li;
20502
+ };
19525
20503
  const build = () => {
19526
20504
  for (const v of views) {
19527
20505
  v.slots.forEach((s) => s.dispose());
19528
20506
  v.taste?.dispose();
19529
20507
  }
20508
+ for (const d of discs)
20509
+ d.dispose();
20510
+ discs = [];
20511
+ pill?.dispose();
20512
+ pill = null;
20513
+ tocRows = [];
19530
20514
  if (full) {
19531
20515
  full.p.snap(0);
19532
20516
  closeFull();
19533
20517
  }
19534
20518
  clearInk();
19535
20519
  views = [];
19536
- pillPlaced = false;
19537
20520
  root.textContent = "";
19538
20521
  root.removeAttribute("data-settled");
20522
+ root.dataset.rail = options.rail === false ? "none" : options.rail === "right" ? "right" : "left";
20523
+ root.toggleAttribute("data-cards", options.cards === true);
20524
+ root.toggleAttribute("data-review", review);
19539
20525
  const layout = el2(doc, "div", "knobs-wt-layout");
19540
20526
  const main = el2(doc, "div", "knobs-wt-main");
20527
+ const flow = el2(doc, "div", "knobs-wt-flow");
19541
20528
  const head = el2(doc, "header", "knobs-wt-head");
19542
20529
  const h1 = el2(doc, "h1", "knobs-wt-title");
19543
20530
  h1.textContent = page.title;
19544
20531
  head.append(h1);
19545
20532
  if (page.summary)
19546
20533
  head.append(prose2(doc, "p", "knobs-wt-summary", page.summary));
20534
+ if (page.more)
20535
+ head.append(more(page.more));
19547
20536
  if (page.meta.length) {
19548
20537
  const meta = el2(doc, "div", "knobs-wt-meta");
19549
20538
  for (const m of page.meta) {
@@ -19564,57 +20553,51 @@ function walkthrough(container, options) {
19564
20553
  }
19565
20554
  head.append(meta);
19566
20555
  }
19567
- main.append(head);
20556
+ flow.append(head);
20557
+ let feedEl = null;
19568
20558
  if (page.feed.length) {
19569
- const feed = el2(doc, "section", "knobs-wt-feed");
19570
- feed.setAttribute("aria-label", "Latest");
19571
- const list = el2(doc, "ol", "knobs-wt-feed-list knobs-scroll-fade");
20559
+ feedEl = el2(doc, "section", "knobs-wt-feed");
20560
+ feedEl.id = "wt-latest";
20561
+ 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");
20564
+ 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");
19572
20569
  const now = Date.now();
19573
- for (const f of page.feed) {
19574
- const li = el2(doc, "li", "knobs-wt-fi");
19575
- li.dataset.kind = f.kind;
19576
- const when = el2(doc, "span", "knobs-wt-fi-when");
19577
- when.textContent = f.at === null ? "" : ago(f.at, now);
19578
- if (f.at !== null)
19579
- when.title = new Date(f.at).toLocaleString();
19580
- const kind = el2(doc, "span", "knobs-wt-fi-kind");
19581
- kind.textContent = f.kind === "qa" ? "Answer" : "Update";
19582
- const body = el2(doc, "div", "knobs-wt-fi-body");
19583
- if (f.q)
19584
- body.append(prose2(doc, "p", "knobs-wt-fi-q", f.q));
19585
- body.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
19586
- li.append(when, kind, body);
19587
- if (f.step) {
19588
- const target = f.step;
19589
- const link = el2(doc, "button", "knobs-wt-fi-step");
19590
- link.type = "button";
19591
- link.textContent = page.steps.find((s) => s.key === target)?.number ?? "";
19592
- link.title = "Go to the step";
19593
- link.addEventListener("click", () => go(target));
19594
- li.append(link);
19595
- }
19596
- list.append(li);
19597
- }
19598
- feed.append(list);
19599
- main.append(feed);
20570
+ for (const f of page.feed)
20571
+ list.append(feedItem(f, now));
20572
+ feedEl.append(fh, list);
20573
+ flow.append(feedEl);
19600
20574
  }
19601
20575
  let rail = null;
19602
- count = fill = tocEl = mark = null;
20576
+ count = fill = tocEl = null;
19603
20577
  if (options.rail !== false) {
19604
20578
  rail = el2(doc, "aside", "knobs-wt-rail");
19605
- rail.setAttribute("aria-label", "Progress");
19606
- const progress = el2(doc, "div", "knobs-wt-progress");
19607
- count = el2(doc, "div", "knobs-wt-count");
19608
- const meter = el2(doc, "div", "knobs-wt-bar");
19609
- fill = el2(doc, "div", "knobs-wt-fill");
19610
- meter.append(fill);
19611
- progress.append(count, meter);
20579
+ rail.setAttribute("aria-label", "Contents");
19612
20580
  tocEl = el2(doc, "nav", "knobs-wt-toc");
19613
20581
  tocEl.setAttribute("aria-label", "Contents");
19614
- mark = el2(doc, "div", "knobs-wt-toc-mark");
19615
- tocEl.append(mark);
19616
- rail.append(progress, tocEl, actions(true));
19617
- if (options.keys !== false) {
20582
+ if (feedEl) {
20583
+ const group = el2(doc, "div", "knobs-wt-toc-group");
20584
+ const target = feedEl;
20585
+ group.append(tocRow("Latest", false, () => target.scrollIntoView({ block: "start", behavior: reduced() ? "auto" : "smooth" })));
20586
+ tocEl.append(group);
20587
+ }
20588
+ 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");
20593
+ meter.append(fill);
20594
+ progress.append(count, meter);
20595
+ rail.append(progress);
20596
+ }
20597
+ rail.append(tocEl);
20598
+ if (review)
20599
+ rail.append(actions(true));
20600
+ if (review && options.keys !== false) {
19618
20601
  const keys = el2(doc, "div", "knobs-wt-keys");
19619
20602
  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" : "");
19620
20603
  rail.append(keys);
@@ -19624,45 +20607,47 @@ function walkthrough(container, options) {
19624
20607
  const sec = el2(doc, "section", "knobs-wt-section");
19625
20608
  sec.id = `wt-${section.id}`;
19626
20609
  const sh = el2(doc, "header", "knobs-wt-shead");
19627
- if (section.link || section.status) {
19628
- const kicker = el2(doc, "div", "knobs-wt-kicker");
19629
- if (section.link) {
19630
- const a = el2(doc, "a");
19631
- a.href = section.link.href;
19632
- a.target = "_blank";
19633
- a.rel = "noreferrer";
19634
- a.textContent = `${section.link.label} ↗`;
19635
- kicker.append(a);
19636
- }
19637
- if (section.status) {
19638
- const s = statusLook(section.status);
19639
- const chip = el2(doc, "span", "knobs-wt-chip");
19640
- chip.dataset.tone = s.tone;
19641
- chip.textContent = s.label;
19642
- kicker.append(chip);
20610
+ const status = review ? section.status : undefined;
20611
+ if (section.kicker || section.link || status) {
20612
+ const meta = metaLine("knobs-wt-kicker");
20613
+ if (section.kicker)
20614
+ metaText(meta, section.kicker, "knobs-wt-kicker-lead");
20615
+ if (section.link)
20616
+ metaLink(meta, section.link.label, section.link.href);
20617
+ if (status) {
20618
+ const s = statusLook(status);
20619
+ metaText(meta, s.label, "knobs-wt-status").dataset.tone = s.tone;
19643
20620
  }
19644
- sh.append(kicker);
20621
+ sh.append(meta);
19645
20622
  }
19646
- const h2 = el2(doc, "h2", "knobs-wt-stitle");
19647
- h2.textContent = section.title;
20623
+ const h2 = el2(doc, "h2", "knobs-wt-heading");
20624
+ h2.innerHTML = inlineMarkdown(section.title);
19648
20625
  sh.append(h2);
20626
+ const intro = el2(doc, "div", "knobs-wt-sintro");
20627
+ intro.append(sh);
19649
20628
  if (section.summary)
19650
- sh.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
19651
- sec.append(sh);
20629
+ intro.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
20630
+ if (section.more)
20631
+ intro.append(more(section.more));
20632
+ sec.append(intro);
20633
+ let group = null;
19652
20634
  if (tocEl) {
19653
- const t = el2(doc, "div", "knobs-wt-toc-section");
19654
- t.textContent = section.link ? `${section.link.label} ${section.title}` : section.title;
19655
- t.title = section.title;
19656
- tocEl.append(t);
20635
+ group = el2(doc, "div", "knobs-wt-toc-group");
20636
+ const first = section.steps[0]?.key;
20637
+ group.append(tocRow(plainMarkdown(section.title), false, () => first ? go(first) : undefined, section.id));
20638
+ tocEl.append(group);
19657
20639
  }
19658
20640
  for (const step of section.steps) {
19659
- const v = stepView(step);
20641
+ const v = stepView(step, group);
19660
20642
  sec.append(v.card);
19661
20643
  views.push(v);
19662
20644
  }
19663
- main.append(sec);
20645
+ flow.append(sec);
19664
20646
  }
19665
- main.append(bar.dock);
20647
+ linkPRs(flow, findPRIn);
20648
+ main.append(flow);
20649
+ if (noteMode !== "hidden")
20650
+ main.append(bar.dock);
19666
20651
  if (rail)
19667
20652
  layout.append(rail);
19668
20653
  layout.append(main);
@@ -19672,16 +20657,46 @@ function walkthrough(container, options) {
19672
20657
  for (const n of notesOn(state, v.step.key))
19673
20658
  renderNote(v, n);
19674
20659
  }
20660
+ if (tocEl)
20661
+ pill = selectionPill(tocEl, {
20662
+ selected: () => viewOf(current)?.toc ?? null,
20663
+ rows: () => tocRows
20664
+ });
19675
20665
  syncProgress();
19676
20666
  current = null;
19677
20667
  fromScroll();
19678
20668
  placeMark();
19679
20669
  bar.sync();
20670
+ syncReserve();
19680
20671
  win.requestAnimationFrame(() => win.requestAnimationFrame(() => {
19681
20672
  if (!disposed && !reduced())
19682
20673
  root.dataset.settled = "";
19683
20674
  }));
19684
20675
  };
20676
+ const more = (text) => {
20677
+ const box = el2(doc, "div", "knobs-wt-more");
20678
+ const body = el2(doc, "div", "knobs-wt-more-text");
20679
+ for (const para of text.split(/\n\s*\n/))
20680
+ if (para.trim())
20681
+ body.append(prose2(doc, "p", "", para.trim()));
20682
+ discs.push(disclosure(box, { label: "More", content: body }));
20683
+ return box;
20684
+ };
20685
+ 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");
20687
+ b.type = "button";
20688
+ if (section)
20689
+ b.dataset.section = section;
20690
+ const t = el2(doc, "span", "knobs-wt-toc-label");
20691
+ t.textContent = label;
20692
+ const e = el2(doc, "span", "knobs-wt-toc-end");
20693
+ b.title = label;
20694
+ b.append(t, e);
20695
+ b.addEventListener("mousedown", (ev) => ev.preventDefault());
20696
+ b.addEventListener("click", run);
20697
+ tocRows.push(b);
20698
+ return b;
20699
+ };
19685
20700
  const actions = (rail) => {
19686
20701
  const row = el2(doc, "div", "knobs-wt-actions");
19687
20702
  const btn = (text, run, primary = false) => {
@@ -19722,37 +20737,38 @@ function walkthrough(container, options) {
19722
20737
  toast("Couldn't reach the clipboard", { tone: "warn", look, doc });
19723
20738
  }
19724
20739
  };
19725
- const stepView = (step) => {
19726
- const card = el2(doc, "article", "knobs-wt-step knobs-surface");
20740
+ const stepView = (step, group) => {
20741
+ const card = el2(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
19727
20742
  card.id = `wt-${step.key.replace("/", "--")}`;
19728
20743
  card.tabIndex = -1;
19729
20744
  card.dataset.key = step.key;
19730
- card.setAttribute("aria-label", `${step.number} ${step.title}`);
20745
+ card.setAttribute("aria-label", plainMarkdown(step.title));
19731
20746
  const head = el2(doc, "header", "knobs-wt-step-head");
19732
- const num = el2(doc, "span", "knobs-wt-num");
19733
- num.textContent = step.number;
19734
20747
  const h3 = el2(doc, "h3", "knobs-wt-stepTitle");
19735
- h3.textContent = step.title;
19736
- head.append(num, h3);
19737
- card.append(head);
20748
+ h3.innerHTML = inlineMarkdown(step.title);
20749
+ head.append(h3);
19738
20750
  if (step.caption)
19739
- card.append(prose2(doc, "p", "knobs-wt-caption", step.caption));
20751
+ head.append(prose2(doc, "p", "knobs-wt-caption", step.caption));
20752
+ if (step.more)
20753
+ head.append(more(step.more));
20754
+ card.append(head);
19740
20755
  const slots = [];
19741
20756
  const onEvent = (e) => record({ ...e, step: step.key });
19742
20757
  let media = null;
19743
20758
  if (step.media.length) {
19744
20759
  media = el2(doc, "div", "knobs-wt-media");
19745
- media.style.setProperty("--kw-n", String(Math.min(step.media.length, 3)));
20760
+ media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
19746
20761
  for (const m of step.media)
19747
20762
  slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
19748
20763
  card.append(media);
19749
20764
  }
19750
20765
  let taste = null;
19751
- if (step.question)
20766
+ if (step.question && review)
19752
20767
  card.append(prose2(doc, "p", "knobs-wt-question", step.question));
19753
20768
  if (step.ask === "pick") {
19754
20769
  taste = tasteTest(card, {
19755
20770
  ...look,
20771
+ pickable: review,
19756
20772
  options: step.options,
19757
20773
  picked: state.steps[step.key]?.pick ?? null,
19758
20774
  onPick: (id) => pick(step.key, id),
@@ -19761,7 +20777,8 @@ function walkthrough(container, options) {
19761
20777
  media ??= taste.element;
19762
20778
  }
19763
20779
  const notes = el2(doc, "div", "knobs-wt-notes");
19764
- card.append(notes);
20780
+ if (review)
20781
+ card.append(notes);
19765
20782
  card.addEventListener("focusin", () => {
19766
20783
  if (scroll.to)
19767
20784
  return;
@@ -19770,16 +20787,13 @@ function walkthrough(container, options) {
19770
20787
  });
19771
20788
  let toc = null;
19772
20789
  let glyph = null;
19773
- if (tocEl) {
19774
- toc = el2(doc, "button", "knobs-wt-toc-step");
19775
- toc.type = "button";
19776
- glyph = el2(doc, "span", "knobs-wt-toc-glyph");
19777
- const label = el2(doc, "span", "knobs-wt-toc-label");
19778
- label.textContent = step.title;
19779
- toc.title = `${step.number} ${step.title}`;
19780
- toc.append(glyph, label);
19781
- toc.addEventListener("click", () => go(step.key));
19782
- tocEl.append(toc);
20790
+ if (group) {
20791
+ toc = tocRow(plainMarkdown(step.title), true, () => go(step.key));
20792
+ if (review) {
20793
+ glyph = el2(doc, "span", "knobs-wt-toc-glyph");
20794
+ toc.querySelector(".knobs-wt-toc-end").append(glyph);
20795
+ }
20796
+ group.append(toc);
19783
20797
  }
19784
20798
  return { step, card, media, notes, toc, glyph, taste, slots };
19785
20799
  };
@@ -19838,19 +20852,25 @@ function walkthrough(container, options) {
19838
20852
  setFull(false);
19839
20853
  return go(next);
19840
20854
  }
20855
+ if ((key === "Home" || key === "End") && views.length) {
20856
+ claim();
20857
+ if (full)
20858
+ setFull(false);
20859
+ return go(views[key === "Home" ? 0 : views.length - 1].step.key);
20860
+ }
19841
20861
  if (key === "ArrowLeft" || key === "ArrowRight") {
19842
20862
  const d = (key === "ArrowLeft" ? -1 : 1) * (e.shiftKey ? 2 : 0.5);
19843
20863
  if (seekBy(d))
19844
20864
  claim();
19845
20865
  return;
19846
20866
  }
19847
- if (key === "n") {
20867
+ if (key === "n" && noteMode !== "hidden") {
19848
20868
  claim();
19849
- bar.input.focus({ preventScroll: true });
20869
+ bar.setOpen(true, true);
19850
20870
  return;
19851
20871
  }
19852
20872
  const v = viewOf(current);
19853
- if (!v || e.shiftKey)
20873
+ if (!v || e.shiftKey || !review)
19854
20874
  return;
19855
20875
  const choice = digitChoice(v.step, state.steps[v.step.key], key);
19856
20876
  if (choice === undefined)
@@ -19925,6 +20945,12 @@ function walkthrough(container, options) {
19925
20945
  win.removeEventListener(type, stopScroll);
19926
20946
  doc.removeEventListener("keydown", onKey);
19927
20947
  unregisterHints();
20948
+ bar.unobserve();
20949
+ pill?.dispose();
20950
+ for (const d of discs)
20951
+ d.dispose();
20952
+ if (padded)
20953
+ padded.el.style.scrollPaddingBottom = padded.was;
19928
20954
  for (const v of views) {
19929
20955
  v.slots.forEach((s) => s.dispose());
19930
20956
  v.taste?.dispose();