@kitlangton/knobs 0.4.0-next.4 → 0.4.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -17
- package/dist/dev/{chunk-0w1xk9jc.js → chunk-dyq4m24m.js} +3 -3
- package/dist/dev/{chunk-rem3qrhz.js → chunk-nhz9en4k.js} +2 -2
- package/dist/dev/{chunk-76frnh2j.js → chunk-ya44t7z5.js} +65 -12
- package/dist/dev/index.js +3 -3
- package/dist/panel.js +67 -14
- package/dist/themes.js +3 -3
- package/dist/types/tokens/schema.d.ts +3 -3
- package/dist/types/ui/compare-css.d.ts +1 -1
- package/dist/types/ui/compare-gpu.d.ts +6 -1
- package/dist/types/ui/compare-model.d.ts +55 -1
- package/dist/types/ui/compare.d.ts +3 -2
- package/dist/types/ui/diff.d.ts +4 -5
- package/dist/types/ui/history-css.d.ts +1 -1
- package/dist/types/ui/history-model.d.ts +15 -2
- package/dist/types/ui/history.d.ts +5 -0
- package/dist/types/ui/index.d.ts +4 -2
- package/dist/types/ui/player-css.d.ts +1 -0
- package/dist/types/ui/player.d.ts +60 -0
- package/dist/types/ui/timeline-model.d.ts +4 -0
- package/dist/types/ui/timeline.d.ts +10 -3
- package/dist/types/ui/walkthrough-annotations.d.ts +42 -0
- package/dist/types/ui/walkthrough-blocks.d.ts +24 -4
- package/dist/types/ui/walkthrough-model.d.ts +78 -10
- package/dist/types/ui/walkthrough.d.ts +14 -2
- package/dist/ui-react.js +2170 -512
- package/dist/ui-solid.js +2182 -524
- package/dist/ui.js +6184 -4354
- package/package.json +1 -1
package/dist/ui-react.js
CHANGED
|
@@ -578,11 +578,11 @@ var compareCss = `
|
|
|
578
578
|
.knobs-cmp-modes[hidden]{display:none}
|
|
579
579
|
.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;
|
|
580
580
|
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
581
|
-
.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
581
|
+
.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
582
582
|
/* Toggles: icon buttons, the accent's wash when on. */
|
|
583
583
|
.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;
|
|
584
584
|
transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
|
|
585
|
-
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
|
|
585
|
+
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
586
586
|
.knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
|
|
587
587
|
.knobs-cmp-toggle svg{display:block}
|
|
588
588
|
.knobs-cmp-fill{flex:1}
|
|
@@ -596,6 +596,8 @@ var compareCss = `
|
|
|
596
596
|
.knobs-cmp-stage{position:relative;flex:1;min-height:96px;overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_ku-cmp-stage);touch-action:pan-y;outline:none}
|
|
597
597
|
.knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
|
|
598
598
|
.knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
|
|
599
|
+
/* Videos: a click on the stage plays and pauses. */
|
|
600
|
+
.knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
|
|
599
601
|
/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
|
|
600
602
|
.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
|
|
601
603
|
.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
|
|
@@ -605,6 +607,9 @@ var compareCss = `
|
|
|
605
607
|
.knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
|
|
606
608
|
.knobs-cmp-media{position:absolute;left:0;top:0;display:block;max-width:none;margin:0;padding:0;border:0;object-fit:fill;transform-origin:0 0;pointer-events:none;will-change:transform}
|
|
607
609
|
.knobs-compare[data-renderer="gpu"] .knobs-cmp-media{opacity:0}
|
|
610
|
+
/* The media comes in with its first frames: a fade and a 4px blur on the default spring. */
|
|
611
|
+
.knobs-cmp-canvas,.knobs-compare[data-renderer="dom"] .knobs-cmp-media{transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
|
|
612
|
+
.knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}
|
|
608
613
|
/* Glass: what's behind shows through, blurred, on the knobs tokens. */
|
|
609
614
|
.knobs-cmp-label,.knobs-cmp-zoom{background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
|
|
610
615
|
box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim),0 2px 10px rgba(0,0,0,calc(.18 * var(--_k-shadow-k,1)))}
|
|
@@ -613,11 +618,17 @@ var compareCss = `
|
|
|
613
618
|
white-space:nowrap;pointer-events:none;will-change:transform}
|
|
614
619
|
.knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
|
|
615
620
|
.knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
.knobs-cmp-
|
|
621
|
+
/* Fusing, the mark folds away and the label is its name. */
|
|
622
|
+
.knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}
|
|
623
|
+
.knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}
|
|
624
|
+
/* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */
|
|
625
|
+
.knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;
|
|
626
|
+
-webkit-mask-image:linear-gradient(to right,transparent,#000 35%,#000 65%,transparent);mask-image:linear-gradient(to right,transparent,#000 35%,#000 65%,transparent)}
|
|
627
|
+
/* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height
|
|
628
|
+
while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the
|
|
629
|
+
fuse; wiping, line and grip brighten. */
|
|
630
|
+
.knobs-cmp-handle{cursor:ew-resize;position:absolute;left:-12px;top:0;width:24px;height:0;opacity:0;pointer-events:none;outline:none;will-change:transform,opacity}
|
|
631
|
+
.knobs-cmp-handle[data-on]{pointer-events:auto}
|
|
621
632
|
.knobs-cmp-handle::before{content:"";position:absolute;top:0;bottom:0;left:11.5px;width:1px;background:color-mix(in srgb,var(--_ku-cmp-line) 72%,transparent);box-shadow:0 0 0 .5px rgba(0,0,0,.28),0 0 6px rgba(0,0,0,.25)}
|
|
622
633
|
.knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
|
|
623
634
|
.knobs-cmp-grip{position:absolute;top:0;left:4px;width:16px;height:28px;border-radius:min(8px,calc(var(--_k-radius) * 999));background:var(--_k-bg-overlay);
|
|
@@ -637,7 +648,7 @@ var compareCss = `
|
|
|
637
648
|
.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)}
|
|
638
649
|
: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;
|
|
639
650
|
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}
|
|
640
|
-
: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)}
|
|
651
|
+
:where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){color:var(--_k-fg);background:color-mix(in srgb,var(--_k-fg) 12%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
641
652
|
.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}
|
|
642
653
|
.knobs-cmp-note:empty{display:none}
|
|
643
654
|
.knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
|
|
@@ -649,6 +660,31 @@ var compareCss = `
|
|
|
649
660
|
@media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}
|
|
650
661
|
`;
|
|
651
662
|
|
|
663
|
+
// src/ui/player-css.ts
|
|
664
|
+
var playerCss = `
|
|
665
|
+
:where(.knobs-player,.knobs-player *){box-sizing:border-box}
|
|
666
|
+
.knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
|
|
667
|
+
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
668
|
+
:where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-player,.knobs-ui.knobs-player[data-nested]{--_kp-stage:var(--k-compare-stage,transparent)}
|
|
669
|
+
.knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage);cursor:pointer}
|
|
670
|
+
.knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
|
|
671
|
+
.knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
|
|
672
|
+
opacity:0;filter:blur(4px);transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
|
|
673
|
+
.knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
|
|
674
|
+
.knobs-player-glyph{position:absolute;left:50%;top:50%;display:grid;place-items:center;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;color:var(--_k-fg);pointer-events:none;
|
|
675
|
+
opacity:0;filter:blur(4px);scale:.96;
|
|
676
|
+
background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
|
|
677
|
+
box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
|
|
678
|
+
transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),filter calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
679
|
+
.knobs-player-glyph svg{width:16px;height:16px}
|
|
680
|
+
.knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
|
|
681
|
+
.knobs-player-line{flex:none;min-height:0}
|
|
682
|
+
/* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover
|
|
683
|
+
states are set here, after it, so the player's line and every timeline share them. */
|
|
684
|
+
.knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
|
|
685
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
686
|
+
`;
|
|
687
|
+
|
|
652
688
|
// src/ui/overlay-css.ts
|
|
653
689
|
var overlayCss = `
|
|
654
690
|
.knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}
|
|
@@ -1513,7 +1549,7 @@ var init_schema = __esm(() => {
|
|
|
1513
1549
|
type: "length",
|
|
1514
1550
|
group: "Type",
|
|
1515
1551
|
source: value("20px"),
|
|
1516
|
-
doc: "
|
|
1552
|
+
doc: "A page's title, weight 500."
|
|
1517
1553
|
},
|
|
1518
1554
|
{
|
|
1519
1555
|
name: "leading-heading",
|
|
@@ -1529,7 +1565,7 @@ var init_schema = __esm(() => {
|
|
|
1529
1565
|
type: "length",
|
|
1530
1566
|
group: "Type",
|
|
1531
1567
|
source: value("16px"),
|
|
1532
|
-
doc: "
|
|
1568
|
+
doc: "Section titles (under their kicker), weight 500."
|
|
1533
1569
|
},
|
|
1534
1570
|
{
|
|
1535
1571
|
name: "leading-title",
|
|
@@ -1561,7 +1597,7 @@ var init_schema = __esm(() => {
|
|
|
1561
1597
|
type: "length",
|
|
1562
1598
|
group: "Type",
|
|
1563
1599
|
source: value("12px"),
|
|
1564
|
-
doc: "
|
|
1600
|
+
doc: "Mono inside body text: code and paths."
|
|
1565
1601
|
},
|
|
1566
1602
|
{
|
|
1567
1603
|
name: "leading-label",
|
|
@@ -3004,8 +3040,6 @@ var css2 = `
|
|
|
3004
3040
|
.knobs-tl-hover{position:absolute;left:0;top:0;bottom:0;width:1px;margin-left:-.5px;background:var(--_k-border-strong);transform:translateX(-100px);pointer-events:none;opacity:0;transition:opacity .15s}
|
|
3005
3041
|
.knobs-tl-track:hover .knobs-tl-hover{opacity:1}
|
|
3006
3042
|
.knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
|
|
3007
|
-
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
3008
|
-
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
3009
3043
|
.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}
|
|
3010
3044
|
.knobs-tl-tag[data-on]{opacity:1}
|
|
3011
3045
|
/* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
|
|
@@ -3021,7 +3055,26 @@ var css2 = `
|
|
|
3021
3055
|
.knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
|
|
3022
3056
|
.knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
|
|
3023
3057
|
.knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
|
|
3024
|
-
|
|
3058
|
+
/* The line (variant: "line"): a player's progress under its media. One 2px line, fg-3 with fg
|
|
3059
|
+
filling it, a segment per chapter; it thickens under the pointer and while scrubbing. The
|
|
3060
|
+
chapter and time under the pointer float over it; nothing else shows. */
|
|
3061
|
+
.knobs-ui.knobs-timeline[data-variant="line"]{height:auto;min-height:0;padding:0;gap:0}
|
|
3062
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-transport{display:none}
|
|
3063
|
+
.knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track{height:16px;overflow:visible;border-radius:var(--_k-radius);background:transparent;box-shadow:none;cursor:pointer}
|
|
3064
|
+
.knobs-timeline[data-variant="line"] :is(.knobs-tl-ruler,.knobs-tl-lane,.knobs-tl-hover,.knobs-tl-head,.knobs-tl-rlabel){display:none}
|
|
3065
|
+
.knobs-tl-segs{position:absolute;left:0;right:0;top:50%;height:2px;margin-top:-1px;pointer-events:none;transition:scale calc(var(--_k-hover-out) * 1ms) var(--_ku-ease)}
|
|
3066
|
+
.knobs-tl-seg{position:absolute;left:0;top:0;height:100%;overflow:hidden;border-radius:1px;background:var(--_k-fg-3);will-change:transform}
|
|
3067
|
+
.knobs-tl-seg-fill{position:absolute;inset:0;background:var(--_k-fg);transform-origin:0 0;will-change:transform}
|
|
3068
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs,.knobs-timeline[data-variant="line"][data-scrubbing] .knobs-tl-segs{scale:1 2}
|
|
3069
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-range{top:3px;bottom:3px;border-radius:3px}
|
|
3070
|
+
.knobs-timeline[data-variant="line"]:focus-visible .knobs-tl-track,.knobs-timeline[data-variant="line"][data-keyboard]:focus .knobs-tl-track{background:transparent;box-shadow:0 0 0 2px color-mix(in srgb,var(--_k-accent) 60%,transparent)}
|
|
3071
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-tag{top:auto;bottom:calc(100% + 2px);display:flex;gap:6px;padding:0 6px;border-radius:calc(var(--_k-radius) * 999);background:color-mix(in srgb,var(--_k-bg) 72%,transparent);
|
|
3072
|
+
-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:inset 0 0 0 .5px var(--_k-border)}
|
|
3073
|
+
.knobs-tl-tag-label{color:var(--_k-fg-2)}
|
|
3074
|
+
.knobs-tl-tag-label:empty{display:none}
|
|
3075
|
+
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
3076
|
+
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
3077
|
+
`, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + playerCss + selectionPillCss) + rolling_css_default, lookDefaults, tune = (s, m) => {
|
|
3025
3078
|
s.omega = m.omega;
|
|
3026
3079
|
s.zeta = m.zeta;
|
|
3027
3080
|
return s;
|
|
@@ -3144,19 +3197,19 @@ var init_diff_css = __esm(() => {
|
|
|
3144
3197
|
@property --kd-fb{syntax:"<length>";inherits:false;initial-value:0px}
|
|
3145
3198
|
:where(.knobs-ui){--kd-add:var(--_k-diff-add);--kd-del:var(--_k-diff-del);--kd-mod:var(--_k-diff-mod);--kd-move:var(--k-diff-move,var(--_k-info))}
|
|
3146
3199
|
:where(.knobs-diff,.knobs-diff *,.knobs-diff-files,.knobs-diff-files *){box-sizing:border-box}
|
|
3147
|
-
.knobs-ui.knobs-diff{--kd-bg:var(--k-diff-bg,var(--_kd-bg,
|
|
3200
|
+
.knobs-ui.knobs-diff{--kd-bg:var(--k-diff-bg,var(--_kd-bg,transparent));--kd-solid:var(--kd-fill,var(--kd-bg));--kd-line:calc(var(--_k-font-size) * 1.5);
|
|
3148
3201
|
--kd-i:var(--k-diff-inset,8px);--kd-x:var(--kd-i);--kd-inner:max(3px,calc(var(--_ku-pane-radius) - var(--kd-i)));--kd-ctl:var(--kd-inner);
|
|
3149
3202
|
--kd-head:calc(var(--_k-control-h) + 2 * var(--kd-i));--kd-fade:var(--k-diff-fade,20px);--kd-stick:0px;position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;
|
|
3150
3203
|
background:var(--kd-bg);border-radius:var(--_ku-pane-radius);font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);text-align:left;
|
|
3151
3204
|
letter-spacing:normal;word-spacing:normal;text-transform:none;-webkit-font-smoothing:antialiased}
|
|
3152
3205
|
.knobs-ui.knobs-diff[data-height="auto"]{height:auto}
|
|
3153
|
-
.knobs-diff::after{content:"";position:absolute;inset:0;z-index:3;
|
|
3206
|
+
.knobs-ui.knobs-diff:not([data-on="pane"])::after{content:"";position:absolute;inset:0;z-index:3;box-shadow:inset 0 -1px 0 var(--_k-border-subtle);pointer-events:none}
|
|
3154
3207
|
.knobs-diff [hidden],.knobs-diff-files [hidden]{display:none!important}
|
|
3155
3208
|
.knobs-diff svg{display:block;flex:none}
|
|
3156
|
-
.knobs-ui.knobs-diff[data-on="
|
|
3157
|
-
/*
|
|
3209
|
+
.knobs-ui.knobs-diff:not([data-on="pane"]){--kd-x:0px;--kd-head:calc(var(--_k-control-h) + var(--kd-i));--kd-ctl:var(--_k-radius)}
|
|
3210
|
+
/* A hairline under the header, firmer once the code has scrolled under it. */
|
|
3158
3211
|
.knobs-diff-head{position:relative;z-index:1;flex:none;display:flex;align-items:center;gap:8px;height:var(--kd-head);padding:0 var(--kd-x);
|
|
3159
|
-
box-shadow:inset 0 -1px
|
|
3212
|
+
box-shadow:inset 0 -1px var(--_k-border-subtle);transition:box-shadow .15s;user-select:none;-webkit-user-select:none}
|
|
3160
3213
|
.knobs-diff[data-scrolled]>.knobs-diff-head{box-shadow:inset 0 -1px var(--_k-border)}
|
|
3161
3214
|
.knobs-diff-path{flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap}
|
|
3162
3215
|
.knobs-diff-dir,.knobs-diff-from{color:var(--_k-fg-3)}
|
|
@@ -3183,17 +3236,26 @@ var init_diff_css = __esm(() => {
|
|
|
3183
3236
|
margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
3184
3237
|
.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;
|
|
3185
3238
|
transition:background .15s,color .15s,opacity .15s}
|
|
3186
|
-
.knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3239
|
+
.knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3187
3240
|
.knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
3188
3241
|
.knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
|
|
3189
3242
|
.knobs-diff-btn svg{width:16px;height:16px}
|
|
3190
3243
|
/* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
|
|
3191
3244
|
.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);
|
|
3192
3245
|
font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
|
|
3193
|
-
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3246
|
+
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3194
3247
|
.knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
3195
3248
|
.knobs-diff-viewed svg{width:16px;height:16px}
|
|
3196
3249
|
.knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
|
|
3250
|
+
/* Narrow, the header gives up words before anything is cut off at its end: Viewed keeps its
|
|
3251
|
+
check, then the counts' bar goes, then the counts; the path fades at its end throughout. */
|
|
3252
|
+
.knobs-diff-head,.knobs-diff-part-head{container-type:inline-size}
|
|
3253
|
+
@container (max-width:520px){
|
|
3254
|
+
.knobs-diff-viewed-label{display:none}
|
|
3255
|
+
.knobs-ui .knobs-diff-viewed{width:var(--_k-control-h);padding:0;justify-content:center}
|
|
3256
|
+
.knobs-diff-counts .knobs-diff-bar{display:none}
|
|
3257
|
+
}
|
|
3258
|
+
@container (max-width:400px){.knobs-diff-counts{display:none}.knobs-diff-nav>span{min-width:2.6em}}
|
|
3197
3259
|
.knobs-diff-path{transition:opacity .2s}
|
|
3198
3260
|
.knobs-diff[data-viewed]>.knobs-diff-head .knobs-diff-path,.knobs-diff-part[data-viewed] .knobs-diff-path{opacity:.6}
|
|
3199
3261
|
/* Split | Unified: the selection pill on the one picked, its bright copy the picked one's text; a
|
|
@@ -3201,7 +3263,7 @@ var init_diff_css = __esm(() => {
|
|
|
3201
3263
|
.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)}
|
|
3202
3264
|
.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);
|
|
3203
3265
|
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
3204
|
-
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
3266
|
+
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3205
3267
|
/* The diff: one layer per file or layout on show, the old fading under the new. */
|
|
3206
3268
|
.knobs-diff-body{position:relative;flex:1;min-height:0}
|
|
3207
3269
|
.knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
|
|
@@ -3222,8 +3284,8 @@ var init_diff_css = __esm(() => {
|
|
|
3222
3284
|
.knobs-diff-hbar{position:absolute;height:5px;pointer-events:none}
|
|
3223
3285
|
.knobs-diff-hbar>i{position:absolute;left:0;top:0;height:5px;border-radius:3px;background:color-mix(in srgb,var(--_k-fg) 30%,transparent);opacity:0;pointer-events:none;
|
|
3224
3286
|
transition:opacity .3s,background .15s}
|
|
3225
|
-
.knobs-diff-body:hover .knobs-diff-hbar>i,.knobs-diff-hbar:is([data-active],[data-dragging])>i{opacity:1;pointer-events:auto;transition-duration
|
|
3226
|
-
.knobs-diff-hbar>i:hover,.knobs-diff-hbar[data-dragging]>i{background:color-mix(in srgb,var(--_k-fg) 48%,transparent)}
|
|
3287
|
+
.knobs-diff-body:hover .knobs-diff-hbar>i,.knobs-diff-hbar:is([data-active],[data-dragging])>i{opacity:1;pointer-events:auto;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3288
|
+
.knobs-diff-hbar>i:hover,.knobs-diff-hbar[data-dragging]>i{background:color-mix(in srgb,var(--_k-fg) 48%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3227
3289
|
/* stack: every file down one scroller, each header sticking until the next pushes it off. It's
|
|
3228
3290
|
flush (square, no card around its file), opaque in the code's own colour, with a hairline
|
|
3229
3291
|
under it only while it's stuck; a gap, not a line, parts one file from the next. */
|
|
@@ -3258,24 +3320,26 @@ var init_diff_css = __esm(() => {
|
|
|
3258
3320
|
--diffs-bg-deletion-number-override:color-mix(in srgb,var(--kd-del) 55%,var(--kd-solid));
|
|
3259
3321
|
--diffs-bg-addition-emphasis-override:color-mix(in srgb,var(--kd-add) 22%,transparent);
|
|
3260
3322
|
--diffs-bg-deletion-emphasis-override:color-mix(in srgb,var(--kd-del) 22%,transparent);
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
--diffs-bg-
|
|
3323
|
+
/* Unchanged lines and the folds between hunks sit on the void; only a split's missing lines
|
|
3324
|
+
are faintly filled. */
|
|
3325
|
+
--diffs-bg-separator-override:var(--kd-solid);
|
|
3326
|
+
--diffs-bg-context-override:var(--kd-solid);
|
|
3327
|
+
--diffs-bg-buffer-override:color-mix(in srgb,var(--_k-fg) 2.5%,var(--kd-solid));
|
|
3264
3328
|
--diffs-bg-selection-override:var(--_k-accent);
|
|
3265
3329
|
--diffs-fg-number-override:var(--_k-fg-3);
|
|
3266
3330
|
}
|
|
3267
|
-
/* diffReview
|
|
3268
|
-
is pinned to its right edge, so it slides out from under the diff), parted
|
|
3269
|
-
|
|
3270
|
-
.knobs-ui.knobs-diff-review{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;
|
|
3331
|
+
/* diffReview, on the void as the diff is: the file list a drawer at its left (its width springs;
|
|
3332
|
+
the list inside is pinned to its right edge, so it slides out from under the diff), parted
|
|
3333
|
+
from the diff by a hairline; one hairline runs under both headers and one under it all. */
|
|
3334
|
+
.knobs-ui.knobs-diff-review{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;
|
|
3271
3335
|
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased}
|
|
3272
|
-
.knobs-diff-review::after{content:"";position:absolute;inset:0;z-index:5;
|
|
3336
|
+
.knobs-diff-review::after{content:"";position:absolute;inset:0;z-index:5;box-shadow:inset 0 -1px 0 var(--_k-border-subtle);pointer-events:none}
|
|
3273
3337
|
:where(.knobs-diff-review,.knobs-diff-review *){box-sizing:border-box}
|
|
3274
|
-
.knobs-diff-drawer{position:relative;flex:none;width:0;height:100%;overflow:hidden;box-shadow:inset -1px 0 var(--_k-border)}
|
|
3338
|
+
.knobs-diff-drawer{position:relative;flex:none;width:0;height:100%;overflow:hidden;box-shadow:inset -1px 0 var(--_k-border-subtle)}
|
|
3275
3339
|
.knobs-diff-drawer[data-shut]{visibility:hidden}
|
|
3276
3340
|
.knobs-diff-drawer-in{position:absolute;top:0;bottom:0;right:0;display:flex;flex-direction:column}
|
|
3277
3341
|
.knobs-diff-drawer-head{flex:none;display:flex;align-items:center;gap:6px;height:calc(var(--_k-control-h) + 2 * var(--k-diff-inset,8px));padding:0 var(--k-diff-inset,8px) 0 calc(var(--_ku-row-x) + 8px);
|
|
3278
|
-
user-select:none;-webkit-user-select:none}
|
|
3342
|
+
box-shadow:inset 0 -1px var(--_k-border-subtle);user-select:none;-webkit-user-select:none}
|
|
3279
3343
|
.knobs-diff-drawer-title{font-weight:var(--_k-weight-title)}
|
|
3280
3344
|
.knobs-diff-drawer-count{color:var(--_k-fg-3);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums}
|
|
3281
3345
|
.knobs-diff-drawer-in>.knobs-diff-files{flex:1;min-height:0;height:auto}
|
|
@@ -3287,15 +3351,16 @@ var init_diff_css = __esm(() => {
|
|
|
3287
3351
|
.knobs-ui .knobs-diff-toggle[aria-pressed]:not(:hover,:focus-visible){background:transparent}
|
|
3288
3352
|
/* diffReview's file list: a filter over a compact tree, a status letter, the counts and their
|
|
3289
3353
|
bar on each file; one highlight glides to the file on show. Viewed files dim, with a tick. */
|
|
3290
|
-
.knobs-diff-files{--k-row:var(--k-diff-row,28px);display:flex;flex-direction:column;height:100%;min-height:0;padding-top:
|
|
3354
|
+
.knobs-diff-files{--k-row:var(--k-diff-row,28px);display:flex;flex-direction:column;height:100%;min-height:0;padding-top:4px}
|
|
3291
3355
|
.knobs-diff-filter{position:relative;flex:none;display:flex;align-items:center;gap:6px;height:26px;margin:0 var(--_ku-row-x) 4px;padding:0 4px 0 8px;
|
|
3292
|
-
border-radius:max(6px,var(--_ku-row-radius));
|
|
3293
|
-
|
|
3356
|
+
border-radius:max(6px,var(--_ku-row-radius));color:var(--_k-fg-3);box-shadow:inset 0 0 0 1px transparent;transition:box-shadow .15s}
|
|
3357
|
+
/* The filter is a line of text, no box; focused, a hairline ring. */
|
|
3358
|
+
.knobs-diff-filter:focus-within{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_k-accent) 45%,transparent)}
|
|
3294
3359
|
.knobs-diff-filter svg{width:12px;height:12px;flex:none}
|
|
3295
3360
|
.knobs-ui .knobs-diff-filter>input{flex:1;min-width:0;height:100%;font:var(--_k-weight) var(--_k-font-size)/1 var(--_k-font);color:var(--_k-fg);caret-color:var(--_k-accent);background:transparent}
|
|
3296
3361
|
.knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
|
|
3297
3362
|
.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}
|
|
3298
|
-
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3363
|
+
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3299
3364
|
.knobs-diff-filter>button svg{width:12px;height:12px}
|
|
3300
3365
|
.knobs-diff-list{position:relative;flex:1;min-height:0}
|
|
3301
3366
|
.knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
|
|
@@ -6215,7 +6280,10 @@ function codeDiff(container, options) {
|
|
|
6215
6280
|
const viewedToggle = () => {
|
|
6216
6281
|
const b = make("button", "knobs-diff-viewed");
|
|
6217
6282
|
b.type = "button";
|
|
6218
|
-
|
|
6283
|
+
const label = make("span", "knobs-diff-viewed-label");
|
|
6284
|
+
label.textContent = "Viewed";
|
|
6285
|
+
b.append(make("span", ""), label);
|
|
6286
|
+
b.setAttribute("aria-label", "Viewed");
|
|
6219
6287
|
return b;
|
|
6220
6288
|
};
|
|
6221
6289
|
const paintViewed = (b, on) => {
|
|
@@ -8502,6 +8570,17 @@ function currentMarker(markers, t, fps) {
|
|
|
8502
8570
|
best = m;
|
|
8503
8571
|
return best;
|
|
8504
8572
|
}
|
|
8573
|
+
function minutes(t) {
|
|
8574
|
+
const s = Math.max(0, Math.floor(t + 0.000001));
|
|
8575
|
+
const h = Math.floor(s / 3600);
|
|
8576
|
+
const m = Math.floor(s % 3600 / 60);
|
|
8577
|
+
const ss = String(s % 60).padStart(2, "0");
|
|
8578
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
8579
|
+
}
|
|
8580
|
+
function chapterBounds(times, duration) {
|
|
8581
|
+
const inside = times.filter((t) => t > 0.000001 && t < duration - 0.000001);
|
|
8582
|
+
return [0, ...[...new Set(inside)].sort((a, b) => a - b), duration];
|
|
8583
|
+
}
|
|
8505
8584
|
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;
|
|
8506
8585
|
var init_timeline_model = __esm(() => {
|
|
8507
8586
|
SECOND_STEPS = [0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600];
|
|
@@ -8519,7 +8598,8 @@ function timeline(container, options) {
|
|
|
8519
8598
|
let speed = options.speed ?? 1;
|
|
8520
8599
|
let repeat = options.repeat ?? true;
|
|
8521
8600
|
const arrows = options.arrows ?? "frames";
|
|
8522
|
-
const
|
|
8601
|
+
const line = options.variant === "line";
|
|
8602
|
+
const lane = line || options.variant === "lane";
|
|
8523
8603
|
let time = Math.min(duration, Math.max(0, options.time ?? 0));
|
|
8524
8604
|
let playing = false;
|
|
8525
8605
|
let markers = [...options.markers ?? []];
|
|
@@ -8554,7 +8634,7 @@ function timeline(container, options) {
|
|
|
8554
8634
|
root.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight , . Home End [ ] I O M");
|
|
8555
8635
|
applyLook2(root, options);
|
|
8556
8636
|
if (lane)
|
|
8557
|
-
root.dataset.variant = "lane";
|
|
8637
|
+
root.dataset.variant = line ? "line" : "lane";
|
|
8558
8638
|
const transport = el("div", "knobs-tl-transport");
|
|
8559
8639
|
const playBtn = el("button", "knobs-tl-play");
|
|
8560
8640
|
playBtn.type = "button";
|
|
@@ -8567,6 +8647,10 @@ function timeline(container, options) {
|
|
|
8567
8647
|
const hover = el("div", "knobs-tl-hover");
|
|
8568
8648
|
const tag = el("span", "knobs-tl-tag");
|
|
8569
8649
|
tag.setAttribute("aria-hidden", "true");
|
|
8650
|
+
const tagLabel = el("span", "knobs-tl-tag-label");
|
|
8651
|
+
const tagTime = el("span", "knobs-tl-tag-time");
|
|
8652
|
+
tag.append(tagLabel, tagTime);
|
|
8653
|
+
const segs = el("div", "knobs-tl-segs");
|
|
8570
8654
|
const head = el("div", "knobs-tl-head");
|
|
8571
8655
|
head.id = `${id}-head`;
|
|
8572
8656
|
head.setAttribute("role", "slider");
|
|
@@ -8574,11 +8658,13 @@ function timeline(container, options) {
|
|
|
8574
8658
|
head.setAttribute("aria-valuemin", "0");
|
|
8575
8659
|
head.setAttribute("aria-valuemax", String(duration));
|
|
8576
8660
|
track.append(canvas, markerLane, hover, head, tag);
|
|
8661
|
+
if (line)
|
|
8662
|
+
track.insertBefore(segs, markerLane);
|
|
8577
8663
|
root.append(transport, track);
|
|
8578
8664
|
container.append(root);
|
|
8579
8665
|
let width = 1;
|
|
8580
8666
|
let height = 1;
|
|
8581
|
-
const fitView = () => clampView({ start: 0, span: duration }, duration, { pad: 0.02 });
|
|
8667
|
+
const fitView = () => clampView({ start: 0, span: duration }, duration, { pad: line ? 0 : 0.02 });
|
|
8582
8668
|
let target = fitView();
|
|
8583
8669
|
const vStart = new Spring(target.start, INSTANT2.omega, 1, 0.00001);
|
|
8584
8670
|
const vSpan = new Spring(target.span, INSTANT2.omega, 1, 0.00001);
|
|
@@ -8587,13 +8673,13 @@ function timeline(container, options) {
|
|
|
8587
8673
|
const xOf = (t) => (t - vStart.x) * pxPerSec();
|
|
8588
8674
|
const tOf = (x) => vStart.x + x / pxPerSec();
|
|
8589
8675
|
const minSpan = () => Math.max(4 / fps, 0.05);
|
|
8590
|
-
const retarget = (next, motion = INSTANT2) => {
|
|
8591
|
-
target = clampView(next, duration, { pad: 0.04, minSpan: minSpan() });
|
|
8676
|
+
const retarget = (next, motion = INSTANT2, hand = false) => {
|
|
8677
|
+
target = clampView(next, duration, { pad: line ? 0 : 0.04, minSpan: minSpan() });
|
|
8592
8678
|
for (const s of [vStart, vSpan]) {
|
|
8593
8679
|
s.omega = motion.omega;
|
|
8594
8680
|
s.zeta = motion.zeta;
|
|
8595
8681
|
}
|
|
8596
|
-
if (reducedMotion(win) && motion === CALM) {
|
|
8682
|
+
if (hand || reducedMotion(win) && motion === CALM) {
|
|
8597
8683
|
vStart.snap(target.start);
|
|
8598
8684
|
vSpan.snap(target.span);
|
|
8599
8685
|
} else {
|
|
@@ -8619,6 +8705,8 @@ function timeline(container, options) {
|
|
|
8619
8705
|
};
|
|
8620
8706
|
let drawn = "";
|
|
8621
8707
|
const drawRuler = () => {
|
|
8708
|
+
if (line)
|
|
8709
|
+
return;
|
|
8622
8710
|
const v = view();
|
|
8623
8711
|
const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${height}:${units}:${fps}`;
|
|
8624
8712
|
if (key === drawn)
|
|
@@ -8706,6 +8794,8 @@ function timeline(container, options) {
|
|
|
8706
8794
|
n.title = `${m.label ? `${m.label} · ` : ""}${clock(m.time)}`;
|
|
8707
8795
|
}
|
|
8708
8796
|
placeMarkers();
|
|
8797
|
+
if (line)
|
|
8798
|
+
syncSegments();
|
|
8709
8799
|
};
|
|
8710
8800
|
const placeMarkers = () => {
|
|
8711
8801
|
for (const m of markers) {
|
|
@@ -8714,6 +8804,36 @@ function timeline(container, options) {
|
|
|
8714
8804
|
n.style.transform = `translateX(${xOf(m.time).toFixed(2)}px)`;
|
|
8715
8805
|
}
|
|
8716
8806
|
};
|
|
8807
|
+
let bounds = [];
|
|
8808
|
+
const segEls = [];
|
|
8809
|
+
const syncSegments = () => {
|
|
8810
|
+
bounds = chapterBounds(markers.map((m) => m.time), duration);
|
|
8811
|
+
while (segEls.length > bounds.length - 1)
|
|
8812
|
+
segEls.pop().el.remove();
|
|
8813
|
+
while (segEls.length < bounds.length - 1) {
|
|
8814
|
+
const n = el("i", "knobs-tl-seg");
|
|
8815
|
+
const fill = el("b", "knobs-tl-seg-fill");
|
|
8816
|
+
n.append(fill);
|
|
8817
|
+
segs.append(n);
|
|
8818
|
+
segEls.push({ el: n, fill });
|
|
8819
|
+
}
|
|
8820
|
+
placeSegments();
|
|
8821
|
+
};
|
|
8822
|
+
const placeSegments = () => {
|
|
8823
|
+
if (!line)
|
|
8824
|
+
return;
|
|
8825
|
+
const last = segEls.length - 1;
|
|
8826
|
+
segEls.forEach((s, i) => {
|
|
8827
|
+
const a = bounds[i];
|
|
8828
|
+
const b = bounds[i + 1];
|
|
8829
|
+
const x0 = xOf(a) + (i > 0 ? SEG_GAP / 2 : 0);
|
|
8830
|
+
const x1 = xOf(b) - (i < last ? SEG_GAP / 2 : 0);
|
|
8831
|
+
s.el.style.transform = `translateX(${x0.toFixed(2)}px)`;
|
|
8832
|
+
s.el.style.width = `${Math.max(0, x1 - x0).toFixed(2)}px`;
|
|
8833
|
+
const k = Math.min(1, Math.max(0, (shownHead - a) / Math.max(0.000000001, b - a)));
|
|
8834
|
+
s.fill.style.transform = `scaleX(${k.toFixed(4)})`;
|
|
8835
|
+
});
|
|
8836
|
+
};
|
|
8717
8837
|
const syncRanges = () => {
|
|
8718
8838
|
const keep = new Set(ranges.map((r) => r.id));
|
|
8719
8839
|
for (const [k, n] of rangeEls)
|
|
@@ -8730,7 +8850,7 @@ function timeline(container, options) {
|
|
|
8730
8850
|
b.dataset.edge = "end";
|
|
8731
8851
|
const label = el("span", "knobs-tl-rlabel");
|
|
8732
8852
|
n.append(label, a, b);
|
|
8733
|
-
track.insertBefore(n, markerLane);
|
|
8853
|
+
track.insertBefore(n, line ? segs : markerLane);
|
|
8734
8854
|
rangeEls.set(r.id, n);
|
|
8735
8855
|
if (built)
|
|
8736
8856
|
enter(n);
|
|
@@ -8782,6 +8902,7 @@ function timeline(container, options) {
|
|
|
8782
8902
|
let currentId = null;
|
|
8783
8903
|
const renderHead = () => {
|
|
8784
8904
|
head.style.transform = `translateX(${xOf(shownHead).toFixed(2)}px)`;
|
|
8905
|
+
placeSegments();
|
|
8785
8906
|
head.setAttribute("aria-valuenow", time.toFixed(3));
|
|
8786
8907
|
head.setAttribute("aria-valuetext", `${time.toFixed(2)} seconds, frame ${toFrame(time, fps)}`);
|
|
8787
8908
|
const cur = currentMarker(markers, time, fps)?.id ?? null;
|
|
@@ -9045,14 +9166,25 @@ function timeline(container, options) {
|
|
|
9045
9166
|
setTime(s.t, "scrub", s.shown, press);
|
|
9046
9167
|
root.toggleAttribute("data-stuck", s.stop !== null);
|
|
9047
9168
|
};
|
|
9169
|
+
let pointerX = null;
|
|
9170
|
+
const zoomAtHand = (factor) => zoom(factor, pointerX === null ? time : target.start + pointerX / width * target.span);
|
|
9048
9171
|
const showTag = (x) => {
|
|
9049
9172
|
const at = Math.min(duration, Math.max(0, tOf(x)));
|
|
9050
|
-
const text = `${at.toFixed(2)} s`;
|
|
9051
|
-
if (
|
|
9052
|
-
|
|
9173
|
+
const text = line ? minutes(at) : `${at.toFixed(2)} s`;
|
|
9174
|
+
if (tagTime.textContent !== text)
|
|
9175
|
+
tagTime.textContent = text;
|
|
9176
|
+
const label = line ? currentMarker(markers, at, fps)?.label ?? "" : "";
|
|
9177
|
+
if (tagLabel.textContent !== label)
|
|
9178
|
+
tagLabel.textContent = label;
|
|
9053
9179
|
const cx = Math.min(width, Math.max(0, x));
|
|
9054
|
-
|
|
9055
|
-
|
|
9180
|
+
if (line) {
|
|
9181
|
+
const w = tag.offsetWidth;
|
|
9182
|
+
const left = Math.min(Math.max(0, width - w), Math.max(0, cx - w / 2));
|
|
9183
|
+
tag.style.transform = `translateX(${left.toFixed(1)}px)`;
|
|
9184
|
+
} else {
|
|
9185
|
+
const flip = cx > width - tag.offsetWidth - 12;
|
|
9186
|
+
tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
|
|
9187
|
+
}
|
|
9056
9188
|
tag.dataset.on = "";
|
|
9057
9189
|
};
|
|
9058
9190
|
const hideTag = () => delete tag.dataset.on;
|
|
@@ -9113,6 +9245,7 @@ function timeline(container, options) {
|
|
|
9113
9245
|
});
|
|
9114
9246
|
track.addEventListener("pointermove", (e) => {
|
|
9115
9247
|
const x = localX(e);
|
|
9248
|
+
pointerX = x;
|
|
9116
9249
|
hover.style.transform = `translateX(${x.toFixed(1)}px)`;
|
|
9117
9250
|
if (!drag || drag.kind === "scrub")
|
|
9118
9251
|
showTag(x);
|
|
@@ -9186,6 +9319,7 @@ function timeline(container, options) {
|
|
|
9186
9319
|
track.addEventListener("pointercancel", endDrag);
|
|
9187
9320
|
track.addEventListener("lostpointercapture", endDrag);
|
|
9188
9321
|
track.addEventListener("pointerleave", () => {
|
|
9322
|
+
pointerX = null;
|
|
9189
9323
|
if (drag)
|
|
9190
9324
|
return;
|
|
9191
9325
|
hover.style.transform = "translateX(-100px)";
|
|
@@ -9193,7 +9327,7 @@ function timeline(container, options) {
|
|
|
9193
9327
|
});
|
|
9194
9328
|
track.addEventListener("dblclick", (e) => {
|
|
9195
9329
|
if (lane)
|
|
9196
|
-
return;
|
|
9330
|
+
return fit();
|
|
9197
9331
|
const markerEl = e.target.closest(".knobs-tl-marker");
|
|
9198
9332
|
if (markerEl)
|
|
9199
9333
|
return rename(markerEl.dataset.id);
|
|
@@ -9204,18 +9338,40 @@ function timeline(container, options) {
|
|
|
9204
9338
|
rename(m.id);
|
|
9205
9339
|
});
|
|
9206
9340
|
track.addEventListener("wheel", (e) => {
|
|
9207
|
-
|
|
9341
|
+
const zooming = e.ctrlKey || e.metaKey;
|
|
9342
|
+
if (zooming && pinching)
|
|
9343
|
+
return e.preventDefault();
|
|
9344
|
+
const sideways = Math.abs(e.deltaX) > Math.abs(e.deltaY);
|
|
9345
|
+
if (lane && !zooming && !sideways)
|
|
9208
9346
|
return;
|
|
9209
9347
|
e.preventDefault();
|
|
9210
9348
|
const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? height : 1;
|
|
9211
|
-
if (
|
|
9349
|
+
if (zooming) {
|
|
9212
9350
|
const at = target.start + localX(e) / width * target.span;
|
|
9213
|
-
|
|
9351
|
+
const k = Math.exp(-e.deltaY * scale * (e.ctrlKey ? 0.01 : 0.002));
|
|
9352
|
+
retarget(zoomView(target, k, at), INSTANT2, lane);
|
|
9214
9353
|
} else {
|
|
9215
|
-
const d = (
|
|
9216
|
-
retarget({ ...target, start: target.start + d / width * target.span });
|
|
9354
|
+
const d = (sideways ? e.deltaX : e.deltaY) * scale;
|
|
9355
|
+
retarget({ ...target, start: target.start + d / width * target.span }, INSTANT2, lane);
|
|
9217
9356
|
}
|
|
9218
9357
|
}, { passive: false });
|
|
9358
|
+
let pinchScale = 1;
|
|
9359
|
+
let pinching = false;
|
|
9360
|
+
const onGesture = (e) => {
|
|
9361
|
+
const g = e;
|
|
9362
|
+
e.preventDefault();
|
|
9363
|
+
if (e.type === "gesturestart")
|
|
9364
|
+
pinchScale = 1;
|
|
9365
|
+
pinching = e.type !== "gestureend";
|
|
9366
|
+
if (e.type !== "gesturechange")
|
|
9367
|
+
return;
|
|
9368
|
+
const s = g.scale ?? 1;
|
|
9369
|
+
const x = g.clientX !== undefined ? localX({ clientX: g.clientX }) : width / 2;
|
|
9370
|
+
retarget(zoomView(target, s / pinchScale, target.start + x / width * target.span), INSTANT2, true);
|
|
9371
|
+
pinchScale = s;
|
|
9372
|
+
};
|
|
9373
|
+
for (const type of ["gesturestart", "gesturechange", "gestureend"])
|
|
9374
|
+
track.addEventListener(type, onGesture);
|
|
9219
9375
|
root.addEventListener("keydown", (e) => {
|
|
9220
9376
|
if (e.target !== root || e.metaKey || e.ctrlKey) {
|
|
9221
9377
|
if ((e.metaKey || e.ctrlKey) && (e.key === "=" || e.key === "-" || e.key === "0") && e.target === root) {
|
|
@@ -9223,7 +9379,7 @@ function timeline(container, options) {
|
|
|
9223
9379
|
if (e.key === "0")
|
|
9224
9380
|
fit();
|
|
9225
9381
|
else
|
|
9226
|
-
|
|
9382
|
+
zoomAtHand(e.key === "=" ? 1.5 : 1 / 1.5);
|
|
9227
9383
|
}
|
|
9228
9384
|
return;
|
|
9229
9385
|
}
|
|
@@ -9305,18 +9461,12 @@ function timeline(container, options) {
|
|
|
9305
9461
|
return;
|
|
9306
9462
|
case "=":
|
|
9307
9463
|
case "+":
|
|
9308
|
-
if (lane)
|
|
9309
|
-
return;
|
|
9310
9464
|
claim();
|
|
9311
|
-
return
|
|
9465
|
+
return zoomAtHand(1.5);
|
|
9312
9466
|
case "-":
|
|
9313
|
-
if (lane)
|
|
9314
|
-
return;
|
|
9315
9467
|
claim();
|
|
9316
|
-
return
|
|
9468
|
+
return zoomAtHand(1 / 1.5);
|
|
9317
9469
|
case "0":
|
|
9318
|
-
if (lane)
|
|
9319
|
-
return;
|
|
9320
9470
|
claim();
|
|
9321
9471
|
return fit();
|
|
9322
9472
|
}
|
|
@@ -9532,7 +9682,7 @@ function timeline(container, options) {
|
|
|
9532
9682
|
};
|
|
9533
9683
|
return api;
|
|
9534
9684
|
}
|
|
9535
|
-
var RULER = 22, INSTANT2, CALM, GLIDE, uid2 = 0, PLAY2 = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5 3.6v8.8c0 .5.5.8.9.5l6.6-4.4c.4-.3.4-.8 0-1L5.9 3.1c-.4-.3-.9 0-.9.5Z" fill="currentColor"/></svg>', PAUSE = '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="4" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/><rect x="9.2" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/></svg>';
|
|
9685
|
+
var RULER = 22, SEG_GAP = 3, INSTANT2, CALM, GLIDE, uid2 = 0, PLAY2 = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M5 3.6v8.8c0 .5.5.8.9.5l6.6-4.4c.4-.3.4-.8 0-1L5.9 3.1c-.4-.3-.9 0-.9.5Z" fill="currentColor"/></svg>', PAUSE = '<svg viewBox="0 0 16 16" aria-hidden="true"><rect x="4" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/><rect x="9.2" y="3.2" width="2.8" height="9.6" rx="1" fill="currentColor"/></svg>';
|
|
9536
9686
|
var init_timeline = __esm(() => {
|
|
9537
9687
|
init_rgba();
|
|
9538
9688
|
init_motion();
|
|
@@ -9604,14 +9754,28 @@ function compareLayout(box, before, after) {
|
|
|
9604
9754
|
const shape = after.w > 0 && after.h > 0 ? after : before;
|
|
9605
9755
|
const pair = letterbox({ w: shape.w * 2, h: shape.h }, box);
|
|
9606
9756
|
const half = pair.w / 2;
|
|
9757
|
+
const fused = { x: pair.x + half / 2, y: pair.y, w: half, h: pair.h };
|
|
9607
9758
|
return {
|
|
9608
9759
|
side: {
|
|
9609
9760
|
before: letterbox(before, { x: pair.x, y: pair.y, w: half, h: pair.h }),
|
|
9610
9761
|
after: letterbox(after, { x: pair.x + half, y: pair.y, w: half, h: pair.h }),
|
|
9611
9762
|
split: pair.x + half
|
|
9612
9763
|
},
|
|
9613
|
-
overlay: { before: letterbox(before,
|
|
9614
|
-
};
|
|
9764
|
+
overlay: { before: letterbox(before, fused), after: letterbox(after, fused) }
|
|
9765
|
+
};
|
|
9766
|
+
}
|
|
9767
|
+
function fuseFrame(L, before, after, k, wipe) {
|
|
9768
|
+
const t = Math.min(1, Math.max(0, k));
|
|
9769
|
+
const o = L.overlay;
|
|
9770
|
+
const left = Math.min(o.before.x, o.after.x);
|
|
9771
|
+
const line = wipeX({ x: left, y: 0, w: Math.max(o.before.x + o.before.w, o.after.x + o.after.w) - left, h: 0 }, wipe);
|
|
9772
|
+
const cut = L.side.split + (line - L.side.split) * t;
|
|
9773
|
+
const contact = Math.max(0, Math.min(cut - after.x, before.x + before.w - cut));
|
|
9774
|
+
const feather = Math.min(contact, 1 + FUSE.feather * Math.sin(Math.PI * t));
|
|
9775
|
+
const overlap = before.x + before.w - after.x;
|
|
9776
|
+
const seam = t > 0.001 && overlap >= 0 ? Math.min(1, Math.max(0, overlap / FUSE.seamFade)) * Math.min(1, t * 4) : 0;
|
|
9777
|
+
const blur = t > 0.005 && t < 0.995 ? FUSE.blur * Math.sin(Math.PI * t) : 0;
|
|
9778
|
+
return { cut, feather, seam, blur };
|
|
9615
9779
|
}
|
|
9616
9780
|
function clampZoom(r) {
|
|
9617
9781
|
const w = Math.min(1, Math.max(1 / ZOOM_MAX, r.w));
|
|
@@ -9698,7 +9862,18 @@ function stripPeaks(diffs, o = {}) {
|
|
|
9698
9862
|
});
|
|
9699
9863
|
return out;
|
|
9700
9864
|
}
|
|
9701
|
-
var ALL_MODES, MODE_KEYS, modeKey = (mode) => MODE_KEYS[mode].toUpperCase(), VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE,
|
|
9865
|
+
var ALL_MODES, MODE_KEYS, modeKey = (mode) => MODE_KEYS[mode].toUpperCase(), VIDEO, frameOf = (t, fps) => Math.max(0, Math.floor(t * fps + 0.0001)), lastFrame = (duration, fps) => Math.max(0, Math.ceil(duration * fps - 0.0001) - 1), sameFrame = (a, b, fps) => frameOf(a, fps) === frameOf(b, fps), SEEK_IDLE, FUSE, fuseCorners = (r, k) => {
|
|
9866
|
+
const inner = r * Math.min(1, Math.max(0, k));
|
|
9867
|
+
return { before: { left: r, right: inner }, after: { left: inner, right: r } };
|
|
9868
|
+
}, labelFold = (fold, mark, blur = 3) => {
|
|
9869
|
+
const f = Math.min(1, Math.max(0, fold));
|
|
9870
|
+
const open = 1 - f;
|
|
9871
|
+
return {
|
|
9872
|
+
width: mark * open,
|
|
9873
|
+
opacity: Math.min(1, Math.max(0, (open - 0.2) / 0.8)),
|
|
9874
|
+
blur: blur * Math.min(1, Math.max(0, (f - 0.25) / 0.75))
|
|
9875
|
+
};
|
|
9876
|
+
}, 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({
|
|
9702
9877
|
u: r.u - dx / Math.max(1, base.w) * r.w,
|
|
9703
9878
|
v: r.v - dy / Math.max(1, base.h) * r.h,
|
|
9704
9879
|
w: r.w
|
|
@@ -9721,6 +9896,7 @@ var init_compare_model = __esm(() => {
|
|
|
9721
9896
|
MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
|
|
9722
9897
|
VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
|
|
9723
9898
|
SEEK_IDLE = { seeking: false, pending: null };
|
|
9899
|
+
FUSE = { feather: 32, blur: 8, seamFade: 48 };
|
|
9724
9900
|
ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
|
|
9725
9901
|
});
|
|
9726
9902
|
|
|
@@ -10453,8 +10629,8 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
10453
10629
|
onLost();
|
|
10454
10630
|
};
|
|
10455
10631
|
lostListeners.add(lost);
|
|
10456
|
-
const uniforms = device.createBuffer({ size:
|
|
10457
|
-
const data = new Float32Array(
|
|
10632
|
+
const uniforms = device.createBuffer({ size: 192, usage: 64 | 8 });
|
|
10633
|
+
const data = new Float32Array(48);
|
|
10458
10634
|
const make = (w, h) => device.createTexture({
|
|
10459
10635
|
size: [Math.max(1, w), Math.max(1, h)],
|
|
10460
10636
|
format: "rgba8unorm",
|
|
@@ -10527,6 +10703,7 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
10527
10703
|
data.set([f.preview[0] ? 1 : 0, f.preview[1] ? 1 : 0, 0, 0], 32);
|
|
10528
10704
|
r(36, f.beforeClip);
|
|
10529
10705
|
r(40, f.afterClip);
|
|
10706
|
+
data.set(f.radii, 44);
|
|
10530
10707
|
device.queue.writeBuffer(uniforms, 0, data);
|
|
10531
10708
|
const encoder = device.createCommandEncoder();
|
|
10532
10709
|
const pass = encoder.beginRenderPass({
|
|
@@ -10568,6 +10745,7 @@ struct U {
|
|
|
10568
10745
|
loupe: vec4f, // x, y, r, zoom (0: none)
|
|
10569
10746
|
src: vec4f, // Before, After from their previews
|
|
10570
10747
|
ca: vec4f, cb: vec4f, // where each side may draw
|
|
10748
|
+
radii: vec4f, // the clips' corners: Before left, right; After left, right
|
|
10571
10749
|
}
|
|
10572
10750
|
@group(0) @binding(0) var<uniform> u: U;
|
|
10573
10751
|
@group(0) @binding(1) var A: texture_2d<f32>;
|
|
@@ -10589,8 +10767,18 @@ fn cover(p: vec2f, r: vec4f) -> f32 {
|
|
|
10589
10767
|
return clamp(d + 0.5, 0.0, 1.0);
|
|
10590
10768
|
}
|
|
10591
10769
|
|
|
10592
|
-
//
|
|
10593
|
-
fn
|
|
10770
|
+
// Coverage of rect r at p with its left corners rounded rl and its right ones rr.
|
|
10771
|
+
fn coverRound(p: vec2f, r: vec4f, rl: f32, rr: f32) -> f32 {
|
|
10772
|
+
let h = r.zw * 0.5;
|
|
10773
|
+
let q0 = p - (r.xy + h);
|
|
10774
|
+
let rad = min(select(rl, rr, q0.x > 0.0), min(h.x, h.y));
|
|
10775
|
+
let q = abs(q0) - h + vec2f(rad);
|
|
10776
|
+
let d = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rad;
|
|
10777
|
+
return clamp(0.5 - d, 0.0, 1.0);
|
|
10778
|
+
}
|
|
10779
|
+
|
|
10780
|
+
// A side's colour where rect r maps it to p, and its coverage inside clip c (corners rc) there.
|
|
10781
|
+
fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, rc: vec2f, near: bool, ready: f32) -> vec4f {
|
|
10594
10782
|
let uv = clamp((p - r.xy) / max(r.zw, vec2f(1e-3)), vec2f(0.0), vec2f(1.0));
|
|
10595
10783
|
var c: vec3f;
|
|
10596
10784
|
if (near) {
|
|
@@ -10599,27 +10787,22 @@ fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, near: bool, ready: f3
|
|
|
10599
10787
|
} else {
|
|
10600
10788
|
c = textureSampleLevel(t, smp, uv, 0.0).rgb;
|
|
10601
10789
|
}
|
|
10602
|
-
return vec4f(c, min(cover(p, r),
|
|
10790
|
+
return vec4f(c, min(cover(p, r), coverRound(p, c0, rc.x, rc.y)) * ready);
|
|
10603
10791
|
}
|
|
10604
10792
|
|
|
10605
10793
|
fn takeA(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
|
|
10606
|
-
if (u.src.x > 0.5) { return take(PA, p, r, c0, near, 1.0); }
|
|
10607
|
-
return take(A, p, r, c0, near, u.diff.z);
|
|
10794
|
+
if (u.src.x > 0.5) { return take(PA, p, r, c0, u.radii.xy, near, 1.0); }
|
|
10795
|
+
return take(A, p, r, c0, u.radii.xy, near, u.diff.z);
|
|
10608
10796
|
}
|
|
10609
10797
|
fn takeB(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
|
|
10610
|
-
if (u.src.y > 0.5) { return take(PB, p, r, c0, near, 1.0); }
|
|
10611
|
-
return take(B, p, r, c0, near, u.diff.w);
|
|
10798
|
+
if (u.src.y > 0.5) { return take(PB, p, r, c0, u.radii.zw, near, 1.0); }
|
|
10799
|
+
return take(B, p, r, c0, u.radii.zw, near, u.diff.w);
|
|
10612
10800
|
}
|
|
10613
10801
|
|
|
10614
10802
|
fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
|
|
10615
10803
|
|
|
10616
10804
|
fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
|
|
10617
10805
|
switch mode {
|
|
10618
|
-
case 1u: {
|
|
10619
|
-
let s = max(u.wipe.y, 0.5);
|
|
10620
|
-
let m = smoothstep(u.wipe.x - s, u.wipe.x + s, p.x);
|
|
10621
|
-
return pm(vec4f(mix(a.rgb, b.rgb, m), mix(a.a, b.a, m)));
|
|
10622
|
-
}
|
|
10623
10806
|
case 2u: {
|
|
10624
10807
|
let d = abs(a.rgb - b.rgb);
|
|
10625
10808
|
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);
|
|
@@ -10628,8 +10811,10 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
|
|
|
10628
10811
|
}
|
|
10629
10812
|
default: {}
|
|
10630
10813
|
}
|
|
10631
|
-
// Side by side
|
|
10632
|
-
|
|
10814
|
+
// Side by side, fusing and wiping: Before over After, cut at the seam with its feather.
|
|
10815
|
+
let s = max(u.wipe.y, 0.5);
|
|
10816
|
+
let ka = a.a * (1.0 - smoothstep(u.wipe.x - s, u.wipe.x + s, p.x));
|
|
10817
|
+
return vec4f(a.rgb * ka + b.rgb * b.a * (1.0 - ka), ka + b.a * (1.0 - ka));
|
|
10633
10818
|
}
|
|
10634
10819
|
|
|
10635
10820
|
@fragment fn fs(@builtin(position) q: vec4f) -> @location(0) vec4f {
|
|
@@ -11031,6 +11216,29 @@ var init_compare_strip = __esm(() => {
|
|
|
11031
11216
|
DIFF = { w: 32, h: 18 };
|
|
11032
11217
|
});
|
|
11033
11218
|
|
|
11219
|
+
// src/ui/player.ts
|
|
11220
|
+
function playGlyph(doc) {
|
|
11221
|
+
const element = doc.createElement("span");
|
|
11222
|
+
element.className = "knobs-player-glyph";
|
|
11223
|
+
element.setAttribute("aria-hidden", "true");
|
|
11224
|
+
let timer = 0;
|
|
11225
|
+
const win = doc.defaultView;
|
|
11226
|
+
return {
|
|
11227
|
+
element,
|
|
11228
|
+
flash(playing) {
|
|
11229
|
+
element.innerHTML = playing ? PLAY2 : PAUSE;
|
|
11230
|
+
element.setAttribute("data-flash", "");
|
|
11231
|
+
win.clearTimeout(timer);
|
|
11232
|
+
timer = win.setTimeout(() => element.removeAttribute("data-flash"), FLASH_MS);
|
|
11233
|
+
},
|
|
11234
|
+
dispose: () => win.clearTimeout(timer)
|
|
11235
|
+
};
|
|
11236
|
+
}
|
|
11237
|
+
var FLASH_MS = 450;
|
|
11238
|
+
var init_player = __esm(() => {
|
|
11239
|
+
init_timeline();
|
|
11240
|
+
});
|
|
11241
|
+
|
|
11034
11242
|
// src/ui/compare.ts
|
|
11035
11243
|
function compare(container, options) {
|
|
11036
11244
|
const doc = container.ownerDocument;
|
|
@@ -11138,6 +11346,8 @@ function compare(container, options) {
|
|
|
11138
11346
|
if (name.textContent !== text)
|
|
11139
11347
|
name.textContent = text;
|
|
11140
11348
|
};
|
|
11349
|
+
const seam = el("div", "knobs-cmp-seam");
|
|
11350
|
+
seam.setAttribute("aria-hidden", "true");
|
|
11141
11351
|
const handle = el("div", "knobs-cmp-handle");
|
|
11142
11352
|
handle.setAttribute("role", "slider");
|
|
11143
11353
|
handle.setAttribute("aria-label", "Wipe");
|
|
@@ -11147,7 +11357,8 @@ function compare(container, options) {
|
|
|
11147
11357
|
grip.setAttribute("aria-hidden", "true");
|
|
11148
11358
|
handle.append(grip);
|
|
11149
11359
|
const note = el("div", "knobs-cmp-note");
|
|
11150
|
-
|
|
11360
|
+
const glyph = playGlyph(doc);
|
|
11361
|
+
stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note, glyph.element);
|
|
11151
11362
|
root.append(bar, stage);
|
|
11152
11363
|
const wantsStrip = options.strip === true;
|
|
11153
11364
|
let tl = bound;
|
|
@@ -11162,7 +11373,7 @@ function compare(container, options) {
|
|
|
11162
11373
|
root.append(host);
|
|
11163
11374
|
tl = timeline(host, {
|
|
11164
11375
|
...look,
|
|
11165
|
-
variant: "
|
|
11376
|
+
variant: "line",
|
|
11166
11377
|
duration: 1,
|
|
11167
11378
|
fps: fps(),
|
|
11168
11379
|
arrows: { step: 0.5, shift: 2 },
|
|
@@ -11183,12 +11394,13 @@ function compare(container, options) {
|
|
|
11183
11394
|
let toMode = mode;
|
|
11184
11395
|
const wipeLine = new Spring(wipe, WIPE_CATCH.omega, WIPE_CATCH.zeta, 0.0001);
|
|
11185
11396
|
let wipeCatching = false;
|
|
11186
|
-
|
|
11397
|
+
const gripAt = new Spring(0.5, LAYOUT2.omega, LAYOUT2.zeta, 0.0001);
|
|
11187
11398
|
let loupeHeld = false;
|
|
11188
11399
|
let pointer = null;
|
|
11189
11400
|
let drag = null;
|
|
11190
11401
|
const touches = new Map;
|
|
11191
11402
|
let pinch = null;
|
|
11403
|
+
let press = null;
|
|
11192
11404
|
const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
11193
11405
|
const zv = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
11194
11406
|
const zw = new Spring(1, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
@@ -11200,6 +11412,7 @@ function compare(container, options) {
|
|
|
11200
11412
|
let stageRect = null;
|
|
11201
11413
|
let accent = [0.49, 0.81, 0.61];
|
|
11202
11414
|
let labelW = [0, 0];
|
|
11415
|
+
let frameRadius = 0;
|
|
11203
11416
|
let dirty = true;
|
|
11204
11417
|
let disposed = false;
|
|
11205
11418
|
let near = options.lazy === false;
|
|
@@ -11475,6 +11688,8 @@ function compare(container, options) {
|
|
|
11475
11688
|
for (const s of sides) {
|
|
11476
11689
|
const st = s.el.style;
|
|
11477
11690
|
st.clipPath = "";
|
|
11691
|
+
st.maskImage = "";
|
|
11692
|
+
st.setProperty("-webkit-mask-image", "");
|
|
11478
11693
|
st.mixBlendMode = "";
|
|
11479
11694
|
st.opacity = "";
|
|
11480
11695
|
s.shown = null;
|
|
@@ -11701,43 +11916,66 @@ function compare(container, options) {
|
|
|
11701
11916
|
viewSent = key;
|
|
11702
11917
|
options.onView({ zoom: clampZoom(z), loop: l });
|
|
11703
11918
|
};
|
|
11704
|
-
const
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
return [at.before, at.after];
|
|
11708
|
-
}
|
|
11709
|
-
const ax = r.after.x + INSET + (r.after.w - labelW[1] - INSET * 2) * lay.x;
|
|
11710
|
-
return [
|
|
11711
|
-
{ x: r.before.x + INSET, opacity: 1 },
|
|
11712
|
-
{ x: ax, opacity: 1 }
|
|
11713
|
-
];
|
|
11919
|
+
const differenceWeight = () => {
|
|
11920
|
+
const f = Math.min(1, Math.max(0, fade.x));
|
|
11921
|
+
return (fromMode === "difference" ? 1 - f : 0) + (toMode === "difference" ? f : 0);
|
|
11714
11922
|
};
|
|
11715
11923
|
const placeGrip = (wr) => {
|
|
11716
|
-
const gy = gripTop(
|
|
11924
|
+
const gy = gripTop(wr.y + gripAt.x * wr.h, wr, GRIP.h) - wr.y;
|
|
11717
11925
|
grip.style.transform = `translate3d(0,${gy.toFixed(2)}px,0)`;
|
|
11718
11926
|
};
|
|
11927
|
+
let folded = 0;
|
|
11719
11928
|
const draw = () => {
|
|
11720
11929
|
dirty = false;
|
|
11930
|
+
if (!root.hasAttribute("data-ready") && sides.every((x) => x.shown || x.failed))
|
|
11931
|
+
root.setAttribute("data-ready", "");
|
|
11721
11932
|
const r = layoutNow();
|
|
11722
11933
|
const wr = wipeRect(r.L.overlay);
|
|
11723
|
-
const
|
|
11724
|
-
const
|
|
11725
|
-
const
|
|
11726
|
-
const
|
|
11727
|
-
const
|
|
11934
|
+
const k = Math.min(1, Math.max(0, lay.x));
|
|
11935
|
+
const fu = fuseFrame(r.L, r.before, r.after, k, wipeLine.x);
|
|
11936
|
+
const corners = fuseCorners(frameRadius, k);
|
|
11937
|
+
const dw = differenceWeight();
|
|
11938
|
+
const fusing = 1 - dw;
|
|
11939
|
+
folded = k * fusing;
|
|
11940
|
+
const fold = labelFold(folded, MARK.w);
|
|
11941
|
+
const w = labelW.map((x) => x - (MARK.w - fold.width) - MARK.gap * folded);
|
|
11942
|
+
const ride = seamLabels(fu.cut, r.before.x, r.after.x + r.after.w, w[0], w[1], INSET);
|
|
11943
|
+
const apart = [r.before.x + INSET, r.after.x + INSET];
|
|
11944
|
+
const corner = [r.before.x + INSET, r.after.x + r.after.w - labelW[1] - INSET];
|
|
11945
|
+
const rides = [ride.before, ride.after];
|
|
11728
11946
|
const ys = [r.before.y + INSET, r.after.y + INSET];
|
|
11729
11947
|
for (const i of [0, 1]) {
|
|
11730
|
-
const
|
|
11731
|
-
|
|
11732
|
-
|
|
11948
|
+
const fused = apart[i] + (rides[i].x - apart[i]) * k;
|
|
11949
|
+
const x = fused + (corner[i] - fused) * dw;
|
|
11950
|
+
const opacity = (1 - k + k * rides[i].opacity) * fusing + dw;
|
|
11951
|
+
const l = labels[i];
|
|
11952
|
+
l.style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
|
|
11953
|
+
l.style.opacity = opacity.toFixed(3);
|
|
11954
|
+
const mark = l.firstElementChild;
|
|
11955
|
+
if (mark) {
|
|
11956
|
+
mark.style.width = `${fold.width.toFixed(2)}px`;
|
|
11957
|
+
mark.style.marginRight = `${(-MARK.gap * folded).toFixed(2)}px`;
|
|
11958
|
+
mark.style.opacity = fold.opacity.toFixed(3);
|
|
11959
|
+
mark.style.filter = fold.blur > 0.05 ? `blur(${fold.blur.toFixed(2)}px)` : "";
|
|
11960
|
+
}
|
|
11733
11961
|
}
|
|
11734
11962
|
const top = Math.min(r.L.side.before.y, r.L.side.after.y);
|
|
11735
11963
|
split.style.transform = `translate(${r.L.side.split.toFixed(2)}px,${top.toFixed(2)}px)`;
|
|
11736
11964
|
split.style.height = `${Math.max(0, r.L.side.after.h).toFixed(2)}px`;
|
|
11737
11965
|
split.style.opacity = String(Math.max(0, 1 - k * 2));
|
|
11966
|
+
const seamOn = fu.seam * fusing;
|
|
11967
|
+
const blur = fu.blur > 0.01 ? `blur(${fu.blur.toFixed(2)}px)` : "none";
|
|
11968
|
+
seam.style.transform = `translate(${fu.cut.toFixed(2)}px,${wr.y.toFixed(2)}px) translateX(-50%)`;
|
|
11969
|
+
seam.style.width = `${(8 + 2 * fu.feather).toFixed(2)}px`;
|
|
11970
|
+
seam.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
|
|
11971
|
+
seam.style.opacity = (blur === "none" ? 0 : seamOn).toFixed(3);
|
|
11972
|
+
seam.style.backdropFilter = blur;
|
|
11973
|
+
seam.style.setProperty("-webkit-backdrop-filter", blur);
|
|
11738
11974
|
handle.toggleAttribute("data-on", mode === "wipe");
|
|
11739
|
-
handle.style.transform = `translate(${
|
|
11975
|
+
handle.style.transform = `translate(${fu.cut.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
|
|
11740
11976
|
handle.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
|
|
11977
|
+
handle.style.opacity = seamOn.toFixed(3);
|
|
11978
|
+
handle.style.filter = blur;
|
|
11741
11979
|
placeGrip(wr);
|
|
11742
11980
|
handle.setAttribute("aria-valuenow", String(Math.round(wipe * 100)));
|
|
11743
11981
|
handle.setAttribute("aria-valuetext", `${Math.round(wipe * 100)}% Before`);
|
|
@@ -11750,24 +11988,30 @@ function compare(container, options) {
|
|
|
11750
11988
|
if (pending)
|
|
11751
11989
|
return;
|
|
11752
11990
|
if (renderer === "dom") {
|
|
11753
|
-
const
|
|
11991
|
+
const cut = mode === "difference" ? null : fu;
|
|
11992
|
+
const place = (n, rect, clip, before) => {
|
|
11993
|
+
const round = before ? corners.before : corners.after;
|
|
11754
11994
|
const st = n.style;
|
|
11755
11995
|
st.transform = `translate(${rect.x.toFixed(2)}px,${rect.y.toFixed(2)}px)`;
|
|
11756
11996
|
st.width = `${rect.w.toFixed(2)}px`;
|
|
11757
11997
|
st.height = `${rect.h.toFixed(2)}px`;
|
|
11998
|
+
const edge = before && cut ? cut.cut + cut.feather : Number.POSITIVE_INFINITY;
|
|
11758
11999
|
const t = Math.max(0, clip.y - rect.y);
|
|
11759
12000
|
const l = Math.max(0, clip.x - rect.x);
|
|
11760
12001
|
const b = Math.max(0, rect.y + rect.h - (clip.y + clip.h));
|
|
11761
|
-
const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w,
|
|
11762
|
-
|
|
12002
|
+
const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, edge));
|
|
12003
|
+
const rr = edge < clip.x + clip.w - 0.5 ? 0 : round.right;
|
|
12004
|
+
const rl = round.left;
|
|
12005
|
+
st.clipPath = t + l + b + right + rl + rr > 0.01 ? `inset(${t.toFixed(2)}px ${right.toFixed(2)}px ${b.toFixed(2)}px ${l.toFixed(2)}px round ${rl.toFixed(2)}px ${rr.toFixed(2)}px ${rr.toFixed(2)}px ${rl.toFixed(2)}px)` : "";
|
|
12006
|
+
const mask = before && cut && cut.feather > 0.5 ? `linear-gradient(to right,#000 ${(cut.cut - cut.feather - rect.x).toFixed(2)}px,transparent ${(edge - rect.x).toFixed(2)}px)` : "";
|
|
12007
|
+
st.maskImage = mask;
|
|
12008
|
+
st.setProperty("-webkit-mask-image", mask);
|
|
11763
12009
|
};
|
|
11764
|
-
const cut = mode === "wipe" ? wx : Number.POSITIVE_INFINITY;
|
|
11765
12010
|
for (const s of sides) {
|
|
11766
12011
|
const rect = s.index === 0 ? r.zBefore : r.zAfter;
|
|
11767
12012
|
const clip = s.index === 0 ? r.before : r.after;
|
|
11768
|
-
|
|
11769
|
-
place(s.
|
|
11770
|
-
place(s.preview, rect, clip, at);
|
|
12013
|
+
place(s.el, rect, clip, s.index === 0);
|
|
12014
|
+
place(s.preview, rect, clip, s.index === 0);
|
|
11771
12015
|
if (s.index !== 0)
|
|
11772
12016
|
continue;
|
|
11773
12017
|
for (const n of [s.el, s.preview])
|
|
@@ -11787,8 +12031,8 @@ function compare(container, options) {
|
|
|
11787
12031
|
width: Math.max(1, Math.round(stageW * dpr)),
|
|
11788
12032
|
height: Math.max(1, Math.round(stageH * dpr)),
|
|
11789
12033
|
dpr,
|
|
11790
|
-
wipeX:
|
|
11791
|
-
soft: 0.75 * dpr,
|
|
12034
|
+
wipeX: fu.cut * dpr,
|
|
12035
|
+
soft: Math.max(0.75, fu.feather) * dpr,
|
|
11792
12036
|
from: MODE_INDEX[fromMode],
|
|
11793
12037
|
to: MODE_INDEX[toMode],
|
|
11794
12038
|
mix: Math.min(1, Math.max(0, fade.x)),
|
|
@@ -11796,7 +12040,13 @@ function compare(container, options) {
|
|
|
11796
12040
|
gain: 1,
|
|
11797
12041
|
accent,
|
|
11798
12042
|
loupe: loupeShown() && pointer ? { x: pointer.x * dpr, y: pointer.y * dpr, r: LOUPE.r * dpr, zoom: LOUPE.zoom } : null,
|
|
11799
|
-
preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)]
|
|
12043
|
+
preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)],
|
|
12044
|
+
radii: [
|
|
12045
|
+
corners.before.left * dpr,
|
|
12046
|
+
corners.before.right * dpr,
|
|
12047
|
+
corners.after.left * dpr,
|
|
12048
|
+
corners.after.right * dpr
|
|
12049
|
+
]
|
|
11800
12050
|
});
|
|
11801
12051
|
};
|
|
11802
12052
|
const drawNow = () => {
|
|
@@ -11815,6 +12065,7 @@ function compare(container, options) {
|
|
|
11815
12065
|
zu.step(dt);
|
|
11816
12066
|
zv.step(dt);
|
|
11817
12067
|
zw.step(dt);
|
|
12068
|
+
gripAt.step(dt);
|
|
11818
12069
|
const playing = isPlaying();
|
|
11819
12070
|
if (playing) {
|
|
11820
12071
|
const m = master();
|
|
@@ -11845,11 +12096,9 @@ function compare(container, options) {
|
|
|
11845
12096
|
pushFrame(s);
|
|
11846
12097
|
}
|
|
11847
12098
|
}
|
|
11848
|
-
const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching;
|
|
12099
|
+
const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching || !gripAt.resting;
|
|
11849
12100
|
if (moving)
|
|
11850
12101
|
dirty = true;
|
|
11851
|
-
if (auto && !lay.resting)
|
|
11852
|
-
fitHeight();
|
|
11853
12102
|
if (dirty)
|
|
11854
12103
|
draw();
|
|
11855
12104
|
return playing || moving;
|
|
@@ -11875,13 +12124,7 @@ function compare(container, options) {
|
|
|
11875
12124
|
zw.set(c.w);
|
|
11876
12125
|
requestDraw();
|
|
11877
12126
|
};
|
|
11878
|
-
const autoHeight = (w) =>
|
|
11879
|
-
const shape = sizeOf(sides[1]);
|
|
11880
|
-
const side = splitHeight(w, shape);
|
|
11881
|
-
const over = w * shape.h / shape.w;
|
|
11882
|
-
const h = side + (over - side) * lay.x;
|
|
11883
|
-
return Math.max(60, Math.min(win.innerHeight * 0.8, h));
|
|
11884
|
-
};
|
|
12127
|
+
const autoHeight = (w) => Math.max(60, Math.min(win.innerHeight * 0.8, splitHeight(w, sizeOf(sides[1]))));
|
|
11885
12128
|
const fitHeight = () => {
|
|
11886
12129
|
const h = autoHeight(stageW);
|
|
11887
12130
|
if (Math.abs(h - stageH) < 0.25)
|
|
@@ -11893,7 +12136,9 @@ function compare(container, options) {
|
|
|
11893
12136
|
const readColors = () => {
|
|
11894
12137
|
const c = tokenColor(win.getComputedStyle(root), "--_ku-cmp-diff", [0.49, 0.81, 0.61, 1]);
|
|
11895
12138
|
accent = [c[0], c[1], c[2]];
|
|
11896
|
-
|
|
12139
|
+
const unfold = MARK.w - labelFold(folded, MARK.w).width + MARK.gap * folded;
|
|
12140
|
+
labelW = [labels[0].offsetWidth + unfold, labels[1].offsetWidth + unfold];
|
|
12141
|
+
frameRadius = Number.parseFloat(win.getComputedStyle(stage).borderTopLeftRadius) || 0;
|
|
11897
12142
|
requestDraw();
|
|
11898
12143
|
};
|
|
11899
12144
|
const measure = () => {
|
|
@@ -11984,8 +12229,10 @@ function compare(container, options) {
|
|
|
11984
12229
|
root.focus({ preventScroll: true });
|
|
11985
12230
|
touches.set(e.pointerId, p);
|
|
11986
12231
|
stage.setPointerCapture(e.pointerId);
|
|
12232
|
+
press = touches.size === 1 && mode !== "wipe" && master() ? p : null;
|
|
11987
12233
|
if (touches.size >= 2) {
|
|
11988
12234
|
e.preventDefault();
|
|
12235
|
+
press = null;
|
|
11989
12236
|
return startPinch();
|
|
11990
12237
|
}
|
|
11991
12238
|
if (mode === "wipe") {
|
|
@@ -11995,7 +12242,7 @@ function compare(container, options) {
|
|
|
11995
12242
|
if (inside && (grab.onLine || !zoomed())) {
|
|
11996
12243
|
drag = { kind: "wipe", offset: grab.offset };
|
|
11997
12244
|
root.dataset.dragging = "";
|
|
11998
|
-
|
|
12245
|
+
gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
|
|
11999
12246
|
e.preventDefault();
|
|
12000
12247
|
if (!grab.onLine)
|
|
12001
12248
|
setWipe(wipeAt(p.x, wr), true, true);
|
|
@@ -12013,6 +12260,8 @@ function compare(container, options) {
|
|
|
12013
12260
|
const onMove = (e) => {
|
|
12014
12261
|
const p = local(e);
|
|
12015
12262
|
pointer = p;
|
|
12263
|
+
if (press && Math.hypot(p.x - press.x, p.y - press.y) > 4)
|
|
12264
|
+
press = null;
|
|
12016
12265
|
if (touches.has(e.pointerId))
|
|
12017
12266
|
touches.set(e.pointerId, p);
|
|
12018
12267
|
if (pinch && touches.size >= 2) {
|
|
@@ -12026,14 +12275,12 @@ function compare(container, options) {
|
|
|
12026
12275
|
pinch = { d, x: mid.x, y: mid.y };
|
|
12027
12276
|
return handZoom(zoomAbout(panned, k, q.pu, q.pv));
|
|
12028
12277
|
}
|
|
12029
|
-
if (
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
if (drag?.kind === "
|
|
12035
|
-
setWipe(wipeAt(p.x, wipeRect(layoutNow().L.overlay), drag.offset));
|
|
12036
|
-
else if (drag?.kind === "pan") {
|
|
12278
|
+
if (drag?.kind === "wipe") {
|
|
12279
|
+
const wr = wipeRect(layoutNow().L.overlay);
|
|
12280
|
+
gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
|
|
12281
|
+
setWipe(wipeAt(p.x, wr, drag.offset));
|
|
12282
|
+
placeGrip(wr);
|
|
12283
|
+
} else if (drag?.kind === "pan") {
|
|
12037
12284
|
const dx = p.x - drag.x;
|
|
12038
12285
|
const dy = p.y - drag.y;
|
|
12039
12286
|
drag.x = p.x;
|
|
@@ -12044,11 +12291,23 @@ function compare(container, options) {
|
|
|
12044
12291
|
drawNow();
|
|
12045
12292
|
};
|
|
12046
12293
|
const endDrag = (e) => {
|
|
12294
|
+
if (press && e.type === "pointerup") {
|
|
12295
|
+
api.toggle();
|
|
12296
|
+
glyph.flash(isPlaying());
|
|
12297
|
+
}
|
|
12298
|
+
press = null;
|
|
12047
12299
|
touches.delete(e.pointerId);
|
|
12048
12300
|
if (touches.size < 2)
|
|
12049
12301
|
pinch = null;
|
|
12050
12302
|
if (!drag)
|
|
12051
12303
|
return;
|
|
12304
|
+
if (drag.kind === "wipe") {
|
|
12305
|
+
if (reducedMotion(win))
|
|
12306
|
+
gripAt.snap(0.5);
|
|
12307
|
+
else
|
|
12308
|
+
gripAt.set(0.5);
|
|
12309
|
+
requestDraw();
|
|
12310
|
+
}
|
|
12052
12311
|
drag = null;
|
|
12053
12312
|
delete root.dataset.dragging;
|
|
12054
12313
|
delete root.dataset.panning;
|
|
@@ -12198,6 +12457,7 @@ function compare(container, options) {
|
|
|
12198
12457
|
old.el.replaceWith(s.el);
|
|
12199
12458
|
old.preview.replaceWith(s.preview);
|
|
12200
12459
|
sides[index] = s;
|
|
12460
|
+
root.toggleAttribute("data-video", sides.some((x) => x.kind === "video"));
|
|
12201
12461
|
note.textContent = "";
|
|
12202
12462
|
setLabel(index, labelOf(index));
|
|
12203
12463
|
if (near)
|
|
@@ -12212,6 +12472,7 @@ function compare(container, options) {
|
|
|
12212
12472
|
stage.insertBefore(sides[0].preview, labels[0]);
|
|
12213
12473
|
setLabel(0, labelOf(0));
|
|
12214
12474
|
setLabel(1, labelOf(1));
|
|
12475
|
+
root.toggleAttribute("data-video", sides.some((s) => s.kind === "video"));
|
|
12215
12476
|
loupeBtn.setAttribute("aria-pressed", String(loupeOn));
|
|
12216
12477
|
loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
|
|
12217
12478
|
const modePill = modeList.length > 1 ? selectionPill(modes, {
|
|
@@ -12412,6 +12673,7 @@ function compare(container, options) {
|
|
|
12412
12673
|
disposed = true;
|
|
12413
12674
|
loop.stop();
|
|
12414
12675
|
modePill?.dispose();
|
|
12676
|
+
glyph.dispose();
|
|
12415
12677
|
win.clearTimeout(releaseTimer);
|
|
12416
12678
|
ro.disconnect();
|
|
12417
12679
|
nearIo?.disconnect();
|
|
@@ -12441,7 +12703,7 @@ function compare(container, options) {
|
|
|
12441
12703
|
wake();
|
|
12442
12704
|
return api;
|
|
12443
12705
|
}
|
|
12444
|
-
var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
12706
|
+
var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
12445
12707
|
var init_compare = __esm(() => {
|
|
12446
12708
|
init_motion();
|
|
12447
12709
|
init_page_hints();
|
|
@@ -12452,15 +12714,17 @@ var init_compare = __esm(() => {
|
|
|
12452
12714
|
init_icons();
|
|
12453
12715
|
init_look();
|
|
12454
12716
|
init_rgba();
|
|
12717
|
+
init_player();
|
|
12455
12718
|
init_selection_pill();
|
|
12456
12719
|
init_timeline();
|
|
12457
12720
|
init_timeline_model();
|
|
12458
|
-
LAYOUT2 = springMotion(
|
|
12721
|
+
LAYOUT2 = springMotion(SPRINGS.default);
|
|
12459
12722
|
FADE6 = springMotion(SPRINGS.snappy);
|
|
12460
12723
|
FADE_REDUCED = springMotion(FADE);
|
|
12461
12724
|
ZOOM = springMotion({ duration: 0.35, bounce: 0 });
|
|
12462
12725
|
WIPE_CATCH = springMotion(CATCH_UP);
|
|
12463
12726
|
GRIP = { w: 16, h: 28 };
|
|
12727
|
+
MARK = { w: 12, gap: 4 };
|
|
12464
12728
|
LOUPE = { r: 88, zoom: 4 };
|
|
12465
12729
|
MODE_LABEL = {
|
|
12466
12730
|
side: { short: "Side", long: "Side by side" },
|
|
@@ -16966,11 +17230,629 @@ function toast(message, options = {}) {
|
|
|
16966
17230
|
};
|
|
16967
17231
|
}
|
|
16968
17232
|
|
|
17233
|
+
// src/ui/walkthrough-annotations.ts
|
|
17234
|
+
var EYE = "M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.47,133.47,0,0,1,25,128,133.33,133.33,0,0,1,48.07,97.25C70.33,75.19,97.22,64,128,64s57.67,11.19,79.93,33.25A133.46,133.46,0,0,1,231.05,128C223.84,141.46,192.43,192,128,192Zm0-112a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Z";
|
|
17235
|
+
var EYE_OFF = "M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.57A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z";
|
|
17236
|
+
var glyph = (d) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="16" height="16" fill="currentColor" aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
|
|
17237
|
+
var ARROW = '<svg class="knobs-wt-anno-arrow" viewBox="0 0 28 28" width="28" height="28" aria-hidden="true"><path d="M3 3 L24 24 M24 24 L24 13 M24 24 L13 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
17238
|
+
var shapeOf = (a) => a.shape ?? (a.w !== undefined && a.h !== undefined ? "box" : "dot");
|
|
17239
|
+
var showsAt = (a, time) => !a.at || time === undefined || time >= a.at[0] && time <= a.at[1];
|
|
17240
|
+
function placeMark(a, rect, view = ALL) {
|
|
17241
|
+
const w = a.w ?? 0;
|
|
17242
|
+
const h = a.h ?? 0;
|
|
17243
|
+
const x = (a.x - view.u) / view.w;
|
|
17244
|
+
const y = (a.y - view.v) / view.h;
|
|
17245
|
+
const ww = w / view.w;
|
|
17246
|
+
const hh = h / view.h;
|
|
17247
|
+
if (x + ww < 0 || y + hh < 0 || x > 1 || y > 1)
|
|
17248
|
+
return null;
|
|
17249
|
+
return { x: rect.x + x * rect.w, y: rect.y + y * rect.h, w: ww * rect.w, h: hh * rect.h };
|
|
17250
|
+
}
|
|
17251
|
+
var ALL = { u: 0, v: 0, w: 1, h: 1 };
|
|
17252
|
+
var scopeOf = (el) => el.closest(".knobs-walkthrough") ?? el.closest(".knobs-wt-slot") ?? el;
|
|
17253
|
+
var annotationsOn = (scope) => scope.dataset.annotations !== "off";
|
|
17254
|
+
function setAnnotations(scope, on) {
|
|
17255
|
+
if (on)
|
|
17256
|
+
delete scope.dataset.annotations;
|
|
17257
|
+
else
|
|
17258
|
+
scope.dataset.annotations = "off";
|
|
17259
|
+
for (const b of Array.from(scope.querySelectorAll(".knobs-wt-anno-toggle")))
|
|
17260
|
+
syncToggle(b, on);
|
|
17261
|
+
}
|
|
17262
|
+
function syncToggle(b, on) {
|
|
17263
|
+
b.setAttribute("aria-pressed", String(on));
|
|
17264
|
+
b.title = on ? "Hide annotations (a)" : "Show annotations (a)";
|
|
17265
|
+
b.innerHTML = glyph(on ? EYE : EYE_OFF);
|
|
17266
|
+
}
|
|
17267
|
+
function annotationLayer(host, list, place, corner = host) {
|
|
17268
|
+
const doc = host.ownerDocument;
|
|
17269
|
+
const layer = doc.createElement("div");
|
|
17270
|
+
layer.className = "knobs-wt-anno";
|
|
17271
|
+
layer.setAttribute("aria-hidden", "true");
|
|
17272
|
+
host.append(layer);
|
|
17273
|
+
const toggle = doc.createElement("button");
|
|
17274
|
+
toggle.type = "button";
|
|
17275
|
+
toggle.className = "knobs-wt-anno-toggle";
|
|
17276
|
+
toggle.setAttribute("aria-label", "Annotations");
|
|
17277
|
+
syncToggle(toggle, annotationsOn(scopeOf(host)));
|
|
17278
|
+
toggle.addEventListener("click", (e) => {
|
|
17279
|
+
e.stopPropagation();
|
|
17280
|
+
const scope = scopeOf(host);
|
|
17281
|
+
setAnnotations(scope, !annotationsOn(scope));
|
|
17282
|
+
});
|
|
17283
|
+
corner.append(toggle);
|
|
17284
|
+
let marks = [];
|
|
17285
|
+
const make = (a) => {
|
|
17286
|
+
const m = doc.createElement("div");
|
|
17287
|
+
const shape = shapeOf(a);
|
|
17288
|
+
m.className = "knobs-wt-mark";
|
|
17289
|
+
m.dataset.shape = shape;
|
|
17290
|
+
m.dataset.hidden = "";
|
|
17291
|
+
if (shape === "arrow")
|
|
17292
|
+
m.innerHTML = ARROW;
|
|
17293
|
+
if (a.label) {
|
|
17294
|
+
const l = doc.createElement("span");
|
|
17295
|
+
l.className = "knobs-wt-mark-label";
|
|
17296
|
+
l.textContent = a.label;
|
|
17297
|
+
m.append(l);
|
|
17298
|
+
}
|
|
17299
|
+
layer.append(m);
|
|
17300
|
+
return m;
|
|
17301
|
+
};
|
|
17302
|
+
let settled = false;
|
|
17303
|
+
const update = () => {
|
|
17304
|
+
const p = place();
|
|
17305
|
+
if (marks.length !== p.rects.length) {
|
|
17306
|
+
for (const row of marks)
|
|
17307
|
+
for (const m of row)
|
|
17308
|
+
m.remove();
|
|
17309
|
+
marks = p.rects.map(() => list.map(make));
|
|
17310
|
+
}
|
|
17311
|
+
p.rects.forEach((rect, i) => {
|
|
17312
|
+
list.forEach((a, j) => {
|
|
17313
|
+
const m = marks[i][j];
|
|
17314
|
+
const box = placeMark(a, rect, p.view);
|
|
17315
|
+
const show = box !== null && showsAt(a, p.time);
|
|
17316
|
+
if (box) {
|
|
17317
|
+
m.style.transform = `translate(${box.x.toFixed(2)}px,${box.y.toFixed(2)}px)`;
|
|
17318
|
+
m.style.width = `${box.w.toFixed(2)}px`;
|
|
17319
|
+
m.style.height = `${box.h.toFixed(2)}px`;
|
|
17320
|
+
}
|
|
17321
|
+
m.toggleAttribute("data-hidden", !show);
|
|
17322
|
+
});
|
|
17323
|
+
});
|
|
17324
|
+
if (!settled) {
|
|
17325
|
+
settled = true;
|
|
17326
|
+
requestAnimationFrame(() => requestAnimationFrame(() => layer.dataset.settled = ""));
|
|
17327
|
+
}
|
|
17328
|
+
};
|
|
17329
|
+
const ro = new ResizeObserver(() => update());
|
|
17330
|
+
ro.observe(host);
|
|
17331
|
+
update();
|
|
17332
|
+
return {
|
|
17333
|
+
element: layer,
|
|
17334
|
+
update,
|
|
17335
|
+
dispose() {
|
|
17336
|
+
ro.disconnect();
|
|
17337
|
+
layer.remove();
|
|
17338
|
+
toggle.remove();
|
|
17339
|
+
}
|
|
17340
|
+
};
|
|
17341
|
+
}
|
|
17342
|
+
|
|
16969
17343
|
// src/ui/walkthrough-blocks.ts
|
|
16970
17344
|
init_motion();
|
|
16971
|
-
|
|
17345
|
+
|
|
17346
|
+
// src/ui/history.ts
|
|
17347
|
+
init_motion();
|
|
17348
|
+
init_glide();
|
|
17349
|
+
|
|
17350
|
+
// src/ui/history-css.ts
|
|
17351
|
+
var historyCss = `
|
|
17352
|
+
:where(.knobs-history,.knobs-history *){box-sizing:border-box}
|
|
17353
|
+
.knobs-ui.knobs-history{--hy-gutter:128px;--hy-lane:24px;--hy-bar:16px;--hy-axis:20px;--hy-bandrow:0px;--hy-c:var(--_k-fg-3);
|
|
17354
|
+
--hy-fade:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);
|
|
17355
|
+
position:relative;display:flex;flex-direction:column;width:100%;min-width:0;outline:none;color:var(--_k-fg);
|
|
17356
|
+
font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";
|
|
17357
|
+
text-align:left;letter-spacing:normal;-webkit-font-smoothing:antialiased;user-select:none;-webkit-user-select:none}
|
|
17358
|
+
.knobs-history[data-bands]{--hy-bandrow:var(--_k-leading-small)}
|
|
17359
|
+
.knobs-history[data-narrow]{--hy-gutter:0px}
|
|
17360
|
+
.knobs-history [hidden]{display:none!important}
|
|
17361
|
+
:where(.knobs-ui) :is(.knobs-hy-ev,.knobs-hy-span,.knobs-hy-jump,.knobs-hy-more){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;
|
|
17362
|
+
background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
17363
|
+
.knobs-history [data-tone="good"]{--hy-c:var(--_k-ok)}
|
|
17364
|
+
.knobs-history [data-tone="warn"]{--hy-c:var(--_k-warn)}
|
|
17365
|
+
.knobs-history [data-tone="bad"]{--hy-c:var(--_k-danger)}
|
|
17366
|
+
.knobs-history [data-tone="info"]{--hy-c:var(--_k-info)}
|
|
17367
|
+
.knobs-history [data-tone="dim"]{--hy-c:var(--_k-fg-3)}
|
|
17368
|
+
|
|
17369
|
+
/* ——— the chart: lane labels beside the plot (above each lane where it's narrow) ——— */
|
|
17370
|
+
.knobs-hy-chart{position:relative;display:grid;grid-template-columns:var(--hy-gutter) minmax(0,1fr)}
|
|
17371
|
+
.knobs-hy-axis{position:relative;grid-column:2;grid-row:1;height:calc(var(--hy-bandrow) + var(--hy-axis))}
|
|
17372
|
+
:is(.knobs-hy-tick,.knobs-hy-now,.knobs-hy-blabel){position:absolute;white-space:nowrap;font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17373
|
+
.knobs-hy-tick{bottom:2px;padding-left:4px}
|
|
17374
|
+
.knobs-hy-now{bottom:2px;color:var(--_k-accent-fg);font-weight:var(--_k-weight-label)}
|
|
17375
|
+
.knobs-hy-blabel{top:0;padding-left:4px;overflow:hidden;color:var(--_k-fg-2)}
|
|
17376
|
+
.knobs-hy-label{grid-column:1;align-self:center;min-width:0;margin-right:var(--_k-space-title);overflow:hidden;white-space:nowrap;
|
|
17377
|
+
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17378
|
+
.knobs-history[data-narrow] .knobs-hy-label{grid-column:2;align-self:end;margin:0}
|
|
17379
|
+
:is(.knobs-hy-label,.knobs-hy-blabel)[data-overflow]{-webkit-mask-image:var(--hy-fade);mask-image:var(--hy-fade)}
|
|
17380
|
+
.knobs-hy-track{position:relative;grid-column:2;height:var(--hy-lane)}
|
|
17381
|
+
.knobs-hy-layer{position:absolute;inset:0;grid-column:2;grid-row:1/-1;pointer-events:none}
|
|
17382
|
+
|
|
17383
|
+
/* Bands and month lines behind the lanes; today, the selection and the pointer's line over them. */
|
|
17384
|
+
.knobs-hy-band{position:absolute;top:0;bottom:0;background:color-mix(in oklch,var(--_k-fg) 3.5%,transparent);box-shadow:inset 1px 0 0 var(--_k-border-subtle)}
|
|
17385
|
+
:is(.knobs-hy-grid,.knobs-hy-today,.knobs-hy-hover,.knobs-hy-sel){position:absolute;left:0;top:calc(var(--hy-bandrow) + var(--hy-axis));bottom:0;width:1px;margin-left:-.5px}
|
|
17386
|
+
.knobs-hy-grid{background:var(--_k-border-subtle)}
|
|
17387
|
+
.knobs-hy-today{top:calc(var(--hy-bandrow) + var(--hy-axis) - 4px);background:var(--_k-accent)}
|
|
17388
|
+
.knobs-hy-hover{background:color-mix(in oklch,var(--_k-fg) 32%,transparent);opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17389
|
+
.knobs-hy-hover[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17390
|
+
.knobs-hy-sel{background:var(--_k-fg-2);opacity:0}
|
|
17391
|
+
.knobs-hy-sel[data-on]{opacity:.6}
|
|
17392
|
+
|
|
17393
|
+
/* Spans: a soft bar in their state's tone, inferred ones hatched; the tag at the end. */
|
|
17394
|
+
.knobs-hy-span{position:absolute;top:calc((var(--hy-lane) - var(--hy-bar)) / 2);height:var(--hy-bar);border-radius:var(--_k-radius-sm);cursor:pointer;
|
|
17395
|
+
background:color-mix(in oklch,var(--hy-c) 24%,transparent);transition:background calc(var(--_k-hover-out) * 1ms),box-shadow calc(var(--_k-hover-out) * 1ms)}
|
|
17396
|
+
.knobs-hy-span[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 34%,transparent) 0 2px,color-mix(in oklch,var(--hy-c) 9%,transparent) 2px 6px)}
|
|
17397
|
+
.knobs-hy-span[data-hover]{background-color:color-mix(in oklch,var(--hy-c) 34%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17398
|
+
.knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 1px var(--hy-c)}
|
|
17399
|
+
.knobs-hy-span[data-clip-start]{border-top-left-radius:0;border-bottom-left-radius:0;-webkit-mask-image:linear-gradient(to right,transparent,#000 16px);mask-image:linear-gradient(to right,transparent,#000 16px)}
|
|
17400
|
+
.knobs-hy-span[data-clip-end]{border-top-right-radius:0;border-bottom-right-radius:0;-webkit-mask-image:linear-gradient(to left,transparent,#000 16px);mask-image:linear-gradient(to left,transparent,#000 16px)}
|
|
17401
|
+
.knobs-hy-tag{position:absolute;top:0;right:6px;font-size:var(--_k-type-small);line-height:var(--hy-bar);white-space:nowrap;pointer-events:none;
|
|
17402
|
+
color:color-mix(in oklch,var(--hy-c) 55%,var(--_k-fg))}
|
|
17403
|
+
.knobs-hy-tag[data-place="after"]{right:auto;left:calc(100% + 6px)}
|
|
17404
|
+
.knobs-hy-tag[data-place="none"]{visibility:hidden}
|
|
17405
|
+
|
|
17406
|
+
/* Event marks (their kind named in the tooltip): a dot for a commit, a diamond for a release, a cross for a deletion, a square for a
|
|
17407
|
+
channel's switch, a triangle for a finding; inferred ones hollow. */
|
|
17408
|
+
.knobs-hy-ev{position:absolute;top:0;width:16px;height:var(--hy-lane);margin-left:-8px;cursor:pointer;--hy-c:var(--_k-fg-2);--hy-s:9px}
|
|
17409
|
+
.knobs-hy-ev::before{content:"";position:absolute;left:50%;top:50%;width:var(--hy-s);height:var(--hy-s);margin:calc(var(--hy-s) / -2) 0 0 calc(var(--hy-s) / -2);
|
|
17410
|
+
border-radius:50%;background:var(--hy-c);transition:scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
17411
|
+
.knobs-hy-ev[data-range]{width:calc(var(--hy-w) + 16px)}
|
|
17412
|
+
.knobs-hy-ev[data-range]::before{left:calc(8px - var(--hy-s) / 2);right:calc(8px - var(--hy-s) / 2);width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
17413
|
+
.knobs-hy-ev[data-type="release"]{--hy-s:8px}
|
|
17414
|
+
.knobs-hy-ev[data-type="release"]::before{border-radius:1.5px;rotate:45deg}
|
|
17415
|
+
.knobs-hy-ev[data-type="channel"]{--hy-s:8px}
|
|
17416
|
+
.knobs-hy-ev[data-type="channel"]::before{border-radius:2px}
|
|
17417
|
+
.knobs-hy-ev[data-type="audit"]{--hy-s:10px}
|
|
17418
|
+
.knobs-hy-ev[data-type="audit"]::before{border-radius:0;clip-path:polygon(50% 4%,100% 92%,0 92%)}
|
|
17419
|
+
.knobs-hy-ev[data-type="deletion"]::before{border-radius:0;rotate:45deg;
|
|
17420
|
+
background:linear-gradient(var(--hy-c),var(--hy-c)) center/100% 1.75px no-repeat,linear-gradient(var(--hy-c),var(--hy-c)) center/1.75px 100% no-repeat}
|
|
17421
|
+
.knobs-hy-ev[data-inferred]:not([data-type="audit"],[data-type="deletion"])::before{background:transparent;box-shadow:inset 0 0 0 1.5px var(--hy-c)}
|
|
17422
|
+
.knobs-hy-ev[data-inferred]:is([data-type="audit"],[data-type="deletion"])::before{opacity:.5}
|
|
17423
|
+
/* Held-back marks (commits, by default) come up while the pointer is on their lane, and stay with
|
|
17424
|
+
"Show commits" or while selected. */
|
|
17425
|
+
.knobs-hy-ev[data-quiet]{opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17426
|
+
.knobs-hy-track:hover .knobs-hy-ev[data-quiet]{opacity:.6;transition-duration:calc(var(--_k-motion-snappy-ms) * 1ms)}
|
|
17427
|
+
:is(.knobs-history[data-all] .knobs-hy-ev[data-quiet],.knobs-hy-ev[data-quiet]:is([data-selected],[data-hover])){opacity:1}
|
|
17428
|
+
.knobs-hy-ev[data-hover]::before{scale:1.25}
|
|
17429
|
+
.knobs-hy-ev[data-selected]::before{scale:1.3}
|
|
17430
|
+
.knobs-hy-ev[data-selected]::after{content:"";position:absolute;left:50%;top:50%;width:calc(var(--hy-s) + 9px);height:calc(var(--hy-s) + 9px);
|
|
17431
|
+
margin:calc((var(--hy-s) + 9px) / -2) 0 0 calc((var(--hy-s) + 9px) / -2);border-radius:50%;box-shadow:0 0 0 1.5px var(--hy-c)}
|
|
17432
|
+
.knobs-hy-ev[data-range][data-selected]::after{left:3px;right:3px;width:auto;margin-left:0;border-radius:var(--_k-round)}
|
|
17433
|
+
.knobs-history:focus-visible .knobs-hy-ev[data-selected]::after{box-shadow:0 0 0 2px var(--_k-focus)}
|
|
17434
|
+
.knobs-history:focus-visible .knobs-hy-span[data-selected]{box-shadow:inset 0 0 0 2px var(--_k-focus)}
|
|
17435
|
+
|
|
17436
|
+
/* ——— under the chart: "Show commits", and a legend only when inferred and verified both appear ——— */
|
|
17437
|
+
.knobs-hy-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--_k-space-inside) var(--_k-space-title);margin-top:var(--_k-space-sibling);padding-left:var(--hy-gutter);
|
|
17438
|
+
font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17439
|
+
.knobs-hy-legend{display:flex;flex-wrap:wrap;gap:var(--_k-space-inside) var(--_k-space-title)}
|
|
17440
|
+
.knobs-ui .knobs-hy-more{height:20px;margin-left:-6px;padding:0 6px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-3);cursor:pointer;
|
|
17441
|
+
transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17442
|
+
.knobs-ui .knobs-hy-more:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17443
|
+
.knobs-hy-key{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
|
|
17444
|
+
.knobs-hy-swatch{flex:none;width:14px;height:8px;border-radius:2px;background:color-mix(in oklch,var(--hy-c) 34%,transparent)}
|
|
17445
|
+
.knobs-hy-swatch[data-inferred]{background:repeating-linear-gradient(135deg,color-mix(in oklch,var(--hy-c) 60%,transparent) 0 1.5px,transparent 1.5px 4px)}
|
|
17446
|
+
|
|
17447
|
+
/* ——— the detail: one area under the chart, its height on a spring ——— */
|
|
17448
|
+
.knobs-hy-detail{position:relative;overflow:hidden;user-select:text;-webkit-user-select:text}
|
|
17449
|
+
.knobs-hy-detail[data-moving]{-webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent);mask-image:linear-gradient(to bottom,#000 calc(100% - 16px),transparent)}
|
|
17450
|
+
.knobs-hy-dc{padding:var(--_k-space-title) 0 0 var(--hy-gutter);transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
17451
|
+
.knobs-hy-dc:is([data-enter],[data-leave]){opacity:0}
|
|
17452
|
+
.knobs-hy-dc[data-leave]{position:absolute;left:0;right:0;top:0;transition-duration:calc(var(--_k-motion-snappy-ms) * .8ms)}
|
|
17453
|
+
.knobs-hy-dmeta{display:flex;flex-wrap:wrap;gap:0 var(--_k-space-sibling);font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17454
|
+
.knobs-hy-dmeta>[data-tone]{color:var(--hy-c)}
|
|
17455
|
+
.knobs-hy-dtitle{margin-top:var(--_k-space-inside);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight-title);color:var(--_k-fg);text-wrap:balance}
|
|
17456
|
+
.knobs-hy-dtext{margin:var(--_k-space-inside) 0 0;max-width:68ch;font-size:var(--_k-type-body);line-height:var(--_k-leading-body);color:var(--_k-fg-2);text-wrap:pretty}
|
|
17457
|
+
.knobs-hy-dtext code{font:calc(1em - 1px) var(--_k-mono);padding:1px 4px;border-radius:var(--_k-radius-sm);background:var(--_k-fill)}
|
|
17458
|
+
.knobs-hy-dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:var(--_k-space-sibling)}
|
|
17459
|
+
.knobs-hy-dfoot:empty{display:none}
|
|
17460
|
+
.knobs-hy-evidence{display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:var(--_k-radius-sm);background:var(--_k-fill);color:var(--_k-fg-2);
|
|
17461
|
+
font:var(--_k-type-small)/1 var(--_k-mono);text-decoration:none;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17462
|
+
a.knobs-hy-evidence:hover{background:var(--_k-fill-hover);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17463
|
+
.knobs-ui .knobs-hy-jump{display:inline-flex;align-items:center;gap:4px;height:20px;padding:0 6px 0 8px;border-radius:var(--_k-radius-sm);color:var(--_k-fg-2);cursor:pointer;
|
|
17464
|
+
font-size:var(--_k-type-small);transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
17465
|
+
.knobs-ui .knobs-hy-jump:is(:hover,:focus-visible){background:var(--_k-fill);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17466
|
+
.knobs-hy-jump svg{width:12px;height:12px}
|
|
17467
|
+
|
|
17468
|
+
/* ——— the pointer's tooltip: title, date, one line ——— */
|
|
17469
|
+
.knobs-hy-tip{position:absolute;left:0;top:0;max-width:280px;padding:6px 8px;border-radius:var(--_k-radius-md);background:var(--_k-bg-overlay);
|
|
17470
|
+
box-shadow:var(--_k-shadow-overlay),0 0 0 1px var(--_k-border);pointer-events:none;opacity:0;transition:opacity calc(var(--_k-hover-out) * 1ms)}
|
|
17471
|
+
.knobs-hy-tip[data-on]{opacity:1;transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
17472
|
+
.knobs-hy-tt{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);font-weight:var(--_k-weight-label);color:var(--_k-fg);text-wrap:balance}
|
|
17473
|
+
:is(.knobs-hy-td,.knobs-hy-tl){font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
17474
|
+
.knobs-hy-tl{margin-top:2px;color:var(--_k-fg-2);text-wrap:pretty}
|
|
17475
|
+
.knobs-hy-tl:empty{display:none}
|
|
17476
|
+
|
|
17477
|
+
@media(prefers-reduced-motion:reduce){
|
|
17478
|
+
.knobs-history :where(*,::before,::after){transition-property:opacity,background,color,box-shadow!important}
|
|
17479
|
+
.knobs-hy-dc{transition-duration:calc(var(--_k-hover-out) * 1ms)!important}
|
|
17480
|
+
}
|
|
17481
|
+
`;
|
|
17482
|
+
|
|
17483
|
+
// src/ui/history-model.ts
|
|
17484
|
+
var MS_DAY = 86400000;
|
|
17485
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
17486
|
+
var dayOf = (y, m, d) => Math.floor(Date.UTC(y, m, d) / MS_DAY);
|
|
17487
|
+
var parts = (day) => {
|
|
17488
|
+
const t = new Date(day * MS_DAY);
|
|
17489
|
+
return { y: t.getUTCFullYear(), m: t.getUTCMonth(), d: t.getUTCDate() };
|
|
17490
|
+
};
|
|
17491
|
+
var todayDay = (now = Date.now()) => {
|
|
17492
|
+
const t = new Date(now);
|
|
17493
|
+
return dayOf(t.getFullYear(), t.getMonth(), t.getDate());
|
|
17494
|
+
};
|
|
17495
|
+
var monthIndex = (name) => MONTHS.findIndex((m) => m.toLowerCase() === name.slice(0, 3).toLowerCase());
|
|
17496
|
+
function parseDay(text, year) {
|
|
17497
|
+
const s = text.trim();
|
|
17498
|
+
const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(s);
|
|
17499
|
+
if (iso) {
|
|
17500
|
+
const y = Number(iso[1]);
|
|
17501
|
+
const day = dayOf(y, Number(iso[2]) - 1, Number(iso[3]));
|
|
17502
|
+
return { from: day, to: day, year: y };
|
|
17503
|
+
}
|
|
17504
|
+
const m = /^([A-Za-z]{3,9})\.?\s+(\d{1,2})(?:\s*[–—-]\s*(?:([A-Za-z]{3,9})\.?\s+)?(\d{1,2}))?(?:,?\s+(\d{4}))?$/.exec(s);
|
|
17505
|
+
if (!m)
|
|
17506
|
+
return null;
|
|
17507
|
+
const m1 = monthIndex(m[1]);
|
|
17508
|
+
const m2 = m[3] ? monthIndex(m[3]) : m1;
|
|
17509
|
+
if (m1 < 0 || m2 < 0)
|
|
17510
|
+
return null;
|
|
17511
|
+
const written = m[5] ? Number(m[5]) : undefined;
|
|
17512
|
+
const y = written ?? year;
|
|
17513
|
+
if (y === undefined)
|
|
17514
|
+
return null;
|
|
17515
|
+
const from = dayOf(y, m1, Number(m[2]));
|
|
17516
|
+
let to = m[4] ? dayOf(y, m2, Number(m[4])) : from;
|
|
17517
|
+
if (to < from)
|
|
17518
|
+
to = dayOf(y + 1, m2, Number(m[4]));
|
|
17519
|
+
return { from, to, ...written !== undefined ? { year: written } : {} };
|
|
17520
|
+
}
|
|
17521
|
+
function formatDay(day) {
|
|
17522
|
+
const { y, m, d } = parts(day);
|
|
17523
|
+
return `${MONTHS[m]} ${d}, ${y}`;
|
|
17524
|
+
}
|
|
17525
|
+
function formatRange(from, to, open = false) {
|
|
17526
|
+
if (open)
|
|
17527
|
+
return `${formatDay(from)} – today`;
|
|
17528
|
+
if (to <= from)
|
|
17529
|
+
return formatDay(from);
|
|
17530
|
+
const a = parts(from);
|
|
17531
|
+
const b = parts(to);
|
|
17532
|
+
if (a.y === b.y && a.m === b.m)
|
|
17533
|
+
return `${MONTHS[a.m]} ${a.d}–${b.d}, ${a.y}`;
|
|
17534
|
+
if (a.y === b.y)
|
|
17535
|
+
return `${MONTHS[a.m]} ${a.d} – ${MONTHS[b.m]} ${b.d}, ${a.y}`;
|
|
17536
|
+
return `${formatDay(from)} – ${formatDay(to)}`;
|
|
17537
|
+
}
|
|
17538
|
+
var formatDays = (n) => `${n} ${n === 1 ? "day" : "days"}`;
|
|
17539
|
+
function guessTone(state) {
|
|
17540
|
+
const s = state.toLowerCase();
|
|
17541
|
+
if (/broken|bad|fail|missing|deleted|down/.test(s))
|
|
17542
|
+
return "bad";
|
|
17543
|
+
if (/warn|redirect|partial|beta|next|pre/.test(s))
|
|
17544
|
+
return "warn";
|
|
17545
|
+
if (/work|ok|good|fixed|live|green/.test(s))
|
|
17546
|
+
return "good";
|
|
17547
|
+
return "dim";
|
|
17548
|
+
}
|
|
17549
|
+
var bandLabel = (heading) => heading.replace(/^\s*\d+\s*[·.:)\-–]\s*/, "").replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
17550
|
+
function fromGroups(groups, today) {
|
|
17551
|
+
const events = [];
|
|
17552
|
+
const bands = [];
|
|
17553
|
+
const all = groups.flatMap((g) => g.events ?? g.rows ?? []);
|
|
17554
|
+
let year = all.map((e) => parseDay(e.date, 0)?.year).find((y) => y !== undefined) ?? parts(today).y;
|
|
17555
|
+
for (const g of groups) {
|
|
17556
|
+
let lo = Infinity;
|
|
17557
|
+
let hi = -Infinity;
|
|
17558
|
+
for (const e of g.events ?? g.rows ?? []) {
|
|
17559
|
+
const d = parseDay(e.date, year);
|
|
17560
|
+
if (!d)
|
|
17561
|
+
continue;
|
|
17562
|
+
if (d.year !== undefined)
|
|
17563
|
+
year = d.year;
|
|
17564
|
+
lo = Math.min(lo, d.from);
|
|
17565
|
+
hi = Math.max(hi, d.to);
|
|
17566
|
+
events.push({
|
|
17567
|
+
...e,
|
|
17568
|
+
date: iso(d.from),
|
|
17569
|
+
...d.to > d.from ? { to: iso(d.to) } : {},
|
|
17570
|
+
lane: e.lane ?? "code"
|
|
17571
|
+
});
|
|
17572
|
+
}
|
|
17573
|
+
const label = g.heading ? bandLabel(g.heading) : "";
|
|
17574
|
+
if (label && lo <= hi)
|
|
17575
|
+
bands.push({ from: iso(lo), to: iso(hi), label });
|
|
17576
|
+
}
|
|
17577
|
+
return { events, bands };
|
|
17578
|
+
}
|
|
17579
|
+
function iso(day) {
|
|
17580
|
+
const { y, m, d } = parts(day);
|
|
17581
|
+
return `${y}-${String(m + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
|
|
17582
|
+
}
|
|
17583
|
+
var startOfMonth = (day) => {
|
|
17584
|
+
const { y, m } = parts(day);
|
|
17585
|
+
return dayOf(y, m, 1);
|
|
17586
|
+
};
|
|
17587
|
+
var TYPE_TONE = { deletion: "bad" };
|
|
17588
|
+
function normalizeHistory(data, now = todayDay()) {
|
|
17589
|
+
const today = data.axis?.today && parseDay(data.axis.today, parts(now).y)?.from || now;
|
|
17590
|
+
const converted = data.groups?.length ? fromGroups(data.groups, today) : { events: [], bands: [] };
|
|
17591
|
+
const rawEvents = [...data.events ?? [], ...converted.events];
|
|
17592
|
+
const rawBands = [...data.bands ?? [], ...converted.bands];
|
|
17593
|
+
const year = parts(today).y;
|
|
17594
|
+
const day = (s) => s ? parseDay(s, year) ?? null : null;
|
|
17595
|
+
const states = new Map;
|
|
17596
|
+
const stateOf = (key) => {
|
|
17597
|
+
const known = states.get(key);
|
|
17598
|
+
if (known)
|
|
17599
|
+
return known;
|
|
17600
|
+
const def = data.states?.[key];
|
|
17601
|
+
const s = { key, label: def?.label ?? key, tone: def?.tone ?? guessTone(key) };
|
|
17602
|
+
states.set(key, s);
|
|
17603
|
+
return s;
|
|
17604
|
+
};
|
|
17605
|
+
const items = new Map;
|
|
17606
|
+
const laneOrder = [];
|
|
17607
|
+
const lanes = new Map;
|
|
17608
|
+
const lane = (id, label = id === "code" ? "Code" : id) => {
|
|
17609
|
+
let l = lanes.get(id);
|
|
17610
|
+
if (!l) {
|
|
17611
|
+
l = { id, label, spans: [], events: [] };
|
|
17612
|
+
lanes.set(id, l);
|
|
17613
|
+
laneOrder.push(id);
|
|
17614
|
+
}
|
|
17615
|
+
return l;
|
|
17616
|
+
};
|
|
17617
|
+
if (rawEvents.some((e) => !e.lane || e.lane === "code") && !data.lanes?.some((l) => l.id === "code"))
|
|
17618
|
+
lane("code");
|
|
17619
|
+
let inferred = false;
|
|
17620
|
+
for (const l of data.lanes ?? []) {
|
|
17621
|
+
const into = lane(l.id, l.label);
|
|
17622
|
+
(l.spans ?? []).forEach((s, i) => {
|
|
17623
|
+
const a = day(s.from);
|
|
17624
|
+
if (!a)
|
|
17625
|
+
return;
|
|
17626
|
+
const open = !s.to;
|
|
17627
|
+
const b = open ? null : day(s.to);
|
|
17628
|
+
const to = open ? today : b?.to ?? a.to;
|
|
17629
|
+
const st = stateOf(s.state);
|
|
17630
|
+
const days = Math.max(0, to - a.from);
|
|
17631
|
+
const own = s.label ?? data.states?.[s.state]?.tag ?? "{d}d";
|
|
17632
|
+
const tags = own === "" ? [] : [...new Set([own, "{d}d"])].map((t) => t.replace(/\{d\}/g, String(days)));
|
|
17633
|
+
const id = s.id ?? `${l.id}/${i}`;
|
|
17634
|
+
const item = {
|
|
17635
|
+
id,
|
|
17636
|
+
kind: "span",
|
|
17637
|
+
lane: l.id,
|
|
17638
|
+
from: a.from,
|
|
17639
|
+
to,
|
|
17640
|
+
open,
|
|
17641
|
+
title: s.title ?? `${l.label} · ${st.label}`,
|
|
17642
|
+
...s.detail ? { detail: s.detail } : {},
|
|
17643
|
+
tone: st.tone,
|
|
17644
|
+
inferred: s.provenance === "inferred",
|
|
17645
|
+
...s.provenance ? { provenance: s.provenance } : {},
|
|
17646
|
+
...s.source ? { source: s.source } : {},
|
|
17647
|
+
evidence: s.evidence ?? [],
|
|
17648
|
+
...s.step ? { step: s.step } : {},
|
|
17649
|
+
state: s.state,
|
|
17650
|
+
tags
|
|
17651
|
+
};
|
|
17652
|
+
if (item.inferred)
|
|
17653
|
+
inferred = true;
|
|
17654
|
+
into.spans.push(item);
|
|
17655
|
+
items.set(id, item);
|
|
17656
|
+
});
|
|
17657
|
+
}
|
|
17658
|
+
const types = [];
|
|
17659
|
+
const events = [];
|
|
17660
|
+
rawEvents.forEach((e, index) => {
|
|
17661
|
+
const a = day(e.date);
|
|
17662
|
+
if (!a)
|
|
17663
|
+
return;
|
|
17664
|
+
const to = Math.max(a.to, day(e.to)?.to ?? a.to);
|
|
17665
|
+
const into = lane(e.lane ?? "code");
|
|
17666
|
+
const id = e.id ?? `e${index}`;
|
|
17667
|
+
const item = {
|
|
17668
|
+
id,
|
|
17669
|
+
kind: "event",
|
|
17670
|
+
lane: into.id,
|
|
17671
|
+
from: a.from,
|
|
17672
|
+
to,
|
|
17673
|
+
open: false,
|
|
17674
|
+
title: e.title,
|
|
17675
|
+
...e.detail ? { detail: e.detail } : {},
|
|
17676
|
+
tone: e.tone ?? TYPE_TONE[e.type ?? ""] ?? "dim",
|
|
17677
|
+
...e.type ? { type: e.type } : {},
|
|
17678
|
+
inferred: e.provenance === "inferred",
|
|
17679
|
+
...e.provenance ? { provenance: e.provenance } : {},
|
|
17680
|
+
...e.source ? { source: e.source } : {},
|
|
17681
|
+
evidence: e.evidence ?? [],
|
|
17682
|
+
...e.step ? { step: e.step } : {},
|
|
17683
|
+
tags: []
|
|
17684
|
+
};
|
|
17685
|
+
if (item.inferred)
|
|
17686
|
+
inferred = true;
|
|
17687
|
+
if (e.type && !types.includes(e.type))
|
|
17688
|
+
types.push(e.type);
|
|
17689
|
+
into.events.push(item);
|
|
17690
|
+
items.set(id, item);
|
|
17691
|
+
events.push({ item, index });
|
|
17692
|
+
});
|
|
17693
|
+
const bands = [];
|
|
17694
|
+
for (const b of rawBands) {
|
|
17695
|
+
const a = day(b.from);
|
|
17696
|
+
if (!a)
|
|
17697
|
+
continue;
|
|
17698
|
+
const z = b.to ? day(b.to) : null;
|
|
17699
|
+
bands.push({ from: a.from, to: z ? z.to : today, label: b.label });
|
|
17700
|
+
}
|
|
17701
|
+
bands.sort((x, y) => x.from - y.from);
|
|
17702
|
+
const all = [...items.values()].flatMap((i) => [i.from, i.to]);
|
|
17703
|
+
for (const b of bands)
|
|
17704
|
+
all.push(b.from, b.to);
|
|
17705
|
+
const hasOpen = [...items.values()].some((i) => i.open);
|
|
17706
|
+
const lo = all.length ? Math.min(...all) : today - 90;
|
|
17707
|
+
let hi = all.length ? Math.max(...all) : today;
|
|
17708
|
+
if (hasOpen || data.axis?.today)
|
|
17709
|
+
hi = Math.max(hi, today);
|
|
17710
|
+
const from = day(data.axis?.from)?.from ?? startOfMonth(lo);
|
|
17711
|
+
const to = day(data.axis?.to)?.to ?? hi + Math.max(6, Math.round((hi - from) * 0.05));
|
|
17712
|
+
const laneIndex = (id) => laneOrder.indexOf(id);
|
|
17713
|
+
const order = eventOrder(events.map((e) => ({ ...e, laneIndex: laneIndex(e.item.lane) })));
|
|
17714
|
+
for (const l of lanes.values())
|
|
17715
|
+
l.events.sort((a, b) => a.from - b.from);
|
|
17716
|
+
return {
|
|
17717
|
+
from,
|
|
17718
|
+
to: Math.max(to, from + 1),
|
|
17719
|
+
today,
|
|
17720
|
+
lanes: laneOrder.map((id) => lanes.get(id)),
|
|
17721
|
+
events: order,
|
|
17722
|
+
bands,
|
|
17723
|
+
states: [...states.values()],
|
|
17724
|
+
types,
|
|
17725
|
+
inferred,
|
|
17726
|
+
verified: [...items.values()].some((i) => i.provenance === "verified"),
|
|
17727
|
+
items
|
|
17728
|
+
};
|
|
17729
|
+
}
|
|
17730
|
+
var DEFAULT_MARKS = ["release", "deletion"];
|
|
17731
|
+
function quietEvents(events, marks) {
|
|
17732
|
+
if (marks === "all")
|
|
17733
|
+
return new Set;
|
|
17734
|
+
const quiet = events.filter((e) => e.type && !marks.includes(e.type));
|
|
17735
|
+
return quiet.length === events.length ? new Set : new Set(quiet.map((e) => e.id));
|
|
17736
|
+
}
|
|
17737
|
+
var PLURALS = {
|
|
17738
|
+
commit: "commits",
|
|
17739
|
+
release: "releases",
|
|
17740
|
+
deletion: "deletions",
|
|
17741
|
+
channel: "channel switches",
|
|
17742
|
+
audit: "findings"
|
|
17743
|
+
};
|
|
17744
|
+
function quietLabel(events, quiet) {
|
|
17745
|
+
const types = [];
|
|
17746
|
+
for (const e of events)
|
|
17747
|
+
if (quiet.has(e.id) && e.type && !types.includes(e.type))
|
|
17748
|
+
types.push(e.type);
|
|
17749
|
+
const names = types.map((t) => PLURALS[t] ?? `${t}s`);
|
|
17750
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}` : names[0] ?? "";
|
|
17751
|
+
}
|
|
17752
|
+
function eventOrder(events) {
|
|
17753
|
+
return [...events].sort((a, b) => a.item.from - b.item.from || a.laneIndex - b.laneIndex || a.index - b.index).map((e) => e.item);
|
|
17754
|
+
}
|
|
17755
|
+
function stepEvent(order, current, dir, at) {
|
|
17756
|
+
if (!order.length)
|
|
17757
|
+
return null;
|
|
17758
|
+
const i = current === null ? -1 : order.findIndex((e) => e.id === current);
|
|
17759
|
+
if (i < 0 && at !== undefined) {
|
|
17760
|
+
const next = dir > 0 ? order.find((e) => e.from >= at) : [...order].reverse().find((e) => e.from <= at);
|
|
17761
|
+
return next ?? (dir > 0 ? order[order.length - 1] : order[0]);
|
|
17762
|
+
}
|
|
17763
|
+
if (i < 0)
|
|
17764
|
+
return dir > 0 ? order[0] : order[order.length - 1];
|
|
17765
|
+
return order[Math.max(0, Math.min(order.length - 1, i + dir))];
|
|
17766
|
+
}
|
|
17767
|
+
function dayScale(from, to, width) {
|
|
17768
|
+
const span = Math.max(1, to - from);
|
|
17769
|
+
return {
|
|
17770
|
+
x: (day) => (day - from) / span * width,
|
|
17771
|
+
day: (x) => from + x / Math.max(1, width) * span,
|
|
17772
|
+
f: (day) => (day - from) / span
|
|
17773
|
+
};
|
|
17774
|
+
}
|
|
17775
|
+
var STEPS = [1, 2, 3, 6, 12];
|
|
17776
|
+
function monthTicks(from, to, width, minGap = 44) {
|
|
17777
|
+
const perMonth = width / Math.max(1, to - from) * 30.44;
|
|
17778
|
+
const step = STEPS.find((s) => s * perMonth >= minGap) ?? 12;
|
|
17779
|
+
const ticks = [];
|
|
17780
|
+
const { y } = parts(from);
|
|
17781
|
+
let { m } = parts(from);
|
|
17782
|
+
let d = dayOf(y, m, 1);
|
|
17783
|
+
if (d < from) {
|
|
17784
|
+
m++;
|
|
17785
|
+
d = dayOf(y, m, 1);
|
|
17786
|
+
}
|
|
17787
|
+
while (d <= to) {
|
|
17788
|
+
const p = parts(d);
|
|
17789
|
+
if (p.m % step === 0)
|
|
17790
|
+
ticks.push({ day: d, label: p.m === 0 ? String(p.y) : MONTHS[p.m] });
|
|
17791
|
+
m++;
|
|
17792
|
+
d = dayOf(y, m, 1);
|
|
17793
|
+
}
|
|
17794
|
+
return ticks;
|
|
17795
|
+
}
|
|
17796
|
+
function fitLabels(labels, width, reserved = [], gap = 8) {
|
|
17797
|
+
let end = -Infinity;
|
|
17798
|
+
return labels.map(({ x, w }) => {
|
|
17799
|
+
const x1 = x + w;
|
|
17800
|
+
if (x < end + gap || x1 > width)
|
|
17801
|
+
return false;
|
|
17802
|
+
if (reserved.some((r) => x < r.x1 + gap && x1 > r.x0 - gap))
|
|
17803
|
+
return false;
|
|
17804
|
+
end = x1;
|
|
17805
|
+
return true;
|
|
17806
|
+
});
|
|
17807
|
+
}
|
|
17808
|
+
function clipSpan(from, to, axisFrom, axisTo) {
|
|
17809
|
+
if (to < axisFrom || from > axisTo)
|
|
17810
|
+
return null;
|
|
17811
|
+
return {
|
|
17812
|
+
from: Math.max(from, axisFrom),
|
|
17813
|
+
to: Math.min(to, axisTo),
|
|
17814
|
+
clipStart: from < axisFrom,
|
|
17815
|
+
clipEnd: to > axisTo
|
|
17816
|
+
};
|
|
17817
|
+
}
|
|
17818
|
+
function fitTag(widths, spanWidth, roomAfter, pad = 6) {
|
|
17819
|
+
for (let i = 0;i < widths.length; i++)
|
|
17820
|
+
if (widths[i] + pad * 2 <= spanWidth)
|
|
17821
|
+
return { index: i, place: "inside" };
|
|
17822
|
+
for (let i = 0;i < widths.length; i++)
|
|
17823
|
+
if (widths[i] + pad <= roomAfter)
|
|
17824
|
+
return { index: i, place: "after" };
|
|
17825
|
+
return null;
|
|
17826
|
+
}
|
|
17827
|
+
function bandRoom(bands, x) {
|
|
17828
|
+
return bands.map((b, i) => {
|
|
17829
|
+
const next = bands.slice(i + 1).find((n) => n.from > b.from);
|
|
17830
|
+
const end = Math.min(b.to, next ? next.from : Infinity);
|
|
17831
|
+
return Math.max(0, x(end) - x(b.from));
|
|
17832
|
+
});
|
|
17833
|
+
}
|
|
17834
|
+
function nearestEvent(events, day, tolerance) {
|
|
17835
|
+
let best = null;
|
|
17836
|
+
let gap = tolerance;
|
|
17837
|
+
for (const e of events) {
|
|
17838
|
+
const d = day < e.from ? e.from - day : day > e.to ? day - e.to : 0;
|
|
17839
|
+
if (d <= gap) {
|
|
17840
|
+
best = e;
|
|
17841
|
+
gap = d;
|
|
17842
|
+
}
|
|
17843
|
+
}
|
|
17844
|
+
return best;
|
|
17845
|
+
}
|
|
17846
|
+
function firstSentence(text) {
|
|
17847
|
+
if (!text)
|
|
17848
|
+
return "";
|
|
17849
|
+
const plain = text.replace(/`([^`]*)`/g, "$1").replace(/\*\*?([^*]+)\*\*?/g, "$1").trim();
|
|
17850
|
+
const m = /^.*?[.!?](?=\s|$)/.exec(plain);
|
|
17851
|
+
return (m ? m[0] : plain).trim();
|
|
17852
|
+
}
|
|
17853
|
+
|
|
17854
|
+
// src/ui/history.ts
|
|
16972
17855
|
init_look();
|
|
16973
|
-
init_selection_pill();
|
|
16974
17856
|
|
|
16975
17857
|
// src/ui/walkthrough-model.ts
|
|
16976
17858
|
var DEFAULT_VERDICTS = ["ship", "change", "unsure"];
|
|
@@ -17036,7 +17918,14 @@ function fillMedia(m, base) {
|
|
|
17036
17918
|
case "image":
|
|
17037
17919
|
return { ...m, src: resolveSrc(m.src, base) };
|
|
17038
17920
|
case "history":
|
|
17039
|
-
|
|
17921
|
+
if ("data" in m)
|
|
17922
|
+
return m;
|
|
17923
|
+
return {
|
|
17924
|
+
kind: "history",
|
|
17925
|
+
data: historyData(m),
|
|
17926
|
+
...m.label ? { label: m.label } : {},
|
|
17927
|
+
...m.marks ? { marks: m.marks } : {}
|
|
17928
|
+
};
|
|
17040
17929
|
case "stack":
|
|
17041
17930
|
return {
|
|
17042
17931
|
kind: "stack",
|
|
@@ -17049,17 +17938,26 @@ function fillMedia(m, base) {
|
|
|
17049
17938
|
return m;
|
|
17050
17939
|
}
|
|
17051
17940
|
}
|
|
17052
|
-
function
|
|
17941
|
+
function historyData(m) {
|
|
17942
|
+
const events = m.events ?? [];
|
|
17943
|
+
if (m.lanes || m.bands || m.states || m.axis)
|
|
17944
|
+
return {
|
|
17945
|
+
...m.axis ? { axis: m.axis } : {},
|
|
17946
|
+
...m.lanes ? { lanes: m.lanes } : {},
|
|
17947
|
+
...events.length ? { events } : {},
|
|
17948
|
+
...m.bands ? { bands: m.bands } : {},
|
|
17949
|
+
...m.states ? { states: m.states } : {}
|
|
17950
|
+
};
|
|
17053
17951
|
const groups = [];
|
|
17054
|
-
if (
|
|
17055
|
-
groups.push({ events
|
|
17952
|
+
if (events.length)
|
|
17953
|
+
groups.push({ events });
|
|
17056
17954
|
for (const g of m.groups ?? [])
|
|
17057
17955
|
groups.push({
|
|
17058
17956
|
...g.heading ? { heading: g.heading } : {},
|
|
17059
17957
|
...g.summary ? { summary: g.summary } : {},
|
|
17060
17958
|
events: g.events ?? g.rows ?? []
|
|
17061
17959
|
});
|
|
17062
|
-
return groups;
|
|
17960
|
+
return { groups };
|
|
17063
17961
|
}
|
|
17064
17962
|
function parseBranch(branch) {
|
|
17065
17963
|
if (!branch?.trim())
|
|
@@ -17259,7 +18157,7 @@ function normalizeWalkthrough(doc) {
|
|
|
17259
18157
|
...step.question ? { question: step.question } : {},
|
|
17260
18158
|
media: list.map((m) => fillMedia(m, doc.base)),
|
|
17261
18159
|
columns: Math.max(1, Math.min(3, Math.round(step.columns ?? 1))),
|
|
17262
|
-
...step.more?.trim()
|
|
18160
|
+
...step.more?.trim() ? { more: step.more.trim() } : {},
|
|
17263
18161
|
options,
|
|
17264
18162
|
ask
|
|
17265
18163
|
};
|
|
@@ -17274,6 +18172,7 @@ function normalizeWalkthrough(doc) {
|
|
|
17274
18172
|
...typeof s.kicker === "string" && s.kicker.trim() ? { kicker: s.kicker.trim() } : {},
|
|
17275
18173
|
...s.status ? { status: s.status } : {},
|
|
17276
18174
|
...s.summary ? { summary: s.summary } : {},
|
|
18175
|
+
...s.more?.trim() ? { more: s.more.trim() } : {},
|
|
17277
18176
|
steps: own
|
|
17278
18177
|
};
|
|
17279
18178
|
});
|
|
@@ -17307,6 +18206,7 @@ function normalizeWalkthrough(doc) {
|
|
|
17307
18206
|
id: doc.id,
|
|
17308
18207
|
title: doc.title,
|
|
17309
18208
|
...doc.summary ? { summary: doc.summary } : {},
|
|
18209
|
+
...doc.more?.trim() ? { more: doc.more.trim() } : {},
|
|
17310
18210
|
meta,
|
|
17311
18211
|
feed,
|
|
17312
18212
|
verdicts: ((doc.verdicts?.length) ? doc.verdicts : DEFAULT_VERDICTS).map((v) => ({
|
|
@@ -17314,9 +18214,40 @@ function normalizeWalkthrough(doc) {
|
|
|
17314
18214
|
label: label(v)
|
|
17315
18215
|
})),
|
|
17316
18216
|
sections: withKickers(sections, doc.sections, doc.kickers),
|
|
17317
|
-
steps
|
|
18217
|
+
steps,
|
|
18218
|
+
prs: knownPRs(doc, sections),
|
|
18219
|
+
...repoOf(doc) ? { repo: repoOf(doc) } : {}
|
|
17318
18220
|
};
|
|
17319
18221
|
}
|
|
18222
|
+
var prNumber = (href) => /\/(?:pull|merge_requests)\/(\d+)(?:[/?#]|$)/.exec(href)?.[1] ?? null;
|
|
18223
|
+
var repoOf = (doc) => doc.repo?.trim().replace(/^https?:\/\/github\.com\//, "").replace(/\/+$/, "") || undefined;
|
|
18224
|
+
function knownPRs(doc, sections) {
|
|
18225
|
+
const out = {};
|
|
18226
|
+
const repo = repoOf(doc);
|
|
18227
|
+
for (const s of sections) {
|
|
18228
|
+
const n = s.link && prNumber(s.link.href);
|
|
18229
|
+
if (n)
|
|
18230
|
+
out[n] = { href: s.link.href.replace(/(\/pull\/\d+).*$/, "$1") };
|
|
18231
|
+
}
|
|
18232
|
+
for (const [k, pr] of Object.entries(doc.prs ?? {})) {
|
|
18233
|
+
const n = k.replace(/^#/, "");
|
|
18234
|
+
if (!/^\d+$/.test(n))
|
|
18235
|
+
continue;
|
|
18236
|
+
out[n] = { ...out[n], ...pr };
|
|
18237
|
+
}
|
|
18238
|
+
if (repo) {
|
|
18239
|
+
for (const n of Object.keys(out))
|
|
18240
|
+
if (!out[n].href)
|
|
18241
|
+
out[n] = { ...out[n], href: `https://github.com/${repo}/pull/${n}` };
|
|
18242
|
+
}
|
|
18243
|
+
return out;
|
|
18244
|
+
}
|
|
18245
|
+
function findPR(prs, n, repo) {
|
|
18246
|
+
const known = prs[n];
|
|
18247
|
+
if (known)
|
|
18248
|
+
return known;
|
|
18249
|
+
return repo ? { href: `https://github.com/${repo}/pull/${n}` } : null;
|
|
18250
|
+
}
|
|
17320
18251
|
var isPullRequest = (href) => /\/(?:pull|merge_requests)\/\d+(?:[/?#]|$)/.test(href);
|
|
17321
18252
|
function withKickers(sections, source, kickers = "pr") {
|
|
17322
18253
|
if (kickers === false)
|
|
@@ -17645,11 +18576,18 @@ function inlineMarkdown(text) {
|
|
|
17645
18576
|
return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${escapeHtml(codes[Number(i)])}</code>`);
|
|
17646
18577
|
}
|
|
17647
18578
|
|
|
17648
|
-
// src/ui/
|
|
17649
|
-
var
|
|
17650
|
-
|
|
17651
|
-
|
|
18579
|
+
// src/ui/history.ts
|
|
18580
|
+
var HEIGHT2 = springMotion(SPRINGS.default);
|
|
18581
|
+
var GO = svg('<path d="M5 5h6v6M11 5l-6 6"/>');
|
|
18582
|
+
var TYPE_NAMES = {
|
|
18583
|
+
commit: "Commit",
|
|
18584
|
+
release: "Release",
|
|
18585
|
+
deletion: "Deletion",
|
|
18586
|
+
channel: "Channel switch",
|
|
18587
|
+
audit: "Finding"
|
|
17652
18588
|
};
|
|
18589
|
+
var typeName = (t) => TYPE_NAMES[t] ?? t.charAt(0).toUpperCase() + t.slice(1);
|
|
18590
|
+
var REACH = 8;
|
|
17653
18591
|
var el = (doc, tag, cls) => {
|
|
17654
18592
|
const n = doc.createElement(tag);
|
|
17655
18593
|
if (cls)
|
|
@@ -17661,112 +18599,625 @@ var text = (doc, tag, cls, value) => {
|
|
|
17661
18599
|
n.textContent = value;
|
|
17662
18600
|
return n;
|
|
17663
18601
|
};
|
|
18602
|
+
function injectHistoryStyles(doc) {
|
|
18603
|
+
if (doc.querySelector("style[data-knobs-history]"))
|
|
18604
|
+
return;
|
|
18605
|
+
const style = doc.createElement("style");
|
|
18606
|
+
style.dataset.knobsHistory = "";
|
|
18607
|
+
style.textContent = aliased(historyCss);
|
|
18608
|
+
doc.head.append(style);
|
|
18609
|
+
}
|
|
18610
|
+
var when = (it) => it.kind === "span" ? `${formatRange(it.from, it.to, it.open)} · ${formatDays(it.to - it.from)}` : formatRange(it.from, it.to);
|
|
18611
|
+
function history(container, options) {
|
|
18612
|
+
const doc = container.ownerDocument;
|
|
18613
|
+
const win = doc.defaultView;
|
|
18614
|
+
const root = el(doc, "div", "knobs-history");
|
|
18615
|
+
applyLook2(root, options);
|
|
18616
|
+
injectHistoryStyles(doc);
|
|
18617
|
+
root.tabIndex = 0;
|
|
18618
|
+
root.setAttribute("role", "group");
|
|
18619
|
+
root.setAttribute("aria-label", options.label ?? "History");
|
|
18620
|
+
root.setAttribute("aria-roledescription", "timeline");
|
|
18621
|
+
const chart = el(doc, "div", "knobs-hy-chart");
|
|
18622
|
+
const foot = el(doc, "div", "knobs-hy-foot");
|
|
18623
|
+
const detail = el(doc, "div", "knobs-hy-detail");
|
|
18624
|
+
detail.setAttribute("aria-live", "polite");
|
|
18625
|
+
const tip = el(doc, "div", "knobs-hy-tip");
|
|
18626
|
+
tip.setAttribute("aria-hidden", "true");
|
|
18627
|
+
const tipTitle = el(doc, "div", "knobs-hy-tt");
|
|
18628
|
+
const tipDate = el(doc, "div", "knobs-hy-td");
|
|
18629
|
+
const tipLine = el(doc, "div", "knobs-hy-tl");
|
|
18630
|
+
tip.append(tipTitle, tipDate, tipLine);
|
|
18631
|
+
root.append(chart, foot, detail, tip);
|
|
18632
|
+
container.append(root);
|
|
18633
|
+
let model = normalizeHistory(options.data);
|
|
18634
|
+
let selected = options.selected && model.items.has(options.selected) ? options.selected : null;
|
|
18635
|
+
let disposed = false;
|
|
18636
|
+
let quiet = quietEvents(model.events, options.marks ?? DEFAULT_MARKS);
|
|
18637
|
+
let showAll = false;
|
|
18638
|
+
const walked = () => showAll ? model.events : model.events.filter((e) => !quiet.has(e.id));
|
|
18639
|
+
let nodes = new Map;
|
|
18640
|
+
let tracks = new Map;
|
|
18641
|
+
let over = null;
|
|
18642
|
+
let hoverLine = null;
|
|
18643
|
+
let selLine = null;
|
|
18644
|
+
let scale = dayScale(model.from, model.to, 1);
|
|
18645
|
+
let drawnWidth = -1;
|
|
18646
|
+
let hovered = null;
|
|
18647
|
+
let tipKey = "";
|
|
18648
|
+
const fontOf = (n) => {
|
|
18649
|
+
const c = win.getComputedStyle(n);
|
|
18650
|
+
return `${c.fontStyle} ${c.fontWeight} ${c.fontSize} ${c.fontFamily}`;
|
|
18651
|
+
};
|
|
18652
|
+
let measureCtx;
|
|
18653
|
+
const measure = (s, font) => {
|
|
18654
|
+
if (measureCtx === undefined)
|
|
18655
|
+
measureCtx = doc.createElement("canvas").getContext("2d");
|
|
18656
|
+
if (!measureCtx)
|
|
18657
|
+
return s.length * 6.4;
|
|
18658
|
+
measureCtx.font = font;
|
|
18659
|
+
return measureCtx.measureText(s).width;
|
|
18660
|
+
};
|
|
18661
|
+
function drawFoot() {
|
|
18662
|
+
foot.textContent = "";
|
|
18663
|
+
root.toggleAttribute("data-all", showAll);
|
|
18664
|
+
if (quiet.size) {
|
|
18665
|
+
const t = el(doc, "button", "knobs-hy-more");
|
|
18666
|
+
t.type = "button";
|
|
18667
|
+
t.setAttribute("aria-pressed", String(showAll));
|
|
18668
|
+
t.textContent = `${showAll ? "Hide" : "Show"} ${quietLabel(model.events, quiet)}`;
|
|
18669
|
+
t.addEventListener("click", () => {
|
|
18670
|
+
showAll = !showAll;
|
|
18671
|
+
if (!showAll && selected && quiet.has(selected))
|
|
18672
|
+
select(null, true, true);
|
|
18673
|
+
drawFoot();
|
|
18674
|
+
});
|
|
18675
|
+
foot.append(t);
|
|
18676
|
+
}
|
|
18677
|
+
if (model.inferred && model.verified) {
|
|
18678
|
+
const legend = el(doc, "span", "knobs-hy-legend");
|
|
18679
|
+
for (const [label, inferred] of [
|
|
18680
|
+
["Verified", false],
|
|
18681
|
+
["Inferred", true]
|
|
18682
|
+
]) {
|
|
18683
|
+
const k = el(doc, "span", "knobs-hy-key");
|
|
18684
|
+
const sw = el(doc, "span", "knobs-hy-swatch");
|
|
18685
|
+
sw.dataset.tone = "dim";
|
|
18686
|
+
if (inferred)
|
|
18687
|
+
sw.dataset.inferred = "";
|
|
18688
|
+
k.append(sw, text(doc, "span", "", label));
|
|
18689
|
+
legend.append(k);
|
|
18690
|
+
}
|
|
18691
|
+
foot.append(legend);
|
|
18692
|
+
}
|
|
18693
|
+
foot.hidden = !foot.childElementCount;
|
|
18694
|
+
}
|
|
18695
|
+
function draw() {
|
|
18696
|
+
const width = root.clientWidth;
|
|
18697
|
+
drawnWidth = width;
|
|
18698
|
+
const narrow = width > 0 && width < (options.narrow ?? 560);
|
|
18699
|
+
root.toggleAttribute("data-narrow", narrow);
|
|
18700
|
+
root.toggleAttribute("data-bands", model.bands.length > 0);
|
|
18701
|
+
hideHover();
|
|
18702
|
+
chart.textContent = "";
|
|
18703
|
+
nodes = new Map;
|
|
18704
|
+
tracks = new Map;
|
|
18705
|
+
let gutter = 0;
|
|
18706
|
+
if (!narrow) {
|
|
18707
|
+
const probe = el(doc, "div", "knobs-hy-label");
|
|
18708
|
+
chart.append(probe);
|
|
18709
|
+
const font = fontOf(probe);
|
|
18710
|
+
const gap = parseFloat(win.getComputedStyle(probe).marginRight) || 0;
|
|
18711
|
+
probe.remove();
|
|
18712
|
+
const longest = Math.max(0, ...model.lanes.map((l) => measure(l.label, font)));
|
|
18713
|
+
gutter = Math.round(Math.min(Math.max(longest + gap + 2, 64), Math.max(96, width * 0.3), 200));
|
|
18714
|
+
}
|
|
18715
|
+
root.style.setProperty("--hy-gutter", `${gutter}px`);
|
|
18716
|
+
const plotW = Math.max(1, width - gutter);
|
|
18717
|
+
scale = dayScale(model.from, model.to, plotW);
|
|
18718
|
+
const pct = (d) => `${(scale.f(d) * 100).toFixed(4)}%`;
|
|
18719
|
+
const rows = ["auto"];
|
|
18720
|
+
for (let i = 0;i < model.lanes.length; i++)
|
|
18721
|
+
rows.push(...narrow ? ["auto", "var(--hy-lane)"] : ["var(--hy-lane)"]);
|
|
18722
|
+
chart.style.gridTemplateRows = rows.join(" ");
|
|
18723
|
+
const behind = el(doc, "div", "knobs-hy-layer");
|
|
18724
|
+
behind.setAttribute("aria-hidden", "true");
|
|
18725
|
+
for (const b of model.bands) {
|
|
18726
|
+
const c = clipSpan(b.from, b.to, model.from, model.to);
|
|
18727
|
+
if (!c)
|
|
18728
|
+
continue;
|
|
18729
|
+
const band = el(doc, "div", "knobs-hy-band");
|
|
18730
|
+
band.style.left = pct(c.from);
|
|
18731
|
+
band.style.width = pct(model.from + (c.to - c.from));
|
|
18732
|
+
behind.append(band);
|
|
18733
|
+
}
|
|
18734
|
+
const ticks = monthTicks(model.from, model.to, plotW);
|
|
18735
|
+
for (const t of ticks) {
|
|
18736
|
+
const line = el(doc, "div", "knobs-hy-grid");
|
|
18737
|
+
line.style.left = pct(t.day);
|
|
18738
|
+
behind.append(line);
|
|
18739
|
+
}
|
|
18740
|
+
chart.append(behind);
|
|
18741
|
+
const axis = el(doc, "div", "knobs-hy-axis");
|
|
18742
|
+
axis.setAttribute("aria-hidden", "true");
|
|
18743
|
+
const tickEls = ticks.map((t) => {
|
|
18744
|
+
const n = text(doc, "span", "knobs-hy-tick", t.label);
|
|
18745
|
+
n.style.left = pct(t.day);
|
|
18746
|
+
axis.append(n);
|
|
18747
|
+
return n;
|
|
18748
|
+
});
|
|
18749
|
+
const showToday = model.today >= model.from && model.today <= model.to;
|
|
18750
|
+
const now = showToday ? text(doc, "span", "knobs-hy-now", "Today") : null;
|
|
18751
|
+
if (now)
|
|
18752
|
+
axis.append(now);
|
|
18753
|
+
const room = bandRoom(model.bands, scale.x);
|
|
18754
|
+
const bandEls = model.bands.map((b, i) => {
|
|
18755
|
+
const n = text(doc, "span", "knobs-hy-blabel", b.label);
|
|
18756
|
+
n.style.left = pct(Math.max(b.from, model.from));
|
|
18757
|
+
n.style.maxWidth = `${Math.max(0, room[i])}px`;
|
|
18758
|
+
axis.append(n);
|
|
18759
|
+
return n;
|
|
18760
|
+
});
|
|
18761
|
+
chart.append(axis);
|
|
18762
|
+
model.lanes.forEach((lane, i) => {
|
|
18763
|
+
const row = narrow ? 2 + i * 2 : 2 + i;
|
|
18764
|
+
const label = text(doc, "div", "knobs-hy-label", lane.label);
|
|
18765
|
+
label.style.gridRow = String(row);
|
|
18766
|
+
label.title = lane.label;
|
|
18767
|
+
const track = el(doc, "div", "knobs-hy-track");
|
|
18768
|
+
track.style.gridRow = String(narrow ? row + 1 : row);
|
|
18769
|
+
track.dataset.lane = lane.id;
|
|
18770
|
+
tracks.set(lane.id, track);
|
|
18771
|
+
for (const s of lane.spans) {
|
|
18772
|
+
const c = clipSpan(s.from, s.to, model.from, model.to);
|
|
18773
|
+
if (!c)
|
|
18774
|
+
continue;
|
|
18775
|
+
const b = el(doc, "button", "knobs-hy-span");
|
|
18776
|
+
b.type = "button";
|
|
18777
|
+
b.tabIndex = -1;
|
|
18778
|
+
b.dataset.id = s.id;
|
|
18779
|
+
b.dataset.tone = s.tone;
|
|
18780
|
+
if (s.inferred)
|
|
18781
|
+
b.dataset.inferred = "";
|
|
18782
|
+
if (c.clipStart)
|
|
18783
|
+
b.dataset.clipStart = "";
|
|
18784
|
+
if (c.clipEnd)
|
|
18785
|
+
b.dataset.clipEnd = "";
|
|
18786
|
+
b.style.left = pct(c.from);
|
|
18787
|
+
b.style.width = pct(model.from + (c.to - c.from));
|
|
18788
|
+
b.setAttribute("aria-label", `${s.title}, ${when(s)}`);
|
|
18789
|
+
if (s.tags.length)
|
|
18790
|
+
b.append(text(doc, "span", "knobs-hy-tag", s.tags[0]));
|
|
18791
|
+
track.append(b);
|
|
18792
|
+
nodes.set(s.id, b);
|
|
18793
|
+
}
|
|
18794
|
+
for (const e of lane.events) {
|
|
18795
|
+
if (e.to < model.from || e.from > model.to)
|
|
18796
|
+
continue;
|
|
18797
|
+
const b = el(doc, "button", "knobs-hy-ev");
|
|
18798
|
+
b.type = "button";
|
|
18799
|
+
b.tabIndex = -1;
|
|
18800
|
+
b.dataset.id = e.id;
|
|
18801
|
+
b.dataset.tone = e.tone;
|
|
18802
|
+
if (e.type)
|
|
18803
|
+
b.dataset.type = e.type;
|
|
18804
|
+
if (e.inferred)
|
|
18805
|
+
b.dataset.inferred = "";
|
|
18806
|
+
if (quiet.has(e.id))
|
|
18807
|
+
b.dataset.quiet = "";
|
|
18808
|
+
b.style.left = pct(Math.max(e.from, model.from));
|
|
18809
|
+
if (e.to > e.from) {
|
|
18810
|
+
b.dataset.range = "";
|
|
18811
|
+
b.style.setProperty("--hy-w", `${scale.x(model.from + (e.to - e.from)).toFixed(2)}px`);
|
|
18812
|
+
}
|
|
18813
|
+
b.setAttribute("aria-label", `${when(e)}: ${e.title}`);
|
|
18814
|
+
track.append(b);
|
|
18815
|
+
nodes.set(e.id, b);
|
|
18816
|
+
}
|
|
18817
|
+
chart.append(label, track);
|
|
18818
|
+
});
|
|
18819
|
+
over = el(doc, "div", "knobs-hy-layer");
|
|
18820
|
+
over.setAttribute("aria-hidden", "true");
|
|
18821
|
+
if (showToday) {
|
|
18822
|
+
const today = el(doc, "div", "knobs-hy-today");
|
|
18823
|
+
today.style.left = pct(model.today);
|
|
18824
|
+
over.append(today);
|
|
18825
|
+
}
|
|
18826
|
+
selLine = el(doc, "div", "knobs-hy-sel");
|
|
18827
|
+
hoverLine = el(doc, "div", "knobs-hy-hover");
|
|
18828
|
+
over.append(selLine, hoverLine);
|
|
18829
|
+
chart.append(over);
|
|
18830
|
+
const reserved = [];
|
|
18831
|
+
if (now) {
|
|
18832
|
+
const w = now.offsetWidth;
|
|
18833
|
+
const x0 = Math.max(0, Math.min(plotW - w, scale.x(model.today) - w / 2));
|
|
18834
|
+
now.style.left = `${x0}px`;
|
|
18835
|
+
reserved.push({ x0, x1: x0 + w });
|
|
18836
|
+
}
|
|
18837
|
+
const fits = fitLabels(ticks.map((t, i) => ({ x: scale.x(t.day), w: tickEls[i].offsetWidth })), plotW, reserved);
|
|
18838
|
+
tickEls.forEach((n, i) => n.hidden = !fits[i]);
|
|
18839
|
+
for (const n of bandEls)
|
|
18840
|
+
n.toggleAttribute("data-overflow", n.scrollWidth > n.clientWidth + 1);
|
|
18841
|
+
for (const n of chart.querySelectorAll(".knobs-hy-label"))
|
|
18842
|
+
n.toggleAttribute("data-overflow", n.scrollWidth > n.clientWidth + 1);
|
|
18843
|
+
for (const lane of model.lanes) {
|
|
18844
|
+
const spans = lane.spans.map((s) => ({ s, c: clipSpan(s.from, s.to, model.from, model.to) })).filter((x) => x.c).sort((a, b) => a.c.from - b.c.from);
|
|
18845
|
+
spans.forEach(({ s, c }, i) => {
|
|
18846
|
+
const b = nodes.get(s.id);
|
|
18847
|
+
const tag = b?.querySelector(".knobs-hy-tag");
|
|
18848
|
+
if (!b || !tag)
|
|
18849
|
+
return;
|
|
18850
|
+
const font = fontOf(tag);
|
|
18851
|
+
const widths = s.tags.map((t) => measure(t, font));
|
|
18852
|
+
const next = spans[i + 1]?.c;
|
|
18853
|
+
const spanW = scale.x(c.to) - scale.x(c.from);
|
|
18854
|
+
const after = (next ? scale.x(next.from) : plotW) - scale.x(c.to);
|
|
18855
|
+
const fit = fitTag(widths, spanW, after);
|
|
18856
|
+
tag.textContent = fit ? s.tags[fit.index] : s.tags[0] ?? "";
|
|
18857
|
+
tag.dataset.place = fit ? fit.place : "none";
|
|
18858
|
+
});
|
|
18859
|
+
}
|
|
18860
|
+
paintSelection();
|
|
18861
|
+
}
|
|
18862
|
+
const h = new Spring(0, HEIGHT2.omega, HEIGHT2.zeta, 0.25);
|
|
18863
|
+
let goal = 0;
|
|
18864
|
+
let content = null;
|
|
18865
|
+
const loop = frameLoop(win, (dt) => {
|
|
18866
|
+
h.step(dt);
|
|
18867
|
+
const moving = !h.resting;
|
|
18868
|
+
detail.style.height = moving ? `${Math.max(0, h.x).toFixed(2)}px` : goal > 0 ? "" : "0px";
|
|
18869
|
+
detail.toggleAttribute("data-moving", moving);
|
|
18870
|
+
return moving;
|
|
18871
|
+
});
|
|
18872
|
+
function buildDetail(it) {
|
|
18873
|
+
const c = el(doc, "div", "knobs-hy-dc");
|
|
18874
|
+
const meta = el(doc, "div", "knobs-hy-dmeta");
|
|
18875
|
+
meta.append(text(doc, "time", "", when(it)));
|
|
18876
|
+
const lane = model.lanes.find((l) => l.id === it.lane);
|
|
18877
|
+
if (lane && (model.lanes.length > 1 || lane.id !== "code"))
|
|
18878
|
+
meta.append(text(doc, "span", "", lane.label));
|
|
18879
|
+
if (it.type)
|
|
18880
|
+
meta.append(text(doc, "span", "", typeName(it.type)));
|
|
18881
|
+
if (it.provenance || it.source) {
|
|
18882
|
+
const p = text(doc, "span", "", [it.provenance, it.source].filter(Boolean).join(" · "));
|
|
18883
|
+
if (it.inferred) {
|
|
18884
|
+
p.dataset.tone = "warn";
|
|
18885
|
+
p.title = "Worked out from behaviour, not seen in the record";
|
|
18886
|
+
}
|
|
18887
|
+
meta.append(p);
|
|
18888
|
+
}
|
|
18889
|
+
c.append(meta, text(doc, "div", "knobs-hy-dtitle", it.title));
|
|
18890
|
+
if (it.detail) {
|
|
18891
|
+
const p = el(doc, "p", "knobs-hy-dtext");
|
|
18892
|
+
p.innerHTML = inlineMarkdown(it.detail);
|
|
18893
|
+
c.append(p);
|
|
18894
|
+
}
|
|
18895
|
+
const foot = el(doc, "div", "knobs-hy-dfoot");
|
|
18896
|
+
for (const e of it.evidence) {
|
|
18897
|
+
const tag = text(doc, e.href ? "a" : "span", "knobs-hy-evidence", e.label);
|
|
18898
|
+
if (tag instanceof HTMLAnchorElement && e.href) {
|
|
18899
|
+
tag.href = e.href;
|
|
18900
|
+
tag.target = "_blank";
|
|
18901
|
+
tag.rel = "noreferrer";
|
|
18902
|
+
}
|
|
18903
|
+
foot.append(tag);
|
|
18904
|
+
}
|
|
18905
|
+
if (it.step && options.onJump) {
|
|
18906
|
+
const go = el(doc, "button", "knobs-hy-jump");
|
|
18907
|
+
go.type = "button";
|
|
18908
|
+
go.append(text(doc, "span", "", "Go to step"));
|
|
18909
|
+
go.insertAdjacentHTML("beforeend", GO);
|
|
18910
|
+
const step = it.step;
|
|
18911
|
+
go.addEventListener("click", () => options.onJump?.(step));
|
|
18912
|
+
foot.append(go);
|
|
18913
|
+
}
|
|
18914
|
+
c.append(foot);
|
|
18915
|
+
return c;
|
|
18916
|
+
}
|
|
18917
|
+
function showDetail(it, animate) {
|
|
18918
|
+
const from = detail.offsetHeight;
|
|
18919
|
+
const old = content;
|
|
18920
|
+
const next = it ? buildDetail(it) : null;
|
|
18921
|
+
if (old) {
|
|
18922
|
+
if (animate) {
|
|
18923
|
+
old.dataset.leave = "";
|
|
18924
|
+
const drop = () => old.remove();
|
|
18925
|
+
old.addEventListener("transitionend", drop, { once: true });
|
|
18926
|
+
win.setTimeout(drop, 400);
|
|
18927
|
+
} else
|
|
18928
|
+
old.remove();
|
|
18929
|
+
}
|
|
18930
|
+
content = next;
|
|
18931
|
+
if (next) {
|
|
18932
|
+
detail.append(next);
|
|
18933
|
+
if (animate) {
|
|
18934
|
+
next.dataset.enter = "";
|
|
18935
|
+
next.offsetHeight;
|
|
18936
|
+
delete next.dataset.enter;
|
|
18937
|
+
}
|
|
18938
|
+
}
|
|
18939
|
+
goal = next ? next.offsetHeight : 0;
|
|
18940
|
+
if (!animate || reducedMotion(win)) {
|
|
18941
|
+
loop.stop();
|
|
18942
|
+
h.snap(goal);
|
|
18943
|
+
detail.style.height = goal > 0 ? "" : "0px";
|
|
18944
|
+
detail.removeAttribute("data-moving");
|
|
18945
|
+
return;
|
|
18946
|
+
}
|
|
18947
|
+
if (h.resting)
|
|
18948
|
+
h.snap(from);
|
|
18949
|
+
h.set(goal);
|
|
18950
|
+
loop.wake();
|
|
18951
|
+
}
|
|
18952
|
+
function paintSelection() {
|
|
18953
|
+
for (const [id, n] of nodes)
|
|
18954
|
+
n.toggleAttribute("data-selected", id === selected);
|
|
18955
|
+
const it = selected ? model.items.get(selected) : undefined;
|
|
18956
|
+
if (selLine) {
|
|
18957
|
+
const on = it?.kind === "event";
|
|
18958
|
+
selLine.toggleAttribute("data-on", on);
|
|
18959
|
+
if (on)
|
|
18960
|
+
selLine.style.left = `${(scale.f(it.from) * 100).toFixed(4)}%`;
|
|
18961
|
+
}
|
|
18962
|
+
}
|
|
18963
|
+
function select(id, animate, notify) {
|
|
18964
|
+
const next = id && model.items.has(id) ? id : null;
|
|
18965
|
+
if (next === selected)
|
|
18966
|
+
return;
|
|
18967
|
+
selected = next;
|
|
18968
|
+
paintSelection();
|
|
18969
|
+
showDetail(next ? model.items.get(next) : null, animate);
|
|
18970
|
+
if (notify)
|
|
18971
|
+
options.onSelect?.(next);
|
|
18972
|
+
}
|
|
18973
|
+
function itemAt(e) {
|
|
18974
|
+
if (!over)
|
|
18975
|
+
return null;
|
|
18976
|
+
const r = over.getBoundingClientRect();
|
|
18977
|
+
if (!r.width)
|
|
18978
|
+
return null;
|
|
18979
|
+
const x = Math.max(0, Math.min(r.width, e.clientX - r.left));
|
|
18980
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
18981
|
+
const hit = target?.closest("[data-id]");
|
|
18982
|
+
if (hit && chart.contains(hit))
|
|
18983
|
+
return { it: model.items.get(hit.dataset.id) ?? null, x };
|
|
18984
|
+
const laneId = target?.closest(".knobs-hy-track")?.dataset.lane;
|
|
18985
|
+
const lane = laneId ? model.lanes.find((l) => l.id === laneId) : undefined;
|
|
18986
|
+
if (lane) {
|
|
18987
|
+
const reach = REACH / r.width * (model.to - model.from);
|
|
18988
|
+
const near = nearestEvent(lane.events, scale.day(x), reach);
|
|
18989
|
+
if (near)
|
|
18990
|
+
return { it: near, x };
|
|
18991
|
+
}
|
|
18992
|
+
return { it: null, x };
|
|
18993
|
+
}
|
|
18994
|
+
function hideHover() {
|
|
18995
|
+
hoverLine?.removeAttribute("data-on");
|
|
18996
|
+
tip.removeAttribute("data-on");
|
|
18997
|
+
hovered?.removeAttribute("data-hover");
|
|
18998
|
+
hovered = null;
|
|
18999
|
+
tipKey = "";
|
|
19000
|
+
}
|
|
19001
|
+
function hover(e) {
|
|
19002
|
+
const at = itemAt(e);
|
|
19003
|
+
if (!at || !over || !hoverLine)
|
|
19004
|
+
return hideHover();
|
|
19005
|
+
const { it, x } = at;
|
|
19006
|
+
const lineX = it?.kind === "event" ? scale.x(it.from) : x;
|
|
19007
|
+
hoverLine.style.left = `${lineX.toFixed(2)}px`;
|
|
19008
|
+
hoverLine.setAttribute("data-on", "");
|
|
19009
|
+
const node = it ? nodes.get(it.id) ?? null : null;
|
|
19010
|
+
if (node !== hovered) {
|
|
19011
|
+
hovered?.removeAttribute("data-hover");
|
|
19012
|
+
node?.setAttribute("data-hover", "");
|
|
19013
|
+
hovered = node;
|
|
19014
|
+
}
|
|
19015
|
+
const key = it ? it.id : `d${Math.round(scale.day(x))}`;
|
|
19016
|
+
if (key !== tipKey) {
|
|
19017
|
+
tipKey = key;
|
|
19018
|
+
tipTitle.textContent = it ? it.title : formatDay(Math.round(scale.day(x)));
|
|
19019
|
+
tipDate.textContent = it ? [when(it), it.type ? typeName(it.type) : ""].filter(Boolean).join(" · ") : "";
|
|
19020
|
+
tipDate.hidden = !it;
|
|
19021
|
+
tipLine.textContent = it ? firstSentence(it.detail) : "";
|
|
19022
|
+
}
|
|
19023
|
+
const rr = root.getBoundingClientRect();
|
|
19024
|
+
const or = over.getBoundingClientRect();
|
|
19025
|
+
const laneId = it?.lane ?? (e.target instanceof Element ? e.target.closest(".knobs-hy-track")?.dataset.lane : undefined);
|
|
19026
|
+
const lr = laneId && tracks.get(laneId)?.getBoundingClientRect() || or;
|
|
19027
|
+
const tw = tip.offsetWidth;
|
|
19028
|
+
const th = tip.offsetHeight;
|
|
19029
|
+
const left = Math.max(0, Math.min(rr.width - tw, or.left - rr.left + lineX - tw / 2));
|
|
19030
|
+
let top = lr.top - rr.top - th - 6;
|
|
19031
|
+
if (top + rr.top < 0)
|
|
19032
|
+
top = lr.bottom - rr.top + 6;
|
|
19033
|
+
tip.style.transform = `translate3d(${Math.round(left)}px,${Math.round(top)}px,0)`;
|
|
19034
|
+
tip.setAttribute("data-on", "");
|
|
19035
|
+
}
|
|
19036
|
+
chart.addEventListener("pointermove", (e) => {
|
|
19037
|
+
if (e.pointerType === "touch")
|
|
19038
|
+
return;
|
|
19039
|
+
hover(e);
|
|
19040
|
+
});
|
|
19041
|
+
chart.addEventListener("pointerleave", hideHover);
|
|
19042
|
+
chart.addEventListener("mousedown", (e) => {
|
|
19043
|
+
if (!(e.target instanceof Element) || !e.target.closest("button"))
|
|
19044
|
+
return;
|
|
19045
|
+
e.preventDefault();
|
|
19046
|
+
root.focus({ preventScroll: true });
|
|
19047
|
+
});
|
|
19048
|
+
chart.addEventListener("click", (e) => {
|
|
19049
|
+
const at = itemAt(e);
|
|
19050
|
+
const id = at?.it?.id ?? null;
|
|
19051
|
+
select(id !== null && id === selected ? null : id, true, true);
|
|
19052
|
+
});
|
|
19053
|
+
root.addEventListener("keydown", (e) => {
|
|
19054
|
+
const onChart = e.target instanceof Node && chart.contains(e.target);
|
|
19055
|
+
if (e.target !== root && !onChart || e.metaKey || e.ctrlKey || e.altKey)
|
|
19056
|
+
return;
|
|
19057
|
+
let next;
|
|
19058
|
+
const order = walked();
|
|
19059
|
+
const at = selected ? model.items.get(selected)?.from : undefined;
|
|
19060
|
+
if (e.key === "ArrowRight")
|
|
19061
|
+
next = stepEvent(order, selected, 1, at);
|
|
19062
|
+
else if (e.key === "ArrowLeft")
|
|
19063
|
+
next = stepEvent(order, selected, -1, at);
|
|
19064
|
+
else if (e.key === "Home")
|
|
19065
|
+
next = order[0] ?? null;
|
|
19066
|
+
else if (e.key === "End")
|
|
19067
|
+
next = order[order.length - 1] ?? null;
|
|
19068
|
+
else if (e.key === "Escape" && selected)
|
|
19069
|
+
next = null;
|
|
19070
|
+
else if (e.key === "Enter" && selected) {
|
|
19071
|
+
const step = model.items.get(selected)?.step;
|
|
19072
|
+
if (!step || !options.onJump)
|
|
19073
|
+
return;
|
|
19074
|
+
options.onJump(step);
|
|
19075
|
+
e.preventDefault();
|
|
19076
|
+
e.stopPropagation();
|
|
19077
|
+
return;
|
|
19078
|
+
} else
|
|
19079
|
+
return;
|
|
19080
|
+
e.preventDefault();
|
|
19081
|
+
e.stopPropagation();
|
|
19082
|
+
hideHover();
|
|
19083
|
+
select(next?.id ?? null, true, true);
|
|
19084
|
+
});
|
|
19085
|
+
let raf = 0;
|
|
19086
|
+
const ro = new ResizeObserver(() => {
|
|
19087
|
+
if (raf || disposed)
|
|
19088
|
+
return;
|
|
19089
|
+
raf = win.requestAnimationFrame(() => {
|
|
19090
|
+
raf = 0;
|
|
19091
|
+
if (!disposed && root.clientWidth !== drawnWidth)
|
|
19092
|
+
draw();
|
|
19093
|
+
});
|
|
19094
|
+
});
|
|
19095
|
+
ro.observe(root);
|
|
19096
|
+
const restyle = () => draw();
|
|
19097
|
+
root.addEventListener("knobs-restyle", restyle);
|
|
19098
|
+
drawFoot();
|
|
19099
|
+
draw();
|
|
19100
|
+
detail.style.height = "0px";
|
|
19101
|
+
if (selected)
|
|
19102
|
+
showDetail(model.items.get(selected), false);
|
|
19103
|
+
const dispose = () => {
|
|
19104
|
+
if (disposed)
|
|
19105
|
+
return;
|
|
19106
|
+
disposed = true;
|
|
19107
|
+
ro.disconnect();
|
|
19108
|
+
if (raf)
|
|
19109
|
+
win.cancelAnimationFrame(raf);
|
|
19110
|
+
loop.stop();
|
|
19111
|
+
root.removeEventListener("knobs-restyle", restyle);
|
|
19112
|
+
root.remove();
|
|
19113
|
+
};
|
|
19114
|
+
return {
|
|
19115
|
+
element: root,
|
|
19116
|
+
get model() {
|
|
19117
|
+
return model;
|
|
19118
|
+
},
|
|
19119
|
+
get selected() {
|
|
19120
|
+
return selected;
|
|
19121
|
+
},
|
|
19122
|
+
select(id) {
|
|
19123
|
+
select(id, true, false);
|
|
19124
|
+
},
|
|
19125
|
+
set(data) {
|
|
19126
|
+
model = normalizeHistory(data);
|
|
19127
|
+
const keep = selected && model.items.has(selected) ? selected : null;
|
|
19128
|
+
selected = keep;
|
|
19129
|
+
quiet = quietEvents(model.events, options.marks ?? DEFAULT_MARKS);
|
|
19130
|
+
drawFoot();
|
|
19131
|
+
draw();
|
|
19132
|
+
showDetail(keep ? model.items.get(keep) : null, false);
|
|
19133
|
+
},
|
|
19134
|
+
focus() {
|
|
19135
|
+
root.focus();
|
|
19136
|
+
},
|
|
19137
|
+
dispose,
|
|
19138
|
+
destroy: dispose
|
|
19139
|
+
};
|
|
19140
|
+
}
|
|
19141
|
+
|
|
19142
|
+
// src/ui/walkthrough-blocks.ts
|
|
19143
|
+
init_icons();
|
|
19144
|
+
init_look();
|
|
19145
|
+
init_selection_pill();
|
|
19146
|
+
var GLYPH = {
|
|
19147
|
+
caret: icon("caretRight", { small: true })
|
|
19148
|
+
};
|
|
19149
|
+
var el2 = (doc, tag, cls) => {
|
|
19150
|
+
const n = doc.createElement(tag);
|
|
19151
|
+
if (cls)
|
|
19152
|
+
n.className = cls;
|
|
19153
|
+
return n;
|
|
19154
|
+
};
|
|
19155
|
+
var text2 = (doc, tag, cls, value) => {
|
|
19156
|
+
const n = el2(doc, tag, cls);
|
|
19157
|
+
n.textContent = value;
|
|
19158
|
+
return n;
|
|
19159
|
+
};
|
|
17664
19160
|
var prose = (doc, tag, cls, value) => {
|
|
17665
|
-
const n =
|
|
19161
|
+
const n = el2(doc, tag, cls);
|
|
17666
19162
|
n.innerHTML = inlineMarkdown(value);
|
|
17667
19163
|
return n;
|
|
17668
19164
|
};
|
|
17669
19165
|
var link = (doc, cls, label, href) => {
|
|
17670
19166
|
if (!href)
|
|
17671
|
-
return
|
|
17672
|
-
const a =
|
|
19167
|
+
return text2(doc, "span", cls, label);
|
|
19168
|
+
const a = text2(doc, "a", cls, label);
|
|
17673
19169
|
a.href = href;
|
|
17674
19170
|
a.target = "_blank";
|
|
17675
19171
|
a.rel = "noreferrer";
|
|
17676
19172
|
return a;
|
|
17677
19173
|
};
|
|
17678
|
-
function
|
|
19174
|
+
function rowLink(row, ref, steps) {
|
|
17679
19175
|
const s = ref && steps?.find(ref);
|
|
17680
19176
|
if (!s || !steps)
|
|
17681
|
-
return
|
|
17682
|
-
const
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
19177
|
+
return;
|
|
19178
|
+
const doc = row.ownerDocument;
|
|
19179
|
+
row.dataset.go = "";
|
|
19180
|
+
row.tabIndex = 0;
|
|
19181
|
+
row.title = `Go to “${plainMarkdown(s.title)}”`;
|
|
19182
|
+
row.addEventListener("click", (e) => {
|
|
19183
|
+
if (e.target?.closest("a,button"))
|
|
19184
|
+
return;
|
|
19185
|
+
if (doc.getSelection()?.toString())
|
|
19186
|
+
return;
|
|
19187
|
+
steps.go(s.key);
|
|
19188
|
+
});
|
|
19189
|
+
row.addEventListener("keydown", (e) => {
|
|
19190
|
+
if (e.target !== row || e.key !== "Enter" && e.key !== " ")
|
|
19191
|
+
return;
|
|
19192
|
+
e.preventDefault();
|
|
17689
19193
|
steps.go(s.key);
|
|
17690
19194
|
});
|
|
17691
|
-
return b;
|
|
17692
19195
|
}
|
|
17693
|
-
var tags = (doc, list) => {
|
|
17694
|
-
if (!list?.length)
|
|
17695
|
-
return null;
|
|
17696
|
-
const row = el(doc, "div", "knobs-wt-tags");
|
|
17697
|
-
for (const e of list)
|
|
17698
|
-
row.append(link(doc, "knobs-wt-tag", e.label, e.href));
|
|
17699
|
-
return row;
|
|
17700
|
-
};
|
|
17701
19196
|
function mountHistory(host, m, steps) {
|
|
17702
|
-
const
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
|
|
17709
|
-
|
|
17710
|
-
|
|
17711
|
-
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
|
|
17716
|
-
|
|
17717
|
-
|
|
17718
|
-
list.append(historyEvent(doc, e, steps));
|
|
17719
|
-
group.append(list);
|
|
17720
|
-
root.append(group);
|
|
17721
|
-
}
|
|
17722
|
-
host.append(root);
|
|
17723
|
-
return { dispose: () => root.remove(), context: () => ({ kind: "history" }) };
|
|
17724
|
-
}
|
|
17725
|
-
function historyEvent(doc, e, steps) {
|
|
17726
|
-
const li = el(doc, "li", "knobs-wt-hev");
|
|
17727
|
-
if (e.tone)
|
|
17728
|
-
li.dataset.tone = e.tone;
|
|
17729
|
-
if (e.type)
|
|
17730
|
-
li.dataset.type = e.type;
|
|
17731
|
-
if (e.provenance)
|
|
17732
|
-
li.dataset.provenance = e.provenance;
|
|
17733
|
-
const when = el(doc, "div", "knobs-wt-hwhen");
|
|
17734
|
-
when.append(text(doc, "time", "knobs-wt-hdate", e.date));
|
|
17735
|
-
if (e.age)
|
|
17736
|
-
when.append(text(doc, "span", "knobs-wt-hage", e.age));
|
|
17737
|
-
const spine = el(doc, "span", "knobs-wt-hspine");
|
|
17738
|
-
spine.setAttribute("aria-hidden", "true");
|
|
17739
|
-
spine.append(el(doc, "span", "knobs-wt-hdot"));
|
|
17740
|
-
const main = el(doc, "div", "knobs-wt-hmain");
|
|
17741
|
-
const title = el(doc, "div", "knobs-wt-htitle");
|
|
17742
|
-
title.append(text(doc, "span", "knobs-wt-htext", e.title));
|
|
17743
|
-
if (e.provenance || e.source) {
|
|
17744
|
-
const p = text(doc, "span", "knobs-wt-prov", [e.provenance, e.source].filter(Boolean).join(" · "));
|
|
17745
|
-
if (e.provenance === "inferred")
|
|
17746
|
-
p.title = "Inferred from behaviour, not seen in the record";
|
|
17747
|
-
else if (e.provenance === "verified")
|
|
17748
|
-
p.title = "Seen in the record";
|
|
17749
|
-
title.append(p);
|
|
17750
|
-
}
|
|
17751
|
-
const go = stepButton(doc, e.step, steps);
|
|
17752
|
-
if (go)
|
|
17753
|
-
title.append(go);
|
|
17754
|
-
main.append(title);
|
|
17755
|
-
if (e.detail)
|
|
17756
|
-
main.append(prose(doc, "p", "knobs-wt-hdetail", e.detail));
|
|
17757
|
-
const t = tags(doc, e.evidence);
|
|
17758
|
-
if (t)
|
|
17759
|
-
main.append(t);
|
|
17760
|
-
li.append(when, spine, main);
|
|
17761
|
-
return li;
|
|
19197
|
+
const h = history(host, {
|
|
19198
|
+
data: m.data,
|
|
19199
|
+
...m.label ? { label: m.label } : {},
|
|
19200
|
+
...m.marks ? { marks: m.marks } : {},
|
|
19201
|
+
...steps ? {
|
|
19202
|
+
onJump: (ref) => {
|
|
19203
|
+
const s = steps.find(ref);
|
|
19204
|
+
if (s)
|
|
19205
|
+
steps.go(s.key);
|
|
19206
|
+
}
|
|
19207
|
+
} : {}
|
|
19208
|
+
});
|
|
19209
|
+
return {
|
|
19210
|
+
dispose: () => h.dispose(),
|
|
19211
|
+
context: () => ({ kind: "history" })
|
|
19212
|
+
};
|
|
17762
19213
|
}
|
|
17763
19214
|
function mountStack(host, m, steps) {
|
|
17764
19215
|
const doc = host.ownerDocument;
|
|
17765
19216
|
const layout = stackLayout(m);
|
|
17766
|
-
const root =
|
|
19217
|
+
const root = el2(doc, "div", "knobs-wt-stack");
|
|
17767
19218
|
root.toggleAttribute("data-flat", layout.flat);
|
|
17768
19219
|
const lane = (kind) => {
|
|
17769
|
-
const s =
|
|
19220
|
+
const s = el2(doc, "span", "knobs-wt-lane");
|
|
17770
19221
|
s.dataset.rail = kind;
|
|
17771
19222
|
return s;
|
|
17772
19223
|
};
|
|
@@ -17777,19 +19228,19 @@ function mountStack(host, m, steps) {
|
|
|
17777
19228
|
};
|
|
17778
19229
|
const from = layout.trunk ? 0 : 1;
|
|
17779
19230
|
if (layout.trunk && layout.rows.length) {
|
|
17780
|
-
const row =
|
|
19231
|
+
const row = el2(doc, "div", "knobs-wt-srow");
|
|
17781
19232
|
row.dataset.trunk = "";
|
|
17782
|
-
const lanes =
|
|
19233
|
+
const lanes = el2(doc, "span", "knobs-wt-lanes");
|
|
17783
19234
|
lanes.setAttribute("aria-hidden", "true");
|
|
17784
19235
|
lanes.append(node(true));
|
|
17785
|
-
row.append(lanes,
|
|
19236
|
+
row.append(lanes, text2(doc, "span", "knobs-wt-trunk", layout.trunk));
|
|
17786
19237
|
root.append(row);
|
|
17787
19238
|
}
|
|
17788
19239
|
for (const r of layout.rows) {
|
|
17789
19240
|
const it = r.item;
|
|
17790
|
-
const row =
|
|
19241
|
+
const row = el2(doc, "div", "knobs-wt-srow");
|
|
17791
19242
|
if (!layout.flat) {
|
|
17792
|
-
const lanes =
|
|
19243
|
+
const lanes = el2(doc, "span", "knobs-wt-lanes");
|
|
17793
19244
|
lanes.setAttribute("aria-hidden", "true");
|
|
17794
19245
|
for (let k = from;k < r.depth; k++)
|
|
17795
19246
|
lanes.append(lane(r.rails[k] ? "pass" : "none"));
|
|
@@ -17798,24 +19249,21 @@ function mountStack(host, m, steps) {
|
|
|
17798
19249
|
lanes.append(node(r.children > 0));
|
|
17799
19250
|
row.append(lanes);
|
|
17800
19251
|
}
|
|
17801
|
-
const main =
|
|
17802
|
-
const line =
|
|
19252
|
+
const main = el2(doc, "div", "knobs-wt-smain");
|
|
19253
|
+
const line = el2(doc, "div", "knobs-wt-sline");
|
|
17803
19254
|
if (it.order)
|
|
17804
|
-
line.append(
|
|
19255
|
+
line.append(text2(doc, "span", "knobs-wt-sord", it.order));
|
|
17805
19256
|
line.append(link(doc, "knobs-wt-spr", it.pr, it.href));
|
|
17806
19257
|
const target = it.step ? steps?.find(it.step) : null;
|
|
17807
19258
|
if (target && steps) {
|
|
17808
|
-
const title =
|
|
19259
|
+
const title = text2(doc, "button", "knobs-wt-stitle", it.title);
|
|
17809
19260
|
title.type = "button";
|
|
17810
|
-
title.title = `Go to “${target.title}”`;
|
|
19261
|
+
title.title = `Go to “${plainMarkdown(target.title)}”`;
|
|
17811
19262
|
title.addEventListener("click", (e) => {
|
|
17812
19263
|
e.stopPropagation();
|
|
17813
19264
|
steps.go(target.key);
|
|
17814
19265
|
});
|
|
17815
19266
|
line.append(title);
|
|
17816
|
-
const go = stepButton(doc, it.step, steps);
|
|
17817
|
-
if (go)
|
|
17818
|
-
line.append(go);
|
|
17819
19267
|
row.dataset.go = "";
|
|
17820
19268
|
row.addEventListener("click", (e) => {
|
|
17821
19269
|
const t = e.target;
|
|
@@ -17826,14 +19274,14 @@ function mountStack(host, m, steps) {
|
|
|
17826
19274
|
steps.go(target.key);
|
|
17827
19275
|
});
|
|
17828
19276
|
} else
|
|
17829
|
-
line.append(
|
|
19277
|
+
line.append(text2(doc, "span", "knobs-wt-stitle", it.title));
|
|
17830
19278
|
main.append(line);
|
|
17831
19279
|
if (it.summary)
|
|
17832
19280
|
main.append(prose(doc, "p", "knobs-wt-ssummary", it.summary));
|
|
17833
|
-
const meta =
|
|
19281
|
+
const meta = el2(doc, "div", "knobs-wt-smeta");
|
|
17834
19282
|
if (it.head) {
|
|
17835
19283
|
const branch = r.offBase ? `${it.head} → ${r.offBase}` : it.head;
|
|
17836
|
-
meta.append(
|
|
19284
|
+
meta.append(text2(doc, "span", "knobs-wt-sbranch", branch));
|
|
17837
19285
|
}
|
|
17838
19286
|
if (it.delta !== undefined)
|
|
17839
19287
|
meta.append(delta(doc, it.delta));
|
|
@@ -17841,7 +19289,7 @@ function mountStack(host, m, steps) {
|
|
|
17841
19289
|
main.append(meta);
|
|
17842
19290
|
row.append(main);
|
|
17843
19291
|
if (it.status) {
|
|
17844
|
-
const chip =
|
|
19292
|
+
const chip = text2(doc, "span", "knobs-wt-chip knobs-wt-sstatus", it.status);
|
|
17845
19293
|
chip.dataset.tone = it.tone ?? "dim";
|
|
17846
19294
|
row.append(chip);
|
|
17847
19295
|
}
|
|
@@ -17851,40 +19299,38 @@ function mountStack(host, m, steps) {
|
|
|
17851
19299
|
return { dispose: () => root.remove(), context: () => ({ kind: "stack" }) };
|
|
17852
19300
|
}
|
|
17853
19301
|
function delta(doc, d) {
|
|
17854
|
-
const s =
|
|
19302
|
+
const s = el2(doc, "span", "knobs-wt-delta");
|
|
17855
19303
|
if (typeof d === "string") {
|
|
17856
19304
|
s.textContent = d;
|
|
17857
19305
|
return s;
|
|
17858
19306
|
}
|
|
17859
19307
|
const n = (v) => v.toLocaleString("en-US");
|
|
17860
|
-
s.append(
|
|
17861
|
-
s.append(
|
|
19308
|
+
s.append(text2(doc, "span", "knobs-wt-add", `+${n(d.add)}`));
|
|
19309
|
+
s.append(text2(doc, "span", "knobs-wt-del", `−${n(d.del)}`));
|
|
17862
19310
|
s.setAttribute("aria-label", `${d.add} added, ${d.del} removed`);
|
|
17863
19311
|
return s;
|
|
17864
19312
|
}
|
|
17865
|
-
function cellContent(doc, into, cell
|
|
19313
|
+
function cellContent(doc, into, cell) {
|
|
17866
19314
|
let content;
|
|
17867
19315
|
if (cell.badge) {
|
|
17868
19316
|
content = link(doc, "knobs-wt-chip", cell.text, cell.href);
|
|
17869
19317
|
content.dataset.tone = cell.badge;
|
|
17870
19318
|
} else {
|
|
17871
|
-
const inner = cell.code ?
|
|
19319
|
+
const inner = cell.code ? text2(doc, "code", "", cell.text) : null;
|
|
17872
19320
|
if (cell.href) {
|
|
17873
19321
|
content = link(doc, "knobs-wt-clink", inner ? "" : cell.text, cell.href);
|
|
17874
19322
|
if (inner)
|
|
17875
19323
|
content.append(inner);
|
|
17876
19324
|
} else
|
|
17877
|
-
content = inner ??
|
|
19325
|
+
content = inner ?? text2(doc, "span", "", cell.text);
|
|
17878
19326
|
if (cell.tone) {
|
|
17879
19327
|
content.classList.add("knobs-wt-ctone");
|
|
17880
19328
|
content.dataset.tone = cell.tone;
|
|
17881
19329
|
}
|
|
17882
19330
|
}
|
|
17883
19331
|
into.append(content);
|
|
17884
|
-
const go = stepButton(doc, cell.step, steps);
|
|
17885
|
-
if (go)
|
|
17886
|
-
into.append(go);
|
|
17887
19332
|
}
|
|
19333
|
+
var rowStep = (r, columns) => columns.map((c) => r[c.key]?.step).find((step) => step !== undefined);
|
|
17888
19334
|
var cellAttrs = (td, c) => {
|
|
17889
19335
|
td.dataset.align = c.align;
|
|
17890
19336
|
if (c.mono)
|
|
@@ -17893,83 +19339,85 @@ var cellAttrs = (td, c) => {
|
|
|
17893
19339
|
function mountTable(host, m, steps) {
|
|
17894
19340
|
const doc = host.ownerDocument;
|
|
17895
19341
|
const win = doc.defaultView;
|
|
17896
|
-
const box =
|
|
17897
|
-
const scroller =
|
|
17898
|
-
const table =
|
|
19342
|
+
const box = el2(doc, "div", "knobs-wt-tablebox");
|
|
19343
|
+
const scroller = el2(doc, "div", "knobs-wt-table");
|
|
19344
|
+
const table = el2(doc, "table");
|
|
17899
19345
|
if (m.caption)
|
|
17900
|
-
table.append(
|
|
17901
|
-
const head =
|
|
17902
|
-
const hr =
|
|
19346
|
+
table.append(text2(doc, "caption", "", m.caption));
|
|
19347
|
+
const head = el2(doc, "thead");
|
|
19348
|
+
const hr = el2(doc, "tr");
|
|
17903
19349
|
for (const c of m.columns) {
|
|
17904
|
-
const th =
|
|
19350
|
+
const th = text2(doc, "th", "", c.label);
|
|
17905
19351
|
th.scope = "col";
|
|
17906
19352
|
th.dataset.align = c.align;
|
|
17907
19353
|
hr.append(th);
|
|
17908
19354
|
}
|
|
17909
19355
|
head.append(hr);
|
|
17910
|
-
const body =
|
|
19356
|
+
const body = el2(doc, "tbody");
|
|
17911
19357
|
for (const r of m.rows) {
|
|
17912
|
-
const tr =
|
|
19358
|
+
const tr = el2(doc, "tr");
|
|
17913
19359
|
for (const c of m.columns) {
|
|
17914
|
-
const td =
|
|
19360
|
+
const td = el2(doc, "td");
|
|
17915
19361
|
cellAttrs(td, c);
|
|
17916
19362
|
const cell = r[c.key];
|
|
17917
19363
|
if (cell) {
|
|
17918
19364
|
if (cell.text.length > 40 && !c.mono && !cell.code)
|
|
17919
19365
|
td.dataset.long = "";
|
|
17920
|
-
cellContent(doc, td, cell
|
|
19366
|
+
cellContent(doc, td, cell);
|
|
17921
19367
|
}
|
|
17922
19368
|
tr.append(td);
|
|
17923
19369
|
}
|
|
19370
|
+
rowLink(tr, rowStep(r, m.columns), steps);
|
|
17924
19371
|
body.append(tr);
|
|
17925
19372
|
}
|
|
17926
19373
|
table.append(head, body);
|
|
17927
19374
|
scroller.append(table);
|
|
17928
|
-
const stack =
|
|
19375
|
+
const stack = el2(doc, "div", "knobs-wt-tstack");
|
|
17929
19376
|
stack.setAttribute("role", "list");
|
|
17930
19377
|
if (m.caption)
|
|
17931
19378
|
stack.setAttribute("aria-label", m.caption);
|
|
17932
19379
|
const primary = m.columns.find((c) => c.key === m.primary) ?? m.columns[0];
|
|
17933
19380
|
for (const r of m.rows) {
|
|
17934
|
-
const item =
|
|
19381
|
+
const item = el2(doc, "div", "knobs-wt-trow");
|
|
17935
19382
|
item.setAttribute("role", "listitem");
|
|
17936
|
-
const title =
|
|
19383
|
+
const title = el2(doc, "div", "knobs-wt-ttitle");
|
|
17937
19384
|
if (primary) {
|
|
17938
19385
|
cellAttrs(title, primary);
|
|
17939
19386
|
const cell = r[primary.key];
|
|
17940
19387
|
if (cell)
|
|
17941
|
-
cellContent(doc, title, cell
|
|
19388
|
+
cellContent(doc, title, cell);
|
|
17942
19389
|
}
|
|
17943
|
-
const line =
|
|
19390
|
+
const line = el2(doc, "div", "knobs-wt-tline");
|
|
17944
19391
|
line.append(title);
|
|
17945
|
-
const badges =
|
|
19392
|
+
const badges = el2(doc, "div", "knobs-wt-tbadges");
|
|
17946
19393
|
for (const c of m.columns) {
|
|
17947
19394
|
const cell = r[c.key];
|
|
17948
19395
|
if (c !== primary && cell?.badge)
|
|
17949
|
-
cellContent(doc, badges, cell
|
|
19396
|
+
cellContent(doc, badges, cell);
|
|
17950
19397
|
}
|
|
17951
19398
|
if (badges.childNodes.length)
|
|
17952
19399
|
line.append(badges);
|
|
17953
19400
|
item.append(line);
|
|
17954
|
-
const terms =
|
|
19401
|
+
const terms = el2(doc, "dl", "knobs-wt-tterms");
|
|
17955
19402
|
for (const c of m.columns) {
|
|
17956
19403
|
if (c === primary)
|
|
17957
19404
|
continue;
|
|
17958
19405
|
const cell = r[c.key];
|
|
17959
19406
|
if (!cell || cell.text === "" || cell.badge)
|
|
17960
19407
|
continue;
|
|
17961
|
-
const dd =
|
|
19408
|
+
const dd = el2(doc, "dd");
|
|
17962
19409
|
cellAttrs(dd, c);
|
|
17963
19410
|
dd.removeAttribute("data-align");
|
|
17964
|
-
cellContent(doc, dd, cell
|
|
17965
|
-
terms.append(
|
|
19411
|
+
cellContent(doc, dd, cell);
|
|
19412
|
+
terms.append(text2(doc, "dt", "", c.label), dd);
|
|
17966
19413
|
}
|
|
17967
19414
|
if (terms.childNodes.length)
|
|
17968
19415
|
item.append(terms);
|
|
19416
|
+
rowLink(item, rowStep(r, m.columns), steps);
|
|
17969
19417
|
stack.append(item);
|
|
17970
19418
|
}
|
|
17971
19419
|
if (m.caption)
|
|
17972
|
-
stack.append(
|
|
19420
|
+
stack.append(text2(doc, "p", "knobs-wt-tcaption", m.caption));
|
|
17973
19421
|
box.append(scroller, stack);
|
|
17974
19422
|
host.append(box);
|
|
17975
19423
|
let natural = 0;
|
|
@@ -18023,16 +19471,16 @@ function mountTable(host, m, steps) {
|
|
|
18023
19471
|
}
|
|
18024
19472
|
function chapterPills(host, chapters, io) {
|
|
18025
19473
|
const doc = host.ownerDocument;
|
|
18026
|
-
const list =
|
|
19474
|
+
const list = el2(doc, "div", "knobs-wt-chapters");
|
|
18027
19475
|
list.setAttribute("role", "group");
|
|
18028
19476
|
list.setAttribute("aria-label", "Chapters");
|
|
18029
19477
|
let at = -2;
|
|
18030
19478
|
const rows = chapters.map((c, i) => {
|
|
18031
|
-
const b =
|
|
19479
|
+
const b = el2(doc, "button", "knobs-wt-chapter");
|
|
18032
19480
|
b.type = "button";
|
|
18033
|
-
b.append(
|
|
18034
|
-
b.append(
|
|
18035
|
-
b.append(
|
|
19481
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-n", String(i + 1)));
|
|
19482
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-label", c.label));
|
|
19483
|
+
b.append(text2(doc, "span", "knobs-wt-chapter-time", clock(c.start)));
|
|
18036
19484
|
b.addEventListener("click", () => {
|
|
18037
19485
|
io.onEvent?.({ what: "seek", time: Math.round(c.start * 100) / 100, detail: c.label });
|
|
18038
19486
|
io.seek(c.start);
|
|
@@ -18070,15 +19518,13 @@ function disclosure(host, options) {
|
|
|
18070
19518
|
const doc = host.ownerDocument;
|
|
18071
19519
|
const win = doc.defaultView;
|
|
18072
19520
|
let open = options.open === true;
|
|
18073
|
-
const root =
|
|
18074
|
-
const toggle =
|
|
19521
|
+
const root = el2(doc, "div", "knobs-wt-disc");
|
|
19522
|
+
const toggle = el2(doc, "button", "knobs-wt-disc-toggle");
|
|
18075
19523
|
toggle.type = "button";
|
|
18076
19524
|
toggle.innerHTML = GLYPH.caret;
|
|
18077
|
-
toggle.append(
|
|
18078
|
-
|
|
18079
|
-
|
|
18080
|
-
const viewport = el(doc, "div", "knobs-wt-disc-body");
|
|
18081
|
-
const content = el(doc, "div", "knobs-wt-disc-content");
|
|
19525
|
+
toggle.append(text2(doc, "span", "", options.label));
|
|
19526
|
+
const viewport = el2(doc, "div", "knobs-wt-disc-body");
|
|
19527
|
+
const content = el2(doc, "div", "knobs-wt-disc-content");
|
|
18082
19528
|
content.append(options.content);
|
|
18083
19529
|
viewport.append(content);
|
|
18084
19530
|
root.append(toggle, viewport);
|
|
@@ -18090,7 +19536,9 @@ function disclosure(host, options) {
|
|
|
18090
19536
|
const k = full > 0 ? Math.max(0, Math.min(1, h.x / full)) : open ? 1 : 0;
|
|
18091
19537
|
viewport.style.height = !moving && open ? "" : `${Math.max(0, h.x).toFixed(2)}px`;
|
|
18092
19538
|
viewport.toggleAttribute("data-moving", moving);
|
|
18093
|
-
|
|
19539
|
+
const o = open ? Math.min(1, k * 1.25) : Math.max(0, k * 1.5 - 0.5);
|
|
19540
|
+
content.style.opacity = o.toFixed(3);
|
|
19541
|
+
content.style.filter = o >= 1 ? "" : `blur(${((1 - o) * 3).toFixed(2)}px)`;
|
|
18094
19542
|
};
|
|
18095
19543
|
const loop = frameLoop(win, (dt) => {
|
|
18096
19544
|
h.step(dt);
|
|
@@ -18126,6 +19574,7 @@ function disclosure(host, options) {
|
|
|
18126
19574
|
h.snap(open ? full : 0);
|
|
18127
19575
|
viewport.style.height = open ? "" : "0px";
|
|
18128
19576
|
content.style.opacity = open ? "1" : "0";
|
|
19577
|
+
content.style.filter = "";
|
|
18129
19578
|
return;
|
|
18130
19579
|
}
|
|
18131
19580
|
if (h.resting)
|
|
@@ -18142,10 +19591,79 @@ function disclosure(host, options) {
|
|
|
18142
19591
|
toggle.addEventListener("click", () => api.toggle());
|
|
18143
19592
|
return api;
|
|
18144
19593
|
}
|
|
19594
|
+
var PR_PATHS = {
|
|
19595
|
+
pull: "M104,64A32,32,0,1,0,64,95v66a32,32,0,1,0,16,0V95A32.06,32.06,0,0,0,104,64ZM56,64A16,16,0,1,1,72,80,16,16,0,0,1,56,64ZM88,192a16,16,0,1,1-16-16A16,16,0,0,1,88,192Zm120-31V110.63a23.85,23.85,0,0,0-7-17L163.31,56H192a8,8,0,0,0,0-16H144a8,8,0,0,0-8,8V96a8,8,0,0,0,16,0V67.31L189.66,105a8,8,0,0,1,2.34,5.66V161a32,32,0,1,0,16,0Zm-8,47a16,16,0,1,1,16-16A16,16,0,0,1,200,208Z",
|
|
19596
|
+
merge: "M208,112a32.05,32.05,0,0,0-30.69,23l-42.21-6a8,8,0,0,1-4.95-2.71L94.43,84.55A32,32,0,1,0,72,87v82a32,32,0,1,0,16,0V101.63l30,35a24,24,0,0,0,14.83,8.14l44,6.28A32,32,0,1,0,208,112ZM64,56A16,16,0,1,1,80,72,16,16,0,0,1,64,56ZM96,200a16,16,0,1,1-16-16A16,16,0,0,1,96,200Zm112-40a16,16,0,1,1,16-16A16,16,0,0,1,208,160Z"
|
|
19597
|
+
};
|
|
19598
|
+
var prGlyph = (merged) => `<svg class="knobs-icon" viewBox="0 0 256 256" width="12" height="12" fill="currentColor" aria-hidden="true" focusable="false"><path d="${merged ? PR_PATHS.merge : PR_PATHS.pull}"/></svg>`;
|
|
19599
|
+
var PR_STATE = { merged: "Merged", open: "Open", draft: "Draft", closed: "Closed" };
|
|
19600
|
+
function prChip(doc, n, pr) {
|
|
19601
|
+
const chip = el2(doc, pr.href ? "a" : "span", "knobs-wt-pr");
|
|
19602
|
+
if (pr.href) {
|
|
19603
|
+
chip.setAttribute("href", pr.href);
|
|
19604
|
+
chip.setAttribute("target", "_blank");
|
|
19605
|
+
chip.setAttribute("rel", "noreferrer");
|
|
19606
|
+
}
|
|
19607
|
+
if (pr.state)
|
|
19608
|
+
chip.dataset.state = pr.state;
|
|
19609
|
+
chip.innerHTML = prGlyph(pr.state === "merged");
|
|
19610
|
+
chip.append(text2(doc, "span", "", `#${n}`));
|
|
19611
|
+
const tip = [`#${n}`, pr.title, pr.state ? PR_STATE[pr.state] : ""].filter(Boolean).join(" · ");
|
|
19612
|
+
chip.title = tip;
|
|
19613
|
+
chip.setAttribute("aria-label", `Pull request ${tip}`);
|
|
19614
|
+
return chip;
|
|
19615
|
+
}
|
|
19616
|
+
var PR_URL = /^https?:\/\/[^/]+\/.+\/(?:pull|merge_requests)\/(\d+)\/?(?:[?#].*)?$/;
|
|
19617
|
+
var PR_REF = /(^|[^\w&/#])#(\d{3,7})\b/g;
|
|
19618
|
+
function linkPRs(root, find) {
|
|
19619
|
+
const doc = root.ownerDocument;
|
|
19620
|
+
for (const a of Array.from(root.querySelectorAll("a[href]"))) {
|
|
19621
|
+
if (a.classList.contains("knobs-wt-pr") || a.closest(".knobs-wt-slot,button"))
|
|
19622
|
+
continue;
|
|
19623
|
+
const n = PR_URL.exec(a.getAttribute("href") ?? "")?.[1];
|
|
19624
|
+
const words = a.textContent?.trim() ?? "";
|
|
19625
|
+
if (!n || !(words === `#${n}` || words === n || words === a.getAttribute("href")))
|
|
19626
|
+
continue;
|
|
19627
|
+
a.replaceWith(prChip(doc, n, { href: a.href, ...find(n) }));
|
|
19628
|
+
}
|
|
19629
|
+
const walker = doc.createTreeWalker(root, 5);
|
|
19630
|
+
const hits = [];
|
|
19631
|
+
for (let t = walker.nextNode();t; t = walker.nextNode()) {
|
|
19632
|
+
if (t.nodeType !== 3)
|
|
19633
|
+
continue;
|
|
19634
|
+
const p = t.parentElement;
|
|
19635
|
+
if (!p || p.closest("a,code,pre,button,.knobs-wt-slot,.knobs-wt-pr"))
|
|
19636
|
+
continue;
|
|
19637
|
+
PR_REF.lastIndex = 0;
|
|
19638
|
+
if (PR_REF.test(t.nodeValue ?? ""))
|
|
19639
|
+
hits.push(t);
|
|
19640
|
+
}
|
|
19641
|
+
for (const t of hits) {
|
|
19642
|
+
const value = t.nodeValue ?? "";
|
|
19643
|
+
const frag = doc.createDocumentFragment();
|
|
19644
|
+
let last = 0;
|
|
19645
|
+
let changed = false;
|
|
19646
|
+
PR_REF.lastIndex = 0;
|
|
19647
|
+
for (let m = PR_REF.exec(value);m; m = PR_REF.exec(value)) {
|
|
19648
|
+
const pr = find(m[2]);
|
|
19649
|
+
if (!pr)
|
|
19650
|
+
continue;
|
|
19651
|
+
const at = m.index + m[1].length;
|
|
19652
|
+
frag.append(value.slice(last, at), prChip(doc, m[2], pr));
|
|
19653
|
+
last = at + m[2].length + 1;
|
|
19654
|
+
changed = true;
|
|
19655
|
+
}
|
|
19656
|
+
if (!changed)
|
|
19657
|
+
continue;
|
|
19658
|
+
frag.append(value.slice(last));
|
|
19659
|
+
t.replaceWith(frag);
|
|
19660
|
+
}
|
|
19661
|
+
}
|
|
18145
19662
|
|
|
18146
19663
|
// src/ui/walkthrough-media.ts
|
|
18147
19664
|
init_compare_model();
|
|
18148
19665
|
init_glide();
|
|
19666
|
+
init_motion();
|
|
18149
19667
|
init_icons();
|
|
18150
19668
|
init_look();
|
|
18151
19669
|
|
|
@@ -18158,8 +19676,9 @@ var quick = springCss(FADE);
|
|
|
18158
19676
|
var fade2 = "-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 1.5em),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 1.5em),transparent)";
|
|
18159
19677
|
var walkthroughCss = `
|
|
18160
19678
|
:where(.knobs-walkthrough,.knobs-walkthrough *,.knobs-wt-slot,.knobs-wt-slot *){box-sizing:border-box}
|
|
19679
|
+
.knobs-ui:is(.knobs-walkthrough,.knobs-wt-slot){--k-glass:0;--k-glow:0;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms}
|
|
18161
19680
|
.knobs-ui.knobs-walkthrough{--kw-snappy:${snappy3.easing};--kw-snappy-ms:${snappy3.duration}ms;--kw-calm:${calm.easing};--kw-calm-ms:${calm.duration}ms;--kw-state:${state.easing};--kw-state-ms:${state.duration}ms;--kw-quick:${quick.easing};--kw-quick-ms:${quick.duration}ms;
|
|
18162
|
-
--kw-measure:var(--k-walkthrough-measure,68ch);--kw-width:var(--k-walkthrough-width,
|
|
19681
|
+
--kw-measure:var(--k-walkthrough-measure,68ch);--kw-width:var(--k-walkthrough-width,960px);--kw-rail-w:220px;--kw-rail-gap:calc(var(--_k-space-group) + var(--_k-space-column) / 1.5);--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));
|
|
18163
19682
|
--kw-card:var(--_ku-pane-bg);--kw-card-ring:var(--_ku-pane-ring);--kw-under:var(--_k-bg-page);--kw-ink:var(--k-walkthrough-ink,#ff375f);--kw-strong:var(--_k-weight-label);
|
|
18164
19683
|
container:knobs-wt / inline-size;display:block;width:100%;color:var(--_k-fg);font:var(--_k-weight) var(--_k-type-body)/var(--_k-leading-body) var(--_k-font);font-variant-numeric:tabular-nums;text-align:left;
|
|
18165
19684
|
letter-spacing:normal;-webkit-font-smoothing:antialiased}
|
|
@@ -18172,44 +19691,57 @@ var walkthroughCss = `
|
|
|
18172
19691
|
: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;
|
|
18173
19692
|
background:transparent;border:0;border-radius:0;margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
18174
19693
|
|
|
18175
|
-
/* ——— layout:
|
|
18176
|
-
|
|
18177
|
-
|
|
19694
|
+
/* ——— layout: the contents rail sticky at the left edge (right if asked), and the column centred
|
|
19695
|
+
in the room beside it, up to --kw-width: media take the column, prose stops at the measure.
|
|
19696
|
+
One column where it's narrow ——— */
|
|
19697
|
+
.knobs-wt-layout{display:grid;grid-template-columns:var(--kw-rail-w) minmax(0,1fr);gap:var(--kw-rail-gap);align-items:start}
|
|
19698
|
+
.knobs-walkthrough[data-rail="right"] .knobs-wt-layout{grid-template-columns:minmax(0,1fr) var(--kw-rail-w)}
|
|
18178
19699
|
.knobs-walkthrough[data-rail="right"] .knobs-wt-main{grid-column:1;grid-row:1}
|
|
18179
19700
|
.knobs-walkthrough[data-rail="right"] .knobs-wt-rail{grid-column:2;grid-row:1}
|
|
18180
|
-
.knobs-walkthrough[data-rail="
|
|
18181
|
-
.knobs-
|
|
18182
|
-
.knobs-wt-main{display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0;padding-bottom:12px}
|
|
19701
|
+
.knobs-walkthrough[data-rail="none"] .knobs-wt-layout{grid-template-columns:minmax(0,1fr)}
|
|
19702
|
+
.knobs-wt-main{justify-self:center;width:100%;max-width:var(--kw-width);display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0;padding-bottom:12px}
|
|
18183
19703
|
/* The intro, the updates and each section: a section's room apart, the hairline in the middle. */
|
|
18184
19704
|
.knobs-wt-flow{display:flex;flex-direction:column;gap:calc(var(--_k-space-section) / 2);min-width:0}
|
|
18185
19705
|
.knobs-wt-flow>*+*{padding-top:calc(var(--_k-space-section) / 2);box-shadow:inset 0 1px 0 var(--_k-border-subtle)}
|
|
18186
|
-
@container knobs-wt (max-width:
|
|
19706
|
+
@container knobs-wt (max-width:960px){.knobs-walkthrough[data-rail] .knobs-wt-layout{grid-template-columns:minmax(0,1fr);gap:var(--_k-space-title)}
|
|
18187
19707
|
.knobs-walkthrough[data-rail] .knobs-wt-layout>*{grid-column:auto;grid-row:auto}}
|
|
18188
19708
|
|
|
18189
19709
|
/* ——— one line of facts: small, faint, joined by ·, its trailing edge fading ——— */
|
|
18190
19710
|
.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}}
|
|
18191
19711
|
.knobs-wt-metaline>*{flex:none}
|
|
18192
19712
|
.knobs-wt-metaline>*+*::before{content:"·";margin-right:var(--_k-space-inside);color:var(--_k-fg-3)}
|
|
18193
|
-
.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)}
|
|
18194
|
-
.knobs-walkthrough .knobs-wt-metaline a:hover{color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
19713
|
+
.knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr){display:inline-flex;align-items:baseline;color:inherit;text-decoration:none;transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
19714
|
+
.knobs-walkthrough .knobs-wt-metaline a:not(.knobs-wt-pr):hover{color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18195
19715
|
.knobs-wt-metaline svg{align-self:center;width:12px;height:12px;margin-left:2px}
|
|
19716
|
+
/* A pull request: a small chip, its state's icon and number in a quiet tint of the state (open
|
|
19717
|
+
the success hue, merged the violet series, draft and closed neutral), its title on hover. */
|
|
19718
|
+
.knobs-ui .knobs-wt-pr{--kw-pr:var(--_k-fg-2);display:inline-flex;align-items:center;gap:3px;height:18px;padding:0 5px 0 4px;margin:0 1px;border-radius:calc(var(--_k-radius) / 2);vertical-align:1px;
|
|
19719
|
+
background:color-mix(in oklch,var(--kw-pr) 12%,transparent);color:var(--kw-pr);font:var(--_k-weight-label) var(--_k-type-small)/1 var(--_k-font);font-variant-numeric:tabular-nums;white-space:nowrap;text-decoration:none;
|
|
19720
|
+
transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
19721
|
+
.knobs-ui .knobs-wt-pr[data-state="open"]{--kw-pr:color-mix(in oklch,var(--_k-ok) 60%,var(--_k-fg-2))}
|
|
19722
|
+
.knobs-ui .knobs-wt-pr[data-state="merged"]{--kw-pr:color-mix(in oklch,var(--_k-series-5) 60%,var(--_k-fg-2))}
|
|
19723
|
+
.knobs-ui .knobs-wt-pr:is([data-state="draft"],[data-state="closed"]){--kw-pr:var(--_k-fg-3)}
|
|
19724
|
+
.knobs-ui a.knobs-wt-pr:is(:hover,:focus-visible){background:color-mix(in oklch,var(--kw-pr) 22%,transparent);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
19725
|
+
.knobs-wt-pr svg{flex:none}
|
|
19726
|
+
.knobs-wt-metaline .knobs-wt-pr{align-self:center;margin:0}
|
|
18196
19727
|
.knobs-wt-kicker-lead{color:var(--_k-fg);font-weight:var(--kw-strong)}
|
|
18197
19728
|
.knobs-wt-metaline [data-status="on"]{color:var(--_k-fg)}
|
|
18198
19729
|
.knobs-wt-metaline [data-status="warn"]{color:var(--_k-warn)}
|
|
18199
19730
|
|
|
18200
19731
|
/* ——— the page's head ——— */
|
|
18201
19732
|
.knobs-wt-head{display:flex;flex-direction:column;gap:var(--_k-space-title)}
|
|
18202
|
-
.knobs-wt-title{font-size:var(--_k-type-
|
|
19733
|
+
.knobs-wt-title{font-size:var(--_k-type-heading)!important;line-height:var(--_k-leading-heading);font-weight:var(--kw-strong)!important;text-wrap:balance}
|
|
18203
19734
|
.knobs-wt-summary{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
|
|
18204
19735
|
.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)}
|
|
18205
19736
|
.knobs-wt-meta>span{display:inline-flex;gap:var(--_k-space-inside);white-space:nowrap}
|
|
18206
19737
|
.knobs-wt-meta b{font-weight:var(--_k-weight);color:var(--_k-fg-2);font-family:var(--_k-mono)}
|
|
18207
19738
|
|
|
18208
|
-
/* ——— a section: a PR or topic. Its kicker leads the meta line
|
|
19739
|
+
/* ——— a section: a PR or topic. Its kicker leads the meta line, its title under it (16/24, under
|
|
19740
|
+
the page's 20/28), both on the column's edge ——— */
|
|
18209
19741
|
.knobs-wt-section{display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0}
|
|
18210
19742
|
.knobs-wt-sintro{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0}
|
|
18211
19743
|
.knobs-wt-shead{display:flex;flex-direction:column;gap:var(--_k-space-inside);min-width:0;scroll-margin-top:16px}
|
|
18212
|
-
.knobs-wt-heading{font-size:var(--_k-type-
|
|
19744
|
+
.knobs-wt-heading{font-size:var(--_k-type-title)!important;line-height:var(--_k-leading-title);font-weight:var(--kw-strong)!important;text-wrap:balance}
|
|
18213
19745
|
.knobs-wt-ssum{max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
|
|
18214
19746
|
.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;
|
|
18215
19747
|
--kw-tone:var(--_k-fg-2);color:var(--kw-tone);background:color-mix(in srgb,var(--kw-tone) 14%,transparent)}
|
|
@@ -18240,7 +19772,6 @@ var walkthroughCss = `
|
|
|
18240
19772
|
.knobs-wt-feed{display:flex;flex-direction:column;gap:var(--_k-space-title);min-width:0;scroll-margin-top:16px}
|
|
18241
19773
|
.knobs-wt-feed-head{display:flex;align-items:baseline;gap:var(--_k-space-sibling)}
|
|
18242
19774
|
.knobs-wt-feed-title{font-weight:var(--kw-strong)!important}
|
|
18243
|
-
.knobs-wt-feed-count{font-size:var(--_k-type-small);line-height:var(--_k-leading-small);color:var(--_k-fg-3)}
|
|
18244
19775
|
.knobs-wt-feed-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--_k-space-group);min-width:0}
|
|
18245
19776
|
.knobs-wt-fi{position:relative;display:flex;flex-direction:column;gap:var(--_k-space-sibling);min-width:0}
|
|
18246
19777
|
.knobs-wt-fi+.knobs-wt-fi::before{content:"";position:absolute;left:0;right:0;top:calc(var(--_k-space-group) / -2);height:1px;background:var(--_k-border-subtle)}
|
|
@@ -18251,10 +19782,6 @@ var walkthroughCss = `
|
|
|
18251
19782
|
:where(.knobs-ui) .knobs-wt-fi-step{all:revert;box-sizing:border-box;font:inherit;color:inherit;background:transparent;border:0;margin:0;padding:0;cursor:pointer;outline:none;
|
|
18252
19783
|
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
18253
19784
|
.knobs-ui .knobs-wt-fi-step:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18254
|
-
: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;
|
|
18255
|
-
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)}
|
|
18256
|
-
.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)}
|
|
18257
|
-
.knobs-wt-steplink svg{width:12px;height:12px}
|
|
18258
19785
|
|
|
18259
19786
|
/* ——— a disclosure: a small faint toggle with a caret, no fill. The body's height springs (in
|
|
18260
19787
|
script) and its content fades in place, top-anchored, under a soft bottom mask ——— */
|
|
@@ -18265,11 +19792,12 @@ var walkthroughCss = `
|
|
|
18265
19792
|
.knobs-ui .knobs-wt-disc-toggle:focus-visible{box-shadow:0 0 0 2px var(--_k-focus)}
|
|
18266
19793
|
.knobs-wt-disc-toggle svg{flex:none;width:12px;height:12px;transition:rotate var(--kw-state-ms) var(--kw-state)}
|
|
18267
19794
|
.knobs-wt-disc[data-open]>.knobs-wt-disc-toggle svg{rotate:90deg}
|
|
18268
|
-
.knobs-wt-disc-count{color:var(--_k-fg-3)}
|
|
18269
19795
|
.knobs-wt-disc-body{overflow:hidden}
|
|
18270
19796
|
.knobs-wt-disc[data-open]>.knobs-wt-disc-body:not([data-moving]){overflow:visible}
|
|
18271
19797
|
.knobs-wt-disc-body[data-moving]{-webkit-mask-image:linear-gradient(#000 calc(100% - 16px),transparent);mask-image:linear-gradient(#000 calc(100% - 16px),transparent)}
|
|
18272
19798
|
.knobs-wt-disc-content{padding-top:var(--_k-space-inside)}
|
|
19799
|
+
/* "More" under a caption or summary: the rest of its words, in the same voice. */
|
|
19800
|
+
.knobs-wt-more-text{display:flex;flex-direction:column;gap:var(--_k-space-sibling);max-width:var(--kw-measure);color:var(--_k-fg-2);text-wrap:pretty}
|
|
18273
19801
|
/* A trail sits on the toggle's label edge: faint lines, the time trailing. */
|
|
18274
19802
|
.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}
|
|
18275
19803
|
.knobs-wt-trail li{display:flex;align-items:baseline;gap:var(--_k-space-sibling);min-width:0;white-space:nowrap}
|
|
@@ -18387,32 +19915,28 @@ var walkthroughCss = `
|
|
|
18387
19915
|
.knobs-wt-ocap{color:var(--_k-fg-2);max-width:var(--kw-measure)}
|
|
18388
19916
|
|
|
18389
19917
|
/* ——— the rail: progress, contents, export ——— */
|
|
18390
|
-
.knobs-wt-rail{position:sticky;top:
|
|
19918
|
+
.knobs-wt-rail{position:sticky;top:var(--_k-pad-page);display:flex;flex-direction:column;gap:var(--_k-space-title);max-height:calc(100vh - var(--_k-pad-page) * 2);min-height:0;font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
|
|
18391
19919
|
.knobs-wt-progress{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
|
|
18392
19920
|
.knobs-wt-count{display:flex;align-items:baseline;justify-content:space-between;color:var(--_k-fg-2)}
|
|
18393
19921
|
.knobs-wt-count b{color:var(--_k-fg);font-weight:var(--kw-strong)}
|
|
18394
19922
|
.knobs-wt-bar{position:relative;height:4px;border-radius:2px;overflow:hidden;background:var(--_k-fill)}
|
|
18395
19923
|
.knobs-wt-fill{position:absolute;inset:0;border-radius:inherit;background:var(--_k-accent);transform-origin:0 50%;transform:scaleX(var(--kw-done,0))}
|
|
18396
19924
|
.knobs-walkthrough[data-settled] .knobs-wt-fill{transition:transform var(--kw-calm-ms) var(--kw-calm)}
|
|
18397
|
-
/* The contents:
|
|
18398
|
-
steps under them (20 high, half of it), bled out so their text sits on the
|
|
18399
|
-
selection pill slides to the current step, and base rows never brighten
|
|
19925
|
+
/* The contents: every row on one text edge and in one colour, title rows (32 high, the control
|
|
19926
|
+
radius) and the steps under them (20 high, half of it), bled out so their text sits on the
|
|
19927
|
+
rail's edge; the selection pill slides to the current step, and base rows never brighten. */
|
|
18400
19928
|
.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}
|
|
18401
19929
|
.knobs-wt-toc::-webkit-scrollbar{display:none}
|
|
18402
19930
|
.knobs-wt-toc-group{display:flex;flex-direction:column;min-width:0}
|
|
18403
|
-
.knobs-ui .knobs-wt-toc-row{position:relative;display:grid;grid-template-columns:
|
|
18404
|
-
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(--
|
|
19931
|
+
.knobs-ui .knobs-wt-toc-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--_k-space-sibling);align-items:center;flex:none;width:100%;height:32px;
|
|
19932
|
+
padding:0 var(--_k-space-sibling);border-radius:var(--_k-radius);color:var(--_k-fg-2);font-size:var(--_k-type-body);line-height:var(--_k-leading-body);font-weight:var(--_k-weight);text-align:left;white-space:nowrap;cursor:pointer;
|
|
18405
19933
|
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
18406
|
-
.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)
|
|
18407
|
-
.knobs-wt-toc[data-bare] .knobs-wt-toc-row{grid-template-columns:minmax(0,1fr) auto}
|
|
18408
|
-
.knobs-wt-toc[data-bare] .knobs-wt-toc-n{display:none}
|
|
19934
|
+
.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)}
|
|
18409
19935
|
.knobs-ui .knobs-wt-toc-row:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
18410
|
-
.knobs-ui .knobs-wt-toc-sub:is(:hover,:focus-visible){color:var(--_k-fg-2)}
|
|
18411
19936
|
.knobs-ui .knobs-wt-toc-row:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
|
|
18412
|
-
.knobs-wt-toc-
|
|
18413
|
-
.knobs-wt-toc-row[data-active] .knobs-wt-toc-n{color:var(--_k-fg-2)}
|
|
19937
|
+
.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)}
|
|
18414
19938
|
.knobs-wt-toc-label{min-width:0;overflow:hidden;${fade2}}
|
|
18415
|
-
.knobs-pick-copy .knobs-wt-toc-
|
|
19939
|
+
.knobs-pick-copy .knobs-wt-toc-end{color:var(--_k-fg-2)}
|
|
18416
19940
|
/* A step's state: an open ring, a filled dot once answered, nothing to answer a faint ring. */
|
|
18417
19941
|
.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);
|
|
18418
19942
|
transition:background var(--kw-snappy-ms) var(--kw-snappy),box-shadow var(--kw-snappy-ms) var(--kw-snappy)}
|
|
@@ -18427,10 +19951,12 @@ var walkthroughCss = `
|
|
|
18427
19951
|
.knobs-ui .knobs-wt-btn[data-primary]:is(:hover,:focus-visible){background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
|
|
18428
19952
|
.knobs-wt-keys{color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
|
|
18429
19953
|
.knobs-wt-keys kbd{font:inherit;color:var(--_k-fg-2)}
|
|
18430
|
-
/* Narrow: the rail becomes a bar along the top
|
|
18431
|
-
|
|
19954
|
+
/* Narrow: the contents go; with review on, the rail becomes a bar along the top with the
|
|
19955
|
+
progress and the export. */
|
|
19956
|
+
@container knobs-wt (max-width:960px){
|
|
18432
19957
|
.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)}
|
|
18433
19958
|
.knobs-wt-progress{flex:1}.knobs-wt-toc,.knobs-wt-keys{display:none}
|
|
19959
|
+
.knobs-walkthrough:not([data-review]) .knobs-wt-rail{display:none}
|
|
18434
19960
|
.knobs-wt-step,.knobs-wt-shead,.knobs-wt-feed{scroll-margin-top:60px}
|
|
18435
19961
|
}
|
|
18436
19962
|
|
|
@@ -18440,9 +19966,18 @@ var walkthroughCss = `
|
|
|
18440
19966
|
well for hairlines, so nothing draws a fill and a ring of its own there ——— */
|
|
18441
19967
|
.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);
|
|
18442
19968
|
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);
|
|
18443
|
-
--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info))}
|
|
18444
|
-
.knobs-wt-slot
|
|
19969
|
+
--kw-good:var(--k-walkthrough-good,var(--_k-ok));--kw-info:var(--k-walkthrough-info,var(--_k-info));--kw-frame-r:var(--_k-radius-panel)}
|
|
19970
|
+
.knobs-surface .knobs-wt-slot{--kw-frame-r:calc(var(--_k-radius-panel) - 4px)}
|
|
19971
|
+
/* A slot's label: sentence case as written, small and faint, never a heading. */
|
|
19972
|
+
.knobs-wt-slot-label{display:block;min-width:0;overflow:hidden;white-space:nowrap;font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);letter-spacing:normal;text-transform:none;color:var(--_k-fg-3);${fade2}}
|
|
18445
19973
|
.knobs-wt-body{min-width:0}
|
|
19974
|
+
/* Media mounting after the first paint, and a picture still decoding, come into focus: a fade with
|
|
19975
|
+
a small blur on the quick spring, from the top; at rest, nothing. */
|
|
19976
|
+
.knobs-wt-body,.knobs-wt-frame>img{transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
|
|
19977
|
+
.knobs-wt-body[data-entering],.knobs-wt-frame[data-loading]>img{opacity:0;filter:blur(3px);transition:none}
|
|
19978
|
+
/* One frame for every slot that has one (a picture, a video, a code block, a compare's stage, a
|
|
19979
|
+
slot still loading): the panel radius and a quiet fill, no ring; concentric on a card. */
|
|
19980
|
+
.knobs-wt-slot .knobs-cmp-stage{border-radius:var(--kw-frame-r)}
|
|
18446
19981
|
/* A compare is exactly as tall as its pictures at this width, so there's no letterbox: the two
|
|
18447
19982
|
sides edge to edge (side) or one (overlaid), plus its mode bar, the timeline and their two gaps
|
|
18448
19983
|
(92 px), and the frame strip with its gap (158 px). */
|
|
@@ -18450,13 +19985,16 @@ var walkthroughCss = `
|
|
|
18450
19985
|
.knobs-wt-fit[data-overlay]{height:calc(100cqw / var(--kw-aspect,1.7778) + var(--kw-chrome,92px))}
|
|
18451
19986
|
.knobs-wt-fit[data-strip]{--kw-chrome:158px}
|
|
18452
19987
|
.knobs-wt-fit[data-settled]{transition:height var(--k-wt-calm-ms,${calm.duration}ms) ${calm.easing}}
|
|
18453
|
-
.knobs-wt-frame{position:relative;width:100%;overflow:hidden;border-radius:
|
|
19988
|
+
.knobs-wt-frame{position:relative;width:100%;overflow:hidden;border-radius:var(--kw-frame-r);background:var(--_k-fill);aspect-ratio:var(--kw-aspect,16/9)}
|
|
18454
19989
|
.knobs-wt-frame>:is(video,img){position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;margin:0;border:0}
|
|
18455
19990
|
.knobs-wt-frame[data-natural]{aspect-ratio:auto;background:transparent}
|
|
18456
19991
|
.knobs-wt-frame[data-natural]>img{position:static;height:auto}
|
|
18457
|
-
|
|
19992
|
+
/* Diff, code and terminal: one plain container (no glass): a flat inset fill, a hairline ring, the
|
|
19993
|
+
control radius and room inside, so a diff's buttons and the code under them can breathe. */
|
|
19994
|
+
.knobs-wt-slot[data-kind="diff"]>.knobs-wt-body{padding:var(--_k-space-sibling) var(--_k-pad-card) var(--_k-pad-card);border-radius:var(--_k-radius);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle)}
|
|
19995
|
+
.knobs-wt-code{margin:0;padding:var(--_k-pad-card);max-height:420px;overflow:auto;border-radius:var(--_k-radius);background:var(--_k-bg-inset);box-shadow:inset 0 0 0 1px var(--_k-border-subtle);color:var(--_k-fg);
|
|
18458
19996
|
font:var(--_k-type-label)/var(--_k-leading-body) var(--_k-mono);white-space:pre;tab-size:2;scrollbar-width:thin}
|
|
18459
|
-
.knobs-surface .knobs-wt-code{padding-inline:0;border-radius:0;background:transparent;box-shadow:inset 0 1px 0 var(--_k-border-subtle),inset 0 -1px 0 var(--_k-border-subtle)}
|
|
19997
|
+
.knobs-surface :is(.knobs-wt-code,.knobs-wt-slot[data-kind="diff"]>.knobs-wt-body){padding-inline:0;border-radius:0;background:transparent;box-shadow:inset 0 1px 0 var(--_k-border-subtle),inset 0 -1px 0 var(--_k-border-subtle)}
|
|
18460
19998
|
.knobs-wt-code[data-terminal]{white-space:pre-wrap;overflow-wrap:anywhere}
|
|
18461
19999
|
.knobs-wt-prompt{color:var(--_k-fg-3);user-select:none;-webkit-user-select:none}
|
|
18462
20000
|
.knobs-wt-cmd{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
|
|
@@ -18464,8 +20002,8 @@ var walkthroughCss = `
|
|
|
18464
20002
|
.knobs-wt-metric{display:flex;flex-direction:column;gap:var(--_k-space-sibling)}
|
|
18465
20003
|
.knobs-wt-mlabel{color:var(--_k-fg-2)}
|
|
18466
20004
|
.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"}
|
|
18467
|
-
.knobs-wt-mfrom{color:var(--_k-fg-3)
|
|
18468
|
-
.knobs-wt-mto{font-size:var(--_k-type-
|
|
20005
|
+
.knobs-wt-mfrom{color:var(--_k-fg-3)}
|
|
20006
|
+
.knobs-wt-mto{font-size:var(--_k-type-title);line-height:var(--_k-leading-title);font-weight:var(--_k-weight-label)}
|
|
18469
20007
|
.knobs-wt-mof{color:var(--_k-fg-3)}
|
|
18470
20008
|
.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)}
|
|
18471
20009
|
.knobs-wt-mdelta[data-tone="good"]{--kw-tone:var(--kw-good)}
|
|
@@ -18480,51 +20018,40 @@ var walkthroughCss = `
|
|
|
18480
20018
|
.knobs-wt-callout[data-tone="warn"]{--kw-tone:var(--_k-warn)}
|
|
18481
20019
|
.knobs-wt-callout[data-tone="bad"]{--kw-tone:var(--_k-danger)}
|
|
18482
20020
|
.knobs-wt-text{max-width:var(--kw-measure);color:var(--_k-fg-2)}
|
|
18483
|
-
.knobs-wt-hold{border-radius:
|
|
20021
|
+
.knobs-wt-hold{border-radius:var(--kw-frame-r);background:var(--_k-fill)}
|
|
18484
20022
|
.knobs-wt-failed{display:flex;align-items:center;justify-content:center;min-height:80px;color:var(--_k-fg-3);font-size:var(--_k-type-small)}
|
|
18485
20023
|
|
|
20024
|
+
/* Annotations: a calm ring round a box, a dot or an arrow at a point, a small label beside it;
|
|
20025
|
+
each fades in and out with a small blur on the quick spring as its time comes and goes, and a
|
|
20026
|
+
page's switch (the eye, or a) hides them all. Placed in px by script, 1:1 with the picture;
|
|
20027
|
+
last in their box, so they sit over the picture (and under a compare's grip and labels). */
|
|
20028
|
+
.knobs-wt-anno{position:absolute;inset:0;pointer-events:none;overflow:hidden;transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
|
|
20029
|
+
.knobs-wt-anno:not([data-settled]),.knobs-wt-anno:not([data-settled]) *{transition:none!important}
|
|
20030
|
+
[data-annotations="off"] .knobs-wt-anno,.knobs-wt-anno[data-moving]{opacity:0;filter:blur(3px)}
|
|
20031
|
+
.knobs-wt-mark{--kw-mark:var(--_k-accent);position:absolute;left:0;top:0;color:var(--kw-mark);transition:opacity var(--kw-quick-ms) var(--kw-quick),filter var(--kw-quick-ms) var(--kw-quick)}
|
|
20032
|
+
.knobs-wt-mark[data-hidden]{opacity:0;filter:blur(3px)}
|
|
20033
|
+
.knobs-wt-mark[data-shape="box"]{border-radius:6px;box-shadow:0 0 0 1.5px var(--kw-mark),0 0 0 4px color-mix(in srgb,var(--_k-bg) 45%,transparent)}
|
|
20034
|
+
.knobs-wt-mark[data-shape="dot"]::before{content:"";position:absolute;left:-6px;top:-6px;width:12px;height:12px;border-radius:50%;background:var(--kw-mark);box-shadow:0 0 0 3px color-mix(in srgb,var(--kw-mark) 30%,transparent)}
|
|
20035
|
+
.knobs-wt-anno-arrow{position:absolute;right:0;bottom:0;filter:drop-shadow(0 0 2px var(--_k-bg))}
|
|
20036
|
+
.knobs-wt-mark-label{position:absolute;left:0;bottom:calc(100% + 6px);max-width:240px;padding:2px 6px;border-radius:calc(var(--_k-radius) / 2);background:var(--_k-bg-overlay);color:var(--_k-fg);
|
|
20037
|
+
font:var(--_k-weight-label) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);white-space:nowrap;overflow:hidden;box-shadow:var(--_k-shadow-overlay);${fade2}}
|
|
20038
|
+
.knobs-wt-mark:is([data-shape="dot"],[data-shape="arrow"]) .knobs-wt-mark-label{left:12px;bottom:auto;top:10px}
|
|
20039
|
+
.knobs-wt-mark[data-shape="arrow"] .knobs-wt-mark-label{left:auto;right:30px;top:auto;bottom:30px}
|
|
20040
|
+
.knobs-ui .knobs-wt-anno-toggle{all:revert;box-sizing:border-box;position:absolute;top:8px;right:8px;display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;margin:0;
|
|
20041
|
+
border-radius:var(--_k-radius);background:color-mix(in srgb,var(--_k-bg) 72%,transparent);color:var(--_k-fg-2);cursor:pointer;opacity:.6;outline:none;
|
|
20042
|
+
transition:opacity calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
20043
|
+
.knobs-ui .knobs-wt-anno-toggle:is(:hover,:focus-visible){opacity:1;color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
20044
|
+
.knobs-ui .knobs-wt-anno-toggle:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
|
|
20045
|
+
.knobs-cmp-stage>.knobs-wt-anno-toggle{top:auto;bottom:8px}
|
|
20046
|
+
|
|
18486
20047
|
/* ——— data blocks: history, stack, table, chapters. On the step's card, so no card of their own:
|
|
18487
20048
|
hairlines and fills only. Tones read the status tokens; hover fades are the panel's own. ——— */
|
|
18488
|
-
:where(.knobs-wt-
|
|
20049
|
+
:where(.knobs-wt-stack,.knobs-wt-tablebox,.knobs-wt-chapters){--kw-line:var(--_k-leading-body);--kw-rail:var(--_k-border-strong)}
|
|
18489
20050
|
:where(.knobs-wt-slot) [data-tone="good"]{--kw-tone:var(--kw-good)}
|
|
18490
20051
|
:where(.knobs-wt-slot) [data-tone="warn"]{--kw-tone:var(--_k-warn)}
|
|
18491
20052
|
:where(.knobs-wt-slot) [data-tone="bad"]{--kw-tone:var(--_k-danger)}
|
|
18492
20053
|
:where(.knobs-wt-slot) [data-tone="info"]{--kw-tone:var(--kw-info)}
|
|
18493
20054
|
:where(.knobs-wt-slot) [data-tone="dim"]{--kw-tone:var(--_k-fg-3)}
|
|
18494
|
-
.knobs-wt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
|
|
18495
|
-
.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);
|
|
18496
|
-
text-decoration:none;white-space:nowrap;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
|
|
18497
|
-
.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)}
|
|
18498
|
-
|
|
18499
|
-
/* A history: dates down the left, a spine of tone dots joined by a hairline (broken around each
|
|
18500
|
-
dot), the event beside it. Inferred events take a ring for a dot and a dashed tag. */
|
|
18501
|
-
.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)}
|
|
18502
|
-
.knobs-wt-hgroup{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
18503
|
-
.knobs-wt-hhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px}
|
|
18504
|
-
.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)}
|
|
18505
|
-
.knobs-wt-hsum{color:var(--_k-fg-3);font-size:var(--_k-type-small);font-variant-numeric:tabular-nums}
|
|
18506
|
-
.knobs-wt-hlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
|
|
18507
|
-
.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}
|
|
18508
|
-
.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)}
|
|
18509
|
-
.knobs-wt-hage{color:var(--_k-fg-3);font-size:var(--_k-type-small);line-height:var(--_k-leading-small)}
|
|
18510
|
-
.knobs-wt-hspine{grid-area:spine;position:relative;align-self:stretch;margin-block:-5px}
|
|
18511
|
-
.knobs-wt-hspine::before,.knobs-wt-hspine::after{content:"";position:absolute;left:6.5px;width:1px;background:var(--kw-rail)}
|
|
18512
|
-
.knobs-wt-hspine::before{top:0;height:calc(var(--kw-hy) - 6px)}
|
|
18513
|
-
.knobs-wt-hspine::after{top:calc(var(--kw-hy) + 6px);bottom:0}
|
|
18514
|
-
.knobs-wt-hev:first-child .knobs-wt-hspine::before,.knobs-wt-hev:last-child .knobs-wt-hspine::after{content:none}
|
|
18515
|
-
.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))}
|
|
18516
|
-
.knobs-wt-hev[data-type="release"] .knobs-wt-hdot{border-radius:1.5px;rotate:45deg;scale:.9}
|
|
18517
|
-
.knobs-wt-hev[data-type="audit"] .knobs-wt-hdot{border-radius:1.5px}
|
|
18518
|
-
.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))}
|
|
18519
|
-
.knobs-wt-hmain{grid-area:main;display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
18520
|
-
.knobs-wt-htitle{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-weight:var(--_k-weight-label);color:var(--_k-fg)}
|
|
18521
|
-
.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}
|
|
18522
|
-
.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}
|
|
18523
|
-
.knobs-ui .knobs-wt-hdetail{margin:0;max-width:var(--k-walkthrough-measure,68ch);color:var(--_k-fg-2)}
|
|
18524
|
-
@container knobs-wt-slot (max-width:520px){
|
|
18525
|
-
.knobs-wt-hev{grid-template-columns:14px minmax(0,1fr);grid-template-areas:"spine when" "spine main"}
|
|
18526
|
-
.knobs-wt-hwhen{flex-direction:row;flex-wrap:wrap;align-items:baseline;column-gap:8px}
|
|
18527
|
-
}
|
|
18528
20055
|
|
|
18529
20056
|
/* A stack: the trunk at the top, each PR hanging off the branch it merges into, rails drawn as a
|
|
18530
20057
|
git graph (solid, so where two meet they don't darken). A PR with a step goes there on a click. */
|
|
@@ -18569,6 +20096,12 @@ var walkthroughCss = `
|
|
|
18569
20096
|
/* Too narrow for its columns, each row becomes a block: its primary cell titling the rest as term
|
|
18570
20097
|
and value, the blocks parted by hairlines. Scrolling sideways is the last resort (stack: never). */
|
|
18571
20098
|
.knobs-wt-tablebox{min-width:0}
|
|
20099
|
+
/* A row with a step goes there: the whole row, a wash under the pointer, no arrow, so rows with and
|
|
20100
|
+
without one line up. */
|
|
20101
|
+
.knobs-wt-tablebox [data-go]{cursor:pointer;outline:none}
|
|
20102
|
+
.knobs-wt-tablebox tr[data-go]>td,.knobs-wt-trow[data-go]{transition:background calc(var(--_k-hover-out) * 1ms)}
|
|
20103
|
+
.knobs-wt-tablebox tr[data-go]:is(:hover,:focus-visible)>td,.knobs-wt-trow[data-go]:is(:hover,:focus-visible){background:var(--_ku-wash);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
20104
|
+
.knobs-wt-tablebox [data-go]:focus-visible{box-shadow:inset 0 0 0 1px var(--_k-focus)}
|
|
18572
20105
|
.knobs-wt-tablebox[data-measure] table{width:min-content}
|
|
18573
20106
|
.knobs-wt-tstack{display:none;flex-direction:column;min-width:0}
|
|
18574
20107
|
.knobs-wt-tablebox[data-stacked]>.knobs-wt-table{display:none}
|
|
@@ -18592,7 +20125,7 @@ var walkthroughCss = `
|
|
|
18592
20125
|
.knobs-wt-table{max-height:520px;overflow:auto;overscroll-behavior-x:contain;outline:none;scrollbar-width:thin;scrollbar-color:transparent transparent;
|
|
18593
20126
|
transition:scrollbar-color calc(var(--_k-hover-out) * 1ms)}
|
|
18594
20127
|
.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)}
|
|
18595
|
-
.knobs-wt-table:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus);border-radius:
|
|
20128
|
+
.knobs-wt-table:focus-visible{box-shadow:inset 0 0 0 2px var(--_k-focus);border-radius:var(--kw-frame-r)}
|
|
18596
20129
|
@supports (animation-timeline:scroll()){
|
|
18597
20130
|
.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;
|
|
18598
20131
|
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%;
|
|
@@ -18740,6 +20273,8 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
18740
20273
|
fit.style.setProperty("--kw-aspect", String(aspect));
|
|
18741
20274
|
let lastZoom = "all";
|
|
18742
20275
|
let lastLoop = "off";
|
|
20276
|
+
let marks = null;
|
|
20277
|
+
let marking = null;
|
|
18743
20278
|
const c = compare(fit, {
|
|
18744
20279
|
...look,
|
|
18745
20280
|
before: side(before, "Before"),
|
|
@@ -18752,6 +20287,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
18752
20287
|
onMode: (next) => {
|
|
18753
20288
|
fit.toggleAttribute("data-overlay", next !== "side");
|
|
18754
20289
|
onEvent?.({ what: "mode", detail: next });
|
|
20290
|
+
const layer = marks?.element;
|
|
20291
|
+
if (layer) {
|
|
20292
|
+
layer.dataset.moving = "";
|
|
20293
|
+
setTimeout(() => {
|
|
20294
|
+
delete layer.dataset.moving;
|
|
20295
|
+
marks?.update();
|
|
20296
|
+
}, MARK_GLIDE);
|
|
20297
|
+
}
|
|
18755
20298
|
},
|
|
18756
20299
|
onView: (view) => {
|
|
18757
20300
|
const z = view.zoom;
|
|
@@ -18762,8 +20305,23 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
18762
20305
|
const loopKey = l ? `${formatTime(l.start)}–${formatTime(l.end)}` : "off";
|
|
18763
20306
|
if (loopKey !== lastLoop)
|
|
18764
20307
|
onEvent?.({ what: "loop", detail: lastLoop = loopKey });
|
|
20308
|
+
marking?.kick(MARK_GLIDE);
|
|
18765
20309
|
}
|
|
18766
20310
|
});
|
|
20311
|
+
const stage = fit.querySelector(".knobs-cmp-stage");
|
|
20312
|
+
if (m.annotations?.length && stage) {
|
|
20313
|
+
const shape = { w: aspect ?? DEFAULT_ASPECT, h: 1 };
|
|
20314
|
+
marks = annotationLayer(stage, m.annotations, () => {
|
|
20315
|
+
const l = compareLayout({ x: 0, y: 0, w: stage.clientWidth, h: stage.clientHeight }, shape, shape);
|
|
20316
|
+
return {
|
|
20317
|
+
rects: c.mode === "side" ? [l.side.before, l.side.after] : [l.overlay.after],
|
|
20318
|
+
view: c.zoom,
|
|
20319
|
+
time: c.time
|
|
20320
|
+
};
|
|
20321
|
+
});
|
|
20322
|
+
const layer = marks;
|
|
20323
|
+
marking = follow(doc.defaultView, () => c.playing, () => layer.update());
|
|
20324
|
+
}
|
|
18767
20325
|
const chapters = m.chapters?.length ? m.chapters : null;
|
|
18768
20326
|
if (chapters)
|
|
18769
20327
|
c.timeline?.update({
|
|
@@ -18780,10 +20338,14 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
18780
20338
|
const unlisten = c.timeline?.listen({
|
|
18781
20339
|
onSeek: (time, source) => {
|
|
18782
20340
|
pills?.sync();
|
|
20341
|
+
marking?.kick();
|
|
18783
20342
|
if (source !== "play")
|
|
18784
20343
|
onEvent?.({ what: "seek", time: round2(time) });
|
|
18785
20344
|
},
|
|
18786
|
-
onPlay: (on) =>
|
|
20345
|
+
onPlay: (on) => {
|
|
20346
|
+
marking?.kick();
|
|
20347
|
+
onEvent?.({ what: on ? "play" : "pause", time: round2(c.time) });
|
|
20348
|
+
}
|
|
18787
20349
|
});
|
|
18788
20350
|
requestAnimationFrame(() => requestAnimationFrame(() => fit.dataset.settled = ""));
|
|
18789
20351
|
return {
|
|
@@ -18810,6 +20372,8 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
18810
20372
|
};
|
|
18811
20373
|
},
|
|
18812
20374
|
dispose: () => {
|
|
20375
|
+
marking?.stop();
|
|
20376
|
+
marks?.dispose();
|
|
18813
20377
|
unlisten?.();
|
|
18814
20378
|
pills?.dispose();
|
|
18815
20379
|
c.dispose();
|
|
@@ -18818,6 +20382,7 @@ async function mountCompare(host, m, look, alive, onEvent) {
|
|
|
18818
20382
|
};
|
|
18819
20383
|
}
|
|
18820
20384
|
var round2 = (v) => Math.round(v * 100) / 100;
|
|
20385
|
+
var MARK_GLIDE = 450;
|
|
18821
20386
|
var zoomedIn = (z) => z.w < 0.999 || z.h < 0.999;
|
|
18822
20387
|
function mountVideo(host, m, onEvent) {
|
|
18823
20388
|
const doc = host.ownerDocument;
|
|
@@ -18865,6 +20430,16 @@ function mountVideo(host, m, onEvent) {
|
|
|
18865
20430
|
if (pills)
|
|
18866
20431
|
for (const type of ["timeupdate", "seeking", "seeked"])
|
|
18867
20432
|
v.addEventListener(type, sync);
|
|
20433
|
+
const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
|
|
20434
|
+
rects: [
|
|
20435
|
+
contain({ w: frame.clientWidth, h: frame.clientHeight }, { w: v.videoWidth, h: v.videoHeight })
|
|
20436
|
+
],
|
|
20437
|
+
time: v.currentTime
|
|
20438
|
+
})) : null;
|
|
20439
|
+
const marking = marks ? follow(doc.defaultView, () => !v.paused, () => marks.update()) : null;
|
|
20440
|
+
if (marking)
|
|
20441
|
+
for (const type of ["play", "seeked", "loadedmetadata", "timeupdate"])
|
|
20442
|
+
v.addEventListener(type, () => marking.kick());
|
|
18868
20443
|
return {
|
|
18869
20444
|
pause: () => v.pause(),
|
|
18870
20445
|
seekBy: (d) => {
|
|
@@ -18878,6 +20453,8 @@ function mountVideo(host, m, onEvent) {
|
|
|
18878
20453
|
...pills?.current ? { chapter: pills.current.label } : {}
|
|
18879
20454
|
}),
|
|
18880
20455
|
dispose: () => {
|
|
20456
|
+
marking?.stop();
|
|
20457
|
+
marks?.dispose();
|
|
18881
20458
|
pills?.dispose();
|
|
18882
20459
|
v.pause();
|
|
18883
20460
|
v.removeAttribute("src");
|
|
@@ -18895,9 +20472,52 @@ function mountImage(host, m) {
|
|
|
18895
20472
|
img.decoding = "async";
|
|
18896
20473
|
img.alt = m.alt ?? m.label ?? "";
|
|
18897
20474
|
img.src = m.src;
|
|
20475
|
+
if (!img.complete) {
|
|
20476
|
+
frame.dataset.loading = "";
|
|
20477
|
+
img.addEventListener("load", () => delete frame.dataset.loading, { once: true });
|
|
20478
|
+
img.addEventListener("error", () => delete frame.dataset.loading, { once: true });
|
|
20479
|
+
}
|
|
18898
20480
|
frame.append(img);
|
|
18899
20481
|
host.append(frame);
|
|
18900
|
-
|
|
20482
|
+
const marks = m.annotations?.length ? annotationLayer(frame, m.annotations, () => ({
|
|
20483
|
+
rects: [{ x: 0, y: 0, w: frame.clientWidth, h: frame.clientHeight }]
|
|
20484
|
+
})) : null;
|
|
20485
|
+
if (marks)
|
|
20486
|
+
img.addEventListener("load", () => marks.update(), { once: true });
|
|
20487
|
+
return {
|
|
20488
|
+
dispose: () => {
|
|
20489
|
+
marks?.dispose();
|
|
20490
|
+
frame.remove();
|
|
20491
|
+
}
|
|
20492
|
+
};
|
|
20493
|
+
}
|
|
20494
|
+
function contain(box, natural) {
|
|
20495
|
+
if (!(natural.w > 0 && natural.h > 0))
|
|
20496
|
+
return { x: 0, y: 0, w: box.w, h: box.h };
|
|
20497
|
+
const k = Math.min(box.w / natural.w, box.h / natural.h);
|
|
20498
|
+
const w = natural.w * k;
|
|
20499
|
+
const h = natural.h * k;
|
|
20500
|
+
return { x: (box.w - w) / 2, y: (box.h - h) / 2, w, h };
|
|
20501
|
+
}
|
|
20502
|
+
function follow(win, on, fn) {
|
|
20503
|
+
let raf = 0;
|
|
20504
|
+
let until = 0;
|
|
20505
|
+
const tick = () => {
|
|
20506
|
+
fn();
|
|
20507
|
+
raf = on() || performance.now() < until ? win.requestAnimationFrame(tick) : 0;
|
|
20508
|
+
};
|
|
20509
|
+
return {
|
|
20510
|
+
kick(ms = 0) {
|
|
20511
|
+
until = Math.max(until, performance.now() + ms);
|
|
20512
|
+
if (!raf)
|
|
20513
|
+
raf = win.requestAnimationFrame(tick);
|
|
20514
|
+
},
|
|
20515
|
+
stop() {
|
|
20516
|
+
if (raf)
|
|
20517
|
+
win.cancelAnimationFrame(raf);
|
|
20518
|
+
raf = 0;
|
|
20519
|
+
}
|
|
20520
|
+
};
|
|
18901
20521
|
}
|
|
18902
20522
|
async function mountDiff(host, m, look, alive, onEvent) {
|
|
18903
20523
|
const lib = await Promise.resolve().then(() => (init_diff(), {}));
|
|
@@ -19051,6 +20671,14 @@ function mediaSlot(container, options) {
|
|
|
19051
20671
|
let pending = false;
|
|
19052
20672
|
let disposed = false;
|
|
19053
20673
|
let generation = 0;
|
|
20674
|
+
const born = performance.now();
|
|
20675
|
+
const enter = () => {
|
|
20676
|
+
const win = doc.defaultView;
|
|
20677
|
+
if (!win || performance.now() - born < 120 || reducedMotion(win))
|
|
20678
|
+
return;
|
|
20679
|
+
host.dataset.entering = "";
|
|
20680
|
+
win.requestAnimationFrame(() => win.requestAnimationFrame(() => delete host.dataset.entering));
|
|
20681
|
+
};
|
|
19054
20682
|
const hold = (px) => {
|
|
19055
20683
|
host.style.minHeight = px === null ? "" : `${px}px`;
|
|
19056
20684
|
host.classList.toggle("knobs-wt-hold", px !== null);
|
|
@@ -19070,6 +20698,7 @@ function mediaSlot(container, options) {
|
|
|
19070
20698
|
if (!alive())
|
|
19071
20699
|
return r.dispose();
|
|
19072
20700
|
mounted = r;
|
|
20701
|
+
enter();
|
|
19073
20702
|
if (filled)
|
|
19074
20703
|
r.fill?.(true);
|
|
19075
20704
|
if (r.ready)
|
|
@@ -19162,14 +20791,14 @@ function mediaSlot(container, options) {
|
|
|
19162
20791
|
var CHECK3 = icon("check", { small: true });
|
|
19163
20792
|
|
|
19164
20793
|
// src/ui/walkthrough.ts
|
|
19165
|
-
var
|
|
20794
|
+
var el3 = (doc, tag, cls) => {
|
|
19166
20795
|
const n = doc.createElement(tag);
|
|
19167
20796
|
if (cls)
|
|
19168
20797
|
n.className = cls;
|
|
19169
20798
|
return n;
|
|
19170
20799
|
};
|
|
19171
20800
|
var prose2 = (doc, tag, cls, text) => {
|
|
19172
|
-
const n =
|
|
20801
|
+
const n = el3(doc, tag, cls);
|
|
19173
20802
|
n.innerHTML = inlineMarkdown(text);
|
|
19174
20803
|
return n;
|
|
19175
20804
|
};
|
|
@@ -19206,37 +20835,46 @@ function tasteTest(container, options) {
|
|
|
19206
20835
|
...o.media !== undefined ? { media: fillMedia(o.media) } : {},
|
|
19207
20836
|
suggested: o.suggested === true
|
|
19208
20837
|
});
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
root
|
|
19212
|
-
|
|
20838
|
+
const pickable = options.pickable !== false;
|
|
20839
|
+
let picked = pickable ? options.picked ?? null : null;
|
|
20840
|
+
const root = el3(doc, "div", "knobs-wt-options");
|
|
20841
|
+
if (pickable) {
|
|
20842
|
+
root.setAttribute("role", "radiogroup");
|
|
20843
|
+
root.setAttribute("aria-label", "Options");
|
|
20844
|
+
} else
|
|
20845
|
+
root.dataset.static = "";
|
|
19213
20846
|
root.style.setProperty("--kw-n", String(Math.min(list.length, 3)));
|
|
19214
20847
|
if (!container.closest(".knobs-ui"))
|
|
19215
20848
|
applyLook2(root, options);
|
|
19216
20849
|
const slots = [];
|
|
19217
20850
|
const tiles = list.map((o, i) => {
|
|
19218
|
-
const tile =
|
|
19219
|
-
|
|
20851
|
+
const tile = el3(doc, "div", "knobs-wt-option");
|
|
20852
|
+
if (!pickable) {
|
|
20853
|
+
const name = el3(doc, "span", "knobs-wt-slot-label");
|
|
20854
|
+
name.textContent = o.label;
|
|
20855
|
+
tile.append(name);
|
|
20856
|
+
}
|
|
20857
|
+
const pick = el3(doc, "button", "knobs-wt-pick");
|
|
19220
20858
|
pick.type = "button";
|
|
19221
20859
|
pick.setAttribute("role", "radio");
|
|
19222
20860
|
if (i < 9)
|
|
19223
20861
|
pick.setAttribute("aria-keyshortcuts", String(i + 1));
|
|
19224
|
-
const letter =
|
|
20862
|
+
const letter = el3(doc, "span", "knobs-wt-letter");
|
|
19225
20863
|
letter.textContent = o.letter;
|
|
19226
|
-
const name =
|
|
20864
|
+
const name = el3(doc, "span", "knobs-wt-olabel");
|
|
19227
20865
|
name.textContent = o.label;
|
|
19228
20866
|
pick.append(letter, name);
|
|
19229
20867
|
if (o.suggested) {
|
|
19230
|
-
const s =
|
|
20868
|
+
const s = el3(doc, "span", "knobs-wt-suggested");
|
|
19231
20869
|
s.textContent = "Suggested";
|
|
19232
20870
|
pick.append(s);
|
|
19233
20871
|
}
|
|
19234
20872
|
if (i < 9) {
|
|
19235
|
-
const key =
|
|
20873
|
+
const key = el3(doc, "span", "knobs-wt-key");
|
|
19236
20874
|
key.textContent = String(i + 1);
|
|
19237
20875
|
pick.append(key);
|
|
19238
20876
|
}
|
|
19239
|
-
const check =
|
|
20877
|
+
const check = el3(doc, "span", "knobs-wt-check");
|
|
19240
20878
|
check.innerHTML = CHECK3;
|
|
19241
20879
|
pick.append(check);
|
|
19242
20880
|
pick.addEventListener("click", () => {
|
|
@@ -19244,7 +20882,8 @@ function tasteTest(container, options) {
|
|
|
19244
20882
|
api.pick(next);
|
|
19245
20883
|
options.onPick?.(next);
|
|
19246
20884
|
});
|
|
19247
|
-
|
|
20885
|
+
if (pickable)
|
|
20886
|
+
tile.append(pick);
|
|
19248
20887
|
if (o.media)
|
|
19249
20888
|
slots.push(mediaSlot(tile, {
|
|
19250
20889
|
...look,
|
|
@@ -19301,7 +20940,7 @@ function walkthrough(container, options) {
|
|
|
19301
20940
|
injectWalkthroughStyles(doc);
|
|
19302
20941
|
const look = lookOf2(options);
|
|
19303
20942
|
const storage = options.storage === undefined ? browserStorage() : options.storage;
|
|
19304
|
-
const root =
|
|
20943
|
+
const root = el3(doc, "div", "knobs-walkthrough");
|
|
19305
20944
|
applyLook2(root, options);
|
|
19306
20945
|
container.append(root);
|
|
19307
20946
|
let page = normalizeWalkthrough(options.doc);
|
|
@@ -19327,7 +20966,8 @@ function walkthrough(container, options) {
|
|
|
19327
20966
|
let tocRows = [];
|
|
19328
20967
|
let discs = [];
|
|
19329
20968
|
let full = null;
|
|
19330
|
-
const
|
|
20969
|
+
const review = options.review === true;
|
|
20970
|
+
const noteMode = review ? options.noteBar ?? "collapsed" : "hidden";
|
|
19331
20971
|
let reserve = noteBarReserve(52, DOCK_INSET, DOCK_GAP);
|
|
19332
20972
|
const viewOf = (key) => views.find((v) => v.step.key === key);
|
|
19333
20973
|
const feedback = () => collectFeedback(page, state);
|
|
@@ -19513,7 +21153,7 @@ function walkthrough(container, options) {
|
|
|
19513
21153
|
return true;
|
|
19514
21154
|
return false;
|
|
19515
21155
|
};
|
|
19516
|
-
const scrim =
|
|
21156
|
+
const scrim = el3(doc, "div", "knobs-wt-scrim");
|
|
19517
21157
|
scrim.addEventListener("click", () => setFull(false));
|
|
19518
21158
|
const fullRect = () => ({
|
|
19519
21159
|
x: DOCK_INSET,
|
|
@@ -19563,7 +21203,7 @@ function walkthrough(container, options) {
|
|
|
19563
21203
|
}
|
|
19564
21204
|
if (!full) {
|
|
19565
21205
|
const r = v.card.getBoundingClientRect();
|
|
19566
|
-
const hold =
|
|
21206
|
+
const hold = el3(doc, "div", "knobs-wt-hold-card");
|
|
19567
21207
|
hold.style.height = `${r.height}px`;
|
|
19568
21208
|
v.card.before(hold);
|
|
19569
21209
|
v.card.dataset.full = "";
|
|
@@ -19713,7 +21353,7 @@ function walkthrough(container, options) {
|
|
|
19713
21353
|
toast("Couldn't reach the microphone", { tone: "warn", look, doc });
|
|
19714
21354
|
}
|
|
19715
21355
|
};
|
|
19716
|
-
const backPill =
|
|
21356
|
+
const backPill = el3(doc, "button", "knobs-wt-back");
|
|
19717
21357
|
backPill.type = "button";
|
|
19718
21358
|
let backTo = null;
|
|
19719
21359
|
let backTimer = 0;
|
|
@@ -19733,7 +21373,7 @@ function walkthrough(container, options) {
|
|
|
19733
21373
|
return;
|
|
19734
21374
|
backTo = key;
|
|
19735
21375
|
backPill.innerHTML = ICON.back;
|
|
19736
|
-
const words =
|
|
21376
|
+
const words = el3(doc, "span", "knobs-wt-back-label");
|
|
19737
21377
|
words.textContent = `Back to ${plainMarkdown(v.step.title)}`;
|
|
19738
21378
|
backPill.append(words);
|
|
19739
21379
|
backPill.dataset.on = "";
|
|
@@ -19741,16 +21381,16 @@ function walkthrough(container, options) {
|
|
|
19741
21381
|
backTimer = win.setTimeout(hideBack, BACK_MS);
|
|
19742
21382
|
};
|
|
19743
21383
|
const bar = (() => {
|
|
19744
|
-
const dock =
|
|
19745
|
-
const box =
|
|
19746
|
-
const on =
|
|
19747
|
-
const row =
|
|
19748
|
-
const input =
|
|
21384
|
+
const dock = el3(doc, "div", "knobs-wt-dock");
|
|
21385
|
+
const box = el3(doc, "div", "knobs-wt-notebar");
|
|
21386
|
+
const on = el3(doc, "div", "knobs-wt-on");
|
|
21387
|
+
const row = el3(doc, "div", "knobs-wt-row");
|
|
21388
|
+
const input = el3(doc, "textarea", "knobs-wt-input");
|
|
19749
21389
|
input.rows = 1;
|
|
19750
21390
|
input.setAttribute("aria-label", "Note");
|
|
19751
|
-
const attach =
|
|
21391
|
+
const attach = el3(doc, "div", "knobs-wt-attach");
|
|
19752
21392
|
const btn = (icon, label, run) => {
|
|
19753
|
-
const b =
|
|
21393
|
+
const b = el3(doc, "button", "knobs-wt-tool");
|
|
19754
21394
|
b.type = "button";
|
|
19755
21395
|
b.innerHTML = icon;
|
|
19756
21396
|
b.setAttribute("aria-label", label);
|
|
@@ -19761,17 +21401,17 @@ function walkthrough(container, options) {
|
|
|
19761
21401
|
const fullBtn = btn(ICON.full, "Full window · f", () => setFull(!full));
|
|
19762
21402
|
const penBtn = btn(ICON.pen, "Draw on the media", () => setInk(!ink.on));
|
|
19763
21403
|
const micBtn = btn(ICON.mic, "Record a voice note", () => void toggleVoice());
|
|
19764
|
-
const micTime =
|
|
21404
|
+
const micTime = el3(doc, "span", "knobs-wt-rec");
|
|
19765
21405
|
micBtn.append(micTime);
|
|
19766
21406
|
const sendBtn = btn(ICON.send, "Send · Enter", () => send());
|
|
19767
21407
|
sendBtn.dataset.send = "";
|
|
19768
21408
|
row.append(input, attach, fullBtn, penBtn, micBtn, sendBtn);
|
|
19769
21409
|
box.append(on, row);
|
|
19770
|
-
const launch =
|
|
21410
|
+
const launch = el3(doc, "button", "knobs-wt-launch");
|
|
19771
21411
|
launch.type = "button";
|
|
19772
21412
|
launch.innerHTML = ICON.pen;
|
|
19773
21413
|
launch.append("Note");
|
|
19774
|
-
const kbd =
|
|
21414
|
+
const kbd = el3(doc, "kbd");
|
|
19775
21415
|
kbd.textContent = "n";
|
|
19776
21416
|
launch.append(kbd);
|
|
19777
21417
|
launch.setAttribute("aria-label", "Write a note (n)");
|
|
@@ -19823,9 +21463,9 @@ function walkthrough(container, options) {
|
|
|
19823
21463
|
}
|
|
19824
21464
|
});
|
|
19825
21465
|
const chip = (text, clear) => {
|
|
19826
|
-
const c =
|
|
21466
|
+
const c = el3(doc, "span", "knobs-wt-chip-a");
|
|
19827
21467
|
c.append(text);
|
|
19828
|
-
const x =
|
|
21468
|
+
const x = el3(doc, "button", "knobs-wt-x");
|
|
19829
21469
|
x.type = "button";
|
|
19830
21470
|
x.innerHTML = ICON.close;
|
|
19831
21471
|
x.setAttribute("aria-label", `Remove ${text}`);
|
|
@@ -19974,9 +21614,9 @@ function walkthrough(container, options) {
|
|
|
19974
21614
|
}
|
|
19975
21615
|
};
|
|
19976
21616
|
const renderNote = (v, n, entering = false) => {
|
|
19977
|
-
const row =
|
|
21617
|
+
const row = el3(doc, "div", "knobs-wt-n");
|
|
19978
21618
|
row.dataset.id = n.id;
|
|
19979
|
-
const meta =
|
|
21619
|
+
const meta = el3(doc, "span", "knobs-wt-n-meta");
|
|
19980
21620
|
const m = n.context.media;
|
|
19981
21621
|
meta.textContent = m?.time !== undefined ? formatTime(m.time) : m?.file ? m.file.split("/").pop() : "·";
|
|
19982
21622
|
if (m?.time !== undefined) {
|
|
@@ -19992,11 +21632,11 @@ function walkthrough(container, options) {
|
|
|
19992
21632
|
});
|
|
19993
21633
|
}
|
|
19994
21634
|
row.append(meta);
|
|
19995
|
-
const body =
|
|
21635
|
+
const body = el3(doc, "div", "knobs-wt-n-body");
|
|
19996
21636
|
if (n.text)
|
|
19997
21637
|
body.append(prose2(doc, "p", "knobs-wt-n-text", n.text));
|
|
19998
21638
|
if (n.voice) {
|
|
19999
|
-
const a =
|
|
21639
|
+
const a = el3(doc, "audio", "knobs-wt-n-voice");
|
|
20000
21640
|
a.controls = true;
|
|
20001
21641
|
a.preload = "none";
|
|
20002
21642
|
a.src = n.voice.url;
|
|
@@ -20016,7 +21656,7 @@ function walkthrough(container, options) {
|
|
|
20016
21656
|
body.append(thumb);
|
|
20017
21657
|
}
|
|
20018
21658
|
row.append(body);
|
|
20019
|
-
const x =
|
|
21659
|
+
const x = el3(doc, "button", "knobs-wt-x");
|
|
20020
21660
|
x.type = "button";
|
|
20021
21661
|
x.innerHTML = ICON.close;
|
|
20022
21662
|
x.setAttribute("aria-label", "Delete this note");
|
|
@@ -20056,15 +21696,22 @@ function walkthrough(container, options) {
|
|
|
20056
21696
|
row.addEventListener("transitionend", done, { once: true });
|
|
20057
21697
|
win.setTimeout(done, 600);
|
|
20058
21698
|
};
|
|
20059
|
-
const metaLine = (cls) =>
|
|
21699
|
+
const metaLine = (cls) => el3(doc, "div", `knobs-wt-metaline ${cls}`);
|
|
20060
21700
|
const metaText = (line, text, cls) => {
|
|
20061
|
-
const s =
|
|
21701
|
+
const s = el3(doc, "span", cls);
|
|
20062
21702
|
s.textContent = text;
|
|
20063
21703
|
line.append(s);
|
|
20064
21704
|
return s;
|
|
20065
21705
|
};
|
|
21706
|
+
const findPRIn = (n) => findPR(page.prs, n, page.repo);
|
|
20066
21707
|
const metaLink = (line, text, href) => {
|
|
20067
|
-
const
|
|
21708
|
+
const n = prNumber(href);
|
|
21709
|
+
if (n) {
|
|
21710
|
+
const chip = prChip(doc, n, { href, ...findPRIn(n) });
|
|
21711
|
+
line.append(chip);
|
|
21712
|
+
return chip;
|
|
21713
|
+
}
|
|
21714
|
+
const a = el3(doc, "a");
|
|
20068
21715
|
a.href = href;
|
|
20069
21716
|
a.target = "_blank";
|
|
20070
21717
|
a.rel = "noreferrer";
|
|
@@ -20074,58 +21721,58 @@ function walkthrough(container, options) {
|
|
|
20074
21721
|
return a;
|
|
20075
21722
|
};
|
|
20076
21723
|
const feedItem = (f, now) => {
|
|
20077
|
-
const li =
|
|
21724
|
+
const li = el3(doc, "li", "knobs-wt-fi");
|
|
20078
21725
|
li.dataset.kind = f.kind;
|
|
20079
|
-
const q =
|
|
21726
|
+
const q = el3(doc, "div", "knobs-wt-fi-q");
|
|
20080
21727
|
const meta = metaLine("knobs-wt-fi-meta");
|
|
20081
21728
|
if (f.at !== null) {
|
|
20082
21729
|
const when = metaText(meta, ago(f.at, now));
|
|
20083
21730
|
when.title = new Date(f.at).toLocaleString();
|
|
20084
|
-
}
|
|
20085
|
-
metaText(meta, f.kind === "qa" ? "Answer" : "Update");
|
|
21731
|
+
}
|
|
20086
21732
|
const step = f.step ? page.steps.find((s) => s.key === f.step) : undefined;
|
|
20087
21733
|
if (step) {
|
|
20088
21734
|
const section = page.sections.find((s) => s.id === step.section);
|
|
20089
|
-
const b =
|
|
21735
|
+
const b = el3(doc, "button", "knobs-wt-fi-step");
|
|
20090
21736
|
b.type = "button";
|
|
20091
21737
|
b.textContent = section?.link?.label ?? plainMarkdown(step.title);
|
|
20092
21738
|
b.title = `Go to “${plainMarkdown(step.title)}”`;
|
|
20093
21739
|
b.addEventListener("click", () => go(step.key));
|
|
20094
21740
|
meta.append(b);
|
|
20095
21741
|
}
|
|
20096
|
-
if (f.status) {
|
|
21742
|
+
if (f.status && review) {
|
|
20097
21743
|
const id = f.status.toLowerCase();
|
|
20098
21744
|
const shipped = /^(ship|shipped|merged|done)$/.test(id);
|
|
20099
21745
|
const changes = /^(changes?|changes queued)$/.test(id);
|
|
20100
21746
|
const s = metaText(meta, shipped ? "Shipped" : changes ? "Changes queued" : f.status);
|
|
20101
21747
|
s.dataset.status = shipped ? "on" : changes ? "warn" : "";
|
|
20102
21748
|
}
|
|
20103
|
-
|
|
21749
|
+
if (meta.childNodes.length)
|
|
21750
|
+
q.append(meta);
|
|
20104
21751
|
if (f.q)
|
|
20105
21752
|
q.append(prose2(doc, "p", "knobs-wt-fi-ask", f.q));
|
|
20106
21753
|
else
|
|
20107
21754
|
q.append(prose2(doc, "p", "knobs-wt-fi-text", f.text));
|
|
20108
21755
|
li.append(q);
|
|
20109
21756
|
if (f.q || f.trail.length) {
|
|
20110
|
-
const a =
|
|
21757
|
+
const a = el3(doc, "div", "knobs-wt-fi-a");
|
|
20111
21758
|
if (f.q)
|
|
20112
21759
|
a.append(prose2(doc, "div", "knobs-wt-fi-answer", f.text));
|
|
20113
21760
|
if (f.trail.length) {
|
|
20114
|
-
const list =
|
|
21761
|
+
const list = el3(doc, "ol", "knobs-wt-trail");
|
|
20115
21762
|
for (const line of f.trail) {
|
|
20116
21763
|
const i = line.indexOf(" · ");
|
|
20117
|
-
const row =
|
|
20118
|
-
const words =
|
|
21764
|
+
const row = el3(doc, "li");
|
|
21765
|
+
const words = el3(doc, "span", "knobs-wt-trail-text");
|
|
20119
21766
|
words.textContent = i < 0 ? line : line.slice(i + 3);
|
|
20120
21767
|
row.append(words);
|
|
20121
21768
|
if (i >= 0) {
|
|
20122
|
-
const at =
|
|
21769
|
+
const at = el3(doc, "span", "knobs-wt-trail-at");
|
|
20123
21770
|
at.textContent = line.slice(0, i);
|
|
20124
21771
|
row.append(at);
|
|
20125
21772
|
}
|
|
20126
21773
|
list.append(row);
|
|
20127
21774
|
}
|
|
20128
|
-
discs.push(disclosure(a, { label: "Context",
|
|
21775
|
+
discs.push(disclosure(a, { label: "Context", content: list }));
|
|
20129
21776
|
}
|
|
20130
21777
|
li.append(a);
|
|
20131
21778
|
}
|
|
@@ -20150,25 +21797,28 @@ function walkthrough(container, options) {
|
|
|
20150
21797
|
views = [];
|
|
20151
21798
|
root.textContent = "";
|
|
20152
21799
|
root.removeAttribute("data-settled");
|
|
20153
|
-
root.dataset.rail = options.rail === false ? "none" : options.rail === "
|
|
21800
|
+
root.dataset.rail = options.rail === false ? "none" : options.rail === "right" ? "right" : "left";
|
|
20154
21801
|
root.toggleAttribute("data-cards", options.cards === true);
|
|
20155
|
-
|
|
20156
|
-
const
|
|
20157
|
-
const
|
|
20158
|
-
const
|
|
20159
|
-
const
|
|
21802
|
+
root.toggleAttribute("data-review", review);
|
|
21803
|
+
const layout = el3(doc, "div", "knobs-wt-layout");
|
|
21804
|
+
const main = el3(doc, "div", "knobs-wt-main");
|
|
21805
|
+
const flow = el3(doc, "div", "knobs-wt-flow");
|
|
21806
|
+
const head = el3(doc, "header", "knobs-wt-head");
|
|
21807
|
+
const h1 = el3(doc, "h1", "knobs-wt-title");
|
|
20160
21808
|
h1.textContent = page.title;
|
|
20161
21809
|
head.append(h1);
|
|
20162
21810
|
if (page.summary)
|
|
20163
21811
|
head.append(prose2(doc, "p", "knobs-wt-summary", page.summary));
|
|
21812
|
+
if (page.more)
|
|
21813
|
+
head.append(more(page.more));
|
|
20164
21814
|
if (page.meta.length) {
|
|
20165
|
-
const meta =
|
|
21815
|
+
const meta = el3(doc, "div", "knobs-wt-meta");
|
|
20166
21816
|
for (const m of page.meta) {
|
|
20167
|
-
const s =
|
|
21817
|
+
const s = el3(doc, "span");
|
|
20168
21818
|
s.append(m.label);
|
|
20169
|
-
const b =
|
|
21819
|
+
const b = el3(doc, "b");
|
|
20170
21820
|
if (m.href) {
|
|
20171
|
-
const a =
|
|
21821
|
+
const a = el3(doc, "a");
|
|
20172
21822
|
a.href = m.href;
|
|
20173
21823
|
a.target = "_blank";
|
|
20174
21824
|
a.rel = "noreferrer";
|
|
@@ -20184,16 +21834,14 @@ function walkthrough(container, options) {
|
|
|
20184
21834
|
flow.append(head);
|
|
20185
21835
|
let feedEl = null;
|
|
20186
21836
|
if (page.feed.length) {
|
|
20187
|
-
feedEl =
|
|
21837
|
+
feedEl = el3(doc, "section", "knobs-wt-feed");
|
|
20188
21838
|
feedEl.id = "wt-latest";
|
|
20189
21839
|
feedEl.setAttribute("aria-label", "Updates and answers");
|
|
20190
|
-
const fh =
|
|
20191
|
-
const h =
|
|
21840
|
+
const fh = el3(doc, "header", "knobs-wt-feed-head");
|
|
21841
|
+
const h = el3(doc, "h2", "knobs-wt-feed-title");
|
|
20192
21842
|
h.textContent = page.feed.some((f) => f.kind === "qa") ? "Updates & Q&A" : "Updates";
|
|
20193
|
-
|
|
20194
|
-
|
|
20195
|
-
fh.append(h, n);
|
|
20196
|
-
const list = el2(doc, "ol", "knobs-wt-feed-list");
|
|
21843
|
+
fh.append(h);
|
|
21844
|
+
const list = el3(doc, "ol", "knobs-wt-feed-list");
|
|
20197
21845
|
const now = Date.now();
|
|
20198
21846
|
for (const f of page.feed)
|
|
20199
21847
|
list.append(feedItem(f, now));
|
|
@@ -20203,64 +21851,66 @@ function walkthrough(container, options) {
|
|
|
20203
21851
|
let rail = null;
|
|
20204
21852
|
count = fill = tocEl = null;
|
|
20205
21853
|
if (options.rail !== false) {
|
|
20206
|
-
rail =
|
|
20207
|
-
rail.setAttribute("aria-label", "
|
|
20208
|
-
|
|
20209
|
-
count = el2(doc, "div", "knobs-wt-count");
|
|
20210
|
-
const meter = el2(doc, "div", "knobs-wt-bar");
|
|
20211
|
-
fill = el2(doc, "div", "knobs-wt-fill");
|
|
20212
|
-
meter.append(fill);
|
|
20213
|
-
progress.append(count, meter);
|
|
20214
|
-
tocEl = el2(doc, "nav", "knobs-wt-toc");
|
|
21854
|
+
rail = el3(doc, "aside", "knobs-wt-rail");
|
|
21855
|
+
rail.setAttribute("aria-label", "Contents");
|
|
21856
|
+
tocEl = el3(doc, "nav", "knobs-wt-toc");
|
|
20215
21857
|
tocEl.setAttribute("aria-label", "Contents");
|
|
20216
|
-
const ns = page.sections.map((s) => s.kicker && s.link ? s.link.label : "");
|
|
20217
|
-
const nw = Math.max(0, ...ns.map((n) => n.length));
|
|
20218
|
-
tocEl.style.setProperty("--kw-toc-n", nw ? `${nw + 0.5}ch` : "0px");
|
|
20219
|
-
if (!nw)
|
|
20220
|
-
tocEl.dataset.bare = "";
|
|
20221
21858
|
if (feedEl) {
|
|
20222
|
-
const group =
|
|
21859
|
+
const group = el3(doc, "div", "knobs-wt-toc-group");
|
|
20223
21860
|
const target = feedEl;
|
|
20224
|
-
group.append(tocRow("Latest",
|
|
21861
|
+
group.append(tocRow("Latest", false, () => target.scrollIntoView({ block: "start", behavior: reduced() ? "auto" : "smooth" })));
|
|
20225
21862
|
tocEl.append(group);
|
|
20226
21863
|
}
|
|
20227
|
-
|
|
20228
|
-
|
|
20229
|
-
|
|
21864
|
+
if (review) {
|
|
21865
|
+
const progress = el3(doc, "div", "knobs-wt-progress");
|
|
21866
|
+
count = el3(doc, "div", "knobs-wt-count");
|
|
21867
|
+
const meter = el3(doc, "div", "knobs-wt-bar");
|
|
21868
|
+
fill = el3(doc, "div", "knobs-wt-fill");
|
|
21869
|
+
meter.append(fill);
|
|
21870
|
+
progress.append(count, meter);
|
|
21871
|
+
rail.append(progress);
|
|
21872
|
+
}
|
|
21873
|
+
rail.append(tocEl);
|
|
21874
|
+
if (review)
|
|
21875
|
+
rail.append(actions(true));
|
|
21876
|
+
if (review && options.keys !== false) {
|
|
21877
|
+
const keys = el3(doc, "div", "knobs-wt-keys");
|
|
20230
21878
|
keys.innerHTML = "<kbd>j</kbd> <kbd>k</kbd> steps · <kbd>←</kbd> <kbd>→</kbd> seek · <kbd>f</kbd> full · <kbd>n</kbd> note" + (page.steps.some((s) => s.ask === "pick") ? " · <kbd>1</kbd>–<kbd>9</kbd> pick" : "");
|
|
20231
21879
|
rail.append(keys);
|
|
20232
21880
|
}
|
|
20233
21881
|
}
|
|
20234
21882
|
for (const section of page.sections) {
|
|
20235
|
-
const sec =
|
|
21883
|
+
const sec = el3(doc, "section", "knobs-wt-section");
|
|
20236
21884
|
sec.id = `wt-${section.id}`;
|
|
20237
|
-
const sh =
|
|
20238
|
-
|
|
21885
|
+
const sh = el3(doc, "header", "knobs-wt-shead");
|
|
21886
|
+
const status = review ? section.status : undefined;
|
|
21887
|
+
if (section.kicker || section.link || status) {
|
|
20239
21888
|
const meta = metaLine("knobs-wt-kicker");
|
|
20240
21889
|
if (section.kicker)
|
|
20241
21890
|
metaText(meta, section.kicker, "knobs-wt-kicker-lead");
|
|
20242
21891
|
if (section.link)
|
|
20243
21892
|
metaLink(meta, section.link.label, section.link.href);
|
|
20244
|
-
if (
|
|
20245
|
-
const s = statusLook(
|
|
21893
|
+
if (status) {
|
|
21894
|
+
const s = statusLook(status);
|
|
20246
21895
|
metaText(meta, s.label, "knobs-wt-status").dataset.tone = s.tone;
|
|
20247
21896
|
}
|
|
20248
21897
|
sh.append(meta);
|
|
20249
21898
|
}
|
|
20250
|
-
const h2 =
|
|
21899
|
+
const h2 = el3(doc, "h2", "knobs-wt-heading");
|
|
20251
21900
|
h2.innerHTML = inlineMarkdown(section.title);
|
|
20252
21901
|
sh.append(h2);
|
|
20253
|
-
const intro =
|
|
21902
|
+
const intro = el3(doc, "div", "knobs-wt-sintro");
|
|
20254
21903
|
intro.append(sh);
|
|
20255
21904
|
if (section.summary)
|
|
20256
21905
|
intro.append(prose2(doc, "p", "knobs-wt-ssum", section.summary));
|
|
21906
|
+
if (section.more)
|
|
21907
|
+
intro.append(more(section.more));
|
|
20257
21908
|
sec.append(intro);
|
|
20258
21909
|
let group = null;
|
|
20259
21910
|
if (tocEl) {
|
|
20260
|
-
group =
|
|
21911
|
+
group = el3(doc, "div", "knobs-wt-toc-group");
|
|
20261
21912
|
const first = section.steps[0]?.key;
|
|
20262
|
-
|
|
20263
|
-
group.append(tocRow(plainMarkdown(section.title), n, "", false, () => first ? go(first) : undefined, section.id));
|
|
21913
|
+
group.append(tocRow(plainMarkdown(section.title), false, () => first ? go(first) : undefined, section.id));
|
|
20264
21914
|
tocEl.append(group);
|
|
20265
21915
|
}
|
|
20266
21916
|
for (const step of section.steps) {
|
|
@@ -20270,6 +21920,7 @@ function walkthrough(container, options) {
|
|
|
20270
21920
|
}
|
|
20271
21921
|
flow.append(sec);
|
|
20272
21922
|
}
|
|
21923
|
+
linkPRs(flow, findPRIn);
|
|
20273
21924
|
main.append(flow);
|
|
20274
21925
|
if (noteMode !== "hidden")
|
|
20275
21926
|
main.append(bar.dock);
|
|
@@ -20298,28 +21949,35 @@ function walkthrough(container, options) {
|
|
|
20298
21949
|
root.dataset.settled = "";
|
|
20299
21950
|
}));
|
|
20300
21951
|
};
|
|
20301
|
-
const
|
|
20302
|
-
const
|
|
21952
|
+
const more = (text) => {
|
|
21953
|
+
const box = el3(doc, "div", "knobs-wt-more");
|
|
21954
|
+
const body = el3(doc, "div", "knobs-wt-more-text");
|
|
21955
|
+
for (const para of text.split(/\n\s*\n/))
|
|
21956
|
+
if (para.trim())
|
|
21957
|
+
body.append(prose2(doc, "p", "", para.trim()));
|
|
21958
|
+
discs.push(disclosure(box, { label: "More", content: body }));
|
|
21959
|
+
return box;
|
|
21960
|
+
};
|
|
21961
|
+
const tocRow = (label, sub, run, section) => {
|
|
21962
|
+
const b = el3(doc, "button", sub ? "knobs-wt-toc-row knobs-wt-toc-sub" : "knobs-wt-toc-row");
|
|
20303
21963
|
b.type = "button";
|
|
20304
21964
|
if (section)
|
|
20305
21965
|
b.dataset.section = section;
|
|
20306
|
-
const
|
|
20307
|
-
ncol.textContent = n;
|
|
20308
|
-
const t = el2(doc, "span", "knobs-wt-toc-label");
|
|
21966
|
+
const t = el3(doc, "span", "knobs-wt-toc-label");
|
|
20309
21967
|
t.textContent = label;
|
|
20310
|
-
const e = el2(doc, "span", "knobs-wt-toc-end");
|
|
20311
|
-
e.textContent = end;
|
|
20312
21968
|
b.title = label;
|
|
20313
|
-
b.append(
|
|
21969
|
+
b.append(t);
|
|
21970
|
+
if (review)
|
|
21971
|
+
b.append(el3(doc, "span", "knobs-wt-toc-end"));
|
|
20314
21972
|
b.addEventListener("mousedown", (ev) => ev.preventDefault());
|
|
20315
21973
|
b.addEventListener("click", run);
|
|
20316
21974
|
tocRows.push(b);
|
|
20317
21975
|
return b;
|
|
20318
21976
|
};
|
|
20319
21977
|
const actions = (rail) => {
|
|
20320
|
-
const row =
|
|
21978
|
+
const row = el3(doc, "div", "knobs-wt-actions");
|
|
20321
21979
|
const btn = (text, run, primary = false) => {
|
|
20322
|
-
const b =
|
|
21980
|
+
const b = el3(doc, "button", "knobs-wt-btn");
|
|
20323
21981
|
b.type = "button";
|
|
20324
21982
|
b.textContent = text;
|
|
20325
21983
|
if (primary)
|
|
@@ -20357,44 +22015,37 @@ function walkthrough(container, options) {
|
|
|
20357
22015
|
}
|
|
20358
22016
|
};
|
|
20359
22017
|
const stepView = (step, group) => {
|
|
20360
|
-
const card =
|
|
22018
|
+
const card = el3(doc, "article", options.cards ? "knobs-wt-step knobs-surface" : "knobs-wt-step");
|
|
20361
22019
|
card.id = `wt-${step.key.replace("/", "--")}`;
|
|
20362
22020
|
card.tabIndex = -1;
|
|
20363
22021
|
card.dataset.key = step.key;
|
|
20364
22022
|
card.setAttribute("aria-label", plainMarkdown(step.title));
|
|
20365
|
-
const head =
|
|
20366
|
-
const h3 =
|
|
22023
|
+
const head = el3(doc, "header", "knobs-wt-step-head");
|
|
22024
|
+
const h3 = el3(doc, "h3", "knobs-wt-stepTitle");
|
|
20367
22025
|
h3.innerHTML = inlineMarkdown(step.title);
|
|
20368
22026
|
head.append(h3);
|
|
20369
22027
|
if (step.caption)
|
|
20370
22028
|
head.append(prose2(doc, "p", "knobs-wt-caption", step.caption));
|
|
22029
|
+
if (step.more)
|
|
22030
|
+
head.append(more(step.more));
|
|
20371
22031
|
card.append(head);
|
|
20372
22032
|
const slots = [];
|
|
20373
22033
|
const onEvent = (e) => record({ ...e, step: step.key });
|
|
20374
22034
|
let media = null;
|
|
20375
22035
|
if (step.media.length) {
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
|
|
20379
|
-
|
|
20380
|
-
slots.push(mediaSlot(g, { ...look, media: m, onEvent, steps: stepLinks }));
|
|
20381
|
-
return g;
|
|
20382
|
-
};
|
|
20383
|
-
const [first, ...rest] = step.media;
|
|
20384
|
-
media = grid(step.more ? [first] : step.media);
|
|
22036
|
+
media = el3(doc, "div", "knobs-wt-media");
|
|
22037
|
+
media.style.setProperty("--kw-n", String(Math.min(step.media.length, step.columns)));
|
|
22038
|
+
for (const m of step.media)
|
|
22039
|
+
slots.push(mediaSlot(media, { ...look, media: m, onEvent, steps: stepLinks }));
|
|
20385
22040
|
card.append(media);
|
|
20386
|
-
if (step.more && rest.length) {
|
|
20387
|
-
const holder = el2(doc, "div", "knobs-wt-more");
|
|
20388
|
-
discs.push(disclosure(holder, { label: step.more, count: rest.length, content: grid(rest) }));
|
|
20389
|
-
card.append(holder);
|
|
20390
|
-
}
|
|
20391
22041
|
}
|
|
20392
22042
|
let taste = null;
|
|
20393
|
-
if (step.question)
|
|
22043
|
+
if (step.question && review)
|
|
20394
22044
|
card.append(prose2(doc, "p", "knobs-wt-question", step.question));
|
|
20395
22045
|
if (step.ask === "pick") {
|
|
20396
22046
|
taste = tasteTest(card, {
|
|
20397
22047
|
...look,
|
|
22048
|
+
pickable: review,
|
|
20398
22049
|
options: step.options,
|
|
20399
22050
|
picked: state.steps[step.key]?.pick ?? null,
|
|
20400
22051
|
onPick: (id) => pick(step.key, id),
|
|
@@ -20402,8 +22053,9 @@ function walkthrough(container, options) {
|
|
|
20402
22053
|
});
|
|
20403
22054
|
media ??= taste.element;
|
|
20404
22055
|
}
|
|
20405
|
-
const notes =
|
|
20406
|
-
|
|
22056
|
+
const notes = el3(doc, "div", "knobs-wt-notes");
|
|
22057
|
+
if (review)
|
|
22058
|
+
card.append(notes);
|
|
20407
22059
|
card.addEventListener("focusin", () => {
|
|
20408
22060
|
if (scroll.to)
|
|
20409
22061
|
return;
|
|
@@ -20413,9 +22065,11 @@ function walkthrough(container, options) {
|
|
|
20413
22065
|
let toc = null;
|
|
20414
22066
|
let glyph = null;
|
|
20415
22067
|
if (group) {
|
|
20416
|
-
toc = tocRow(plainMarkdown(step.title),
|
|
20417
|
-
|
|
20418
|
-
|
|
22068
|
+
toc = tocRow(plainMarkdown(step.title), true, () => go(step.key));
|
|
22069
|
+
if (review) {
|
|
22070
|
+
glyph = el3(doc, "span", "knobs-wt-toc-glyph");
|
|
22071
|
+
toc.querySelector(".knobs-wt-toc-end").append(glyph);
|
|
22072
|
+
}
|
|
20419
22073
|
group.append(toc);
|
|
20420
22074
|
}
|
|
20421
22075
|
return { step, card, media, notes, toc, glyph, taste, slots };
|
|
@@ -20487,13 +22141,17 @@ function walkthrough(container, options) {
|
|
|
20487
22141
|
claim();
|
|
20488
22142
|
return;
|
|
20489
22143
|
}
|
|
22144
|
+
if (key === "a" && !e.shiftKey && root.querySelector(".knobs-wt-anno")) {
|
|
22145
|
+
claim();
|
|
22146
|
+
return setAnnotations(root, !annotationsOn(root));
|
|
22147
|
+
}
|
|
20490
22148
|
if (key === "n" && noteMode !== "hidden") {
|
|
20491
22149
|
claim();
|
|
20492
22150
|
bar.setOpen(true, true);
|
|
20493
22151
|
return;
|
|
20494
22152
|
}
|
|
20495
22153
|
const v = viewOf(current);
|
|
20496
|
-
if (!v || e.shiftKey)
|
|
22154
|
+
if (!v || e.shiftKey || !review)
|
|
20497
22155
|
return;
|
|
20498
22156
|
const choice = digitChoice(v.step, state.steps[v.step.key], key);
|
|
20499
22157
|
if (choice === undefined)
|