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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui.js CHANGED
@@ -570,12 +570,20 @@ var compareCss = `
570
570
  display:flex;flex-direction:column;gap:6px;width:100%;height:100%;min-height:160px;outline:none;user-select:none;-webkit-user-select:none;
571
571
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
572
572
  :where(.knobs-ui) :is(.knobs-cmp-mode,.knobs-cmp-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}
573
- .knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:2px;height:24px;padding:0 2px}
574
- .knobs-cmp-modes{display:flex;align-items:center;gap:2px}
575
- .knobs-ui .knobs-cmp-mode,.knobs-ui .knobs-cmp-toggle{height:22px;padding:0 7px;border-radius:max(0px,calc(var(--_k-radius) - 2px));color:var(--_k-fg-3);cursor:pointer;white-space:nowrap;
576
- transition:color .15s,background .15s}
577
- .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible),.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
578
- .knobs-ui .knobs-cmp-mode[aria-checked="true"],.knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
573
+ .knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:var(--_k-gap);height:var(--_k-control-h)}
574
+ /* The modes: one fill, the selection pill on the one picked; a mode isn't brighter for being
575
+ picked: the pill's bright copy is. */
576
+ .knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
577
+ .knobs-cmp-modes[hidden]{display:none}
578
+ .knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
579
+ transition:color calc(var(--_k-hover-out) * 1ms)}
580
+ .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
581
+ /* Toggles: icon buttons, the accent's wash when on. */
582
+ .knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-3);cursor:pointer;
583
+ transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
584
+ .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
585
+ .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
586
+ .knobs-cmp-toggle svg{display:block}
579
587
  .knobs-cmp-fill{flex:1}
580
588
  .knobs-cmp-toggle[hidden]{display:none}
581
589
  /* One surface: on a card (a walkthrough's step, a split's pane, anything .knobs-surface) or marked
@@ -590,7 +598,8 @@ var compareCss = `
590
598
  /* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
591
599
  .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
592
600
  .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
593
- .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:ew-resize!important}
601
+ /* Wiping, the grip stands in for the cursor. */
602
+ .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
594
603
  .knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
595
604
  .knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
596
605
  .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}
@@ -598,18 +607,26 @@ var compareCss = `
598
607
  /* Glass: what's behind shows through, blurred, on the knobs tokens. */
599
608
  .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);
600
609
  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)))}
601
- .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 .15s}
602
- .knobs-cmp-label[data-lit]{color:var(--_k-fg)}
603
- .knobs-cmp-label[data-dim]{color:var(--_k-fg-3)}
604
- /* The wipe: a hairline down the line with a grip at its middle; a wider strip takes the hand and
605
- the keys (a slider). It fades with the mode. */
610
+ /* ✗ Before · ✓ After: Before quieter (fg-3), After in full text. Neutral: no colour of its own. */
611
+ .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);
612
+ white-space:nowrap;pointer-events:none;will-change:transform}
613
+ .knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
614
+ .knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
615
+ .knobs-cmp-mark svg{display:block;width:12px;height:12px}
616
+ /* The wipe: a hairline down the line with a raised grip at the pointer's height; a wider strip
617
+ takes the hand and the keys (a slider). It fades with the mode; wiping, line and grip brighten. */
606
618
  .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)}
607
619
  .knobs-cmp-handle[data-on]{opacity:1;pointer-events:auto;transition:opacity .22s cubic-bezier(.2,.8,.2,1)}
608
- .knobs-cmp-handle::before{content:"";position:absolute;top:0;bottom:0;left:11.5px;width:1px;background:var(--_ku-cmp-line);box-shadow:0 0 0 .5px rgba(0,0,0,.28),0 0 6px rgba(0,0,0,.25)}
609
- .knobs-cmp-handle::after{content:"";position:absolute;top:50%;left:9px;width:6px;height:28px;margin-top:-14px;border-radius:3px;background:var(--_ku-cmp-line);
610
- box-shadow:0 1px 3px rgba(0,0,0,.35),0 0 0 .5px rgba(0,0,0,.2);transition:scale .18s var(--_ku-ease),box-shadow .15s}
611
- .knobs-compare[data-dragging] .knobs-cmp-handle::after{scale:1.12}
612
- .knobs-cmp-handle:focus-visible::after,.knobs-compare[data-keyboard] .knobs-cmp-handle:focus::after{box-shadow:0 1px 3px rgba(0,0,0,.35),0 0 0 3px color-mix(in srgb,var(--_k-accent) 40%,transparent)}
620
+ .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)}
621
+ .knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
622
+ .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);
623
+ box-shadow:var(--_k-shadow-overlay),inset 0 0 0 1px var(--_k-border),inset 0 1px 0 var(--_k-rim);will-change:transform;transition:box-shadow calc(var(--_k-hover-out) * 1ms)}
624
+ .knobs-cmp-grip::before,.knobs-cmp-grip::after{content:"";position:absolute;top:10px;width:1px;height:8px;background:var(--_k-fg-3);transition:background calc(var(--_k-hover-out) * 1ms)}
625
+ .knobs-cmp-grip::before{left:6px}
626
+ .knobs-cmp-grip::after{left:9px}
627
+ .knobs-compare[data-dragging] .knobs-cmp-grip{box-shadow:var(--_k-shadow-overlay),inset 0 0 0 1px var(--_k-border-strong),inset 0 1px 0 var(--_k-rim)}
628
+ .knobs-compare[data-dragging] .knobs-cmp-grip::before,.knobs-compare[data-dragging] .knobs-cmp-grip::after{background:var(--_k-fg)}
629
+ .knobs-cmp-handle:focus-visible .knobs-cmp-grip,.knobs-compare[data-keyboard] .knobs-cmp-handle:focus .knobs-cmp-grip{box-shadow:var(--_k-shadow-overlay),0 0 0 2px var(--_k-accent)}
613
630
  /* The split's hairline, down the middle side by side. */
614
631
  .knobs-cmp-split{position:absolute;left:0;top:0;width:1px;height:0;margin-left:-.5px;background:color-mix(in srgb,var(--_k-bg) 72%,transparent);pointer-events:none;will-change:transform,opacity}
615
632
  /* Zoomed: how far, and a way back, in the stage's corner; it fades in and out, nothing shifts. */
@@ -618,12 +635,12 @@ var compareCss = `
618
635
  opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
619
636
  .knobs-cmp-zoom[data-on]{opacity:1;scale:1;pointer-events:auto;transition:opacity .2s cubic-bezier(.2,.8,.2,1),scale .2s cubic-bezier(.2,.8,.2,1)}
620
637
  :where(.knobs-ui) .knobs-cmp-zoom-reset{all:revert;box-sizing:border-box;font:inherit;letter-spacing:inherit;margin:0;border:0;outline:none;box-shadow:none;
621
- height:20px;padding:0 8px;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:pointer;transition:color .15s,background .15s}
638
+ display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:pointer;transition:color .15s,background .15s}
622
639
  :where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){color:var(--_k-fg);background:color-mix(in srgb,var(--_k-fg) 12%,transparent)}
623
640
  .knobs-cmp-note{position:absolute;left:50%;top:50%;translate:-50% -50%;color:var(--_k-fg-3);font-size:var(--_k-text-meta);pointer-events:none}
624
641
  .knobs-cmp-note:empty{display:none}
625
642
  .knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
626
- .knobs-cmp-timeline-host{flex:none;height:56px;min-height:0}
643
+ .knobs-cmp-timeline-host{flex:none;min-height:0}
627
644
  /* The frame strip: Before over After per column, a difference bar under them, a dot where it peaks. */
628
645
  .knobs-ui.knobs-cmp-strip{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));position:relative;width:100%;height:100%;min-height:40px;overflow:hidden;cursor:col-resize;touch-action:none;
