@kitlangton/knobs 0.4.0-next.4 → 0.4.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -14
- package/dist/dev/{chunk-0w1xk9jc.js → chunk-dyq4m24m.js} +3 -3
- package/dist/dev/{chunk-76frnh2j.js → chunk-qc6szek7.js} +58 -9
- package/dist/dev/{chunk-rem3qrhz.js → chunk-y2axkmz3.js} +2 -2
- package/dist/dev/index.js +3 -3
- package/dist/panel.js +60 -11
- 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 +4 -1
- package/dist/types/ui/compare-model.d.ts +36 -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 +2 -1
- 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-blocks.d.ts +18 -0
- package/dist/types/ui/walkthrough-model.d.ts +32 -1
- package/dist/types/ui/walkthrough.d.ts +14 -2
- package/dist/ui-react.js +586 -209
- package/dist/ui-solid.js +586 -209
- package/dist/ui.js +756 -209
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1181,7 +1181,7 @@ const clock = timeline(dock, {
|
|
|
1181
1181
|
})
|
|
1182
1182
|
```
|
|
1183
1183
|
|
|
1184
|
-
**`timeline(container, options)`** is a scrubber for time-based work. The ruler counts in seconds or frames (`units`) with ticks that thin out as it zooms (`⌘` wheel, a pinch, `+`/`-`, `0` fits) and pans with the wheel or a trackpad. The playhead follows the hand 1:1, pulls softly toward markers and lands on frames (`snap`). Markers can be added (`M`), dragged, renamed (`Enter`) and removed, and `[`/`]` jump between them; `I` and `O` set a loop range. `Space` plays on one rAF clock that sleeps when nothing moves, and `←`/`→` step a frame (`⇧` ten; `arrows: { step: 0.5, shift: 2 }` makes them seconds, the playhead gliding there) and `,`/`.` always do. A jump the hand didn't make catches up: a marker's click, a loop's wrap, a press on the track away from the playhead, play from elsewhere and `seek(t, { glide: true })` all carry the playhead from where it is on a quick spring (.15 s, no bounce) while the time lands at once; a drag stays 1:1, and moves during a catch-up follow the hand while the rest settles. There's no clock beside play: the time shows under the pointer on the track, in seconds, while hovering or scrubbing. `variant: "lane"` makes it one slim lane (a compare's own): ticks without labels, markers as faint chapter ticks with the current one (the last at or before the playhead) in text colour, nothing to edit,
|
|
1184
|
+
**`timeline(container, options)`** is a scrubber for time-based work. The ruler counts in seconds or frames (`units`) with ticks that thin out as it zooms (`⌘` wheel, a pinch, `+`/`-`, `0` fits) and pans with the wheel or a trackpad. The playhead follows the hand 1:1, pulls softly toward markers and lands on frames (`snap`). Markers can be added (`M`), dragged, renamed (`Enter`) and removed, and `[`/`]` jump between them; `I` and `O` set a loop range. `Space` plays on one rAF clock that sleeps when nothing moves, and `←`/`→` step a frame (`⇧` ten; `arrows: { step: 0.5, shift: 2 }` makes them seconds, the playhead gliding there) and `,`/`.` always do. A jump the hand didn't make catches up: a marker's click, a loop's wrap, a press on the track away from the playhead, play from elsewhere and `seek(t, { glide: true })` all carry the playhead from where it is on a quick spring (.15 s, no bounce) while the time lands at once; a drag stays 1:1, and moves during a catch-up follow the hand while the rest settles. There's no clock beside play: the time shows under the pointer on the track, in seconds, while hovering or scrubbing. `variant: "lane"` makes it one slim lane (a compare's own): ticks without labels, markers as faint chapter ticks with the current one (the last at or before the playhead) in text colour, nothing to edit; a pinch, `⌘`/ctrl-wheel or `=`/`-` zoom it around the pointer and a sideways scroll pans it, both 1:1, a double-click or `0` fits it again, and an up-and-down wheel is left to the page. A right-click on the ruler, a marker or a range opens a context menu. It reports `onSeek(time, source)` (`"scrub" | "key" | "marker" | "play" | "code"`), `onPlay`, `onMarkers`, `onRanges` and `onCross(marker)`. The handle has `time`, `playing`, `duration`, `fps`, `markers`, `ranges`, `view`, `seek`, `play`, `pause`, `toggle`, `update`, `fit`, `zoom`, `focus` and `dispose`. Pieces bound to a timeline hear it with `listen({ onSeek, onPlay, onView, onScrub, onRanges })` (it returns a stop), and `drive(clock)` makes playback follow another clock, such as a video's `currentTime`, so the two never drift. `Timeline` in `knobs/ui/react` and `knobs/ui/solid` follows its props. The Lab's scene clock and Boom retrigger run from one under the viewport.
|
|
1185
1185
|
|
|
1186
1186
|
### Compare
|
|
1187
1187
|
|
|
@@ -1213,7 +1213,9 @@ compare(viewer, { before, after, timeline: clock })
|
|
|
1213
1213
|
compareStrip(strip, { before, after, timeline: clock })
|
|
1214
1214
|
```
|
|
1215
1215
|
|
|
1216
|
-
**`compare(container, options)`** is a before/after viewer for videos and images (by extension, or `kind`). It offers two modes, side by side (the default `mode`: a split screen down the middle, each side filling its half) and a wipe; `modes: ["side", "wipe", "difference"]` adds a difference (changes above `threshold` in one accent, `--k-compare-diff`), and one mode hides the bar. The modes sit in a segmented control above the stage, the selection pill sliding to the one picked. It draws in one WebGPU pass that copies a frame in only when one arrives, and draws only when something changed; where there's no WebGPU, two `<video>`s with a clip-path wipe stand in. Pinch, `⌘`/ctrl-wheel or double-click zooms into either side and both zoom to the same part (the wheel and a drag pan it, both 1:1); a small glass pill in the corner shows how far, with a reset (`0`, a double-click, `Escape`), which glides back. Overlaid, the sides are letterboxed, never cropped. Both videos run from one clock: paused, scrubbed or stepped, each seeks to the frame's midpoint, coalesced while a seek is in flight; playing, After leads, Before's rate leans by up to 25% to stay on it (it seeks past 90 ms), and a bound timeline follows After's time, so only one clock ever moves it (`Space` resumes from the frame showing). Scrubbing answers at once even on raw screen recordings with a keyframe every 8–18 s: frames decoded in the background (WebCodecs where the file is a plain MP4, else a hidden `<video>`) sit in a small cache shared by every viewer and strip of the same source, and the nearest shows while the exact seek lands (mid-drag, far from any cached frame, `fastSeek` answers first; letting go seeks exactly). Encoding with a keyframe every 15 frames makes every seek exact and near instant: `COMPARE_FFMPEG` holds the command (`ffmpeg -i in.mov -vf fps=30 -fps_mode cfr -c:v libx264 -crf 18 -g 15 … out.mp4`). Sources load only near the view and are let go 3 s after leaving it (`lazy: false` keeps them), every viewer shares one GPU device, and in a container with no height of its own it takes the height its sides need, half its width in their shape, so the split has no margins (`aspect` before they load); with a height of its own (a card going full-window, say), the pair fits as a whole and refits as it resizes, keeping the zoom. On a card (`.knobs-surface`, a split's pane) or marked `data-nested`, its stage drops its fill, so one surface shows. Bind an existing `timeline()` (`timeline: clock`), or pass `timeline: true` for
|
|
1216
|
+
**`compare(container, options)`** is a before/after viewer for videos and images (by extension, or `kind`). It offers two modes, side by side (the default `mode`: a split screen down the middle, each side filling its half) and a wipe; `modes: ["side", "wipe", "difference"]` adds a difference (changes above `threshold` in one accent, `--k-compare-diff`), and one mode hides the bar. The modes sit in a segmented control above the stage, the selection pill sliding to the one picked. It draws in one WebGPU pass that copies a frame in only when one arrives, and draws only when something changed; where there's no WebGPU, two `<video>`s with a clip-path wipe stand in. Pinch, `⌘`/ctrl-wheel or double-click zooms into either side and both zoom to the same part (the wheel and a drag pan it, both 1:1); a small glass pill in the corner shows how far, with a reset (`0`, a double-click, `Escape`), which glides back. Side ↔ Wipe is Kit Motion's Comparison fuse: the two sides glide together on one spring (.3 s) and fuse at one side's size in the middle, so the stage never changes size; Before is cut only where they overlap, with a feather that swells mid-transition and a blurred strip over the seam, the wipe's handle fades in with it, and the labels fold to their names and ride either side of the seam (Wipe → Side runs it back, and either way it can be turned around midway). Overlaid, the sides are letterboxed, never cropped. Both videos run from one clock: paused, scrubbed or stepped, each seeks to the frame's midpoint, coalesced while a seek is in flight; playing, After leads, Before's rate leans by up to 25% to stay on it (it seeks past 90 ms), and a bound timeline follows After's time, so only one clock ever moves it (`Space` resumes from the frame showing). Scrubbing answers at once even on raw screen recordings with a keyframe every 8–18 s: frames decoded in the background (WebCodecs where the file is a plain MP4, else a hidden `<video>`) sit in a small cache shared by every viewer and strip of the same source, and the nearest shows while the exact seek lands (mid-drag, far from any cached frame, `fastSeek` answers first; letting go seeks exactly). Encoding with a keyframe every 15 frames makes every seek exact and near instant: `COMPARE_FFMPEG` holds the command (`ffmpeg -i in.mov -vf fps=30 -fps_mode cfr -c:v libx264 -crf 18 -g 15 … out.mp4`). Sources load only near the view and are let go 3 s after leaving it (`lazy: false` keeps them), every viewer shares one GPU device, and in a container with no height of its own it takes the height its sides need, half its width in their shape, so the split has no margins (`aspect` before they load); with a height of its own (a card going full-window, say), the pair fits as a whole and refits as it resizes, keeping the zoom. On a card (`.knobs-surface`, a split's pane) or marked `data-nested`, its stage drops its fill, so one surface shows. Bind an existing `timeline()` (`timeline: clock`), or pass `timeline: true` for one of its own: a player's thin progress line under the stage (`variant: "line"`, the same as `player()`'s), its markers (a walkthrough's chapters) segments with a small gap between; there's no clock or frame readout anywhere, only the chapter and time under the pointer over the line. A click on the stage plays and pauses (except wiping, where a press moves the line), flashing the glyph for it. `seek(t, { glide: true })` is a chapter's jump: the videos land at once and the playhead catches up. Loop (`L`, the loop icon) sets a loop range around the playhead on the timeline, whose ends drag, and playback stays inside it. The wipe's line carries a raised 16×28 grip in its middle that follows the pointer's height only while dragged and settles back when let go; dragging is 1:1, the cursor hides and the line and grip brighten, and a press elsewhere on the stage brings the line there on a quick catch-up (.15 s). Before and After ride either side of the seam while wiping, each fading as its side runs out of room. The wipe is a slider too (`←`/`→`, `⇧` 10%); a mode change glides. Keys when focused: `Space` plays, `←`/`→` seek half a second (`⇧` two), the playhead gliding there while the videos land at once, `,`/`.` step a frame, `1`–`3` pick the offered modes in order and `S` `W` (`D`, when offered) by name, `Z` held shows the loupe (or `loupe: true`), `L` loops, `=`/`-`/`0` zoom. It reports `onMode`, `onWipe` and `onView({ zoom, loop })`; the handle has `mode`, `modes`, `wipe`, `zoom`, `loop`, `time`, `playing`, `duration`, `renderer`, `timeline`, `strip`, `update`, `seek`, `seekBy`, `play`, `pause`, `toggle`, `step`, `setZoom`, `setLoop`, `focus` and `dispose`. A frame strip is off by default (`strip: true` puts one over the timeline); **`compareStrip(container, { before, after, timeline })`** is the same piece on its own: it draws Before over After along the timeline's view from the same frame cache, decoded only while it's on screen, with a bar for how much each column differs; a press scrubs. `Compare` and `CompareStrip` are in `knobs/ui/react` and `knobs/ui/solid`. The Lab's View switch shows one on its scene timeline.
|
|
1217
|
+
|
|
1218
|
+
**`player(container, { src, chapters? })`** is a plain video in the same look: the picture in the compare's frame, then one thin progress line under it (2px, the tertiary text colour filled in text colour, a segment per chapter `{ label, start }`, thicker under the pointer). Click the picture, or press `Space` with the line focused, to play and pause (a click flashes the glyph for it); click or drag the line to seek, 1:1, and a click away from where it is glides there while the video lands at once. The chapter and time under the pointer float over the line; `←`/`→` seek 5 s (`⇧` 10 s), and a pinch, `⌘`/ctrl-wheel or `=`/`-` zoom the line as a lane's. The picture comes in with its first frame (a 4px blur and a fade). It takes `poster`, `captions` (WebVTT), `loop` (true), `muted` (true), `aspect` and `fps`, reports `onPlay(playing, time)` and `onSeek(time)` (a seek that landed, paused), and the handle has `video`, `timeline`, `time`, `playing`, `duration`, `play`, `pause`, `toggle`, `seek(t, { glide })`, `seekBy`, `focus` and `dispose`. Under it is `timeline(…, { variant: "line" })`, which you can bind to anything else with a clock.
|
|
1217
1219
|
|
|
1218
1220
|
### Activity
|
|
1219
1221
|
|
|
@@ -1283,7 +1285,7 @@ import { codeDiff, diffReview } from "knobs/ui"
|
|
|
1283
1285
|
// A patch's files in a tree beside the diff, in a split.
|
|
1284
1286
|
const review = diffReview(container, { ...look, id: "review", patch: gitShowOutput })
|
|
1285
1287
|
|
|
1286
|
-
// Or the diff alone
|
|
1288
|
+
// Or the diff alone: one file or many, from a patch or before and after text.
|
|
1287
1289
|
const diff = codeDiff(el, {
|
|
1288
1290
|
...look,
|
|
1289
1291
|
files: [{ name: "src/spring.ts", before, after }],
|
|
@@ -1296,13 +1298,13 @@ const diff = codeDiff(el, {
|
|
|
1296
1298
|
for (const step of steps) codeDiff(step.el, { ...look, patch: step.patch, height: "auto" })
|
|
1297
1299
|
```
|
|
1298
1300
|
|
|
1299
|
-
**`codeDiff(container, options)`** shows a code diff
|
|
1301
|
+
**`codeDiff(container, options)`** shows a code diff on the void, with no card, fill or ring (a hairline under its header and one under the code, as a walkthrough's code block has, and only changed lines tinted): a quiet header with the file's path, its `+12 −3`, ‹ 2 of 5 › when there are several files (`nav: false` hides it), a wrap toggle and Split | Unified (a segmented control, the selection pill sliding to the one picked), over the diff. Its buttons are Phosphor icons. The diff itself is drawn by [`@pierre/diffs`](https://diffs.com) (Shiki highlighting, word-level changes, hunks that expand when whole files are known), an optional peer dependency: install it to use diffs (`npm i @pierre/diffs`). It loads on first use, so `knobs/ui` doesn't carry it. Its colours come from the pieces' tokens, so dark, light, presets and `restyle()` carry into it. `--k-diff-add`, `--k-diff-del` and `--k-diff-bg` tune it, and `syntax: { dark, light }` picks Shiki themes for the code. It takes `patch` (`git diff` or `git show` output, one file or many) or `files: [{ name, before, after, lang? }]`, plus `file`, `layout`, `wrap`, `onFile`, `onLayout`, `onWrap` and `onLines(lines, file)` (dragging over line numbers selects lines). Changing the file, the layout or the wrap fades the new view in over the old in place (.3 s in, .24 s out, a 3px blur while it fades; a .16 s fade under reduced motion). With `height: "fill"` the card fills its container and each file keeps its scroll. With `height: "auto"` it's a self-contained piece for a long page: it's as tall as the file's diff up to `maxHeight` (640, or 70% of the window when that's less) and scrolls inside past that, springing to each file's height (.4 s, no bounce). It holds the height the diff will take from the first frame, counted from the patch's rows before the renderer has loaded, so nothing below it jumps. It also draws only within a screen of the viewport (of the scroller it sits in) and lets go of its views four screens away, keeping its height; `lazy: false` draws it at once, and `lazy: true` does this with `height: "fill"`. Pass `viewed` (names) or `onViewed(viewed, file)` for a Viewed tick in the header. `stack: true` puts every file down one scroller, as GitHub's Files changed does: each under its own header (status, path, counts and their bar, Viewed) that sticks to the top until the next file's pushes it off: flush (square, no card around each file, a gap between files), opaque in the code's colour, with a hairline under it only while it's stuck. The card's header then sums the change, ‹ › and `show()` glide to a file (.4 s), the file on show follows the scroll, and each file draws only near the view.
|
|
1300
1302
|
|
|
1301
|
-
Its scrollers fade their content out at an edge with more beyond it (a mask: at the top once scrolled, below a stuck header, and at the bottom while there's more below, `--k-diff-fade`, 20px), easing in and out on the .25 s spring. Its scrollbars are thin ones drawn over the code that take no room and stay hidden until the pointer is over the diff, or while you scroll or drag: `overlayScrollbar` up and down, and a bar per code column across, kept at the bottom of what shows of the column however far down a long file you are (the renderer's own, inside its shadow root, are hidden).
|
|
1303
|
+
Its scrollers fade their content out at an edge with more beyond it (a mask: at the top once scrolled, below a stuck header, and at the bottom while there's more below, `--k-diff-fade`, 20px), easing in and out on the .25 s spring. Its scrollbars are thin ones drawn over the code that take no room and stay hidden until the pointer is over the diff, or while you scroll or drag: `overlayScrollbar` up and down, and a bar per code column across, kept at the bottom of what shows of the column however far down a long file you are (the renderer's own, inside its shadow root, are hidden). On its own or on a surface the header and the code line up with the column's content (no inset across); in a split's pane or a `diffReview`, one inset runs all round, `--k-diff-inset` (8px, the renderer's own gap): the header's controls sit it in from the top and right, the path, the separators and the line numbers it in from the left, and the code ends it above the bottom. Inner corners (separators, the header's controls) are the outer corner less that distance, so they're concentric with the card. On another surface the diff takes no inset across, so its header and code line up with the surface's own content (a walkthrough's caption), and its corners are measured from the surface's. The header's hairline shows only once the code has scrolled under it. The handle has `files`, `file`, `layout`, `wrap`, `viewed`, `show(name)`, `step(±1)`, `update({ patch?, files?, file?, layout?, wrap?, viewed?, height?, maxHeight? })`, `ready` (once drawn) and `dispose()`. `update({ height: "fill" | "auto" })` switches in place, keeping the views, their scroll and the sticky headers; the new height isn't sprung, so a container whose size is moving (a walkthrough card going full window) is followed, not fought.
|
|
1302
1304
|
|
|
1303
1305
|
**One surface.** Cards never nest. Whatever draws a card carries `.knobs-surface` (a walkthrough's step; put it on your own cards too), and a split's pane counts as one; a piece inside a surface draws no card of its own, so a diff in a step, a pane or your card shows no second fill or ring, and a `diffReview` inside one parts its panes on a hairline instead of drawing them as cards. The diff's code keeps an opaque fill under its sticky line numbers, the colour showing behind it, so it still matches.
|
|
1304
1306
|
|
|
1305
|
-
**`diffReview(container, options)`** pairs it with a file list
|
|
1307
|
+
**`diffReview(container, options)`** pairs it with a file list, on the void as the diff is: a hairline parts the list from the diff, one runs under both headers and one under it all, and the filter is a line of text with no box. The list is a drawer at the review's left that springs open and shut between 0 and 256px (`listSize`; .4 s, no bounce), from a button at the start of the diff's header or `⌘B` / `Ctrl+B` while you're working in the review; the diff reflows as it moves, the list slides out from under it, and with an `id` it stays as you left it (`listOpen: false` starts it shut). The list is built on `tree()` and laid out as GitHub's, Graphite's and VS Code's are: a filter field on top (every word must be in the path: `ui diff`; `Esc` clears it, `↓` or `Enter` goes to the list), then a compact tree (28px rows, `--k-diff-row`) with folders folded into paths (`src/ui` is one row when `src` holds nothing else). Each file shows its change as a coloured letter (M amber, A green, D red and struck through, R blue; `--k-diff-mod` and `--k-diff-move` tune the two new ones), its `+12 −3` and a five-square bar of the share added and removed, and a tick to mark it viewed (on a folder, everything in it). Viewed files dim, the list's title counts them (`2/5`), and with an `id` they're remembered until their change moves on (each is saved with a fingerprint of its diff). The file on show has the selection pill: one highlight that slides to the next on the state spring (.3 s, no bounce), its row's text sharp and bright under it while other rows stay as they are (the tree's `glide` option). Picking a file shows it. The handle has the pieces, `tree` and `diff`, plus `listOpen`, `toggleList(open?)`, `viewed`, `setViewed(names, on)`, `filter(query)` and `update()`; `viewed` and `onViewed(viewed, file)` seed and report the marks, and `search: false` drops the field. To place the tree and the diff yourself, `diffFiles({ patch })` lists the files with their counts in tree order, `diffTree(files)` makes the tree's items, and `diffCounts(file, doc, { bar })` and `diffStatus(file)` give a row's counts and letter (a tree's `detail` and `icon`). `CodeDiff` and `DiffReview` in `knobs/ui/react` and `knobs/ui/solid` follow `patch`, `files`, `file`, `layout`, `wrap`, `viewed`, `height` and `maxHeight`. The demo is `examples/lab-diff.html`.
|
|
1306
1308
|
|
|
1307
1309
|
### Ideas grid (bento)
|
|
1308
1310
|
|
|
@@ -1353,13 +1355,22 @@ const doc: WalkthroughDoc = {
|
|
|
1353
1355
|
const w = walkthrough(container, { doc, onFeedback: (fb) => send(fb) })
|
|
1354
1356
|
```
|
|
1355
1357
|
|
|
1356
|
-
**`walkthrough(container, { doc })`** is the page an agent writes to show its work: a plain document (JSON) of sections and steps, each step a title, a caption, its media and what it asks (a note, or a pick between options).
|
|
1358
|
+
**`walkthrough(container, { doc })`** is the page an agent writes to show its work: a plain document (JSON) of sections and steps, each step a title, a caption, its media and what it asks (a note, or a pick between options). The contents rail sits sticky at the page's left edge (`rail: "right"` mirrors it, `false` drops it; under 960px it hides, or with review on folds to a bar on top with the progress and the export). Its rows share one text edge and one colour, the selection pill marks the step you're on, and a click goes to a row, and the column is centred in the room beside it, up to `--k-walkthrough-width` (960px): media take the column's width, prose stops at the measure. It's laid out on the page tokens: the intro, the updates and each section a section's room apart (`--k-space-section`, 96px, a hairline in the middle), the groups and steps inside a section a group's room apart (`--k-space-group`, 48px), prose at 68ch. Steps aren't boxes: they sit on the page with a hairline between them, and the spacing roles do the grouping; `cards: true` puts each on a card of its own, the one surface its media sit on (nothing inside draws a fill and a ring of its own). Titles take inline Markdown (`` `code` ``, `[links](…)`); the contents read them as plain words.
|
|
1359
|
+
|
|
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
|
+
|
|
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.
|
|
1357
1363
|
|
|
1358
|
-
|
|
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.
|
|
1359
1365
|
|
|
1360
|
-
|
|
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.
|
|
1367
|
+
|
|
1368
|
+
```json
|
|
1369
|
+
{ "title": "Retired models get a way out", "caption": "After: **Pick another model**, and the prompt back.",
|
|
1370
|
+
"more": "A mock provider answers 410 Gone.\n\n**Pick another model** opens the picker." }
|
|
1371
|
+
```
|
|
1361
1372
|
|
|
1362
|
-
|
|
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)".
|
|
1363
1374
|
|
|
1364
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.
|
|
1365
1376
|
|
|
@@ -1527,13 +1538,13 @@ The page roles (**Space**: `--k-space-inside` 4 · `sibling` 8 · `title` 12 ·
|
|
|
1527
1538
|
|---|---|---|---|---|
|
|
1528
1539
|
| `--k-font` | input | `system-ui,-apple-system,"SF Pro Text",sans-serif` | `system-ui,-apple-system,"SF Pro Text",sans-serif` | UI font stack. |
|
|
1529
1540
|
| `--k-mono` | input | `"SF Mono",SFMono-Regular,ui-monospace,Menlo,monospace` | `"SF Mono",SFMono-Regular,ui-monospace,Menlo,monospace` | Monospace font stack. |
|
|
1530
|
-
| `--k-type-heading` | semantic | `20px` | `20px` |
|
|
1541
|
+
| `--k-type-heading` | semantic | `20px` | `20px` | A page's title, weight 500. |
|
|
1531
1542
|
| `--k-leading-heading` | semantic | `28px` | `28px` | Line height for type-heading. |
|
|
1532
|
-
| `--k-type-title` | semantic | `16px` | `16px` |
|
|
1543
|
+
| `--k-type-title` | semantic | `16px` | `16px` | Section titles (under their kicker), weight 500. |
|
|
1533
1544
|
| `--k-leading-title` | semantic | `24px` | `24px` | Line height for type-title. |
|
|
1534
1545
|
| `--k-type-body` | semantic | `13px` | `13px` | Prose, questions, list rows, step titles; 400, or 500 for emphasis. |
|
|
1535
1546
|
| `--k-leading-body` | semantic | `20px` | `20px` | Line height for type-body. |
|
|
1536
|
-
| `--k-type-label` | semantic | `12px` | `12px` |
|
|
1547
|
+
| `--k-type-label` | semantic | `12px` | `12px` | Mono inside body text: code and paths. |
|
|
1537
1548
|
| `--k-leading-label` | semantic | `16px` | `16px` | Line height for type-label. |
|
|
1538
1549
|
| `--k-type-small` | semantic | `11px` | `11px` | Meta, controls, keys, timestamps; mono inside labels. |
|
|
1539
1550
|
| `--k-leading-small` | semantic | `16px` | `16px` | Line height for type-small. |
|
|
@@ -1913,7 +1924,7 @@ The package has two core exports selected through the standard `development` con
|
|
|
1913
1924
|
| --- | --- |
|
|
1914
1925
|
| `split(container, { panes, direction?, id?, storage?, onLayout?, preset?, theme? })` | Resizable panes and docks that collapse to rails or headers; layouts saved by `id`. |
|
|
1915
1926
|
| `tree(container, { items, onSelect?, onMove?, onRename?, toggles?, filter?, typeahead?, … })` | A virtualised outliner with keyboard moves, drag reorder and rename; `typeahead: false \| (key) => boolean` leaves letters to the page. |
|
|
1916
|
-
| `codeDiff(container, { patch? \| files?, file?, layout?, wrap?, height?, onFile?, onLines?, … })` | A code diff
|
|
1927
|
+
| `codeDiff(container, { patch? \| files?, file?, layout?, wrap?, height?, onFile?, onLines?, … })` | A code diff, no card (needs `@pierre/diffs`); files and layouts fade in place. |
|
|
1917
1928
|
| `diffReview(container, { patch? \| files?, id?, … })` | The changed files (a filter, viewed ticks) in a tree beside the diff, in a split. |
|
|
1918
1929
|
| `readout(field, { preset?, theme?, label? })` | A readout on its own: `{ element, push(value), value, dispose() }`. |
|
|
1919
1930
|
| `popover(anchor, content, { placement?, offset?, role?, label?, focus?, trap?, closeOnOutside?, onClose? })` | A floating layer that flips and shifts; `{ element, open, reposition(), close() }`. |
|
|
@@ -1370,7 +1370,7 @@ var theme = { from: "theme" }, css = (s) => ({ css: s }), value = (s) => ({ valu
|
|
|
1370
1370
|
type: "length",
|
|
1371
1371
|
group: "Type",
|
|
1372
1372
|
source: value("20px"),
|
|
1373
|
-
doc: "
|
|
1373
|
+
doc: "A page's title, weight 500."
|
|
1374
1374
|
},
|
|
1375
1375
|
{
|
|
1376
1376
|
name: "leading-heading",
|
|
@@ -1386,7 +1386,7 @@ var theme = { from: "theme" }, css = (s) => ({ css: s }), value = (s) => ({ valu
|
|
|
1386
1386
|
type: "length",
|
|
1387
1387
|
group: "Type",
|
|
1388
1388
|
source: value("16px"),
|
|
1389
|
-
doc: "
|
|
1389
|
+
doc: "Section titles (under their kicker), weight 500."
|
|
1390
1390
|
},
|
|
1391
1391
|
{
|
|
1392
1392
|
name: "leading-title",
|
|
@@ -1418,7 +1418,7 @@ var theme = { from: "theme" }, css = (s) => ({ css: s }), value = (s) => ({ valu
|
|
|
1418
1418
|
type: "length",
|
|
1419
1419
|
group: "Type",
|
|
1420
1420
|
source: value("12px"),
|
|
1421
|
-
doc: "
|
|
1421
|
+
doc: "Mono inside body text: code and paths."
|
|
1422
1422
|
},
|
|
1423
1423
|
{
|
|
1424
1424
|
name: "leading-label",
|
|
@@ -116,7 +116,7 @@ import {
|
|
|
116
116
|
createHintController,
|
|
117
117
|
pageHints,
|
|
118
118
|
toggleLayer
|
|
119
|
-
} from "./chunk-
|
|
119
|
+
} from "./chunk-dyq4m24m.js";
|
|
120
120
|
|
|
121
121
|
// src/panel/die.ts
|
|
122
122
|
var ROUND = 16 * Math.sqrt(5), DIE = { half: 64 + ROUND, round: ROUND, pip: 12 }, PIPS = {
|
|
@@ -11473,6 +11473,8 @@ var compareCss = `
|
|
|
11473
11473
|
.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}
|
|
11474
11474
|
.knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
|
|
11475
11475
|
.knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
|
|
11476
|
+
/* Videos: a click on the stage plays and pauses. */
|
|
11477
|
+
.knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
|
|
11476
11478
|
/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
|
|
11477
11479
|
.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
|
|
11478
11480
|
.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
|
|
@@ -11482,6 +11484,9 @@ var compareCss = `
|
|
|
11482
11484
|
.knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
|
|
11483
11485
|
.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}
|
|
11484
11486
|
.knobs-compare[data-renderer="gpu"] .knobs-cmp-media{opacity:0}
|
|
11487
|
+
/* The media comes in with its first frames: a fade and a 4px blur on the default spring. */
|
|
11488
|
+
.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)}
|
|
11489
|
+
.knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}
|
|
11485
11490
|
/* Glass: what's behind shows through, blurred, on the knobs tokens. */
|
|
11486
11491
|
.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);
|
|
11487
11492
|
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)))}
|
|
@@ -11490,11 +11495,17 @@ var compareCss = `
|
|
|
11490
11495
|
white-space:nowrap;pointer-events:none;will-change:transform}
|
|
11491
11496
|
.knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
|
|
11492
11497
|
.knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
.knobs-cmp-
|
|
11498
|
+
/* Fusing, the mark folds away and the label is its name. */
|
|
11499
|
+
.knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}
|
|
11500
|
+
.knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}
|
|
11501
|
+
/* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */
|
|
11502
|
+
.knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;
|
|
11503
|
+
-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)}
|
|
11504
|
+
/* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height
|
|
11505
|
+
while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the
|
|
11506
|
+
fuse; wiping, line and grip brighten. */
|
|
11507
|
+
.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}
|
|
11508
|
+
.knobs-cmp-handle[data-on]{pointer-events:auto}
|
|
11498
11509
|
.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)}
|
|
11499
11510
|
.knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
|
|
11500
11511
|
.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);
|
|
@@ -11526,6 +11537,27 @@ var compareCss = `
|
|
|
11526
11537
|
@media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}
|
|
11527
11538
|
`;
|
|
11528
11539
|
|
|
11540
|
+
// src/ui/player-css.ts
|
|
11541
|
+
var playerCss = `
|
|
11542
|
+
:where(.knobs-player,.knobs-player *){box-sizing:border-box}
|
|
11543
|
+
.knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
|
|
11544
|
+
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
11545
|
+
: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)}
|
|
11546
|
+
.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}
|
|
11547
|
+
.knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
|
|
11548
|
+
.knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
|
|
11549
|
+
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)}
|
|
11550
|
+
.knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
|
|
11551
|
+
.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;
|
|
11552
|
+
opacity:0;filter:blur(4px);scale:.96;
|
|
11553
|
+
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);
|
|
11554
|
+
box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
|
|
11555
|
+
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)}
|
|
11556
|
+
.knobs-player-glyph svg{width:16px;height:16px}
|
|
11557
|
+
.knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
|
|
11558
|
+
.knobs-player-line{flex:none;min-height:0}
|
|
11559
|
+
`;
|
|
11560
|
+
|
|
11529
11561
|
// src/ui/overlay-css.ts
|
|
11530
11562
|
var overlayCss = `
|
|
11531
11563
|
.knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}
|
|
@@ -12034,8 +12066,6 @@ var css = `
|
|
|
12034
12066
|
.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}
|
|
12035
12067
|
.knobs-tl-track:hover .knobs-tl-hover{opacity:1}
|
|
12036
12068
|
.knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
|
|
12037
|
-
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
12038
|
-
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
12039
12069
|
.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}
|
|
12040
12070
|
.knobs-tl-tag[data-on]{opacity:1}
|
|
12041
12071
|
/* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
|
|
@@ -12051,7 +12081,26 @@ var css = `
|
|
|
12051
12081
|
.knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
|
|
12052
12082
|
.knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
|
|
12053
12083
|
.knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
|
|
12054
|
-
|
|
12084
|
+
/* The line (variant: "line"): a player's progress under its media. One 2px line, fg-3 with fg
|
|
12085
|
+
filling it, a segment per chapter; it thickens under the pointer and while scrubbing. The
|
|
12086
|
+
chapter and time under the pointer float over it; nothing else shows. */
|
|
12087
|
+
.knobs-ui.knobs-timeline[data-variant="line"]{height:auto;min-height:0;padding:0;gap:0}
|
|
12088
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-transport{display:none}
|
|
12089
|
+
.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}
|
|
12090
|
+
.knobs-timeline[data-variant="line"] :is(.knobs-tl-ruler,.knobs-tl-lane,.knobs-tl-hover,.knobs-tl-head,.knobs-tl-rlabel){display:none}
|
|
12091
|
+
.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)}
|
|
12092
|
+
.knobs-tl-seg{position:absolute;left:0;top:0;height:100%;overflow:hidden;border-radius:1px;background:var(--_k-fg-3);will-change:transform}
|
|
12093
|
+
.knobs-tl-seg-fill{position:absolute;inset:0;background:var(--_k-fg);transform-origin:0 0;will-change:transform}
|
|
12094
|
+
.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}
|
|
12095
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-range{top:3px;bottom:3px;border-radius:3px}
|
|
12096
|
+
.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)}
|
|
12097
|
+
.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);
|
|
12098
|
+
-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:inset 0 0 0 .5px var(--_k-border)}
|
|
12099
|
+
.knobs-tl-tag-label{color:var(--_k-fg-2)}
|
|
12100
|
+
.knobs-tl-tag-label:empty{display:none}
|
|
12101
|
+
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
12102
|
+
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
12103
|
+
`, uiCss = () => aliased(css + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + playerCss + selectionPillCss) + rolling_css_default;
|
|
12055
12104
|
function injectUiStyles(doc) {
|
|
12056
12105
|
if (injectTokens(doc), doc.querySelector("style[data-knobs-ui]"))
|
|
12057
12106
|
return;
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
writeSession,
|
|
10
10
|
injectStyles,
|
|
11
11
|
toggleLayer
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-dyq4m24m.js";
|
|
13
13
|
|
|
14
14
|
// src/panel/attach.ts
|
|
15
15
|
function attach(controls, element, options = {}) {
|
|
@@ -85,7 +85,7 @@ function attach(controls, element, options = {}) {
|
|
|
85
85
|
return;
|
|
86
86
|
loading = !0;
|
|
87
87
|
try {
|
|
88
|
-
let { createPanel } = await (__preload("
|
|
88
|
+
let { createPanel } = await (__preload("qc6szek7"), import("./chunk-qc6szek7.js"));
|
|
89
89
|
if (disposed || !controls.enabled || panel)
|
|
90
90
|
return;
|
|
91
91
|
if (panel = createPanel(controls, element, close, { restored, chip }), remember)
|
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,["
|
|
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);
|
|
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("
|
|
1381
|
+
let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("y2axkmz3"), import("./chunk-y2axkmz3.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("
|
|
1385
|
+
let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("qc6szek7"), import("./chunk-qc6szek7.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
|
@@ -1922,7 +1922,7 @@ var init_schema = __esm(() => {
|
|
|
1922
1922
|
type: "length",
|
|
1923
1923
|
group: "Type",
|
|
1924
1924
|
source: value("20px"),
|
|
1925
|
-
doc: "
|
|
1925
|
+
doc: "A page's title, weight 500."
|
|
1926
1926
|
},
|
|
1927
1927
|
{
|
|
1928
1928
|
name: "leading-heading",
|
|
@@ -1938,7 +1938,7 @@ var init_schema = __esm(() => {
|
|
|
1938
1938
|
type: "length",
|
|
1939
1939
|
group: "Type",
|
|
1940
1940
|
source: value("16px"),
|
|
1941
|
-
doc: "
|
|
1941
|
+
doc: "Section titles (under their kicker), weight 500."
|
|
1942
1942
|
},
|
|
1943
1943
|
{
|
|
1944
1944
|
name: "leading-title",
|
|
@@ -1970,7 +1970,7 @@ var init_schema = __esm(() => {
|
|
|
1970
1970
|
type: "length",
|
|
1971
1971
|
group: "Type",
|
|
1972
1972
|
source: value("12px"),
|
|
1973
|
-
doc: "
|
|
1973
|
+
doc: "Mono inside body text: code and paths."
|
|
1974
1974
|
},
|
|
1975
1975
|
{
|
|
1976
1976
|
name: "leading-label",
|
|
@@ -21096,6 +21096,8 @@ var compareCss = `
|
|
|
21096
21096
|
.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}
|
|
21097
21097
|
.knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
|
|
21098
21098
|
.knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
|
|
21099
|
+
/* Videos: a click on the stage plays and pauses. */
|
|
21100
|
+
.knobs-compare[data-video]:not([data-mode="wipe"],[data-zoomed]) .knobs-cmp-stage{cursor:pointer}
|
|
21099
21101
|
/* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
|
|
21100
21102
|
.knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
|
|
21101
21103
|
.knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
|
|
@@ -21105,6 +21107,9 @@ var compareCss = `
|
|
|
21105
21107
|
.knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
|
|
21106
21108
|
.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}
|
|
21107
21109
|
.knobs-compare[data-renderer="gpu"] .knobs-cmp-media{opacity:0}
|
|
21110
|
+
/* The media comes in with its first frames: a fade and a 4px blur on the default spring. */
|
|
21111
|
+
.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)}
|
|
21112
|
+
.knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}
|
|
21108
21113
|
/* Glass: what's behind shows through, blurred, on the knobs tokens. */
|
|
21109
21114
|
.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);
|
|
21110
21115
|
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)))}
|
|
@@ -21113,11 +21118,17 @@ var compareCss = `
|
|
|
21113
21118
|
white-space:nowrap;pointer-events:none;will-change:transform}
|
|
21114
21119
|
.knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
|
|
21115
21120
|
.knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
|
|
21116
|
-
|
|
21117
|
-
|
|
21118
|
-
|
|
21119
|
-
|
|
21120
|
-
.knobs-cmp-
|
|
21121
|
+
/* Fusing, the mark folds away and the label is its name. */
|
|
21122
|
+
.knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}
|
|
21123
|
+
.knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}
|
|
21124
|
+
/* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */
|
|
21125
|
+
.knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;
|
|
21126
|
+
-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)}
|
|
21127
|
+
/* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height
|
|
21128
|
+
while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the
|
|
21129
|
+
fuse; wiping, line and grip brighten. */
|
|
21130
|
+
.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}
|
|
21131
|
+
.knobs-cmp-handle[data-on]{pointer-events:auto}
|
|
21121
21132
|
.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)}
|
|
21122
21133
|
.knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
|
|
21123
21134
|
.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);
|
|
@@ -21149,6 +21160,27 @@ var compareCss = `
|
|
|
21149
21160
|
@media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}
|
|
21150
21161
|
`;
|
|
21151
21162
|
|
|
21163
|
+
// src/ui/player-css.ts
|
|
21164
|
+
var playerCss = `
|
|
21165
|
+
:where(.knobs-player,.knobs-player *){box-sizing:border-box}
|
|
21166
|
+
.knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
|
|
21167
|
+
font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
|
|
21168
|
+
: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)}
|
|
21169
|
+
.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}
|
|
21170
|
+
.knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
|
|
21171
|
+
.knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
|
|
21172
|
+
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)}
|
|
21173
|
+
.knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
|
|
21174
|
+
.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;
|
|
21175
|
+
opacity:0;filter:blur(4px);scale:.96;
|
|
21176
|
+
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);
|
|
21177
|
+
box-shadow:inset 0 0 0 .5px var(--_k-border),inset 0 1px 0 var(--_k-rim);
|
|
21178
|
+
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)}
|
|
21179
|
+
.knobs-player-glyph svg{width:16px;height:16px}
|
|
21180
|
+
.knobs-player-glyph[data-flash]{opacity:1;filter:none;scale:1}
|
|
21181
|
+
.knobs-player-line{flex:none;min-height:0}
|
|
21182
|
+
`;
|
|
21183
|
+
|
|
21152
21184
|
// src/ui/overlay-css.ts
|
|
21153
21185
|
var overlayCss = `
|
|
21154
21186
|
.knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}
|
|
@@ -21714,8 +21746,6 @@ var css3 = `
|
|
|
21714
21746
|
.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}
|
|
21715
21747
|
.knobs-tl-track:hover .knobs-tl-hover{opacity:1}
|
|
21716
21748
|
.knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
|
|
21717
|
-
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
21718
|
-
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
21719
21749
|
.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}
|
|
21720
21750
|
.knobs-tl-tag[data-on]{opacity:1}
|
|
21721
21751
|
/* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
|
|
@@ -21731,7 +21761,26 @@ var css3 = `
|
|
|
21731
21761
|
.knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
|
|
21732
21762
|
.knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
|
|
21733
21763
|
.knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
|
|
21734
|
-
|
|
21764
|
+
/* The line (variant: "line"): a player's progress under its media. One 2px line, fg-3 with fg
|
|
21765
|
+
filling it, a segment per chapter; it thickens under the pointer and while scrubbing. The
|
|
21766
|
+
chapter and time under the pointer float over it; nothing else shows. */
|
|
21767
|
+
.knobs-ui.knobs-timeline[data-variant="line"]{height:auto;min-height:0;padding:0;gap:0}
|
|
21768
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-transport{display:none}
|
|
21769
|
+
.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}
|
|
21770
|
+
.knobs-timeline[data-variant="line"] :is(.knobs-tl-ruler,.knobs-tl-lane,.knobs-tl-hover,.knobs-tl-head,.knobs-tl-rlabel){display:none}
|
|
21771
|
+
.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)}
|
|
21772
|
+
.knobs-tl-seg{position:absolute;left:0;top:0;height:100%;overflow:hidden;border-radius:1px;background:var(--_k-fg-3);will-change:transform}
|
|
21773
|
+
.knobs-tl-seg-fill{position:absolute;inset:0;background:var(--_k-fg);transform-origin:0 0;will-change:transform}
|
|
21774
|
+
.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}
|
|
21775
|
+
.knobs-timeline[data-variant="line"] .knobs-tl-range{top:3px;bottom:3px;border-radius:3px}
|
|
21776
|
+
.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)}
|
|
21777
|
+
.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);
|
|
21778
|
+
-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:inset 0 0 0 .5px var(--_k-border)}
|
|
21779
|
+
.knobs-tl-tag-label{color:var(--_k-fg-2)}
|
|
21780
|
+
.knobs-tl-tag-label:empty{display:none}
|
|
21781
|
+
@media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
|
|
21782
|
+
@media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
|
|
21783
|
+
`, uiCss = () => aliased(css3 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + playerCss + selectionPillCss) + rolling_css_default, lookDefaults, svg = (body, box = 16) => `<svg viewBox="0 0 ${box} ${box}" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`, ICONS2;
|
|
21735
21784
|
var init_look = __esm(() => {
|
|
21736
21785
|
init_appearance();
|
|
21737
21786
|
init_activity_css();
|
package/dist/themes.js
CHANGED
|
@@ -623,7 +623,7 @@ var TOKENS = [
|
|
|
623
623
|
type: "length",
|
|
624
624
|
group: "Type",
|
|
625
625
|
source: value("20px"),
|
|
626
|
-
doc: "
|
|
626
|
+
doc: "A page's title, weight 500."
|
|
627
627
|
},
|
|
628
628
|
{
|
|
629
629
|
name: "leading-heading",
|
|
@@ -639,7 +639,7 @@ var TOKENS = [
|
|
|
639
639
|
type: "length",
|
|
640
640
|
group: "Type",
|
|
641
641
|
source: value("16px"),
|
|
642
|
-
doc: "
|
|
642
|
+
doc: "Section titles (under their kicker), weight 500."
|
|
643
643
|
},
|
|
644
644
|
{
|
|
645
645
|
name: "leading-title",
|
|
@@ -671,7 +671,7 @@ var TOKENS = [
|
|
|
671
671
|
type: "length",
|
|
672
672
|
group: "Type",
|
|
673
673
|
source: value("12px"),
|
|
674
|
-
doc: "
|
|
674
|
+
doc: "Mono inside body text: code and paths."
|
|
675
675
|
},
|
|
676
676
|
{
|
|
677
677
|
name: "leading-label",
|
|
@@ -608,7 +608,7 @@ export declare const TOKENS: readonly [{
|
|
|
608
608
|
readonly source: {
|
|
609
609
|
readonly value: "20px";
|
|
610
610
|
};
|
|
611
|
-
readonly doc: "
|
|
611
|
+
readonly doc: "A page's title, weight 500.";
|
|
612
612
|
}, {
|
|
613
613
|
readonly name: "leading-heading";
|
|
614
614
|
readonly tier: "semantic";
|
|
@@ -626,7 +626,7 @@ export declare const TOKENS: readonly [{
|
|
|
626
626
|
readonly source: {
|
|
627
627
|
readonly value: "16px";
|
|
628
628
|
};
|
|
629
|
-
readonly doc: "
|
|
629
|
+
readonly doc: "Section titles (under their kicker), weight 500.";
|
|
630
630
|
}, {
|
|
631
631
|
readonly name: "leading-title";
|
|
632
632
|
readonly tier: "semantic";
|
|
@@ -662,7 +662,7 @@ export declare const TOKENS: readonly [{
|
|
|
662
662
|
readonly source: {
|
|
663
663
|
readonly value: "12px";
|
|
664
664
|
};
|
|
665
|
-
readonly doc: "
|
|
665
|
+
readonly doc: "Mono inside body text: code and paths.";
|
|
666
666
|
}, {
|
|
667
667
|
readonly name: "leading-label";
|
|
668
668
|
readonly tier: "semantic";
|