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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1353,7 +1353,23 @@ const doc: WalkthroughDoc = {
1353
1353
  const w = walkthrough(container, { doc, onFeedback: (fb) => send(fb) })
1354
1354
  ```
1355
1355
 
1356
- **`walkthrough(container, { doc })`** is the page an agent writes to show its work: a plain document (JSON) of sections and steps, each step one card holding a caption, its media and what it asks (a note, or a pick between options). Notes come from a bar docked at the bottom, typed, spoken or inked, each carrying 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. `j` / `k` move between steps, `←` / `→` seek, `f` goes full window, `n` writes a note, `1`–`9` pick. 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 card is the surface, so media inside draw no card of their own. Wherever media name a step (`step` on a history event, a stack item or a table cell) they take its key, id or number (`"1.2"`), and clicking goes there as `j` / `k` do. The demo is `examples/walkthrough.html`.
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). It reads as one calm column at a readable width (`--k-walkthrough-width`, 680px), the contents rail beside it on the right (`rail: "left"`, or `false`), 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 sit on the page with a hairline between them; `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.
1357
+
1358
+ 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, status and so on follow in faint, joined by `·`. Its title (20/28) outranks the page's (16/24) and its steps' (13/20). 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.
1359
+
1360
+ 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.
1361
+
1362
+ 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, and `more: "More clips"` folds everything after the first under a quiet toggle. Only a picture's frame and a code block are bounded; a metric, a callout, a table, a stack and a history sit on the page.
1363
+
1364
+ **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
+
1366
+ ```json
1367
+ { "qa": [{ "at": "2026-10-19T12:10:00-07:00", "q": "Why is there no video for #57158?",
1368
+ "a": "It's the installer redirect: nothing to watch.", "step": "4.1", "status": "shipped",
1369
+ "trail": ["0:03 · Viewed TUI /connect", "0:11 · Played the compare at 0:04"] }] }
1370
+ ```
1371
+
1372
+ The demo is `examples/walkthrough.html`.
1357
1373
 
1358
1374
  **`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.
1359
1375
 
@@ -1376,7 +1392,7 @@ const w = walkthrough(container, { doc, onFeedback: (fb) => send(fb) })
1376
1392
  "delta": { "add": 446, "del": 47 }, "status": "Stacked on #57146", "tone": "good", "summary": "Modal, notice and prompt recall." } ] }
1377
1393
  ```
1378
1394
 
1379
- **`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, a wide one scrolls across, its edges fade where there's more, and its bars show thin on hover. `caption` goes under it.
1395
+ **`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.
1380
1396
 
1381
1397
  ```json
1382
1398
  { "kind": "table", "caption": "From a clean HOME, Oct 20", "columns": [
@@ -1387,7 +1403,7 @@ const w = walkthrough(container, { doc, onFeedback: (fb) => send(fb) })
1387
1403
  { "channel": "brew install …/opencode-v2", "days": 31, "state": { "text": "Pending", "badge": "info", "step": "4.6" } } ] }
1388
1404
  ```
1389
1405
 
1390
- **`chapters`** on a `video` (or a `compare`): timestamp pills under it. A click seeks there and plays, and goes in the note's log; one highlight springs to the chapter playing as it plays (.4 s, no bounce), 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.
1406
+ **`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.
1391
1407
 
1392
1408
  ```json
1393
1409
  { "kind": "video", "src": "journey.mp4", "poster": "journey.png", "captions": "captions.vtt", "chapters": [
@@ -1401,7 +1417,7 @@ Panels take a `theme`, an `appearance`, `density: "compact" | "default" | "comfo
1401
1417
 
1402
1418
  ### Themes and appearance
1403
1419
 
1404
- Two themes ship in `knobs/themes`: **Knobs** (the default: a near-black glass panel, a white accent, alpha hairlines) and **Void** (true black and flat, solid hairlines, text capped at `#ededed`, a black stage behind compared media; for OLED screens and video review). Each has a dark and a light appearance.
1420
+ Two themes ship in `knobs/themes`: **Knobs** (the default: a near-black glass panel, a white accent, alpha hairlines) and **Void** (true black and flat, solid hairlines, text capped at `#ededed`, a black stage behind compared media; for OLED screens and video review). Each has a dark and a light appearance. `--k-before` and `--k-after` name a comparison's two sides, neutral by default (`fg-3` and `fg`).
1405
1421
 
1406
1422
  ```ts
1407
1423
  import { defineTheme, themes } from "knobs/themes"
@@ -1468,6 +1484,8 @@ A reset that went back to the active preset rather than the defaults carries `ba
1468
1484
 
1469
1485
  Generated from the token schema (`bun run scripts/tokens-table.ts`), with the Knobs theme's values. Inputs are the dials a theme turns, semantic tokens are what pieces read, hooks style one component.
1470
1486
 
1487
+ The page roles (**Space**: `--k-space-inside` 4 · `sibling` 8 · `title` 12 · `group` 48 · `section` 96 · `column` 24, `--k-pad-page` 24 (16 under 640px) and `--k-pad-card` 12; **Type**: `--k-type-heading` 20/28 · `title` 16/24 · `body` 13/20 · `label` 12/16 · `small` 11/16, with `--k-leading-*`) are what a page built from knobs/ui (a walkthrough) is laid out on. They don't scale with density: density sizes a panel's controls, and the panel doesn't read them. Two weights (400, 500), mono one size down from what's around it, tabular figures.
1488
+
1471
1489
  <!-- tokens:start -->
1472
1490
 
1473
1491
  ### Accent
@@ -1509,6 +1527,16 @@ Generated from the token schema (`bun run scripts/tokens-table.ts`), with the Kn
1509
1527
  |---|---|---|---|---|
1510
1528
  | `--k-font` | input | `system-ui,-apple-system,"SF Pro Text",sans-serif` | `system-ui,-apple-system,"SF Pro Text",sans-serif` | UI font stack. |
1511
1529
  | `--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` | Section headings (a PR's title, under its kicker), weight 500. |
1531
+ | `--k-leading-heading` | semantic | `28px` | `28px` | Line height for type-heading. |
1532
+ | `--k-type-title` | semantic | `16px` | `16px` | A page's title, weight 500. |
1533
+ | `--k-leading-title` | semantic | `24px` | `24px` | Line height for type-title. |
1534
+ | `--k-type-body` | semantic | `13px` | `13px` | Prose, questions, list rows, step titles; 400, or 500 for emphasis. |
1535
+ | `--k-leading-body` | semantic | `20px` | `20px` | Line height for type-body. |
1536
+ | `--k-type-label` | semantic | `12px` | `12px` | Labels over media, weight 500; mono inside body text. |
1537
+ | `--k-leading-label` | semantic | `16px` | `16px` | Line height for type-label. |
1538
+ | `--k-type-small` | semantic | `11px` | `11px` | Meta, controls, keys, timestamps; mono inside labels. |
1539
+ | `--k-leading-small` | semantic | `16px` | `16px` | Line height for type-small. |
1512
1540
  | `--k-font-size` | semantic | `round(calc(12px * (1 + (var(--k-density) - 1) * .5)),1px)` | `round(calc(12px * (1 + (var(--k-density) - 1) * .5)),1px)` | Body size; a 5-step scale derives privately. Derived: `round(calc(12px * (1 + (var(--k-density) - 1) * .5)),1px)`. |
1513
1541
  | `--k-value-font` | semantic | `var(--k-font)` | `var(--k-font)` | Font for readouts and values (labPreset: mono). Derived: `var(--k-font)`. |
1514
1542
 
@@ -1575,6 +1603,8 @@ Generated from the token schema (`bun run scripts/tokens-table.ts`), with the Kn
1575
1603
  | `--k-diff-add` | semantic | `#59d38c` | `#0a7e3a` | Added lines; line and word tints derive privately (12% / 38%). Derived: `var(--k-ok)`. |
1576
1604
  | `--k-diff-del` | semantic | `#fa6863` | `#cc2827` | Deleted lines. Derived: `var(--k-danger)`. |
1577
1605
  | `--k-diff-mod` | semantic | `#f7bc50` | `#b66c00` | Modified lines. Derived: `var(--k-warn)`. |
1606
+ | `--k-before` | semantic | `#9e9ea3` | `#96969b` | Before in a comparison, and only Before: its label and side of the seam. Neutral unless a theme gives it a colour. Derived: `var(--k-fg-3)`. |
1607
+ | `--k-after` | semantic | `#f4f5f9` | `#1a1a1d` | After in a comparison, and only After. Derived: `var(--k-fg)`. |
1578
1608
  | `--k-series-1` | semantic | `#6db0f4` | `#2378c2` | Chart series 1. |
1579
1609
  | `--k-series-2` | semantic | `#66c189` | `#008b4f` | Chart series 2. |
1580
1610
  | `--k-series-3` | semantic | `#d79f4c` | `#9c6900` | Chart series 3. |
@@ -1582,6 +1612,19 @@ Generated from the token schema (`bun run scripts/tokens-table.ts`), with the Kn
1582
1612
  | `--k-series-5` | semantic | `#bc97e8` | `#885cb5` | Chart series 5. |
1583
1613
  | `--k-series-6` | semantic | `#25c2c2` | `#008686` | Chart series 6. |
1584
1614
 
1615
+ ### Space
1616
+
1617
+ | Token | Tier | Dark | Light | |
1618
+ |---|---|---|---|---|
1619
+ | `--k-space-inside` | semantic | `var(--k-unit)` | `var(--k-unit)` | Parts of one thing: an icon and its label, a meta line and its title. Derived: `var(--k-unit)`. |
1620
+ | `--k-space-sibling` | semantic | `calc(var(--k-unit) * 2)` | `calc(var(--k-unit) * 2)` | Siblings in a group: buttons in a row, a list item's lines. Derived: `calc(var(--k-unit) * 2)`. |
1621
+ | `--k-space-title` | semantic | `calc(var(--k-unit) * 3)` | `calc(var(--k-unit) * 3)` | A heading and what it heads. Derived: `calc(var(--k-unit) * 3)`. |
1622
+ | `--k-space-group` | semantic | `calc(var(--k-unit) * 12)` | `calc(var(--k-unit) * 12)` | Groups inside a section (prose, video, diff), stacked media, feed items (a hairline in the middle). Derived: `calc(var(--k-unit) * 12)`. |
1623
+ | `--k-space-section` | semantic | `calc(var(--k-unit) * 24)` | `calc(var(--k-unit) * 24)` | Sections of a page (the intro, the updates, each PR), a hairline in the middle. Derived: `calc(var(--k-unit) * 24)`. |
1624
+ | `--k-space-column` | semantic | `calc(var(--k-unit) * 6)` | `calc(var(--k-unit) * 6)` | Columns: a contents rail and the page, media side by side. Derived: `calc(var(--k-unit) * 6)`. |
1625
+ | `--k-pad-page` | semantic | `calc(var(--k-unit) * 6)` | `calc(var(--k-unit) * 6)` | A page's edge; 16px under 640px wide. Derived: `calc(var(--k-unit) * 6)`. |
1626
+ | `--k-pad-card` | semantic | `calc(var(--k-unit) * 3)` | `calc(var(--k-unit) * 3)` | Inside a card or popover. Derived: `calc(var(--k-unit) * 3)`. |
1627
+
1585
1628
  ### Material
1586
1629
 
1587
1630
  | Token | Tier | Dark | Light | |
@@ -1194,6 +1194,22 @@ var theme = { from: "theme" }, css = (s) => ({ css: s }), value = (s) => ({ valu
1194
1194
  source: css("var(--k-warn)"),
1195
1195
  doc: "Modified lines."
1196
1196
  },
1197
+ {
1198
+ name: "before",
1199
+ tier: "semantic",
1200
+ type: "color",
1201
+ group: "Data",
1202
+ source: css("var(--k-fg-3)"),
1203
+ doc: "Before in a comparison, and only Before: its label and side of the seam. Neutral unless a theme gives it a colour."
1204
+ },
1205
+ {
1206
+ name: "after",
1207
+ tier: "semantic",
1208
+ type: "color",
1209
+ group: "Data",
1210
+ source: css("var(--k-fg)"),
1211
+ doc: "After in a comparison, and only After."
1212
+ },
1197
1213
  {
1198
1214
  name: "series-1",
1199
1215
  tier: "semantic",
@@ -1284,6 +1300,150 @@ var theme = { from: "theme" }, css = (s) => ({ css: s }), value = (s) => ({ valu
1284
1300
  source: value("288px"),
1285
1301
  doc: "Panel width."
1286
1302
  },
1303
+ {
1304
+ name: "space-inside",
1305
+ tier: "semantic",
1306
+ type: "length",
1307
+ group: "Space",
1308
+ source: css("var(--k-unit)"),
1309
+ doc: "Parts of one thing: an icon and its label, a meta line and its title."
1310
+ },
1311
+ {
1312
+ name: "space-sibling",
1313
+ tier: "semantic",
1314
+ type: "length",
1315
+ group: "Space",
1316
+ source: css("calc(var(--k-unit) * 2)"),
1317
+ doc: "Siblings in a group: buttons in a row, a list item's lines."
1318
+ },
1319
+ {
1320
+ name: "space-title",
1321
+ tier: "semantic",
1322
+ type: "length",
1323
+ group: "Space",
1324
+ source: css("calc(var(--k-unit) * 3)"),
1325
+ doc: "A heading and what it heads."
1326
+ },
1327
+ {
1328
+ name: "space-group",
1329
+ tier: "semantic",
1330
+ type: "length",
1331
+ group: "Space",
1332
+ source: css("calc(var(--k-unit) * 12)"),
1333
+ doc: "Groups inside a section (prose, video, diff), stacked media, feed items (a hairline in the middle)."
1334
+ },
1335
+ {
1336
+ name: "space-section",
1337
+ tier: "semantic",
1338
+ type: "length",
1339
+ group: "Space",
1340
+ source: css("calc(var(--k-unit) * 24)"),
1341
+ doc: "Sections of a page (the intro, the updates, each PR), a hairline in the middle."
1342
+ },
1343
+ {
1344
+ name: "space-column",
1345
+ tier: "semantic",
1346
+ type: "length",
1347
+ group: "Space",
1348
+ source: css("calc(var(--k-unit) * 6)"),
1349
+ doc: "Columns: a contents rail and the page, media side by side."
1350
+ },
1351
+ {
1352
+ name: "pad-page",
1353
+ tier: "semantic",
1354
+ type: "length",
1355
+ group: "Space",
1356
+ source: css("calc(var(--k-unit) * 6)"),
1357
+ doc: "A page's edge; 16px under 640px wide."
1358
+ },
1359
+ {
1360
+ name: "pad-card",
1361
+ tier: "semantic",
1362
+ type: "length",
1363
+ group: "Space",
1364
+ source: css("calc(var(--k-unit) * 3)"),
1365
+ doc: "Inside a card or popover."
1366
+ },
1367
+ {
1368
+ name: "type-heading",
1369
+ tier: "semantic",
1370
+ type: "length",
1371
+ group: "Type",
1372
+ source: value("20px"),
1373
+ doc: "Section headings (a PR's title, under its kicker), weight 500."
1374
+ },
1375
+ {
1376
+ name: "leading-heading",
1377
+ tier: "semantic",
1378
+ type: "length",
1379
+ group: "Type",
1380
+ source: value("28px"),
1381
+ doc: "Line height for type-heading."
1382
+ },
1383
+ {
1384
+ name: "type-title",
1385
+ tier: "semantic",
1386
+ type: "length",
1387
+ group: "Type",
1388
+ source: value("16px"),
1389
+ doc: "A page's title, weight 500."
1390
+ },
1391
+ {
1392
+ name: "leading-title",
1393
+ tier: "semantic",
1394
+ type: "length",
1395
+ group: "Type",
1396
+ source: value("24px"),
1397
+ doc: "Line height for type-title."
1398
+ },
1399
+ {
1400
+ name: "type-body",
1401
+ tier: "semantic",
1402
+ type: "length",
1403
+ group: "Type",
1404
+ source: value("13px"),
1405
+ doc: "Prose, questions, list rows, step titles; 400, or 500 for emphasis."
1406
+ },
1407
+ {
1408
+ name: "leading-body",
1409
+ tier: "semantic",
1410
+ type: "length",
1411
+ group: "Type",
1412
+ source: value("20px"),
1413
+ doc: "Line height for type-body."
1414
+ },
1415
+ {
1416
+ name: "type-label",
1417
+ tier: "semantic",
1418
+ type: "length",
1419
+ group: "Type",
1420
+ source: value("12px"),
1421
+ doc: "Labels over media, weight 500; mono inside body text."
1422
+ },
1423
+ {
1424
+ name: "leading-label",
1425
+ tier: "semantic",
1426
+ type: "length",
1427
+ group: "Type",
1428
+ source: value("16px"),
1429
+ doc: "Line height for type-label."
1430
+ },
1431
+ {
1432
+ name: "type-small",
1433
+ tier: "semantic",
1434
+ type: "length",
1435
+ group: "Type",
1436
+ source: value("11px"),
1437
+ doc: "Meta, controls, keys, timestamps; mono inside labels."
1438
+ },
1439
+ {
1440
+ name: "leading-small",
1441
+ tier: "semantic",
1442
+ type: "length",
1443
+ group: "Type",
1444
+ source: value("16px"),
1445
+ doc: "Line height for type-small."
1446
+ },
1287
1447
  {
1288
1448
  name: "font-size",
1289
1449
  tier: "semantic",
@@ -2141,8 +2301,9 @@ ${register(["width"], "<length-percentage>", "288px")}
2141
2301
  @media(prefers-reduced-motion:reduce){:where(.knobs-chip,.knobs-ui){--k-token-t:0s}}`, aliased = (css) => css.replace(new RegExp(`var\\(--_k-(${ANIMATED.join("|")})\\)`, "g"), "var(--kv-$1)").replace(/var\(--k-blur\)/g, "var(--kv-blur)");
2142
2302
 
2143
2303
  // src/core/root.ts
2144
- var ROOT_CLASS = "knobs-root", extras = `:where(.${ROOT_CLASS}){--_k-shadow-k:calc(var(--k-shadow-strength,calc(.48 - .32 * var(--_k-light,0))) / .48);--_k-glass-tone:var(--k-glass-tone,calc(.5 + .58 * var(--_k-light,0)));--_k-fill-base:var(--k-fill-base,var(--_k-fill-selected));--_k-fill-hover-mix:var(--k-fill-hover-mix,var(--k-fill-rest-mix,20%));--_k-reveal-ease:var(--k-ease,cubic-bezier(.2,.8,.2,1))}`, spring = `:where(:root,:host){--k-fill-ease:${DEFAULT_LINEAR.easing};--k-fill-ms:${DEFAULT_LINEAR.ms}}`, rootCss = `${tokensCss(knobs)}
2304
+ var ROOT_CLASS = "knobs-root", extras = `:where(.${ROOT_CLASS}){--_k-shadow-k:calc(var(--k-shadow-strength,calc(.48 - .32 * var(--_k-light,0))) / .48);--_k-glass-tone:var(--k-glass-tone,calc(.5 + .58 * var(--_k-light,0)));--_k-fill-base:var(--k-fill-base,var(--_k-fill-selected));--_k-fill-hover-mix:var(--k-fill-hover-mix,var(--k-fill-rest-mix,20%));--_k-reveal-ease:var(--k-ease,cubic-bezier(.2,.8,.2,1))}`, narrow = `@media(max-width:639.98px){:where(.${ROOT_CLASS}){--_k-pad-page:var(--k-pad-page,calc(var(--_k-unit) * 4))}}`, spring = `:where(:root,:host){--k-fill-ease:${DEFAULT_LINEAR.easing};--k-fill-ms:${DEFAULT_LINEAR.ms}}`, rootCss = `${tokensCss(knobs)}
2145
2305
  ${extras}
2306
+ ${narrow}
2146
2307
  ${spring}
2147
2308
  ${DRIVERS}
2148
2309
  ${motionCss}`;
@@ -116,7 +116,7 @@ import {
116
116
  createHintController,
117
117
  pageHints,
118
118
  toggleLayer
119
- } from "./chunk-8mabjeyk.js";
119
+ } from "./chunk-0w1xk9jc.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 = {
@@ -11485,7 +11485,12 @@ var compareCss = `
11485
11485
  /* Glass: what's behind shows through, blurred, on the knobs tokens. */
11486
11486
  .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
11487
  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)))}
11488
- .knobs-cmp-label{position:absolute;left:0;top:0;padding:1px 7px;border-radius:999px;font-size:var(--_k-text-meta);line-height:16px;color:var(--_k-fg-2);white-space:nowrap;pointer-events:none;will-change:transform;transition:color calc(var(--_k-hover-out) * 1ms)}
11488
+ /* ✗ Before · ✓ After: Before quieter (fg-3), After in full text. Neutral: no colour of its own. */
11489
+ .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);
11490
+ white-space:nowrap;pointer-events:none;will-change:transform}
11491
+ .knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
11492
+ .knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
11493
+ .knobs-cmp-mark svg{display:block;width:12px;height:12px}
11489
11494
  /* The wipe: a hairline down the line with a raised grip at the pointer's height; a wider strip
11490
11495
  takes the hand and the keys (a slider). It fades with the mode; wiping, line and grip brighten. */
11491
11496
  .knobs-cmp-handle{cursor:ew-resize;position:absolute;left:-12px;top:0;width:24px;height:0;opacity:0;pointer-events:none;outline:none;will-change:transform;transition:opacity .18s cubic-bezier(.4,0,1,1)}
@@ -11798,6 +11803,12 @@ var css = `
11798
11803
  --_ku-pane-bg and --_ku-pane-ring so this reaches them. */
11799
11804
  :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui:not(.knobs-surface),.knobs-ui[data-nested]{--_ku-gap:var(--k-ui-gap,0px);--_ku-pane-bg:transparent;--_ku-pane-ring:none;
11800
11805
  --_ku-divider:var(--_k-split-gutter);--_kd-bg:transparent}
11806
+ /* What draws its box without those: a picker's card keeps its fill as the highlight but drops its
11807
+ ring; a timeline's track drops its well for a hairline under the lane; an inline panel sheds its
11808
+ card (--k-chrome 0). A full-window step is the one raised exception (it is the surface). */
11809
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-picker-card,.knobs-ui[data-nested] .knobs-picker-card{box-shadow:none}
11810
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-tl-track,.knobs-ui[data-nested] .knobs-tl-track{background:transparent;border-radius:0;box-shadow:inset 0 -1px 0 var(--_k-border-subtle)}
11811
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-panel.knobs-inline:not(.knobs-page){--k-chrome:0}
11801
11812
  /* Only the pieces' own elements take their type and resets: what you put in a pane is yours. */
11802
11813
  .knobs-ui{box-sizing:border-box}
11803
11814
  :where(.knobs-split>*,.knobs-pane-clip,.knobs-pane-inner,.knobs-pane-body,.knobs-pane-head,.knobs-pane-head *,.knobs-pane-rail,.knobs-pane-rail *,.knobs-tree,.knobs-tree *,.knobs-tree-ghost){box-sizing:border-box}
@@ -9,7 +9,7 @@ import {
9
9
  writeSession,
10
10
  injectStyles,
11
11
  toggleLayer
12
- } from "./chunk-8mabjeyk.js";
12
+ } from "./chunk-0w1xk9jc.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("zwj6yngd"), import("./chunk-zwj6yngd.js"));
88
+ let { createPanel } = await (__preload("76frnh2j"), import("./chunk-76frnh2j.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,["jfxt9kfq","zwj6yngd","j6zmtens","3ygqgy1x","8mabjeyk"],[["./index.js",3],["./chunk-zwj6yngd.js",3,4],["./chunk-j6zmtens.js",3,4],["./chunk-3ygqgy1x.js"],["./chunk-8mabjeyk.js",3]],0);
79
+ __chunks(import.meta.url,["0ytb6x87","76frnh2j","rem3qrhz","3ygqgy1x","0w1xk9jc"],[["./index.js",3],["./chunk-76frnh2j.js",3,4],["./chunk-rem3qrhz.js",3,4],["./chunk-3ygqgy1x.js"],["./chunk-0w1xk9jc.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("j6zmtens"), import("./chunk-j6zmtens.js")).then((m) => m.attach);
1381
+ let load = options.ship ? () => options.ship().then((m) => m.attach) : () => (__preload("rem3qrhz"), import("./chunk-rem3qrhz.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("zwj6yngd"), import("./chunk-zwj6yngd.js")).then((m) => m.mountPanel);
1385
+ let load = options.ship ? () => options.ship().then((m) => m.mountPanel) : () => (__preload("76frnh2j"), import("./chunk-76frnh2j.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
@@ -1746,6 +1746,22 @@ var init_schema = __esm(() => {
1746
1746
  source: css("var(--k-warn)"),
1747
1747
  doc: "Modified lines."
1748
1748
  },
1749
+ {
1750
+ name: "before",
1751
+ tier: "semantic",
1752
+ type: "color",
1753
+ group: "Data",
1754
+ source: css("var(--k-fg-3)"),
1755
+ doc: "Before in a comparison, and only Before: its label and side of the seam. Neutral unless a theme gives it a colour."
1756
+ },
1757
+ {
1758
+ name: "after",
1759
+ tier: "semantic",
1760
+ type: "color",
1761
+ group: "Data",
1762
+ source: css("var(--k-fg)"),
1763
+ doc: "After in a comparison, and only After."
1764
+ },
1749
1765
  {
1750
1766
  name: "series-1",
1751
1767
  tier: "semantic",
@@ -1836,6 +1852,150 @@ var init_schema = __esm(() => {
1836
1852
  source: value("288px"),
1837
1853
  doc: "Panel width."
1838
1854
  },
1855
+ {
1856
+ name: "space-inside",
1857
+ tier: "semantic",
1858
+ type: "length",
1859
+ group: "Space",
1860
+ source: css("var(--k-unit)"),
1861
+ doc: "Parts of one thing: an icon and its label, a meta line and its title."
1862
+ },
1863
+ {
1864
+ name: "space-sibling",
1865
+ tier: "semantic",
1866
+ type: "length",
1867
+ group: "Space",
1868
+ source: css("calc(var(--k-unit) * 2)"),
1869
+ doc: "Siblings in a group: buttons in a row, a list item's lines."
1870
+ },
1871
+ {
1872
+ name: "space-title",
1873
+ tier: "semantic",
1874
+ type: "length",
1875
+ group: "Space",
1876
+ source: css("calc(var(--k-unit) * 3)"),
1877
+ doc: "A heading and what it heads."
1878
+ },
1879
+ {
1880
+ name: "space-group",
1881
+ tier: "semantic",
1882
+ type: "length",
1883
+ group: "Space",
1884
+ source: css("calc(var(--k-unit) * 12)"),
1885
+ doc: "Groups inside a section (prose, video, diff), stacked media, feed items (a hairline in the middle)."
1886
+ },
1887
+ {
1888
+ name: "space-section",
1889
+ tier: "semantic",
1890
+ type: "length",
1891
+ group: "Space",
1892
+ source: css("calc(var(--k-unit) * 24)"),
1893
+ doc: "Sections of a page (the intro, the updates, each PR), a hairline in the middle."
1894
+ },
1895
+ {
1896
+ name: "space-column",
1897
+ tier: "semantic",
1898
+ type: "length",
1899
+ group: "Space",
1900
+ source: css("calc(var(--k-unit) * 6)"),
1901
+ doc: "Columns: a contents rail and the page, media side by side."
1902
+ },
1903
+ {
1904
+ name: "pad-page",
1905
+ tier: "semantic",
1906
+ type: "length",
1907
+ group: "Space",
1908
+ source: css("calc(var(--k-unit) * 6)"),
1909
+ doc: "A page's edge; 16px under 640px wide."
1910
+ },
1911
+ {
1912
+ name: "pad-card",
1913
+ tier: "semantic",
1914
+ type: "length",
1915
+ group: "Space",
1916
+ source: css("calc(var(--k-unit) * 3)"),
1917
+ doc: "Inside a card or popover."
1918
+ },
1919
+ {
1920
+ name: "type-heading",
1921
+ tier: "semantic",
1922
+ type: "length",
1923
+ group: "Type",
1924
+ source: value("20px"),
1925
+ doc: "Section headings (a PR's title, under its kicker), weight 500."
1926
+ },
1927
+ {
1928
+ name: "leading-heading",
1929
+ tier: "semantic",
1930
+ type: "length",
1931
+ group: "Type",
1932
+ source: value("28px"),
1933
+ doc: "Line height for type-heading."
1934
+ },
1935
+ {
1936
+ name: "type-title",
1937
+ tier: "semantic",
1938
+ type: "length",
1939
+ group: "Type",
1940
+ source: value("16px"),
1941
+ doc: "A page's title, weight 500."
1942
+ },
1943
+ {
1944
+ name: "leading-title",
1945
+ tier: "semantic",
1946
+ type: "length",
1947
+ group: "Type",
1948
+ source: value("24px"),
1949
+ doc: "Line height for type-title."
1950
+ },
1951
+ {
1952
+ name: "type-body",
1953
+ tier: "semantic",
1954
+ type: "length",
1955
+ group: "Type",
1956
+ source: value("13px"),
1957
+ doc: "Prose, questions, list rows, step titles; 400, or 500 for emphasis."
1958
+ },
1959
+ {
1960
+ name: "leading-body",
1961
+ tier: "semantic",
1962
+ type: "length",
1963
+ group: "Type",
1964
+ source: value("20px"),
1965
+ doc: "Line height for type-body."
1966
+ },
1967
+ {
1968
+ name: "type-label",
1969
+ tier: "semantic",
1970
+ type: "length",
1971
+ group: "Type",
1972
+ source: value("12px"),
1973
+ doc: "Labels over media, weight 500; mono inside body text."
1974
+ },
1975
+ {
1976
+ name: "leading-label",
1977
+ tier: "semantic",
1978
+ type: "length",
1979
+ group: "Type",
1980
+ source: value("16px"),
1981
+ doc: "Line height for type-label."
1982
+ },
1983
+ {
1984
+ name: "type-small",
1985
+ tier: "semantic",
1986
+ type: "length",
1987
+ group: "Type",
1988
+ source: value("11px"),
1989
+ doc: "Meta, controls, keys, timestamps; mono inside labels."
1990
+ },
1991
+ {
1992
+ name: "leading-small",
1993
+ tier: "semantic",
1994
+ type: "length",
1995
+ group: "Type",
1996
+ source: value("16px"),
1997
+ doc: "Line height for type-small."
1998
+ },
1839
1999
  {
1840
2000
  name: "font-size",
1841
2001
  tier: "semantic",
@@ -2800,16 +2960,18 @@ function injectTokens(doc) {
2800
2960
  style.textContent = rootCss;
2801
2961
  doc.head.append(style);
2802
2962
  }
2803
- var ROOT_CLASS = "knobs-root", extras, spring, rootCss;
2963
+ var ROOT_CLASS = "knobs-root", extras, narrow, spring, rootCss;
2804
2964
  var init_root = __esm(() => {
2805
2965
  init_knobs();
2806
2966
  init_css();
2807
2967
  init_glide();
2808
2968
  init_motion();
2809
2969
  extras = `:where(.${ROOT_CLASS}){--_k-shadow-k:calc(var(--k-shadow-strength,calc(.48 - .32 * var(--_k-light,0))) / .48);--_k-glass-tone:var(--k-glass-tone,calc(.5 + .58 * var(--_k-light,0)));--_k-fill-base:var(--k-fill-base,var(--_k-fill-selected));--_k-fill-hover-mix:var(--k-fill-hover-mix,var(--k-fill-rest-mix,20%));--_k-reveal-ease:var(--k-ease,cubic-bezier(.2,.8,.2,1))}`;
2970
+ narrow = `@media(max-width:639.98px){:where(.${ROOT_CLASS}){--_k-pad-page:var(--k-pad-page,calc(var(--_k-unit) * 4))}}`;
2810
2971
  spring = `:where(:root,:host){--k-fill-ease:${DEFAULT_LINEAR.easing};--k-fill-ms:${DEFAULT_LINEAR.ms}}`;
2811
2972
  rootCss = `${tokensCss(knobs)}
2812
2973
  ${extras}
2974
+ ${narrow}
2813
2975
  ${spring}
2814
2976
  ${DRIVERS}
2815
2977
  ${motionCss}`;
@@ -20946,7 +21108,12 @@ var compareCss = `
20946
21108
  /* Glass: what's behind shows through, blurred, on the knobs tokens. */
20947
21109
  .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);
20948
21110
  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)))}
20949
- .knobs-cmp-label{position:absolute;left:0;top:0;padding:1px 7px;border-radius:999px;font-size:var(--_k-text-meta);line-height:16px;color:var(--_k-fg-2);white-space:nowrap;pointer-events:none;will-change:transform;transition:color calc(var(--_k-hover-out) * 1ms)}
21111
+ /* ✗ Before · ✓ After: Before quieter (fg-3), After in full text. Neutral: no colour of its own. */
21112
+ .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);
21113
+ white-space:nowrap;pointer-events:none;will-change:transform}
21114
+ .knobs-cmp-label[data-side="before"]{color:var(--_k-fg-3)}
21115
+ .knobs-cmp-label[data-side="after"]{color:var(--_k-fg)}
21116
+ .knobs-cmp-mark svg{display:block;width:12px;height:12px}
20950
21117
  /* The wipe: a hairline down the line with a raised grip at the pointer's height; a wider strip
20951
21118
  takes the hand and the keys (a slider). It fades with the mode; wiping, line and grip brighten. */
20952
21119
  .knobs-cmp-handle{cursor:ew-resize;position:absolute;left:-12px;top:0;width:24px;height:0;opacity:0;pointer-events:none;outline:none;will-change:transform;transition:opacity .18s cubic-bezier(.4,0,1,1)}
@@ -21316,6 +21483,12 @@ var css3 = `
21316
21483
  --_ku-pane-bg and --_ku-pane-ring so this reaches them. */
21317
21484
  :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-ui:not(.knobs-surface),.knobs-ui[data-nested]{--_ku-gap:var(--k-ui-gap,0px);--_ku-pane-bg:transparent;--_ku-pane-ring:none;
21318
21485
  --_ku-divider:var(--_k-split-gutter);--_kd-bg:transparent}
21486
+ /* What draws its box without those: a picker's card keeps its fill as the highlight but drops its
21487
+ ring; a timeline's track drops its well for a hairline under the lane; an inline panel sheds its
21488
+ card (--k-chrome 0). A full-window step is the one raised exception (it is the surface). */
21489
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-picker-card,.knobs-ui[data-nested] .knobs-picker-card{box-shadow:none}
21490
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-tl-track,.knobs-ui[data-nested] .knobs-tl-track{background:transparent;border-radius:0;box-shadow:inset 0 -1px 0 var(--_k-border-subtle)}
21491
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-panel.knobs-inline:not(.knobs-page){--k-chrome:0}
21319
21492
  /* Only the pieces' own elements take their type and resets: what you put in a pane is yours. */
21320
21493
  .knobs-ui{box-sizing:border-box}
21321
21494
  :where(.knobs-split>*,.knobs-pane-clip,.knobs-pane-inner,.knobs-pane-body,.knobs-pane-head,.knobs-pane-head *,.knobs-pane-rail,.knobs-pane-rail *,.knobs-tree,.knobs-tree *,.knobs-tree-ghost){box-sizing:border-box}