629
646
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg)}
@@ -766,7 +783,7 @@ var init_picker_css = __esm(() => {
766
783
  .knobs-picker-suggested{flex:none;color:var(--_k-fg-3);font-size:var(--_k-text-meta)}
767
784
  .knobs-picker-check{flex:none;display:flex;width:14px;height:14px;color:var(--_k-accent);opacity:0;scale:.6;filter:blur(2px);
768
785
  transition:opacity .11s ease-in,scale .11s ease-in,filter .11s ease-in}
769
- .knobs-picker-check svg{display:block;width:14px;height:14px}
786
+ .knobs-picker-check svg{display:block;width:12px;height:12px}
770
787
  .knobs-picker-option[data-chosen] .knobs-picker-check{opacity:1;scale:1;filter:none;transition:opacity .15s ease-out,scale var(--kp-snappy-ms) var(--kp-snappy),filter .15s ease-out}
771
788
  .knobs-picker-body{position:relative;overflow:visible}
772
789
  .knobs-picker-body[data-moving]{overflow:hidden}
@@ -870,6 +887,15 @@ var readoutCss = `
870
887
  @media(prefers-reduced-motion:reduce){.knobs-rd :where(*){transition:none!important}}
871
888
  `;
872
889
 
890
+ // src/ui/selection-pill-css.ts
891
+ var selectionPillCss = `
892
+ .knobs-pick-pill,.knobs-pick-copy{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;transition:opacity .1s linear}
893
+ .knobs-pick-pill{background:var(--_k-fill-selected);will-change:transform}
894
+ .knobs-pick-copy{overflow:hidden;will-change:transform}
895
+ .knobs-pick-copy-rows{position:absolute;left:0;top:0;width:0;height:0}
896
+ :is(.knobs-pick-pill,.knobs-pick-copy)[data-off]{opacity:0}
897
+ `;
898
+
873
899
  // src/tokens/color.ts
874
900
  function linearToOklch([r, g, b, a]) {
875
901
  const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
@@ -1310,6 +1336,22 @@ var init_schema = __esm(() => {
1310
1336
  source: css("var(--k-warn)"),
1311
1337
  doc: "Modified lines."
1312
1338
  },
1339
+ {
1340
+ name: "before",
1341
+ tier: "semantic",
1342
+ type: "color",
1343
+ group: "Data",
1344
+ source: css("var(--k-fg-3)"),
1345
+ doc: "Before in a comparison, and only Before: its label and side of the seam. Neutral unless a theme gives it a colour."
1346
+ },
1347
+ {
1348
+ name: "after",
1349
+ tier: "semantic",
1350
+ type: "color",
1351
+ group: "Data",
1352
+ source: css("var(--k-fg)"),
1353
+ doc: "After in a comparison, and only After."
1354
+ },
1313
1355
  {
1314
1356
  name: "series-1",
1315
1357
  tier: "semantic",
@@ -1400,6 +1442,150 @@ var init_schema = __esm(() => {
1400
1442
  source: value("288px"),
1401
1443
  doc: "Panel width."
1402
1444
  },
1445
+ {
1446
+ name: "space-inside",
1447
+ tier: "semantic",
1448
+ type: "length",
1449
+ group: "Space",
1450
+ source: css("var(--k-unit)"),
1451
+ doc: "Parts of one thing: an icon and its label, a meta line and its title."
1452
+ },
1453
+ {
1454
+ name: "space-sibling",
1455
+ tier: "semantic",
1456
+ type: "length",
1457
+ group: "Space",
1458
+ source: css("calc(var(--k-unit) * 2)"),
1459
+ doc: "Siblings in a group: buttons in a row, a list item's lines."
1460
+ },
1461
+ {
1462
+ name: "space-title",
1463
+ tier: "semantic",
1464
+ type: "length",
1465
+ group: "Space",
1466
+ source: css("calc(var(--k-unit) * 3)"),
1467
+ doc: "A heading and what it heads."
1468
+ },
1469
+ {
1470
+ name: "space-group",
1471
+ tier: "semantic",
1472
+ type: "length",
1473
+ group: "Space",
1474
+ source: css("calc(var(--k-unit) * 12)"),
1475
+ doc: "Groups inside a section (prose, video, diff), stacked media, feed items (a hairline in the middle)."
1476
+ },
1477
+ {
1478
+ name: "space-section",
1479
+ tier: "semantic",
1480
+ type: "length",
1481
+ group: "Space",
1482
+ source: css("calc(var(--k-unit) * 24)"),
1483
+ doc: "Sections of a page (the intro, the updates, each PR), a hairline in the middle."
1484
+ },
1485
+ {
1486
+ name: "space-column",
1487
+ tier: "semantic",
1488
+ type: "length",
1489
+ group: "Space",
1490
+ source: css("calc(var(--k-unit) * 6)"),
1491
+ doc: "Columns: a contents rail and the page, media side by side."
1492
+ },
1493
+ {
1494
+ name: "pad-page",
1495
+ tier: "semantic",
1496
+ type: "length",
1497
+ group: "Space",
1498
+ source: css("calc(var(--k-unit) * 6)"),
1499
+ doc: "A page's edge; 16px under 640px wide."
1500
+ },
1501
+ {
1502
+ name: "pad-card",
1503
+ tier: "semantic",
1504
+ type: "length",
1505
+ group: "Space",
1506
+ source: css("calc(var(--k-unit) * 3)"),
1507
+ doc: "Inside a card or popover."
1508
+ },
1509
+ {
1510
+ name: "type-heading",
1511
+ tier: "semantic",
1512
+ type: "length",
1513
+ group: "Type",
1514
+ source: value("20px"),
1515
+ doc: "Section headings (a PR's title, under its kicker), weight 500."
1516
+ },
1517
+ {
1518
+ name: "leading-heading",
1519
+ tier: "semantic",
1520
+ type: "length",
1521
+ group: "Type",
1522
+ source: value("28px"),
1523
+ doc: "Line height for type-heading."
1524
+ },
1525
+ {
1526
+ name: "type-title",
1527
+ tier: "semantic",
1528
+ type: "length",
1529
+ group: "Type",
1530
+ source: value("16px"),
1531
+ doc: "A page's title, weight 500."
1532
+ },
1533
+ {
1534
+ name: "leading-title",
1535
+ tier: "semantic",
1536
+ type: "length",
1537
+ group: "Type",
1538
+ source: value("24px"),
1539
+ doc: "Line height for type-title."
1540
+ },
1541
+ {
1542
+ name: "type-body",
1543
+ tier: "semantic",
1544
+ type: "length",
1545
+ group: "Type",
1546
+ source: value("13px"),
1547
+ doc: "Prose, questions, list rows, step titles; 400, or 500 for emphasis."
1548
+ },
1549
+ {
1550
+ name: "leading-body",
1551
+ tier: "semantic",
1552
+ type: "length",
1553
+ group: "Type",
1554
+ source: value("20px"),
1555
+ doc: "Line height for type-body."
1556
+ },
1557
+ {
1558
+ name: "type-label",
1559
+ tier: "semantic",
1560
+ type: "length",
1561
+ group: "Type",
1562
+ source: value("12px"),
1563
+ doc: "Labels over media, weight 500; mono inside body text."
1564
+ },
1565
+ {
1566
+ name: "leading-label",
1567
+ tier: "semantic",
1568
+ type: "length",
1569
+ group: "Type",
1570
+ source: value("16px"),
1571
+ doc: "Line height for type-label."
1572
+ },
1573
+ {
1574
+ name: "type-small",
1575
+ tier: "semantic",
1576
+ type: "length",
1577
+ group: "Type",
1578
+ source: value("11px"),
1579
+ doc: "Meta, controls, keys, timestamps; mono inside labels."
1580
+ },
1581
+ {
1582
+ name: "leading-small",
1583
+ tier: "semantic",
1584
+ type: "length",
1585
+ group: "Type",
1586
+ source: value("16px"),
1587
+ doc: "Line height for type-small."
1588
+ },
1403
1589
  {
1404
1590
  name: "font-size",
1405
1591
  tier: "semantic",
@@ -2364,16 +2550,18 @@ function injectTokens(doc) {
2364
2550
  style.textContent = rootCss;
2365
2551
  doc.head.append(style);
2366
2552
  }
2367
- var ROOT_CLASS = "knobs-root", extras, spring, rootCss;
2553
+ var ROOT_CLASS = "knobs-root", extras, narrow, spring, rootCss;
2368
2554
  var init_root = __esm(() => {
2369
2555
  init_knobs();
2370
2556
  init_css();
2371
2557
  init_glide();
2372
2558
  init_motion();
2373
2559
  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))}`;
2560
+ narrow = `@media(max-width:639.98px){:where(.${ROOT_CLASS}){--_k-pad-page:var(--k-pad-page,calc(var(--_k-unit) * 4))}}`;
2374
2561
  spring = `:where(:root,:host){--k-fill-ease:${DEFAULT_LINEAR.easing};--k-fill-ms:${DEFAULT_LINEAR.ms}}`;
2375
2562
  rootCss = `${tokensCss(knobs)}
2376
2563
  ${extras}
2564
+ ${narrow}
2377
2565
  ${spring}
2378
2566
  ${DRIVERS}
2379
2567
  ${motionCss}`;
@@ -2663,6 +2851,12 @@ var css2 = `
2663
2851
  --_ku-pane-bg and --_ku-pane-ring so this reaches them. */
2664
2852
  :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;
2665
2853
  --_ku-divider:var(--_k-split-gutter);--_kd-bg:transparent}
2854
+ /* What draws its box without those: a picker's card keeps its fill as the highlight but drops its
2855
+ ring; a timeline's track drops its well for a hairline under the lane; an inline panel sheds its
2856
+ card (--k-chrome 0). A full-window step is the one raised exception (it is the surface). */
2857
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-picker-card,.knobs-ui[data-nested] .knobs-picker-card{box-shadow:none}
2858
+ :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)}
2859
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-panel.knobs-inline:not(.knobs-page){--k-chrome:0}
2666
2860
  /* Only the pieces' own elements take their type and resets: what you put in a pane is yours. */
2667
2861
  .knobs-ui{box-sizing:border-box}
2668
2862
  :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}
@@ -2797,11 +2991,9 @@ var css2 = `
2797
2991
  .knobs-tree:focus .knobs-tree-row[data-active][aria-selected="true"]::before{background:var(--_ku-select-focus)}
2798
2992
  .knobs-tree-row[data-drop="into"]::before{opacity:1;background:color-mix(in srgb,var(--_k-accent) 30%,transparent)}
2799
2993
  .knobs-tree-row[data-dragged]{opacity:.38}
2800
- /* glide: one highlight under the selected row that glides between rows (the row's own stays off). */
2801
- .knobs-tree-cursor{position:absolute;left:var(--_ku-row-x);right:var(--_ku-row-x);top:0;height:var(--_k-row);border-radius:var(--_ku-row-radius);background:var(--_ku-select);
2802
- opacity:0;pointer-events:none;will-change:transform;transition:opacity .15s ease-out,background .12s}
2803
- .knobs-tree-cursor[data-on]{opacity:1}
2804
- .knobs-tree:focus .knobs-tree-cursor{background:var(--_ku-select-focus)}
2994
+ /* glide: the selection pill on the selected row, in the selection's colour (the row's own stays off). */
2995
+ .knobs-tree>.knobs-pick-pill{background:var(--_ku-select)}
2996
+ .knobs-tree:focus>.knobs-pick-pill{background:var(--_ku-select-focus)}
2805
2997
  .knobs-tree[data-glide] .knobs-tree-row[aria-selected="true"]::before{opacity:0!important}
2806
2998
  .knobs-tree-row>*{position:relative}
2807
2999
  .knobs-tree-caret{flex:none;display:flex;align-items:center;justify-content:center;width:16px;height:100%;color:var(--_k-fg-3);transition:transform var(--_ku-ease-ms) var(--_ku-ease)}
@@ -2833,22 +3025,20 @@ var css2 = `
2833
3025
  box-shadow:0 0 0 1px var(--_k-border-strong),0 6px 18px rgba(0,0,0,calc(.35 * var(--_k-shadow-k,1)));pointer-events:none;white-space:nowrap;font:var(--_k-weight) var(--_k-font-size)/1.6 var(--_k-font)}
2834
3026
  .knobs-tree-empty{padding:10px var(--_k-inset);color:var(--_k-fg-3)}
2835
3027
 
2836
- /* Timeline: a transport (play, the time) beside a track: the ruler's canvas over a lane of
2837
- markers, ranges as soft bands, and the playhead, a hairline in the accent with a cap. */
3028
+ /* Timeline: play beside a track: the ruler's canvas over a lane of markers, ranges as soft bands,
3029
+ and the playhead, a hairline in the accent with a cap. No clock: the time shows under the
3030
+ pointer on the track (the tag), in seconds, while hovering or scrubbing. */
2838
3031
  :where(.knobs-timeline,.knobs-timeline *){box-sizing:border-box}
2839
3032
  .knobs-ui.knobs-timeline{display:flex;align-items:stretch;gap:10px;width:100%;height:100%;min-height:52px;padding:6px var(--_k-inset) 8px 8px;outline:none;user-select:none;-webkit-user-select:none;
2840
3033
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
2841
3034
  :where(.knobs-ui) :is(.knobs-tl-play,.knobs-tl-edit){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}
2842
- .knobs-tl-transport{flex:none;display:flex;align-items:center;gap:8px;min-width:96px}
3035
+ .knobs-tl-transport{flex:none;display:flex;align-items:center}
2843
3036
  .knobs-ui .knobs-tl-play{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);min-width:22px;min-height:22px;border-radius:calc(var(--_k-radius) * 999);
2844
3037
  background:var(--_k-fill-selected);color:var(--_k-fg);cursor:pointer;transition:background .15s,transform .15s var(--_ku-ease)}
2845
3038
  .knobs-ui .knobs-tl-play:hover{background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
2846
3039
  .knobs-ui .knobs-tl-play:active{transform:scale(.94)}
2847
3040
  .knobs-tl-play svg{width:12px;height:12px}
2848
3041
  .knobs-timeline[data-playing] .knobs-tl-play{background:var(--_k-accent);color:var(--_k-bg)}
2849
- .knobs-tl-time{display:flex;flex-direction:column;line-height:1.2;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
2850
- .knobs-tl-now{font:var(--_k-weight) var(--_k-value-size)/1.2 var(--_k-value-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";color:var(--_k-fg);white-space:nowrap}
2851
- .knobs-tl-sub{font-size:var(--_k-text-meta);color:var(--_k-fg-3);white-space:nowrap}
2852
3042
  .knobs-tl-track{position:relative;flex:1;min-width:0;overflow:hidden;border-radius:var(--_k-radius);background:var(--_k-fill);cursor:col-resize;touch-action:none;
2853
3043
  transition:background calc(var(--_k-hover-out) * 1ms)}
2854
3044
  .knobs-timeline:focus-visible .knobs-tl-track,.knobs-timeline[data-keyboard]:focus .knobs-tl-track{background:color-mix(in srgb,var(--_k-fill-selected) 80%,transparent)}
@@ -2872,7 +3062,8 @@ var css2 = `
2872
3062
  .knobs-tl-pin{position:absolute;top:13px;left:-5px;width:10px;height:10px;pointer-events:auto;cursor:grab}
2873
3063
  .knobs-tl-pin::after{content:"";position:absolute;inset:1.5px;border-radius:min(2px,var(--_k-radius));background:var(--knobs-tl-c);rotate:45deg;transition:background .15s,scale .2s var(--_ku-ease)}
2874
3064
  .knobs-tl-mlabel{position:absolute;top:26px;left:3px;max-width:140px;padding:0 6px;border-radius:max(0px,calc(var(--_k-radius) - 3px));background:var(--_k-fill-selected);color:var(--_k-fg);
2875
- font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:auto;cursor:grab;transition:background .15s,color .15s}
3065
+ font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;pointer-events:auto;cursor:grab;transition:background .15s,color .15s}
3066
+ .knobs-tl-mtext{display:block;overflow:hidden;white-space:nowrap}
2876
3067
  .knobs-tl-marker[data-selected]{--knobs-tl-c:var(--_k-accent)}
2877
3068
  .knobs-tl-marker[data-selected] .knobs-tl-mlabel{background:color-mix(in srgb,var(--_k-accent) 24%,var(--_k-fill-selected))}
2878
3069
  .knobs-tl-marker[data-selected] .knobs-tl-pin::after{scale:1.2}
@@ -2893,7 +3084,22 @@ var css2 = `
2893
3084
  .knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
2894
3085
  @media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
2895
3086
  @media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
2896
- `, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss) + rolling_css_default, RESTYLED = ".knobs-ui,.knobs-panel,.knobs-chip,.knobs-notice", lookDefaults, tune = (s, m) => {
3087
+ .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}
3088
+ .knobs-tl-tag[data-on]{opacity:1}
3089
+ /* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
3090
+ faint chapter ticks, the current one in text colour; nothing to edit. */
3091
+ .knobs-ui.knobs-timeline[data-variant="lane"]{height:auto;min-height:0;padding:0;gap:var(--_k-gap)}
3092
+ .knobs-timeline[data-variant="lane"] .knobs-tl-track{height:var(--_k-control-h)}
3093
+ .knobs-timeline[data-variant="lane"] .knobs-tl-ruler{height:100%}
3094
+ .knobs-timeline[data-variant="lane"] .knobs-tl-lane{top:0}
3095
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker{top:0}
3096
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker::before{top:0;bottom:50%;left:-.75px;width:1.5px;background:var(--_k-fg-3)}
3097
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker[data-current]::before{background:var(--_k-fg)}
3098
+ .knobs-timeline[data-variant="lane"] :is(.knobs-tl-pin,.knobs-tl-mlabel,.knobs-tl-rlabel){display:none}
3099
+ .knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
3100
+ .knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
3101
+ .knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
3102
+ `, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + selectionPillCss) + rolling_css_default, RESTYLED = ".knobs-ui,.knobs-panel,.knobs-chip,.knobs-notice", lookDefaults, tune = (s, m) => {
2897
3103
  s.omega = m.omega;
2898
3104
  s.zeta = m.zeta;
2899
3105
  return s;
@@ -4659,6 +4865,283 @@ var init_tree_model = __esm(() => {
4659
4865
  };
4660
4866
  });
4661
4867
 
4868
+ // src/ui/icons.ts
4869
+ function icon(name, options = {}) {
4870
+ const size = options.small ? 12 : 16;
4871
+ return `<svg class="knobs-icon" viewBox="0 0 256 256" width="${size}" height="${size}" fill="currentColor" aria-hidden="true" focusable="false"><path d="${PATHS[name]}"/></svg>`;
4872
+ }
4873
+ var PATHS, ICON_NAMES;
4874
+ var init_icons = __esm(() => {
4875
+ PATHS = {
4876
+ arrowLeft: "M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z",
4877
+ arrowUpRight: "M200,64V168a8,8,0,0,1-16,0V83.31L69.66,197.66a8,8,0,0,1-11.32-11.32L172.69,72H88a8,8,0,0,1,0-16H192A8,8,0,0,1,200,64Z",
4878
+ arrowSquareOut: "M224,104a8,8,0,0,1-16,0V59.32l-66.33,66.34a8,8,0,0,1-11.32-11.32L196.68,48H152a8,8,0,0,1,0-16h64a8,8,0,0,1,8,8Zm-40,24a8,8,0,0,0-8,8v72H48V80h72a8,8,0,0,0,0-16H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V136A8,8,0,0,0,184,128Z",
4879
+ arrowCounterClockwise: "M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z",
4880
+ arrowUDownLeft: "M232,112a64.07,64.07,0,0,1-64,64H51.31l34.35,34.34a8,8,0,0,1-11.32,11.32l-48-48a8,8,0,0,1,0-11.32l48-48a8,8,0,0,1,11.32,11.32L51.31,160H168a48,48,0,0,0,0-96H80a8,8,0,0,1,0-16h88A64.07,64.07,0,0,1,232,112Z",
4881
+ arrowsHorizontal: "M136,40V216a8,8,0,0,1-16,0V40a8,8,0,0,1,16,0ZM96,120H35.31l18.35-18.34A8,8,0,0,0,42.34,90.34l-32,32a8,8,0,0,0,0,11.32l32,32a8,8,0,0,0,11.32-11.32L35.31,136H96a8,8,0,0,0,0-16Zm149.66,2.34-32-32a8,8,0,0,0-11.32,11.32L220.69,120H160a8,8,0,0,0,0,16h60.69l-18.35,18.34a8,8,0,0,0,11.32,11.32l32-32A8,8,0,0,0,245.66,122.34Z",
4882
+ caretDown: "M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z",
4883
+ caretLeft: "M165.66,202.34a8,8,0,0,1-11.32,11.32l-80-80a8,8,0,0,1,0-11.32l80-80a8,8,0,0,1,11.32,11.32L91.31,128Z",
4884
+ caretRight: "M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z",
4885
+ caretUp: "M213.66,165.66a8,8,0,0,1-11.32,0L128,91.31,53.66,165.66a8,8,0,0,1-11.32-11.32l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,213.66,165.66Z",
4886
+ check: "M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z",
4887
+ checkCircle: "M173.66,98.34a8,8,0,0,1,0,11.32l-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35A8,8,0,0,1,173.66,98.34ZM232,128A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z",
4888
+ circle: "M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Z",
4889
+ copy: "M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z",
4890
+ cornersIn: "M152,96V48a8,8,0,0,1,16,0V88h40a8,8,0,0,1,0,16H160A8,8,0,0,1,152,96ZM96,152H48a8,8,0,0,0,0,16H88v40a8,8,0,0,0,16,0V160A8,8,0,0,0,96,152Zm112,0H160a8,8,0,0,0-8,8v48a8,8,0,0,0,16,0V168h40a8,8,0,0,0,0-16ZM96,40a8,8,0,0,0-8,8V88H48a8,8,0,0,0,0,16H96a8,8,0,0,0,8-8V48A8,8,0,0,0,96,40Z",
4891
+ cornersOut: "M216,48V88a8,8,0,0,1-16,0V56H168a8,8,0,0,1,0-16h40A8,8,0,0,1,216,48ZM88,200H56V168a8,8,0,0,0-16,0v40a8,8,0,0,0,8,8H88a8,8,0,0,0,0-16Zm120-40a8,8,0,0,0-8,8v32H168a8,8,0,0,0,0,16h40a8,8,0,0,0,8-8V168A8,8,0,0,0,208,160ZM88,40H48a8,8,0,0,0-8,8V88a8,8,0,0,0,16,0V56H88a8,8,0,0,0,0-16Z",
4892
+ folder: "M216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16H216.89A15.13,15.13,0,0,0,232,200.89V88A16,16,0,0,0,216,72Zm0,128H40V64H93.33L123.2,86.4A8,8,0,0,0,128,88h88Z",
4893
+ keyboard: "M224,48H32A16,16,0,0,0,16,64V192a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V64A16,16,0,0,0,224,48Zm0,144H32V64H224V192Zm-16-64a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H200A8,8,0,0,1,208,128Zm0-32a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16H200A8,8,0,0,1,208,96ZM72,160a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16h8A8,8,0,0,1,72,160Zm96,0a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,160Zm40,0a8,8,0,0,1-8,8h-8a8,8,0,0,1,0-16h8A8,8,0,0,1,208,160Z",
4894
+ magnifyingGlass: "M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z",
4895
+ magnifyingGlassMinus: "M152,112a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h64A8,8,0,0,1,152,112Zm77.66,117.66a8,8,0,0,1-11.32,0l-50.06-50.07a88.11,88.11,0,1,1,11.31-11.31l50.07,50.06A8,8,0,0,1,229.66,229.66ZM112,184a72,72,0,1,0-72-72A72.08,72.08,0,0,0,112,184Z",
4896
+ magnifyingGlassPlus: "M152,112a8,8,0,0,1-8,8H120v24a8,8,0,0,1-16,0V120H80a8,8,0,0,1,0-16h24V80a8,8,0,0,1,16,0v24h24A8,8,0,0,1,152,112Zm77.66,117.66a8,8,0,0,1-11.32,0l-50.06-50.07a88.11,88.11,0,1,1,11.31-11.31l50.07,50.06A8,8,0,0,1,229.66,229.66ZM112,184a72,72,0,1,0-72-72A72.08,72.08,0,0,0,112,184Z",
4897
+ microphone: "M128,176a48.05,48.05,0,0,0,48-48V64a48,48,0,0,0-96,0v64A48.05,48.05,0,0,0,128,176ZM96,64a32,32,0,0,1,64,0v64a32,32,0,0,1-64,0Zm40,143.6V240a8,8,0,0,1-16,0V207.6A80.11,80.11,0,0,1,48,128a8,8,0,0,1,16,0,64,64,0,0,0,128,0,8,8,0,0,1,16,0A80.11,80.11,0,0,1,136,207.6Z",
4898
+ paperPlaneRight: "M231.87,114l-168-95.89A16,16,0,0,0,40.92,37.34L71.55,128,40.92,218.67A16,16,0,0,0,56,240a16.15,16.15,0,0,0,7.93-2.1l167.92-96.05a16,16,0,0,0,.05-27.89ZM56,224a.56.56,0,0,0,0-.12L85.74,136H144a8,8,0,0,0,0-16H85.74L56.06,32.16A.46.46,0,0,0,56,32l168,95.83Z",
4899
+ pencilSimple: "M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z",
4900
+ repeat: "M24,128A72.08,72.08,0,0,1,96,56H204.69L194.34,45.66a8,8,0,0,1,11.32-11.32l24,24a8,8,0,0,1,0,11.32l-24,24a8,8,0,0,1-11.32-11.32L204.69,72H96a56.06,56.06,0,0,0-56,56,8,8,0,0,1-16,0Zm200-8a8,8,0,0,0-8,8,56.06,56.06,0,0,1-56,56H51.31l10.35-10.34a8,8,0,0,0-11.32-11.32l-24,24a8,8,0,0,0,0,11.32l24,24a8,8,0,0,0,11.32-11.32L51.31,200H160a72.08,72.08,0,0,0,72-72A8,8,0,0,0,224,120Z",
4901
+ sidebar: "M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM40,56H80V200H40ZM216,200H96V56H216V200Z",
4902
+ x: "M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"
4903
+ };
4904
+ ICON_NAMES = Object.keys(PATHS);
4905
+ });
4906
+
4907
+ // src/ui/selection-pill.ts
4908
+ function pillSpring(distance, base = SPRINGS.default) {
4909
+ return distance > 400 ? { duration: base.duration * 1.4, bounce: base.bounce } : base;
4910
+ }
4911
+ function pillBoxOf(row, container) {
4912
+ return {
4913
+ x: row.left - container.left - container.clientLeft + container.scrollLeft,
4914
+ y: row.top - container.top - container.clientTop + container.scrollTop,
4915
+ w: row.width,
4916
+ h: row.height
4917
+ };
4918
+ }
4919
+ function pillRadius(from, to, h, k) {
4920
+ const t = Math.abs(to.h - from.h) > 0.5 ? (h - from.h) / (to.h - from.h) : Number.isFinite(k) ? k : 1;
4921
+ return from.r + (to.r - from.r) * Math.min(1, Math.max(0, t));
4922
+ }
4923
+ function pillProgress(from, to, at) {
4924
+ const d = Math.hypot(to.x - from.x, to.y - from.y);
4925
+ if (d < 0.5)
4926
+ return 1;
4927
+ const p = ((at.x - from.x) * (to.x - from.x) + (at.y - from.y) * (to.y - from.y)) / (d * d);
4928
+ return Math.min(1, Math.max(0, p));
4929
+ }
4930
+ function planPill(state, next, flags = {}) {
4931
+ if (!next)
4932
+ return { kind: "hide" };
4933
+ if (!state.shown || !state.at || flags.first || flags.reduced || flags.snap)
4934
+ return { kind: "land", box: next };
4935
+ const t = state.target;
4936
+ if (t && t.x === next.x && t.y === next.y && t.w === next.w && t.h === next.h)
4937
+ return { kind: "stay" };
4938
+ const d = Math.hypot(next.x - state.at.x, next.y - state.at.y);
4939
+ return { kind: "travel", box: next, spring: pillSpring(d) };
4940
+ }
4941
+ function selectionPill(container, options) {
4942
+ const doc = container.ownerDocument;
4943
+ const win = doc.defaultView;
4944
+ injectUiStyles(doc);
4945
+ if (win.getComputedStyle(container).position === "static")
4946
+ container.style.position = "relative";
4947
+ const pill = doc.createElement("i");
4948
+ pill.className = "knobs-pick-pill";
4949
+ pill.setAttribute("aria-hidden", "true");
4950
+ pill.dataset.off = "";
4951
+ const copyOn = options.copy !== false;
4952
+ const copy = doc.createElement("div");
4953
+ copy.className = "knobs-pick-copy";
4954
+ copy.setAttribute("aria-hidden", "true");
4955
+ copy.inert = true;
4956
+ copy.dataset.off = "";
4957
+ const copyRows = doc.createElement("div");
4958
+ copyRows.className = "knobs-pick-copy-rows";
4959
+ copy.append(copyRows);
4960
+ container.prepend(pill);
4961
+ if (copyOn)
4962
+ container.append(copy);
4963
+ const { omega, zeta } = springMotion(SPRINGS.default);
4964
+ const springs = [0, 0, 0, 0].map(() => new Spring(0, omega, zeta, 0.01));
4965
+ const [sx, sy, sw, sh] = springs;
4966
+ let shown = false;
4967
+ let first = true;
4968
+ let target = null;
4969
+ let from = { x: 0, y: 0, w: 0, h: 0 };
4970
+ let fromR = 0;
4971
+ let toR = 0;
4972
+ let rowBoxes = [];
4973
+ const clones = new Map;
4974
+ let disposed = false;
4975
+ const radiusOf = (row) => options.radius?.(row) ?? (Number.parseFloat(win.getComputedStyle(row).borderTopLeftRadius) || 0);
4976
+ const rowsNow = () => options.rows ? [...options.rows()] : [...container.children].filter((n) => n !== pill && n !== copy);
4977
+ const measure = (row) => {
4978
+ const c = container.getBoundingClientRect();
4979
+ const r = row.getBoundingClientRect();
4980
+ return pillBoxOf(r, {
4981
+ left: c.left,
4982
+ top: c.top,
4983
+ scrollLeft: container.scrollLeft,
4984
+ scrollTop: container.scrollTop,
4985
+ clientLeft: container.clientLeft,
4986
+ clientTop: container.clientTop
4987
+ });
4988
+ };
4989
+ const at = () => ({ x: sx.x, y: sy.x, w: sw.x, h: sh.x });
4990
+ const syncCopy = (span) => {
4991
+ if (!copyOn)
4992
+ return;
4993
+ const keep = new Set;
4994
+ for (const { row, box } of rowBoxes) {
4995
+ if (!boxesMeet(box, span))
4996
+ continue;
4997
+ keep.add(row);
4998
+ let n = clones.get(row);
4999
+ if (!n) {
5000
+ n = row.cloneNode(true);
5001
+ n.removeAttribute("id");
5002
+ for (const d of n.querySelectorAll("[id]"))
5003
+ d.removeAttribute("id");
5004
+ clones.set(row, n);
5005
+ copyRows.append(n);
5006
+ }
5007
+ const s = n.style;
5008
+ s.position = "absolute";
5009
+ s.inset = "auto";
5010
+ s.left = `${box.x}px`;
5011
+ s.top = `${box.y}px`;
5012
+ s.width = `${box.w}px`;
5013
+ s.height = `${box.h}px`;
5014
+ s.margin = "0";
5015
+ s.transform = "none";
5016
+ s.clipPath = "";
5017
+ s.opacity = "";
5018
+ s.boxSizing = "border-box";
5019
+ s.color = options.color ?? "var(--_k-fg)";
5020
+ }
5021
+ for (const [row, n] of clones)
5022
+ if (!keep.has(row)) {
5023
+ n.remove();
5024
+ clones.delete(row);
5025
+ }
5026
+ };
5027
+ const paint = () => {
5028
+ const b = at();
5029
+ const x0 = grid(b.x);
5030
+ const y0 = grid(b.y);
5031
+ const w = grid(b.x + b.w) - x0;
5032
+ const h = grid(b.y + b.h) - y0;
5033
+ const r = target ? pillRadius({ h: from.h, r: fromR }, { h: target.h, r: toR }, b.h, pillProgress(from, target, b)) : toR;
5034
+ const radius = `${Math.max(0, Math.min(r, h / 2, w / 2)).toFixed(2)}px`;
5035
+ for (const n of copyOn ? [pill, copy] : [pill]) {
5036
+ n.style.transform = `translate3d(${x0}px,${y0}px,0)`;
5037
+ n.style.width = `${w}px`;
5038
+ n.style.height = `${h}px`;
5039
+ n.style.borderRadius = radius;
5040
+ }
5041
+ if (copyOn)
5042
+ copyRows.style.transform = `translate3d(${-x0}px,${-y0}px,0)`;
5043
+ };
5044
+ const loop = frameLoop(win, (dt) => {
5045
+ for (const s of springs)
5046
+ s.step(dt);
5047
+ paint();
5048
+ return springs.some((s) => !s.resting);
5049
+ });
5050
+ const show = (on) => {
5051
+ shown = on;
5052
+ pill.toggleAttribute("data-off", !on);
5053
+ copy.toggleAttribute("data-off", !on);
5054
+ container.toggleAttribute("data-pick-empty", !on);
5055
+ };
5056
+ const update = (o) => {
5057
+ if (disposed)
5058
+ return;
5059
+ const row = options.selected();
5060
+ const next = row?.isConnected ? measure(row) : null;
5061
+ const plan = planPill({ shown, at: shown ? at() : null, target }, next, {
5062
+ first,
5063
+ reduced: reducedMotion(win),
5064
+ ...o?.snap ? { snap: true } : {}
5065
+ });
5066
+ first = false;
5067
+ for (const n of clones.values())
5068
+ n.remove();
5069
+ clones.clear();
5070
+ rowBoxes = copyOn ? rowsNow().filter((r) => r.isConnected).map((r) => ({ row: r, box: measure(r) })) : [];
5071
+ if (plan.kind === "hide") {
5072
+ if (shown)
5073
+ show(false);
5074
+ return;
5075
+ }
5076
+ if (plan.kind === "stay") {
5077
+ syncCopy(boxUnion(at(), target));
5078
+ return;
5079
+ }
5080
+ const r = radiusOf(row);
5081
+ if (plan.kind === "land") {
5082
+ sx.snap(plan.box.x);
5083
+ sy.snap(plan.box.y);
5084
+ sw.snap(plan.box.w);
5085
+ sh.snap(plan.box.h);
5086
+ from = plan.box;
5087
+ fromR = r;
5088
+ toR = r;
5089
+ target = plan.box;
5090
+ syncCopy(plan.box);
5091
+ paint();
5092
+ loop.stop();
5093
+ if (!shown)
5094
+ show(true);
5095
+ return;
5096
+ }
5097
+ const m = springMotion(plan.spring);
5098
+ for (const s of springs) {
5099
+ s.omega = m.omega;
5100
+ s.zeta = m.zeta;
5101
+ }
5102
+ const now = at();
5103
+ fromR = target ? pillRadius({ h: from.h, r: fromR }, { h: target.h, r: toR }, now.h, pillProgress(from, target, now)) : r;
5104
+ from = now;
5105
+ toR = r;
5106
+ target = plan.box;
5107
+ sx.set(plan.box.x);
5108
+ sy.set(plan.box.y);
5109
+ sw.set(plan.box.w);
5110
+ sh.set(plan.box.h);
5111
+ syncCopy(boxUnion(now, plan.box));
5112
+ loop.wake();
5113
+ };
5114
+ const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => update({ snap: true })) : null;
5115
+ ro?.observe(container);
5116
+ update();
5117
+ return {
5118
+ element: pill,
5119
+ update,
5120
+ dispose() {
5121
+ disposed = true;
5122
+ loop.stop();
5123
+ ro?.disconnect();
5124
+ pill.remove();
5125
+ copy.remove();
5126
+ container.removeAttribute("data-pick-empty");
5127
+ }
5128
+ };
5129
+ }
5130
+ var boxesMeet = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h, boxUnion = (a, b) => {
5131
+ const x = Math.min(a.x, b.x);
5132
+ const y = Math.min(a.y, b.y);
5133
+ return {
5134
+ x,
5135
+ y,
5136
+ w: Math.max(a.x + a.w, b.x + b.w) - x,
5137
+ h: Math.max(a.y + a.h, b.y + b.h) - y
5138
+ };
5139
+ }, grid = (v) => Math.round(v * 64) / 64;
5140
+ var init_selection_pill = __esm(() => {
5141
+ init_motion();
5142
+ init_look();
5143
+ });
5144
+
4662
5145
  // src/core/storage.ts
4663
5146
  function browserStorage() {
4664
5147
  return typeof window === "undefined" ? null : window.localStorage;
@@ -4981,10 +5464,25 @@ function advance(t, dt, o) {
4981
5464
  }
4982
5465
  return { t: hi, ended: true, wrapped: false };
4983
5466
  }
4984
- 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;
5467
+ function moveHead(head, t, motion, catching, minGap = 0) {
5468
+ if (motion === "jump" && Math.abs(t - head.x) > minGap)
5469
+ return { x: head.x, target: t, catching: true };
5470
+ if (catching)
5471
+ return { x: head.x + (t - head.target), target: t, catching: true };
5472
+ return { x: t, target: t, catching: false };
5473
+ }
5474
+ function currentMarker(markers, t, fps) {
5475
+ let best = null;
5476
+ for (const m of markers)
5477
+ if (m.time <= t + 0.5 / fps && (!best || m.time >= best.time))
5478
+ best = m;
5479
+ return best;
5480
+ }
5481
+ 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;
4985
5482
  var init_timeline_model = __esm(() => {
4986
5483
  SECOND_STEPS = [0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600];
4987
5484
  FRAME_STEPS = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 9000, 18000];
5485
+ CATCH_UP = { duration: 0.15, bounce: 0 };
4988
5486
  });
4989
5487
 
4990
5488
  // src/ui/timeline.ts
@@ -4997,6 +5495,7 @@ function timeline(container, options) {
4997
5495
  let speed = options.speed ?? 1;
4998
5496
  let repeat = options.repeat ?? true;
4999
5497
  const arrows = options.arrows ?? "frames";
5498
+ const lane = options.variant === "lane";
5000
5499
  let time = Math.min(duration, Math.max(0, options.time ?? 0));
5001
5500
  let playing = false;
5002
5501
  let markers = [...options.markers ?? []];
@@ -5030,27 +5529,27 @@ function timeline(container, options) {
5030
5529
  root.setAttribute("aria-label", options.label ?? "Timeline");
5031
5530
  root.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight , . Home End [ ] I O M");
5032
5531
  applyLook2(root, options);
5532
+ if (lane)
5533
+ root.dataset.variant = "lane";
5033
5534
  const transport = el("div", "knobs-tl-transport");
5034
5535
  const playBtn = el("button", "knobs-tl-play");
5035
5536
  playBtn.type = "button";
5036
5537
  playBtn.tabIndex = -1;
5037
- const readout = el("div", "knobs-tl-time");
5038
- const now = el("span", "knobs-tl-now");
5039
- const sub = el("span", "knobs-tl-sub");
5040
- readout.append(now, sub);
5041
- transport.append(playBtn, readout);
5538
+ transport.append(playBtn);
5042
5539
  const track = el("div", "knobs-tl-track");
5043
5540
  const canvas = el("canvas", "knobs-tl-ruler");
5044
5541
  canvas.setAttribute("aria-hidden", "true");
5045
- const lane = el("div", "knobs-tl-lane");
5542
+ const markerLane = el("div", "knobs-tl-lane");
5046
5543
  const hover = el("div", "knobs-tl-hover");
5544
+ const tag = el("span", "knobs-tl-tag");
5545
+ tag.setAttribute("aria-hidden", "true");
5047
5546
  const head = el("div", "knobs-tl-head");
5048
5547
  head.id = `${id}-head`;
5049
5548
  head.setAttribute("role", "slider");
5050
5549
  head.setAttribute("aria-label", "Playhead");
5051
5550
  head.setAttribute("aria-valuemin", "0");
5052
5551
  head.setAttribute("aria-valuemax", String(duration));
5053
- track.append(canvas, lane, hover, head);
5552
+ track.append(canvas, markerLane, hover, head, tag);
5054
5553
  root.append(transport, track);
5055
5554
  container.append(root);
5056
5555
  let width = 1;
@@ -5097,13 +5596,14 @@ function timeline(container, options) {
5097
5596
  let drawn = "";
5098
5597
  const drawRuler = () => {
5099
5598
  const v = view();
5100
- const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${units}:${fps}`;
5599
+ const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${height}:${units}:${fps}`;
5101
5600
  if (key === drawn)
5102
5601
  return;
5103
5602
  drawn = key;
5104
5603
  const dpr = win.devicePixelRatio || 1;
5105
5604
  const w = Math.max(1, Math.round(width * dpr));
5106
- const h = Math.round(RULER * dpr);
5605
+ const rh = lane ? height : RULER;
5606
+ const h = Math.round(rh * dpr);
5107
5607
  if (canvas.width !== w || canvas.height !== h) {
5108
5608
  canvas.width = w;
5109
5609
  canvas.height = h;
@@ -5112,8 +5612,19 @@ function timeline(container, options) {
5112
5612
  if (!ctx)
5113
5613
  return;
5114
5614
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
5115
- ctx.clearRect(0, 0, width, RULER);
5615
+ ctx.clearRect(0, 0, width, rh);
5116
5616
  const ticks = rulerTicks(v, width, { fps, units });
5617
+ if (lane) {
5618
+ ctx.fillStyle = colors.tick;
5619
+ for (const t of ticks.minor)
5620
+ if (t >= -0.000000001 && t <= duration + 0.000000001)
5621
+ ctx.fillRect(xOf(t) - 0.5, rh - 4, 1, 4);
5622
+ ctx.fillStyle = colors.major;
5623
+ for (const m of ticks.major)
5624
+ if (m.t >= -0.000000001 && m.t <= duration + 0.000000001)
5625
+ ctx.fillRect(xOf(m.t) - 0.5, rh - 7, 1, 7);
5626
+ return;
5627
+ }
5117
5628
  const px = (t) => Math.round(xOf(t)) + 0.5;
5118
5629
  ctx.fillStyle = colors.line;
5119
5630
  ctx.fillRect(0, RULER - 1, width, 1);
@@ -5153,18 +5664,21 @@ function timeline(container, options) {
5153
5664
  n.dataset.id = m.id;
5154
5665
  const pin = el("span", "knobs-tl-pin");
5155
5666
  const label = el("span", "knobs-tl-mlabel");
5667
+ label.append(el("span", "knobs-tl-mtext knobs-scroll-fade-x"));
5156
5668
  n.append(pin, label);
5157
- lane.append(n);
5669
+ markerLane.append(n);
5158
5670
  markerEls.set(m.id, n);
5159
5671
  if (built)
5160
5672
  enter(n);
5161
5673
  }
5162
5674
  const label = n.querySelector(".knobs-tl-mlabel");
5163
- if (label.textContent !== (m.label ?? ""))
5164
- label.textContent = m.label ?? "";
5675
+ const text = label.firstElementChild;
5676
+ if (text.textContent !== (m.label ?? ""))
5677
+ text.textContent = m.label ?? "";
5165
5678
  label.hidden = !m.label;
5166
5679
  n.style.setProperty("--knobs-tl-color", m.color ?? "");
5167
5680
  n.toggleAttribute("data-selected", selected?.kind === "marker" && selected.id === m.id);
5681
+ n.toggleAttribute("data-current", m.id === currentId);
5168
5682
  n.title = `${m.label ? `${m.label} · ` : ""}${clock(m.time)}`;
5169
5683
  }
5170
5684
  placeMarkers();
@@ -5192,7 +5706,7 @@ function timeline(container, options) {
5192
5706
  b.dataset.edge = "end";
5193
5707
  const label = el("span", "knobs-tl-rlabel");
5194
5708
  n.append(label, a, b);
5195
- track.insertBefore(n, lane);
5709
+ track.insertBefore(n, markerLane);
5196
5710
  rangeEls.set(r.id, n);
5197
5711
  if (built)
5198
5712
  enter(n);
@@ -5241,16 +5755,17 @@ function timeline(container, options) {
5241
5755
  };
5242
5756
  const clock = (t) => units === "frames" ? `f${toFrame(t, fps)}` : `${timeLabel(t, "seconds", fps, 0.01)}`;
5243
5757
  let shownHead = time;
5758
+ let currentId = null;
5244
5759
  const renderHead = () => {
5245
5760
  head.style.transform = `translateX(${xOf(shownHead).toFixed(2)}px)`;
5246
- const main = units === "frames" ? `f${toFrame(time, fps)}` : `${time.toFixed(2)}s`;
5247
- const minor = units === "frames" ? `${time.toFixed(2)}s` : `f${toFrame(time, fps)}`;
5248
- if (now.textContent !== main)
5249
- now.textContent = main;
5250
- if (sub.textContent !== minor)
5251
- sub.textContent = minor;
5252
5761
  head.setAttribute("aria-valuenow", time.toFixed(3));
5253
5762
  head.setAttribute("aria-valuetext", `${time.toFixed(2)} seconds, frame ${toFrame(time, fps)}`);
5763
+ const cur = currentMarker(markers, time, fps)?.id ?? null;
5764
+ if (cur !== currentId) {
5765
+ markerEls.get(currentId ?? "")?.removeAttribute("data-current");
5766
+ currentId = cur;
5767
+ markerEls.get(cur ?? "")?.setAttribute("data-current", "");
5768
+ }
5254
5769
  };
5255
5770
  let viewSent = "";
5256
5771
  const render = () => {
@@ -5276,18 +5791,27 @@ function timeline(container, options) {
5276
5791
  };
5277
5792
  const caret = new Spring(time, GLIDE.omega, GLIDE.zeta, 0.00001);
5278
5793
  let gliding = false;
5279
- const setTime = (t, source, shown = t, glide = false) => {
5280
- const next = Math.min(duration, Math.max(0, t));
5281
- if (glide && !reducedMotion(win)) {
5794
+ const headTo = (shown, motion) => {
5795
+ const from = gliding ? { x: caret.x, target: caret.target } : { x: shownHead, target: shownHead };
5796
+ const jump = motion === "jump" && !reducedMotion(win);
5797
+ const next = moveHead(from, shown, jump ? "jump" : "follow", gliding, 2 / pxPerSec());
5798
+ if (next.catching) {
5282
5799
  if (!gliding)
5283
- caret.snap(shownHead);
5284
- caret.set(next);
5800
+ caret.snap(from.x);
5801
+ caret.x = next.x;
5802
+ caret.target = next.target;
5285
5803
  gliding = true;
5804
+ shownHead = caret.x;
5286
5805
  loop.wake();
5287
5806
  } else {
5288
5807
  gliding = false;
5289
- shownHead = shown;
5808
+ caret.snap(next.x);
5809
+ shownHead = next.x;
5290
5810
  }
5811
+ };
5812
+ const setTime = (t, source, shown = t, glide = false) => {
5813
+ const next = Math.min(duration, Math.max(0, t));
5814
+ headTo(Math.min(duration, Math.max(0, shown)), glide ? "jump" : "follow");
5291
5815
  const moved = next !== time;
5292
5816
  time = next;
5293
5817
  renderHead();
@@ -5328,8 +5852,7 @@ function timeline(container, options) {
5328
5852
  options.onCross?.(m);
5329
5853
  }
5330
5854
  time = step.t;
5331
- shownHead = time;
5332
- gliding = false;
5855
+ headTo(time, step.wrapped ? "jump" : "follow");
5333
5856
  emitSeek(time, "play");
5334
5857
  keepInView();
5335
5858
  if (step.ended) {
@@ -5342,7 +5865,7 @@ function timeline(container, options) {
5342
5865
  shownHead = caret.step(dt);
5343
5866
  if (caret.resting) {
5344
5867
  gliding = false;
5345
- shownHead = time;
5868
+ shownHead = caret.target;
5346
5869
  }
5347
5870
  }
5348
5871
  vStart.step(dt);
@@ -5355,9 +5878,9 @@ function timeline(container, options) {
5355
5878
  return;
5356
5879
  const r = loopRange();
5357
5880
  if (r && (time < r.start || time >= r.end - 0.5 / fps))
5358
- setTime(r.start, "code");
5881
+ setTime(r.start, "code", r.start, true);
5359
5882
  else if (!r && time >= duration - 0.5 / fps)
5360
- setTime(0, "code");
5883
+ setTime(0, "code", 0, true);
5361
5884
  playing = true;
5362
5885
  syncPlay();
5363
5886
  emitPlay(true);
@@ -5486,24 +6009,35 @@ function timeline(container, options) {
5486
6009
  };
5487
6010
  const localX = (e) => e.clientX - track.getBoundingClientRect().left;
5488
6011
  let drag = null;
5489
- const scrubTo = (x) => {
6012
+ const scrubTo = (x, press = false) => {
5490
6013
  const shownX = softClamp(x, xOf(0), xOf(duration), 10);
5491
6014
  const s = scrubTime(tOf(shownX), {
5492
6015
  duration,
5493
6016
  fps,
5494
6017
  pxPerSec: pxPerSec(),
5495
- stops: snapMarkers ? markers.map((m) => m.time) : [],
6018
+ stops: snapMarkers && !lane ? markers.map((m) => m.time) : [],
5496
6019
  frames: snapFrames
5497
6020
  });
5498
- setTime(s.t, "scrub", s.shown);
6021
+ setTime(s.t, "scrub", s.shown, press);
5499
6022
  root.toggleAttribute("data-stuck", s.stop !== null);
5500
6023
  };
6024
+ const showTag = (x) => {
6025
+ const at = Math.min(duration, Math.max(0, tOf(x)));
6026
+ const text = `${at.toFixed(2)} s`;
6027
+ if (tag.textContent !== text)
6028
+ tag.textContent = text;
6029
+ const cx = Math.min(width, Math.max(0, x));
6030
+ const flip = cx > width - tag.offsetWidth - 12;
6031
+ tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
6032
+ tag.dataset.on = "";
6033
+ };
6034
+ const hideTag = () => delete tag.dataset.on;
5501
6035
  track.addEventListener("pointerdown", (e) => {
5502
6036
  if (e.button !== 0 || editing)
5503
6037
  return;
5504
6038
  root.focus({ preventScroll: true });
5505
6039
  const t = e.target;
5506
- const markerEl = t.closest(".knobs-tl-marker");
6040
+ const markerEl = lane ? null : t.closest(".knobs-tl-marker");
5507
6041
  const edgeEl = t.closest(".knobs-tl-edge");
5508
6042
  const rangeEl = t.closest(".knobs-tl-range");
5509
6043
  track.setPointerCapture(e.pointerId);
@@ -5548,13 +6082,16 @@ function timeline(container, options) {
5548
6082
  root.dataset.scrubbing = "";
5549
6083
  for (const l of listeners)
5550
6084
  l.onScrub?.(true);
5551
- scrubTo(localX(e));
6085
+ scrubTo(localX(e), true);
6086
+ showTag(localX(e));
5552
6087
  }
5553
6088
  e.preventDefault();
5554
6089
  });
5555
6090
  track.addEventListener("pointermove", (e) => {
5556
6091
  const x = localX(e);
5557
6092
  hover.style.transform = `translateX(${x.toFixed(1)}px)`;
6093
+ if (!drag || drag.kind === "scrub")
6094
+ showTag(x);
5558
6095
  if (!drag)
5559
6096
  return;
5560
6097
  if (drag.kind === "scrub")
@@ -5600,8 +6137,10 @@ function timeline(container, options) {
5600
6137
  delete root.dataset.dragging;
5601
6138
  root.removeAttribute("data-stuck");
5602
6139
  if (d.kind === "scrub") {
5603
- shownHead = time;
6140
+ headTo(time, "follow");
5604
6141
  renderHead();
6142
+ if (!track.matches(":hover"))
6143
+ hideTag();
5605
6144
  for (const l of listeners)
5606
6145
  l.onScrub?.(false);
5607
6146
  if (d.wasPlaying)
@@ -5614,7 +6153,7 @@ function timeline(container, options) {
5614
6153
  } else {
5615
6154
  const m = markers.find((x) => x.id === d.id);
5616
6155
  if (m)
5617
- setTime(m.time, "marker");
6156
+ setTime(m.time, "marker", m.time, true);
5618
6157
  }
5619
6158
  } else if (d.kind === "edge" || d.moved)
5620
6159
  emitRanges();
@@ -5623,10 +6162,14 @@ function timeline(container, options) {
5623
6162
  track.addEventListener("pointercancel", endDrag);
5624
6163
  track.addEventListener("lostpointercapture", endDrag);
5625
6164
  track.addEventListener("pointerleave", () => {
5626
- if (!drag)
5627
- hover.style.transform = "translateX(-100px)";
6165
+ if (drag)
6166
+ return;
6167
+ hover.style.transform = "translateX(-100px)";
6168
+ hideTag();
5628
6169
  });
5629
6170
  track.addEventListener("dblclick", (e) => {
6171
+ if (lane)
6172
+ return;
5630
6173
  const markerEl = e.target.closest(".knobs-tl-marker");
5631
6174
  if (markerEl)
5632
6175
  return rename(markerEl.dataset.id);
@@ -5637,6 +6180,8 @@ function timeline(container, options) {
5637
6180
  rename(m.id);
5638
6181
  });
5639
6182
  track.addEventListener("wheel", (e) => {
6183
+ if (lane)
6184
+ return;
5640
6185
  e.preventDefault();
5641
6186
  const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? height : 1;
5642
6187
  if (e.ctrlKey || e.metaKey) {
@@ -5700,7 +6245,7 @@ function timeline(container, options) {
5700
6245
  if (!m)
5701
6246
  return;
5702
6247
  select({ kind: "marker", id: m.id });
5703
- return setTime(m.time, "marker");
6248
+ return setTime(m.time, "marker", m.time, true);
5704
6249
  }
5705
6250
  case "i":
5706
6251
  case "I":
@@ -5712,11 +6257,13 @@ function timeline(container, options) {
5712
6257
  return setEdge("end", time);
5713
6258
  case "m":
5714
6259
  case "M":
6260
+ if (lane)
6261
+ return;
5715
6262
  claim();
5716
6263
  addMarker(time);
5717
6264
  return;
5718
6265
  case "Enter":
5719
- if (selected?.kind === "marker") {
6266
+ if (selected?.kind === "marker" && !lane) {
5720
6267
  claim();
5721
6268
  rename(selected.id);
5722
6269
  }
@@ -5734,19 +6281,25 @@ function timeline(container, options) {
5734
6281
  return;
5735
6282
  case "=":
5736
6283
  case "+":
6284
+ if (lane)
6285
+ return;
5737
6286
  claim();
5738
6287
  return zoom(1.5);
5739
6288
  case "-":
6289
+ if (lane)
6290
+ return;
5740
6291
  claim();
5741
6292
  return zoom(1 / 1.5);
5742
6293
  case "0":
6294
+ if (lane)
6295
+ return;
5743
6296
  claim();
5744
6297
  return fit();
5745
6298
  }
5746
6299
  });
5747
6300
  const unregisterHints = options.hints === false ? undefined : hints.register(root, () => [
5748
6301
  { el: playBtn, label: playing ? "Pause" : "Play", activate: toggle },
5749
- ...markers.flatMap((m) => {
6302
+ ...(lane ? [] : markers).flatMap((m) => {
5750
6303
  const n = markerEls.get(m.id);
5751
6304
  if (!n || n.dataset.leaving !== undefined)
5752
6305
  return [];
@@ -5758,7 +6311,7 @@ function timeline(container, options) {
5758
6311
  ...label && !label.hidden ? { labelEl: label } : {},
5759
6312
  activate: () => {
5760
6313
  select({ kind: "marker", id: m.id });
5761
- setTime(m.time, "marker");
6314
+ setTime(m.time, "marker", m.time, true);
5762
6315
  root.focus({ preventScroll: true });
5763
6316
  }
5764
6317
  }
@@ -5770,7 +6323,7 @@ function timeline(container, options) {
5770
6323
  activate: () => root.focus({ preventScroll: true })
5771
6324
  }
5772
6325
  ], { listen: false, rank: 1 });
5773
- const menu = contextMenu(track, (e) => {
6326
+ const menu = lane ? null : contextMenu(track, (e) => {
5774
6327
  const t = e?.target ?? null;
5775
6328
  const markerEl = t?.closest(".knobs-tl-marker");
5776
6329
  const rangeEl = t?.closest(".knobs-tl-range");
@@ -5785,7 +6338,7 @@ function timeline(container, options) {
5785
6338
  run: () => {
5786
6339
  const m = markers.find((x) => x.id === mid);
5787
6340
  if (m)
5788
- setTime(m.time, "marker");
6341
+ setTime(m.time, "marker", m.time, true);
5789
6342
  }
5790
6343
  },
5791
6344
  { label: "Rename…", shortcut: "Enter", run: () => rename(mid) },
@@ -5949,7 +6502,7 @@ function timeline(container, options) {
5949
6502
  listeners.clear();
5950
6503
  loop.stop();
5951
6504
  ro.disconnect();
5952
- menu.dispose();
6505
+ menu?.dispose();
5953
6506
  root.remove();
5954
6507
  }
5955
6508
  };
@@ -5966,10 +6519,21 @@ var init_timeline = __esm(() => {
5966
6519
  init_timeline_model();
5967
6520
  INSTANT = springMotion(SPRINGS.instant);
5968
6521
  CALM = springMotion(SPRINGS.calm);
5969
- GLIDE = springMotion(SPRINGS.snappy);
6522
+ GLIDE = springMotion(CATCH_UP);
5970
6523
  });
5971
6524
 
5972
6525
  // src/ui/compare-model.ts
6526
+ function compareModes(modes, mode) {
6527
+ const list = [...new Set((modes ?? ["side", "wipe"]).filter((m) => ALL_MODES.includes(m)))];
6528
+ const offered = list.length ? list : ["side", "wipe"];
6529
+ return { modes: offered, mode: mode && offered.includes(mode) ? mode : offered[0] };
6530
+ }
6531
+ function modeForKey(key, modes) {
6532
+ const k = key.toLowerCase();
6533
+ if (/^[1-9]$/.test(k))
6534
+ return modes[Number(k) - 1] ?? null;
6535
+ return modes.find((m) => MODE_KEYS[m] === k) ?? null;
6536
+ }
5973
6537
  function mediaKind(src, kind) {
5974
6538
  if (kind)
5975
6539
  return kind;
@@ -6048,6 +6612,18 @@ function wipeGrab(x, rect, wipe, slop = 12) {
6048
6612
  const onLine = Math.abs(x - line) <= slop;
6049
6613
  return { onLine, offset: onLine ? x - line : 0 };
6050
6614
  }
6615
+ function seamLabels(x, left, right, wb, wa, gap = 8) {
6616
+ const room = (space, w) => Math.min(1, Math.max(0, (space - w - 2 * gap) / gap));
6617
+ return {
6618
+ before: { x: x - gap - wb, opacity: room(x - left, wb) },
6619
+ after: { x: x + gap, opacity: room(right - x, wa) }
6620
+ };
6621
+ }
6622
+ function gripTop(y, rect, grip, margin = 8) {
6623
+ const lo = rect.y + margin;
6624
+ const hi = rect.y + rect.h - margin - grip;
6625
+ return hi < lo ? rect.y + (rect.h - grip) / 2 : Math.min(hi, Math.max(lo, y - grip / 2));
6626
+ }
6051
6627
  function stripColumns(view, width, o) {
6052
6628
  const { fps } = o;
6053
6629
  const total = Math.max(1, Math.round(o.duration * fps));
@@ -6098,7 +6674,7 @@ function stripPeaks(diffs, o = {}) {
6098
6674
  });
6099
6675
  return out;
6100
6676
  }
6101
- var 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), seekTime = (t, fps, duration) => frameMidpoint(frameOf(t, fps), fps, duration), 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({
6677
+ 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), seekTime = (t, fps, duration) => frameMidpoint(frameOf(t, fps), fps, duration), 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({
6102
6678
  u: r.u - dx / Math.max(1, base.w) * r.w,
6103
6679
  v: r.v - dy / Math.max(1, base.h) * r.h,
6104
6680
  w: r.w
@@ -6117,6 +6693,8 @@ var VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), last
6117
6693
  h: a.h + (b.h - a.h) * k
6118
6694
  }), wipeX = (rect, wipe) => rect.x + Math.min(1, Math.max(0, wipe)) * rect.w, wipeAt = (x, rect, offset = 0) => rect.w <= 0 ? 0.5 : Math.min(1, Math.max(0, (x - offset - rect.x) / rect.w)), wipeKey = (wipe, dir, big = false) => Math.min(1, Math.max(0, Math.round((wipe + dir * (big ? 0.1 : 0.01)) * 1000) / 1000));
6119
6695
  var init_compare_model = __esm(() => {
6696
+ ALL_MODES = ["side", "wipe", "difference"];
6697
+ MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
6120
6698
  VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
6121
6699
  SEEK_IDLE = { seeking: false, pending: null };
6122
6700
  ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
@@ -6916,8 +7494,8 @@ async function createCompareGpu(canvas, onLost) {
6916
7494
  r(0, f.before);
6917
7495
  r(4, f.after);
6918
7496
  data.set([f.width, f.height, f.dpr, 0], 8);
6919
- data.set([f.wipeX, f.soft, 0, f.phase ? 1 : 0], 12);
6920
- data.set([f.from, f.to, f.mix, f.flick ? 1 : 0], 16);
7497
+ data.set([f.wipeX, f.soft, 0, 0], 12);
7498
+ data.set([f.from, f.to, f.mix, 0], 16);
6921
7499
  data.set([f.threshold, f.gain, ready[0], ready[1]], 20);
6922
7500
  data.set([f.accent[0], f.accent[1], f.accent[2], 1], 24);
6923
7501
  const l = f.loupe;
@@ -6959,8 +7537,8 @@ var MODE_INDEX, WGSL = `
6959
7537
  struct U {
6960
7538
  ra: vec4f, rb: vec4f,
6961
7539
  view: vec4f, // w, h, dpr, _
6962
- wipe: vec4f, // x, soft, _, phase
6963
- modes: vec4f, // from, to, mix, flick
7540
+ wipe: vec4f, // x, soft, _, _
7541
+ modes: vec4f, // from, to, mix, _
6964
7542
  diff: vec4f, // threshold, gain, readyA, readyB
6965
7543
  accent: vec4f,
6966
7544
  loupe: vec4f, // x, y, r, zoom (0: none)
@@ -7011,21 +7589,14 @@ fn takeB(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
7011
7589
 
7012
7590
  fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
7013
7591
 
7014
- fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f, a2: vec4f) -> vec4f {
7015
- let flick = u.modes.w > 0.5;
7592
+ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
7016
7593
  switch mode {
7017
- case 0u: {
7018
- let bb = select(b, vec4f(a2.rgb, b.a), flick);
7019
- return vec4f(a.rgb * a.a + bb.rgb * bb.a * (1.0 - a.a), a.a + bb.a * (1.0 - a.a));
7020
- }
7021
7594
  case 1u: {
7022
- if (flick) { return pm(a); }
7023
7595
  let s = max(u.wipe.y, 0.5);
7024
7596
  let m = smoothstep(u.wipe.x - s, u.wipe.x + s, p.x);
7025
7597
  return pm(vec4f(mix(a.rgb, b.rgb, m), mix(a.a, b.a, m)));
7026
7598
  }
7027
7599
  case 2u: {
7028
- if (flick) { return pm(a); }
7029
7600
  let d = abs(a.rgb - b.rgb);
7030
7601
  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);
7031
7602
  let c = mix(vec3f(luma(b.rgb) * 0.35), u.accent.rgb, h);
@@ -7033,8 +7604,8 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f, a2: vec4f) -> vec4f {
7033
7604
  }
7034
7605
  default: {}
7035
7606
  }
7036
- // Flicker: After, or Before while X is held or on the flicker's other beat.
7037
- return pm(select(b, a, flick || u.wipe.w > 0.5));
7607
+ // Side by side: each side in its own place, Before over After where they meet.
7608
+ return vec4f(a.rgb * a.a + b.rgb * b.a * (1.0 - a.a), a.a + b.a * (1.0 - a.a));
7038
7609
  }
7039
7610
 
7040
7611
  @fragment fn fs(@builtin(position) q: vec4f) -> @location(0) vec4f {
@@ -7052,16 +7623,14 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f, a2: vec4f) -> vec4f {
7052
7623
  }
7053
7624
  let a = takeA(p, u.ra, u.ca, near);
7054
7625
  let b = takeB(p, u.rb, u.cb, near);
7055
- // Before in After's place (X held, side by side): After's rect, as if Before were zoomed there.
7056
- let a2 = takeA(p, u.rb, u.cb, near);
7057
- var o = mix(composite(u32(u.modes.x), p, a, b, a2), composite(u32(u.modes.y), p, a, b, a2), u.modes.z);
7626
+ var o = mix(composite(u32(u.modes.x), p, a, b), composite(u32(u.modes.y), p, a, b), u.modes.z);
7058
7627
  if (near) { o = o + vec4f(0.05, 0.05, 0.06, 1.0) * (1.0 - o.a); }
7059
7628
  o = mix(o, vec4f(0.92), ring * 0.9);
7060
7629
  return o;
7061
7630
  }
7062
7631
  `, TEXTURE_BINDING = 4, COPY_DST = 2, RENDER_ATTACHMENT = 16, shared = null, lostListeners;
7063
7632
  var init_compare_gpu = __esm(() => {
7064
- MODE_INDEX = { side: 0, wipe: 1, difference: 2, flicker: 3 };
7633
+ MODE_INDEX = { side: 0, wipe: 1, difference: 2 };
7065
7634
  lostListeners = new Set;
7066
7635
  });
7067
7636
 
@@ -7447,7 +8016,9 @@ function compare(container, options) {
7447
8016
  ...options.theme ? { theme: options.theme } : {},
7448
8017
  ...options.appearance ? { appearance: options.appearance } : {}
7449
8018
  };
7450
- let mode = options.mode ?? "side";
8019
+ const offered = compareModes(options.modes, options.mode);
8020
+ const modeList = offered.modes;
8021
+ let mode = offered.mode;
7451
8022
  let wipe = Math.min(1, Math.max(0, options.wipe ?? 0.5));
7452
8023
  let loupeOn = options.loupe ?? false;
7453
8024
  let threshold = options.threshold ?? 4 / 255;
@@ -7465,7 +8036,7 @@ function compare(container, options) {
7465
8036
  root.setAttribute("role", "group");
7466
8037
  root.setAttribute("aria-roledescription", "compare");
7467
8038
  root.setAttribute("aria-label", options.label ?? "Compare");
7468
- root.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight Shift+ArrowLeft Shift+ArrowRight , . 1 2 3 4 S W D X Z L 0 = -");
8039
+ root.setAttribute("aria-keyshortcuts", `Space ArrowLeft ArrowRight Shift+ArrowLeft Shift+ArrowRight , . ${modeList.map((m, i) => `${i + 1} ${modeKey(m)}`).join(" ")} Z L 0 = -`);
7469
8040
  applyLook2(root, options);
7470
8041
  root.dataset.mode = mode;
7471
8042
  if (auto)
@@ -7474,35 +8045,37 @@ function compare(container, options) {
7474
8045
  const modes = el("div", "knobs-cmp-modes");
7475
8046
  modes.setAttribute("role", "radiogroup");
7476
8047
  modes.setAttribute("aria-label", "Mode");
7477
- const modeBtns = MODES2.map((m, i) => {
8048
+ modes.hidden = modeList.length < 2;
8049
+ const modeBtns = modeList.map((m, i) => {
7478
8050
  const b = el("button", "knobs-cmp-mode");
7479
8051
  b.type = "button";
7480
8052
  b.tabIndex = -1;
7481
- b.textContent = m.label;
7482
- b.title = `${m.label} · ${i + 1}${m.key ? ` or ${m.key}` : " (hold X to flick)"}`;
8053
+ b.textContent = MODE_LABEL[m].short;
8054
+ b.title = `${MODE_LABEL[m].long} · ${i + 1} or ${modeKey(m)}`;
7483
8055
  b.setAttribute("role", "radio");
7484
- b.setAttribute("aria-keyshortcuts", m.key ? `${i + 1} ${m.key}` : `${i + 1}`);
8056
+ b.setAttribute("aria-keyshortcuts", `${i + 1} ${modeKey(m)}`);
7485
8057
  b.addEventListener("click", () => {
7486
- setMode(m.mode);
8058
+ setMode(m);
7487
8059
  root.focus({ preventScroll: true });
7488
8060
  });
7489
8061
  modes.append(b);
7490
8062
  return b;
7491
8063
  });
7492
- const loupeBtn = el("button", "knobs-cmp-toggle");
7493
- loupeBtn.type = "button";
7494
- loupeBtn.tabIndex = -1;
7495
- loupeBtn.textContent = "Loupe";
7496
- loupeBtn.title = "Loupe · hold Z";
8064
+ const toggleBtn = (name, label, title) => {
8065
+ const b = el("button", "knobs-cmp-toggle");
8066
+ b.type = "button";
8067
+ b.tabIndex = -1;
8068
+ b.innerHTML = icon(name);
8069
+ b.setAttribute("aria-label", label);
8070
+ b.title = title;
8071
+ return b;
8072
+ };
8073
+ const loupeBtn = toggleBtn("magnifyingGlass", "Loupe", "Loupe · hold Z");
7497
8074
  loupeBtn.addEventListener("click", () => {
7498
8075
  setLoupe(!loupeOn);
7499
8076
  root.focus({ preventScroll: true });
7500
8077
  });
7501
- const loopBtn = el("button", "knobs-cmp-toggle");
7502
- loopBtn.type = "button";
7503
- loopBtn.tabIndex = -1;
7504
- loopBtn.textContent = "Loop";
7505
- loopBtn.title = "Loop · L (drag its ends on the timeline)";
8078
+ const loopBtn = toggleBtn("repeat", "Loop", "Loop · L (drag its ends on the timeline)");
7506
8079
  loopBtn.setAttribute("aria-keyshortcuts", "L");
7507
8080
  loopBtn.addEventListener("click", () => {
7508
8081
  api.setLoop(!loopNow());
@@ -7519,7 +8092,8 @@ function compare(container, options) {
7519
8092
  const zoomReset = el("button", "knobs-cmp-zoom-reset");
7520
8093
  zoomReset.type = "button";
7521
8094
  zoomReset.tabIndex = -1;
7522
- zoomReset.textContent = "Reset";
8095
+ zoomReset.innerHTML = icon("arrowCounterClockwise", { small: true });
8096
+ zoomReset.setAttribute("aria-label", "Reset zoom");
7523
8097
  zoomReset.title = "Fit · 0 or double-click";
7524
8098
  zoomReset.addEventListener("click", () => {
7525
8099
  api.setZoom(null);
@@ -7529,11 +8103,25 @@ function compare(container, options) {
7529
8103
  const labels = [el("div", "knobs-cmp-label"), el("div", "knobs-cmp-label")];
7530
8104
  labels[0].dataset.side = "before";
7531
8105
  labels[1].dataset.side = "after";
8106
+ const setLabel = (index, text) => {
8107
+ const l = labels[index];
8108
+ if (!l.firstElementChild) {
8109
+ const mark = el("span", "knobs-cmp-mark");
8110
+ mark.innerHTML = icon(index === 0 ? "x" : "check", { small: true });
8111
+ l.append(mark, el("span", "knobs-cmp-name"));
8112
+ }
8113
+ const name = l.lastElementChild;
8114
+ if (name.textContent !== text)
8115
+ name.textContent = text;
8116
+ };
7532
8117
  const handle = el("div", "knobs-cmp-handle");
7533
8118
  handle.setAttribute("role", "slider");
7534
8119
  handle.setAttribute("aria-label", "Wipe");
7535
8120
  handle.setAttribute("aria-valuemin", "0");
7536
8121
  handle.setAttribute("aria-valuemax", "100");
8122
+ const grip = el("span", "knobs-cmp-grip");
8123
+ grip.setAttribute("aria-hidden", "true");
8124
+ handle.append(grip);
7537
8125
  const note = el("div", "knobs-cmp-note");
7538
8126
  stage.append(canvas, split, labels[0], labels[1], handle, zoomPill, note);
7539
8127
  root.append(bar, stage);
@@ -7550,6 +8138,7 @@ function compare(container, options) {
7550
8138
  root.append(host);
7551
8139
  tl = timeline(host, {
7552
8140
  ...look,
8141
+ variant: "lane",
7553
8142
  duration: 1,
7554
8143
  fps: fps(),
7555
8144
  arrows: { step: 0.5, shift: 2 },
@@ -7568,9 +8157,9 @@ function compare(container, options) {
7568
8157
  const fade = new Spring(1, FADE2.omega, FADE2.zeta, 0.001);
7569
8158
  let fromMode = mode;
7570
8159
  let toMode = mode;
7571
- let phase = false;
7572
- let flickerTimer = 0;
7573
- let flickHeld = false;
8160
+ const wipeLine = new Spring(wipe, WIPE_CATCH.omega, WIPE_CATCH.zeta, 0.0001);
8161
+ let wipeCatching = false;
8162
+ let gripY = null;
7574
8163
  let loupeHeld = false;
7575
8164
  let pointer = null;
7576
8165
  let drag = null;
@@ -7888,25 +8477,16 @@ function compare(container, options) {
7888
8477
  gpu = g;
7889
8478
  setRenderer("gpu");
7890
8479
  };
7891
- const syncFlicker = () => {
7892
- win.clearInterval(flickerTimer);
7893
- flickerTimer = 0;
7894
- phase = false;
7895
- if (mode === "flicker")
7896
- flickerTimer = win.setInterval(() => {
7897
- phase = !phase;
7898
- requestDraw();
7899
- }, FLICKER_MS);
7900
- };
7901
8480
  const syncModeUi = () => {
7902
8481
  root.dataset.mode = mode;
7903
- MODES2.forEach((m, i) => {
7904
- modeBtns[i].setAttribute("aria-checked", String(m.mode === mode));
8482
+ modeList.forEach((m, i) => {
8483
+ modeBtns[i].setAttribute("aria-checked", String(m === mode));
7905
8484
  });
7906
8485
  handle.tabIndex = mode === "wipe" ? 0 : -1;
8486
+ modePill?.update();
7907
8487
  };
7908
8488
  const setMode = (next, emit = true) => {
7909
- if (next === mode)
8489
+ if (next === mode || !modeList.includes(next))
7910
8490
  return;
7911
8491
  mode = next;
7912
8492
  const reduced = reducedMotion(win);
@@ -7922,19 +8502,29 @@ function compare(container, options) {
7922
8502
  else
7923
8503
  lay.set(next === "side" ? 0 : 1);
7924
8504
  syncModeUi();
7925
- syncFlicker();
7926
8505
  if (emit)
7927
8506
  options.onMode?.(next);
7928
8507
  requestDraw();
7929
8508
  };
7930
- const setWipe = (next, emit = true) => {
8509
+ const setWipe = (next, emit = true, jump = false) => {
7931
8510
  const w = Math.min(1, Math.max(0, next));
7932
- if (w === wipe)
8511
+ if (w === wipe && !jump)
7933
8512
  return;
7934
8513
  wipe = w;
8514
+ const minGap = 2 / Math.max(1, wipeRect(layoutNow().L.overlay).w);
8515
+ const head = moveHead({ x: wipeLine.x, target: wipeLine.target }, w, jump && !reducedMotion(win) ? "jump" : "follow", wipeCatching, minGap);
8516
+ wipeCatching = head.catching;
8517
+ if (head.catching) {
8518
+ wipeLine.x = head.x;
8519
+ wipeLine.target = head.target;
8520
+ } else
8521
+ wipeLine.snap(head.x);
7935
8522
  if (emit)
7936
8523
  options.onWipe?.(w);
7937
- drawNow();
8524
+ if (head.catching)
8525
+ requestDraw();
8526
+ else
8527
+ drawNow();
7938
8528
  };
7939
8529
  const setLoupe = (on) => {
7940
8530
  loupeOn = on;
@@ -8087,28 +8677,44 @@ function compare(container, options) {
8087
8677
  viewSent = key;
8088
8678
  options.onView({ zoom: clampZoom(z), loop: l });
8089
8679
  };
8680
+ const labelsIn = (m, r, wr, wx) => {
8681
+ if (m === "wipe") {
8682
+ const at = seamLabels(wx, wr.x, wr.x + wr.w, labelW[0], labelW[1], INSET);
8683
+ return [at.before, at.after];
8684
+ }
8685
+ const ax = r.after.x + INSET + (r.after.w - labelW[1] - INSET * 2) * lay.x;
8686
+ return [
8687
+ { x: r.before.x + INSET, opacity: 1 },
8688
+ { x: ax, opacity: 1 }
8689
+ ];
8690
+ };
8691
+ const placeGrip = (wr) => {
8692
+ const gy = gripTop(gripY ?? wr.y + wr.h / 2, wr, GRIP.h) - wr.y;
8693
+ grip.style.transform = `translate3d(0,${gy.toFixed(2)}px,0)`;
8694
+ };
8090
8695
  const draw = () => {
8091
8696
  dirty = false;
8092
8697
  const r = layoutNow();
8093
8698
  const wr = wipeRect(r.L.overlay);
8094
- const wx = wipeX(wr, wipe);
8699
+ const wx = wipeX(wr, wipeLine.x);
8095
8700
  const k = lay.x;
8096
- const ax = r.after.x + INSET + (r.after.w - labelW[1] - INSET * 2) * k;
8097
- labels[0].style.transform = `translate(${(r.before.x + INSET).toFixed(2)}px,${(r.before.y + INSET).toFixed(2)}px)`;
8098
- labels[1].style.transform = `translate(${ax.toFixed(2)}px,${(r.after.y + INSET).toFixed(2)}px)`;
8099
- const showsBefore = flickHeld || mode === "flicker" && phase;
8100
- const flickering = flickHeld || mode === "flicker";
8101
- labels[0].toggleAttribute("data-lit", flickering && showsBefore);
8102
- labels[1].toggleAttribute("data-lit", flickering && !showsBefore);
8103
- labels[0].toggleAttribute("data-dim", flickering && !showsBefore || mode === "wipe" && wipe < 0.02);
8104
- labels[1].toggleAttribute("data-dim", flickering && showsBefore || mode === "wipe" && wipe > 0.98);
8701
+ const a = labelsIn(fromMode, r, wr, wx);
8702
+ const b = labelsIn(toMode, r, wr, wx);
8703
+ const f = Math.min(1, Math.max(0, fade.x));
8704
+ const ys = [r.before.y + INSET, r.after.y + INSET];
8705
+ for (const i of [0, 1]) {
8706
+ const x = a[i].x + (b[i].x - a[i].x) * f;
8707
+ labels[i].style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
8708
+ labels[i].style.opacity = (a[i].opacity + (b[i].opacity - a[i].opacity) * f).toFixed(3);
8709
+ }
8105
8710
  const top = Math.min(r.L.side.before.y, r.L.side.after.y);
8106
8711
  split.style.transform = `translate(${r.L.side.split.toFixed(2)}px,${top.toFixed(2)}px)`;
8107
8712
  split.style.height = `${Math.max(0, r.L.side.after.h).toFixed(2)}px`;
8108
8713
  split.style.opacity = String(Math.max(0, 1 - k * 2));
8109
- handle.toggleAttribute("data-on", mode === "wipe" && !flickHeld);
8714
+ handle.toggleAttribute("data-on", mode === "wipe");
8110
8715
  handle.style.transform = `translate(${wx.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
8111
8716
  handle.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
8717
+ placeGrip(wr);
8112
8718
  handle.setAttribute("aria-valuenow", String(Math.round(wipe * 100)));
8113
8719
  handle.setAttribute("aria-valuetext", `${Math.round(wipe * 100)}% Before`);
8114
8720
  const pct = `${Math.round(100 / zw.x)}%`;
@@ -8131,7 +8737,7 @@ function compare(container, options) {
8131
8737
  const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, cutAt));
8132
8738
  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)` : "";
8133
8739
  };
8134
- const cut = mode === "wipe" && !flickHeld ? wx : Number.POSITIVE_INFINITY;
8740
+ const cut = mode === "wipe" ? wx : Number.POSITIVE_INFINITY;
8135
8741
  for (const s of sides) {
8136
8742
  const rect = s.index === 0 ? r.zBefore : r.zAfter;
8137
8743
  const clip = s.index === 0 ? r.before : r.after;
@@ -8140,10 +8746,8 @@ function compare(container, options) {
8140
8746
  place(s.preview, rect, clip, at);
8141
8747
  if (s.index !== 0)
8142
8748
  continue;
8143
- for (const n of [s.el, s.preview]) {
8144
- n.style.mixBlendMode = mode === "difference" && !flickHeld ? "difference" : "";
8145
- n.style.opacity = mode === "flicker" && !showsBefore ? "0" : "";
8146
- }
8749
+ for (const n of [s.el, s.preview])
8750
+ n.style.mixBlendMode = mode === "difference" ? "difference" : "";
8147
8751
  }
8148
8752
  return;
8149
8753
  }
@@ -8164,8 +8768,6 @@ function compare(container, options) {
8164
8768
  from: MODE_INDEX[fromMode],
8165
8769
  to: MODE_INDEX[toMode],
8166
8770
  mix: Math.min(1, Math.max(0, fade.x)),
8167
- flick: flickHeld,
8168
- phase,
8169
8771
  threshold,
8170
8772
  gain: 1,
8171
8773
  accent,
@@ -8181,6 +8783,11 @@ function compare(container, options) {
8181
8783
  const loop = frameLoop(win, (dt) => {
8182
8784
  lay.step(dt);
8183
8785
  fade.step(dt);
8786
+ if (wipeCatching) {
8787
+ wipeLine.step(dt);
8788
+ if (wipeLine.resting)
8789
+ wipeCatching = false;
8790
+ }
8184
8791
  zu.step(dt);
8185
8792
  zv.step(dt);
8186
8793
  zw.step(dt);
@@ -8214,7 +8821,7 @@ function compare(container, options) {
8214
8821
  pushFrame(s);
8215
8822
  }
8216
8823
  }
8217
- const moving = !lay.resting || !fade.resting || !zoomResting();
8824
+ const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching;
8218
8825
  if (moving)
8219
8826
  dirty = true;
8220
8827
  if (auto && !lay.resting)
@@ -8357,16 +8964,19 @@ function compare(container, options) {
8357
8964
  e.preventDefault();
8358
8965
  return startPinch();
8359
8966
  }
8360
- if (mode === "wipe" && !flickHeld) {
8967
+ if (mode === "wipe") {
8361
8968
  const wr = wipeRect(layoutNow().L.overlay);
8362
8969
  const inside = p.x >= wr.x - SLOP && p.x <= wr.x + wr.w + SLOP && p.y >= wr.y && p.y <= wr.y + wr.h;
8363
- const grab = wipeGrab(p.x, wr, wipe, SLOP);
8970
+ const grab = wipeGrab(p.x, wr, wipeLine.x, SLOP);
8364
8971
  if (inside && (grab.onLine || !zoomed())) {
8365
8972
  drag = { kind: "wipe", offset: grab.offset };
8366
8973
  root.dataset.dragging = "";
8974
+ gripY = p.y;
8367
8975
  e.preventDefault();
8368
8976
  if (!grab.onLine)
8369
- setWipe(wipeAt(p.x, wr));
8977
+ setWipe(wipeAt(p.x, wr), true, true);
8978
+ else
8979
+ drawNow();
8370
8980
  return;
8371
8981
  }
8372
8982
  }
@@ -8392,6 +9002,11 @@ function compare(container, options) {
8392
9002
  pinch = { d, x: mid.x, y: mid.y };
8393
9003
  return handZoom(zoomAbout(panned, k, q.pu, q.pv));
8394
9004
  }
9005
+ if (mode === "wipe" && drag?.kind !== "pan") {
9006
+ gripY = p.y;
9007
+ if (!drag)
9008
+ placeGrip(wipeRect(layoutNow().L.overlay));
9009
+ }
8395
9010
  if (drag?.kind === "wipe")
8396
9011
  setWipe(wipeAt(p.x, wipeRect(layoutNow().L.overlay), drag.offset));
8397
9012
  else if (drag?.kind === "pan") {
@@ -8464,12 +9079,6 @@ function compare(container, options) {
8464
9079
  const z = { u: zu.target, v: zv.target, w: zw.target, h: zw.target };
8465
9080
  glideZoom(zoomAbout(z, factor, 0.5, 0.5));
8466
9081
  };
8467
- const holdFlick = (on) => {
8468
- if (flickHeld === on)
8469
- return;
8470
- flickHeld = on;
8471
- drawNow();
8472
- };
8473
9082
  const holdLoupe = (on) => {
8474
9083
  if (loupeHeld === on)
8475
9084
  return;
@@ -8515,25 +9124,17 @@ function compare(container, options) {
8515
9124
  case "s":
8516
9125
  case "w":
8517
9126
  case "d":
8518
- if (e.altKey || e.shiftKey || key === "s" && claimsHintKey(doc, key))
8519
- return;
8520
- claim();
8521
- return setMode(KEY_MODE[key]);
8522
9127
  case "1":
8523
9128
  case "2":
8524
- case "3":
8525
- case "4":
8526
- if (e.altKey || e.shiftKey)
9129
+ case "3": {
9130
+ if (e.altKey || e.shiftKey || key === "s" && claimsHintKey(doc, key))
8527
9131
  return;
8528
- claim();
8529
- return setMode(MODES2[Number(key) - 1].mode);
8530
- case "x":
8531
- if (e.altKey)
9132
+ const m = modeForKey(key, modeList);
9133
+ if (!m)
8532
9134
  return;
8533
9135
  claim();
8534
- if (!e.repeat)
8535
- holdFlick(true);
8536
- return;
9136
+ return setMode(m);
9137
+ }
8537
9138
  case "z":
8538
9139
  if (e.altKey)
8539
9140
  return;
@@ -8574,7 +9175,7 @@ function compare(container, options) {
8574
9175
  old.preview.replaceWith(s.preview);
8575
9176
  sides[index] = s;
8576
9177
  note.textContent = "";
8577
- labels[index].textContent = labelOf(index);
9178
+ setLabel(index, labelOf(index));
8578
9179
  if (near)
8579
9180
  loadSide(s);
8580
9181
  readColors();
@@ -8585,12 +9186,15 @@ function compare(container, options) {
8585
9186
  stage.insertBefore(sides[1].preview, labels[0]);
8586
9187
  stage.insertBefore(sides[0].el, labels[0]);
8587
9188
  stage.insertBefore(sides[0].preview, labels[0]);
8588
- labels[0].textContent = labelOf(0);
8589
- labels[1].textContent = labelOf(1);
9189
+ setLabel(0, labelOf(0));
9190
+ setLabel(1, labelOf(1));
8590
9191
  loupeBtn.setAttribute("aria-pressed", String(loupeOn));
8591
9192
  loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
9193
+ const modePill = modeList.length > 1 ? selectionPill(modes, {
9194
+ rows: () => modeBtns,
9195
+ selected: () => modeBtns[modeList.indexOf(mode)] ?? null
9196
+ }) : null;
8592
9197
  syncModeUi();
8593
- syncFlicker();
8594
9198
  const unlisten = tl?.listen({
8595
9199
  onSeek: (t, source) => {
8596
9200
  if (source === "play") {
@@ -8648,24 +9252,20 @@ function compare(container, options) {
8648
9252
  });
8649
9253
  root.addEventListener("keydown", onKey);
8650
9254
  root.addEventListener("keyup", (e) => {
8651
- const key = e.key.toLowerCase();
8652
- if (key === "x")
8653
- holdFlick(false);
8654
- else if (key === "z")
9255
+ if (e.key.toLowerCase() === "z")
8655
9256
  holdLoupe(false);
8656
9257
  });
8657
9258
  root.addEventListener("focusout", (e) => {
8658
9259
  if (root.contains(e.relatedTarget))
8659
9260
  return;
8660
- holdFlick(false);
8661
9261
  holdLoupe(false);
8662
9262
  });
8663
9263
  const unregisterHints = options.hints === false ? undefined : hints.register(root, () => [
8664
- ...MODES2.map((m, i) => ({
9264
+ ...modeList.length > 1 ? modeList.map((m, i) => ({
8665
9265
  el: modeBtns[i],
8666
- label: m.label,
8667
- activate: () => setMode(m.mode)
8668
- })),
9266
+ label: MODE_LABEL[m].long,
9267
+ activate: () => setMode(m)
9268
+ })) : [],
8669
9269
  { el: loopBtn, label: "Loop", activate: () => api.setLoop(!loopNow()) },
8670
9270
  ...loupeBtn.hidden ? [] : [{ el: loupeBtn, label: "Loupe", activate: () => setLoupe(!loupeOn) }],
8671
9271
  ...zoomed() ? [{ el: zoomReset, label: "Reset zoom", activate: () => api.setZoom(null) }] : [],
@@ -8690,6 +9290,7 @@ function compare(container, options) {
8690
9290
  get mode() {
8691
9291
  return mode;
8692
9292
  },
9293
+ modes: modeList,
8693
9294
  get wipe() {
8694
9295
  return wipe;
8695
9296
  },
@@ -8726,11 +9327,11 @@ function compare(container, options) {
8726
9327
  if (before)
8727
9328
  replace(0, before);
8728
9329
  else if (next.before)
8729
- labels[0].textContent = next.before.label ?? "Before";
9330
+ setLabel(0, next.before.label ?? "Before");
8730
9331
  if (after)
8731
9332
  replace(1, after);
8732
9333
  else if (next.after)
8733
- labels[1].textContent = next.after.label ?? "After";
9334
+ setLabel(1, next.after.label ?? "After");
8734
9335
  if (before || after)
8735
9336
  strip?.update({ ...before ? { before } : {}, ...after ? { after } : {} });
8736
9337
  if (next.before || next.after)
@@ -8745,7 +9346,7 @@ function compare(container, options) {
8745
9346
  tl?.update({ fps: next.fps });
8746
9347
  requestDraw();
8747
9348
  },
8748
- seek: (t) => tl ? tl.seek(t) : ownSeek(t),
9349
+ seek: (t, o) => tl ? tl.seek(t, o?.glide ? { glide: true } : undefined) : ownSeek(t),
8749
9350
  seekBy(d) {
8750
9351
  const total = duration || tl?.duration || 0;
8751
9352
  const t = Math.min(total, Math.max(0, now() + d));
@@ -8786,7 +9387,7 @@ function compare(container, options) {
8786
9387
  dispose() {
8787
9388
  disposed = true;
8788
9389
  loop.stop();
8789
- win.clearInterval(flickerTimer);
9390
+ modePill?.dispose();
8790
9391
  win.clearTimeout(releaseTimer);
8791
9392
  ro.disconnect();
8792
9393
  nearIo?.disconnect();
@@ -8816,7 +9417,7 @@ function compare(container, options) {
8816
9417
  wake();
8817
9418
  return api;
8818
9419
  }
8819
- var COMPARE_FFMPEG = "ffmpeg -i in.mov -vf fps=30 -fps_mode cfr -c:v libx264 -preset slow -crf 18 -g 15 -keyint_min 15 -pix_fmt yuv420p -movflags +faststart -an out.mp4", LAYOUT, FADE2, FADE_REDUCED, ZOOM, FLICKER_MS = 400, SLOP = 12, INSET = 8, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODES2, KEY_MODE;
9420
+ var COMPARE_FFMPEG = "ffmpeg -i in.mov -vf fps=30 -fps_mode cfr -c:v libx264 -preset slow -crf 18 -g 15 -keyint_min 15 -pix_fmt yuv420p -movflags +faststart -an out.mp4", LAYOUT, FADE2, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
8820
9421
  var init_compare = __esm(() => {
8821
9422
  init_motion();
8822
9423
  init_page_hints();
@@ -8824,21 +9425,24 @@ var init_compare = __esm(() => {
8824
9425
  init_compare_gpu();
8825
9426
  init_compare_model();
8826
9427
  init_compare_strip();
9428
+ init_icons();
8827
9429
  init_look();
8828
9430
  init_rgba();
9431
+ init_selection_pill();
8829
9432
  init_timeline();
9433
+ init_timeline_model();
8830
9434
  LAYOUT = springMotion({ duration: 0.35, bounce: 0 });
8831
9435
  FADE2 = springMotion(SPRINGS.snappy);
8832
9436
  FADE_REDUCED = springMotion(FADE);
8833
9437
  ZOOM = springMotion({ duration: 0.35, bounce: 0 });
9438
+ WIPE_CATCH = springMotion(CATCH_UP);
9439
+ GRIP = { w: 16, h: 28 };
8834
9440
  LOUPE = { r: 88, zoom: 4 };
8835
- MODES2 = [
8836
- { mode: "side", label: "Side by side", key: "S" },
8837
- { mode: "wipe", label: "Wipe", key: "W" },
8838
- { mode: "difference", label: "Difference", key: "D" },
8839
- { mode: "flicker", label: "Flicker", key: "" }
8840
- ];
8841
- KEY_MODE = { s: "side", w: "wipe", d: "difference" };
9441
+ MODE_LABEL = {
9442
+ side: { short: "Side", long: "Side by side" },
9443
+ wipe: { short: "Wipe", long: "Wipe" },
9444
+ difference: { short: "Difference", long: "Difference" }
9445
+ };
8842
9446
  });
8843
9447
 
8844
9448
  // src/ui/scrollbar.ts
@@ -8960,34 +9564,27 @@ function tree(container, options) {
8960
9564
  const empty = doc.createElement("div");
8961
9565
  empty.className = "knobs-tree-empty";
8962
9566
  empty.hidden = true;
8963
- const cursor = doc.createElement("div");
8964
- cursor.className = "knobs-tree-cursor";
8965
- root.append(space, cursor, line, empty);
8966
- const cursorY = new Spring(0, MOVE.omega, MOVE.zeta, 0.1);
8967
- let cursorOn = false;
8968
- const drawCursor = () => {
8969
- cursor.style.transform = `translate3d(0,${Math.round(cursorY.x * 2) / 2}px,0)`;
9567
+ root.append(space, line, empty);
9568
+ let pill = null;
9569
+ let picked = null;
9570
+ let travelUntil = 0;
9571
+ const pickedRow = () => {
9572
+ const id = sel.ids.length === 1 ? sel.ids[0] : null;
9573
+ const v = id === null ? undefined : views.get(id);
9574
+ return v && !v.leaving && rowIndex.has(v.id) ? v : null;
8970
9575
  };
8971
9576
  const placeCursor = (animate) => {
8972
- if (!options.glide)
9577
+ if (!pill)
8973
9578
  return;
8974
9579
  const id = sel.ids.length === 1 ? sel.ids[0] : null;
8975
- const i = id === null ? undefined : rowIndex.get(id);
8976
- root.toggleAttribute("data-glide", i !== undefined);
8977
- if (i === undefined) {
8978
- cursor.removeAttribute("data-on");
8979
- cursorOn = false;
8980
- return;
8981
- }
8982
- if (cursorOn && animate && !reducedMotion(win))
8983
- cursorY.set(i * h);
8984
- else
8985
- cursorY.snap(i * h);
8986
- cursorOn = true;
8987
- cursor.setAttribute("data-on", "");
8988
- drawCursor();
8989
- if (!cursorY.resting)
8990
- loop.wake();
9580
+ root.toggleAttribute("data-glide", id !== null && rowIndex.has(id));
9581
+ const fresh = id !== picked;
9582
+ picked = id;
9583
+ if (animate && fresh) {
9584
+ travelUntil = performance.now() + 450;
9585
+ pill.update();
9586
+ } else if (performance.now() >= travelUntil)
9587
+ pill.update({ snap: true });
8991
9588
  };
8992
9589
  container.append(root);
8993
9590
  const scrollbar = overlayScrollbar(root, space);
@@ -9016,6 +9613,12 @@ function tree(container, options) {
9016
9613
  };
9017
9614
  const views = new Map;
9018
9615
  const pool = [];
9616
+ if (options.glide)
9617
+ pill = selectionPill(root, {
9618
+ rows: () => [...views.values()].filter((v) => !v.leaving).map((v) => v.el),
9619
+ selected: () => pickedRow()?.el ?? null,
9620
+ radius: (row) => Number.parseFloat(win.getComputedStyle(row, "::before").borderTopLeftRadius) || 0
9621
+ });
9019
9622
  const makeView = (id) => {
9020
9623
  const v = pool.pop() ?? createView();
9021
9624
  v.id = id;
@@ -9196,12 +9799,6 @@ function tree(container, options) {
9196
9799
  function frame(dt) {
9197
9800
  let moving = false;
9198
9801
  let bottom = rows.length * h;
9199
- if (options.glide && !cursorY.resting) {
9200
- cursorY.step(dt);
9201
- drawCursor();
9202
- if (!cursorY.resting)
9203
- moving = true;
9204
- }
9205
9802
  for (const v of views.values()) {
9206
9803
  v.y.step(dt);
9207
9804
  if (!v.y.resting)
@@ -9221,6 +9818,8 @@ function tree(container, options) {
9221
9818
  }
9222
9819
  setSpace(moving ? bottom : rows.length * h);
9223
9820
  trim();
9821
+ if (pill && moving && performance.now() >= travelUntil)
9822
+ pill.update({ snap: true });
9224
9823
  return moving;
9225
9824
  }
9226
9825
  const trim = () => {
@@ -9849,6 +10448,7 @@ function tree(container, options) {
9849
10448
  autoScroll.stop();
9850
10449
  ro.disconnect();
9851
10450
  scrollbar.dispose();
10451
+ pill?.dispose();
9852
10452
  dragging?.ghost.remove();
9853
10453
  root.remove();
9854
10454
  }
@@ -9867,6 +10467,7 @@ var init_tree = __esm(() => {
9867
10467
  init_motion();
9868
10468
  init_page_hints();
9869
10469
  init_look();
10470
+ init_selection_pill();
9870
10471
  init_tree_model();
9871
10472
  MOVE = springMotion(SPRINGS.default);
9872
10473
  });
@@ -9916,33 +10517,29 @@ var init_diff_css = __esm(() => {
9916
10517
  .knobs-diff-fill{flex:1}
9917
10518
  .knobs-diff-nav{flex:none;display:inline-flex;align-items:center;gap:1px;color:var(--_k-fg-3);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums}
9918
10519
  .knobs-diff-nav>span{min-width:3.6em;text-align:center}
9919
- :where(.knobs-ui) :is(.knobs-diff-btn,.knobs-diff-seg>button,.knobs-diff-viewed,.knobs-diff-filter>input,.knobs-diff-filter>button){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;border-radius:0;
10520
+ :where(.knobs-ui) :is(.knobs-diff-btn,.knobs-diff-choice,.knobs-diff-viewed,.knobs-diff-filter>input,.knobs-diff-filter>button){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;border-radius:0;
9920
10521
  margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
9921
- .knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;
10522
+ .knobs-ui .knobs-diff-btn{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:calc(var(--_k-radius) * 999);color:var(--_k-fg-3);cursor:pointer;
9922
10523
  transition:background .15s,color .15s,opacity .15s}
9923
10524
  .knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
9924
10525
  .knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
9925
10526
  .knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
9926
- .knobs-diff-btn svg{width:13px;height:13px}
10527
+ .knobs-diff-btn svg{width:16px;height:16px}
9927
10528
  /* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
9928
10529
  .knobs-ui .knobs-diff-viewed{flex:none;display:inline-flex;align-items:center;gap:5px;height:var(--_k-control-h);padding:0 8px 0 6px;border-radius:var(--kd-ctl);color:var(--_k-fg-2);
9929
10530
  font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
9930
10531
  .knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
9931
10532
  .knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
9932
- .knobs-diff-viewed svg{width:13px;height:13px}
10533
+ .knobs-diff-viewed svg{width:16px;height:16px}
9933
10534
  .knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
9934
10535
  .knobs-diff-path{transition:opacity .2s}
9935
10536
  .knobs-diff[data-viewed]>.knobs-diff-head .knobs-diff-path,.knobs-diff-part[data-viewed] .knobs-diff-path{opacity:.6}
9936
- /* Split | Unified: one pill that glides between the two, raised as a panel's segments are (a
9937
- lighter bg in dark, near white in light). */
9938
- .knobs-diff-seg{position:relative;flex:none;display:grid;grid-template-columns:1fr 1fr;height:var(--_k-control-h);padding:2px;border-radius:var(--kd-ctl);background:var(--_k-segmented-track)}
9939
- .knobs-ui .knobs-diff-seg>button{position:relative;z-index:1;padding:0 9px;border-radius:max(0px,calc(var(--kd-ctl) - 2px));color:var(--_k-fg-2);font-size:var(--_k-text-meta);
9940
- font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color .15s}
9941
- .knobs-ui .knobs-diff-seg>button:is(:hover,:focus-visible),.knobs-ui .knobs-diff-seg>button[aria-checked="true"]{color:var(--_k-fg)}
9942
- .knobs-diff-pill{position:absolute;top:2px;bottom:2px;left:2px;width:calc(50% - 2px);border-radius:max(0px,calc(var(--kd-ctl) - 2px));background:color-mix(in srgb,var(--_k-accent) 18%,var(--_k-segmented-active));
9943
- box-shadow:0 1px 2px rgba(0,0,0,calc(.16 * var(--k-lift,1)));transition:transform var(--_ku-ease-ms) var(--_ku-ease)}
9944
- .knobs-diff-seg[data-value="unified"]>.knobs-diff-pill{transform:translateX(100%)}
9945
- .knobs-diff[data-frame="flat"] .knobs-diff-pill{box-shadow:none}
10537
+ /* Split | Unified: the selection pill on the one picked, its bright copy the picked one's text; a
10538
+ choice isn't brighter for being picked. Choices are styled by class, as the copy holds them too. */
10539
+ .knobs-diff-seg{position:relative;flex:none;display:grid;grid-template-columns:1fr 1fr;height:var(--_k-control-h);padding:2px;border-radius:var(--kd-ctl);background:var(--_k-fill)}
10540
+ .knobs-ui .knobs-diff-choice{padding:0 9px;border-radius:max(0px,calc(var(--kd-ctl) - 2px));color:var(--_k-fg-2);font-size:var(--_k-text-meta);
10541
+ font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
10542
+ .knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg)}
9946
10543
  /* The diff: one layer per file or layout on show, the old fading under the new. */
9947
10544
  .knobs-diff-body{position:relative;flex:1;min-height:0}
9948
10545
  .knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
@@ -10037,7 +10634,7 @@ var init_diff_css = __esm(() => {
10037
10634
  .knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
10038
10635
  .knobs-ui .knobs-diff-filter>button{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:999px;color:var(--_k-fg-3);cursor:pointer;transition:background .12s,color .12s,opacity .12s}
10039
10636
  .knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg)}
10040
- .knobs-diff-filter>button svg{width:10px;height:10px}
10637
+ .knobs-diff-filter>button svg{width:12px;height:12px}
10041
10638
  .knobs-diff-list{position:relative;flex:1;min-height:0}
10042
10639
  .knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
10043
10640
  .knobs-diff-files .knobs-diff-counts{gap:5px}
@@ -10843,7 +11440,7 @@ function codeDiff(container, options) {
10843
11440
  };
10844
11441
  const nav = make("span", "knobs-diff-nav");
10845
11442
  const prev = button("Previous file", PREV);
10846
- const next = button("Next file", ICONS.caret);
11443
+ const next = button("Next file", NEXT);
10847
11444
  const place = make("span", "");
10848
11445
  nav.append(prev, place, next);
10849
11446
  const wrapButton = button("Wrap lines", WRAP);
@@ -10864,9 +11461,8 @@ function codeDiff(container, options) {
10864
11461
  const seg = make("div", "knobs-diff-seg");
10865
11462
  seg.setAttribute("role", "radiogroup");
10866
11463
  seg.setAttribute("aria-label", "Layout");
10867
- const pill = make("i", "knobs-diff-pill");
10868
11464
  const choice = (value, label) => {
10869
- const b = make("button", "");
11465
+ const b = make("button", "knobs-diff-choice");
10870
11466
  b.type = "button";
10871
11467
  b.setAttribute("role", "radio");
10872
11468
  b.dataset.value = value;
@@ -10875,7 +11471,7 @@ function codeDiff(container, options) {
10875
11471
  };
10876
11472
  const splitButton = choice("split", "Split");
10877
11473
  const unifiedButton = choice("unified", "Unified");
10878
- seg.append(pill, splitButton, unifiedButton);
11474
+ seg.append(splitButton, unifiedButton);
10879
11475
  if (options.lead)
10880
11476
  head.append(options.lead);
10881
11477
  head.append(path, stat, make("span", "knobs-diff-fill"), nav, wrapButton, seg, viewedButton);
@@ -10886,6 +11482,11 @@ function codeDiff(container, options) {
10886
11482
  body.append(note, probe);
10887
11483
  root.append(head, body);
10888
11484
  container.append(root);
11485
+ const segPill = selectionPill(seg, {
11486
+ rows: () => [splitButton, unifiedButton],
11487
+ selected: () => layout === "split" ? splitButton : unifiedButton
11488
+ });
11489
+ let pilled = layout;
10889
11490
  const fileNow = () => files.find((f) => f.name === current) ?? null;
10890
11491
  const paintHead = () => {
10891
11492
  const file = fileNow();
@@ -10918,6 +11519,10 @@ function codeDiff(container, options) {
10918
11519
  next.disabled = at >= files.length - 1;
10919
11520
  wrapButton.setAttribute("aria-pressed", String(wrap));
10920
11521
  seg.dataset.value = layout;
11522
+ if (pilled !== layout) {
11523
+ pilled = layout;
11524
+ segPill.update();
11525
+ }
10921
11526
  splitButton.setAttribute("aria-checked", String(layout === "split"));
10922
11527
  unifiedButton.setAttribute("aria-checked", String(layout === "unified"));
10923
11528
  splitButton.tabIndex = layout === "split" ? 0 : -1;
@@ -11752,6 +12357,7 @@ function codeDiff(container, options) {
11752
12357
  dispose() {
11753
12358
  disposed = true;
11754
12359
  loop.stop();
12360
+ segPill.dispose();
11755
12361
  sizes.disconnect();
11756
12362
  attaching.disconnect();
11757
12363
  resized.disconnect();
@@ -12107,7 +12713,7 @@ function diffReview(container, options) {
12107
12713
  }
12108
12714
  };
12109
12715
  }
12110
- var lib, load = () => lib ??= import("@pierre/diffs"), SYNTAX, IN, OUT, FADE3, HEIGHT, DRAWER, BLUR = 3, WRAP, PREV, UNVIEWED, VIEWED, SEARCH, CLEAR, SIDEBAR, SEPARATOR = 32;
12716
+ var lib, load = () => lib ??= import("@pierre/diffs"), SYNTAX, IN, OUT, FADE3, HEIGHT, DRAWER, BLUR = 3, WRAP, PREV, NEXT, UNVIEWED, VIEWED, SEARCH, CLEAR, SIDEBAR, SEPARATOR = 32;
12111
12717
  var init_diff = __esm(() => {
12112
12718
  init_glide();
12113
12719
  init_motion();
@@ -12115,6 +12721,8 @@ var init_diff = __esm(() => {
12115
12721
  init_diff_model();
12116
12722
  init_look();
12117
12723
  init_rgba();
12724
+ init_icons();
12725
+ init_selection_pill();
12118
12726
  init_tree();
12119
12727
  SYNTAX = { dark: "pierre-dark", light: "pierre-light" };
12120
12728
  IN = springMotion(SPRINGS.default);
@@ -12122,13 +12730,14 @@ var init_diff = __esm(() => {
12122
12730
  FADE3 = springMotion(FADE);
12123
12731
  HEIGHT = springMotion(SPRINGS.calm);
12124
12732
  DRAWER = springMotion(SPRINGS.calm);
12125
- WRAP = svg('<path d="M2.5 4h11"/><path d="M2.5 8h9a2 2 0 0 1 0 4H8.5"/><path d="M10 10.5 8.5 12l1.5 1.5"/><path d="M2.5 12h3"/>');
12126
- PREV = svg('<path d="M10 3.5 5.5 8l4.5 4.5"/>');
12127
- UNVIEWED = svg('<circle cx="8" cy="8" r="5.5"/>');
12128
- VIEWED = svg('<circle cx="8" cy="8" r="5.5"/><path d="m5.6 8.1 1.7 1.7 3.2-3.5"/>');
12129
- SEARCH = svg('<circle cx="7" cy="7" r="4.5"/><path d="m10.5 10.5 3 3"/>');
12130
- CLEAR = svg('<path d="m5 5 6 6M11 5l-6 6"/>');
12131
- SIDEBAR = svg('<rect x="2" y="3" width="12" height="10" rx="2"/><path d="M6.5 3v10"/>');
12733
+ WRAP = icon("arrowUDownLeft");
12734
+ PREV = icon("caretLeft");
12735
+ NEXT = icon("caretRight");
12736
+ UNVIEWED = icon("circle");
12737
+ VIEWED = icon("checkCircle");
12738
+ SEARCH = icon("magnifyingGlass", { small: true });
12739
+ CLEAR = icon("x", { small: true });
12740
+ SIDEBAR = icon("sidebar");
12132
12741
  });
12133
12742
 
12134
12743
  // src/ui/index.ts
@@ -14629,6 +15238,11 @@ function readout(field, options = {}) {
14629
15238
  dispose: () => view.dispose()
14630
15239
  };
14631
15240
  }
15241
+
15242
+ // src/ui/index.ts
15243
+ init_icons();
15244
+ init_selection_pill();
15245
+
14632
15246
  // src/ui/tooltip.ts
14633
15247
  init_keys();
14634
15248
  init_layer();
@@ -18390,6 +19004,7 @@ function activity(container, options) {
18390
19004
  // src/ui/picker.ts
18391
19005
  init_motion();
18392
19006
  init_look();
19007
+ init_icons();
18393
19008
 
18394
19009
  // src/ui/picker-model.ts
18395
19010
  function digitPick(ids, key) {
@@ -18444,7 +19059,7 @@ var CLOSE3 = springMotion(CLOSE);
18444
19059
  var CARD = springMotion(SPRINGS.default);
18445
19060
  var FADE6 = springMotion(FADE);
18446
19061
  var DRIFT = 6;
18447
- var CHECK2 = svg('<path d="m3.5 8.4 3 3 6-6.6"/>');
19062
+ var CHECK2 = icon("check", { small: true });
18448
19063
  function optionPicker(container, options) {
18449
19064
  const doc = container.ownerDocument;
18450
19065
  const win = doc.defaultView;
@@ -18947,401 +19562,15 @@ function optionPicker(container, options) {
18947
19562
  // src/ui/walkthrough.ts
18948
19563
  init_motion();
18949
19564
  init_page_hints();
19565
+ init_icons();
18950
19566
  init_look();
19567
+ init_selection_pill();
18951
19568
 
18952
- // src/ui/walkthrough-media.ts
18953
- init_compare_model();
18954
- init_glide();
18955
- init_look();
18956
-
18957
- // src/ui/walkthrough-css.ts
19569
+ // src/ui/walkthrough-blocks.ts
18958
19570
  init_motion();
18959
- var snappy3 = springCss(SPRINGS.snappy);
18960
- var calm = springCss(SPRINGS.calm);
18961
- var walkthroughCss = `
18962
- :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
18963
- .knobs-ui.knobs-walkthrough{--kw-snappy:${snappy3.easing};--kw-snappy-ms:${snappy3.duration}ms;--kw-calm:${calm.easing};--kw-calm-ms:${calm.duration}ms;
18964
- --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));
18965
- --kw-card:var(--_ku-pane-bg);--kw-card-ring:var(--_ku-pane-ring);--kw-pad:16px;--kw-ink:var(--k-walkthrough-ink,#ff375f);
18966
- 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;
18967
- letter-spacing:normal;-webkit-font-smoothing:antialiased}
18968
- .knobs-walkthrough [hidden]{display:none!important}
18969
- .knobs-walkthrough :is(h1,h2,h3,p){margin:0;font:inherit;letter-spacing:inherit}
18970
- .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}
18971
- .knobs-walkthrough a:hover{text-decoration-color:currentColor}
18972
- .knobs-walkthrough code,.knobs-wt-slot code{font:calc(1em - 1px) var(--_k-mono);padding:1px 5px;border-radius:5px;background:var(--_k-fill)}
18973
- :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;
18974
- background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
18975
-
18976
- /* ——— layout: the rail beside the column; one column where it's narrow ——— */
18977
- .knobs-wt-layout{display:grid;grid-template-columns:200px minmax(0,1fr);gap:40px;align-items:start}
18978
- .knobs-wt-main{display:flex;flex-direction:column;gap:56px;min-width:0;max-width:1120px;padding-bottom:40vh}
18979
- @container knobs-wt (max-width:880px){.knobs-wt-layout{grid-template-columns:minmax(0,1fr);gap:0}.knobs-wt-main{gap:44px}}
18980
-
18981
- /* ——— the page's head ——— */
18982
- .knobs-wt-head{display:flex;flex-direction:column;gap:10px}
18983
- .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}
18984
- .knobs-wt-summary{max-width:var(--kw-measure);color:var(--_k-fg-2)}
18985
- .knobs-wt-meta{display:flex;flex-wrap:wrap;gap:6px 16px;color:var(--_k-fg-3);font-size:calc(var(--kw-text) - 2px)}
18986
- .knobs-wt-meta>span{display:inline-flex;gap:6px;white-space:nowrap}
18987
- .knobs-wt-meta b{font-weight:var(--_k-weight);color:var(--_k-fg-2);font-family:var(--_k-mono);font-size:calc(1em - .5px)}
18988
-
18989
- /* ——— a section: a PR or topic ——— */
18990
- .knobs-wt-section{display:flex;flex-direction:column;gap:14px;min-width:0}
18991
- .knobs-wt-shead{display:flex;flex-direction:column;gap:6px;scroll-margin-top:16px}
18992
- .knobs-wt-kicker{display:flex;align-items:center;gap:10px;font-size:calc(var(--kw-text) - 2px);color:var(--_k-fg-3)}
18993
- .knobs-wt-kicker a{font-family:var(--_k-mono);color:var(--_k-fg-2);text-decoration:none}
18994
- .knobs-wt-kicker a:hover{color:var(--_k-fg)}
18995
- .knobs-wt-stitle{font-size:calc(var(--kw-text) + 4px)!important;font-weight:var(--_k-weight-title)!important;line-height:1.3}
18996
- .knobs-wt-ssum{max-width:var(--kw-measure);color:var(--_k-fg-2)}
18997
- .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;
18998
- --kw-tone:var(--_k-fg-2);color:var(--kw-tone);background:color-mix(in srgb,var(--kw-tone) 14%,transparent)}
18999
- .knobs-wt-chip::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
19000
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="good"]{--kw-tone:var(--kw-good)}
19001
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="warn"]{--kw-tone:var(--_k-warn)}
19002
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="bad"]{--kw-tone:var(--_k-danger)}
19003
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="info"]{--kw-tone:var(--kw-info)}
19004
- :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="dim"]{--kw-tone:var(--_k-fg-2)}
19005
-
19006
- /* ——— a step: one card ——— */
19007
- .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);
19008
- scroll-margin-top:16px;outline:none;container:knobs-wt-step / inline-size}
19009
- .knobs-walkthrough[data-frame="flat"] .knobs-wt-step{box-shadow:inset 0 0 0 1px var(--_k-border)}
19010
- .knobs-wt-step-head{display:flex;align-items:baseline;gap:10px;min-width:0}
19011
- .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}
19012
- .knobs-wt-stepTitle{flex:1;min-width:0;font-weight:var(--_k-weight-title)!important}
19013
- .knobs-wt-caption{max-width:var(--kw-measure);color:var(--_k-fg-2);margin-top:-6px!important}
19014
- .knobs-wt-question{font-weight:var(--_k-weight-label)!important}
19015
- /* The step Kit is on: its number lights, and its choices show their keys. */
19016
- .knobs-wt-step[data-current] .knobs-wt-num{color:var(--_k-fg)}
19017
- .knobs-wt-media{display:grid;grid-template-columns:repeat(var(--kw-n,1),minmax(0,1fr));gap:12px;min-width:0}
19018
- @container knobs-wt-step (max-width:640px){.knobs-wt-media{grid-template-columns:minmax(0,1fr)}}
19019
-
19020
- /* ——— the feed: the agent's updates and answers, newest first, compact ——— */
19021
- .knobs-wt-feed{margin-top:-24px}
19022
- .knobs-wt-feed-list{list-style:none;margin:0;padding:0;max-height:260px;overflow:auto;scrollbar-width:none;display:flex;flex-direction:column}
19023
- .knobs-wt-feed-list::-webkit-scrollbar{display:none}
19024
- .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)}
19025
- .knobs-wt-fi+.knobs-wt-fi{box-shadow:inset 0 1px 0 var(--_k-border)}
19026
- .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}
19027
- .knobs-wt-fi-kind{color:var(--_k-fg-2);font-size:calc(var(--kw-text) - 2.5px);font-weight:var(--_k-weight-label)}
19028
- .knobs-wt-fi[data-kind="qa"] .knobs-wt-fi-kind{color:var(--kw-info)}
19029
- .knobs-wt-fi-body{max-width:var(--kw-measure);color:var(--_k-fg)}
19030
- .knobs-wt-fi-q{color:var(--_k-fg-2)}
19031
- .knobs-wt-fi-q::before{content:"You asked: ";color:var(--_k-fg-3)}
19032
- :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;
19033
- padding:3px 6px;margin:0;border-radius:6px;cursor:pointer;outline:none;transition:background .15s,color .15s}
19034
- .knobs-ui :is(.knobs-wt-fi-step,.knobs-wt-steplink):is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19035
-
19036
- /* ——— a step's sent notes ——— */
19037
- .knobs-wt-notes{display:flex;flex-direction:column}
19038
- .knobs-wt-notes:empty{display:none}
19039
- .knobs-wt-n{display:flex;align-items:flex-start;gap:10px;padding:6px 0;overflow:hidden;
19040
- 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)}
19041
- .knobs-wt-n[data-entering]{opacity:0;filter:blur(3px)}
19042
- .knobs-wt-n[data-leaving]{opacity:0;filter:blur(3px);padding-block:0}
19043
- .knobs-wt-n+.knobs-wt-n{box-shadow:inset 0 1px 0 var(--_k-border)}
19044
- .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}
19045
- .knobs-wt-n-meta[data-seek]{cursor:pointer}
19046
- .knobs-wt-n-meta[data-seek]:hover{color:var(--_k-fg)}
19047
- .knobs-wt-n-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px;font-size:calc(var(--kw-text) - 1px)}
19048
- .knobs-wt-n-voice{height:30px;max-width:320px}
19049
- .knobs-wt-n-ink{display:block;width:120px;max-height:80px;border-radius:6px;background:var(--_k-fill)}
19050
- .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}
19051
-
19052
- /* ——— the note bar, docked at the bottom of the column ——— */
19053
- .knobs-wt-dock{position:sticky;bottom:12px;z-index:40;margin-top:-24px}
19054
- .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);
19055
- box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay)}
19056
- .knobs-wt-on{padding-top:3px;color:var(--_k-fg-3);font-size:calc(var(--kw-text) - 2.5px);white-space:nowrap;overflow:hidden;
19057
- -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)}
19058
- .knobs-wt-row{display:flex;align-items:flex-end;gap:4px}
19059
- :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;
19060
- border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
19061
- .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)}
19062
- .knobs-ui .knobs-wt-input::placeholder{color:var(--_k-fg-3)}
19063
- .knobs-wt-attach{display:flex;gap:4px;align-items:center;align-self:center}
19064
- .knobs-wt-attach:empty{display:none}
19065
- .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}
19066
- .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}
19067
- .knobs-ui .knobs-wt-x:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19068
- .knobs-wt-x svg{width:10px;height:10px}
19069
- .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);
19070
- color:var(--_k-fg-2);cursor:pointer;transition:background .15s,color .15s}
19071
- .knobs-ui .knobs-wt-tool:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19072
- .knobs-ui .knobs-wt-tool[aria-pressed="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
19073
- .knobs-wt-tool svg{width:15px;height:15px}
19074
- .knobs-wt-rec{font-size:calc(var(--kw-text) - 2.5px);font-variant-numeric:tabular-nums}
19075
- .knobs-wt-rec:empty{display:none}
19076
- .knobs-ui .knobs-wt-tool[data-send]{color:var(--_k-fg-3)}
19077
- .knobs-ui .knobs-wt-tool[data-send][data-ready]{background:var(--_k-accent);color:var(--_k-bg)}
19078
- /* Back: in place over the bar for a few seconds after a note moves the page on. */
19079
- .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);
19080
- 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;
19081
- 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)}
19082
- .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)}
19083
- .knobs-ui .knobs-wt-back:hover{background:var(--_k-fill-selected)}
19084
-
19085
- /* ——— ink over a step's media ——— */
19086
- .knobs-wt-media,.knobs-wt-options{position:relative}
19087
- .knobs-wt-ink{position:absolute;inset:0;z-index:5;width:100%;height:100%;overflow:visible;pointer-events:none;touch-action:none}
19088
- .knobs-wt-ink[data-on]{pointer-events:auto;cursor:crosshair}
19089
- .knobs-wt-ink path{fill:none;stroke:var(--kw-ink);stroke-width:3px;vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round}
19090
-
19091
- /* ——— full-window: the step over the page, its media filling it; its place held in the page ——— */
19092
- .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}
19093
- .knobs-wt-scrim[data-on]{pointer-events:auto}
19094
- .knobs-wt-hold-card{flex:none}
19095
- .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)}
19096
- .knobs-wt-step[data-full]>:not(.knobs-wt-media){flex:none}
19097
- .knobs-wt-step[data-full] .knobs-wt-notes{display:none}
19098
- .knobs-wt-step[data-full]>.knobs-wt-media{flex:1;min-height:0;grid-auto-rows:minmax(0,1fr)}
19099
- .knobs-wt-step[data-full] .knobs-wt-slot{display:flex;flex-direction:column;min-height:0}
19100
- .knobs-wt-step[data-full] .knobs-wt-body{flex:1;min-height:0}
19101
- .knobs-wt-step[data-full] .knobs-wt-fit{height:100%;transition:none}
19102
- .knobs-wt-step[data-full] .knobs-wt-frame{height:100%;aspect-ratio:auto}
19103
-
19104
- /* ——— a taste test: the options side by side, Kit's pick lit and the rest stepping back ——— */
19105
- .knobs-wt-options{display:grid;grid-template-columns:repeat(var(--kw-n,2),minmax(0,1fr));gap:10px}
19106
- @container knobs-wt-step (max-width:640px){.knobs-wt-options{grid-template-columns:minmax(0,1fr)}}
19107
- .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);
19108
- transition:background var(--kw-snappy-ms) var(--kw-snappy)}
19109
- .knobs-wt-option[data-picked]{background:color-mix(in srgb,var(--_k-accent) 12%,var(--_ku-wash))}
19110
- .knobs-wt-option>:not(.knobs-wt-pick){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
19111
- .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-pick){opacity:.55}
19112
- .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}
19113
- .knobs-ui .knobs-wt-pick:is(:hover,:focus-visible){color:var(--_k-fg)}
19114
- .knobs-wt-letter{flex:none;font:var(--_k-weight-title) calc(var(--kw-text) - 1px)/1 var(--_k-font);color:var(--_k-fg)}
19115
- .knobs-wt-olabel{flex:1;min-width:0;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
19116
- .knobs-wt-suggested{flex:none;font-size:calc(var(--kw-text) - 3px);color:var(--_k-fg-3)}
19117
- .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;
19118
- transition:background var(--kw-snappy-ms) var(--kw-snappy),color var(--kw-snappy-ms) var(--kw-snappy)}
19119
- .knobs-wt-check svg{width:12px;height:12px}
19120
- .knobs-wt-option[data-picked] .knobs-wt-check{background:var(--_k-accent);color:var(--_k-bg)}
19121
- .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);
19122
- font:var(--_k-weight-label) calc(var(--kw-text) - 4px)/1 var(--_k-font);font-variant-numeric:tabular-nums;opacity:.5;transition:opacity .15s}
19123
- .knobs-wt-step[data-current] .knobs-wt-key{opacity:1}
19124
- .knobs-wt-ocap{color:var(--_k-fg-2);font-size:calc(var(--kw-text) - 1px);padding:0 4px}
19125
-
19126
- /* ——— the rail: progress, contents, export ——— */
19127
- .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)}
19128
- .knobs-wt-progress{display:flex;flex-direction:column;gap:7px}
19129
- .knobs-wt-count{display:flex;align-items:baseline;justify-content:space-between;color:var(--_k-fg-2);font-variant-numeric:tabular-nums}
19130
- .knobs-wt-count b{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
19131
- .knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
19132
- .knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
19133
- .knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
19134
- /* The contents: no box, one text spine for sections and steps (the steps' dots hang in the
19135
- gutter left of it), and one pill behind the current step that springs between rows. */
19136
- .knobs-wt-toc{position:relative;flex:1 1 auto;min-height:0;overflow:auto;scrollbar-width:none;margin:0 -8px;padding:0 0 8px}
19137
- .knobs-wt-toc::-webkit-scrollbar{display:none}
19138
- .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;
19139
- -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)}
19140
- .knobs-wt-toc-section:first-of-type{padding-top:2px}
19141
- .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}
19142
- .knobs-wt-toc-mark[data-on]{opacity:1}
19143
- .knobs-walkthrough[data-settled] .knobs-wt-toc-mark{transition:opacity .15s}
19144
- .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}
19145
- .knobs-ui .knobs-wt-toc-step:is(:hover,:focus-visible){color:var(--_k-fg)}
19146
- .knobs-wt-toc-step[aria-current="step"]{color:var(--_k-fg)}
19147
- .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)}
19148
- /* A step's state: an open ring, a filled dot once answered, nothing to answer a faint ring. */
19149
- .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);
19150
- transition:background var(--kw-snappy-ms) var(--kw-snappy),box-shadow var(--kw-snappy-ms) var(--kw-snappy)}
19151
- .knobs-wt-toc-glyph[data-tone="accent"]{--kw-tone:var(--_k-accent)}
19152
- .knobs-wt-toc-glyph[data-done]{background:var(--kw-tone)}
19153
- .knobs-wt-toc-glyph[data-tone="none"]{box-shadow:inset 0 0 0 1px var(--_k-border)}
19154
- .knobs-wt-actions{display:flex;flex-wrap:wrap;gap:4px}
19155
- .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;
19156
- transition:background .15s,color .15s}
19157
- .knobs-ui .knobs-wt-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
19158
- .knobs-ui .knobs-wt-btn[data-primary]{background:var(--_k-fill-selected);color:var(--_k-fg)}
19159
- .knobs-ui .knobs-wt-btn[data-primary]:is(:hover,:focus-visible){background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
19160
- .knobs-wt-keys{color:var(--_k-fg-3);font-size:calc(var(--kw-text) - 3px);line-height:1.6}
19161
- .knobs-wt-keys kbd{font:inherit;color:var(--_k-fg-2)}
19162
- /* Narrow: the rail becomes a bar along the top, with the progress and the export only. */
19163
- @container knobs-wt (max-width:880px){
19164
- .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)}
19165
- .knobs-wt-progress{flex:1}.knobs-wt-toc,.knobs-wt-keys{display:none}
19166
- .knobs-wt-step,.knobs-wt-shead{scroll-margin-top:60px}
19167
- }
19168
-
19169
- /* ——— media slots ——— */
19170
- .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);
19171
- --kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info))}
19172
- .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}
19173
- /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
19174
- sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
19175
- (92 px), and the frame strip with its gap (158 px). */
19176
- .knobs-wt-fit{width:100%;height:calc(100cqw / 2 / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
19177
- .knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
19178
- .knobs-wt-fit[data-strip]{--kw-chrome:158px}
19179
- .knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
19180
- .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)}
19181
- .knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
19182
- .knobs-wt-frame[data-natural]{aspect-ratio:auto}
19183
- .knobs-wt-frame[data-natural]>img{position:static;height:auto}
19184
- .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);
19185
- font:calc(var(--k-walkthrough-text,14px) - 2px)/1.55 var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
19186
- .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
19187
- .knobs-wt-prompt{color:var(--_k-fg-3);user-select:none;-webkit-user-select:none}
19188
- .knobs-wt-cmd{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
19189
- .knobs-wt-out{color:var(--_k-fg-2)}
19190
- .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)}
19191
- .knobs-wt-mlabel{color:var(--_k-fg-2);font-size:calc(var(--k-walkthrough-text,14px) - 2px)}
19192
- .knobs-wt-mvalue{display:flex;align-items:baseline;flex-wrap:wrap;gap:8px;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
19193
- .knobs-wt-mfrom{color:var(--_k-fg-3);font-size:calc(var(--k-walkthrough-text,14px) + 2px)}
19194
- .knobs-wt-mto{font-size:calc(var(--k-walkthrough-text,14px) + 12px);font-weight:var(--_k-weight-title);line-height:1}
19195
- .knobs-wt-mof{color:var(--_k-fg-3)}
19196
- .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)}
19197
- .knobs-wt-mdelta[data-tone="good"]{--kw-tone:var(--kw-good)}
19198
- .knobs-wt-mdelta[data-tone="bad"]{--kw-tone:var(--_k-danger)}
19199
- .knobs-wt-mtrack{position:relative;height:4px;border-radius:2px;background:color-mix(in srgb,var(--_k-fg) 8%,transparent)}
19200
- .knobs-wt-mtrack>span{position:absolute;top:0;bottom:0;left:0;border-radius:inherit}
19201
- .knobs-wt-mtrack>.knobs-wt-mafter{background:var(--_k-accent)}
19202
- .knobs-wt-mtrack>.knobs-wt-mbefore{background:color-mix(in srgb,var(--_k-fg) 30%,transparent)}
19203
- .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)}
19204
- .knobs-wt-callout::before{content:"";flex:none;width:6px;height:6px;margin-top:.62em;border-radius:50%;background:var(--kw-tone)}
19205
- .knobs-wt-callout[data-tone="good"]{--kw-tone:var(--kw-good)}
19206
- .knobs-wt-callout[data-tone="warn"]{--kw-tone:var(--_k-warn)}
19207
- .knobs-wt-callout[data-tone="bad"]{--kw-tone:var(--_k-danger)}
19208
- .knobs-wt-text{max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
19209
- .knobs-wt-hold{border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill)}
19210
- .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)}
19211
-
19212
- /* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
19213
- hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
19214
- :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)}
19215
- :where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
19216
- :where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
19217
- :where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
19218
- :where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
19219
- :where(.knobs-wt-slot) [data-tone="dim"]{--kw-tone:var(--_k-fg-3)}
19220
- .knobs-wt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
19221
- .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);
19222
- text-decoration:none;white-space:nowrap;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
19223
- .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)}
19224
-
19225
- /* A history: dates down the left, a spine of tone dots joined by a hairline (broken around each
19226
- dot), the event beside it. Inferred events take a ring for a dot and a dashed tag. */
19227
- .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)}
19228
- .knobs-wt-hgroup{display:flex;flex-direction:column;gap:6px;min-width:0}
19229
- .knobs-wt-hhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px}
19230
- .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)}
19231
- .knobs-wt-hsum{color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 2px);font-variant-numeric:tabular-nums}
19232
- .knobs-wt-hlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
19233
- .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}
19234
- .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)}
19235
- .knobs-wt-hage{color:var(--_k-fg-3);font-size:calc(var(--kw-t) - 3px);line-height:1.35}
19236
- .knobs-wt-hspine{grid-area:spine;position:relative;align-self:stretch;margin-block:-5px}
19237
- .knobs-wt-hspine::before,.knobs-wt-hspine::after{content:"";position:absolute;left:6.5px;width:1px;background:var(--kw-rail)}
19238
- .knobs-wt-hspine::before{top:0;height:calc(var(--kw-hy) - 6px)}
19239
- .knobs-wt-hspine::after{top:calc(var(--kw-hy) + 6px);bottom:0}
19240
- .knobs-wt-hev:first-child .knobs-wt-hspine::before,.knobs-wt-hev:last-child .knobs-wt-hspine::after{content:none}
19241
- .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))}
19242
- .knobs-wt-hev[data-type="release"] .knobs-wt-hdot{border-radius:1.5px;rotate:45deg;scale:.9}
19243
- .knobs-wt-hev[data-type="audit"] .knobs-wt-hdot{border-radius:1.5px}
19244
- .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))}
19245
- .knobs-wt-hmain{grid-area:main;display:flex;flex-direction:column;gap:2px;min-width:0}
19246
- .knobs-wt-htitle{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
19247
- .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}
19248
- .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}
19249
- .knobs-ui .knobs-wt-hdetail{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
19250
- @container knobs-wt-slot (max-width:520px){
19251
- .knobs-wt-hev{grid-template-columns:14px minmax(0,1fr);grid-template-areas:"spine when" "spine main"}
19252
- .knobs-wt-hwhen{flex-direction:row;flex-wrap:wrap;align-items:baseline;column-gap:8px}
19253
- }
19254
-
19255
- /* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
19256
- git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
19257
- .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)}
19258
- .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);
19259
- border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
19260
- .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
19261
- .knobs-wt-srow[data-go]{cursor:pointer}
19262
- .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19263
- .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
19264
- .knobs-wt-lane{position:relative;flex:none;width:16px}
19265
- .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)}
19266
- .knobs-wt-lane[data-down]::before{top:calc(var(--kw-sy) + 3.5px)}
19267
- .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}
19268
- .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)}
19269
- .knobs-wt-srow[data-trunk] .knobs-wt-lane[data-rail="node"]::after{background:var(--_k-fg-2)}
19270
- .knobs-wt-trunk{font:var(--_k-weight-label) calc(var(--kw-t) - 2px)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2)}
19271
- .knobs-wt-smain{display:flex;flex-direction:column;gap:1px;min-width:0}
19272
- .knobs-wt-sline{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 8px}
19273
- .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}
19274
- .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}
19275
- .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
19276
- .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
19277
- .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)}
19278
- .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)}
19279
- .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
19280
- .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)}
19281
- .knobs-wt-delta{display:inline-flex;gap:6px}
19282
- .knobs-wt-add{color:var(--_k-diff-add)}
19283
- .knobs-wt-del{color:var(--_k-diff-del)}
19284
- .knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
19285
- .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
19286
- .knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
19287
- @container knobs-wt-slot (max-width:520px){
19288
- .knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
19289
- .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
19290
- .knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
19291
- }
19292
-
19293
- /* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
19294
- (the top one under the head), and thin bars show on hover. */
19295
- @property --kw-fade-l{syntax:"<number>";inherits:false;initial-value:1}
19296
- @property --kw-fade-r{syntax:"<number>";inherits:false;initial-value:1}
19297
- @keyframes kw-fade-l{from{--kw-fade-l:1}to{--kw-fade-l:0}}
19298
- @keyframes kw-fade-r{from{--kw-fade-r:0}to{--kw-fade-r:1}}
19299
- .knobs-wt-table{max-height:520px;overflow:auto;overscroll-behavior-x:contain;outline:none;scrollbar-width:thin;scrollbar-color:transparent transparent;
19300
- transition:scrollbar-color calc(var(--_k-hover-out) * 1ms)}
19301
- .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)}
19302
- .knobs-wt-table:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus);border-radius:calc(var(--_k-radius-panel) - 4px)}
19303
- @supports (animation-timeline:scroll()){
19304
- .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;
19305
- 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%;
19306
- --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)));
19307
- --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)));
19308
- -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}
19309
- }
19310
- .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)}
19311
- .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)}
19312
- .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;
19313
- background:var(--_k-pane-bg);box-shadow:inset 0 -1px 0 var(--_k-border)}
19314
- .knobs-wt-table td{padding:8px 12px;vertical-align:baseline;box-shadow:inset 0 -1px 0 var(--_k-border)}
19315
- .knobs-wt-table tr:last-child td{box-shadow:none}
19316
- .knobs-wt-table :is(th,td):first-child{padding-left:0}
19317
- .knobs-wt-table :is(th,td):last-child{padding-right:0}
19318
- .knobs-wt-table td:first-child{color:var(--_k-fg)}
19319
- .knobs-wt-table [data-align="right"]{text-align:right}
19320
- .knobs-wt-table [data-align="center"]{text-align:center}
19321
- .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}
19322
- .knobs-wt-table td[data-long]{min-width:24ch}
19323
- .knobs-wt-table td>.knobs-wt-steplink{margin-left:4px}
19324
- .knobs-ui .knobs-wt-table a.knobs-wt-chip{text-decoration:none}
19325
- .knobs-wt-ctone{color:var(--kw-tone)}
19326
-
19327
- /* Chapters: timestamp pills under a video; one highlight springs to the one playing. */
19328
- .knobs-wt-chapters{position:relative;display:flex;flex-wrap:wrap;gap:4px;margin-top:8px}
19329
- .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;
19330
- transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms)}
19331
- .knobs-wt-chapter-mark[data-on]{opacity:1}
19332
- .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);
19333
- 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)}
19334
- .knobs-ui .knobs-wt-chapter:hover{background:var(--_ku-wash);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
19335
- .knobs-ui .knobs-wt-chapter[aria-current]{background:transparent;color:var(--_k-fg)}
19336
- .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus)}
19337
- .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)}
19338
- .knobs-wt-chapter[aria-current] .knobs-wt-chapter-time{color:var(--_k-fg-2)}
19339
- @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
19340
- `;
19341
-
19342
- // src/ui/walkthrough-blocks.ts
19343
- init_motion();
19344
- init_look();
19571
+ init_icons();
19572
+ init_look();
19573
+ init_selection_pill();
19345
19574
 
19346
19575
  // src/ui/walkthrough-model.ts
19347
19576
  var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
@@ -19548,7 +19777,15 @@ function fillTable(m) {
19548
19777
  out[k] = fillCell(v);
19549
19778
  return out;
19550
19779
  });
19551
- return { kind: "table", columns, rows, ...m.caption ? { caption: m.caption } : {} };
19780
+ const primary = columns.find((c) => c.key === m.primary)?.key ?? columns[0]?.key ?? "";
19781
+ return {
19782
+ kind: "table",
19783
+ columns,
19784
+ rows,
19785
+ ...m.caption ? { caption: m.caption } : {},
19786
+ stack: m.stack ?? "auto",
19787
+ primary
19788
+ };
19552
19789
  }
19553
19790
  function fillChapters(chapters) {
19554
19791
  const id = uniqueIds();
@@ -19621,6 +19858,8 @@ function normalizeWalkthrough(doc) {
19621
19858
  ...step.caption ? { caption: step.caption } : {},
19622
19859
  ...step.question ? { question: step.question } : {},
19623
19860
  media: list.map((m) => fillMedia(m, doc.base)),
19861
+ columns: Math.max(1, Math.min(3, Math.round(step.columns ?? 1))),
19862
+ ...step.more?.trim() && list.length > 1 ? { more: step.more.trim() } : {},
19624
19863
  options,
19625
19864
  ask
19626
19865
  };
@@ -19632,6 +19871,7 @@ function normalizeWalkthrough(doc) {
19632
19871
  id,
19633
19872
  title: s.title,
19634
19873
  ...link ? { link } : {},
19874
+ ...typeof s.kicker === "string" && s.kicker.trim() ? { kicker: s.kicker.trim() } : {},
19635
19875
  ...s.status ? { status: s.status } : {},
19636
19876
  ...s.summary ? { summary: s.summary } : {},
19637
19877
  steps: own
@@ -19642,14 +19882,25 @@ function normalizeWalkthrough(doc) {
19642
19882
  value
19643
19883
  }));
19644
19884
  const stepRef = (ref) => findStep(steps, ref)?.key ?? null;
19885
+ const extra = (x) => ({
19886
+ ...x.status?.trim() ? { status: x.status.trim() } : {},
19887
+ trail: (x.trail ?? []).filter((t) => typeof t === "string" && t.trim())
19888
+ });
19645
19889
  const feed = sortFeed([
19646
- ...(doc.updates ?? []).map((u) => ({ kind: "update", at: parseAt(u.at), text: u.text, step: stepRef(u.step) })),
19890
+ ...(doc.updates ?? []).map((u) => ({
19891
+ kind: "update",
19892
+ at: parseAt(u.at),
19893
+ text: u.text,
19894
+ step: stepRef(u.step),
19895
+ ...extra(u)
19896
+ })),
19647
19897
  ...(doc.qa ?? []).map((x) => ({
19648
19898
  kind: "qa",
19649
19899
  at: parseAt(x.at),
19650
19900
  text: x.a,
19651
19901
  q: x.q,
19652
- step: stepRef(x.step)
19902
+ step: stepRef(x.step),
19903
+ ...extra(x)
19653
19904
  }))
19654
19905
  ]);
19655
19906
  return {
@@ -19662,10 +19913,32 @@ function normalizeWalkthrough(doc) {
19662
19913
  id: slug(v),
19663
19914
  label: label(v)
19664
19915
  })),
19665
- sections,
19916
+ sections: withKickers(sections, doc.sections, doc.kickers),
19666
19917
  steps
19667
19918
  };
19668
19919
  }
19920
+ var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
19921
+ function withKickers(sections, source, kickers = "pr") {
19922
+ if (kickers === false)
19923
+ return [...sections];
19924
+ const noun = typeof kickers === "object" ? kickers.noun.trim() : "PR";
19925
+ const counted = typeof kickers === "object" ? sections : sections.filter((s) => s.link && isPullRequest(s.link.href));
19926
+ return sections.map((s, i) => {
19927
+ const own = source[i]?.kicker;
19928
+ if (own !== undefined || s.kicker)
19929
+ return s;
19930
+ const at = counted.indexOf(s);
19931
+ return at < 0 ? s : { ...s, kicker: `${noun} ${at + 1} of ${counted.length}`.trim() };
19932
+ });
19933
+ }
19934
+ function tableStacks(stack, room, natural) {
19935
+ if (stack !== "auto")
19936
+ return stack === "always";
19937
+ return room > 0 && natural > room + 1;
19938
+ }
19939
+ function noteBarReserve(bar, inset, gap) {
19940
+ return Math.max(0, Math.ceil(bar)) + inset + gap;
19941
+ }
19669
19942
  function parseAt(at) {
19670
19943
  if (!at)
19671
19944
  return null;
@@ -19950,6 +20223,9 @@ function feedbackMarkdown(fb, verdicts) {
19950
20223
  `;
19951
20224
  }
19952
20225
  var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
20226
+ function plainMarkdown(text) {
20227
+ 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();
20228
+ }
19953
20229
  function inlineMarkdown(text) {
19954
20230
  const codes = [];
19955
20231
  let s = text.replace(/`([^`\n]+)`/g, (_, c) => {
@@ -19970,6 +20246,10 @@ function inlineMarkdown(text) {
19970
20246
  }
19971
20247
 
19972
20248
  // src/ui/walkthrough-blocks.ts
20249
+ var GLYPH = {
20250
+ go: icon("caretRight", { small: true }),
20251
+ caret: icon("caretRight", { small: true })
20252
+ };
19973
20253
  var el = (doc, tag, cls) => {
19974
20254
  const n = doc.createElement(tag);
19975
20255
  if (cls)
@@ -19999,9 +20279,10 @@ function stepButton(doc, ref, steps) {
19999
20279
  const s = ref && steps?.find(ref);
20000
20280
  if (!s || !steps)
20001
20281
  return null;
20002
- const b = text(doc, "button", "knobs-wt-steplink", s.number);
20282
+ const b = el(doc, "button", "knobs-wt-steplink");
20283
+ b.innerHTML = GLYPH.go;
20003
20284
  b.type = "button";
20004
- b.title = `Go to ${s.number} ${s.title}`;
20285
+ b.title = `Go to “${s.title}”`;
20005
20286
  b.setAttribute("aria-label", b.title);
20006
20287
  b.addEventListener("click", (e) => {
20007
20288
  e.stopPropagation();
@@ -20126,7 +20407,7 @@ function mountStack(host, m, steps) {
20126
20407
  if (target && steps) {
20127
20408
  const title = text(doc, "button", "knobs-wt-stitle", it.title);
20128
20409
  title.type = "button";
20129
- title.title = `Go to ${target.number} ${target.title}`;
20410
+ title.title = `Go to “${target.title}”`;
20130
20411
  title.addEventListener("click", (e) => {
20131
20412
  e.stopPropagation();
20132
20413
  steps.go(target.key);
@@ -20181,175 +20462,775 @@ function delta(doc, d) {
20181
20462
  s.setAttribute("aria-label", `${d.add} added, ${d.del} removed`);
20182
20463
  return s;
20183
20464
  }
20184
- function mountTable(host, m, steps) {
20185
- const doc = host.ownerDocument;
20186
- const win = doc.defaultView;
20187
- const scroller = el(doc, "div", "knobs-wt-table");
20188
- const table = el(doc, "table");
20189
- if (m.caption)
20190
- table.append(text(doc, "caption", "", m.caption));
20191
- const head = el(doc, "thead");
20192
- const hr = el(doc, "tr");
20193
- for (const c of m.columns) {
20194
- const th = text(doc, "th", "", c.label);
20195
- th.scope = "col";
20196
- th.dataset.align = c.align;
20197
- hr.append(th);
20198
- }
20199
- head.append(hr);
20200
- const body = el(doc, "tbody");
20201
- for (const r of m.rows) {
20202
- const tr = el(doc, "tr");
20203
- for (const c of m.columns) {
20204
- const td = el(doc, "td");
20205
- td.dataset.align = c.align;
20206
- if (c.mono)
20207
- td.dataset.mono = "";
20208
- const cell = r[c.key];
20209
- if (cell) {
20210
- if (cell.text.length > 40 && !c.mono && !cell.code)
20211
- td.dataset.long = "";
20212
- let content;
20213
- if (cell.badge) {
20214
- content = link(doc, "knobs-wt-chip", cell.text, cell.href);
20215
- content.dataset.tone = cell.badge;
20216
- } else {
20217
- const inner = cell.code ? text(doc, "code", "", cell.text) : null;
20218
- if (cell.href) {
20219
- content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
20220
- if (inner)
20221
- content.append(inner);
20222
- } else
20223
- content = inner ?? text(doc, "span", "", cell.text);
20224
- if (cell.tone) {
20225
- content.classList.add("knobs-wt-ctone");
20226
- content.dataset.tone = cell.tone;
20227
- }
20228
- }
20229
- td.append(content);
20230
- const go = stepButton(doc, cell.step, steps);
20231
- if (go)
20232
- td.append(go);
20233
- }
20234
- tr.append(td);
20235
- }
20236
- body.append(tr);
20237
- }
20238
- table.append(head, body);
20239
- scroller.append(table);
20240
- host.append(scroller);
20241
- const measure = () => {
20242
- scroller.style.setProperty("--kw-thead", `${head.offsetHeight}px`);
20243
- const scrolls = scroller.scrollWidth > scroller.clientWidth + 1 || scroller.scrollHeight > scroller.clientHeight + 1;
20244
- if (scrolls) {
20245
- scroller.tabIndex = 0;
20246
- scroller.setAttribute("role", "region");
20247
- scroller.setAttribute("aria-label", m.caption ?? "Table");
20248
- } else {
20249
- scroller.removeAttribute("tabindex");
20250
- scroller.removeAttribute("role");
20251
- scroller.removeAttribute("aria-label");
20252
- }
20253
- };
20254
- const ro = win && "ResizeObserver" in win ? new win.ResizeObserver(measure) : null;
20255
- ro?.observe(scroller);
20256
- measure();
20257
- return {
20258
- dispose: () => {
20259
- ro?.disconnect();
20260
- scroller.remove();
20261
- },
20262
- context: () => ({ kind: "table" })
20263
- };
20465
+ function cellContent(doc, into, cell, steps) {
20466
+ let content;
20467
+ if (cell.badge) {
20468
+ content = link(doc, "knobs-wt-chip", cell.text, cell.href);
20469
+ content.dataset.tone = cell.badge;
20470
+ } else {
20471
+ const inner = cell.code ? text(doc, "code", "", cell.text) : null;
20472
+ if (cell.href) {
20473
+ content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
20474
+ if (inner)
20475
+ content.append(inner);
20476
+ } else
20477
+ content = inner ?? text(doc, "span", "", cell.text);
20478
+ if (cell.tone) {
20479
+ content.classList.add("knobs-wt-ctone");
20480
+ content.dataset.tone = cell.tone;
20481
+ }
20482
+ }
20483
+ into.append(content);
20484
+ const go = stepButton(doc, cell.step, steps);
20485
+ if (go)
20486
+ into.append(go);
20487
+ }
20488
+ var cellAttrs = (td, c) => {
20489
+ td.dataset.align = c.align;
20490
+ if (c.mono)
20491
+ td.dataset.mono = "";
20492
+ };
20493
+ function mountTable(host, m, steps) {
20494
+ const doc = host.ownerDocument;
20495
+ const win = doc.defaultView;
20496
+ const box = el(doc, "div", "knobs-wt-tablebox");
20497
+ const scroller = el(doc, "div", "knobs-wt-table");
20498
+ const table = el(doc, "table");
20499
+ if (m.caption)
20500
+ table.append(text(doc, "caption", "", m.caption));
20501
+ const head = el(doc, "thead");
20502
+ const hr = el(doc, "tr");
20503
+ for (const c of m.columns) {
20504
+ const th = text(doc, "th", "", c.label);
20505
+ th.scope = "col";
20506
+ th.dataset.align = c.align;
20507
+ hr.append(th);
20508
+ }
20509
+ head.append(hr);
20510
+ const body = el(doc, "tbody");
20511
+ for (const r of m.rows) {
20512
+ const tr = el(doc, "tr");
20513
+ for (const c of m.columns) {
20514
+ const td = el(doc, "td");
20515
+ cellAttrs(td, c);
20516
+ const cell = r[c.key];
20517
+ if (cell) {
20518
+ if (cell.text.length > 40 && !c.mono && !cell.code)
20519
+ td.dataset.long = "";
20520
+ cellContent(doc, td, cell, steps);
20521
+ }
20522
+ tr.append(td);
20523
+ }
20524
+ body.append(tr);
20525
+ }
20526
+ table.append(head, body);
20527
+ scroller.append(table);
20528
+ const stack = el(doc, "div", "knobs-wt-tstack");
20529
+ stack.setAttribute("role", "list");
20530
+ if (m.caption)
20531
+ stack.setAttribute("aria-label", m.caption);
20532
+ const primary = m.columns.find((c) => c.key === m.primary) ?? m.columns[0];
20533
+ for (const r of m.rows) {
20534
+ const item = el(doc, "div", "knobs-wt-trow");
20535
+ item.setAttribute("role", "listitem");
20536
+ const title = el(doc, "div", "knobs-wt-ttitle");
20537
+ if (primary) {
20538
+ cellAttrs(title, primary);
20539
+ const cell = r[primary.key];
20540
+ if (cell)
20541
+ cellContent(doc, title, cell, steps);
20542
+ }
20543
+ const line = el(doc, "div", "knobs-wt-tline");
20544
+ line.append(title);
20545
+ const badges = el(doc, "div", "knobs-wt-tbadges");
20546
+ for (const c of m.columns) {
20547
+ const cell = r[c.key];
20548
+ if (c !== primary && cell?.badge)
20549
+ cellContent(doc, badges, cell, steps);
20550
+ }
20551
+ if (badges.childNodes.length)
20552
+ line.append(badges);
20553
+ item.append(line);
20554
+ const terms = el(doc, "dl", "knobs-wt-tterms");
20555
+ for (const c of m.columns) {
20556
+ if (c === primary)
20557
+ continue;
20558
+ const cell = r[c.key];
20559
+ if (!cell || cell.text === "" || cell.badge)
20560
+ continue;
20561
+ const dd = el(doc, "dd");
20562
+ cellAttrs(dd, c);
20563
+ dd.removeAttribute("data-align");
20564
+ cellContent(doc, dd, cell, steps);
20565
+ terms.append(text(doc, "dt", "", c.label), dd);
20566
+ }
20567
+ if (terms.childNodes.length)
20568
+ item.append(terms);
20569
+ stack.append(item);
20570
+ }
20571
+ if (m.caption)
20572
+ stack.append(text(doc, "p", "knobs-wt-tcaption", m.caption));
20573
+ box.append(scroller, stack);
20574
+ host.append(box);
20575
+ let natural = 0;
20576
+ const measureNatural = () => {
20577
+ if (!box.isConnected)
20578
+ return 0;
20579
+ const was = box.dataset.stacked;
20580
+ delete box.dataset.stacked;
20581
+ box.dataset.measure = "";
20582
+ const w = table.getBoundingClientRect().width;
20583
+ delete box.dataset.measure;
20584
+ if (was !== undefined)
20585
+ box.dataset.stacked = was;
20586
+ return w;
20587
+ };
20588
+ const measure = () => {
20589
+ if (!natural)
20590
+ natural = measureNatural();
20591
+ const stacked = tableStacks(m.stack, box.clientWidth, natural);
20592
+ box.toggleAttribute("data-stacked", stacked);
20593
+ scroller.style.setProperty("--kw-thead", `${head.offsetHeight}px`);
20594
+ const scrolls = !stacked && (scroller.scrollWidth > scroller.clientWidth + 1 || scroller.scrollHeight > scroller.clientHeight + 1);
20595
+ if (scrolls) {
20596
+ scroller.tabIndex = 0;
20597
+ scroller.setAttribute("role", "region");
20598
+ scroller.setAttribute("aria-label", m.caption ?? "Table");
20599
+ } else {
20600
+ scroller.removeAttribute("tabindex");
20601
+ scroller.removeAttribute("role");
20602
+ scroller.removeAttribute("aria-label");
20603
+ }
20604
+ };
20605
+ if (m.stack === "always")
20606
+ box.dataset.stacked = "";
20607
+ const ro = win && "ResizeObserver" in win ? new win.ResizeObserver(measure) : null;
20608
+ ro?.observe(box);
20609
+ measure();
20610
+ doc.fonts?.ready.then(() => {
20611
+ if (!box.isConnected)
20612
+ return;
20613
+ natural = 0;
20614
+ measure();
20615
+ });
20616
+ return {
20617
+ dispose: () => {
20618
+ ro?.disconnect();
20619
+ box.remove();
20620
+ },
20621
+ context: () => ({ kind: "table" })
20622
+ };
20623
+ }
20624
+ function chapterPills(host, chapters, io) {
20625
+ const doc = host.ownerDocument;
20626
+ const list = el(doc, "div", "knobs-wt-chapters");
20627
+ list.setAttribute("role", "group");
20628
+ list.setAttribute("aria-label", "Chapters");
20629
+ let at = -2;
20630
+ const rows = chapters.map((c, i) => {
20631
+ const b = el(doc, "button", "knobs-wt-chapter");
20632
+ b.type = "button";
20633
+ b.append(text(doc, "span", "knobs-wt-chapter-n", String(i + 1)));
20634
+ b.append(text(doc, "span", "knobs-wt-chapter-label", c.label));
20635
+ b.append(text(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
20636
+ b.addEventListener("click", () => {
20637
+ io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
20638
+ io.seek(c.start);
20639
+ select(i);
20640
+ });
20641
+ list.append(b);
20642
+ return b;
20643
+ });
20644
+ host.append(list);
20645
+ const pill = selectionPill(list, {
20646
+ selected: () => rows[at] ?? null,
20647
+ rows: () => rows
20648
+ });
20649
+ const select = (i) => {
20650
+ if (i === at)
20651
+ return;
20652
+ at = i;
20653
+ rows.forEach((b, k) => k === at ? b.setAttribute("aria-current", "true") : b.removeAttribute("aria-current"));
20654
+ pill.update();
20655
+ };
20656
+ const sync = () => select(chapterAt(chapters, io.now()));
20657
+ sync();
20658
+ return {
20659
+ sync,
20660
+ get current() {
20661
+ return chapters[at] ?? null;
20662
+ },
20663
+ dispose() {
20664
+ pill.dispose();
20665
+ list.remove();
20666
+ }
20667
+ };
20668
+ }
20669
+ function disclosure(host, options) {
20670
+ const doc = host.ownerDocument;
20671
+ const win = doc.defaultView;
20672
+ let open = options.open === true;
20673
+ const root = el(doc, "div", "knobs-wt-disc");
20674
+ const toggle = el(doc, "button", "knobs-wt-disc-toggle");
20675
+ toggle.type = "button";
20676
+ toggle.innerHTML = GLYPH.caret;
20677
+ toggle.append(text(doc, "span", "", options.label));
20678
+ if (options.count !== undefined)
20679
+ toggle.append(text(doc, "span", "knobs-wt-disc-count", String(options.count)));
20680
+ const viewport = el(doc, "div", "knobs-wt-disc-body");
20681
+ const content = el(doc, "div", "knobs-wt-disc-content");
20682
+ content.append(options.content);
20683
+ viewport.append(content);
20684
+ root.append(toggle, viewport);
20685
+ host.append(root);
20686
+ const STATE = springMotion(SPRINGS.default);
20687
+ const h = new Spring(0, STATE.omega, STATE.zeta, 0.25);
20688
+ let full = 0;
20689
+ const paint = (moving) => {
20690
+ const k = full > 0 ? Math.max(0, Math.min(1, h.x / full)) : open ? 1 : 0;
20691
+ viewport.style.height = !moving && open ? "" : `${Math.max(0, h.x).toFixed(2)}px`;
20692
+ viewport.toggleAttribute("data-moving", moving);
20693
+ content.style.opacity = (open ? Math.min(1, k * 1.25) : Math.max(0, k * 1.5 - 0.5)).toFixed(3);
20694
+ };
20695
+ const loop = frameLoop(win, (dt) => {
20696
+ h.step(dt);
20697
+ const moving = !h.resting;
20698
+ paint(moving);
20699
+ return moving;
20700
+ });
20701
+ const sync = () => {
20702
+ root.toggleAttribute("data-open", open);
20703
+ toggle.setAttribute("aria-expanded", String(open));
20704
+ viewport.inert = !open;
20705
+ };
20706
+ sync();
20707
+ h.snap(0);
20708
+ paint(false);
20709
+ if (open) {
20710
+ viewport.style.height = "";
20711
+ content.style.opacity = "1";
20712
+ }
20713
+ const api = {
20714
+ element: root,
20715
+ get open() {
20716
+ return open;
20717
+ },
20718
+ toggle(next = !open) {
20719
+ if (next === open)
20720
+ return;
20721
+ open = next;
20722
+ sync();
20723
+ full = content.offsetHeight;
20724
+ if (reducedMotion(win)) {
20725
+ loop.stop();
20726
+ h.snap(open ? full : 0);
20727
+ viewport.style.height = open ? "" : "0px";
20728
+ content.style.opacity = open ? "1" : "0";
20729
+ return;
20730
+ }
20731
+ if (h.resting)
20732
+ h.snap(open ? 0 : full);
20733
+ h.set(open ? full : 0);
20734
+ paint(true);
20735
+ loop.wake();
20736
+ },
20737
+ dispose() {
20738
+ loop.stop();
20739
+ root.remove();
20740
+ }
20741
+ };
20742
+ toggle.addEventListener("click", () => api.toggle());
20743
+ return api;
20744
+ }
20745
+
20746
+ // src/ui/walkthrough-media.ts
20747
+ init_compare_model();
20748
+ init_glide();
20749
+ init_icons();
20750
+ init_look();
20751
+
20752
+ // src/ui/walkthrough-css.ts
20753
+ init_motion();
20754
+ var snappy3 = springCss(SPRINGS.snappy);
20755
+ var calm = springCss(SPRINGS.calm);
20756
+ var state = springCss(SPRINGS.default);
20757
+ var quick = springCss(FADE);
20758
+ 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)";
20759
+ var walkthroughCss = `
20760
+ :where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
20761
+ .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;
20762
+ --kw-measure:var(--k-walkthrough-measure,68ch);--kw-width:var(--k-walkthrough-width,680px);--kw-rail-w:220px;--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
20763
+ --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);
20764
+ 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;
20765
+ letter-spacing:normal;-webkit-font-smoothing:antialiased}
20766
+ .knobs-walkthrough [hidden]{display:none!important}
20767
+ /* (0,0,1): a host's element rules stop here, and every class rule below wins over it. */
20768
+ :where(.knobs-walkthrough) :is(h1,h2,h3,p,ol,ul,dl,dd){margin:0;font:inherit;letter-spacing:inherit}
20769
+ .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}
20770
+ .knobs-walkthrough a:hover{text-decoration-color:currentColor}
20771
+ .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)}
20772
+ :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;
20773
+ background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
20774
+
20775
+ /* ——— layout: a readable column, centred, with a slim contents rail beside it (right by default);
20776
+ one column where it's narrow ——— */
20777
+ .knobs-wt-layout{display:grid;grid-template-columns:minmax(0,var(--kw-width)) var(--kw-rail-w);justify-content:center;gap:var(--_k-space-column);align-items:start}
20778
+ .knobs-walkthrough[data-rail="right"] .knobs-wt-main{grid-column:1;grid-row:1}
20779
+ .knobs-walkthrough[data-rail="right"] .knobs-wt-rail{grid-column:2;grid-row:1}
20780
+ .knobs-walkthrough[data-rail="left"] .knobs-wt-layout{grid-template-columns:var(--kw-rail-w) minmax(0,var(--kw-width))}
20781
+ .knobs-walkthrough[data-rail="none"] .knobs-wt-layout{grid-template-columns:minmax(0,var(--kw-width))}
20782
+ .knobs-wt-main{display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0;padding-bottom:12px}
20783
+ /* The intro, the updates and each section: a section's room apart, the hairline in the middle. */
20784
+ .knobs-wt-flow{display:flex;flex-direction:column;gap:calc(var(--_k-space-section) / 2);min-width:0}
20785
+ .knobs-wt-flow>*+*{padding-top:calc(var(--_k-space-section) / 2);box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
20786
+ @container knobs-wt (max-width:940px){.knobs-walkthrough .knobs-wt-layout{grid-template-columns:minmax(0,1fr);gap:var(--_k-space-title)}
20787
+ .knobs-walkthrough[data-rail] .knobs-wt-layout>*{grid-column:auto;grid-row:auto}}
20788
+
20789
+ /* ——— one line of facts: small, faint, joined by ·, its trailing edge fading ——— */
20790
+ .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}}
20791
+ .knobs-wt-metaline>*{flex:none}
20792
+ .knobs-wt-metaline>*+*::before{content:"·";margin-right:var(--_k-space-inside);color:var(--_k-fg-3)}
20793
+ .knobs-walkthrough .knobs-wt-metaline a{display:inline-flex;align-items:baseline;color:inherit;text-decoration:none;transition:color calc(var(--_k-hover-out) * 1ms)}
20794
+ .knobs-walkthrough .knobs-wt-metaline a:hover{color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20795
+ .knobs-wt-metaline svg{align-self:center;width:12px;height:12px;margin-left:2px}
20796
+ .knobs-wt-kicker-lead{color:var(--_k-fg);font-weight:var(--kw-strong)}
20797
+ .knobs-wt-metaline [data-status="on"]{color:var(--_k-fg)}
20798
+ .knobs-wt-metaline [data-status="warn"]{color:var(--_k-warn)}
20799
+
20800
+ /* ——— the page's head ——— */
20801
+ .knobs-wt-head{display:flex;flex-direction:column;gap:var(--_k-space-title)}
20802
+ .knobs-wt-title{font-size:var(--_k-type-title)!important;line-height:var(--_k-leading-title);font-weight:var(--kw-strong)!important;text-wrap:balance}
20803
+ .knobs-wt-summary{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
20804
+ .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)}
20805
+ .knobs-wt-meta>span{display:inline-flex;gap:var(--_k-space-inside);white-space:nowrap}
20806
+ .knobs-wt-meta b{font-weight:var(--_k-weight);color:var(--_k-fg-2);font-family:var(--_k-mono)}
20807
+
20808
+ /* ——— a section: a PR or topic. Its kicker leads the meta line; its title outranks the page's ——— */
20809
+ .knobs-wt-section{display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0}
20810
+ .knobs-wt-sintro{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0}
20811
+ .knobs-wt-shead{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0;scroll-margin-top:16px}
20812
+ .knobs-wt-heading{font-size:var(--_k-type-heading)!important;line-height:var(--_k-leading-heading);font-weight:var(--kw-strong)!important;letter-spacing:-.01em!important;text-wrap:balance}
20813
+ .knobs-wt-ssum{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
20814
+ .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;
20815
+ --kw-tone:var(--_k-fg-2);color:var(--kw-tone);background:color-mix(in srgb,var(--kw-tone) 14%,transparent)}
20816
+ .knobs-wt-chip::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
20817
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="good"]{--kw-tone:var(--kw-good)}
20818
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="warn"]{--kw-tone:var(--_k-warn)}
20819
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="bad"]{--kw-tone:var(--_k-danger)}
20820
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="info"]{--kw-tone:var(--kw-info)}
20821
+ :is(.knobs-wt-chip,.knobs-wt-toc-glyph)[data-tone="dim"]{--kw-tone:var(--_k-fg-2)}
20822
+
20823
+ /* ——— a step: on the page by default, a group's room between steps with a hairline in the middle;
20824
+ or (cards) one card each, the one surface its pieces sit on ——— */
20825
+ .knobs-walkthrough:not([data-cards]) .knobs-wt-step:not([data-full]){padding:0;border-radius:0;background:transparent;box-shadow:none}
20826
+ .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}
20827
+ .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);
20828
+ scroll-margin-top:16px;outline:none;container:knobs-wt-step / inline-size}
20829
+ .knobs-walkthrough[data-cards]{--kw-under:var(--kw-card)}
20830
+ .knobs-walkthrough[data-cards][data-frame="flat"] .knobs-wt-step{box-shadow:inset 0 0 0 1px var(--_k-border)}
20831
+ .knobs-wt-step-head{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0}
20832
+ .knobs-wt-stepTitle{min-width:0;font-weight:var(--kw-strong)!important;text-wrap:balance}
20833
+ .knobs-wt-caption{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
20834
+ .knobs-wt-question{font-weight:var(--kw-strong)!important;max-width:var(--kw-measure)}
20835
+ .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}
20836
+ @container knobs-wt-step (max-width:640px){.knobs-wt-media{grid-template-columns:minmax(0,1fr)}}
20837
+
20838
+ /* ——— the feed: updates and answers, newest first. Tight inside an item (4 / 8 / 4), a group's
20839
+ room between items with the hairline in the middle; no boxes ——— */
20840
+ .knobs-wt-feed{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0;scroll-margin-top:16px}
20841
+ .knobs-wt-feed-head{display:flex;align-items:baseline;gap:var(--_k-space-sibling)}
20842
+ .knobs-wt-feed-title{font-weight:var(--kw-strong)!important}
20843
+ .knobs-wt-feed-count{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
20844
+ .knobs-wt-feed-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0}
20845
+ .knobs-wt-fi{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
20846
+ .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)}
20847
+ .knobs-wt-fi-q,.knobs-wt-fi-a{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0}
20848
+ .knobs-wt-fi-ask{max-width:var(--kw-measure);color:var(--_k-fg);font-weight:var(--kw-strong);text-wrap:pretty}
20849
+ .knobs-wt-fi-text{max-width:var(--kw-measure);color:var(--_k-fg);text-wrap:pretty}
20850
+ .knobs-wt-fi-answer{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
20851
+ :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;
20852
+ transition:color calc(var(--_k-hover-out) * 1ms)}
20853
+ .knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
20854
+ :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;
20855
+ 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)}
20856
+ .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)}
20857
+ .knobs-wt-steplink svg{width:12px;height:12px}
20858
+
20859
+ /* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
20860
+ script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
20861
+ .knobs-wt-disc{display:flex;flex-direction:column;min-width:0}
20862
+ .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;
20863
+ border-radius:calc(var(--_k-radius) / 2);transition:color calc(var(--_k-hover-out) * 1ms)}
20864
+ .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)}
20865
+ .knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
20866
+ .knobs-wt-disc-toggle svg{flex:none;width:12px;height:12px;transition:rotate var(--kw-state-ms) var(--kw-state)}
20867
+ .knobs-wt-disc[data-open]>.knobs-wt-disc-toggle svg{rotate:90deg}
20868
+ .knobs-wt-disc-count{color:var(--_k-fg-3)}
20869
+ .knobs-wt-disc-body{overflow:hidden}
20870
+ .knobs-wt-disc[data-open]>.knobs-wt-disc-body:not([data-moving]){overflow:visible}
20871
+ .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)}
20872
+ .knobs-wt-disc-content{padding-top:var(--_k-space-inside)}
20873
+ /* A trail sits on the toggle's label edge: faint lines, the time trailing. */
20874
+ .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}
20875
+ .knobs-wt-trail li{display:flex;align-items:baseline;gap:var(--_k-space-sibling);min-width:0;white-space:nowrap}
20876
+ .knobs-wt-trail-text{flex:0 1 auto;min-width:0;overflow:hidden;${fade2}}
20877
+ .knobs-wt-trail-at{flex:none}
20878
+
20879
+ /* ——— a step's sent notes ——— */
20880
+ .knobs-wt-notes{display:flex;flex-direction:column}
20881
+ .knobs-wt-notes:empty{display:none}
20882
+ .knobs-wt-n{display:flex;align-items:flex-start;gap:var(--_k-space-sibling);padding:6px 0;overflow:hidden;
20883
+ 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)}
20884
+ .knobs-wt-n[data-entering]{opacity:0;filter:blur(3px)}
20885
+ .knobs-wt-n[data-leaving]{opacity:0;filter:blur(3px);padding-block:0}
20886
+ .knobs-wt-n+.knobs-wt-n{box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
20887
+ .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)}
20888
+ .knobs-wt-n-meta[data-seek]{cursor:pointer}
20889
+ .knobs-wt-n-meta[data-seek]:hover{color:var(--_k-fg)}
20890
+ .knobs-wt-n-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
20891
+ .knobs-wt-n-voice{height:30px;max-width:320px}
20892
+ .knobs-wt-n-ink{display:block;width:120px;max-height:80px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-fill)}
20893
+ .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}
20894
+
20895
+ /* ——— the note bar, docked at the bottom of the column. It holds its own place at the page's end
20896
+ (so the last step stays clear of it), and script keeps its height as the scroller's
20897
+ scroll-padding-bottom ——— */
20898
+ .knobs-wt-dock{position:sticky;bottom:12px;z-index:40;display:grid;pointer-events:none}
20899
+ .knobs-wt-dock>*{pointer-events:auto}
20900
+ .knobs-wt-dock>:is(.knobs-wt-notebar,.knobs-wt-launch){grid-area:1/1}
20901
+ .knobs-wt-dock[data-mode="docked"]>.knobs-wt-launch{display:none}
20902
+ /* Collapsed: the launcher and the bar swap in place, a quick fade with a little scale and blur
20903
+ from the launcher's corner; the exit is the shorter. */
20904
+ :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);
20905
+ 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%;
20906
+ 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)}
20907
+ .knobs-ui .knobs-wt-launch:is(:hover,:focus-visible){color:var(--_k-fg)}
20908
+ .knobs-ui .knobs-wt-launch:focus-visible{box-shadow:var(--_k-focus-ring)}
20909
+ .knobs-wt-launch svg{width:16px;height:16px}
20910
+ .knobs-wt-launch kbd{font:inherit;font-weight:var(--_k-weight);color:var(--_k-fg-3)}
20911
+ .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)}
20912
+ .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;
20913
+ transition-duration:calc(var(--kw-quick-ms) * .8)}
20914
+ .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);
20915
+ box-shadow:var(--_ku-pane-ring),var(--_k-shadow-overlay)}
20916
+ .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}}
20917
+ .knobs-wt-row{display:flex;align-items:flex-end;gap:4px}
20918
+ :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;
20919
+ border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
20920
+ .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)}
20921
+ .knobs-ui .knobs-wt-input::placeholder{color:var(--_k-fg-3)}
20922
+ .knobs-wt-attach{display:flex;gap:4px;align-items:center;align-self:center}
20923
+ .knobs-wt-attach:empty{display:none}
20924
+ .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}
20925
+ .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}
20926
+ .knobs-ui .knobs-wt-x:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
20927
+ .knobs-wt-x svg{width:12px;height:12px}
20928
+ .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);
20929
+ color:var(--_k-fg-2);cursor:pointer;transition:background .15s,color .15s}
20930
+ .knobs-ui .knobs-wt-tool:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
20931
+ .knobs-ui .knobs-wt-tool[aria-pressed="true"]{background:var(--_k-fill-selected);color:var(--_k-fg)}
20932
+ .knobs-wt-tool svg{width:16px;height:16px}
20933
+ .knobs-wt-rec{font-size:var(--_k-type-small)}
20934
+ .knobs-wt-rec:empty{display:none}
20935
+ .knobs-ui .knobs-wt-tool[data-send]{color:var(--_k-fg-3)}
20936
+ .knobs-ui .knobs-wt-tool[data-send][data-ready]{background:var(--_k-accent);color:var(--_k-bg)}
20937
+ /* Back: in place over the bar for a few seconds after a note moves the page on. */
20938
+ .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;
20939
+ 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;
20940
+ 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)}
20941
+ .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)}
20942
+ .knobs-ui .knobs-wt-back:hover{background:var(--_k-fill-selected)}
20943
+ .knobs-wt-back svg{flex:none;width:12px;height:12px}
20944
+ .knobs-wt-back-label{min-width:0;overflow:hidden;white-space:nowrap;${fade2}}
20945
+
20946
+ /* ——— ink over a step's media ——— */
20947
+ .knobs-wt-media,.knobs-wt-options{position:relative}
20948
+ .knobs-wt-ink{position:absolute;inset:0;z-index:5;width:100%;height:100%;overflow:visible;pointer-events:none;touch-action:none}
20949
+ .knobs-wt-ink[data-on]{pointer-events:auto;cursor:crosshair}
20950
+ .knobs-wt-ink path{fill:none;stroke:var(--kw-ink);stroke-width:3px;vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round}
20951
+
20952
+ /* ——— full-window: the step over the page above the note bar, its media filling it; its place held
20953
+ in the page. The one raised surface ——— */
20954
+ .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}
20955
+ .knobs-wt-scrim[data-on]{pointer-events:auto}
20956
+ .knobs-wt-hold-card{flex:none}
20957
+ .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)}
20958
+ .knobs-wt-step[data-full]::before{content:none}
20959
+ .knobs-wt-step[data-full]>:not(.knobs-wt-media){flex:none}
20960
+ .knobs-wt-step[data-full] .knobs-wt-notes{display:none}
20961
+ .knobs-wt-step[data-full]>.knobs-wt-media{flex:1;min-height:0;grid-auto-rows:minmax(0,1fr)}
20962
+ .knobs-wt-step[data-full] .knobs-wt-slot{display:flex;flex-direction:column;min-height:0}
20963
+ .knobs-wt-step[data-full] .knobs-wt-body{flex:1;min-height:0}
20964
+ .knobs-wt-step[data-full] .knobs-wt-fit{height:100%;transition:none}
20965
+ .knobs-wt-step[data-full] .knobs-wt-frame{height:100%;aspect-ratio:auto}
20966
+
20967
+ /* ——— a taste test: the options side by side, Kit's pick checked and the rest stepping back. On
20968
+ the step's card, so no fill of their own ——— */
20969
+ .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)}
20970
+ @container knobs-wt-step (max-width:640px){.knobs-wt-options{grid-template-columns:minmax(0,1fr)}}
20971
+ .knobs-wt-option{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
20972
+ .knobs-wt-option>:not(.knobs-wt-pick){transition:opacity var(--kw-snappy-ms) var(--kw-snappy)}
20973
+ .knobs-wt-options[data-has-pick] .knobs-wt-option:not([data-picked])>:not(.knobs-wt-pick){opacity:.55}
20974
+ .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;
20975
+ transition:background calc(var(--_k-hover-out) * 1ms)}
20976
+ .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)}
20977
+ .knobs-wt-letter{flex:none;font-weight:var(--kw-strong);color:var(--_k-fg)}
20978
+ .knobs-wt-olabel{flex:1;min-width:0;font-weight:var(--kw-strong);color:var(--_k-fg)}
20979
+ .knobs-wt-suggested{flex:none;font-size:var(--_k-type-small);color:var(--_k-fg-3)}
20980
+ .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;
20981
+ transition:background var(--kw-snappy-ms) var(--kw-snappy),color var(--kw-snappy-ms) var(--kw-snappy)}
20982
+ .knobs-wt-check svg{width:12px;height:12px}
20983
+ .knobs-wt-option[data-picked] .knobs-wt-check{background:var(--_k-accent);color:var(--_k-bg)}
20984
+ .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);
20985
+ font:var(--kw-strong) var(--_k-type-small)/1 var(--_k-font);opacity:.5;transition:opacity .15s}
20986
+ .knobs-wt-step[data-current] .knobs-wt-key{opacity:1}
20987
+ .knobs-wt-ocap{color:var(--_k-fg-2);max-width:var(--kw-measure)}
20988
+
20989
+ /* ——— the rail: progress, contents, export ——— */
20990
+ .knobs-wt-rail{position:sticky;top:16px;display:flex;flex-direction:column;gap:var(--_k-space-title);max-height:calc(100vh - 32px);min-height:0;font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
20991
+ .knobs-wt-progress{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
20992
+ .knobs-wt-count{display:flex;align-items:baseline;justify-content:space-between;color:var(--_k-fg-2)}
20993
+ .knobs-wt-count b{color:var(--_k-fg);font-weight:var(--kw-strong)}
20994
+ .knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
20995
+ .knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
20996
+ .knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
20997
+ /* The contents: rows centred on one spine, title rows (32 high, the control radius) and the
20998
+ steps under them (20 high, half of it), bled out so their text sits on the rail's edge; the
20999
+ selection pill slides to the current step, and base rows never brighten for being current. */
21000
+ .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}
21001
+ .knobs-wt-toc::-webkit-scrollbar{display:none}
21002
+ .knobs-wt-toc-group{display:flex;flex-direction:column;min-width:0}
21003
+ .knobs-ui .knobs-wt-toc-row{position:relative;display:grid;grid-template-columns:var(--kw-toc-n,0px) minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;flex:none;width:100%;height:32px;
21004
+ 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(--kw-strong);text-align:left;white-space:nowrap;cursor:pointer;
21005
+ transition:color calc(var(--_k-hover-out) * 1ms)}
21006
+ .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);color:var(--_k-fg-3)}
21007
+ .knobs-wt-toc[data-bare] .knobs-wt-toc-row{grid-template-columns:minmax(0,1fr) auto}
21008
+ .knobs-wt-toc[data-bare] .knobs-wt-toc-n{display:none}
21009
+ .knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
21010
+ .knobs-ui .knobs-wt-toc-sub:is(:hover,:focus-visible){color:var(--_k-fg-2)}
21011
+ .knobs-ui .knobs-wt-toc-row:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
21012
+ .knobs-wt-toc-n,.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)}
21013
+ .knobs-wt-toc-row[data-active] .knobs-wt-toc-n{color:var(--_k-fg-2)}
21014
+ .knobs-wt-toc-label{min-width:0;overflow:hidden;${fade2}}
21015
+ .knobs-pick-copy .knobs-wt-toc-n,.knobs-pick-copy .knobs-wt-toc-end{color:var(--_k-fg-2)}
21016
+ /* A step's state: an open ring, a filled dot once answered, nothing to answer a faint ring. */
21017
+ .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);
21018
+ transition:background var(--kw-snappy-ms) var(--kw-snappy),box-shadow var(--kw-snappy-ms) var(--kw-snappy)}
21019
+ .knobs-wt-toc-glyph[data-tone="accent"]{--kw-tone:var(--_k-accent)}
21020
+ .knobs-wt-toc-glyph[data-done]{background:var(--kw-tone)}
21021
+ .knobs-wt-toc-glyph[data-tone="none"]{box-shadow:inset 0 0 0 1px var(--_k-border)}
21022
+ .knobs-wt-actions{display:flex;flex-wrap:wrap;gap:4px}
21023
+ .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;
21024
+ transition:background .15s,color .15s}
21025
+ .knobs-ui .knobs-wt-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
21026
+ .knobs-ui .knobs-wt-btn[data-primary]{background:var(--_k-fill-selected);color:var(--_k-fg)}
21027
+ .knobs-ui .knobs-wt-btn[data-primary]:is(:hover,:focus-visible){background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
21028
+ .knobs-wt-keys{color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
21029
+ .knobs-wt-keys kbd{font:inherit;color:var(--_k-fg-2)}
21030
+ /* Narrow: the rail becomes a bar along the top, with the progress and the export only. */
21031
+ @container knobs-wt (max-width:940px){
21032
+ .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)}
21033
+ .knobs-wt-progress{flex:1}.knobs-wt-toc,.knobs-wt-keys{display:none}
21034
+ .knobs-wt-step,.knobs-wt-shead,.knobs-wt-feed{scroll-margin-top:60px}
21035
+ }
21036
+
21037
+ /* ——— media slots. Only a picture's frame (a quiet fill while it loads or letterboxes) and a code
21038
+ block (a well) are bounded; a metric, a callout, a table, a stack and a history sit on the page,
21039
+ set apart by space, hairlines and their own colours. On a card (.knobs-surface) code drops its
21040
+ well for hairlines, so nothing draws a fill and a ring of its own there ——— */
21041
+ .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);
21042
+ 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);
21043
+ --kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info))}
21044
+ .knobs-wt-slot-label{display:block;font:var(--_k-weight-label) var(--_k-type-label)/var(--_k-leading-label) var(--_k-font);color:var(--_k-fg-2)}
21045
+ .knobs-wt-body{min-width:0}
21046
+ /* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
21047
+ sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
21048
+ (92 px), and the frame strip with its gap (158 px). */
21049
+ .knobs-wt-fit{width:100%;height:calc(100cqw / 2 / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
21050
+ .knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
21051
+ .knobs-wt-fit[data-strip]{--kw-chrome:158px}
21052
+ .knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
21053
+ .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)}
21054
+ .knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
21055
+ .knobs-wt-frame[data-natural]{aspect-ratio:auto;background:transparent}
21056
+ .knobs-wt-frame[data-natural]>img{position:static;height:auto}
21057
+ .knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill);color:var(--_k-fg);
21058
+ font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
21059
+ .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)}
21060
+ .knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
21061
+ .knobs-wt-prompt{color:var(--_k-fg-3);user-select:none;-webkit-user-select:none}
21062
+ .knobs-wt-cmd{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
21063
+ .knobs-wt-out{color:var(--_k-fg-2)}
21064
+ .knobs-wt-metric{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
21065
+ .knobs-wt-mlabel{color:var(--_k-fg-2)}
21066
+ .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"}
21067
+ .knobs-wt-mfrom{color:var(--_k-fg-3);font-size:var(--_k-type-title);line-height:var(--_k-leading-title)}
21068
+ .knobs-wt-mto{font-size:var(--_k-type-heading);line-height:var(--_k-leading-heading);font-weight:var(--_k-weight-label)}
21069
+ .knobs-wt-mof{color:var(--_k-fg-3)}
21070
+ .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)}
21071
+ .knobs-wt-mdelta[data-tone="good"]{--kw-tone:var(--kw-good)}
21072
+ .knobs-wt-mdelta[data-tone="bad"]{--kw-tone:var(--_k-danger)}
21073
+ .knobs-wt-mtrack{position:relative;height:4px;border-radius:2px;background:color-mix(in srgb,var(--_k-fg) 8%,transparent)}
21074
+ .knobs-wt-mtrack>span{position:absolute;top:0;bottom:0;left:0;border-radius:inherit}
21075
+ .knobs-wt-mtrack>.knobs-wt-mafter{background:var(--_k-accent)}
21076
+ .knobs-wt-mtrack>.knobs-wt-mbefore{background:color-mix(in srgb,var(--_k-fg) 30%,transparent)}
21077
+ .knobs-wt-callout{--kw-tone:var(--kw-info);display:flex;gap:var(--_k-space-sibling);max-width:var(--kw-measure);color:var(--_k-fg)}
21078
+ .knobs-wt-callout::before{content:"";flex:none;width:6px;height:6px;margin-top:7px;border-radius:50%;background:var(--kw-tone)}
21079
+ .knobs-wt-callout[data-tone="good"]{--kw-tone:var(--kw-good)}
21080
+ .knobs-wt-callout[data-tone="warn"]{--kw-tone:var(--_k-warn)}
21081
+ .knobs-wt-callout[data-tone="bad"]{--kw-tone:var(--_k-danger)}
21082
+ .knobs-wt-text{max-width:var(--kw-measure);color:var(--_k-fg-2)}
21083
+ .knobs-wt-hold{border-radius:calc(var(--_k-radius-panel) - 4px);background:var(--_k-fill)}
21084
+ .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)}
21085
+
21086
+ /* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
21087
+ hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
21088
+ :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)}
21089
+ :where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
21090
+ :where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
21091
+ :where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
21092
+ :where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
21093
+ :where(.knobs-wt-slot) [data-tone="dim"]{--kw-tone:var(--_k-fg-3)}
21094
+ .knobs-wt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
21095
+ .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);
21096
+ text-decoration:none;white-space:nowrap;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
21097
+ .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)}
21098
+
21099
+ /* A history: dates down the left, a spine of tone dots joined by a hairline (broken around each
21100
+ dot), the event beside it. Inferred events take a ring for a dot and a dashed tag. */
21101
+ .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)}
21102
+ .knobs-wt-hgroup{display:flex;flex-direction:column;gap:6px;min-width:0}
21103
+ .knobs-wt-hhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px}
21104
+ .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)}
21105
+ .knobs-wt-hsum{color:var(--_k-fg-3);font-size:var(--_k-type-small);font-variant-numeric:tabular-nums}
21106
+ .knobs-wt-hlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
21107
+ .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}
21108
+ .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)}
21109
+ .knobs-wt-hage{color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
21110
+ .knobs-wt-hspine{grid-area:spine;position:relative;align-self:stretch;margin-block:-5px}
21111
+ .knobs-wt-hspine::before,.knobs-wt-hspine::after{content:"";position:absolute;left:6.5px;width:1px;background:var(--kw-rail)}
21112
+ .knobs-wt-hspine::before{top:0;height:calc(var(--kw-hy) - 6px)}
21113
+ .knobs-wt-hspine::after{top:calc(var(--kw-hy) + 6px);bottom:0}
21114
+ .knobs-wt-hev:first-child .knobs-wt-hspine::before,.knobs-wt-hev:last-child .knobs-wt-hspine::after{content:none}
21115
+ .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))}
21116
+ .knobs-wt-hev[data-type="release"] .knobs-wt-hdot{border-radius:1.5px;rotate:45deg;scale:.9}
21117
+ .knobs-wt-hev[data-type="audit"] .knobs-wt-hdot{border-radius:1.5px}
21118
+ .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))}
21119
+ .knobs-wt-hmain{grid-area:main;display:flex;flex-direction:column;gap:2px;min-width:0}
21120
+ .knobs-wt-htitle{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
21121
+ .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}
21122
+ .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}
21123
+ .knobs-ui .knobs-wt-hdetail{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
21124
+ @container knobs-wt-slot (max-width:520px){
21125
+ .knobs-wt-hev{grid-template-columns:14px minmax(0,1fr);grid-template-areas:"spine when" "spine main"}
21126
+ .knobs-wt-hwhen{flex-direction:row;flex-wrap:wrap;align-items:baseline;column-gap:8px}
21127
+ }
21128
+
21129
+ /* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
21130
+ git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
21131
+ .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)}
21132
+ .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);
21133
+ border-radius:max(0px,calc(var(--_k-radius-panel) - 8px));transition:background calc(var(--_k-hover-out) * 1ms)}
21134
+ .knobs-wt-srow[data-trunk]{--kw-spb:0px;grid-template-columns:auto minmax(0,1fr)}
21135
+ .knobs-wt-srow[data-go]{cursor:pointer}
21136
+ .knobs-wt-srow[data-go]:hover{background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
21137
+ .knobs-wt-lanes{display:flex;align-self:stretch;margin-block:calc(var(--kw-spt) * -1) calc(var(--kw-spb) * -1)}
21138
+ .knobs-wt-lane{position:relative;flex:none;width:16px}
21139
+ .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)}
21140
+ .knobs-wt-lane[data-down]::before{top:calc(var(--kw-sy) + 3.5px)}
21141
+ .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}
21142
+ .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)}
21143
+ .knobs-wt-srow[data-trunk] .knobs-wt-lane[data-rail="node"]::after{background:var(--_k-fg-2)}
21144
+ .knobs-wt-trunk{font:var(--_k-weight-label) var(--_k-type-label)/var(--kw-line) var(--_k-mono);color:var(--_k-fg-2)}
21145
+ .knobs-wt-smain{display:flex;flex-direction:column;gap:1px;min-width:0}
21146
+ .knobs-wt-sline{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 8px}
21147
+ .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}
21148
+ .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}
21149
+ .knobs-ui a.knobs-wt-spr:is(:hover,:focus-visible){color:var(--_k-fg);text-decoration:underline}
21150
+ .knobs-ui .knobs-wt-stitle{font-weight:var(--_k-weight-label);line-height:var(--kw-line);color:var(--_k-fg);text-align:left}
21151
+ .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)}
21152
+ .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)}
21153
+ .knobs-ui .knobs-wt-ssummary{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
21154
+ .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)}
21155
+ .knobs-wt-delta{display:inline-flex;gap:6px}
21156
+ .knobs-wt-add{color:var(--_k-diff-add)}
21157
+ .knobs-wt-del{color:var(--_k-diff-del)}
21158
+ .knobs-wt-sstatus{margin-top:calc((var(--kw-line) - 20px) / 2)}
21159
+ .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr) auto}
21160
+ .knobs-wt-stack[data-flat] .knobs-wt-srow+.knobs-wt-srow{box-shadow:inset 0 1px 0 var(--_k-border)}
21161
+ @container knobs-wt-slot (max-width:520px){
21162
+ .knobs-wt-srow:not([data-trunk]){grid-template-columns:auto minmax(0,1fr)}
21163
+ .knobs-wt-stack[data-flat] .knobs-wt-srow{grid-template-columns:minmax(0,1fr)}
21164
+ .knobs-wt-sstatus{grid-column:-2;justify-self:start;margin-top:4px}
20264
21165
  }
20265
- var grid = (v) => Math.round(v * 64) / 64;
20266
- function chapterPills(host, chapters, io) {
20267
- const doc = host.ownerDocument;
20268
- const win = doc.defaultView;
20269
- const bar = el(doc, "div", "knobs-wt-chapters");
20270
- bar.setAttribute("role", "group");
20271
- bar.setAttribute("aria-label", "Chapters");
20272
- const mark = el(doc, "span", "knobs-wt-chapter-mark");
20273
- mark.setAttribute("aria-hidden", "true");
20274
- bar.append(mark);
20275
- const pills = chapters.map((c) => {
20276
- const b = el(doc, "button", "knobs-wt-chapter");
20277
- b.type = "button";
20278
- b.append(text(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
20279
- b.append(text(doc, "span", "knobs-wt-chapter-label", c.label));
20280
- b.addEventListener("click", () => {
20281
- io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
20282
- io.seek(c.start);
20283
- sync();
20284
- });
20285
- bar.append(b);
20286
- return b;
20287
- });
20288
- host.append(bar);
20289
- const x = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
20290
- const y = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
20291
- const w = new Spring(0, MOTION.calm.omega, MOTION.calm.zeta, 0.25);
20292
- const draw = () => {
20293
- const left = grid(x.x);
20294
- mark.style.transform = `translate(${left}px,${grid(y.x)}px)`;
20295
- mark.style.width = `${grid(x.x + w.x) - left}px`;
20296
- };
20297
- const loop = frameLoop(win, (dt) => {
20298
- x.step(dt);
20299
- y.step(dt);
20300
- w.step(dt);
20301
- draw();
20302
- return !x.resting || !y.resting || !w.resting;
20303
- });
20304
- let at = -2;
20305
- let shown = false;
20306
- const place = (snap) => {
20307
- const p = pills[at];
20308
- if (!p) {
20309
- mark.removeAttribute("data-on");
20310
- pills.forEach((b) => b.removeAttribute("aria-current"));
20311
- shown = false;
20312
- return;
20313
- }
20314
- pills.forEach((b, i) => i === at ? b.setAttribute("aria-current", "true") : b.removeAttribute("aria-current"));
20315
- const to = [p.offsetLeft, p.offsetTop, p.offsetWidth];
20316
- if (snap || !shown || reducedMotion(win)) {
20317
- x.snap(to[0]);
20318
- y.snap(to[1]);
20319
- w.snap(to[2]);
20320
- draw();
20321
- } else {
20322
- x.set(to[0]);
20323
- y.set(to[1]);
20324
- w.set(to[2]);
20325
- loop.wake();
20326
- }
20327
- mark.style.height = `${p.offsetHeight}px`;
20328
- mark.dataset.on = "";
20329
- shown = true;
20330
- };
20331
- const sync = () => {
20332
- const next = chapterAt(chapters, io.now());
20333
- if (next === at)
20334
- return;
20335
- at = next;
20336
- place(false);
20337
- };
20338
- sync();
20339
- const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => place(true)) : null;
20340
- ro?.observe(bar);
20341
- return {
20342
- sync,
20343
- get current() {
20344
- return chapters[at] ?? null;
20345
- },
20346
- dispose() {
20347
- loop.stop();
20348
- ro?.disconnect();
20349
- bar.remove();
20350
- }
20351
- };
21166
+
21167
+ /* A table: a real one, its head stuck while it scrolls, both ways; edges fade where there's more
21168
+ (the top one under the head), and thin bars show on hover. */
21169
+ /* Too narrow for its columns, each row becomes a block: its primary cell titling the rest as term
21170
+ and value, the blocks parted by hairlines. Scrolling sideways is the last resort (stack: never). */
21171
+ .knobs-wt-tablebox{min-width:0}
21172
+ .knobs-wt-tablebox[data-measure] table{width:min-content}
21173
+ .knobs-wt-tstack{display:none;flex-direction:column;min-width:0}
21174
+ .knobs-wt-tablebox[data-stacked]>.knobs-wt-table{display:none}
21175
+ .knobs-wt-tablebox[data-stacked]>.knobs-wt-tstack{display:flex}
21176
+ .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)}
21177
+ .knobs-wt-trow:first-child{padding-top:0}
21178
+ .knobs-wt-trow:last-of-type{padding-bottom:0;box-shadow:none}
21179
+ .knobs-wt-tline{display:flex;align-items:baseline;justify-content:space-between;gap:var(--_k-space-title);min-width:0}
21180
+ .knobs-wt-tbadges{flex:none;display:flex;align-items:center;gap:var(--_k-space-inside)}
21181
+ .knobs-wt-ttitle{flex:1 1 auto;min-width:0;color:var(--_k-fg);font-weight:var(--_k-weight-label);overflow-wrap:anywhere}
21182
+ .knobs-wt-ttitle[data-mono]{font-family:var(--_k-mono);font-size:var(--_k-type-label)}
21183
+ .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}
21184
+ .knobs-wt-tterms dt{color:var(--_k-fg-3);font-size:var(--_k-type-small)}
21185
+ .knobs-wt-tterms dd{min-width:0;color:var(--_k-fg-2);overflow-wrap:anywhere}
21186
+ .knobs-wt-tterms dd[data-mono]{font-family:var(--_k-mono);font-size:var(--_k-type-label);color:var(--_k-fg)}
21187
+ .knobs-wt-tcaption{padding-top:var(--_k-space-sibling);color:var(--_k-fg-3);font-size:var(--_k-type-small)}
21188
+ @property --kw-fade-l{syntax:"<number>";inherits:false;initial-value:1}
21189
+ @property --kw-fade-r{syntax:"<number>";inherits:false;initial-value:1}
21190
+ @keyframes kw-fade-l{from{--kw-fade-l:1}to{--kw-fade-l:0}}
21191
+ @keyframes kw-fade-r{from{--kw-fade-r:0}to{--kw-fade-r:1}}
21192
+ .knobs-wt-table{max-height:520px;overflow:auto;overscroll-behavior-x:contain;outline:none;scrollbar-width:thin;scrollbar-color:transparent transparent;
21193
+ transition:scrollbar-color calc(var(--_k-hover-out) * 1ms)}
21194
+ .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)}
21195
+ .knobs-wt-table:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus);border-radius:calc(var(--_k-radius-panel) - 4px)}
21196
+ @supports (animation-timeline:scroll()){
21197
+ .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;
21198
+ 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%;
21199
+ --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)));
21200
+ --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)));
21201
+ -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}
20352
21202
  }
21203
+ .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)}
21204
+ .knobs-wt-table caption{caption-side:bottom;padding-top:8px;text-align:left;color:var(--_k-fg-3);font-size:var(--_k-type-small)}
21205
+ .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;
21206
+ background:var(--kw-under,var(--_k-pane-bg));box-shadow:inset 0 -1px 0 var(--_k-border)}
21207
+ .knobs-wt-table td{padding:8px 12px;vertical-align:baseline;box-shadow:inset 0 -1px 0 var(--_k-border)}
21208
+ .knobs-wt-table tr:last-child td{box-shadow:none}
21209
+ .knobs-wt-table :is(th,td):first-child{padding-left:0}
21210
+ .knobs-wt-table :is(th,td):last-child{padding-right:0}
21211
+ .knobs-wt-table td:first-child{color:var(--_k-fg)}
21212
+ .knobs-wt-table [data-align="right"]{text-align:right}
21213
+ .knobs-wt-table [data-align="center"]{text-align:center}
21214
+ .knobs-wt-table td[data-mono]{font-family:var(--_k-mono);font-size:var(--_k-type-label);color:var(--_k-fg);white-space:nowrap}
21215
+ .knobs-wt-table td[data-long]{min-width:24ch}
21216
+ .knobs-ui :is(.knobs-wt-table,.knobs-wt-tstack) a.knobs-wt-chip{text-decoration:none}
21217
+ .knobs-wt-ctone{color:var(--kw-tone)}
21218
+
21219
+ /* Chapters: a readout list under a video or compare: number, label, start; the selection pill
21220
+ on the one playing, its rows bled out so their text sits on the media's edge. */
21221
+ .knobs-wt-body:has(>.knobs-wt-chapters){display:flex;flex-direction:column;gap:var(--_k-space-title)}
21222
+ .knobs-wt-chapters{position:relative;display:flex;flex-direction:column;margin:0 calc(var(--_k-space-sibling) * -1)}
21223
+ .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);
21224
+ 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)}
21225
+ .knobs-ui .knobs-wt-chapter:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
21226
+ .knobs-ui .knobs-wt-chapter:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
21227
+ .knobs-wt-chapter-n{font-size:var(--_k-type-small);color:var(--_k-fg-3)}
21228
+ .knobs-wt-chapter-label{min-width:0;overflow:hidden;${fade2}}
21229
+ .knobs-wt-chapter-time{font:var(--_k-weight) var(--_k-type-small)/1 var(--_k-mono);color:var(--_k-fg-3)}
21230
+ .knobs-pick-copy :is(.knobs-wt-chapter-n,.knobs-wt-chapter-time){color:var(--_k-fg-2)}
21231
+ @media(prefers-reduced-motion:reduce){:is(.knobs-walkthrough,.knobs-wt-slot) :where(*,::before,::after){transition-duration:.15s!important;transition-property:opacity,background,color!important}}
21232
+ @media(prefers-reduced-motion:reduce){.knobs-wt-disc-content{transition:opacity .15s}}
21233
+ `;
20353
21234
 
20354
21235
  // src/ui/walkthrough-media.ts
20355
21236
  function injectWalkthroughStyles(doc) {
@@ -20878,7 +21759,7 @@ function mediaSlot(container, options) {
20878
21759
  }
20879
21760
  };
20880
21761
  }
20881
- var CHECK3 = svg('<path d="m3.5 8.4 3 3 6-6.6"/>');
21762
+ var CHECK3 = icon("check", { small: true });
20882
21763
 
20883
21764
  // src/ui/walkthrough.ts
20884
21765
  var el2 = (doc, tag, cls) => {
@@ -20898,14 +21779,18 @@ var lookOf2 = (o) => ({
20898
21779
  });
20899
21780
  var grid2 = (v) => Math.round(v * 64) / 64;
20900
21781
  var ICON = {
20901
- full: svg('<path d="M3 6.5V3h3.5M13 6.5V3H9.5M3 9.5V13h3.5M13 9.5V13H9.5"/>'),
20902
- exit: svg('<path d="M6.5 3v3.5H3M9.5 3v3.5H13M6.5 13V9.5H3M9.5 13V9.5H13"/>'),
20903
- 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"/>'),
20904
- 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"/>'),
21782
+ full: icon("cornersOut"),
21783
+ exit: icon("cornersIn"),
21784
+ pen: icon("pencilSimple"),
21785
+ mic: icon("microphone"),
20905
21786
  stop: svg('<rect x="4.5" y="4.5" width="7" height="7" rx="1.5" fill="currentColor" stroke="none"/>'),
20906
- send: svg('<path d="M8 13V3.5M4 7.5 8 3.5l4 4"/>'),
20907
- close: svg('<path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/>')
21787
+ send: icon("paperPlaneRight"),
21788
+ close: icon("x", { small: true }),
21789
+ back: icon("arrowLeft", { small: true }),
21790
+ out: icon("arrowUpRight", { small: true })
20908
21791
  };
21792
+ var DOCK_INSET = 12;
21793
+ var DOCK_GAP = 12;
20909
21794
  var CALM2 = springMotion(SPRINGS.calm);
20910
21795
  var SMOOTH = springMotion(SPRINGS.smooth);
20911
21796
  var BACK_MS = 4500;
@@ -21038,8 +21923,12 @@ function walkthrough(container, options) {
21038
21923
  let count = null;
21039
21924
  let fill = null;
21040
21925
  let tocEl = null;
21041
- let mark = null;
21926
+ let pill = null;
21927
+ let tocRows = [];
21928
+ let discs = [];
21042
21929
  let full = null;
21930
+ const noteMode = options.noteBar ?? "collapsed";
21931
+ let reserve = noteBarReserve(52, DOCK_INSET, DOCK_GAP);
21043
21932
  const viewOf = (key) => views.find((v) => v.step.key === key);
21044
21933
  const feedback = () => collectFeedback(page, state);
21045
21934
  const markdown = () => feedbackMarkdown(feedback(), page.verdicts);
@@ -21063,9 +21952,6 @@ function walkthrough(container, options) {
21063
21952
  else
21064
21953
  run();
21065
21954
  };
21066
- const pillY = new Spring(0, CALM2.omega, CALM2.zeta, 0.25);
21067
- const pillH = new Spring(0, CALM2.omega, CALM2.zeta, 0.25);
21068
- let pillPlaced = false;
21069
21955
  const scroll = {
21070
21956
  spring: new Spring(0, CALM2.omega, CALM2.zeta, 0.5),
21071
21957
  to: null,
@@ -21073,13 +21959,6 @@ function walkthrough(container, options) {
21073
21959
  };
21074
21960
  const motion = frameLoop(win, (dt) => {
21075
21961
  let more = false;
21076
- if (mark) {
21077
- pillY.step(dt);
21078
- pillH.step(dt);
21079
- mark.style.transform = `translateY(${grid2(pillY.x)}px)`;
21080
- mark.style.height = `${grid2(pillY.x + pillH.x) - grid2(pillY.x)}px`;
21081
- more ||= !pillY.resting || !pillH.resting;
21082
- }
21083
21962
  if (scroll.to && scroll.el) {
21084
21963
  scroll.spring.set(scrollTarget(scroll.to, scroll.el));
21085
21964
  scroll.spring.step(dt);
@@ -21143,21 +22022,12 @@ function walkthrough(container, options) {
21143
22022
  v.glyph.dataset.tone = v.step.ask === "none" ? "none" : verdict ? verdictTone(verdict, page.verdicts.findIndex((x) => x.id === verdict)) : done ? "accent" : "dim";
21144
22023
  };
21145
22024
  const placeMark = () => {
21146
- if (!mark || !tocEl)
22025
+ if (!pill || !tocEl)
21147
22026
  return;
22027
+ pill.update();
21148
22028
  const row = viewOf(current)?.toc;
21149
22029
  if (!row)
21150
- return void mark.removeAttribute("data-on");
21151
- mark.dataset.on = "";
21152
- if (!pillPlaced || reduced()) {
21153
- pillY.snap(row.offsetTop);
21154
- pillH.snap(row.offsetHeight);
21155
- pillPlaced = true;
21156
- } else {
21157
- pillY.set(row.offsetTop);
21158
- pillH.set(row.offsetHeight);
21159
- }
21160
- motion.wake();
22030
+ return;
21161
22031
  const top = row.offsetTop;
21162
22032
  const bottom = top + row.offsetHeight;
21163
22033
  if (top < tocEl.scrollTop + 8)
@@ -21174,6 +22044,9 @@ function walkthrough(container, options) {
21174
22044
  const v = viewOf(key);
21175
22045
  v?.card.setAttribute("data-current", "");
21176
22046
  v?.toc?.setAttribute("aria-current", "step");
22047
+ for (const r of tocRows)
22048
+ if (r.dataset.section)
22049
+ r.toggleAttribute("data-active", r.dataset.section === v?.step.section);
21177
22050
  if (key)
21178
22051
  record({ what: "step", step: key });
21179
22052
  placeMark();
@@ -21190,6 +22063,16 @@ function walkthrough(container, options) {
21190
22063
  else
21191
22064
  break;
21192
22065
  }
22066
+ const s = scrollerOf(root);
22067
+ if (s.scrollTop > 0 && s.scrollTop >= s.scrollHeight - s.clientHeight - 2) {
22068
+ const bottom = (s === doc.scrollingElement ? win.innerHeight : s.getBoundingClientRect().bottom) - reserve;
22069
+ for (let i = views.length - 1;i >= 0; i--) {
22070
+ if (views[i].card.getBoundingClientRect().top < bottom) {
22071
+ pick = views[i].step.key;
22072
+ break;
22073
+ }
22074
+ }
22075
+ }
21193
22076
  setCurrent(pick);
21194
22077
  };
21195
22078
  let scrollRaf = 0;
@@ -21232,7 +22115,12 @@ function walkthrough(container, options) {
21232
22115
  };
21233
22116
  const scrim = el2(doc, "div", "knobs-wt-scrim");
21234
22117
  scrim.addEventListener("click", () => setFull(false));
21235
- const fullRect = () => ({ x: 12, y: 12, w: win.innerWidth - 24, h: win.innerHeight - 24 });
22118
+ const fullRect = () => ({
22119
+ x: DOCK_INSET,
22120
+ y: DOCK_INSET,
22121
+ w: win.innerWidth - DOCK_INSET * 2,
22122
+ h: win.innerHeight - DOCK_INSET - reserve
22123
+ });
21236
22124
  const placeFull = (f) => {
21237
22125
  const a = f.hold.getBoundingClientRect();
21238
22126
  const b = fullRect();
@@ -21377,6 +22265,7 @@ function walkthrough(container, options) {
21377
22265
  ink.on = true;
21378
22266
  ink.layer.dataset.on = "";
21379
22267
  root.dataset.inking = "";
22268
+ bar.setOpen(true);
21380
22269
  bar.sync();
21381
22270
  };
21382
22271
  const voice = {
@@ -21416,6 +22305,7 @@ function walkthrough(container, options) {
21416
22305
  });
21417
22306
  voice.recorder = rec;
21418
22307
  voice.started = performance.now();
22308
+ bar.setOpen(true);
21419
22309
  rec.start();
21420
22310
  voice.timer = win.setInterval(() => bar.sync(), 250);
21421
22311
  bar.sync();
@@ -21442,7 +22332,10 @@ function walkthrough(container, options) {
21442
22332
  if (!v)
21443
22333
  return;
21444
22334
  backTo = key;
21445
- backPill.textContent = `← Back to ${v.step.number}`;
22335
+ backPill.innerHTML = ICON.back;
22336
+ const words = el2(doc, "span", "knobs-wt-back-label");
22337
+ words.textContent = `Back to ${plainMarkdown(v.step.title)}`;
22338
+ backPill.append(words);
21446
22339
  backPill.dataset.on = "";
21447
22340
  win.clearTimeout(backTimer);
21448
22341
  backTimer = win.setTimeout(hideBack, BACK_MS);
@@ -21474,7 +22367,41 @@ function walkthrough(container, options) {
21474
22367
  sendBtn.dataset.send = "";
21475
22368
  row.append(input, attach, fullBtn, penBtn, micBtn, sendBtn);
21476
22369
  box.append(on, row);
21477
- dock.append(backPill, box);
22370
+ const launch = el2(doc, "button", "knobs-wt-launch");
22371
+ launch.type = "button";
22372
+ launch.innerHTML = ICON.pen;
22373
+ launch.append("Note");
22374
+ const kbd = el2(doc, "kbd");
22375
+ kbd.textContent = "n";
22376
+ launch.append(kbd);
22377
+ launch.setAttribute("aria-label", "Write a note (n)");
22378
+ launch.addEventListener("click", () => setOpen(true, true));
22379
+ dock.dataset.mode = noteMode;
22380
+ dock.append(backPill, box, launch);
22381
+ let open = noteMode !== "collapsed";
22382
+ const setOpen = (on, focus = false) => {
22383
+ if (noteMode === "collapsed" && on !== open) {
22384
+ open = on;
22385
+ dock.toggleAttribute("data-open", on);
22386
+ box.inert = !on;
22387
+ launch.inert = on;
22388
+ syncReserve();
22389
+ }
22390
+ if (on && focus)
22391
+ input.focus({ preventScroll: true });
22392
+ };
22393
+ dock.toggleAttribute("data-open", open);
22394
+ box.inert = !open;
22395
+ launch.inert = open;
22396
+ const empty = () => !input.value.trim() && !voice.pending && !voice.recorder && !ink.on && !ink.paths.length;
22397
+ const onOutside = (e) => {
22398
+ if (open && noteMode === "collapsed" && !dock.contains(e.target) && empty())
22399
+ setOpen(false);
22400
+ };
22401
+ doc.addEventListener("pointerdown", onOutside, true);
22402
+ const ro = "ResizeObserver" in win ? new win.ResizeObserver(() => syncReserve()) : null;
22403
+ ro?.observe(box);
22404
+ ro?.observe(launch);
21478
22405
  const fit = () => {
21479
22406
  input.style.height = "auto";
21480
22407
  input.style.height = `${Math.min(140, input.scrollHeight)}px`;
@@ -21490,6 +22417,8 @@ function walkthrough(container, options) {
21490
22417
  } else if (e.key === "Escape") {
21491
22418
  e.preventDefault();
21492
22419
  input.blur();
22420
+ if (empty())
22421
+ setOpen(false);
21493
22422
  viewOf(current)?.card.focus({ preventScroll: true });
21494
22423
  }
21495
22424
  });
@@ -21509,13 +22438,13 @@ function walkthrough(container, options) {
21509
22438
  const sync = () => {
21510
22439
  const v = viewOf(current);
21511
22440
  const ctx = describeMedia(mediaContext(v));
21512
- const where = v ? `${v.step.number} ${v.step.title}` : "General note";
22441
+ const where = v ? plainMarkdown(v.step.title) : "General note";
21513
22442
  const text = ctx ? `${where} · ${ctx}` : where;
21514
22443
  if (text !== lastOn) {
21515
22444
  on.textContent = text;
21516
22445
  lastOn = text;
21517
22446
  }
21518
- input.placeholder = v ? `Note on ${v.step.number}… (n)` : "A note on the whole page… (n)";
22447
+ input.placeholder = v ? "A note on this step… (n)" : "A note on the whole page… (n)";
21519
22448
  const attached = `${voice.pending?.url ?? ""}|${ink.paths.length}`;
21520
22449
  if (attached !== lastAttached) {
21521
22450
  lastAttached = attached;
@@ -21554,14 +22483,45 @@ function walkthrough(container, options) {
21554
22483
  };
21555
22484
  return {
21556
22485
  dock,
22486
+ box,
22487
+ launch,
21557
22488
  input,
21558
22489
  sync,
22490
+ get open() {
22491
+ return open;
22492
+ },
22493
+ setOpen,
22494
+ empty,
22495
+ unobserve: () => {
22496
+ ro?.disconnect();
22497
+ doc.removeEventListener("pointerdown", onOutside, true);
22498
+ },
21559
22499
  clear() {
21560
22500
  input.value = "";
21561
22501
  fit();
21562
22502
  }
21563
22503
  };
21564
22504
  })();
22505
+ let padded = null;
22506
+ function syncReserve() {
22507
+ if (disposed)
22508
+ return;
22509
+ const h = (bar.open ? bar.box : bar.launch).offsetHeight;
22510
+ if (noteMode === "hidden")
22511
+ reserve = 0;
22512
+ else if (h)
22513
+ reserve = noteBarReserve(h, DOCK_INSET, DOCK_GAP);
22514
+ root.style.setProperty("--kw-reserve", `${reserve}px`);
22515
+ const s = scrollerOf(root);
22516
+ if (padded?.el !== s) {
22517
+ if (padded)
22518
+ padded.el.style.scrollPaddingBottom = padded.was;
22519
+ padded = { el: s, was: s.style.scrollPaddingBottom };
22520
+ }
22521
+ s.style.scrollPaddingBottom = `${reserve}px`;
22522
+ if (full)
22523
+ placeFull(full);
22524
+ }
21565
22525
  const ticker = win.setInterval(() => !doc.hidden && bar.sync(), 500);
21566
22526
  const send = (text = bar.input.value) => {
21567
22527
  if (voice.recorder)
@@ -21597,6 +22557,10 @@ function walkthrough(container, options) {
21597
22557
  syncProgress();
21598
22558
  emit();
21599
22559
  bar.sync();
22560
+ if (noteMode === "collapsed") {
22561
+ bar.input.blur();
22562
+ bar.setOpen(false);
22563
+ }
21600
22564
  if (v && options.advance !== false) {
21601
22565
  const to = adjacentStep(views.map((x) => x.step.key), v.step.key, 1);
21602
22566
  if (to && to !== v.step.key) {
@@ -21604,7 +22568,8 @@ function walkthrough(container, options) {
21604
22568
  setFull(false);
21605
22569
  go(to);
21606
22570
  showBack(v.step.key);
21607
- bar.input.focus({ preventScroll: true });
22571
+ if (noteMode === "docked")
22572
+ bar.input.focus({ preventScroll: true });
21608
22573
  }
21609
22574
  }
21610
22575
  };
@@ -21691,22 +22656,105 @@ function walkthrough(container, options) {
21691
22656
  row.addEventListener("transitionend", done, { once: true });
21692
22657
  win.setTimeout(done, 600);
21693
22658
  };
22659
+ const metaLine = (cls) => el2(doc, "div", `knobs-wt-metaline ${cls}`);
22660
+ const metaText = (line, text, cls) => {
22661
+ const s = el2(doc, "span", cls);
22662
+ s.textContent = text;
22663
+ line.append(s);
22664
+ return s;
22665
+ };
22666
+ const metaLink = (line, text, href) => {
22667
+ const a = el2(doc, "a");
22668
+ a.href = href;
22669
+ a.target = "_blank";
22670
+ a.rel = "noreferrer";
22671
+ a.textContent = text;
22672
+ a.insertAdjacentHTML("beforeend", ICON.out);
22673
+ line.append(a);
22674
+ return a;
22675
+ };
22676
+ const feedItem = (f, now) => {
22677
+ const li = el2(doc, "li", "knobs-wt-fi");
22678
+ li.dataset.kind = f.kind;
22679
+ const q = el2(doc, "div", "knobs-wt-fi-q");
22680
+ const meta = metaLine("knobs-wt-fi-meta");
22681
+ if (f.at !== null) {
22682
+ const when = metaText(meta, ago(f.at, now));
22683
+ when.title = new Date(f.at).toLocaleString();
22684
+ } else
22685
+ metaText(meta, f.kind === "qa" ? "Answer" : "Update");
22686
+ const step = f.step ? page.steps.find((s) => s.key === f.step) : undefined;
22687
+ if (step) {
22688
+ const section = page.sections.find((s) => s.id === step.section);
22689
+ const b = el2(doc, "button", "knobs-wt-fi-step");
22690
+ b.type = "button";
22691
+ b.textContent = section?.link?.label ?? plainMarkdown(step.title);
22692
+ b.title = `Go to “${plainMarkdown(step.title)}”`;
22693
+ b.addEventListener("click", () => go(step.key));
22694
+ meta.append(b);
22695
+ }
22696
+ if (f.status) {
22697
+ const id = f.status.toLowerCase();
22698
+ const shipped = /^(ship|shipped|merged|done)$/.test(id);
22699
+ const changes = /^(changes?|changes queued)$/.test(id);
22700
+ const s = metaText(meta, shipped ? "Shipped" : changes ? "Changes queued" : f.status);
22701
+ s.dataset.status = shipped ? "on" : changes ? "warn" : "";
22702
+ }
22703
+ q.append(meta);
22704
+ if (f.q)
22705
+ q.append(prose2(doc, "p", "knobs-wt-fi-ask", f.q));
22706
+ else
22707
+ q.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
22708
+ li.append(q);
22709
+ if (f.q || f.trail.length) {
22710
+ const a = el2(doc, "div", "knobs-wt-fi-a");
22711
+ if (f.q)
22712
+ a.append(prose2(doc, "div", "knobs-wt-fi-answer", f.text));
22713
+ if (f.trail.length) {
22714
+ const list = el2(doc, "ol", "knobs-wt-trail");
22715
+ for (const line of f.trail) {
22716
+ const i = line.indexOf(" · ");
22717
+ const row = el2(doc, "li");
22718
+ const words = el2(doc, "span", "knobs-wt-trail-text");
22719
+ words.textContent = i < 0 ? line : line.slice(i + 3);
22720
+ row.append(words);
22721
+ if (i >= 0) {
22722
+ const at = el2(doc, "span", "knobs-wt-trail-at");
22723
+ at.textContent = line.slice(0, i);
22724
+ row.append(at);
22725
+ }
22726
+ list.append(row);
22727
+ }
22728
+ discs.push(disclosure(a, { label: "Context", count: f.trail.length, content: list }));
22729
+ }
22730
+ li.append(a);
22731
+ }
22732
+ return li;
22733
+ };
21694
22734
  const build = () => {
21695
22735
  for (const v of views) {
21696
22736
  v.slots.forEach((s) => s.dispose());
21697
22737
  v.taste?.dispose();
21698
22738
  }
22739
+ for (const d of discs)
22740
+ d.dispose();
22741
+ discs = [];
22742
+ pill?.dispose();
22743
+ pill = null;
22744
+ tocRows = [];
21699
22745
  if (full) {
21700
22746
  full.p.snap(0);
21701
22747
  closeFull();
21702
22748
  }
21703
22749
  clearInk();
21704
22750
  views = [];
21705
- pillPlaced = false;
21706
22751
  root.textContent = "";
21707
22752
  root.removeAttribute("data-settled");
22753
+ root.dataset.rail = options.rail === false ? "none" : options.rail === "left" ? "left" : "right";
22754
+ root.toggleAttribute("data-cards", options.cards === true);
21708
22755
  const layout = el2(doc, "div", "knobs-wt-layout");
21709
22756
  const main = el2(doc, "div", "knobs-wt-main");
22757
+ const flow = el2(doc, "div", "knobs-wt-flow");
21710
22758
  const head = el2(doc, "header", "knobs-wt-head");
21711
22759
  const h1 = el2(doc, "h1", "knobs-wt-title");
21712
22760
  h1.textContent = page.title;
@@ -21733,42 +22781,27 @@ function walkthrough(container, options) {
21733
22781
  }
21734
22782
  head.append(meta);
21735
22783
  }
21736
- main.append(head);
22784
+ flow.append(head);
22785
+ let feedEl = null;
21737
22786
  if (page.feed.length) {
21738
- const feed = el2(doc, "section", "knobs-wt-feed");
21739
- feed.setAttribute("aria-label", "Latest");
21740
- const list = el2(doc, "ol", "knobs-wt-feed-list knobs-scroll-fade");
22787
+ feedEl = el2(doc, "section", "knobs-wt-feed");
22788
+ feedEl.id = "wt-latest";
22789
+ feedEl.setAttribute("aria-label", "Updates and answers");
22790
+ const fh = el2(doc, "header", "knobs-wt-feed-head");
22791
+ const h = el2(doc, "h2", "knobs-wt-feed-title");
22792
+ h.textContent = page.feed.some((f) => f.kind === "qa") ? "Updates & Q&A" : "Updates";
22793
+ const n = el2(doc, "span", "knobs-wt-feed-count");
22794
+ n.textContent = String(page.feed.length);
22795
+ fh.append(h, n);
22796
+ const list = el2(doc, "ol", "knobs-wt-feed-list");
21741
22797
  const now = Date.now();
21742
- for (const f of page.feed) {
21743
- const li = el2(doc, "li", "knobs-wt-fi");
21744
- li.dataset.kind = f.kind;
21745
- const when = el2(doc, "span", "knobs-wt-fi-when");
21746
- when.textContent = f.at === null ? "" : ago(f.at, now);
21747
- if (f.at !== null)
21748
- when.title = new Date(f.at).toLocaleString();
21749
- const kind = el2(doc, "span", "knobs-wt-fi-kind");
21750
- kind.textContent = f.kind === "qa" ? "Answer" : "Update";
21751
- const body = el2(doc, "div", "knobs-wt-fi-body");
21752
- if (f.q)
21753
- body.append(prose2(doc, "p", "knobs-wt-fi-q", f.q));
21754
- body.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
21755
- li.append(when, kind, body);
21756
- if (f.step) {
21757
- const target = f.step;
21758
- const link = el2(doc, "button", "knobs-wt-fi-step");
21759
- link.type = "button";
21760
- link.textContent = page.steps.find((s) => s.key === target)?.number ?? "";
21761
- link.title = "Go to the step";
21762
- link.addEventListener("click", () => go(target));
21763
- li.append(link);
21764
- }
21765
- list.append(li);
21766
- }
21767
- feed.append(list);
21768
- main.append(feed);
22798
+ for (const f of page.feed)
22799
+ list.append(feedItem(f, now));
22800
+ feedEl.append(fh, list);
22801
+ flow.append(feedEl);
21769
22802
  }
21770
22803
  let rail = null;
21771
- count = fill = tocEl = mark = null;
22804
+ count = fill = tocEl = null;
21772
22805
  if (options.rail !== false) {
21773
22806
  rail = el2(doc, "aside", "knobs-wt-rail");
21774
22807
  rail.setAttribute("aria-label", "Progress");
@@ -21780,8 +22813,17 @@ function walkthrough(container, options) {
21780
22813
  progress.append(count, meter);
21781
22814
  tocEl = el2(doc, "nav", "knobs-wt-toc");
21782
22815
  tocEl.setAttribute("aria-label", "Contents");
21783
- mark = el2(doc, "div", "knobs-wt-toc-mark");
21784
- tocEl.append(mark);
22816
+ const ns = page.sections.map((s) => s.kicker && s.link ? s.link.label : "");
22817
+ const nw = Math.max(0, ...ns.map((n) => n.length));
22818
+ tocEl.style.setProperty("--kw-toc-n", nw ? `${nw + 0.5}ch` : "0px");
22819
+ if (!nw)
22820
+ tocEl.dataset.bare = "";
22821
+ if (feedEl) {
22822
+ const group = el2(doc, "div", "knobs-wt-toc-group");
22823
+ const target = feedEl;
22824
+ group.append(tocRow("Latest", "", String(page.feed.length), false, () => target.scrollIntoView({ block: "start", behavior: reduced() ? "auto" : "smooth" })));
22825
+ tocEl.append(group);
22826
+ }
21785
22827
  rail.append(progress, tocEl, actions(true));
21786
22828
  if (options.keys !== false) {
21787
22829
  const keys = el2(doc, "div", "knobs-wt-keys");
@@ -21793,45 +22835,44 @@ function walkthrough(container, options) {
21793
22835
  const sec = el2(doc, "section", "knobs-wt-section");
21794
22836
  sec.id = `wt-${section.id}`;
21795
22837
  const sh = el2(doc, "header", "knobs-wt-shead");
21796
- if (section.link || section.status) {
21797
- const kicker = el2(doc, "div", "knobs-wt-kicker");
21798
- if (section.link) {
21799
- const a = el2(doc, "a");
21800
- a.href = section.link.href;
21801
- a.target = "_blank";
21802
- a.rel = "noreferrer";
21803
- a.textContent = `${section.link.label} ↗`;
21804
- kicker.append(a);
21805
- }
22838
+ if (section.kicker || section.link || section.status) {
22839
+ const meta = metaLine("knobs-wt-kicker");
22840
+ if (section.kicker)
22841
+ metaText(meta, section.kicker, "knobs-wt-kicker-lead");
22842
+ if (section.link)
22843
+ metaLink(meta, section.link.label, section.link.href);
21806
22844
  if (section.status) {
21807
22845
  const s = statusLook(section.status);
21808
- const chip = el2(doc, "span", "knobs-wt-chip");
21809
- chip.dataset.tone = s.tone;
21810
- chip.textContent = s.label;
21811
- kicker.append(chip);
22846
+ metaText(meta, s.label, "knobs-wt-status").dataset.tone = s.tone;
21812
22847
  }
21813
- sh.append(kicker);
22848
+ sh.append(meta);
21814
22849
  }
21815
- const h2 = el2(doc, "h2", "knobs-wt-stitle");
21816
- h2.textContent = section.title;
22850
+ const h2 = el2(doc, "h2", "knobs-wt-heading");
22851
+ h2.innerHTML = inlineMarkdown(section.title);
21817
22852
  sh.append(h2);
22853
+ const intro = el2(doc, "div", "knobs-wt-sintro");
22854
+ intro.append(sh);
21818
22855
  if (section.summary)
21819
- sh.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
21820
- sec.append(sh);
22856
+ intro.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
22857
+ sec.append(intro);
22858
+ let group = null;
21821
22859
  if (tocEl) {
21822
- const t = el2(doc, "div", "knobs-wt-toc-section");
21823
- t.textContent = section.link ? `${section.link.label} ${section.title}` : section.title;
21824
- t.title = section.title;
21825
- tocEl.append(t);
22860
+ group = el2(doc, "div", "knobs-wt-toc-group");
22861
+ const first = section.steps[0]?.key;
22862
+ const n = section.kicker && section.link ? section.link.label : "";
22863
+ group.append(tocRow(plainMarkdown(section.title), n, "", false, () => first ? go(first) : undefined, section.id));
22864
+ tocEl.append(group);
21826
22865
  }
21827
22866
  for (const step of section.steps) {
21828
- const v = stepView(step);
22867
+ const v = stepView(step, group);
21829
22868
  sec.append(v.card);
21830
22869
  views.push(v);
21831
22870
  }
21832
- main.append(sec);
22871
+ flow.append(sec);
21833
22872
  }
21834
- main.append(bar.dock);
22873
+ main.append(flow);
22874
+ if (noteMode !== "hidden")
22875
+ main.append(bar.dock);
21835
22876
  if (rail)
21836
22877
  layout.append(rail);
21837
22878
  layout.append(main);
@@ -21841,16 +22882,40 @@ function walkthrough(container, options) {
21841
22882
  for (const n of notesOn(state, v.step.key))
21842
22883
  renderNote(v, n);
21843
22884
  }
22885
+ if (tocEl)
22886
+ pill = selectionPill(tocEl, {
22887
+ selected: () => viewOf(current)?.toc ?? null,
22888
+ rows: () => tocRows
22889
+ });
21844
22890
  syncProgress();
21845
22891
  current = null;
21846
22892
  fromScroll();
21847
22893
  placeMark();
21848
22894
  bar.sync();
22895
+ syncReserve();
21849
22896
  win.requestAnimationFrame(() => win.requestAnimationFrame(() => {
21850
22897
  if (!disposed && !reduced())
21851
22898
  root.dataset.settled = "";
21852
22899
  }));
21853
22900
  };
22901
+ const tocRow = (label, n, end, sub, run, section) => {
22902
+ const b = el2(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
22903
+ b.type = "button";
22904
+ if (section)
22905
+ b.dataset.section = section;
22906
+ const ncol = el2(doc, "span", "knobs-wt-toc-n");
22907
+ ncol.textContent = n;
22908
+ const t = el2(doc, "span", "knobs-wt-toc-label");
22909
+ t.textContent = label;
22910
+ const e = el2(doc, "span", "knobs-wt-toc-end");
22911
+ e.textContent = end;
22912
+ b.title = label;
22913
+ b.append(ncol, t, e);
22914
+ b.addEventListener("mousedown", (ev) => ev.preventDefault());
22915
+ b.addEventListener("click", run);
22916
+ tocRows.push(b);
22917
+ return b;
22918
+ };
21854
22919
  const actions = (rail) => {
21855
22920
  const row = el2(doc, "div", "knobs-wt-actions");
21856
22921
  const btn = (text, run, primary = false) => {
@@ -21891,30 +22956,38 @@ function walkthrough(container, options) {
21891
22956
  toast("Couldn't reach the clipboard", { tone: "warn", look, doc });
21892
22957
  }
21893
22958
  };
21894
- const stepView = (step) => {
21895
- const card = el2(doc, "article", "knobs-wt-step knobs-surface");
22959
+ const stepView = (step, group) => {
22960
+ const card = el2(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
21896
22961
  card.id = `wt-${step.key.replace("/", "--")}`;
21897
22962
  card.tabIndex = -1;
21898
22963
  card.dataset.key = step.key;
21899
- card.setAttribute("aria-label", `${step.number} ${step.title}`);
22964
+ card.setAttribute("aria-label", plainMarkdown(step.title));
21900
22965
  const head = el2(doc, "header", "knobs-wt-step-head");
21901
- const num = el2(doc, "span", "knobs-wt-num");
21902
- num.textContent = step.number;
21903
22966
  const h3 = el2(doc, "h3", "knobs-wt-stepTitle");
21904
- h3.textContent = step.title;
21905
- head.append(num, h3);
21906
- card.append(head);
22967
+ h3.innerHTML = inlineMarkdown(step.title);
22968
+ head.append(h3);
21907
22969
  if (step.caption)
21908
- card.append(prose2(doc, "p", "knobs-wt-caption", step.caption));
22970
+ head.append(prose2(doc, "p", "knobs-wt-caption", step.caption));
22971
+ card.append(head);
21909
22972
  const slots = [];
21910
22973
  const onEvent = (e) => record({ ...e, step: step.key });
21911
22974
  let media = null;
21912
22975
  if (step.media.length) {
21913
- media = el2(doc, "div", "knobs-wt-media");
21914
- media.style.setProperty("--kw-n", String(Math.min(step.media.length, 3)));
21915
- for (const m of step.media)
21916
- slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
22976
+ const grid = (list) => {
22977
+ const g = el2(doc, "div", "knobs-wt-media");
22978
+ g.style.setProperty("--kw-n", String(Math.min(list.length, step.columns)));
22979
+ for (const m of list)
22980
+ slots.push(mediaSlot(g, { ...look, media: m, onEvent, steps: stepLinks }));
22981
+ return g;
22982
+ };
22983
+ const [first, ...rest] = step.media;
22984
+ media = grid(step.more ? [first] : step.media);
21917
22985
  card.append(media);
22986
+ if (step.more && rest.length) {
22987
+ const holder = el2(doc, "div", "knobs-wt-more");
22988
+ discs.push(disclosure(holder, { label: step.more, count: rest.length, content: grid(rest) }));
22989
+ card.append(holder);
22990
+ }
21918
22991
  }
21919
22992
  let taste = null;
21920
22993
  if (step.question)
@@ -21939,16 +23012,11 @@ function walkthrough(container, options) {
21939
23012
  });
21940
23013
  let toc = null;
21941
23014
  let glyph = null;
21942
- if (tocEl) {
21943
- toc = el2(doc, "button", "knobs-wt-toc-step");
21944
- toc.type = "button";
23015
+ if (group) {
23016
+ toc = tocRow(plainMarkdown(step.title), "", "", true, () => go(step.key));
21945
23017
  glyph = el2(doc, "span", "knobs-wt-toc-glyph");
21946
- const label = el2(doc, "span", "knobs-wt-toc-label");
21947
- label.textContent = step.title;
21948
- toc.title = `${step.number} ${step.title}`;
21949
- toc.append(glyph, label);
21950
- toc.addEventListener("click", () => go(step.key));
21951
- tocEl.append(toc);
23018
+ toc.querySelector(".knobs-wt-toc-end").append(glyph);
23019
+ group.append(toc);
21952
23020
  }
21953
23021
  return { step, card, media, notes, toc, glyph, taste, slots };
21954
23022
  };
@@ -22007,15 +23075,21 @@ function walkthrough(container, options) {
22007
23075
  setFull(false);
22008
23076
  return go(next);
22009
23077
  }
23078
+ if ((key === "Home" || key === "End") && views.length) {
23079
+ claim();
23080
+ if (full)
23081
+ setFull(false);
23082
+ return go(views[key === "Home" ? 0 : views.length - 1].step.key);
23083
+ }
22010
23084
  if (key === "ArrowLeft" || key === "ArrowRight") {
22011
23085
  const d = (key === "ArrowLeft" ? -1 : 1) * (e.shiftKey ? 2 : 0.5);
22012
23086
  if (seekBy(d))
22013
23087
  claim();
22014
23088
  return;
22015
23089
  }
22016
- if (key === "n") {
23090
+ if (key === "n" && noteMode !== "hidden") {
22017
23091
  claim();
22018
- bar.input.focus({ preventScroll: true });
23092
+ bar.setOpen(true, true);
22019
23093
  return;
22020
23094
  }
22021
23095
  const v = viewOf(current);
@@ -22094,6 +23168,12 @@ function walkthrough(container, options) {
22094
23168
  win.removeEventListener(type, stopScroll);
22095
23169
  doc.removeEventListener("keydown", onKey);
22096
23170
  unregisterHints();
23171
+ bar.unobserve();
23172
+ pill?.dispose();
23173
+ for (const d of discs)
23174
+ d.dispose();
23175
+ if (padded)
23176
+ padded.el.style.scrollPaddingBottom = padded.was;
22097
23177
  for (const v of views) {
22098
23178
  v.slots.forEach((s) => s.dispose());
22099
23179
  v.taste?.dispose();
@@ -22104,9 +23184,11 @@ function walkthrough(container, options) {
22104
23184
  return api;
22105
23185
  }
22106
23186
  export {
23187
+ CATCH_UP,
22107
23188
  COLLAPSE_PAST,
22108
23189
  COMPARE_FFMPEG,
22109
23190
  FIT_MAX,
23191
+ ICON_NAMES,
22110
23192
  activity,
22111
23193
  adjacentMarker,
22112
23194
  advance,
@@ -22123,12 +23205,14 @@ export {
22123
23205
  commandsFrom,
22124
23206
  compare,
22125
23207
  compareLayout,
23208
+ compareModes,
22126
23209
  comparePaths,
22127
23210
  compareStrip,
22128
23211
  compareText,
22129
23212
  confirm,
22130
23213
  contextMenu,
22131
23214
  countChanges,
23215
+ currentMarker,
22132
23216
  diffActivity,
22133
23217
  diffBar,
22134
23218
  diffCounts,
@@ -22153,7 +23237,9 @@ export {
22153
23237
  frameOf,
22154
23238
  frameTime,
22155
23239
  fuzzy,
23240
+ gripTop,
22156
23241
  hints,
23242
+ icon,
22157
23243
  inTriangle,
22158
23244
  indexActivity,
22159
23245
  indexTree,
@@ -22172,7 +23258,9 @@ export {
22172
23258
  mediaKind,
22173
23259
  mediaSlot,
22174
23260
  menu,
23261
+ modeForKey,
22175
23262
  motionBlur,
23263
+ moveHead,
22176
23264
  navigate,
22177
23265
  navigateActivity,
22178
23266
  normalizeWalkthrough,
@@ -22181,9 +23269,14 @@ export {
22181
23269
  pageHints,
22182
23270
  parseLayout,
22183
23271
  parsePatch,
23272
+ pillBoxOf,
23273
+ pillProgress,
23274
+ pillRadius,
23275
+ pillSpring,
22184
23276
  place,
22185
23277
  plainAccess,
22186
23278
  planBento,
23279
+ planPill,
22187
23280
  popover,
22188
23281
  railSegments,
22189
23282
  rank,
@@ -22195,9 +23288,11 @@ export {
22195
23288
  rulerTicks,
22196
23289
  safeTriangle,
22197
23290
  scrubTime,
23291
+ seamLabels,
22198
23292
  seekTime,
22199
23293
  seeked,
22200
23294
  select,
23295
+ selectionPill,
22201
23296
  showsSelection,
22202
23297
  softClamp,
22203
23298
  solve2 as solveSplit,