@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-solid.js CHANGED
@@ -595,6 +595,8 @@ var compareCss = `
595
595
  .knobs-cmp-stage{position:relative;flex:1;min-height:96px;overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_ku-cmp-stage);touch-action:pan-y;outline:none}
596
596
  .knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
597
597
  .knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
598
+ /* Videos: a click on the stage plays and pauses. */
599
+ .knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
598
600
  /* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
599
601
  .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
600
602
  .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
@@ -604,14 +606,28 @@ var compareCss = `
604
606
  .knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
605
607
  .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}
606
608
  .knobs-compare[data-renderer="gpu"] .knobs-cmp-media{opacity:0}
609
+ /* The media comes in with its first frames: a fade and a 4px blur on the default spring. */
610
+ .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)}
611
+ .knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}
607
612
  /* Glass: what's behind shows through, blurred, on the knobs tokens. */
608
613
  .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);
609
614
  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)))}
610
- .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)}
611
- /* The wipe: a hairline down the line with a raised grip at the pointer's height; a wider strip
612
- takes the hand and the keys (a slider). It fades with the mode; wiping, line and grip brighten. */
613
- .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)}
614
- .knobs-cmp-handle[data-on]{opacity:1;pointer-events:auto;transition:opacity .22s cubic-bezier(.2,.8,.2,1)}
615
+ /* ✗ Before · ✓ After: Before quieter (fg-3), After in full text. Neutral: no colour of its own. */
616
+ .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);
617
+ white-space:nowrap;pointer-events:none;will-change:transform}
618
+ .knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
619
+ .knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
620
+ /* Fusing, the mark folds away and the label is its name. */
621
+ .knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}
622
+ .knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}
623
+ /* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */
624
+ .knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;
625
+ -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)}
626
+ /* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height
627
+ while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the
628
+ fuse; wiping, line and grip brighten. */
629
+ .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}
630
+ .knobs-cmp-handle[data-on]{pointer-events:auto}
615
631
  .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)}
616
632
  .knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
617
633
  .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);
@@ -643,6 +659,27 @@ var compareCss = `
643
659
  @media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}
644
660
  `;
645
661
 
662
+ // src/ui/player-css.ts
663
+ var playerCss = `
664
+ :where(.knobs-player,.knobs-player *){box-sizing:border-box}
665
+ .knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
666
+ font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
667
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-player,.knobs-ui.knobs-player[data-nested]{--_kp-stage:var(--k-compare-stage,transparent)}
668
+ .knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage);cursor:pointer}
669
+ .knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
670
+ .knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
671
+ opacity:0;filter:blur(4px);transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
672
+ .knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
673
+ .knobs-player-glyph{position:absolute;left:50%;top:50%;display:grid;place-items:center;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;color:var(--_k-fg);pointer-events:none;
674
+ opacity:0;filter:blur(4px);scale:.96;
675
+ background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
676
+ box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
677
+ transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),filter calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
678
+ .knobs-player-glyph svg{width:16px;height:16px}
679
+ .knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
680
+ .knobs-player-line{flex:none;min-height:0}
681
+ `;
682
+
646
683
  // src/ui/overlay-css.ts
647
684
  var overlayCss = `
648
685
  .knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}
@@ -1331,6 +1368,22 @@ var init_schema = __esm(() => {
1331
1368
  source: css("var(--k-warn)"),
1332
1369
  doc: "Modified lines."
1333
1370
  },
1371
+ {
1372
+ name: "before",
1373
+ tier: "semantic",
1374
+ type: "color",
1375
+ group: "Data",
1376
+ source: css("var(--k-fg-3)"),
1377
+ doc: "Before in a comparison, and only Before: its label and side of the seam. Neutral unless a theme gives it a colour."
1378
+ },
1379
+ {
1380
+ name: "after",
1381
+ tier: "semantic",
1382
+ type: "color",
1383
+ group: "Data",
1384
+ source: css("var(--k-fg)"),
1385
+ doc: "After in a comparison, and only After."
1386
+ },
1334
1387
  {
1335
1388
  name: "series-1",
1336
1389
  tier: "semantic",
@@ -1421,6 +1474,150 @@ var init_schema = __esm(() => {
1421
1474
  source: value("288px"),
1422
1475
  doc: "Panel width."
1423
1476
  },
1477
+ {
1478
+ name: "space-inside",
1479
+ tier: "semantic",
1480
+ type: "length",
1481
+ group: "Space",
1482
+ source: css("var(--k-unit)"),
1483
+ doc: "Parts of one thing: an icon and its label, a meta line and its title."
1484
+ },
1485
+ {
1486
+ name: "space-sibling",
1487
+ tier: "semantic",
1488
+ type: "length",
1489
+ group: "Space",
1490
+ source: css("calc(var(--k-unit) * 2)"),
1491
+ doc: "Siblings in a group: buttons in a row, a list item's lines."
1492
+ },
1493
+ {
1494
+ name: "space-title",
1495
+ tier: "semantic",
1496
+ type: "length",
1497
+ group: "Space",
1498
+ source: css("calc(var(--k-unit) * 3)"),
1499
+ doc: "A heading and what it heads."
1500
+ },
1501
+ {
1502
+ name: "space-group",
1503
+ tier: "semantic",
1504
+ type: "length",
1505
+ group: "Space",
1506
+ source: css("calc(var(--k-unit) * 12)"),
1507
+ doc: "Groups inside a section (prose, video, diff), stacked media, feed items (a hairline in the middle)."
1508
+ },
1509
+ {
1510
+ name: "space-section",
1511
+ tier: "semantic",
1512
+ type: "length",
1513
+ group: "Space",
1514
+ source: css("calc(var(--k-unit) * 24)"),
1515
+ doc: "Sections of a page (the intro, the updates, each PR), a hairline in the middle."
1516
+ },
1517
+ {
1518
+ name: "space-column",
1519
+ tier: "semantic",
1520
+ type: "length",
1521
+ group: "Space",
1522
+ source: css("calc(var(--k-unit) * 6)"),
1523
+ doc: "Columns: a contents rail and the page, media side by side."
1524
+ },
1525
+ {
1526
+ name: "pad-page",
1527
+ tier: "semantic",
1528
+ type: "length",
1529
+ group: "Space",
1530
+ source: css("calc(var(--k-unit) * 6)"),
1531
+ doc: "A page's edge; 16px under 640px wide."
1532
+ },
1533
+ {
1534
+ name: "pad-card",
1535
+ tier: "semantic",
1536
+ type: "length",
1537
+ group: "Space",
1538
+ source: css("calc(var(--k-unit) * 3)"),
1539
+ doc: "Inside a card or popover."
1540
+ },
1541
+ {
1542
+ name: "type-heading",
1543
+ tier: "semantic",
1544
+ type: "length",
1545
+ group: "Type",
1546
+ source: value("20px"),
1547
+ doc: "A page's title, weight 500."
1548
+ },
1549
+ {
1550
+ name: "leading-heading",
1551
+ tier: "semantic",
1552
+ type: "length",
1553
+ group: "Type",
1554
+ source: value("28px"),
1555
+ doc: "Line height for type-heading."
1556
+ },
1557
+ {
1558
+ name: "type-title",
1559
+ tier: "semantic",
1560
+ type: "length",
1561
+ group: "Type",
1562
+ source: value("16px"),
1563
+ doc: "Section titles (under their kicker), weight 500."
1564
+ },
1565
+ {
1566
+ name: "leading-title",
1567
+ tier: "semantic",
1568
+ type: "length",
1569
+ group: "Type",
1570
+ source: value("24px"),
1571
+ doc: "Line height for type-title."
1572
+ },
1573
+ {
1574
+ name: "type-body",
1575
+ tier: "semantic",
1576
+ type: "length",
1577
+ group: "Type",
1578
+ source: value("13px"),
1579
+ doc: "Prose, questions, list rows, step titles; 400, or 500 for emphasis."
1580
+ },
1581
+ {
1582
+ name: "leading-body",
1583
+ tier: "semantic",
1584
+ type: "length",
1585
+ group: "Type",
1586
+ source: value("20px"),
1587
+ doc: "Line height for type-body."
1588
+ },
1589
+ {
1590
+ name: "type-label",
1591
+ tier: "semantic",
1592
+ type: "length",
1593
+ group: "Type",
1594
+ source: value("12px"),
1595
+ doc: "Mono inside body text: code and paths."
1596
+ },
1597
+ {
1598
+ name: "leading-label",
1599
+ tier: "semantic",
1600
+ type: "length",
1601
+ group: "Type",
1602
+ source: value("16px"),
1603
+ doc: "Line height for type-label."
1604
+ },
1605
+ {
1606
+ name: "type-small",
1607
+ tier: "semantic",
1608
+ type: "length",
1609
+ group: "Type",
1610
+ source: value("11px"),
1611
+ doc: "Meta, controls, keys, timestamps; mono inside labels."
1612
+ },
1613
+ {
1614
+ name: "leading-small",
1615
+ tier: "semantic",
1616
+ type: "length",
1617
+ group: "Type",
1618
+ source: value("16px"),
1619
+ doc: "Line height for type-small."
1620
+ },
1424
1621
  {
1425
1622
  name: "font-size",
1426
1623
  tier: "semantic",
@@ -2385,16 +2582,18 @@ function injectTokens(doc) {
2385
2582
  style.textContent = rootCss;
2386
2583
  doc.head.append(style);
2387
2584
  }
2388
- var ROOT_CLASS = "knobs-root", extras, spring, rootCss;
2585
+ var ROOT_CLASS = "knobs-root", extras, narrow, spring, rootCss;
2389
2586
  var init_root = __esm(() => {
2390
2587
  init_knobs();
2391
2588
  init_css();
2392
2589
  init_glide();
2393
2590
  init_motion();
2394
2591
  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))}`;
