@kitlangton/knobs 0.4.0-next.5 → 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 CHANGED
@@ -1359,11 +1359,11 @@ const w = walkthrough(container, { doc, onFeedback: (fb) => send(fb) })
1359
1359
 
1360
1360
  A section's meta line leads with its **kicker** in text colour: `"PR 2 of 5"` by default where its `link` is a pull request (counted among the sections that link one). The doc's `kickers` counts with another noun, `{ noun: "Part" }` giving every section "Part 2 of 6", or none with `false`. A section's own `kicker: "Epilogue"` wins over its count and `kicker: false` hides it, without renumbering the rest. The contents rail isn't numbered. Its PR link follows in faint, joined by `·` (its `status` only with review on). One type scale: the page's title 20/28, a section's 16/24, a step's 13/20 at 500, prose 13/20 in the second ink, meta 11/16 in the third. Steps aren't numbered on screen, but a ref still names one by its key, id or number (`"1.2"`): `step` on an update, a history event, a stack item or a table cell, and clicking goes there as `j` / `k` do.
1361
1361
 
1362
- By default the page only shows: no progress, notes or Copy feedback, no key hints, and a step's `options` sit side by side under their labels. `review: true` brings Kit's review tools back: the progress in the rail, Copy feedback, picking between options (`1`–`9`) and the note bar. With review on, notes come from a launcher in the column's corner (`noteBar: "collapsed"`, the default) that opens into the bar on a click, `n`, a recording or ink, and folds back when it's empty; `"docked"` keeps the bar there, `"hidden"` drops it (`w.send()` still works). Typed, spoken or inked, each note carries what was on show (a video's time and chapter, a compare's loop and zoom, a diff's file) and a short log of what led up to it; `onFeedback`, `w.feedback` and `w.markdown()` hand it back. The page keeps the bar's height clear at its end and as its scroller's `scroll-padding-bottom`, so `j` / `k`, `go()` and the last step never land under it, and full window stops above it. `j` / `k` move between steps (`Home` / `End` to the first and last), `←` / `→` seek, `f` goes full window, `n` writes a note, `1`–`9` pick.
1362
+ By default the page only shows: no progress, notes or Copy feedback, no key hints, no statuses (a section's or an update's) and no counts, and a step's `options` sit side by side under their labels. `review: true` brings Kit's review tools back: the progress in the rail and its state dots, statuses, Copy feedback, picking between options (`1`–`9`) and the note bar. With review on, notes come from a launcher in the column's corner (`noteBar: "collapsed"`, the default) that opens into the bar on a click, `n`, a recording or ink, and folds back when it's empty; `"docked"` keeps the bar there, `"hidden"` drops it (`w.send()` still works). Typed, spoken or inked, each note carries what was on show (a video's time and chapter, a compare's loop and zoom, a diff's file) and a short log of what led up to it; `onFeedback`, `w.feedback` and `w.markdown()` hand it back. The page keeps the bar's height clear at its end and as its scroller's `scroll-padding-bottom`, so `j` / `k`, `go()` and the last step never land under it, and full window stops above it. `j` / `k` move between steps (`Home` / `End` to the first and last), `←` / `→` seek, `f` goes full window, `n` writes a note, `1`–`9` pick.
1363
1363
 
1364
- Media is a bare URL (a video or picture), or `compare`, `video`, `image`, `diff`, `code`, `terminal`, `metric`, `callout`, `text`, `history`, `stack`, `table`, or `mount` (your own, `(el) => cleanup`; the one that isn't JSON). A step's media sit one under another (a group's room apart); `columns: 2` sets them side by side where there's room. Every slot with a frame (a picture, a video, a code block, a compare's stage, one still loading) takes the same one: the panel radius and a quiet fill, no ring. A metric, a callout, a table, a stack and a history sit on the page. A slot's `label` reads as written (sentence case), small and faint above it.
1364
+ Media is a bare URL (a video or picture), or `compare`, `video`, `image`, `diff`, `code`, `terminal`, `metric`, `callout`, `text`, `history`, `stack`, `table`, or `mount` (your own, `(el) => cleanup`; the one that isn't JSON). A step's media sit one under another (a group's room apart); `columns: 2` sets them side by side where there's room. Every picture, video and compare stage (and a slot still loading) takes the same frame: the panel radius and a quiet fill, no ring. A diff, code and a terminal sit in one plain container: a flat inset fill, a hairline ring, the control radius and room inside. Nothing in a walkthrough is glass. A metric, a callout, a table, a stack and a history sit on the page. A slot's `label` reads as written (sentence case), small and faint above it.
1365
1365
 
1366
- **Keep it scannable.** A step's title is its claim, in about ten words; its caption is one sentence; everything else goes in `more` (Markdown, blank lines between paragraphs), folded under the caption behind a small **More** toggle whose height springs open on the state spring while the words fade in place. Sections take `more` under their summary, and the page under its own.
1366
+ **Keep it scannable.** A step's title is its claim, in about ten words; its caption is one sentence; everything else goes in `more` (Markdown, blank lines between paragraphs), folded under the caption behind a small **More** toggle whose height springs open on the state spring while the words come into focus (a fade with a small blur, from the top). Sections take `more` under their summary, and the page under its own. Media that mounts after the first paint (near the view, or a picture still loading) comes in the same way; nothing moves on load.
1367
1367
 
1368
1368
  ```json
1369
1369
  { "title": "Retired models get a way out", "caption": "After: **Pick another model**, and the prompt back.",
@@ -1372,7 +1372,7 @@ Media is a bare URL (a video or picture), or `compare`, `video`, `image`, `diff`
1372
1372
 
1373
1373
  **Pull requests** read as small chips: an icon for their state, the number, the title and state on hover, in a quiet tint (open green, merged violet, draft and closed neutral). A section's PR `link` shows as its chip on the meta line ("PR 3 of 9 · ⟨#57158⟩"), and in titles, captions, summaries and `more`, a link to a pull request whose words are its number, and a bare `#57148` the page can resolve, become chips too. The doc's `prs` gives their state and title by number (`{ "57148": { state: "merged", title: "…" } }`), and `repo: "owner/name"` links bare numbers it doesn't list. So a caption needn't spell out "#57158 (Draft · Green)".
1374
1374
 
1375
- **The feed** (`updates` and `qa`, newest first) is a calm list a group's room apart, a hairline between items and no boxes: a meta line (when · the step's PR · `status`), the question in text colour (or the update), its answer in dim prose, and its `trail` (what Kit did before asking, `"0:04 · Played the compare"`) folded under a small faint **Context** toggle whose height springs open on the state spring (.3 s) while the lines fade in place under a soft bottom edge. `status: "shipped"` reads Shipped, `"changes"` reads Changes queued.
1375
+ **The feed** (`updates` and `qa`, newest first) is a calm list a group's room apart, a hairline between items and no boxes: a meta line (when · the step's PR, and `status` with review on), the question in text colour (or the update), its answer in dim prose, and its `trail` (what Kit did before asking, `"0:04 · Played the compare"`) folded under a small faint **Context** toggle whose height springs open on the state spring (.3 s) while the lines fade in place under a soft bottom edge. `status: "shipped"` reads Shipped, `"changes"` reads Changes queued.
1376
1376
 
1377
1377
  ```json
1378
1378
  { "qa": [{ "at": "2026-10-19T12:10:00-07:00", "q": "Why is there no video for #57158?",
@@ -1382,7 +1382,7 @@ Media is a bare URL (a video or picture), or `compare`, `video`, `image`, `diff`
1382
1382
 
1383
1383
  The demo is `examples/walkthrough.html`.
1384
1384
 
1385
- **`history`**: dated events down a spine, a tone dot each (`good`, `warn`, `bad`, `info`, `dim`), under quiet group headings. `provenance` says whether an event was seen in the record (`"verified"`) or worked out from behaviour (`"inferred"`, a ring for a dot and a dashed tag), and `source` where (`"verified · git"`). `evidence` is small tags, linked or not; `type: "release"` draws a diamond, `"audit"` a square. Groups take `events` or `rows`; a bare `events` list is one group. In a narrow card the date goes over the event.
1385
+ **`history`**: drawn by **`history(container, { data })`** (also exported on its own): a shared date axis (month ticks, today's line) over lanes, one per channel or track, each carrying spans whose tone says their state, marks for releases and deletions, faint bands for phases; hovering shows a date line and a tooltip, and a click or `←` / `→` opens one detail area under the chart. Write the lanes shape (`lanes: [{ id, label, spans: [{ from, to?, state, label?, detail?, provenance?, evidence?, step? }] }]`, `events: [{ date, lane?, title, type?, tone?, provenance?, step? }]`, `bands`, `states`, `axis`), or the older groups of dated events below, which it lays on one code lane under a band per heading. `provenance: "inferred"` hatches a span and hollows a mark; a `step` gets "Go to step" in the detail; `label` names it, and `marks` picks which event types are marked from the start (releases and deletions, or `"all"`).
1386
1386
 
1387
1387
  ```json
1388
1388
  { "kind": "history", "groups": [{ "heading": "v2 dropped it", "summary": "Jun 26 – Jul 30", "events": [
@@ -1403,7 +1403,7 @@ The demo is `examples/walkthrough.html`.
1403
1403
  "delta": { "add": 446, "del": 47 }, "status": "Stacked on #57146", "tone": "good", "summary": "Modal, notice and prompt recall." } ] }
1404
1404
  ```
1405
1405
 
1406
- **`table`**: a real `<table>` for audits and matrices. Rows are cells by column key, bare or under `cells`; a cell is text, a number, or `{ text, href?, code?, badge?, tone?, step? }` (a `badge` is a tone chip). Columns take `mono` and `align` (number columns go right by themselves). Figures are tabular; a tall table keeps its head stuck, and its bars show thin on hover. `caption` goes under it. Where the step is narrower than the columns' natural width (long text keeping a measure, code on one line), each row becomes a block: its `primary` cell (the first column by default) as its title with its badges at the end of the line, the rest as term and value under it, the blocks parted by hairlines. `stack: "always"` always stacks; `stack: "never"` scrolls across instead, edges fading where there's more.
1406
+ **`table`**: a real `<table>` for audits and matrices. Rows are cells by column key, bare or under `cells`; a cell is text, a number, or `{ text, href?, code?, badge?, tone?, step? }` (a `badge` is a tone chip). A row with a `step` on any cell goes there: the whole row, a wash under the pointer, Enter when it has focus, and nothing trailing it, so every row lines up. Columns take `mono` and `align` (number columns go right by themselves). Figures are tabular; a tall table keeps its head stuck, and its bars show thin on hover. `caption` goes under it. Where the step is narrower than the columns' natural width (long text keeping a measure, code on one line), each row becomes a block: its `primary` cell (the first column by default) as its title with its badges at the end of the line, the rest as term and value under it, the blocks parted by hairlines. `stack: "always"` always stacks; `stack: "never"` scrolls across instead, edges fading where there's more.
1407
1407
 
1408
1408
  ```json
1409
1409
  { "kind": "table", "caption": "From a clean HOME, Oct 20", "columns": [
@@ -1414,6 +1414,8 @@ The demo is `examples/walkthrough.html`.
1414
1414
  { "channel": "brew install …/opencode-v2", "days": 31, "state": { "text": "Pending", "badge": "info", "step": "4.6" } } ] }
1415
1415
  ```
1416
1416
 
1417
+ **`annotations`** on an `image`, a `video` or a `compare` draw the eye to what matters: `[{ x, y, w?, h?, label?, at?: [start, end], shape?: "box" | "dot" | "arrow" }]`, in 0–1 of the picture (a box's top-left, or the point). A box takes a calm ring, a point a dot (or an arrow pointing at it), each with a small label; on a video or compare one shows only between its `at` seconds, fading in and out with a small blur. On a compare they're drawn on both sides and stay on their spot through a zoom. They're on by default; the eye button on the picture, or `a`, hides or shows every one on the page.
1418
+
1417
1419
  **`chapters`** on a `video` (or a `compare`): a readout list under it, a row each (number · label · start). A click selects the chapter at once while the media seeks there and plays, and goes in the note's log; the selection pill slides to the chapter playing (the last that started at or before the playhead) and fades out in place outside them, and a note's `media` names it (`chapter`). A chapter ends at `end`, else `start + duration`, else the next one's start. On a compare they're also its timeline's markers. A video's `captions` (or `captionsSrc`) is a WebVTT URL, shown by default.
1418
1420
 
1419
1421
  ```json
@@ -85,7 +85,7 @@ function attach(controls, element, options = {}) {
85
85
  return;
86
86
  loading = !0;
87
87
  try {
88
- let { createPanel } = await (__preload("qc6szek7"), import("./chunk-qc6szek7.js"));
88
+ let { createPanel } = await (__preload("ya44t7z5"), import("./chunk-ya44t7z5.js"));
89
89
  if (disposed || !controls.enabled || panel)
90
90
  return;
91
91
  if (panel = createPanel(controls, element, close, { restored, chip }), remember)
@@ -11455,11 +11455,11 @@ var compareCss = `
11455
11455
  .knobs-cmp-modes[hidden]{display:none}
11456
11456
  .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;
11457
11457
  transition:color calc(var(--_k-hover-out) * 1ms)}
11458
- .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
11458
+ .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
11459
11459
  /* Toggles: icon buttons, the accent's wash when on. */
11460
11460
  .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;
11461
11461
  transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
11462
- .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
11462
+ .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)}
11463
11463
  .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
11464
11464
  .knobs-cmp-toggle svg{display:block}
11465
11465
  .knobs-cmp-fill{flex:1}
@@ -11525,7 +11525,7 @@ var compareCss = `
11525
11525
  .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)}
11526
11526
  :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;
11527
11527
  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}
11528
- :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)}
11528
+ :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)}
11529
11529
  .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}
