@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-solid.js
CHANGED
|
@@ -577,11 +577,11 @@ var compareCss = `
|
|
|
577
577
|
.knobs-cmp-modes[hidden]{display:none}
|
|
578
578
|
.knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:pointer;white-space:nowrap;
|
|
579
579
|
transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
580
|
-
.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
580
|
+
.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
581
581
|
/* Toggles: icon buttons, the accent's wash when on. */
|
|
582
582
|
.knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-3);cursor:pointer;
|
|
583
583
|
transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
|
|
584
|
-
.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
|
|
584
|
+
.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)}
|
|
585
585
|
.knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
|
|
586
586
|
.knobs-cmp-toggle svg{display:block}
|
|
587
587
|
.knobs-cmp-fill{flex:1}
|
|
@@ -595,6 +595,8 @@ var compareCss = `
|
|
|
595
595
|
.knobs-cmp-stage{position:relative;flex:1;min-height:96px;overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_ku-cmp-stage);touch-action:pan-y;outline:none}
|
|
596
596
|
.knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
|
|
597
597
|
.knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
|
|
598
|
+
/* Videos: a click on the stage plays and pauses. */
|
|
599
|
+
.knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
|
|
598
600
|
/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
|
|
599
601
|
.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
|
|
600
602
|
.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
|
|
@@ -604,6 +606,9 @@ var compareCss = `
|
|
|
604
606
|
.knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
|
|
605
607
|
.knobs-cmp-media{position:absolute;left:0;top:0;display:block;max-width:none;margin:0;padding:0;border:0;object-fit:fill;transform-origin:0 0;pointer-events:none;will-change:transform}
|
|
606
608
|
.knobs-compare[data-renderer="gpu"] .knobs-cmp-media{opacity:0}
|
|
609
|
+
/* The media comes in with its first frames: a fade and a 4px blur on the default spring. */
|
|
610
|
+
.knobs-cmp-canvas,.knobs-compare[data-renderer="dom"] .knobs-cmp-media{transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
|
|
611
|
+
.knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}
|
|
607
612
|
/* Glass: what's behind shows through, blurred, on the knobs tokens. */
|
|
608
613
|
.knobs-cmp-label,.knobs-cmp-zoom{background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
|
|
609
614
|
box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim),0 2px 10px rgba(0,0,0,calc(.18 * var(--_k-shadow-k,1)))}
|
|
@@ -612,11 +617,17 @@ var compareCss = `
|
|
|
612
617
|
white-space:nowrap;pointer-events:none;will-change:transform}
|
|
613
618
|
.knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
|
|
614
619
|
.knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
.knobs-cmp-
|
|
620
|
+
/* Fusing, the mark folds away and the label is its name. */
|
|
621
|
+
.knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}
|
|
622
|
+
.knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}
|
|
623
|
+
/* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */
|
|
624
|
+
.knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;
|
|
625
|
+
-webkit-mask-image:linear-gradient(to right,transparent,#000 35%,#000 65%,transparent);mask-image:linear-gradient(to right,transparent,#000 35%,#000 65%,transparent)}
|
|
626
|
+
/* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height
|
|
627
|
+
while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the
|
|
628
|
+
fuse; wiping, line and grip brighten. */
|
|
629
|
+
.knobs-cmp-handle{cursor:ew-resize;position:absolute;left:-12px;top:0;width:24px;height:0;opacity:0;pointer-events:none;outline:none;will-change:transform,opacity}
|
|
630
|
+
.knobs-cmp-handle[data-on]{pointer-events:auto}
|
|
620
631
|
.knobs-cmp-handle::before{content:"";position:absolute;top:0;bottom:0;left:11.5px;width:1px;background:color-mix(in srgb,var(--_ku-cmp-line) 72%,transparent);box-shadow:0 0 0 .5px rgba(0,0,0,.28),0 0 6px rgba(0,0,0,.25)}
|
|
621
632
|
.knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
|
|
622
633
|
.knobs-cmp-grip{position:absolute;top:0;left:4px;width:16px;height:28px;border-radius:min(8px,calc(var(--_k-radius) * 999));background:var(--_k-bg-overlay);
|
|
@@ -636,7 +647,7 @@ var compareCss = `
|
|
|
636
647
|
.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)}
|
|
637
648
|
: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;
|
|
638
649
|
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}
|
|
639
|
-
:where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){color:var(--_k-fg);background:color-mix(in srgb,var(--_k-fg) 12%,transparent)}
|
|
650
|
+
: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)}
|
|
640
651
|
.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}
|
|
641
652
|
.knobs-cmp-note:empty{display:none}
|
|
642
653
|
.knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
|
|
@@ -648,6 +659,31 @@ var compareCss = `
|
|
|
648
659
|
@media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}
|
|
649
660
|
`;
|
|
650
661
|
|
|
662
|
+
// src/ui/player-css.ts
|
|
663
|
+
var playerCss = `
|
|
664
|
+
:where(.knobs-player,.knobs-player *){box-sizing:border-box}
|
|
665
|
+
.knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
|
|
666
|
+
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
667
|
+
:where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-player,.knobs-ui.knobs-player[data-nested]{--_kp-stage:var(--k-compare-stage,transparent)}
|
|
668
|
+
.knobs-player-frame{position:relative;width:100%;aspect-ratio:var(--kp-aspect,16/9);overflow:hidden;border-radius:var(--_k-radius-panel);background:var(--_kp-stage);cursor:pointer}
|
|
669
|
+
.knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
|
|
670
|
+
.knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
|
|
671
|
+
opacity:0;filter:blur(4px);transition:opacity calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default),filter calc(var(--_k-motion-default-ms) * 1ms) var(--_k-motion-default)}
|
|
672
|
+
.knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
|
|
673
|
+
.knobs-player-glyph{position:absolute;left:50%;top:50%;display:grid;place-items:center;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;color:var(--_k-fg);pointer-events:none;
|
|
674
|
+
opacity:0;filter:blur(4px);scale:.96;
|
|
675
|
+
background:color-mix(in srgb,var(--_k-bg) 52%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.6);backdrop-filter:blur(14px) saturate(1.6);
|
|
676
|
+
box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
|
|
677
|
+
transition:opacity calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),filter calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy),scale calc(var(--_k-motion-snappy-ms) * 1ms) var(--_k-motion-snappy)}
|
|
678
|
+
.knobs-player-glyph svg{width:16px;height:16px}
|
|
679
|
+
.knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
|
|
680
|
+
.knobs-player-line{flex:none;min-height:0}
|
|
681
|
+
/* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover
|
|
682
|
+
states are set here, after it, so the player's line and every timeline share them. */
|
|
683
|
+
.knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
|
|
684
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
685
|
+
`;
|
|
686
|
+
|
|
651
687
|
// src/ui/overlay-css.ts
|
|
652
688
|
var overlayCss = `
|
|
653
689
|
.knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}
|
|
@@ -1512,7 +1548,7 @@ var init_schema = __esm(() => {
|
|
|
1512
1548
|
type: "length",
|
|
1513
1549
|
group: "Type",
|
|
1514
1550
|
source: value("20px"),
|
|
1515
|
-
doc: "
|
|
1551
|
+
doc: "A page's title, weight 500."
|
|
1516
1552
|
},
|
|
1517
1553
|
{
|
|
1518
1554
|
name: "leading-heading",
|
|
@@ -1528,7 +1564,7 @@ var init_schema = __esm(() => {
|
|
|
1528
1564
|
type: "length",
|
|
1529
1565
|
group: "Type",
|
|
1530
1566
|
source: value("16px"),
|
|
1531
|
-
doc: "
|
|
1567
|
+
doc: "Section titles (under their kicker), weight 500."
|
|
1532
1568
|
},
|
|
1533
1569
|
{
|
|
1534
1570
|
name: "leading-title",
|
|
@@ -1560,7 +1596,7 @@ var init_schema = __esm(() => {
|
|
|
1560
1596
|
type: "length",
|
|
1561
1597
|
group: "Type",
|
|
1562
1598
|
source: value("12px"),
|
|
1563
|
-
doc: "
|
|
1599
|
+
doc: "Mono inside body text: code and paths."
|
|
1564
1600
|
},
|
|
1565
1601
|
{
|
|
1566
1602
|
name: "leading-label",
|
|
@@ -3003,8 +3039,6 @@ var css2 = `
|
|
|
3003
3039
|
.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}
|
|
3004
3040
|
.knobs-tl-track:hover .knobs-tl-hover{opacity:1}
|
|
3005
3041
|
.knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
|
|
3006
|
-
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
3007
|
-
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
3008
3042
|
.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}
|
|
3009
3043
|
.knobs-tl-tag[data-on]{opacity:1}
|
|
3010
3044
|
/* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
|
|
@@ -3020,7 +3054,26 @@ var css2 = `
|
|
|
3020
3054
|
.knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
|
|
3021
3055
|
.knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
|
|
3022
3056
|
.knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
|
|
3023
|
-
|
|
3057
|
+
/* The line (variant: "line"): a player's progress under its media. One 2px line, fg-3 with fg
|
|
3058
|
+
filling it, a segment per chapter; it thickens under the pointer and while scrubbing. The
|
|
3059
|
+
chapter and time under the pointer float over it; nothing else shows. */
|
|
3060
|
+
.knobs-ui.knobs-timeline[data-variant="line"]{height:auto;min-height:0;padding:0;gap:0}
|
|
3061
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-transport{display:none}
|
|
3062
|
+
.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}
|
|
3063
|
+
.knobs-timeline[data-variant="line"] :is(.knobs-tl-ruler,.knobs-tl-lane,.knobs-tl-hover,.knobs-tl-head,.knobs-tl-rlabel){display:none}
|
|
3064
|
+
.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)}
|
|
3065
|
+
.knobs-tl-seg{position:absolute;left:0;top:0;height:100%;overflow:hidden;border-radius:1px;background:var(--_k-fg-3);will-change:transform}
|
|
3066
|
+
.knobs-tl-seg-fill{position:absolute;inset:0;background:var(--_k-fg);transform-origin:0 0;will-change:transform}
|
|
3067
|
+
.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}
|
|
3068
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-range{top:3px;bottom:3px;border-radius:3px}
|
|
3069
|
+
.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)}
|
|
3070
|
+
.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);
|
|
3071
|
+
-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:inset 0 0 0 .5px var(--_k-border)}
|
|
3072
|
+
.knobs-tl-tag-label{color:var(--_k-fg-2)}
|
|
3073
|
+
.knobs-tl-tag-label:empty{display:none}
|
|
3074
|
+
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
3075
|
+
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
3076
|
+
`, uiCss = () => aliased(css2 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + playerCss + selectionPillCss) + rolling_css_default, lookDefaults, tune = (s, m) => {
|
|
3024
3077
|
s.omega = m.omega;
|
|
3025
3078
|
s.zeta = m.zeta;
|
|
3026
3079
|
return s;
|
|
@@ -3143,19 +3196,19 @@ var init_diff_css = __esm(() => {
|
|
|
3143
3196
|
@property --kd-fb{syntax:"<length>";inherits:false;initial-value:0px}
|
|
3144
3197
|
: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))}
|
|
3145
3198
|
:where(.knobs-diff,.knobs-diff *,.knobs-diff-files,.knobs-diff-files *){box-sizing:border-box}
|
|
3146
|
-
.knobs-ui.knobs-diff{--kd-bg:var(--k-diff-bg,var(--_kd-bg,
|
|
3199
|
+
.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);
|
|
3147
3200
|
--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);
|
|
3148
3201
|
--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;
|
|
3149
3202
|
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;
|
|
3150
3203
|
letter-spacing:normal;word-spacing:normal;text-transform:none;-webkit-font-smoothing:antialiased}
|
|
3151
3204
|
.knobs-ui.knobs-diff[data-height="auto"]{height:auto}
|
|
3152
|
-
.knobs-diff::after{content:"";position:absolute;inset:0;z-index:3;
|
|
3205
|
+
.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}
|
|
3153
3206
|
.knobs-diff [hidden],.knobs-diff-files [hidden]{display:none!important}
|
|
3154
3207
|
.knobs-diff svg{display:block;flex:none}
|
|
3155
|
-
.knobs-ui.knobs-diff[data-on="
|
|
3156
|
-
/*
|
|
3208
|
+
.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)}
|
|
3209
|
+
/* A hairline under the header, firmer once the code has scrolled under it. */
|
|
3157
3210
|
.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);
|
|
3158
|
-
box-shadow:inset 0 -1px
|
|
3211
|
+
box-shadow:inset 0 -1px var(--_k-border-subtle);transition:box-shadow .15s;user-select:none;-webkit-user-select:none}
|
|
3159
3212
|
.knobs-diff[data-scrolled]>.knobs-diff-head{box-shadow:inset 0 -1px var(--_k-border)}
|
|
3160
3213
|
.knobs-diff-path{flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap}
|
|
3161
3214
|
.knobs-diff-dir,.knobs-diff-from{color:var(--_k-fg-3)}
|
|
@@ -3182,17 +3235,26 @@ var init_diff_css = __esm(() => {
|
|
|
3182
3235
|
margin:0;padding:0;min-width:0;box-shadow:none;outline:none}
|
|
3183
3236
|
.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;
|
|
3184
3237
|
transition:background .15s,color .15s,opacity .15s}
|
|
3185
|
-
.knobs-ui .knobs-diff-btn:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3238
|
+
.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)}
|
|
3186
3239
|
.knobs-ui .knobs-diff-btn[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
3187
3240
|
.knobs-ui .knobs-diff-btn:disabled{opacity:.35;cursor:default;background:transparent}
|
|
3188
3241
|
.knobs-diff-btn svg{width:16px;height:16px}
|
|
3189
3242
|
/* Viewed: a check with its word, as on GitHub; the path dims once it's ticked. */
|
|
3190
3243
|
.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);
|
|
3191
3244
|
font-size:var(--_k-text-meta);font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:background .15s,color .15s}
|
|
3192
|
-
.knobs-ui .knobs-diff-viewed:is(:hover,:focus-visible){background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3245
|
+
.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)}
|
|
3193
3246
|
.knobs-ui .knobs-diff-viewed[aria-pressed="true"]{color:var(--_k-fg);background:var(--_ku-wash)}
|
|
3194
3247
|
.knobs-diff-viewed svg{width:16px;height:16px}
|
|
3195
3248
|
.knobs-diff-viewed[aria-pressed="true"] svg{color:var(--kd-add)}
|
|
3249
|
+
/* Narrow, the header gives up words before anything is cut off at its end: Viewed keeps its
|
|
3250
|
+
check, then the counts' bar goes, then the counts; the path fades at its end throughout. */
|
|
3251
|
+
.knobs-diff-head,.knobs-diff-part-head{container-type:inline-size}
|
|
3252
|
+
@container (max-width:520px){
|
|
3253
|
+
.knobs-diff-viewed-label{display:none}
|
|
3254
|
+
.knobs-ui .knobs-diff-viewed{width:var(--_k-control-h);padding:0;justify-content:center}
|
|
3255
|
+
.knobs-diff-counts .knobs-diff-bar{display:none}
|
|
3256
|
+
}
|
|
3257
|
+
@container (max-width:400px){.knobs-diff-counts{display:none}.knobs-diff-nav>span{min-width:2.6em}}
|
|
3196
3258
|
.knobs-diff-path{transition:opacity .2s}
|
|
3197
3259
|
.knobs-diff[data-viewed]>.knobs-diff-head .knobs-diff-path,.knobs-diff-part[data-viewed] .knobs-diff-path{opacity:.6}
|
|
3198
3260
|
/* Split | Unified: the selection pill on the one picked, its bright copy the picked one's text; a
|
|
@@ -3200,7 +3262,7 @@ var init_diff_css = __esm(() => {
|
|
|
3200
3262
|
.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)}
|
|
3201
3263
|
.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);
|
|
3202
3264
|
font-weight:var(--_k-weight-label);white-space:nowrap;cursor:pointer;transition:color calc(var(--_k-hover-out) * 1ms)}
|
|
3203
|
-
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg)}
|
|
3265
|
+
.knobs-ui .knobs-diff-choice:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3204
3266
|
/* The diff: one layer per file or layout on show, the old fading under the new. */
|
|
3205
3267
|
.knobs-diff-body{position:relative;flex:1;min-height:0}
|
|
3206
3268
|
.knobs-diff-probe{position:absolute;left:0;top:0;width:0;height:var(--kd-line);visibility:hidden;pointer-events:none}
|
|
@@ -3221,8 +3283,8 @@ var init_diff_css = __esm(() => {
|
|
|
3221
3283
|
.knobs-diff-hbar{position:absolute;height:5px;pointer-events:none}
|
|
3222
3284
|
.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;
|
|
3223
3285
|
transition:opacity .3s,background .15s}
|
|
3224
|
-
.knobs-diff-body:hover .knobs-diff-hbar>i,.knobs-diff-hbar:is([data-active],[data-dragging])>i{opacity:1;pointer-events:auto;transition-duration
|
|
3225
|
-
.knobs-diff-hbar>i:hover,.knobs-diff-hbar[data-dragging]>i{background:color-mix(in srgb,var(--_k-fg) 48%,transparent)}
|
|
3286
|
+
.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)}
|
|
3287
|
+
.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)}
|
|
3226
3288
|
/* stack: every file down one scroller, each header sticking until the next pushes it off. It's
|
|
3227
3289
|
flush (square, no card around its file), opaque in the code's own colour, with a hairline
|
|
3228
3290
|
under it only while it's stuck; a gap, not a line, parts one file from the next. */
|
|
@@ -3257,24 +3319,26 @@ var init_diff_css = __esm(() => {
|
|
|
3257
3319
|
--diffs-bg-deletion-number-override:color-mix(in srgb,var(--kd-del) 55%,var(--kd-solid));
|
|
3258
3320
|
--diffs-bg-addition-emphasis-override:color-mix(in srgb,var(--kd-add) 22%,transparent);
|
|
3259
3321
|
--diffs-bg-deletion-emphasis-override:color-mix(in srgb,var(--kd-del) 22%,transparent);
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
--diffs-bg-
|
|
3322
|
+
/* Unchanged lines and the folds between hunks sit on the void; only a split's missing lines
|
|
3323
|
+
are faintly filled. */
|
|
3324
|
+
--diffs-bg-separator-override:var(--kd-solid);
|
|
3325
|
+
--diffs-bg-context-override:var(--kd-solid);
|
|
3326
|
+
--diffs-bg-buffer-override:color-mix(in srgb,var(--_k-fg) 2.5%,var(--kd-solid));
|
|
3263
3327
|
--diffs-bg-selection-override:var(--_k-accent);
|
|
3264
3328
|
--diffs-fg-number-override:var(--_k-fg-3);
|
|
3265
3329
|
}
|
|
3266
|
-
/* diffReview
|
|
3267
|
-
is pinned to its right edge, so it slides out from under the diff), parted
|
|
3268
|
-
|
|
3269
|
-
.knobs-ui.knobs-diff-review{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;
|
|
3330
|
+
/* diffReview, on the void as the diff is: the file list a drawer at its left (its width springs;
|
|
3331
|
+
the list inside is pinned to its right edge, so it slides out from under the diff), parted
|
|
3332
|
+
from the diff by a hairline; one hairline runs under both headers and one under it all. */
|
|
3333
|
+
.knobs-ui.knobs-diff-review{position:relative;display:flex;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;
|
|
3270
3334
|
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}
|
|
3271
|
-
.knobs-diff-review::after{content:"";position:absolute;inset:0;z-index:5;
|
|
3335
|
+
.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}
|
|
3272
3336
|
:where(.knobs-diff-review,.knobs-diff-review *){box-sizing:border-box}
|
|
3273
|
-
.knobs-diff-drawer{position:relative;flex:none;width:0;height:100%;overflow:hidden;box-shadow:inset -1px 0 var(--_k-border)}
|
|
3337
|
+
.knobs-diff-drawer{position:relative;flex:none;width:0;height:100%;overflow:hidden;box-shadow:inset -1px 0 var(--_k-border-subtle)}
|
|
3274
3338
|
.knobs-diff-drawer[data-shut]{visibility:hidden}
|
|
3275
3339
|
.knobs-diff-drawer-in{position:absolute;top:0;bottom:0;right:0;display:flex;flex-direction:column}
|
|
3276
3340
|
.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);
|
|
3277
|
-
user-select:none;-webkit-user-select:none}
|
|
3341
|
+
box-shadow:inset 0 -1px var(--_k-border-subtle);user-select:none;-webkit-user-select:none}
|
|
3278
3342
|
.knobs-diff-drawer-title{font-weight:var(--_k-weight-title)}
|
|
3279
3343
|
.knobs-diff-drawer-count{color:var(--_k-fg-3);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums}
|
|
3280
3344
|
.knobs-diff-drawer-in>.knobs-diff-files{flex:1;min-height:0;height:auto}
|
|
@@ -3286,15 +3350,16 @@ var init_diff_css = __esm(() => {
|
|
|
3286
3350
|
.knobs-ui .knobs-diff-toggle[aria-pressed]:not(:hover,:focus-visible){background:transparent}
|
|
3287
3351
|
/* diffReview's file list: a filter over a compact tree, a status letter, the counts and their
|
|
3288
3352
|
bar on each file; one highlight glides to the file on show. Viewed files dim, with a tick. */
|
|
3289
|
-
.knobs-diff-files{--k-row:var(--k-diff-row,28px);display:flex;flex-direction:column;height:100%;min-height:0;padding-top:
|
|
3353
|
+
.knobs-diff-files{--k-row:var(--k-diff-row,28px);display:flex;flex-direction:column;height:100%;min-height:0;padding-top:4px}
|
|
3290
3354
|
.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;
|
|
3291
|
-
border-radius:max(6px,var(--_ku-row-radius));
|
|
3292
|
-
|
|
3355
|
+
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}
|
|
3356
|
+
/* The filter is a line of text, no box; focused, a hairline ring. */
|
|
3357
|
+
.knobs-diff-filter:focus-within{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_k-accent) 45%,transparent)}
|
|
3293
3358
|
.knobs-diff-filter svg{width:12px;height:12px;flex:none}
|
|
3294
3359
|
.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}
|
|
3295
3360
|
.knobs-ui .knobs-diff-filter>input::placeholder{color:var(--_k-fg-3)}
|
|
3296
3361
|
.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}
|
|
3297
|
-
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg)}
|
|
3362
|
+
.knobs-ui .knobs-diff-filter>button:hover{background:var(--_ku-wash-hi);color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
|
|
3298
3363
|
.knobs-diff-filter>button svg{width:12px;height:12px}
|
|
3299
3364
|
.knobs-diff-list{position:relative;flex:1;min-height:0}
|
|
3300
3365
|
.knobs-diff-files .knobs-diff-counts{font-size:var(--_k-text-meta);align-items:center}
|
|
@@ -6214,7 +6279,10 @@ function codeDiff(container, options) {
|
|
|
6214
6279
|
const viewedToggle = () => {
|
|
6215
6280
|
const b = make("button", "knobs-diff-viewed");
|
|
6216
6281
|
b.type = "button";
|
|
6217
|
-
|
|
6282
|
+
const label = make("span", "knobs-diff-viewed-label");
|
|
6283
|
+
label.textContent = "Viewed";
|
|
6284
|
+
b.append(make("span", ""), label);
|
|
6285
|
+
b.setAttribute("aria-label", "Viewed");
|
|
6218
6286
|
return b;
|
|
6219
6287
|
};
|
|
6220
6288
|
const paintViewed = (b, on) => {
|
|
@@ -8501,6 +8569,17 @@ function currentMarker(markers, t, fps) {
|
|
|
8501
8569
|
best = m;
|
|
8502
8570
|
return best;
|
|
8503
8571
|
}
|
|
8572
|
+
function minutes(t) {
|
|
8573
|
+
const s = Math.max(0, Math.floor(t + 0.000001));
|
|
8574
|
+
const h = Math.floor(s / 3600);
|
|
8575
|
+
const m = Math.floor(s % 3600 / 60);
|
|
8576
|
+
const ss = String(s % 60).padStart(2, "0");
|
|
8577
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
8578
|
+
}
|
|
8579
|
+
function chapterBounds(times, duration) {
|
|
8580
|
+
const inside = times.filter((t) => t > 0.000001 && t < duration - 0.000001);
|
|
8581
|
+
return [0, ...[...new Set(inside)].sort((a, b) => a - b), duration];
|
|
8582
|
+
}
|
|
8504
8583
|
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;
|
|
8505
8584
|
var init_timeline_model = __esm(() => {
|
|
8506
8585
|
SECOND_STEPS = [0.1, 0.2, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600];
|
|
@@ -8518,7 +8597,8 @@ function timeline(container, options) {
|
|
|
8518
8597
|
let speed = options.speed ?? 1;
|
|
8519
8598
|
let repeat = options.repeat ?? true;
|
|
8520
8599
|
const arrows = options.arrows ?? "frames";
|
|
8521
|
-
const
|
|
8600
|
+
const line = options.variant === "line";
|
|
8601
|
+
const lane = line || options.variant === "lane";
|
|
8522
8602
|
let time = Math.min(duration, Math.max(0, options.time ?? 0));
|
|
8523
8603
|
let playing = false;
|
|
8524
8604
|
let markers = [...options.markers ?? []];
|
|
@@ -8553,7 +8633,7 @@ function timeline(container, options) {
|
|
|
8553
8633
|
root.setAttribute("aria-keyshortcuts", "Space ArrowLeft ArrowRight , . Home End [ ] I O M");
|
|
8554
8634
|
applyLook2(root, options);
|
|
8555
8635
|
if (lane)
|
|
8556
|
-
root.dataset.variant = "lane";
|
|
8636
|
+
root.dataset.variant = line ? "line" : "lane";
|
|
8557
8637
|
const transport = el("div", "knobs-tl-transport");
|
|
8558
8638
|
const playBtn = el("button", "knobs-tl-play");
|
|
8559
8639
|
playBtn.type = "button";
|
|
@@ -8566,6 +8646,10 @@ function timeline(container, options) {
|
|
|
8566
8646
|
const hover = el("div", "knobs-tl-hover");
|
|
8567
8647
|
const tag = el("span", "knobs-tl-tag");
|
|
8568
8648
|
tag.setAttribute("aria-hidden", "true");
|
|
8649
|
+
const tagLabel = el("span", "knobs-tl-tag-label");
|
|
8650
|
+
const tagTime = el("span", "knobs-tl-tag-time");
|
|
8651
|
+
tag.append(tagLabel, tagTime);
|
|
8652
|
+
const segs = el("div", "knobs-tl-segs");
|
|
8569
8653
|
const head = el("div", "knobs-tl-head");
|
|
8570
8654
|
head.id = `${id}-head`;
|
|
8571
8655
|
head.setAttribute("role", "slider");
|
|
@@ -8573,11 +8657,13 @@ function timeline(container, options) {
|
|
|
8573
8657
|
head.setAttribute("aria-valuemin", "0");
|
|
8574
8658
|
head.setAttribute("aria-valuemax", String(duration));
|
|
8575
8659
|
track.append(canvas, markerLane, hover, head, tag);
|
|
8660
|
+
if (line)
|
|
8661
|
+
track.insertBefore(segs, markerLane);
|
|
8576
8662
|
root.append(transport, track);
|
|
8577
8663
|
container.append(root);
|
|
8578
8664
|
let width = 1;
|
|
8579
8665
|
let height = 1;
|
|
8580
|
-
const fitView = () => clampView({ start: 0, span: duration }, duration, { pad: 0.02 });
|
|
8666
|
+
const fitView = () => clampView({ start: 0, span: duration }, duration, { pad: line ? 0 : 0.02 });
|
|
8581
8667
|
let target = fitView();
|
|
8582
8668
|
const vStart = new Spring(target.start, INSTANT2.omega, 1, 0.00001);
|
|
8583
8669
|
const vSpan = new Spring(target.span, INSTANT2.omega, 1, 0.00001);
|
|
@@ -8586,13 +8672,13 @@ function timeline(container, options) {
|
|
|
8586
8672
|
const xOf = (t) => (t - vStart.x) * pxPerSec();
|
|
8587
8673
|
const tOf = (x) => vStart.x + x / pxPerSec();
|
|
8588
8674
|
const minSpan = () => Math.max(4 / fps, 0.05);
|
|
8589
|
-
const retarget = (next, motion = INSTANT2) => {
|
|
8590
|
-
target = clampView(next, duration, { pad: 0.04, minSpan: minSpan() });
|
|
8675
|
+
const retarget = (next, motion = INSTANT2, hand = false) => {
|
|
8676
|
+
target = clampView(next, duration, { pad: line ? 0 : 0.04, minSpan: minSpan() });
|
|
8591
8677
|
for (const s of [vStart, vSpan]) {
|
|
8592
8678
|
s.omega = motion.omega;
|
|
8593
8679
|
s.zeta = motion.zeta;
|
|
8594
8680
|
}
|
|
8595
|
-
if (reducedMotion(win) && motion === CALM) {
|
|
8681
|
+
if (hand || reducedMotion(win) && motion === CALM) {
|
|
8596
8682
|
vStart.snap(target.start);
|
|
8597
8683
|
vSpan.snap(target.span);
|
|
8598
8684
|
} else {
|
|
@@ -8618,6 +8704,8 @@ function timeline(container, options) {
|
|
|
8618
8704
|
};
|
|
8619
8705
|
let drawn = "";
|
|
8620
8706
|
const drawRuler = () => {
|
|
8707
|
+
if (line)
|
|
8708
|
+
return;
|
|
8621
8709
|
const v = view();
|
|
8622
8710
|
const key = `${v.start.toFixed(6)}:${v.span.toFixed(6)}:${width}:${height}:${units}:${fps}`;
|
|
8623
8711
|
if (key === drawn)
|
|
@@ -8705,6 +8793,8 @@ function timeline(container, options) {
|
|
|
8705
8793
|
n.title = `${m.label ? `${m.label} · ` : ""}${clock(m.time)}`;
|
|
8706
8794
|
}
|
|
8707
8795
|
placeMarkers();
|
|
8796
|
+
if (line)
|
|
8797
|
+
syncSegments();
|
|
8708
8798
|
};
|
|
8709
8799
|
const placeMarkers = () => {
|
|
8710
8800
|
for (const m of markers) {
|
|
@@ -8713,6 +8803,36 @@ function timeline(container, options) {
|
|
|
8713
8803
|
n.style.transform = `translateX(${xOf(m.time).toFixed(2)}px)`;
|
|
8714
8804
|
}
|
|
8715
8805
|
};
|
|
8806
|
+
let bounds = [];
|
|
8807
|
+
const segEls = [];
|
|
8808
|
+
const syncSegments = () => {
|
|
8809
|
+
bounds = chapterBounds(markers.map((m) => m.time), duration);
|
|
8810
|
+
while (segEls.length > bounds.length - 1)
|
|
8811
|
+
segEls.pop().el.remove();
|
|
8812
|
+
while (segEls.length < bounds.length - 1) {
|
|
8813
|
+
const n = el("i", "knobs-tl-seg");
|
|
8814
|
+
const fill = el("b", "knobs-tl-seg-fill");
|
|
8815
|
+
n.append(fill);
|
|
8816
|
+
segs.append(n);
|
|
8817
|
+
segEls.push({ el: n, fill });
|
|
8818
|
+
}
|
|
8819
|
+
placeSegments();
|
|
8820
|
+
};
|
|
8821
|
+
const placeSegments = () => {
|
|
8822
|
+
if (!line)
|
|
8823
|
+
return;
|
|
8824
|
+
const last = segEls.length - 1;
|
|
8825
|
+
segEls.forEach((s, i) => {
|
|
8826
|
+
const a = bounds[i];
|
|
8827
|
+
const b = bounds[i + 1];
|
|
8828
|
+
const x0 = xOf(a) + (i > 0 ? SEG_GAP / 2 : 0);
|
|
8829
|
+
const x1 = xOf(b) - (i < last ? SEG_GAP / 2 : 0);
|
|
8830
|
+
s.el.style.transform = `translateX(${x0.toFixed(2)}px)`;
|
|
8831
|
+
s.el.style.width = `${Math.max(0, x1 - x0).toFixed(2)}px`;
|
|
8832
|
+
const k = Math.min(1, Math.max(0, (shownHead - a) / Math.max(0.000000001, b - a)));
|
|
8833
|
+
s.fill.style.transform = `scaleX(${k.toFixed(4)})`;
|
|
8834
|
+
});
|
|
8835
|
+
};
|
|
8716
8836
|
const syncRanges = () => {
|
|
8717
8837
|
const keep = new Set(ranges.map((r) => r.id));
|
|
8718
8838
|
for (const [k, n] of rangeEls)
|
|
@@ -8729,7 +8849,7 @@ function timeline(container, options) {
|
|
|
8729
8849
|
b.dataset.edge = "end";
|
|
8730
8850
|
const label = el("span", "knobs-tl-rlabel");
|
|
8731
8851
|
n.append(label, a, b);
|
|
8732
|
-
track.insertBefore(n, markerLane);
|
|
8852
|
+
track.insertBefore(n, line ? segs : markerLane);
|
|
8733
8853
|
rangeEls.set(r.id, n);
|
|
8734
8854
|
if (built)
|
|
8735
8855
|
enter(n);
|
|
@@ -8781,6 +8901,7 @@ function timeline(container, options) {
|
|
|
8781
8901
|
let currentId = null;
|
|
8782
8902
|
const renderHead = () => {
|
|
8783
8903
|
head.style.transform = `translateX(${xOf(shownHead).toFixed(2)}px)`;
|
|
8904
|
+
placeSegments();
|
|
8784
8905
|
head.setAttribute("aria-valuenow", time.toFixed(3));
|
|
8785
8906
|
head.setAttribute("aria-valuetext", `${time.toFixed(2)} seconds, frame ${toFrame(time, fps)}`);
|
|
8786
8907
|
const cur = currentMarker(markers, time, fps)?.id ?? null;
|
|
@@ -9044,14 +9165,25 @@ function timeline(container, options) {
|
|
|
9044
9165
|
setTime(s.t, "scrub", s.shown, press);
|
|
9045
9166
|
root.toggleAttribute("data-stuck", s.stop !== null);
|
|
9046
9167
|
};
|
|
9168
|
+
let pointerX = null;
|
|
9169
|
+
const zoomAtHand = (factor) => zoom(factor, pointerX === null ? time : target.start + pointerX / width * target.span);
|
|
9047
9170
|
const showTag = (x) => {
|
|
9048
9171
|
const at = Math.min(duration, Math.max(0, tOf(x)));
|
|
9049
|
-
const text = `${at.toFixed(2)} s`;
|
|
9050
|
-
if (
|
|
9051
|
-
|
|
9172
|
+
const text = line ? minutes(at) : `${at.toFixed(2)} s`;
|
|
9173
|
+
if (tagTime.textContent !== text)
|
|
9174
|
+
tagTime.textContent = text;
|
|
9175
|
+
const label = line ? currentMarker(markers, at, fps)?.label ?? "" : "";
|
|
9176
|
+
if (tagLabel.textContent !== label)
|
|
9177
|
+
tagLabel.textContent = label;
|
|
9052
9178
|
const cx = Math.min(width, Math.max(0, x));
|
|
9053
|
-
|
|
9054
|
-
|
|
9179
|
+
if (line) {
|
|
9180
|
+
const w = tag.offsetWidth;
|
|
9181
|
+
const left = Math.min(Math.max(0, width - w), Math.max(0, cx - w / 2));
|
|
9182
|
+
tag.style.transform = `translateX(${left.toFixed(1)}px)`;
|
|
9183
|
+
} else {
|
|
9184
|
+
const flip = cx > width - tag.offsetWidth - 12;
|
|
9185
|
+
tag.style.transform = `translateX(${cx.toFixed(1)}px) translateX(${flip ? "calc(-100% - 4px)" : "4px"})`;
|
|
9186
|
+
}
|
|
9055
9187
|
tag.dataset.on = "";
|
|
9056
9188
|
};
|
|
9057
9189
|
const hideTag = () => delete tag.dataset.on;
|
|
@@ -9112,6 +9244,7 @@ function timeline(container, options) {
|
|
|
9112
9244
|
});
|
|
9113
9245
|
track.addEventListener("pointermove", (e) => {
|
|
9114
9246
|
const x = localX(e);
|
|
9247
|
+
pointerX = x;
|
|
9115
9248
|
hover.style.transform = `translateX(${x.toFixed(1)}px)`;
|
|
9116
9249
|
if (!drag || drag.kind === "scrub")
|
|
9117
9250
|
showTag(x);
|
|
@@ -9185,6 +9318,7 @@ function timeline(container, options) {
|
|
|
9185
9318
|
track.addEventListener("pointercancel", endDrag);
|
|
9186
9319
|
track.addEventListener("lostpointercapture", endDrag);
|
|
9187
9320
|
track.addEventListener("pointerleave", () => {
|
|
9321
|
+
pointerX = null;
|
|
9188
9322
|
if (drag)
|
|
9189
9323
|
return;
|
|
9190
9324
|
hover.style.transform = "translateX(-100px)";
|
|
@@ -9192,7 +9326,7 @@ function timeline(container, options) {
|
|
|
9192
9326
|
});
|
|
9193
9327
|
track.addEventListener("dblclick", (e) => {
|
|
9194
9328
|
if (lane)
|
|
9195
|
-
return;
|
|
9329
|
+
return fit();
|
|
9196
9330
|
const markerEl = e.target.closest(".knobs-tl-marker");
|
|
9197
9331
|
if (markerEl)
|
|
9198
9332
|
return rename(markerEl.dataset.id);
|
|
@@ -9203,18 +9337,40 @@ function timeline(container, options) {
|
|
|
9203
9337
|
rename(m.id);
|
|
9204
9338
|
});
|
|
9205
9339
|
track.addEventListener("wheel", (e) => {
|
|
9206
|
-
|
|
9340
|
+
const zooming = e.ctrlKey || e.metaKey;
|
|
9341
|
+
if (zooming && pinching)
|
|
9342
|
+
return e.preventDefault();
|
|
9343
|
+
const sideways = Math.abs(e.deltaX) > Math.abs(e.deltaY);
|
|
9344
|
+
if (lane && !zooming && !sideways)
|
|
9207
9345
|
return;
|
|
9208
9346
|
e.preventDefault();
|
|
9209
9347
|
const scale = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? height : 1;
|
|
9210
|
-
if (
|
|
9348
|
+
if (zooming) {
|
|
9211
9349
|
const at = target.start + localX(e) / width * target.span;
|
|
9212
|
-
|
|
9350
|
+
const k = Math.exp(-e.deltaY * scale * (e.ctrlKey ? 0.01 : 0.002));
|
|
9351
|
+
retarget(zoomView(target, k, at), INSTANT2, lane);
|
|
9213
9352
|
} else {
|
|
9214
|
-
const d = (
|
|
9215
|
-
retarget({ ...target, start: target.start + d / width * target.span });
|
|
9353
|
+
const d = (sideways ? e.deltaX : e.deltaY) * scale;
|
|
9354
|
+
retarget({ ...target, start: target.start + d / width * target.span }, INSTANT2, lane);
|
|
9216
9355
|
}
|
|
9217
9356
|
}, { passive: false });
|
|
9357
|
+
let pinchScale = 1;
|
|
9358
|
+
let pinching = false;
|
|
9359
|
+
const onGesture = (e) => {
|
|
9360
|
+
const g = e;
|
|
9361
|
+
e.preventDefault();
|
|
9362
|
+
if (e.type === "gesturestart")
|
|
9363
|
+
pinchScale = 1;
|
|
9364
|
+
pinching = e.type !== "gestureend";
|
|
9365
|
+
if (e.type !== "gesturechange")
|
|
9366
|
+
return;
|
|
9367
|
+
const s = g.scale ?? 1;
|
|
9368
|
+
const x = g.clientX !== undefined ? localX({ clientX: g.clientX }) : width / 2;
|
|
9369
|
+
retarget(zoomView(target, s / pinchScale, target.start + x / width * target.span), INSTANT2, true);
|
|
9370
|
+
pinchScale = s;
|
|
9371
|
+
};
|
|
9372
|
+
for (const type of ["gesturestart", "gesturechange", "gestureend"])
|
|
9373
|
+
track.addEventListener(type, onGesture);
|
|
9218
9374
|
root.addEventListener("keydown", (e) => {
|
|
9219
9375
|
if (e.target !== root || e.metaKey || e.ctrlKey) {
|
|
9220
9376
|
if ((e.metaKey || e.ctrlKey) && (e.key === "=" || e.key === "-" || e.key === "0") && e.target === root) {
|
|
@@ -9222,7 +9378,7 @@ function timeline(container, options) {
|
|
|
9222
9378
|
if (e.key === "0")
|
|
9223
9379
|
fit();
|
|
9224
9380
|
else
|
|
9225
|
-
|
|
9381
|
+
zoomAtHand(e.key === "=" ? 1.5 : 1 / 1.5);
|
|
9226
9382
|
}
|
|
9227
9383
|
return;
|
|
9228
9384
|
}
|
|
@@ -9304,18 +9460,12 @@ function timeline(container, options) {
|
|
|
9304
9460
|
return;
|
|
9305
9461
|
case "=":
|
|
9306
9462
|
case "+":
|
|
9307
|
-
if (lane)
|
|
9308
|
-
return;
|
|
9309
9463
|
claim();
|
|
9310
|
-
return
|
|
9464
|
+
return zoomAtHand(1.5);
|
|
9311
9465
|
case "-":
|
|
9312
|
-
if (lane)
|
|
9313
|
-
return;
|
|
9314
9466
|
claim();
|
|
9315
|
-
return
|
|
9467
|
+
return zoomAtHand(1 / 1.5);
|
|
9316
9468
|
case "0":
|
|
9317
|
-
if (lane)
|
|
9318
|
-
return;
|
|
9319
9469
|
claim();
|
|
9320
9470
|
return fit();
|
|
9321
9471
|
}
|
|
@@ -9531,7 +9681,7 @@ function timeline(container, options) {
|
|
|
9531
9681
|
};
|
|
9532
9682
|
return api;
|
|
9533
9683
|
}
|
|
9534
|
-
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>';
|
|
9684
|
+
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>';
|
|
9535
9685
|
var init_timeline = __esm(() => {
|
|
9536
9686
|
init_rgba();
|
|
9537
9687
|
init_motion();
|
|
@@ -9603,14 +9753,28 @@ function compareLayout(box, before, after) {
|
|
|
9603
9753
|
const shape = after.w > 0 && after.h > 0 ? after : before;
|
|
9604
9754
|
const pair = letterbox({ w: shape.w * 2, h: shape.h }, box);
|
|
9605
9755
|
const half = pair.w / 2;
|
|
9756
|
+
const fused = { x: pair.x + half / 2, y: pair.y, w: half, h: pair.h };
|
|
9606
9757
|
return {
|
|
9607
9758
|
side: {
|
|
9608
9759
|
before: letterbox(before, { x: pair.x, y: pair.y, w: half, h: pair.h }),
|
|
9609
9760
|
after: letterbox(after, { x: pair.x + half, y: pair.y, w: half, h: pair.h }),
|
|
9610
9761
|
split: pair.x + half
|
|
9611
9762
|
},
|
|
9612
|
-
overlay: { before: letterbox(before,
|
|
9613
|
-
};
|
|
9763
|
+
overlay: { before: letterbox(before, fused), after: letterbox(after, fused) }
|
|
9764
|
+
};
|
|
9765
|
+
}
|
|
9766
|
+
function fuseFrame(L, before, after, k, wipe) {
|
|
9767
|
+
const t = Math.min(1, Math.max(0, k));
|
|
9768
|
+
const o = L.overlay;
|
|
9769
|
+
const left = Math.min(o.before.x, o.after.x);
|
|
9770
|
+
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);
|
|
9771
|
+
const cut = L.side.split + (line - L.side.split) * t;
|
|
9772
|
+
const contact = Math.max(0, Math.min(cut - after.x, before.x + before.w - cut));
|
|
9773
|
+
const feather = Math.min(contact, 1 + FUSE.feather * Math.sin(Math.PI * t));
|
|
9774
|
+
const overlap = before.x + before.w - after.x;
|
|
9775
|
+
const seam = t > 0.001 && overlap >= 0 ? Math.min(1, Math.max(0, overlap / FUSE.seamFade)) * Math.min(1, t * 4) : 0;
|
|
9776
|
+
const blur = t > 0.005 && t < 0.995 ? FUSE.blur * Math.sin(Math.PI * t) : 0;
|
|
9777
|
+
return { cut, feather, seam, blur };
|
|
9614
9778
|
}
|
|
9615
9779
|
function clampZoom(r) {
|
|
9616
9780
|
const w = Math.min(1, Math.max(1 / ZOOM_MAX, r.w));
|
|
@@ -9697,7 +9861,18 @@ function stripPeaks(diffs, o = {}) {
|
|
|
9697
9861
|
});
|
|
9698
9862
|
return out;
|
|
9699
9863
|
}
|
|
9700
|
-
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,
|
|
9864
|
+
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) => {
|
|
9865
|
+
const inner = r * Math.min(1, Math.max(0, k));
|
|
9866
|
+
return { before: { left: r, right: inner }, after: { left: inner, right: r } };
|
|
9867
|
+
}, labelFold = (fold, mark, blur = 3) => {
|
|
9868
|
+
const f = Math.min(1, Math.max(0, fold));
|
|
9869
|
+
const open = 1 - f;
|
|
9870
|
+
return {
|
|
9871
|
+
width: mark * open,
|
|
9872
|
+
opacity: Math.min(1, Math.max(0, (open - 0.2) / 0.8)),
|
|
9873
|
+
blur: blur * Math.min(1, Math.max(0, (f - 0.25) / 0.75))
|
|
9874
|
+
};
|
|
9875
|
+
}, 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({
|
|
9701
9876
|
u: r.u - dx / Math.max(1, base.w) * r.w,
|
|
9702
9877
|
v: r.v - dy / Math.max(1, base.h) * r.h,
|
|
9703
9878
|
w: r.w
|
|
@@ -9720,6 +9895,7 @@ var init_compare_model = __esm(() => {
|
|
|
9720
9895
|
MODE_KEYS = { side: "s", wipe: "w", difference: "d" };
|
|
9721
9896
|
VIDEO = /\.(mp4|m4v|mov|webm|ogv|mkv)$/i;
|
|
9722
9897
|
SEEK_IDLE = { seeking: false, pending: null };
|
|
9898
|
+
FUSE = { feather: 32, blur: 8, seamFade: 48 };
|
|
9723
9899
|
ZOOM_FIT = { u: 0, v: 0, w: 1, h: 1 };
|
|
9724
9900
|
});
|
|
9725
9901
|
|
|
@@ -10452,8 +10628,8 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
10452
10628
|
onLost();
|
|
10453
10629
|
};
|
|
10454
10630
|
lostListeners.add(lost);
|
|
10455
|
-
const uniforms = device.createBuffer({ size:
|
|
10456
|
-
const data = new Float32Array(
|
|
10631
|
+
const uniforms = device.createBuffer({ size: 192, usage: 64 | 8 });
|
|
10632
|
+
const data = new Float32Array(48);
|
|
10457
10633
|
const make = (w, h) => device.createTexture({
|
|
10458
10634
|
size: [Math.max(1, w), Math.max(1, h)],
|
|
10459
10635
|
format: "rgba8unorm",
|
|
@@ -10526,6 +10702,7 @@ async function createCompareGpu(canvas, onLost) {
|
|
|
10526
10702
|
data.set([f.preview[0] ? 1 : 0, f.preview[1] ? 1 : 0, 0, 0], 32);
|
|
10527
10703
|
r(36, f.beforeClip);
|
|
10528
10704
|
r(40, f.afterClip);
|
|
10705
|
+
data.set(f.radii, 44);
|
|
10529
10706
|
device.queue.writeBuffer(uniforms, 0, data);
|
|
10530
10707
|
const encoder = device.createCommandEncoder();
|
|
10531
10708
|
const pass = encoder.beginRenderPass({
|
|
@@ -10567,6 +10744,7 @@ struct U {
|
|
|
10567
10744
|
loupe: vec4f, // x, y, r, zoom (0: none)
|
|
10568
10745
|
src: vec4f, // Before, After from their previews
|
|
10569
10746
|
ca: vec4f, cb: vec4f, // where each side may draw
|
|
10747
|
+
radii: vec4f, // the clips' corners: Before left, right; After left, right
|
|
10570
10748
|
}
|
|
10571
10749
|
@group(0) @binding(0) var<uniform> u: U;
|
|
10572
10750
|
@group(0) @binding(1) var A: texture_2d<f32>;
|
|
@@ -10588,8 +10766,18 @@ fn cover(p: vec2f, r: vec4f) -> f32 {
|
|
|
10588
10766
|
return clamp(d + 0.5, 0.0, 1.0);
|
|
10589
10767
|
}
|
|
10590
10768
|
|
|
10591
|
-
//
|
|
10592
|
-
fn
|
|
10769
|
+
// Coverage of rect r at p with its left corners rounded rl and its right ones rr.
|
|
10770
|
+
fn coverRound(p: vec2f, r: vec4f, rl: f32, rr: f32) -> f32 {
|
|
10771
|
+
let h = r.zw * 0.5;
|
|
10772
|
+
let q0 = p - (r.xy + h);
|
|
10773
|
+
let rad = min(select(rl, rr, q0.x > 0.0), min(h.x, h.y));
|
|
10774
|
+
let q = abs(q0) - h + vec2f(rad);
|
|
10775
|
+
let d = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rad;
|
|
10776
|
+
return clamp(0.5 - d, 0.0, 1.0);
|
|
10777
|
+
}
|
|
10778
|
+
|
|
10779
|
+
// A side's colour where rect r maps it to p, and its coverage inside clip c (corners rc) there.
|
|
10780
|
+
fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, rc: vec2f, near: bool, ready: f32) -> vec4f {
|
|
10593
10781
|
let uv = clamp((p - r.xy) / max(r.zw, vec2f(1e-3)), vec2f(0.0), vec2f(1.0));
|
|
10594
10782
|
var c: vec3f;
|
|
10595
10783
|
if (near) {
|
|
@@ -10598,27 +10786,22 @@ fn take(t: texture_2d<f32>, p: vec2f, r: vec4f, c0: vec4f, near: bool, ready: f3
|
|
|
10598
10786
|
} else {
|
|
10599
10787
|
c = textureSampleLevel(t, smp, uv, 0.0).rgb;
|
|
10600
10788
|
}
|
|
10601
|
-
return vec4f(c, min(cover(p, r),
|
|
10789
|
+
return vec4f(c, min(cover(p, r), coverRound(p, c0, rc.x, rc.y)) * ready);
|
|
10602
10790
|
}
|
|
10603
10791
|
|
|
10604
10792
|
fn takeA(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
|
|
10605
|
-
if (u.src.x > 0.5) { return take(PA, p, r, c0, near, 1.0); }
|
|
10606
|
-
return take(A, p, r, c0, near, u.diff.z);
|
|
10793
|
+
if (u.src.x > 0.5) { return take(PA, p, r, c0, u.radii.xy, near, 1.0); }
|
|
10794
|
+
return take(A, p, r, c0, u.radii.xy, near, u.diff.z);
|
|
10607
10795
|
}
|
|
10608
10796
|
fn takeB(p: vec2f, r: vec4f, c0: vec4f, near: bool) -> vec4f {
|
|
10609
|
-
if (u.src.y > 0.5) { return take(PB, p, r, c0, near, 1.0); }
|
|
10610
|
-
return take(B, p, r, c0, near, u.diff.w);
|
|
10797
|
+
if (u.src.y > 0.5) { return take(PB, p, r, c0, u.radii.zw, near, 1.0); }
|
|
10798
|
+
return take(B, p, r, c0, u.radii.zw, near, u.diff.w);
|
|
10611
10799
|
}
|
|
10612
10800
|
|
|
10613
10801
|
fn pm(c: vec4f) -> vec4f { return vec4f(c.rgb * c.a, c.a); }
|
|
10614
10802
|
|
|
10615
10803
|
fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
|
|
10616
10804
|
switch mode {
|
|
10617
|
-
case 1u: {
|
|
10618
|
-
let s = max(u.wipe.y, 0.5);
|
|
10619
|
-
let m = smoothstep(u.wipe.x - s, u.wipe.x + s, p.x);
|
|
10620
|
-
return pm(vec4f(mix(a.rgb, b.rgb, m), mix(a.a, b.a, m)));
|
|
10621
|
-
}
|
|
10622
10805
|
case 2u: {
|
|
10623
10806
|
let d = abs(a.rgb - b.rgb);
|
|
10624
10807
|
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);
|
|
@@ -10627,8 +10810,10 @@ fn composite(mode: u32, p: vec2f, a: vec4f, b: vec4f) -> vec4f {
|
|
|
10627
10810
|
}
|
|
10628
10811
|
default: {}
|
|
10629
10812
|
}
|
|
10630
|
-
// Side by side
|
|
10631
|
-
|
|
10813
|
+
// Side by side, fusing and wiping: Before over After, cut at the seam with its feather.
|
|
10814
|
+
let s = max(u.wipe.y, 0.5);
|
|
10815
|
+
let ka = a.a * (1.0 - smoothstep(u.wipe.x - s, u.wipe.x + s, p.x));
|
|
10816
|
+
return vec4f(a.rgb * ka + b.rgb * b.a * (1.0 - ka), ka + b.a * (1.0 - ka));
|
|
10632
10817
|
}
|
|
10633
10818
|
|
|
10634
10819
|
@fragment fn fs(@builtin(position) q: vec4f) -> @location(0) vec4f {
|
|
@@ -11030,6 +11215,29 @@ var init_compare_strip = __esm(() => {
|
|
|
11030
11215
|
DIFF = { w: 32, h: 18 };
|
|
11031
11216
|
});
|
|
11032
11217
|
|
|
11218
|
+
// src/ui/player.ts
|
|
11219
|
+
function playGlyph(doc) {
|
|
11220
|
+
const element = doc.createElement("span");
|
|
11221
|
+
element.className = "knobs-player-glyph";
|
|
11222
|
+
element.setAttribute("aria-hidden", "true");
|
|
11223
|
+
let timer = 0;
|
|
11224
|
+
const win = doc.defaultView;
|
|
11225
|
+
return {
|
|
11226
|
+
element,
|
|
11227
|
+
flash(playing) {
|
|
11228
|
+
element.innerHTML = playing ? PLAY2 : PAUSE;
|
|
11229
|
+
element.setAttribute("data-flash", "");
|
|
11230
|
+
win.clearTimeout(timer);
|
|
11231
|
+
timer = win.setTimeout(() => element.removeAttribute("data-flash"), FLASH_MS);
|
|
11232
|
+
},
|
|
11233
|
+
dispose: () => win.clearTimeout(timer)
|
|
11234
|
+
};
|
|
11235
|
+
}
|
|
11236
|
+
var FLASH_MS = 450;
|
|
11237
|
+
var init_player = __esm(() => {
|
|
11238
|
+
init_timeline();
|
|
11239
|
+
});
|
|
11240
|
+
|
|
11033
11241
|
// src/ui/compare.ts
|
|
11034
11242
|
function compare(container, options) {
|
|
11035
11243
|
const doc = container.ownerDocument;
|
|
@@ -11137,6 +11345,8 @@ function compare(container, options) {
|
|
|
11137
11345
|
if (name.textContent !== text)
|
|
11138
11346
|
name.textContent = text;
|
|
11139
11347
|
};
|
|
11348
|
+
const seam = el("div", "knobs-cmp-seam");
|
|
11349
|
+
seam.setAttribute("aria-hidden", "true");
|
|
11140
11350
|
const handle = el("div", "knobs-cmp-handle");
|
|
11141
11351
|
handle.setAttribute("role", "slider");
|
|
11142
11352
|
handle.setAttribute("aria-label", "Wipe");
|
|
@@ -11146,7 +11356,8 @@ function compare(container, options) {
|
|
|
11146
11356
|
grip.setAttribute("aria-hidden", "true");
|
|
11147
11357
|
handle.append(grip);
|
|
11148
11358
|
const note = el("div", "knobs-cmp-note");
|
|
11149
|
-
|
|
11359
|
+
const glyph = playGlyph(doc);
|
|
11360
|
+
stage.append(canvas, split, seam, labels[0], labels[1], handle, zoomPill, note, glyph.element);
|
|
11150
11361
|
root.append(bar, stage);
|
|
11151
11362
|
const wantsStrip = options.strip === true;
|
|
11152
11363
|
let tl = bound;
|
|
@@ -11161,7 +11372,7 @@ function compare(container, options) {
|
|
|
11161
11372
|
root.append(host);
|
|
11162
11373
|
tl = timeline(host, {
|
|
11163
11374
|
...look,
|
|
11164
|
-
variant: "
|
|
11375
|
+
variant: "line",
|
|
11165
11376
|
duration: 1,
|
|
11166
11377
|
fps: fps(),
|
|
11167
11378
|
arrows: { step: 0.5, shift: 2 },
|
|
@@ -11182,12 +11393,13 @@ function compare(container, options) {
|
|
|
11182
11393
|
let toMode = mode;
|
|
11183
11394
|
const wipeLine = new Spring(wipe, WIPE_CATCH.omega, WIPE_CATCH.zeta, 0.0001);
|
|
11184
11395
|
let wipeCatching = false;
|
|
11185
|
-
|
|
11396
|
+
const gripAt = new Spring(0.5, LAYOUT2.omega, LAYOUT2.zeta, 0.0001);
|
|
11186
11397
|
let loupeHeld = false;
|
|
11187
11398
|
let pointer = null;
|
|
11188
11399
|
let drag = null;
|
|
11189
11400
|
const touches = new Map;
|
|
11190
11401
|
let pinch = null;
|
|
11402
|
+
let press = null;
|
|
11191
11403
|
const zu = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
11192
11404
|
const zv = new Spring(0, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
11193
11405
|
const zw = new Spring(1, ZOOM.omega, ZOOM.zeta, 0.00001);
|
|
@@ -11199,6 +11411,7 @@ function compare(container, options) {
|
|
|
11199
11411
|
let stageRect = null;
|
|
11200
11412
|
let accent = [0.49, 0.81, 0.61];
|
|
11201
11413
|
let labelW = [0, 0];
|
|
11414
|
+
let frameRadius = 0;
|
|
11202
11415
|
let dirty = true;
|
|
11203
11416
|
let disposed = false;
|
|
11204
11417
|
let near = options.lazy === false;
|
|
@@ -11474,6 +11687,8 @@ function compare(container, options) {
|
|
|
11474
11687
|
for (const s of sides) {
|
|
11475
11688
|
const st = s.el.style;
|
|
11476
11689
|
st.clipPath = "";
|
|
11690
|
+
st.maskImage = "";
|
|
11691
|
+
st.setProperty("-webkit-mask-image", "");
|
|
11477
11692
|
st.mixBlendMode = "";
|
|
11478
11693
|
st.opacity = "";
|
|
11479
11694
|
s.shown = null;
|
|
@@ -11700,43 +11915,66 @@ function compare(container, options) {
|
|
|
11700
11915
|
viewSent = key;
|
|
11701
11916
|
options.onView({ zoom: clampZoom(z), loop: l });
|
|
11702
11917
|
};
|
|
11703
|
-
const
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
return [at.before, at.after];
|
|
11707
|
-
}
|
|
11708
|
-
const ax = r.after.x + INSET + (r.after.w - labelW[1] - INSET * 2) * lay.x;
|
|
11709
|
-
return [
|
|
11710
|
-
{ x: r.before.x + INSET, opacity: 1 },
|
|
11711
|
-
{ x: ax, opacity: 1 }
|
|
11712
|
-
];
|
|
11918
|
+
const differenceWeight = () => {
|
|
11919
|
+
const f = Math.min(1, Math.max(0, fade.x));
|
|
11920
|
+
return (fromMode === "difference" ? 1 - f : 0) + (toMode === "difference" ? f : 0);
|
|
11713
11921
|
};
|
|
11714
11922
|
const placeGrip = (wr) => {
|
|
11715
|
-
const gy = gripTop(
|
|
11923
|
+
const gy = gripTop(wr.y + gripAt.x * wr.h, wr, GRIP.h) - wr.y;
|
|
11716
11924
|
grip.style.transform = `translate3d(0,${gy.toFixed(2)}px,0)`;
|
|
11717
11925
|
};
|
|
11926
|
+
let folded = 0;
|
|
11718
11927
|
const draw = () => {
|
|
11719
11928
|
dirty = false;
|
|
11929
|
+
if (!root.hasAttribute("data-ready") && sides.every((x) => x.shown || x.failed))
|
|
11930
|
+
root.setAttribute("data-ready", "");
|
|
11720
11931
|
const r = layoutNow();
|
|
11721
11932
|
const wr = wipeRect(r.L.overlay);
|
|
11722
|
-
const
|
|
11723
|
-
const
|
|
11724
|
-
const
|
|
11725
|
-
const
|
|
11726
|
-
const
|
|
11933
|
+
const k = Math.min(1, Math.max(0, lay.x));
|
|
11934
|
+
const fu = fuseFrame(r.L, r.before, r.after, k, wipeLine.x);
|
|
11935
|
+
const corners = fuseCorners(frameRadius, k);
|
|
11936
|
+
const dw = differenceWeight();
|
|
11937
|
+
const fusing = 1 - dw;
|
|
11938
|
+
folded = k * fusing;
|
|
11939
|
+
const fold = labelFold(folded, MARK.w);
|
|
11940
|
+
const w = labelW.map((x) => x - (MARK.w - fold.width) - MARK.gap * folded);
|
|
11941
|
+
const ride = seamLabels(fu.cut, r.before.x, r.after.x + r.after.w, w[0], w[1], INSET);
|
|
11942
|
+
const apart = [r.before.x + INSET, r.after.x + INSET];
|
|
11943
|
+
const corner = [r.before.x + INSET, r.after.x + r.after.w - labelW[1] - INSET];
|
|
11944
|
+
const rides = [ride.before, ride.after];
|
|
11727
11945
|
const ys = [r.before.y + INSET, r.after.y + INSET];
|
|
11728
11946
|
for (const i of [0, 1]) {
|
|
11729
|
-
const
|
|
11730
|
-
|
|
11731
|
-
|
|
11947
|
+
const fused = apart[i] + (rides[i].x - apart[i]) * k;
|
|
11948
|
+
const x = fused + (corner[i] - fused) * dw;
|
|
11949
|
+
const opacity = (1 - k + k * rides[i].opacity) * fusing + dw;
|
|
11950
|
+
const l = labels[i];
|
|
11951
|
+
l.style.transform = `translate(${x.toFixed(2)}px,${ys[i].toFixed(2)}px)`;
|
|
11952
|
+
l.style.opacity = opacity.toFixed(3);
|
|
11953
|
+
const mark = l.firstElementChild;
|
|
11954
|
+
if (mark) {
|
|
11955
|
+
mark.style.width = `${fold.width.toFixed(2)}px`;
|
|
11956
|
+
mark.style.marginRight = `${(-MARK.gap * folded).toFixed(2)}px`;
|
|
11957
|
+
mark.style.opacity = fold.opacity.toFixed(3);
|
|
11958
|
+
mark.style.filter = fold.blur > 0.05 ? `blur(${fold.blur.toFixed(2)}px)` : "";
|
|
11959
|
+
}
|
|
11732
11960
|
}
|
|
11733
11961
|
const top = Math.min(r.L.side.before.y, r.L.side.after.y);
|
|
11734
11962
|
split.style.transform = `translate(${r.L.side.split.toFixed(2)}px,${top.toFixed(2)}px)`;
|
|
11735
11963
|
split.style.height = `${Math.max(0, r.L.side.after.h).toFixed(2)}px`;
|
|
11736
11964
|
split.style.opacity = String(Math.max(0, 1 - k * 2));
|
|
11965
|
+
const seamOn = fu.seam * fusing;
|
|
11966
|
+
const blur = fu.blur > 0.01 ? `blur(${fu.blur.toFixed(2)}px)` : "none";
|
|
11967
|
+
seam.style.transform = `translate(${fu.cut.toFixed(2)}px,${wr.y.toFixed(2)}px) translateX(-50%)`;
|
|
11968
|
+
seam.style.width = `${(8 + 2 * fu.feather).toFixed(2)}px`;
|
|
11969
|
+
seam.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
|
|
11970
|
+
seam.style.opacity = (blur === "none" ? 0 : seamOn).toFixed(3);
|
|
11971
|
+
seam.style.backdropFilter = blur;
|
|
11972
|
+
seam.style.setProperty("-webkit-backdrop-filter", blur);
|
|
11737
11973
|
handle.toggleAttribute("data-on", mode === "wipe");
|
|
11738
|
-
handle.style.transform = `translate(${
|
|
11974
|
+
handle.style.transform = `translate(${fu.cut.toFixed(2)}px,${wr.y.toFixed(2)}px)`;
|
|
11739
11975
|
handle.style.height = `${Math.max(0, wr.h).toFixed(2)}px`;
|
|
11976
|
+
handle.style.opacity = seamOn.toFixed(3);
|
|
11977
|
+
handle.style.filter = blur;
|
|
11740
11978
|
placeGrip(wr);
|
|
11741
11979
|
handle.setAttribute("aria-valuenow", String(Math.round(wipe * 100)));
|
|
11742
11980
|
handle.setAttribute("aria-valuetext", `${Math.round(wipe * 100)}% Before`);
|
|
@@ -11749,24 +11987,30 @@ function compare(container, options) {
|
|
|
11749
11987
|
if (pending)
|
|
11750
11988
|
return;
|
|
11751
11989
|
if (renderer === "dom") {
|
|
11752
|
-
const
|
|
11990
|
+
const cut = mode === "difference" ? null : fu;
|
|
11991
|
+
const place = (n, rect, clip, before) => {
|
|
11992
|
+
const round = before ? corners.before : corners.after;
|
|
11753
11993
|
const st = n.style;
|
|
11754
11994
|
st.transform = `translate(${rect.x.toFixed(2)}px,${rect.y.toFixed(2)}px)`;
|
|
11755
11995
|
st.width = `${rect.w.toFixed(2)}px`;
|
|
11756
11996
|
st.height = `${rect.h.toFixed(2)}px`;
|
|
11997
|
+
const edge = before && cut ? cut.cut + cut.feather : Number.POSITIVE_INFINITY;
|
|
11757
11998
|
const t = Math.max(0, clip.y - rect.y);
|
|
11758
11999
|
const l = Math.max(0, clip.x - rect.x);
|
|
11759
12000
|
const b = Math.max(0, rect.y + rect.h - (clip.y + clip.h));
|
|
11760
|
-
const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w,
|
|
11761
|
-
|
|
12001
|
+
const right = Math.max(0, rect.x + rect.w - Math.min(clip.x + clip.w, edge));
|
|
12002
|
+
const rr = edge < clip.x + clip.w - 0.5 ? 0 : round.right;
|
|
12003
|
+
const rl = round.left;
|
|
12004
|
+
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)` : "";
|
|
12005
|
+
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)` : "";
|
|
12006
|
+
st.maskImage = mask;
|
|
12007
|
+
st.setProperty("-webkit-mask-image", mask);
|
|
11762
12008
|
};
|
|
11763
|
-
const cut = mode === "wipe" ? wx : Number.POSITIVE_INFINITY;
|
|
11764
12009
|
for (const s of sides) {
|
|
11765
12010
|
const rect = s.index === 0 ? r.zBefore : r.zAfter;
|
|
11766
12011
|
const clip = s.index === 0 ? r.before : r.after;
|
|
11767
|
-
|
|
11768
|
-
place(s.
|
|
11769
|
-
place(s.preview, rect, clip, at);
|
|
12012
|
+
place(s.el, rect, clip, s.index === 0);
|
|
12013
|
+
place(s.preview, rect, clip, s.index === 0);
|
|
11770
12014
|
if (s.index !== 0)
|
|
11771
12015
|
continue;
|
|
11772
12016
|
for (const n of [s.el, s.preview])
|
|
@@ -11786,8 +12030,8 @@ function compare(container, options) {
|
|
|
11786
12030
|
width: Math.max(1, Math.round(stageW * dpr)),
|
|
11787
12031
|
height: Math.max(1, Math.round(stageH * dpr)),
|
|
11788
12032
|
dpr,
|
|
11789
|
-
wipeX:
|
|
11790
|
-
soft: 0.75 * dpr,
|
|
12033
|
+
wipeX: fu.cut * dpr,
|
|
12034
|
+
soft: Math.max(0.75, fu.feather) * dpr,
|
|
11791
12035
|
from: MODE_INDEX[fromMode],
|
|
11792
12036
|
to: MODE_INDEX[toMode],
|
|
11793
12037
|
mix: Math.min(1, Math.max(0, fade.x)),
|
|
@@ -11795,7 +12039,13 @@ function compare(container, options) {
|
|
|
11795
12039
|
gain: 1,
|
|
11796
12040
|
accent,
|
|
11797
12041
|
loupe: loupeShown() && pointer ? { x: pointer.x * dpr, y: pointer.y * dpr, r: LOUPE.r * dpr, zoom: LOUPE.zoom } : null,
|
|
11798
|
-
preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)]
|
|
12042
|
+
preview: [Boolean(sides[0].shown?.cached), Boolean(sides[1].shown?.cached)],
|
|
12043
|
+
radii: [
|
|
12044
|
+
corners.before.left * dpr,
|
|
12045
|
+
corners.before.right * dpr,
|
|
12046
|
+
corners.after.left * dpr,
|
|
12047
|
+
corners.after.right * dpr
|
|
12048
|
+
]
|
|
11799
12049
|
});
|
|
11800
12050
|
};
|
|
11801
12051
|
const drawNow = () => {
|
|
@@ -11814,6 +12064,7 @@ function compare(container, options) {
|
|
|
11814
12064
|
zu.step(dt);
|
|
11815
12065
|
zv.step(dt);
|
|
11816
12066
|
zw.step(dt);
|
|
12067
|
+
gripAt.step(dt);
|
|
11817
12068
|
const playing = isPlaying();
|
|
11818
12069
|
if (playing) {
|
|
11819
12070
|
const m = master();
|
|
@@ -11844,11 +12095,9 @@ function compare(container, options) {
|
|
|
11844
12095
|
pushFrame(s);
|
|
11845
12096
|
}
|
|
11846
12097
|
}
|
|
11847
|
-
const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching;
|
|
12098
|
+
const moving = !lay.resting || !fade.resting || !zoomResting() || wipeCatching || !gripAt.resting;
|
|
11848
12099
|
if (moving)
|
|
11849
12100
|
dirty = true;
|
|
11850
|
-
if (auto && !lay.resting)
|
|
11851
|
-
fitHeight();
|
|
11852
12101
|
if (dirty)
|
|
11853
12102
|
draw();
|
|
11854
12103
|
return playing || moving;
|
|
@@ -11874,13 +12123,7 @@ function compare(container, options) {
|
|
|
11874
12123
|
zw.set(c.w);
|
|
11875
12124
|
requestDraw();
|
|
11876
12125
|
};
|
|
11877
|
-
const autoHeight = (w) =>
|
|
11878
|
-
const shape = sizeOf(sides[1]);
|
|
11879
|
-
const side = splitHeight(w, shape);
|
|
11880
|
-
const over = w * shape.h / shape.w;
|
|
11881
|
-
const h = side + (over - side) * lay.x;
|
|
11882
|
-
return Math.max(60, Math.min(win.innerHeight * 0.8, h));
|
|
11883
|
-
};
|
|
12126
|
+
const autoHeight = (w) => Math.max(60, Math.min(win.innerHeight * 0.8, splitHeight(w, sizeOf(sides[1]))));
|
|
11884
12127
|
const fitHeight = () => {
|
|
11885
12128
|
const h = autoHeight(stageW);
|
|
11886
12129
|
if (Math.abs(h - stageH) < 0.25)
|
|
@@ -11892,7 +12135,9 @@ function compare(container, options) {
|
|
|
11892
12135
|
const readColors = () => {
|
|
11893
12136
|
const c = tokenColor(win.getComputedStyle(root), "--_ku-cmp-diff", [0.49, 0.81, 0.61, 1]);
|
|
11894
12137
|
accent = [c[0], c[1], c[2]];
|
|
11895
|
-
|
|
12138
|
+
const unfold = MARK.w - labelFold(folded, MARK.w).width + MARK.gap * folded;
|
|
12139
|
+
labelW = [labels[0].offsetWidth + unfold, labels[1].offsetWidth + unfold];
|
|
12140
|
+
frameRadius = Number.parseFloat(win.getComputedStyle(stage).borderTopLeftRadius) || 0;
|
|
11896
12141
|
requestDraw();
|
|
11897
12142
|
};
|
|
11898
12143
|
const measure = () => {
|
|
@@ -11983,8 +12228,10 @@ function compare(container, options) {
|
|
|
11983
12228
|
root.focus({ preventScroll: true });
|
|
11984
12229
|
touches.set(e.pointerId, p);
|
|
11985
12230
|
stage.setPointerCapture(e.pointerId);
|
|
12231
|
+
press = touches.size === 1 && mode !== "wipe" && master() ? p : null;
|
|
11986
12232
|
if (touches.size >= 2) {
|
|
11987
12233
|
e.preventDefault();
|
|
12234
|
+
press = null;
|
|
11988
12235
|
return startPinch();
|
|
11989
12236
|
}
|
|
11990
12237
|
if (mode === "wipe") {
|
|
@@ -11994,7 +12241,7 @@ function compare(container, options) {
|
|
|
11994
12241
|
if (inside && (grab.onLine || !zoomed())) {
|
|
11995
12242
|
drag = { kind: "wipe", offset: grab.offset };
|
|
11996
12243
|
root.dataset.dragging = "";
|
|
11997
|
-
|
|
12244
|
+
gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
|
|
11998
12245
|
e.preventDefault();
|
|
11999
12246
|
if (!grab.onLine)
|
|
12000
12247
|
setWipe(wipeAt(p.x, wr), true, true);
|
|
@@ -12012,6 +12259,8 @@ function compare(container, options) {
|
|
|
12012
12259
|
const onMove = (e) => {
|
|
12013
12260
|
const p = local(e);
|
|
12014
12261
|
pointer = p;
|
|
12262
|
+
if (press && Math.hypot(p.x - press.x, p.y - press.y) > 4)
|
|
12263
|
+
press = null;
|
|
12015
12264
|
if (touches.has(e.pointerId))
|
|
12016
12265
|
touches.set(e.pointerId, p);
|
|
12017
12266
|
if (pinch && touches.size >= 2) {
|
|
@@ -12025,14 +12274,12 @@ function compare(container, options) {
|
|
|
12025
12274
|
pinch = { d, x: mid.x, y: mid.y };
|
|
12026
12275
|
return handZoom(zoomAbout(panned, k, q.pu, q.pv));
|
|
12027
12276
|
}
|
|
12028
|
-
if (
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
if (drag?.kind === "
|
|
12034
|
-
setWipe(wipeAt(p.x, wipeRect(layoutNow().L.overlay), drag.offset));
|
|
12035
|
-
else if (drag?.kind === "pan") {
|
|
12277
|
+
if (drag?.kind === "wipe") {
|
|
12278
|
+
const wr = wipeRect(layoutNow().L.overlay);
|
|
12279
|
+
gripAt.snap(wr.h > 0 ? (p.y - wr.y) / wr.h : 0.5);
|
|
12280
|
+
setWipe(wipeAt(p.x, wr, drag.offset));
|
|
12281
|
+
placeGrip(wr);
|
|
12282
|
+
} else if (drag?.kind === "pan") {
|
|
12036
12283
|
const dx = p.x - drag.x;
|
|
12037
12284
|
const dy = p.y - drag.y;
|
|
12038
12285
|
drag.x = p.x;
|
|
@@ -12043,11 +12290,23 @@ function compare(container, options) {
|
|
|
12043
12290
|
drawNow();
|
|
12044
12291
|
};
|
|
12045
12292
|
const endDrag = (e) => {
|
|
12293
|
+
if (press && e.type === "pointerup") {
|
|
12294
|
+
api.toggle();
|
|
12295
|
+
glyph.flash(isPlaying());
|
|
12296
|
+
}
|
|
12297
|
+
press = null;
|
|
12046
12298
|
touches.delete(e.pointerId);
|
|
12047
12299
|
if (touches.size < 2)
|
|
12048
12300
|
pinch = null;
|
|
12049
12301
|
if (!drag)
|
|
12050
12302
|
return;
|
|
12303
|
+
if (drag.kind === "wipe") {
|
|
12304
|
+
if (reducedMotion(win))
|
|
12305
|
+
gripAt.snap(0.5);
|
|
12306
|
+
else
|
|
12307
|
+
gripAt.set(0.5);
|
|
12308
|
+
requestDraw();
|
|
12309
|
+
}
|
|
12051
12310
|
drag = null;
|
|
12052
12311
|
delete root.dataset.dragging;
|
|
12053
12312
|
delete root.dataset.panning;
|
|
@@ -12197,6 +12456,7 @@ function compare(container, options) {
|
|
|
12197
12456
|
old.el.replaceWith(s.el);
|
|
12198
12457
|
old.preview.replaceWith(s.preview);
|
|
12199
12458
|
sides[index] = s;
|
|
12459
|
+
root.toggleAttribute("data-video", sides.some((x) => x.kind === "video"));
|
|
12200
12460
|
note.textContent = "";
|
|
12201
12461
|
setLabel(index, labelOf(index));
|
|
12202
12462
|
if (near)
|
|
@@ -12211,6 +12471,7 @@ function compare(container, options) {
|
|
|
12211
12471
|
stage.insertBefore(sides[0].preview, labels[0]);
|
|
12212
12472
|
setLabel(0, labelOf(0));
|
|
12213
12473
|
setLabel(1, labelOf(1));
|
|
12474
|
+
root.toggleAttribute("data-video", sides.some((s) => s.kind === "video"));
|
|
12214
12475
|
loupeBtn.setAttribute("aria-pressed", String(loupeOn));
|
|
12215
12476
|
loopBtn.setAttribute("aria-pressed", String(loopNow() !== null));
|
|
12216
12477
|
const modePill = modeList.length > 1 ? selectionPill(modes, {
|
|
@@ -12411,6 +12672,7 @@ function compare(container, options) {
|
|
|
12411
12672
|
disposed = true;
|
|
12412
12673
|
loop.stop();
|
|
12413
12674
|
modePill?.dispose();
|
|
12675
|
+
glyph.dispose();
|
|
12414
12676
|
win.clearTimeout(releaseTimer);
|
|
12415
12677
|
ro.disconnect();
|
|
12416
12678
|
nearIo?.disconnect();
|
|
@@ -12440,7 +12702,7 @@ function compare(container, options) {
|
|
|
12440
12702
|
wake();
|
|
12441
12703
|
return api;
|
|
12442
12704
|
}
|
|
12443
|
-
var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
12705
|
+
var LAYOUT2, FADE6, FADE_REDUCED, ZOOM, WIPE_CATCH, SLOP = 12, INSET = 8, GRIP, MARK, LOUPE, FAST_SEEK_FRAMES = 15, RELEASE_MS = 3000, MODE_LABEL;
|
|
12444
12706
|
var init_compare = __esm(() => {
|
|
12445
12707
|
init_motion();
|
|
12446
12708
|
init_page_hints();
|
|
@@ -12451,15 +12713,17 @@ var init_compare = __esm(() => {
|
|
|
12451
12713
|
init_icons();
|
|
12452
12714
|
init_look();
|
|
12453
12715
|
init_rgba();
|
|
12716
|
+
init_player();
|
|
12454
12717
|
init_selection_pill();
|
|
12455
12718
|
init_timeline();
|
|
12456
12719
|
init_timeline_model();
|
|
12457
|
-
LAYOUT2 = springMotion(
|
|
12720
|
+
LAYOUT2 = springMotion(SPRINGS.default);
|
|
12458
12721
|
FADE6 = springMotion(SPRINGS.snappy);
|
|
12459
12722
|
FADE_REDUCED = springMotion(FADE);
|
|
12460
12723
|
ZOOM = springMotion({ duration: 0.35, bounce: 0 });
|
|
12461
12724
|
WIPE_CATCH = springMotion(CATCH_UP);
|
|
12462
12725
|
GRIP = { w: 16, h: 28 };
|
|
12726
|
+
MARK = { w: 12, gap: 4 };
|
|
12463
12727
|
LOUPE = { r: 88, zoom: 4 };
|
|
12464
12728
|
MODE_LABEL = {
|
|
12465
12729
|
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)
|
|
@@ -20585,7 +22243,7 @@ function walkthrough(container, options) {
|
|
|
20585
22243
|
}
|
|
20586
22244
|
|
|
20587
22245
|
// src/ui/solid.ts
|
|
20588
|
-
var
|
|
22246
|
+
var follow2 = (read, apply) => {
|
|
20589
22247
|
const changes = propChanges(untrack(read));
|
|
20590
22248
|
createEffect(() => {
|
|
20591
22249
|
const changed = changes(read());
|
|
@@ -20633,7 +22291,7 @@ function Tree(props) {
|
|
|
20633
22291
|
}
|
|
20634
22292
|
}));
|
|
20635
22293
|
untrack(() => props.onReady?.(t));
|
|
20636
|
-
|
|
22294
|
+
follow2(() => ({ items: props.items, filter: props.filter, selection: props.selection }), (c) => {
|
|
20637
22295
|
if ("items" in c)
|
|
20638
22296
|
t.update(c.items);
|
|
20639
22297
|
if ("filter" in c)
|
|
@@ -20669,7 +22327,7 @@ function CodeDiff(props) {
|
|
|
20669
22327
|
onViewed: props.onViewed && ((viewed, file) => props.onViewed?.(viewed, file))
|
|
20670
22328
|
}));
|
|
20671
22329
|
untrack(() => props.onReady?.(d));
|
|
20672
|
-
|
|
22330
|
+
follow2(diffProps(props), (c) => {
|
|
20673
22331
|
if (c.height)
|
|
20674
22332
|
host.style.height = c.height === "auto" ? "" : "100%";
|
|
20675
22333
|
d.update(c);
|
|
@@ -20692,7 +22350,7 @@ function DiffReview(props) {
|
|
|
20692
22350
|
onViewed: (viewed, file) => props.onViewed?.(viewed, file)
|
|
20693
22351
|
}));
|
|
20694
22352
|
untrack(() => props.onReady?.(r));
|
|
20695
|
-
|
|
22353
|
+
follow2(diffProps(props), (c) => {
|
|
20696
22354
|
if (c.height)
|
|
20697
22355
|
host.style.height = c.height === "auto" ? "" : "100%";
|
|
20698
22356
|
r.update(c);
|
|
@@ -20714,7 +22372,7 @@ function Timeline(props) {
|
|
|
20714
22372
|
onCross: (m) => props.onCross?.(m)
|
|
20715
22373
|
}));
|
|
20716
22374
|
untrack(() => props.onReady?.(t));
|
|
20717
|
-
|
|
22375
|
+
follow2(() => ({
|
|
20718
22376
|
duration: props.duration,
|
|
20719
22377
|
fps: props.fps,
|
|
20720
22378
|
units: props.units,
|
|
@@ -20743,7 +22401,7 @@ function Compare(props) {
|
|
|
20743
22401
|
onView: (view) => props.onView?.(view)
|
|
20744
22402
|
}));
|
|
20745
22403
|
untrack(() => props.onReady?.(c));
|
|
20746
|
-
|
|
22404
|
+
follow2(() => ({
|
|
20747
22405
|
before: props.before,
|
|
20748
22406
|
after: props.after,
|
|
20749
22407
|
mode: props.mode,
|
|
@@ -20766,7 +22424,7 @@ function CompareStrip(props) {
|
|
|
20766
22424
|
host.style.height = "100%";
|
|
20767
22425
|
const s = untrack(() => compareStrip(host, { ...props, onPick: (time) => props.onPick?.(time) }));
|
|
20768
22426
|
untrack(() => props.onReady?.(s));
|
|
20769
|
-
|
|
22427
|
+
follow2(() => ({ before: props.before.src, after: props.after.src }), (changed) => s.update({
|
|
20770
22428
|
..."before" in changed ? { before: props.before } : {},
|
|
20771
22429
|
..."after" in changed ? { after: props.after } : {}
|
|
20772
22430
|
}));
|
|
@@ -20786,7 +22444,7 @@ function Bento(props) {
|
|
|
20786
22444
|
onActive: (id) => props.onActive?.(id)
|
|
20787
22445
|
}));
|
|
20788
22446
|
untrack(() => props.onReady?.(b));
|
|
20789
|
-
|
|
22447
|
+
follow2(() => ({ items: props.items, picks: props.picks }), (c) => {
|
|
20790
22448
|
if ("items" in c)
|
|
20791
22449
|
b.update(c.items);
|
|
20792
22450
|
if (c.picks)
|
|
@@ -20808,7 +22466,7 @@ function Activity(props) {
|
|
|
20808
22466
|
onPlay: props.onPlay && ((id, event) => props.onPlay?.(id, event))
|
|
20809
22467
|
}));
|
|
20810
22468
|
untrack(() => props.onReady?.(a));
|
|
20811
|
-
|
|
22469
|
+
follow2(() => ({ events: props.events, newest: props.newest, selected: props.selected }), (c) => {
|
|
20812
22470
|
if (c.events || c.newest)
|
|
20813
22471
|
a.update({ events: c.events, newest: c.newest });
|
|
20814
22472
|
if (c.selected !== undefined && c.selected !== a.selected)
|
|
@@ -20837,7 +22495,7 @@ function OptionPicker(props) {
|
|
|
20837
22495
|
}
|
|
20838
22496
|
}));
|
|
20839
22497
|
untrack(() => props.onReady?.(p));
|
|
20840
|
-
|
|
22498
|
+
follow2(() => ({ options: props.options, selected: props.selected, chosen: props.chosen }), (c) => {
|
|
20841
22499
|
if ("options" in c)
|
|
20842
22500
|
p.set(c.options);
|
|
20843
22501
|
if (c.selected != null)
|
|
@@ -20854,7 +22512,7 @@ function Walkthrough(props) {
|
|
|
20854
22512
|
const host = document.createElement("div");
|
|
20855
22513
|
const w = untrack(() => walkthrough(host, { ...props, onFeedback: (feedback) => props.onFeedback?.(feedback) }));
|
|
20856
22514
|
untrack(() => props.onReady?.(w));
|
|
20857
|
-
|
|
22515
|
+
follow2(() => ({ doc: props.doc }), (c) => c.doc && w.set(c.doc));
|
|
20858
22516
|
onCleanup(() => w.dispose());
|
|
20859
22517
|
return host;
|
|
20860
22518
|
}
|
|
@@ -20864,7 +22522,7 @@ function TasteTest(props) {
|
|
|
20864
22522
|
const host = document.createElement("div");
|
|
20865
22523
|
const t = untrack(() => tasteTest(host, { ...props, onPick: (id) => props.onPick?.(id) }));
|
|
20866
22524
|
untrack(() => props.onReady?.(t));
|
|
20867
|
-
|
|
22525
|
+
follow2(() => ({ picked: props.picked }), (c) => ("picked" in c) && t.pick(c.picked ?? null));
|
|
20868
22526
|
onCleanup(() => t.dispose());
|
|
20869
22527
|
return host;
|
|
20870
22528
|
}
|