2592
+ narrow = `@media(max-width:639.98px){:where(.${ROOT_CLASS}){--_k-pad-page:var(--k-pad-page,calc(var(--_k-unit) * 4))}}`;
2395
2593
  spring = `:where(:root,:host){--k-fill-ease:${DEFAULT_LINEAR.easing};--k-fill-ms:${DEFAULT_LINEAR.ms}}`;
2396
2594
  rootCss = `${tokensCss(knobs)}
2397
2595
  ${extras}
2596
+ ${narrow}
2398
2597
  ${spring}
2399
2598
  ${DRIVERS}
2400
2599
  ${motionCss}`;
@@ -2605,6 +2804,12 @@ var css2 = `
2605
2804
  --_ku-pane-bg and --_ku-pane-ring so this reaches them. */
2606
2805
  :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;
2607
2806
  --_ku-divider:var(--_k-split-gutter);--_kd-bg:transparent}
2807
+ /* What draws its box without those: a picker's card keeps its fill as the highlight but drops its
2808
+ ring; a timeline's track drops its well for a hairline under the lane; an inline panel sheds its
2809
+ card (--k-chrome 0). A full-window step is the one raised exception (it is the surface). */
2810
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-picker-card,.knobs-ui[data-nested] .knobs-picker-card{box-shadow:none}
2811
+ :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)}
2812
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-panel.knobs-inline:not(.knobs-page){--k-chrome:0}
2608
2813
  /* Only the pieces' own elements take their type and resets: what you put in a pane is yours. */
2609
2814
  .knobs-ui{box-sizing:border-box}
2610
2815
  :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}
@@ -2830,8 +3035,6 @@ var css2 = `
2830
3035
  .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}
2831
3036
  .knobs-tl-track:hover .knobs-tl-hover{opacity:1}
2832
3037
  .knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
2833
- @media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
2834
- @media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
2835
3038
  .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}
2836
3039
  .knobs-tl-tag[data-on]{opacity:1}