11530
11530
  .knobs-cmp-note:empty{display:none}
11531
11531
  .knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
@@ -11556,6 +11556,10 @@ var playerCss = `
11556
11556
  .knobs-player-glyph svg{width:16px;height:16px}
11557
11557
  .knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
11558
11558
  .knobs-player-line{flex:none;min-height:0}
11559
+ /* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover
11560
+ states are set here, after it, so the player's line and every timeline share them. */
11561
+ .knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
11562
+ .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
11559
11563
  `;
11560
11564
 
11561
11565
  // src/ui/overlay-css.ts
package/dist/dev/index.js CHANGED
@@ -76,7 +76,7 @@ import {
76
76
  urlFlag2,
77
77
  storageKey
78
78
  } from "./chunk-3ygqgy1x.js";
79
- __chunks(import.meta.url,["z8z1xkt0","qc6szek7","y2axkmz3","3ygqgy1x","dyq4m24m"],[["./index.js",3],["./chunk-qc6szek7.js",3,4],["./chunk-y2axkmz3.js",3,4],["./chunk-3ygqgy1x.js"],["./chunk-dyq4m24m.js",3]],0);
79
+ __chunks(import.meta.url,["535tjwkq","ya44t7z5","nhz9en4k","3ygqgy1x","dyq4m24m"],[["./index.js",3],["./chunk-ya44t7z5.js",3,4],["./chunk-nhz9en4k.js",3,4],["./chunk-3ygqgy1x.js"],["./chunk-dyq4m24m.js",3]],0);
80
80
  // src/core/random.ts
81
81
  var lattice = (min, max, step, r) => min + Math.floor(r * (Math.floor((max - min) / step + 0.000000001) + 1)) * step, between = (min, max, r) => min + (max - min) * r;
82
82
  function pick(field, random) {
@@ -1378,11 +1378,11 @@ function defineControls(id, input, options = {}) {
1378
1378
  code: (format, values) => formatCode(values ?? get(), format),
1379
1379
  variants,
1380
1380
  attach(element, chip) {
1381
- let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("y2axkmz3"), import("./chunk-y2axkmz3.js")).then((m) => m.attach);
1381
+ let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("nhz9en4k"), import("./chunk-nhz9en4k.js")).then((m) => m.attach);
1382
1382
  return live(load, (attach) => attach(controls, element, chip), (detach) => detach());
1383
1383
  },
1384
1384
  mount(container, mountOptions) {
1385
- let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("qc6szek7"), import("./chunk-qc6szek7.js")).then((m) => m.mountPanel);
1385
+ let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("ya44t7z5"), import("./chunk-ya44t7z5.js")).then((m) => m.mountPanel);
1386
1386
  return live(load, (mountPanel) => {
1387
1387
  let panel = mountPanel(controls, container, mountOptions);
1388
1388
  return mountOptions?.onReady?.(panel), panel;
package/dist/panel.js CHANGED
@@ -21078,11 +21078,11 @@ var compareCss = `
21078
21078
  .knobs-cmp-modes[hidden]{display:none}
21079
21079
  .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;
21080
21080
  transition:color calc(var(--_k-hover-out) * 1ms)}
21081
- .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}
21081
+ .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}
21082
21082
  /* Toggles: icon buttons, the accent's wash when on. */
21083
21083
  .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;
21084
21084
  transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}
21085
- .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
21085
+ .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)}
21086
21086
  .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
21087
21087
  .knobs-cmp-toggle svg{display:block}
21088
21088
  .knobs-cmp-fill{flex:1}
@@ -21148,7 +21148,7 @@ var compareCss = `
21148
21148
  .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)}
21149
21149
  :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;
21150
21150
  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}
21151
- :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)}
21151
+ :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)}
21152
21152
  .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}
21153
21153
  .knobs-cmp-note:empty{display:none}
21154
21154
  .knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
@@ -21179,6 +21179,10 @@ var playerCss = `
21179
21179
  .knobs-player-glyph svg{width:16px;height:16px}
21180
21180
  .knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
21181
21181
  .knobs-player-line{flex:none;min-height:0}
21182
+ /* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover
21183
+ states are set here, after it, so the player's line and every timeline share them. */
21184
+ .knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
21185
+ .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
21182
21186
  `;
21183
21187
 
21184
21188
  // src/ui/overlay-css.ts