2837
3040
  /* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
@@ -2847,7 +3050,26 @@ var css2 = `
2847
3050
  .knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
2848
3051
  .knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
2849
3052
  .knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
2850
- `, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + selectionPillCss) + rolling_css_default, lookDefaults, tune = (s, m) => {
3053
+ /* The line (variant: "line"): a player's progress under its media. One 2px line, fg-3 with fg
3054
+ filling it, a segment per chapter; it thickens under the pointer and while scrubbing. The
3055
+ chapter and time under the pointer float over it; nothing else shows. */
3056
+ .knobs-ui.knobs-timeline[data-variant="line"]{height:auto;min-height:0;padding:0;gap:0}
3057
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:none}
3058
+ .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}
3059
+ .knobs-timeline[data-variant="line"] :is(.knobs-tl-ruler,.knobs-tl-lane,.knobs-tl-hover,.knobs-tl-head,.knobs-tl-rlabel){display:none}
3060
+ .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)}
3061
+ .knobs-tl-seg{position:absolute;left:0;top:0;height:100%;overflow:hidden;border-radius:1px;background:var(--_k-fg-3);will-change:transform}
3062
+ .knobs-tl-seg-fill{position:absolute;inset:0;background:var(--_k-fg);transform-origin:0 0;will-change:transform}
3063
+ .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}
3064
+ .knobs-timeline[data-variant="line"] .knobs-tl-range{top:3px;bottom:3px;border-radius:3px}
3065
+ .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)}
3066
+ .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);
3067
+ -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:inset 0 0 0 .5px var(--_k-border)}
3068
+ .knobs-tl-tag-label{color:var(--_k-fg-2)}
3069
+ .knobs-tl-tag-label:empty{display:none}
3070
+ @media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
3071
+ @media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
3072
+ `, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + playerCss + selectionPillCss) + rolling_css_default, lookDefaults, tune = (s, m) => {
2851
3073
  s.omega = m.omega;
2852
3074
  s.zeta = m.zeta;
2853
3075
  return s;
@@ -2970,19 +3192,19 @@ var init_diff_css = __esm(() => {
2970
3192
  @property --kd-fb{syntax:"<length>";inherits:false;initial-value:0px}
2971
3193
  :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))}
2972
3194
  :where(.knobs-diff,.knobs-diff *,.knobs-diff-files,.knobs-diff-files *){box-sizing:border-box}
2973
- .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);
3195
+ .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);
2974
3196
  --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);
2975
3197
  --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;
2976
3198
  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;
2977
3199
  letter-spacing:normal;word-spacing:normal;text-transform:none;-webkit-font-smoothing:antialiased}
2978
3200
  .knobs-ui.knobs-diff[data-height="auto"]{height:auto}
2979
- .knobs-diff::after{content:"";position:absolute;inset:0;z-index:3;border-radius:inherit;box-shadow:var(--_ku-pane-ring);pointer-events:none}
3201
+ .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}
2980
3202
  .knobs-diff [hidden],.knobs-diff-files [hidden]{display:none!important}
2981
3203
  .knobs-diff svg{display:block;flex:none}
2982
- .knobs-ui.knobs-diff[data-on="surface"]{--kd-x:0px;--kd-head:var(--_k-control-h);--kd-ctl:var(--_k-radius)}
2983
- /* The header's hairline shows only once the code has scrolled under it. */
3204
+ .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)}
3205
+ /* A hairline under the header, firmer once the code has scrolled under it. */
2984
3206
  .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);
2985
- box-shadow:inset 0 -1px transparent;transition:box-shadow .15s;user-select:none;-webkit-user-select:none}
3207
+ box-shadow:inset 0 -1px var(--_k-border-subtle);transition:box-shadow .15s;user-select:none;-webkit-user-select:none}
2986
3208
  .knobs-diff[data-scrolled]>.knobs-diff-head{box-shadow:inset 0 -1px var(--_k-border)}
2987
3209
  .knobs-diff-path{flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap}
2988
3210
  .knobs-diff-dir,.knobs-diff-from{color:var(--_k-fg-3)}
@@ -3020,6 +3242,15 @@ var init_diff_css = __esm(() => {
3020
3242
  .knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
3021
3243
  .knobs-diff-viewed svg{width:16px;height:16px}
3022
3244
  .knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
3245
+ /* Narrow, the header gives up words before anything is cut off at its end: Viewed keeps its
3246
+ check, then the counts' bar goes, then the counts; the path fades at its end throughout. */
3247
+ .knobs-diff-head,.knobs-diff-part-head{container-type:inline-size}
3248
+ @container (max-width:520px){
3249
+ .knobs-diff-viewed-label{display:none}
3250
+ .knobs-ui .knobs-diff-viewed{width:var(--_k-control-h);padding:0;justify-content:center}
3251
+ .knobs-diff-counts .knobs-diff-bar{display:none}
3252
+ }
3253
+ @container (max-width:400px){.knobs-diff-counts{display:none}.knobs-diff-nav>span{min-width:2.6em}}
3023
3254
  .knobs-diff-path{transition:opacity .2s}
3024
3255
  .knobs-diff[data-viewed]>.knobs-diff-head .knobs-diff-path,.knobs-diff-part[data-viewed] .knobs-diff-path{opacity:.6}
3025
3256
  /* Split | Unified: the selection pill on the one picked, its bright copy the picked one's text; a
@@ -3084,24 +3315,26 @@ var init_diff_css = __esm(() => {
3084
3315
  --diffs-bg-deletion-number-override:color-mix(in srgb,var(--kd-del) 55%,var(--kd-solid));
3085
3316
  --diffs-bg-addition-emphasis-override:color-mix(in srgb,var(--kd-add) 22%,transparent);
3086
3317
  --diffs-bg-deletion-emphasis-override:color-mix(in srgb,var(--kd-del) 22%,transparent);
3087
- --diffs-bg-separator-override:color-mix(in srgb,var(--_k-fg) 4.5%,var(--kd-solid));
3088
- --diffs-bg-context-override:color-mix(in srgb,var(--_k-fg) 3%,var(--kd-solid));
3089
- --diffs-bg-buffer-override:color-mix(in srgb,var(--_k-fg) 3.5%,var(--kd-solid));
3318
+ /* Unchanged lines and the folds between hunks sit on the void; only a split's missing lines
3319
+ are faintly filled. */
3320
+ --diffs-bg-separator-override:var(--kd-solid);
3321
+ --diffs-bg-context-override:var(--kd-solid);
3322
+ --diffs-bg-buffer-override:color-mix(in srgb,var(--_k-fg) 2.5%,var(--kd-solid));
3090
3323
  --diffs-bg-selection-override:var(--_k-accent);
3091
3324
  --diffs-fg-number-override:var(--_k-fg-3);
3092
3325
  }
3093
- /* diffReview: one card, the file list a drawer at its left (its width springs; the list inside
3094
- is pinned to its right edge, so it slides out from under the diff), parted from the diff by a
3095
- hairline. The diff in it draws no card of its own. */
3096
- .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);
3326
+ /* diffReview, on the void as the diff is: the file list a drawer at its left (its width springs;
3327
+ the list inside is pinned to its right edge, so it slides out from under the diff), parted
3328
+ from the diff by a hairline; one hairline runs under both headers and one under it all. */
3329
+ .knobs-ui.knobs-diff-review{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;
3097
3330
  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}
3098
- .knobs-diff-review::after{content:"";position:absolute;inset:0;z-index:5;border-radius:inherit;box-shadow:var(--_ku-pane-ring);pointer-events:none}
3331
+ .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}
3099
3332
  :where(.knobs-diff-review,.knobs-diff-review *){box-sizing:border-box}
3100
- .knobs-diff-drawer{position:relative;flex:none;width:0;height:100%;overflow:hidden;box-shadow:inset -1px 0 var(--_k-border)}
3333
+ .knobs-diff-drawer{position:relative;flex:none;width:0;height:100%;overflow:hidden;box-shadow:inset -1px 0 var(--_k-border-subtle)}
3101
3334
  .knobs-diff-drawer[data-shut]{visibility:hidden}
3102
3335
  .knobs-diff-drawer-in{position:absolute;top:0;bottom:0;right:0;display:flex;flex-direction:column}
3103
3336
  .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);
3104
- user-select:none;-webkit-user-select:none}
3337
+ box-shadow:inset 0 -1px var(--_k-border-subtle);user-select:none;-webkit-user-select:none}
3105
3338
  .knobs-diff-drawer-title{font-weight:var(--_k-weight-title)}
3106
3339
  .knobs-diff-drawer-count{color:var(--_k-fg-3);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums}
3107
3340
  .knobs-diff-drawer-in>.knobs-diff-files{flex:1;min-height:0;height:auto}
@@ -3113,10 +3346,11 @@ var init_diff_css = __esm(() => {
3113
3346
  .knobs-ui .knobs-diff-toggle[aria-pressed]:not(:hover,:focus-visible){background:transparent}
3114
3347
  /* diffReview's file list: a filter over a compact tree, a status letter, the counts and their
3115
3348
  bar on each file; one highlight glides to the file on show. Viewed files dim, with a tick. */
3116
- .knobs-diff-files{--k-row:var(--k-diff-row,28px);display:flex;flex-direction:column;height:100%;min-height:0;padding-top:2px}
3349
+ .knobs-diff-files{--k-row:var(--k-diff-row,28px);display:flex;flex-direction:column;height:100%;min-height:0;padding-top:4px}
3117
3350
  .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;
3118
- 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}
3119
- .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)}
3351
+ 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}
3352
+ /* The filter is a line of text, no box; focused, a hairline ring. */
3353
+ .knobs-diff-filter:focus-within{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_k-accent) 45%,transparent)}
3120
3354
  .knobs-diff-filter svg{width:12px;height:12px;flex:none}
3121
3355
  .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}
3122
3356
  .knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
@@ -6041,7 +6275,10 @@ function codeDiff(container, options) {
6041
6275
  const viewedToggle = () => {
6042
6276
  const b = make("button", "knobs-diff-viewed");
6043
6277
  b.type = "button";
6044
- b.append(make("span", ""), "Viewed");
6278
+ const label = make("span", "knobs-diff-viewed-label");
6279
+ label.textContent = "Viewed";
6280
+ b.append(make("span", ""), label);
6281
+ b.setAttribute("aria-label", "Viewed");
6045
6282
  return b;
6046
6283
  };
6047
6284
  const paintViewed = (b, on) => {
@@ -8328,6 +8565,17 @@ function currentMarker(markers, t, fps) {
8328
8565
  best = m;
8329
8566
  return best;
8330
8567
  }
8568
+ function minutes(t) {
8569
+ const s = Math.max(0, Math.floor(t + 0.000001));
8570
+ const h = Math.floor(s / 3600);
8571
+ const m = Math.floor(s % 3600 / 60);
8572
+ const ss = String(s % 60).padStart(2, "0");
8573
+ return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
8574
+ }
8575
+ function chapterBounds(times, duration) {
8576
+ const inside = times.filter((t) => t > 0.000001 && t < duration - 0.000001);
8577
+ return [0, ...[...new Set(inside)].sort((a, b) => a - b), duration];
8578
+ }
8331
8579
  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;
8332
8580
  var init_timeline_model = __esm(() => {
8333
8581
  SECOND_STEPS = [0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600];
@@ -8345,7 +8593,8 @@ function timeline(container, options) {
8345
8593
  let speed = options.speed ?? 1;
8346
8594
  let repeat = options.repeat ?? true;
8347
8595
  const arrows = options.arrows ?? "frames";
8348
- const lane = options.variant === "lane";
8596
+ const line = options.variant === "line";
8597
+ const lane = line || options.variant === "lane";
8349
8598
  let time = Math.min(duration, Math.max(0, options.time ?? 0));
8350
8599
  let playing = false;
8351
8600
  let markers = [...options.markers ?? []];
@@ -8380,7 +8629,7 @@ function timeline(container, options) {
8380
8629
  root.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight , . Home End [ ] I O M");
8381
8630
  applyLook2(root, options);
8382
8631
  if (lane)
8383
- root.dataset.variant = "lane";
8632
+ root.dataset.variant = line ? "line" : "lane";
8384
8633
  const transport = el("div", "knobs-tl-transport");
8385
8634
  const playBtn = el("button", "knobs-tl-play");
8386
8635
  playBtn.type = "button";
@@ -8393,6 +8642,10 @@ function timeline(container, options) {
8393
8642
  const hover = el("div", "knobs-tl-hover");
8394
8643
  const tag = el("span", "knobs-tl-tag");
8395
8644
  tag.setAttribute("aria-hidden", "true");
8645
+ const tagLabel = el("span", "knobs-tl-tag-label");
8646
+ const tagTime = el("span", "knobs-tl-tag-time");
8647
+ tag.append(tagLabel, tagTime);
8648
+ const segs = el("div", "knobs-tl-segs");
8396
8649
  const head = el("div", "knobs-tl-head");
8397
8650
  head.id = `${id}-head`;
8398
8651
  head.setAttribute("role", "slider");
@@ -8400,11 +8653,13 @@ function timeline(container, options) {
8400
8653
  head.setAttribute("aria-valuemin", "0");
8401
8654
  head.setAttribute("aria-valuemax", String(duration));
8402
8655
  track.append(canvas, markerLane, hover, head, tag);
8656
+ if (line)
8657
+ track.insertBefore(segs, markerLane);
8403
8658
  root.append(transport, track);
8404
8659
  container.append(root);
8405
8660
  let width = 1;
8406
8661
  let height = 1;
8407
- const fitView = () => clampView({ start: 0, span: duration }, duration, { pad: 0.02 });
8662
+ const fitView = () => clampView({ start: 0, span: duration }, duration, { pad: line ? 0 : 0.02 });
8408
8663
  let target = fitView();
8409
8664
  const vStart = new Spring(target.start, INSTANT2.omega, 1, 0.00001);
8410
8665
  const vSpan = new Spring(target.span, INSTANT2.omega, 1, 0.00001);
@@ -8413,13 +8668,13 @@ function timeline(container, options) {
8413
8668
  const xOf = (t) => (t - vStart.x) * pxPerSec();
8414
8669
  const tOf = (x) => vStart.x + x / pxPerSec();
8415
8670
  const minSpan = () => Math.max(4 / fps, 0.05);
8416
- const retarget = (next, motion = INSTANT2) => {
8417
- target = clampView(next, duration, { pad: 0.04, minSpan: minSpan() });
8671
+ const retarget = (next, motion = INSTANT2, hand = false) => {
8672
+ target = clampView(next, duration, { pad: line ? 0 : 0.04, minSpan: minSpan() });
8418
8673
  for (const s of [vStart, vSpan]) {
8419
8674
  s.omega = motion.omega;
8420
8675
  s.zeta = motion.zeta;
8421
8676
  }
8422
- if (reducedMotion(win) && motion === CALM) {
8677
+ if (hand || reducedMotion(win) && motion === CALM) {
8423
8678
  vStart.snap(target.start);
8424
8679
  vSpan.snap(target.span);
8425
8680
  } else {
@@ -8445,6 +8700,8 @@ function timeline(container, options) {
8445
8700
  };
8446
8701
  let drawn = "";
8447
8702
  const drawRuler = () => {
8703
+ if (line)
8704
+ return;
8448
8705
  const v = view();
8449
8706
  const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${height}:${units}:${fps}`;