@@ -1 +1 @@
1
- export declare const compareCss = "\n:where(.knobs-compare,.knobs-compare *,.knobs-cmp-strip,.knobs-cmp-strip *){box-sizing:border-box}\n.knobs-ui.knobs-compare{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));--_ku-cmp-line:var(--_k-compare-divider);\n --_ku-cmp-stage:var(--_k-compare-stage);\n display:flex;flex-direction:column;gap:6px;width:100%;height:100%;min-height:160px;outline:none;user-select:none;-webkit-user-select:none;\n font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}\n:where(.knobs-ui) :is(.knobs-cmp-mode,.knobs-cmp-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}\n.knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:var(--_k-gap);height:var(--_k-control-h)}\n/* The modes: one fill, the selection pill on the one picked; a mode isn't brighter for being\n picked: the pill's bright copy is. */\n.knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}\n.knobs-cmp-modes[hidden]{display:none}\n.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;\n transition:color calc(var(--_k-hover-out) * 1ms)}\n.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg)}\n/* Toggles: icon buttons, the accent's wash when on. */\n.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;\n transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}\n.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}\n.knobs-ui .knobs-cmp-toggle[aria-pressed=\"true\"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}\n.knobs-cmp-toggle svg{display:block}\n.knobs-cmp-fill{flex:1}\n.knobs-cmp-toggle[hidden]{display:none}\n/* One surface: on a card (a walkthrough's step, a split's pane, anything .knobs-surface) or marked\n data-nested, the stage drops its fill and the sides sit on that card. */\n:where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-compare,.knobs-ui.knobs-compare[data-nested]{--_ku-cmp-stage:var(--k-compare-stage,transparent)}\n/* In a page's flow (a container with no height), it takes the height its sides need. */\n.knobs-ui.knobs-compare[data-auto]{height:auto}\n.knobs-compare[data-auto] .knobs-cmp-stage{flex:none;min-height:0}\n.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}\n.knobs-compare[data-frame=\"flat\"] .knobs-cmp-stage{border-radius:0}\n.knobs-compare[data-mode=\"wipe\"] .knobs-cmp-stage{cursor:ew-resize}\n/* Videos: a click on the stage plays and pauses. */\n.knobs-compare[data-video]:not([data-mode=\"wipe\"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}\n/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */\n.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}\n.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}\n/* Wiping, the grip stands in for the cursor. */\n.knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}\n.knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}\n.knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}\n.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}\n.knobs-compare[data-renderer=\"gpu\"] .knobs-cmp-media{opacity:0}\n/* The media comes in with its first frames: a fade and a 4px blur on the default spring. */\n.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)}\n.knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}\n/* Glass: what's behind shows through, blurred, on the knobs tokens. */\n.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);\n 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)))}\n/* \u2717 Before \u00B7 \u2713 After: Before quieter (fg-3), After in full text. Neutral: no colour of its own. */\n.knobs-cmp-label{position:absolute;left:0;top:0;display:flex;align-items:center;gap:4px;padding:1px 7px 1px 5px;border-radius:999px;font-size:var(--_k-text-meta);line-height:16px;font-weight:var(--_k-weight-label);\n white-space:nowrap;pointer-events:none;will-change:transform}\n.knobs-cmp-label[data-side=\"before\"]{color:var(--_k-fg-3)}\n.knobs-cmp-label[data-side=\"after\"]{color:var(--_k-fg)}\n/* Fusing, the mark folds away and the label is its name. */\n.knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}\n.knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}\n/* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */\n.knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;\n -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)}\n/* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height\n while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the\n fuse; wiping, line and grip brighten. */\n.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}\n.knobs-cmp-handle[data-on]{pointer-events:auto}\n.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)}\n.knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}\n.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);\n box-shadow:var(--_k-shadow-overlay),inset 0 0 0 1px var(--_k-border),inset 0 1px 0 var(--_k-rim);will-change:transform;transition:box-shadow calc(var(--_k-hover-out) * 1ms)}\n.knobs-cmp-grip::before,.knobs-cmp-grip::after{content:\"\";position:absolute;top:10px;width:1px;height:8px;background:var(--_k-fg-3);transition:background calc(var(--_k-hover-out) * 1ms)}\n.knobs-cmp-grip::before{left:6px}\n.knobs-cmp-grip::after{left:9px}\n.knobs-compare[data-dragging] .knobs-cmp-grip{box-shadow:var(--_k-shadow-overlay),inset 0 0 0 1px var(--_k-border-strong),inset 0 1px 0 var(--_k-rim)}\n.knobs-compare[data-dragging] .knobs-cmp-grip::before,.knobs-compare[data-dragging] .knobs-cmp-grip::after{background:var(--_k-fg)}\n.knobs-cmp-handle:focus-visible .knobs-cmp-grip,.knobs-compare[data-keyboard] .knobs-cmp-handle:focus .knobs-cmp-grip{box-shadow:var(--_k-shadow-overlay),0 0 0 2px var(--_k-accent)}\n/* The split's hairline, down the middle side by side. */\n.knobs-cmp-split{position:absolute;left:0;top:0;width:1px;height:0;margin-left:-.5px;background:color-mix(in srgb,var(--_k-bg) 72%,transparent);pointer-events:none;will-change:transform,opacity}\n/* Zoomed: how far, and a way back, in the stage's corner; it fades in and out, nothing shifts. */\n.knobs-cmp-zoom{position:absolute;right:8px;bottom:8px;display:flex;align-items:center;gap:4px;height:26px;padding:0 3px 0 10px;border-radius:999px;\n color:var(--_k-fg);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums;font-feature-settings:\"tnum\";white-space:nowrap;\n opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}\n.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)}\n: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;\n 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}\n: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)}\n.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}\n.knobs-cmp-note:empty{display:none}\n.knobs-cmp-strip-host{flex:none;height:60px;min-height:0}\n.knobs-cmp-timeline-host{flex:none;min-height:0}\n/* The frame strip: Before over After per column, a difference bar under them, a dot where it peaks. */\n.knobs-ui.knobs-cmp-strip{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));position:relative;width:100%;height:100%;min-height:40px;overflow:hidden;cursor:col-resize;touch-action:none;\n font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg)}\n.knobs-cmp-strip>canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}\n@media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}\n";
1
+ export declare const compareCss = "\n:where(.knobs-compare,.knobs-compare *,.knobs-cmp-strip,.knobs-cmp-strip *){box-sizing:border-box}\n.knobs-ui.knobs-compare{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));--_ku-cmp-line:var(--_k-compare-divider);\n --_ku-cmp-stage:var(--_k-compare-stage);\n display:flex;flex-direction:column;gap:6px;width:100%;height:100%;min-height:160px;outline:none;user-select:none;-webkit-user-select:none;\n font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}\n:where(.knobs-ui) :is(.knobs-cmp-mode,.knobs-cmp-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}\n.knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:var(--_k-gap);height:var(--_k-control-h)}\n/* The modes: one fill, the selection pill on the one picked; a mode isn't brighter for being\n picked: the pill's bright copy is. */\n.knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}\n.knobs-cmp-modes[hidden]{display:none}\n.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;\n transition:color calc(var(--_k-hover-out) * 1ms)}\n.knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible){color:var(--_k-fg);transition-duration:calc(var(--_k-hover-in) * 1ms)}\n/* Toggles: icon buttons, the accent's wash when on. */\n.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;\n transition:color calc(var(--_k-hover-out) * 1ms),background calc(var(--_k-hover-out) * 1ms)}\n.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)}\n.knobs-ui .knobs-cmp-toggle[aria-pressed=\"true\"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}\n.knobs-cmp-toggle svg{display:block}\n.knobs-cmp-fill{flex:1}\n.knobs-cmp-toggle[hidden]{display:none}\n/* One surface: on a card (a walkthrough's step, a split's pane, anything .knobs-surface) or marked\n data-nested, the stage drops its fill and the sides sit on that card. */\n:where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui.knobs-compare,.knobs-ui.knobs-compare[data-nested]{--_ku-cmp-stage:var(--k-compare-stage,transparent)}\n/* In a page's flow (a container with no height), it takes the height its sides need. */\n.knobs-ui.knobs-compare[data-auto]{height:auto}\n.knobs-compare[data-auto] .knobs-cmp-stage{flex:none;min-height:0}\n.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}\n.knobs-compare[data-frame=\"flat\"] .knobs-cmp-stage{border-radius:0}\n.knobs-compare[data-mode=\"wipe\"] .knobs-cmp-stage{cursor:ew-resize}\n/* Videos: a click on the stage plays and pauses. */\n.knobs-compare[data-video]:not([data-mode=\"wipe\"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}\n/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */\n.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}\n.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}\n/* Wiping, the grip stands in for the cursor. */\n.knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}\n.knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}\n.knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}\n.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}\n.knobs-compare[data-renderer=\"gpu\"] .knobs-cmp-media{opacity:0}\n/* The media comes in with its first frames: a fade and a 4px blur on the default spring. */\n.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)}\n.knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}\n/* Glass: what's behind shows through, blurred, on the knobs tokens. */\n.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);\n 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)))}\n/* \u2717 Before \u00B7 \u2713 After: Before quieter (fg-3), After in full text. Neutral: no colour of its own. */\n.knobs-cmp-label{position:absolute;left:0;top:0;display:flex;align-items:center;gap:4px;padding:1px 7px 1px 5px;border-radius:999px;font-size:var(--_k-text-meta);line-height:16px;font-weight:var(--_k-weight-label);\n white-space:nowrap;pointer-events:none;will-change:transform}\n.knobs-cmp-label[data-side=\"before\"]{color:var(--_k-fg-3)}\n.knobs-cmp-label[data-side=\"after\"]{color:var(--_k-fg)}\n/* Fusing, the mark folds away and the label is its name. */\n.knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}\n.knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}\n/* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */\n.knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;\n -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)}\n/* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height\n while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the\n fuse; wiping, line and grip brighten. */\n.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}\n.knobs-cmp-handle[data-on]{pointer-events:auto}\n.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)}\n.knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}\n.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);\n box-shadow:var(--_k-shadow-overlay),inset 0 0 0 1px var(--_k-border),inset 0 1px 0 var(--_k-rim);will-change:transform;transition:box-shadow calc(var(--_k-hover-out) * 1ms)}\n.knobs-cmp-grip::before,.knobs-cmp-grip::after{content:\"\";position:absolute;top:10px;width:1px;height:8px;background:var(--_k-fg-3);transition:background calc(var(--_k-hover-out) * 1ms)}\n.knobs-cmp-grip::before{left:6px}\n.knobs-cmp-grip::after{left:9px}\n.knobs-compare[data-dragging] .knobs-cmp-grip{box-shadow:var(--_k-shadow-overlay),inset 0 0 0 1px var(--_k-border-strong),inset 0 1px 0 var(--_k-rim)}\n.knobs-compare[data-dragging] .knobs-cmp-grip::before,.knobs-compare[data-dragging] .knobs-cmp-grip::after{background:var(--_k-fg)}\n.knobs-cmp-handle:focus-visible .knobs-cmp-grip,.knobs-compare[data-keyboard] .knobs-cmp-handle:focus .knobs-cmp-grip{box-shadow:var(--_k-shadow-overlay),0 0 0 2px var(--_k-accent)}\n/* The split's hairline, down the middle side by side. */\n.knobs-cmp-split{position:absolute;left:0;top:0;width:1px;height:0;margin-left:-.5px;background:color-mix(in srgb,var(--_k-bg) 72%,transparent);pointer-events:none;will-change:transform,opacity}\n/* Zoomed: how far, and a way back, in the stage's corner; it fades in and out, nothing shifts. */\n.knobs-cmp-zoom{position:absolute;right:8px;bottom:8px;display:flex;align-items:center;gap:4px;height:26px;padding:0 3px 0 10px;border-radius:999px;\n color:var(--_k-fg);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums;font-feature-settings:\"tnum\";white-space:nowrap;\n opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}\n.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)}\n: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;\n 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}\n: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)}\n.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}\n.knobs-cmp-note:empty{display:none}\n.knobs-cmp-strip-host{flex:none;height:60px;min-height:0}\n.knobs-cmp-timeline-host{flex:none;min-height:0}\n/* The frame strip: Before over After per column, a difference bar under them, a dot where it peaks. */\n.knobs-ui.knobs-cmp-strip{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));position:relative;width:100%;height:100%;min-height:40px;overflow:hidden;cursor:col-resize;touch-action:none;\n font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg)}\n.knobs-cmp-strip>canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}\n@media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}\n";
@@ -11,6 +11,8 @@ export interface CompareDraw {
11
11
  /** Device px: the part of the stage each side may draw in. */
12
12
  readonly beforeClip: Rect;
13
13
  readonly afterClip: Rect;
14
+ /** Device px: the clips' corner radii, Before's left and right, then After's. */
15
+ readonly radii: readonly [number, number, number, number];
14
16
  readonly width: number;
15
17
  readonly height: number;
16
18
  readonly dpr: number;
@@ -100,10 +100,13 @@ export interface CompareLayout {
100
100
  }
101
101
  /** Where the two sides sit inside `box`, side by side and overlaid. */
102
102
  export declare function compareLayout(box: Rect, before: Size, after: Size): CompareLayout;
103
- /** The fuse's feather at its middle, the seam's blur as it starts and the overlap it fades in over (px). */
103
+ /**
104
+ * Kit Motion's blend blur, in px: the feather Before's cut swells to mid-fuse, the blur of the
105
+ * strip over the seam at its peak (mid-fuse too), and the overlap the seam fades in over.
106
+ */
104
107
  export declare const FUSE: {
105
- readonly feather: 24;
106
- readonly blur: 4;
108
+ readonly feather: 32;
109
+ readonly blur: 8;
107
110
  readonly seamFade: 48;
108
111
  };
109
112
  export interface FuseFrame {
@@ -113,20 +116,36 @@ export interface FuseFrame {
113
116
  readonly feather: number;
114
117
  /** The seam's (and the wipe handle's) opacity, 0–1. */
115
118
  readonly seam: number;
116
- /** The seam's blur (px), gone once fused. */
119
+ /** The seam's blur (px): none apart or fused, `FUSE.blur` mid-fuse. */
117
120
  readonly blur: number;
118
121
  }
119
122
  /**
120
123
  * Kit Motion's Comparison fuse, one frame of it: side by side (`k` 0) the panes glide together
121
124
  * into one (`k` 1). Before is cut only where the inner edges overlap, with a feather that swells
122
- * mid-transition; the seam fades in as they overlap, blurred until they've fused. `before` and
123
- * `after` are where the sides are drawn this frame.
125
+ * mid-transition, and a blurred strip over the seam blends the two, strongest mid-transition
126
+ * (the blend blur); it fades in as they overlap. `before` and `after` are where the sides are
127
+ * drawn this frame.
124
128
  */
125
129
  export declare function fuseFrame(L: CompareLayout, before: Rect, after: Rect, k: number, wipe: number): FuseFrame;
126
130
  /**
127
131
  * A label folded `fold` (0 open, 1 folded to its name): its mark's width, opacity and blur (px,
128
132
  * up to `blur` as it goes, kit-motion's in-place exit).
129
133
  */
134
+ /**
135
+ * Each side's corner radii, `left` and `right` (px), so the frame is rounded at every frame of the
136
+ * fuse: a side's outer corners always take the frame's radius `r`; its inner ones (at the split)
137
+ * are square apart and grow to `r` as the sides fuse into one picture.
138
+ */
139
+ export declare const fuseCorners: (r: number, k: number) => {
140
+ before: {
141
+ left: number;
142
+ right: number;
143
+ };
144
+ after: {
145
+ left: number;
146
+ right: number;
147
+ };
148
+ };
130
149
  export declare const labelFold: (fold: number, mark: number, blur?: number) => {
131
150
  width: number;
132
151
  opacity: number;
@@ -19,6 +19,7 @@ export { adjacentMarker, advance, CATCH_UP, clampView, currentMarker, followView
19
19
  export { compare, COMPARE_FFMPEG, type Compare, type CompareLoop, type CompareMode, type CompareOptions, type CompareSource, type CompareView, type MediaKind, type ZoomRegion, } from "./compare.js";
20
20
  export { compareStrip, type CompareStrip, type CompareStripOptions } from "./compare-strip.js";
21
21
  export { player, type Player, type PlayerChapter, type PlayerOptions } from "./player.js";
22
+ export { history, type History, type HistoryBand, type HistoryData, type HistoryEvent, type HistoryEvidence, type HistoryGroup, type HistoryLane, type HistoryOptions, type HistoryProvenance, type HistorySpan, type HistoryState, type HistoryTone, } from "./history.js";
22
23
  export { compareLayout, compareModes, frameMidpoint, frameOf, gripTop, letterbox, mediaKind, meanAbsDiff, modeForKey, requestSeek, seamLabels, seeked, seekTime, stripColumns, stripPeaks, syncAction, wipeAt, wipeGrab, wipeKey, wipeX, zoomAbout, type CompareLayout, type SeekState, type StripColumn, type SyncAction, } from "./compare-model.js";
23
24
  export { tree, type Tree, type TreeOptions, type TreeRowContext, type TreeToggle } from "./tree.js";
24
25
  export { applyMove, plainAccess, dropTarget, flatten, indexTree, matchLabel, navigate, select, stepMove, topmost, typeahead, validMove, virtualWindow, type DropTarget, type FlatRow, type Selection, type TreeAccess, type TreeFilter, type TreeMove, } from "./tree-model.js";
@@ -32,4 +33,4 @@ export { optionPicker, type OptionPicker, type OptionPickerOptions, type PickerO
32
33
  export { claimsDigit, digitPick, keepPick, stepPick } from "./picker-model.js";
33
34
  export { tasteTest, walkthrough, type TasteTest, type TasteTestOptions, type Walkthrough, type WalkthroughOptions, } from "./walkthrough.js";
34
35
  export { mediaSlot, type MediaSlot, type MediaSlotOptions, type StepLinks, } from "./walkthrough-media.js";
35
- export { collectFeedback, feedbackMarkdown, inlineMarkdown, normalizeWalkthrough, readState as readWalkthroughState, stackLayout, walkthroughProgress, type ContextEvent, type FeedbackItem, type FeedbackNote, type FeedItem, type MediaContext, type NoteContext, type StackLayout, type StackRow, type WalkInk, type WalkNote, type WalkVoice, type WalkthroughQA, type WalkthroughUpdate, type WalkPage, type WalkStep, type WalkthroughChapter, type WalkthroughDoc, type WalkthroughEvidence, type WalkthroughFeedback, type WalkthroughHistoryEvent, type WalkthroughHistoryGroup, type WalkthroughMedia, type WalkthroughMeta, type WalkthroughOption, type WalkthroughSection, type WalkthroughSource, type WalkthroughStackItem, type WalkthroughState, type WalkthroughStatus, type WalkthroughStep, type WalkthroughTableCell, type WalkthroughTableColumn, type WalkthroughTableRow, type WalkthroughTone, } from "./walkthrough-model.js";
36
+ export { collectFeedback, feedbackMarkdown, inlineMarkdown, plainMarkdown, normalizeWalkthrough, readState as readWalkthroughState, stackLayout, walkthroughProgress, type ContextEvent, type FeedbackItem, type FeedbackNote, type FeedItem, type MediaContext, type NoteContext, type StackLayout, type StackRow, type WalkInk, type WalkNote, type WalkVoice, type WalkthroughQA, type WalkthroughUpdate, type WalkPage, type WalkStep, type WalkthroughChapter, type WalkthroughDoc, type WalkthroughEvidence, type WalkthroughFeedback, type WalkthroughHistoryEvent, type WalkthroughHistoryGroup, type WalkthroughMedia, type WalkthroughMeta, type WalkthroughOption, type WalkthroughSection, type WalkthroughSource, type WalkthroughStackItem, type WalkthroughState, type WalkthroughStatus, type WalkthroughStep, type WalkthroughTableCell, type WalkthroughTableColumn, type WalkthroughTableRow, type WalkthroughTone, type WalkthroughPR, type WalkthroughAnnotation, } from "./walkthrough-model.js";
@@ -1 +1 @@
1
- export declare const playerCss = "\n:where(.knobs-player,.knobs-player *){box-sizing:border-box}\n.knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;\n font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}\n: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)}\n.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}\n.knobs-player[data-frame=\"flat\"] .knobs-player-frame{border-radius:0}\n.knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;\n 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)}\n.knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}\n.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;\n opacity:0;filter:blur(4px);scale:.96;\n 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);\n box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);\n 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)}\n.knobs-player-glyph svg{width:16px;height:16px}\n.knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}\n.knobs-player-line{flex:none;min-height:0}\n";
1
+ export declare const playerCss = "\n:where(.knobs-player,.knobs-player *){box-sizing:border-box}\n.knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;\n font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}\n: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)}\n.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}\n.knobs-player[data-frame=\"flat\"] .knobs-player-frame{border-radius:0}\n.knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;\n 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)}\n.knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}\n.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;\n opacity:0;filter:blur(4px);scale:.96;\n 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);\n box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);\n 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)}\n.knobs-player-glyph svg{width:16px;height:16px}\n.knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}\n.knobs-player-line{flex:none;min-height:0}\n/* Hover in is immediate (0 ms), out eases. The timeline's own stylesheet is look.ts's; its hover\n states are set here, after it, so the player's line and every timeline share them. */\n.knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,\n.knobs-timeline[data-variant=\"line\"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}\n";
@@ -0,0 +1,42 @@
1
+ import type { WalkthroughAnnotation } from "./walkthrough-model.js";
2
+ export interface Rect {
3
+ readonly x: number;
4
+ readonly y: number;
5
+ readonly w: number;
6
+ readonly h: number;
7
+ }
8
+ /** The part of the picture in view, 0–1 (`{ u: 0, v: 0, w: 1, h: 1 }` is all of it). */
9
+ export interface View {
10
+ readonly u: number;
11
+ readonly v: number;
12
+ readonly w: number;
13
+ readonly h: number;
14
+ }
15
+ /** Where the picture sits now: its rects in the layer (one, or a compare's two), view and time. */
16
+ export interface Placement {
17
+ readonly rects: readonly Rect[];
18
+ readonly view?: View;
19
+ readonly time?: number;
20
+ }
21
+ export interface AnnotationLayer {
22
+ readonly element: HTMLElement;
23
+ /** Places the marks again: after a resize, a zoom, a seek, a mode change. */
24
+ update(): void;
25
+ dispose(): void;
26
+ }
27
+ /** Whether a mark shows at `time` (always without `at`, or without a time). */
28
+ export declare const showsAt: (a: WalkthroughAnnotation, time: number | undefined) => boolean;
29
+ /**
30
+ * A mark's box in the layer, in px, for a picture at `rect` showing `view` of itself; null when
31
+ * it's out of view.
32
+ */
33
+ export declare function placeMark(a: WalkthroughAnnotation, rect: Rect, view?: View): Rect | null;
34
+ /** Whether a page's annotations show. */
35
+ export declare const annotationsOn: (scope: HTMLElement) => boolean;
36
+ /** Shows or hides every annotation on a page (or a lone slot); its eye buttons follow. */
37
+ export declare function setAnnotations(scope: HTMLElement, on: boolean): void;
38
+ /**
39
+ * Draws `list` over `host` (positioned, the picture's box or a compare's stage), placing it with
40
+ * `place()`; the eye button goes in `corner` (the host by default).
41
+ */
42
+ export declare function annotationLayer(host: HTMLElement, list: readonly WalkthroughAnnotation[], place: () => Placement, corner?: HTMLElement): AnnotationLayer;
@@ -1,7 +1,6 @@
1
1
  import { type ContextEvent, type WalkChapter, type WalkMedia } from "./walkthrough-model.js";
2
2
  /** The glyphs the blocks draw. */
3
3
  export declare const GLYPH: {
4
- go: string;
5
4
  caret: string;
6
5
  };
7
6
  /** How media reach the page's steps: `step` on a history event, a stack item or a table cell. */
@@ -22,8 +21,12 @@ export interface Block {
22
21
  kind: string;
23
22
  };
24
23
  }
25
- /** A small arrow that goes to the step a ref names; null when it names none. */
26
- export declare function stepButton(doc: Document, ref: string | undefined, steps: StepLinks | undefined): HTMLButtonElement | null;
24
+ /**
25
+ * Makes a whole row go to the step a ref names (a click away from its links, Enter or Space when
26
+ * it has focus); nothing trails it, so rows with and without a step line up.
27
+ */
28
+ export declare function rowLink(row: HTMLElement, ref: string | undefined, steps: StepLinks | undefined): void;
29
+ /** A history through `history()`: lanes of spans over a date axis, or the older groups of events. */
27
30
  export declare function mountHistory(host: HTMLElement, m: Extract<WalkMedia, {
28
31
  kind: "history";
29
32
  }>, steps: StepLinks | undefined): Block;
@@ -55,7 +58,6 @@ export interface Disclosure {
55
58
  }
56
59
  export declare function disclosure(host: HTMLElement, options: {
57
60
  label: string;
58
- count?: number;
59
61
  open?: boolean;
60
62
  content: HTMLElement;
61
63
  }): Disclosure;
@@ -1,5 +1,6 @@
1
1
  import type { CompareMode } from "./compare-model.js";
2
2
  import type { DiffSource } from "./diff-model.js";
3
+ import type { HistoryData, HistoryEvent } from "./history-model.js";
3
4
  /** Where a PR or topic stands, as the agent sees it: a chip beside its title. */
4
5
  export type WalkthroughStatus = "ready" | "needs-taste" | "draft" | "blocked" | "merged" | (string & {});
5
6
  /** A picture or video, by URL. */
@@ -89,6 +90,22 @@ export interface WalkthroughTableColumn {
89
90
  export type WalkthroughTableRow = Readonly<Record<string, WalkthroughTableCell>> | {
90
91
  readonly cells: Readonly<Record<string, WalkthroughTableCell>>;
91
92
  };
93
+ /**
94
+ * A mark on a picture drawing the eye to what matters: a ring round a box, a dot or an arrow at a
95
+ * point, with a small label. In 0–1 of the picture (`x`, `y` the box's top-left or the point).
96
+ */
97
+ export interface WalkthroughAnnotation {
98
+ readonly x: number;
99
+ readonly y: number;
100
+ /** A box's size; left out, it's a point. */
101
+ readonly w?: number;
102
+ readonly h?: number;
103
+ readonly label?: string;
104
+ /** Seconds it shows between, on a video or compare; always, left out. */
105
+ readonly at?: readonly [number, number];
106
+ /** `"box"` where it has a size, else `"dot"`. */
107
+ readonly shape?: "box" | "dot" | "arrow";
108
+ }
92
109
  /** A stretch of a video: a pill under it, and a marker on its timeline. */
93
110
  export interface WalkthroughChapter {
94
111
  readonly id?: string;
@@ -115,6 +132,8 @@ export type WalkthroughMedia = string | {
115
132
  readonly strip?: boolean;
116
133
  /** Pills under it that seek, and markers on its timeline. */
117
134
  readonly chapters?: readonly WalkthroughChapter[];
135
+ /** Marks on the pictures, on both sides (and under a zoom). */
136
+ readonly annotations?: readonly WalkthroughAnnotation[];
118
137
  } | {
119
138
  readonly kind: "video";
120
139
  readonly src: string;
@@ -127,12 +146,23 @@ export type WalkthroughMedia = string | {
127
146
  readonly captions?: string;
128
147
  /** Another name for `captions`. */
129
148
  readonly captionsSrc?: string;
149
+ /** Marks on the picture. */
150
+ readonly annotations?: readonly WalkthroughAnnotation[];
130
151
  } | {
131
152
  readonly kind: "history";
132
- /** Dated events under quiet headings… */
153
+ /** Lanes of dated spans over one axis (`history()`'s shape)… */
154
+ readonly lanes?: HistoryData["lanes"];
155
+ readonly bands?: HistoryData["bands"];
156
+ readonly states?: HistoryData["states"];
157
+ readonly axis?: HistoryData["axis"];
158
+ /** …dated events (on the lanes, or one run of them in the older shape)… */
159
+ readonly events?: readonly (WalkthroughHistoryEvent | HistoryEvent)[];
160
+ /** …or the older groups of events under quiet headings. */
133
161
  readonly groups?: readonly WalkthroughHistoryGroup[];
134
- /** …or one run of them. */
135
- readonly events?: readonly WalkthroughHistoryEvent[];
162
+ /** Its accessible name. */
163
+ readonly label?: string;
164
+ /** The event types marked from the start (releases and deletions), or `"all"`. */
165
+ readonly marks?: readonly string[] | "all";
136
166
  } | {
137
167
  readonly kind: "stack";
138
168
  /** The branch the stack lands on ("dev"); else the one most items merge into. */
@@ -157,6 +187,8 @@ export type WalkthroughMedia = string | {
157
187
  readonly src: string;
158
188
  readonly label?: string;
159
189
  readonly alt?: string;
190
+ /** Marks on the picture. */
191
+ readonly annotations?: readonly WalkthroughAnnotation[];
160
192
  } | {
161
193
  readonly kind: "diff";
162
194
  /** `git diff` / `git show` output. */
@@ -245,6 +277,7 @@ export interface WalkthroughSection {
245
277
  * `link` is a pull request, or the doc's `kickers` noun), your own words, or `false` for none.
246
278
  */
247
279
  readonly kicker?: string | false;
280
+ /** A chip on its meta line ("needs-taste" → Needs taste), with `review: true` only. */
248
281
  readonly status?: WalkthroughStatus;
249
282
  readonly summary?: string;
250
283
  /** The rest, folded away under the summary behind "More" (Markdown). */
@@ -336,11 +369,6 @@ export interface WalkChapter {
336
369
  readonly start: number;
337
370
  readonly end?: number;
338
371
  }
339
- export interface WalkHistoryGroup {
340
- readonly heading?: string;
341
- readonly summary?: string;
342
- readonly events: readonly WalkthroughHistoryEvent[];
343
- }
344
372
  /** A stack's item filled in: its PR as `#123`, its branch split, `step` for `stepId`. */
345
373
  export interface WalkStackItem {
346
374
  readonly pr: string;
@@ -387,7 +415,9 @@ export type WalkMedia = Exclude<WalkthroughMedia, string | {
387
415
  readonly chapters?: readonly WalkChapter[];
388
416
  }) | {
389
417
  readonly kind: "history";
390
- readonly groups: readonly WalkHistoryGroup[];
418
+ readonly data: HistoryData;
419
+ readonly label?: string;
420
+ readonly marks?: readonly string[] | "all";
391
421
  } | {
392
422
  readonly kind: "stack";
393
423
  readonly base?: string;
@@ -486,6 +516,13 @@ export declare const mediaOf: (src: string) => WalkMedia;
486
516
  export declare function parseAspect(aspect: number | string | undefined): number | undefined;
487
517
  /** Media filled in: a bare URL as a video or image, sources resolved against `base`. */
488
518
  export declare function fillMedia(m: WalkthroughMedia, base?: string): WalkMedia;
519
+ /**
520
+ * What `history()` draws: the lanes shape as written, or the older groups (`rows` read as
521
+ * `events`, bare `events` as one group first) for it to lay on the code lane.
522
+ */
523
+ export declare function historyData(m: Extract<WalkthroughMedia, {
524
+ kind: "history";
525
+ }>): HistoryData;
489
526
  /** `"head → base"` (or `->`, `=>`, `⇒`) as its two branches; one name is the head alone. */
490
527
  export declare function parseBranch(branch: string | undefined): {
491
528
  head?: string;