8450
8707
  if (key === drawn)
@@ -8532,6 +8789,8 @@ function timeline(container, options) {
8532
8789
  n.title = `${m.label ? `${m.label} · ` : ""}${clock(m.time)}`;
8533
8790
  }
8534
8791
  placeMarkers();
8792
+ if (line)
8793
+ syncSegments();
8535
8794
  };
8536
8795
  const placeMarkers = () => {
8537
8796
  for (const m of markers) {
@@ -8540,6 +8799,36 @@ function timeline(container, options) {
8540
8799
  n.style.transform = `translateX(${xOf(m.time).toFixed(2)}px)`;
8541
8800
  }
8542
8801
  };
8802
+ let bounds = [];
8803
+ const segEls = [];
8804
+ const syncSegments = () => {
8805
+ bounds = chapterBounds(markers.map((m) => m.time), duration);
8806
+ while (segEls.length > bounds.length - 1)
8807
+ segEls.pop().el.remove();
8808
+ while (segEls.length < bounds.length - 1) {
8809
+ const n = el("i", "knobs-tl-seg");
8810
+ const fill = el("b", "knobs-tl-seg-fill");
8811
+ n.append(fill);
8812
+ segs.append(n);
8813
+ segEls.push({ el: n, fill });
8814
+ }
8815
+ placeSegments();
8816
+ };
8817
+ const placeSegments = () => {
8818
+ if (!line)
8819
+ return;
8820
+ const last = segEls.length - 1;
8821
+ segEls.forEach((s, i) => {
8822
+ const a = bounds[i];
8823
+ const b = bounds[i + 1];
8824
+ const x0 = xOf(a) + (i > 0 ? SEG_GAP / 2 : 0);
8825
+ const x1 = xOf(b) - (i < last ? SEG_GAP / 2 : 0);
8826
+ s.el.style.transform = `translateX(${x0.toFixed(2)}px)`;
8827
+ s.el.style.width = `${Math.max(0, x1 - x0).toFixed(2)}px`;
8828
+ const k = Math.min(1, Math.max(0, (shownHead - a) / Math.max(0.000000001, b - a)));
8829
+ s.fill.style.transform = `scaleX(${k.toFixed(4)})`;
8830
+ });
8831
+ };
8543
8832
  const syncRanges = () => {
8544
8833
  const keep = new Set(ranges.map((r) => r.id));
8545
8834
  for (const [k, n] of rangeEls)
@@ -8556,7 +8845,7 @@ function timeline(container, options) {
8556
8845
  b.dataset.edge = "end";
8557
8846
  const label = el("span", "knobs-tl-rlabel");
8558
8847
  n.append(label, a, b);
8559
- track.insertBefore(n, markerLane);
8848
+ track.insertBefore(n, line ? segs : markerLane);
8560
8849
  rangeEls.set(r.id, n);
8561
8850
  if (built)
8562
8851
  enter(n);
@@ -8608,6 +8897,7 @@ function timeline(container, options) {
8608
8897
  let currentId = null;
8609
8898
  const renderHead = () => {
8610
8899
  head.style.transform = `translateX(${xOf(shownHead).toFixed(2)}px)`;
8900
+ placeSegments();
8611
8901
  head.setAttribute("aria-valuenow", time.toFixed(3));
8612
8902
  head.setAttribute("aria-valuetext", `${time.toFixed(2)} seconds, frame ${toFrame(time, fps)}`);
8613
8903
  const cur = currentMarker(markers, time, fps)?.id ?? null;
@@ -8871,14 +9161,25 @@ function timeline(container, options) {
8871
9161
  setTime(s.t, "scrub", s.shown, press);
8872
9162
  root.toggleAttribute("data-stuck", s.stop !== null);
8873
9163
  };
9164
+ let pointerX = null;
9165
+ const zoomAtHand = (factor) => zoom(factor, pointerX === null ? time : target.start + pointerX / width * target.span);
8874
9166
  const showTag = (x) => {
8875
9167
  const at = Math.min(duration, Math.max(0, tOf(x)));
8876
- const text = `${at.toFixed(2)} s`;
8877
- if (tag.textContent !== text)
8878
- tag.textContent = text;
9168
+ const text = line ? minutes(at) : `${at.toFixed(2)} s`;
9169
+ if (tagTime.textContent !== text)
9170
+ tagTime.textContent = text;
9171
+ const label = line ? currentMarker(markers, at, fps)?.label ?? "" : "";
9172
+ if (tagLabel.textContent !== label)
9173
+ tagLabel.textContent = label;
8879
9174
  const cx = Math.min(width, Math.max(0, x));
8880
- const flip = cx > width - tag.offsetWidth - 12;
8881
- tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
9175
+ if (line) {
9176
+ const w = tag.offsetWidth;
9177
+ const left = Math.min(Math.max(0, width - w), Math.max(0, cx - w / 2));
9178
+ tag.style.transform = `translateX(${left.toFixed(1)}px)`;
9179
+ } else {
9180
+ const flip = cx > width - tag.offsetWidth - 12;
9181
+ tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
9182
+ }
8882
9183
  tag.dataset.on = "";
8883
9184
  };
8884
9185
  const hideTag = () => delete tag.dataset.on;
@@ -8939,6 +9240,7 @@ function timeline(container, options) {
8939
9240
  });
8940
9241
  track.addEventListener("pointermove", (e) => {
8941
9242
  const x = localX(e);
9243
+ pointerX = x;
8942
9244
  hover.style.transform = `translateX(${x.toFixed(1)}px)`;
8943
9245
  if (!drag || drag.kind === "scrub")
8944
9246
  showTag(x);
@@ -9012,6 +9314,7 @@ function timeline(container, options) {
9012
9314
  track.addEventListener("pointercancel", endDrag);
9013
9315
  track.addEventListener("lostpointercapture", endDrag);
9014
9316
  track.addEventListener("pointerleave", () => {
9317
+ pointerX = null;
9015
9318
  if (drag)
9016
9319
  return;
9017
9320
  hover.style.transform = "translateX(-100px)";
@@ -9019,7 +9322,7 @@ function timeline(container, options) {
9019
9322
  });
9020
9323
  track.addEventListener("dblclick", (e) => {
9021
9324
  if (lane)
9022
- return;
9325
+ return fit();
9023
9326
  const markerEl = e.target.closest(".knobs-tl-marker");
9024
9327
  if (markerEl)
9025
9328
  return rename(markerEl.dataset.id);
@@ -9030,18 +9333,40 @@ function timeline(container, options) {
9030
9333
  rename(m.id);
9031
9334
  });
9032
9335
  track.addEventListener("wheel", (e) => {
9033
- if (lane)
9336
+ const zooming = e.ctrlKey || e.metaKey;
9337
+ if (zooming && pinching)
9338
+ return e.preventDefault();
9339
+ const sideways = Math.abs(e.deltaX) > Math.abs(e.deltaY);
9340
+ if (lane && !zooming && !sideways)
9034
9341
  return;
9035
9342
  e.preventDefault();
9036
9343
  const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? height : 1;
9037
- if (e.ctrlKey || e.metaKey) {
9344
+ if (zooming) {
9038
9345
  const at = target.start + localX(e) / width * target.span;
9039
- retarget(zoomView(target, Math.exp(-e.deltaY * scale * (e.ctrlKey ? 0.01 : 0.002)), at));
9346
+ const k = Math.exp(-e.deltaY * scale * (e.ctrlKey ? 0.01 : 0.002));
9347
+ retarget(zoomView(target, k, at), INSTANT2, lane);
9040
9348
  } else {
9041
- const d = (Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY) * scale;
9042
- retarget({ ...target, start: target.start + d / width * target.span });
9349
+ const d = (sideways ? e.deltaX : e.deltaY) * scale;
9350
+ retarget({ ...target, start: target.start + d / width * target.span }, INSTANT2, lane);
9043
9351
  }
9044
9352
  }, { passive: false });
9353
+ let pinchScale = 1;
9354
+ let pinching = false;
9355
+ const onGesture = (e) => {
9356
+ const g = e;
9357
+ e.preventDefault();
9358
+ if (e.type === "gesturestart")
9359
+ pinchScale = 1;
9360
+ pinching = e.type !== "gestureend";
9361
+ if (e.type !== "gesturechange")
9362
+ return;
9363
+ const s = g.scale ?? 1;
9364
+ const x = g.clientX !== undefined ? localX({ clientX: g.clientX }) : width / 2;
9365
+ retarget(zoomView(target, s / pinchScale, target.start + x / width * target.span), INSTANT2, true);
9366
+ pinchScale = s;
9367
+ };
9368
+ for (const type of ["gesturestart", "gesturechange", "gestureend"])
9369
+ track.addEventListener(type, onGesture);
9045
9370
  root.addEventListener("keydown", (e) => {
9046
9371
  if (e.target !== root || e.metaKey || e.ctrlKey) {
9047
9372
  if ((e.metaKey || e.ctrlKey) && (e.key === "=" || e.key === "-" || e.key === "0") && e.target === root) {
@@ -9049,7 +9374,7 @@ function timeline(container, options) {
9049
9374
  if (e.key === "0")
9050
9375
  fit();
9051
9376
  else
9052
- zoom(e.key === "=" ? 1.5 : 1 / 1.5);
9377
+ zoomAtHand(e.key === "=" ? 1.5 : 1 / 1.5);
9053
9378
  }
9054
9379
  return;
9055
9380
  }
@@ -9131,18 +9456,12 @@ function timeline(container, options) {
9131
9456
  return;
9132
9457
  case "=":
9133
9458
  case "+":
9134
- if (lane)
9135
- return;
9136
9459
  claim();
9137
- return zoom(1.5);
9460
+ return zoomAtHand(1.5);
9138
9461
  case "-":
9139
- if (lane)
9140
- return;
9141
9462
  claim();
9142
- return zoom(1 / 1.5);
9463
+ return zoomAtHand(1 / 1.5);
9143
9464
  case "0":
9144
- if (lane)
9145
- return;
9146
9465
  claim();
9147
9466
  return fit();
9148
9467
  }
@@ -9358,7 +9677,7 @@ function timeline(container, options) {
9358
9677
  };
9359
9678
  return api;
9360
9679
  }
9361
- 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>';
9680
+ 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>';
9362
9681
  var init_timeline = __esm(() => {
9363
9682
  init_rgba();
9364
9683
  init_motion();
@@ -9430,15 +9749,28 @@ function compareLayout(box, before, after) {
9430
9749
  const shape = after.w > 0 && after.h > 0 ? after : before;
9431
9750
  const pair = letterbox({ w: shape.w * 2, h: shape.h }, box);
9432
9751
  const half = pair.w / 2;
9752
+ const fused = { x: pair.x + half / 2, y: pair.y, w: half, h: pair.h };
9433
9753
  return {
9434
9754
  side: {
9435
9755
  before: letterbox(before, { x: pair.x, y: pair.y, w: half, h: pair.h }),
9436
9756
  after: letterbox(after, { x: pair.x + half, y: pair.y, w: half, h: pair.h }),
9437
9757
  split: pair.x + half
9438
9758
  },
9439
- overlay: { before: letterbox(before, box), after: letterbox(after, box) }
9759
+ overlay: { before: letterbox(before, fused), after: letterbox(after, fused) }
9440
9760
  };
9441
9761
  }
9762
+ function fuseFrame(L, before, after, k, wipe) {
9763
+ const t = Math.min(1, Math.max(0, k));
9764
+ const o = L.overlay;
9765
+ const left = Math.min(o.before.x, o.after.x);
9766
+ 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);
9767
+ const cut = L.side.split + (line - L.side.split) * t;
9768
+ const contact = Math.max(0, Math.min(cut - after.x, before.x + before.w - cut));
9769
+ const feather = Math.min(contact, 1 + FUSE.feather * Math.sin(Math.PI * t));
9770
+ const overlap = before.x + before.w - after.x;
9771
+ const seam = t > 0.001 && overlap >= 0 ? Math.min(1, Math.max(0, overlap / FUSE.seamFade)) * Math.min(1, t * 4) : 0;
9772
+ return { cut, feather, seam, blur: t < 0.995 ? FUSE.blur * (1 - t) : 0 };
9773
+ }
9442
9774
  function clampZoom(r) {
9443
9775
  const w = Math.min(1, Math.max(1 / ZOOM_MAX, r.w));
9444
9776
  const u = Math.min(1 - w, Math.max(0, r.u));
@@ -9524,7 +9856,15 @@ function stripPeaks(diffs, o = {}) {
9524
9856
  });
9525
9857
  return out;
9526
9858
  }
9527
- 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({
9859
+ var ALL_MODES, MODE_KEYS, modeKey = (mode) => MODE_KEYS[mode].toUpperCase(), VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE, FUSE, labelFold = (fold, mark, blur = 3) => {
9860
+ const f = Math.min(1, Math.max(0, fold));
9861
+ const open = 1 - f;
9862
+ return {
9863
+ width: mark * open,
9864
+ opacity: Math.min(1, Math.max(0, (open - 0.2) / 0.8)),
9865
+ blur: blur * Math.min(1, Math.max(0, (f - 0.25) / 0.75))
9866
+ };
9867
+ }, 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({
9528
9868
  u: r.u - dx / Math.max(1, base.w) * r.w,
9529
9869
  v: r.v - dy / Math.max(1, base.h) * r.h,
9530
9870
  w: r.w
@@ -9547,6 +9887,7 @@ var init_compare_model = __esm(() => {
9547
9887
  MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
9548
9888
  VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
9549
9889
  SEEK_IDLE = { seeking: false, pending: null };
9890
+ FUSE = { feather: 24, blur: 4, seamFade: 48 };
9550
9891
  ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
9551
9892
  });
9552
9893
 
@@ -10441,11 +10782,6 @@ fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
10441
10782
 
10442
10783
  fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
10443
10784
  switch mode {
10444
- case 1u: {
10445
- let s = max(u.wipe.y, 0.5);
10446
- let m = smoothstep(u.wipe.x - s, u.wipe.x + s, p.x);
10447
- return pm(vec4f(mix(a.rgb, b.rgb, m), mix(a.a, b.a, m)));
10448
- }
10449
10785
  case 2u: {
10450
10786
  let d = abs(a.rgb - b.rgb);
10451
10787
  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);
@@ -10454,8 +10790,10 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
10454
10790
  }
10455
10791
  default: {}
10456
10792
  }
10457
- // Side by side: each side in its own place, Before over After where they meet.
10458
- return vec4f(a.rgb * a.a + b.rgb * b.a * (1.0 - a.a), a.a + b.a * (1.0 - a.a));
10793
+ // Side by side, fusing and wiping: Before over After, cut at the seam with its feather.
10794
+ let s = max(u.wipe.y, 0.5);
10795
+ let ka = a.a * (1.0 - smoothstep(u.wipe.x - s, u.wipe.x + s, p.x));
10796
+ return vec4f(a.rgb * ka + b.rgb * b.a * (1.0 - ka), ka + b.a * (1.0 - ka));
10459
10797
  }
10460
10798
 
10461
10799
  @fragment fn fs(@builtin(position) q: vec4f) -> @location(0) vec4f {
@@ -10857,6 +11195,29 @@ var init_compare_strip = __esm(() => {
10857
11195
  DIFF = { w: 32, h: 18 };
10858
11196
  });
10859
11197
 
11198
+ // src/ui/player.ts
11199
+ function playGlyph(doc) {
11200
+ const element = doc.createElement("span");
11201
+ element.className = "knobs-player-glyph";
11202
+ element.setAttribute("aria-hidden", "true");
11203
+ let timer = 0;
11204
+ const win = doc.defaultView;
11205
+ return {
11206
+ element,
11207
+ flash(playing) {
11208
+ element.innerHTML = playing ? PLAY2 : PAUSE;
11209
+ element.setAttribute("data-flash", "");
11210
+ win.clearTimeout(timer);
11211
+ timer = win.setTimeout(() => element.removeAttribute("data-flash"), FLASH_MS);
11212
+ },
11213
+ dispose: () => win.clearTimeout(timer)
11214
+ };
11215
+ }
11216
+ var FLASH_MS = 450;
11217
+ var init_player = __esm(() => {
11218
+ init_timeline();
11219
+ });
11220
+
10860
11221
  // src/ui/compare.ts
10861
11222
  function compare(container, options) {
10862
11223
  const doc = container.ownerDocument;
@@ -10953,6 +11314,19 @@ function compare(container, options) {
10953
11314
  const labels = [el("div", "knobs-cmp-label"), el("div", "knobs-cmp-label")];
10954
11315
  labels[0].dataset.side = "before";
10955
11316
  labels[1].dataset.side = "after";
11317
+ const setLabel = (index, text) => {
11318
+ const l = labels[index];
11319
+ if (!l.firstElementChild) {
11320
+ const mark = el("span", "knobs-cmp-mark");
11321
+ mark.innerHTML = icon(index === 0 ? "x" : "check", { small: true });
11322
+ l.append(mark, el("span", "knobs-cmp-name"));
11323
+ }
11324
+ const name = l.lastElementChild;
11325
+ if (name.textContent !== text)
11326
+ name.textContent = text;
11327
+ };
11328
+ const seam = el("div", "knobs-cmp-seam");
11329
+ seam.setAttribute("aria-hidden", "true");
10956
11330
  const handle = el("div", "knobs-cmp-handle");
10957
11331
  handle.setAttribute("role", "slider");
10958
11332
  handle.setAttribute("aria-label", "Wipe");
@@ -10962,7 +11336,8 @@ function compare(container, options) {
10962
11336
  grip.setAttribute("aria-hidden", "true");
10963
11337
  handle.append(grip);
10964
11338
  const note = el("div", "knobs-cmp-note");
10965
- stage.append(canvas, split, labels[0], labels[1], handle, zoomPill, note);
11339
+ const glyph = playGlyph(doc);
11340
+ stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note, glyph.element);
10966
11341
  root.append(bar, stage);
10967
11342
  const wantsStrip = options.strip === true;
10968
11343
  let tl = bound;
@@ -10977,7 +11352,7 @@ function compare(container, options) {
10977
11352
  root.append(host);
10978
11353
  tl = timeline(host, {
10979
11354
  ...look,
10980
- variant: "lane",
11355
+ variant: "line",
10981
11356
  duration: 1,
10982
11357
  fps: fps(),
10983
11358
  arrows: { step: 0.5, shift: 2 },
@@ -10998,12 +11373,13 @@ function compare(container, options) {
10998
11373
  let toMode = mode;
10999
11374
  const wipeLine = new Spring(wipe, WIPE_CATCH.omega, WIPE_CATCH.zeta, 0.0001);
11000
11375
  let wipeCatching = false;
11001
- let gripY = null;
11376
+ const gripAt = new Spring(0.5, LAYOUT2.omega, LAYOUT2.zeta, 0.0001);
11002
11377
  let loupeHeld = false;
11003
11378
  let pointer = null;
11004
11379
  let drag = null;
11005
11380
  const touches = new Map;
11006
11381
  let pinch = null;
11382
+ let press = null;
11007
11383
  const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
11008
11384
  const zv = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
11009
11385
  const zw = new Spring(1, ZOOM.omega, ZOOM.zeta, 0.00001);
@@ -11290,6 +11666,8 @@ function compare(container, options) {
11290
11666
  for (const s of sides) {
11291
11667
  const st = s.el.style;
11292
11668
  st.clipPath = "";
11669
+ st.maskImage = "";
11670
+ st.setProperty("-webkit-mask-image", "");
11293
11671
  st.mixBlendMode = "";
11294
11672
  st.opacity = "";
11295
11673
  s.shown = null;
@@ -11516,43 +11894,65 @@ function compare(container, options) {
11516
11894
  viewSent = key;
11517
11895
  options.onView({ zoom: clampZoom(z), loop: l });
11518
11896
  };
11519
- const labelsIn = (m, r, wr, wx) => {
11520
- if (m === "wipe") {
11521
- const at = seamLabels(wx, wr.x, wr.x + wr.w, labelW[0], labelW[1], INSET);
11522
- return [at.before, at.after];
11523
- }
11524
- const ax = r.after.x + INSET + (r.after.w - labelW[1] - INSET * 2) * lay.x;
11525
- return [
11526
- { x: r.before.x + INSET, opacity: 1 },
11527
- { x: ax, opacity: 1 }
11528
- ];
11897
+ const differenceWeight = () => {
11898
+ const f = Math.min(1, Math.max(0, fade.x));
11899
+ return (fromMode === "difference" ? 1 - f : 0) + (toMode === "difference" ? f : 0);
11529
11900
  };
11530
11901
  const placeGrip = (wr) => {
11531
- const gy = gripTop(gripY ?? wr.y + wr.h / 2, wr, GRIP.h) - wr.y;
11902
+ const gy = gripTop(wr.y + gripAt.x * wr.h, wr, GRIP.h) - wr.y;
11532
11903
  grip.style.transform = `translate3d(0,${gy.toFixed(2)}px,0)`;
11533
11904
  };
11905
+ let folded = 0;
11534
11906
  const draw = () => {
11535
11907
  dirty = false;
11908
+ if (!root.hasAttribute("data-ready") && sides.every((x) => x.shown || x.failed))
11909
+ root.setAttribute("data-ready", "");
11536
11910
  const r = layoutNow();
11537
11911
  const wr = wipeRect(r.L.overlay);
11538
- const wx = wipeX(wr, wipeLine.x);
11539
- const k = lay.x;
11540
- const a = labelsIn(fromMode, r, wr, wx);
11541
- const b = labelsIn(toMode, r, wr, wx);
11542
- const f = Math.min(1, Math.max(0, fade.x));
11912
+ const k = Math.min(1, Math.max(0, lay.x));
11913
+ const fu = fuseFrame(r.L, r.before, r.after, k, wipeLine.x);
11914
+ const dw = differenceWeight();
11915
+ const fusing = 1 - dw;
11916
+ folded = k * fusing;
11917
+ const fold = labelFold(folded, MARK.w);
11918
+ const w = labelW.map((x) => x - (MARK.w - fold.width) - MARK.gap * folded);
11919
+ const ride = seamLabels(fu.cut, r.before.x, r.after.x + r.after.w, w[0], w[1], INSET);
11920
+ const apart = [r.before.x + INSET, r.after.x + INSET];
11921
+ const corner = [r.before.x + INSET, r.after.x + r.after.w - labelW[1] - INSET];
11922
+ const rides = [ride.before, ride.after];
11543
11923
  const ys = [r.before.y + INSET, r.after.y + INSET];
11544
11924
  for (const i of [0, 1]) {
11545
- const x = a[i].x + (b[i].x - a[i].x) * f;
11546
- labels[i].style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
11547
- labels[i].style.opacity = (a[i].opacity + (b[i].opacity - a[i].opacity) * f).toFixed(3);
11925
+ const fused = apart[i] + (rides[i].x - apart[i]) * k;
11926
+ const x = fused + (corner[i] - fused) * dw;
11927
+ const opacity = (1 - k + k * rides[i].opacity) * fusing + dw;
11928
+ const l = labels[i];
11929
+ l.style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
11930
+ l.style.opacity = opacity.toFixed(3);
11931
+ const mark = l.firstElementChild;
11932
+ if (mark) {
11933
+ mark.style.width = `${fold.width.toFixed(2)}px`;
11934
+ mark.style.marginRight = `${(-MARK.gap * folded).toFixed(2)}px`;
11935
+ mark.style.opacity = fold.opacity.toFixed(3);
11936
+ mark.style.filter = fold.blur > 0.05 ? `blur(${fold.blur.toFixed(2)}px)` : "";
11937
+ }
11548
11938
  }
11549
11939
  const top = Math.min(r.L.side.before.y, r.L.side.after.y);
11550
11940
  split.style.transform = `translate(${r.L.side.split.toFixed(2)}px,${top.toFixed(2)}px)`;
11551
11941
  split.style.height = `${Math.max(0, r.L.side.after.h).toFixed(2)}px`;
11552
11942
  split.style.opacity = String(Math.max(0, 1 - k * 2));
11943
+ const seamOn = fu.seam * fusing;
11944
+ const blur = fu.blur > 0.01 ? `blur(${fu.blur.toFixed(2)}px)` : "none";
11945
+ seam.style.transform = `translate(${fu.cut.toFixed(2)}px,${wr.y.toFixed(2)}px) translateX(-50%)`;
11946
+ seam.style.width = `${(8 + 2 * fu.feather).toFixed(2)}px`;
11947
+ seam.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
11948
+ seam.style.opacity = (blur === "none" ? 0 : seamOn).toFixed(3);
11949
+ seam.style.backdropFilter = blur;
11950
+ seam.style.setProperty("-webkit-backdrop-filter", blur);
11553
11951
  handle.toggleAttribute("data-on", mode === "wipe");
11554
- handle.style.transform = `translate(${wx.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
11952
+ handle.style.transform = `translate(${fu.cut.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
11555
11953
  handle.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
11954
+ handle.style.opacity = seamOn.toFixed(3);
11955
+ handle.style.filter = blur;
11556
11956
  placeGrip(wr);
11557
11957
  handle.setAttribute("aria-valuenow", String(Math.round(wipe * 100)));
11558
11958
  handle.setAttribute("aria-valuetext", `${Math.round(wipe * 100)}% Before`);
@@ -11565,24 +11965,27 @@ function compare(container, options) {
11565
11965
  if (pending)
11566
11966
  return;
11567
11967
  if (renderer === "dom") {
11568
- const place = (n, rect, clip, cutAt) => {
11968
+ const cut = mode === "difference" ? null : fu;
11969
+ const place = (n, rect, clip, before) => {
11569
11970
  const st = n.style;
11570
11971
  st.transform = `translate(${rect.x.toFixed(2)}px,${rect.y.toFixed(2)}px)`;
11571
11972
  st.width = `${rect.w.toFixed(2)}px`;
11572
11973
  st.height = `${rect.h.toFixed(2)}px`;
11974
+ const edge = before && cut ? cut.cut + cut.feather : Number.POSITIVE_INFINITY;
11573
11975
  const t = Math.max(0, clip.y - rect.y);
11574
11976
  const l = Math.max(0, clip.x - rect.x);
11575
11977
  const b = Math.max(0, rect.y + rect.h - (clip.y + clip.h));
11576
- const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, cutAt));
11978
+ const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, edge));
11577
11979
  st.clipPath = t + l + b + right > 0.01 ? `inset(${t.toFixed(2)}px ${right.toFixed(2)}px ${b.toFixed(2)}px ${l.toFixed(2)}px)` : "";
11980
+ const mask = before && cut && cut.feather > 0.5 ? `linear-gradient(to right,#000 ${(cut.cut - cut.feather - rect.x).toFixed(2)}px,transparent ${(edge - rect.x).toFixed(2)}px)` : "";
11981
+ st.maskImage = mask;
11982
+ st.setProperty("-webkit-mask-image", mask);
11578
11983
  };
11579
- const cut = mode === "wipe" ? wx : Number.POSITIVE_INFINITY;
11580
11984
  for (const s of sides) {
11581
11985
  const rect = s.index === 0 ? r.zBefore : r.zAfter;
11582
11986
  const clip = s.index === 0 ? r.before : r.after;
11583
- const at = s.index === 0 ? cut : Number.POSITIVE_INFINITY;
11584
- place(s.el, rect, clip, at);
11585
- place(s.preview, rect, clip, at);
11987
+ place(s.el, rect, clip, s.index === 0);
11988
+ place(s.preview, rect, clip, s.index === 0);
11586
11989
  if (s.index !== 0)
11587
11990
  continue;
11588
11991
  for (const n of [s.el, s.preview])
@@ -11602,8 +12005,8 @@ function compare(container, options) {
11602
12005
  width: Math.max(1, Math.round(stageW * dpr)),
11603
12006
  height: Math.max(1, Math.round(stageH * dpr)),
11604
12007
  dpr,
11605
- wipeX: wx * dpr,
11606
- soft: 0.75 * dpr,
12008
+ wipeX: fu.cut * dpr,
12009
+ soft: Math.max(0.75, fu.feather) * dpr,
11607
12010
  from: MODE_INDEX[fromMode],
11608
12011
  to: MODE_INDEX[toMode],
11609
12012
  mix: Math.min(1, Math.max(0, fade.x)),
@@ -11630,6 +12033,7 @@ function compare(container, options) {
11630
12033
  zu.step(dt);
11631
12034
  zv.step(dt);
11632
12035
  zw.step(dt);
12036
+ gripAt.step(dt);
11633
12037
  const playing = isPlaying();
11634
12038
  if (playing) {
11635
12039
  const m = master();
@@ -11660,11 +12064,9 @@ function compare(container, options) {
11660
12064
  pushFrame(s);
11661
12065
  }
11662
12066
  }
11663
- const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching;
12067
+ const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching || !gripAt.resting;
11664
12068
  if (moving)
11665
12069
  dirty = true;
11666
- if (auto && !lay.resting)
11667
- fitHeight();
11668
12070
  if (dirty)
11669
12071
  draw();
11670
12072
  return playing || moving;
@@ -11690,13 +12092,7 @@ function compare(container, options) {
11690
12092
  zw.set(c.w);
11691
12093
  requestDraw();
11692
12094
  };
11693
- const autoHeight = (w) => {
11694
- const shape = sizeOf(sides[1]);
11695
- const side = splitHeight(w, shape);
11696
- const over = w * shape.h / shape.w;
11697
- const h = side + (over - side) * lay.x;
11698
- return Math.max(60, Math.min(win.innerHeight * 0.8, h));
11699
- };
12095
+ const autoHeight = (w) => Math.max(60, Math.min(win.innerHeight * 0.8, splitHeight(w, sizeOf(sides[1]))));
11700
12096
  const fitHeight = () => {
11701
12097
  const h = autoHeight(stageW);
11702
12098
  if (Math.abs(h - stageH) < 0.25)
@@ -11708,7 +12104,8 @@ function compare(container, options) {
11708
12104
  const readColors = () => {
11709
12105
  const c = tokenColor(win.getComputedStyle(root), "--_ku-cmp-diff", [0.49, 0.81, 0.61, 1]);
11710
12106
  accent = [c[0], c[1], c[2]];
11711
- labelW = [labels[0].offsetWidth, labels[1].offsetWidth];
12107
+ const unfold = MARK.w - labelFold(folded, MARK.w).width + MARK.gap * folded;
12108
+ labelW = [labels[0].offsetWidth + unfold, labels[1].offsetWidth + unfold];
11712
12109
  requestDraw();
11713
12110
  };
11714
12111
  const measure = () => {
@@ -11799,8 +12196,10 @@ function compare(container, options) {
11799
12196
  root.focus({ preventScroll: true });
11800
12197
  touches.set(e.pointerId, p);
11801
12198
  stage.setPointerCapture(e.pointerId);
12199
+ press = touches.size === 1 && mode !== "wipe" && master() ? p : null;
11802
12200
  if (touches.size >= 2) {
11803
12201
  e.preventDefault();
12202
+ press = null;
11804
12203
  return startPinch();
11805
12204
  }
11806
12205
  if (mode === "wipe") {
@@ -11810,7 +12209,7 @@ function compare(container, options) {
11810
12209
  if (inside && (grab.onLine || !zoomed())) {
11811
12210
  drag = { kind: "wipe", offset: grab.offset };
11812
12211
  root.dataset.dragging = "";
11813
- gripY = p.y;
12212
+ gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
11814
12213
  e.preventDefault();
11815
12214
  if (!grab.onLine)
11816
12215
  setWipe(wipeAt(p.x, wr), true, true);
@@ -11828,6 +12227,8 @@ function compare(container, options) {
11828
12227
  const onMove = (e) => {
11829
12228
  const p = local(e);
11830
12229
  pointer = p;
12230
+ if (press && Math.hypot(p.x - press.x, p.y - press.y) > 4)
12231
+ press = null;
11831
12232
  if (touches.has(e.pointerId))
11832
12233
  touches.set(e.pointerId, p);
11833
12234
  if (pinch && touches.size >= 2) {
@@ -11841,14 +12242,12 @@ function compare(container, options) {
11841
12242
  pinch = { d, x: mid.x, y: mid.y };
11842
12243
  return handZoom(zoomAbout(panned, k, q.pu, q.pv));
11843
12244
  }
11844
- if (mode === "wipe" && drag?.kind !== "pan") {
11845
- gripY = p.y;
11846
- if (!drag)
11847
- placeGrip(wipeRect(layoutNow().L.overlay));
11848
- }
11849
- if (drag?.kind === "wipe")
11850
- setWipe(wipeAt(p.x, wipeRect(layoutNow().L.overlay), drag.offset));
11851
- else if (drag?.kind === "pan") {
12245
+ if (drag?.kind === "wipe") {
12246
+ const wr = wipeRect(layoutNow().L.overlay);
12247
+ gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
12248
+ setWipe(wipeAt(p.x, wr, drag.offset));
12249
+ placeGrip(wr);
12250
+ } else if (drag?.kind === "pan") {
11852
12251
  const dx = p.x - drag.x;
11853
12252
  const dy = p.y - drag.y;
11854
12253
  drag.x = p.x;
@@ -11859,11 +12258,23 @@ function compare(container, options) {
11859
12258
  drawNow();
11860
12259
  };
11861
12260
  const endDrag = (e) => {
12261
+ if (press && e.type === "pointerup") {
12262
+ api.toggle();
12263
+ glyph.flash(isPlaying());
12264
+ }
12265
+ press = null;
11862
12266
  touches.delete(e.pointerId);
11863
12267
  if (touches.size < 2)
11864
12268
  pinch = null;
11865
12269
  if (!drag)
11866
12270
  return;
12271
+ if (drag.kind === "wipe") {
12272
+ if (reducedMotion(win))
12273
+ gripAt.snap(0.5);
12274
+ else
12275
+ gripAt.set(0.5);
12276
+ requestDraw();
12277
+ }
11867
12278
  drag = null;
11868
12279
  delete root.dataset.dragging;
11869
12280
  delete root.dataset.panning;
@@ -12013,8 +12424,9 @@ function compare(container, options) {
12013
12424
  old.el.replaceWith(s.el);
12014
12425
  old.preview.replaceWith(s.preview);
12015
12426
  sides[index] = s;
12427
+ root.toggleAttribute("data-video", sides.some((x) => x.kind === "video"));
12016
12428
  note.textContent = "";
12017
- labels[index].textContent = labelOf(index);
12429
+ setLabel(index, labelOf(index));
12018
12430
  if (near)
12019
12431
  loadSide(s);
12020
12432
  readColors();
@@ -12025,8 +12437,9 @@ function compare(container, options) {
12025
12437
  stage.insertBefore(sides[1].preview, labels[0]);
12026
12438
  stage.insertBefore(sides[0].el, labels[0]);
12027
12439
  stage.insertBefore(sides[0].preview, labels[0]);
12028
- labels[0].textContent = labelOf(0);
12029
- labels[1].textContent = labelOf(1);
12440
+ setLabel(0, labelOf(0));
12441
+ setLabel(1, labelOf(1));
12442
+ root.toggleAttribute("data-video", sides.some((s) => s.kind === "video"));
12030
12443
  loupeBtn.setAttribute("aria-pressed", String(loupeOn));
12031
12444
  loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
12032
12445
  const modePill = modeList.length > 1 ? selectionPill(modes, {
@@ -12166,11 +12579,11 @@ function compare(container, options) {
12166
12579
  if (before)
12167
12580
  replace(0, before);
12168
12581
  else if (next.before)
12169
- labels[0].textContent = next.before.label ?? "Before";
12582
+ setLabel(0, next.before.label ?? "Before");
12170
12583
  if (after)
12171
12584
  replace(1, after);
12172
12585
  else if (next.after)
12173
- labels[1].textContent = next.after.label ?? "After";
12586
+ setLabel(1, next.after.label ?? "After");
12174
12587
  if (before || after)
12175
12588
  strip?.update({ ...before ? { before } : {}, ...after ? { after } : {} });
12176
12589
  if (next.before || next.after)
@@ -12227,6 +12640,7 @@ function compare(container, options) {
12227
12640
  disposed = true;
12228
12641
  loop.stop();
12229
12642
  modePill?.dispose();
12643
+ glyph.dispose();
12230
12644
  win.clearTimeout(releaseTimer);
12231
12645
  ro.disconnect();
12232
12646
  nearIo?.disconnect();
@@ -12256,7 +12670,7 @@ function compare(container, options) {
12256
12670
  wake();
12257
12671
  return api;
12258
12672
  }
12259
- var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
12673
+ var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
12260
12674
  var init_compare = __esm(() => {
12261
12675
  init_motion();
12262
12676
  init_page_hints();
@@ -12267,15 +12681,17 @@ var init_compare = __esm(() => {
12267
12681
  init_icons();
12268
12682
  init_look();
12269
12683
  init_rgba();
12684
+ init_player();
12270
12685
  init_selection_pill();
12271
12686
  init_timeline();
12272
12687
  init_timeline_model();
12273
- LAYOUT2 = springMotion({ duration: 0.35, bounce: 0 });
12688
+ LAYOUT2 = springMotion(SPRINGS.default);
12274
12689
  FADE6 = springMotion(SPRINGS.snappy);
12275
12690
  FADE_REDUCED = springMotion(FADE);
12276
12691
  ZOOM = springMotion({ duration: 0.35, bounce: 0 });
12277
12692
  WIPE_CATCH = springMotion(CATCH_UP);
12278
12693
  GRIP = { w: 16, h: 28 };
12694
+ MARK = { w: 12, gap: 4 };
12279
12695
  LOUPE = { r: 88, zoom: 4 };
12280
12696
  MODE_LABEL = {
12281
12697
  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();