@kitlangton/knobs 0.4.0-next.1 → 0.4.0-next.11

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.
Files changed (53) hide show
  1. package/README.md +191 -13
  2. package/dist/dev/{chunk-ayb292zj.js → chunk-75gbzbyd.js} +3 -3
  3. package/dist/dev/{chunk-1qzffbpa.js → chunk-bnhzf1tj.js} +240 -87
  4. package/dist/dev/{chunk-8mabjeyk.js → chunk-j57rhjtm.js} +166 -2
  5. package/dist/dev/{chunk-3ygqgy1x.js → chunk-mjdgzad5.js} +15 -2
  6. package/dist/dev/index.js +37 -14
  7. package/dist/index.js +46 -11
  8. package/dist/panel.js +408 -73
  9. package/dist/review-react.js +18580 -0
  10. package/dist/review-solid.js +18482 -0
  11. package/dist/review.js +18488 -0
  12. package/dist/themes.js +281 -23
  13. package/dist/types/core/remote.d.ts +2 -0
  14. package/dist/types/core/schema.d.ts +13 -1
  15. package/dist/types/core/store.d.ts +8 -0
  16. package/dist/types/panel/gpu/renderer.d.ts +2 -0
  17. package/dist/types/panel/presets.d.ts +7 -1
  18. package/dist/types/review/font.d.ts +13 -0
  19. package/dist/types/review/foundations.d.ts +175 -0
  20. package/dist/types/review/index.d.ts +13 -0
  21. package/dist/types/review/react.d.ts +1 -0
  22. package/dist/types/review/solid.d.ts +1 -0
  23. package/dist/types/themes/index.d.ts +7 -2
  24. package/dist/types/themes/review.d.ts +8 -0
  25. package/dist/types/tokens/schema.d.ts +180 -0
  26. package/dist/types/ui/compare-css.d.ts +1 -1
  27. package/dist/types/ui/compare-gpu.d.ts +6 -6
  28. package/dist/types/ui/compare-model.d.ts +85 -2
  29. package/dist/types/ui/compare.d.ts +18 -4
  30. package/dist/types/ui/diff.d.ts +4 -5
  31. package/dist/types/ui/history-css.d.ts +1 -0
  32. package/dist/types/ui/history-model.d.ts +249 -0
  33. package/dist/types/ui/history.d.ts +38 -0
  34. package/dist/types/ui/icons.d.ts +40 -0
  35. package/dist/types/ui/index.d.ts +8 -4
  36. package/dist/types/ui/player-css.d.ts +1 -0
  37. package/dist/types/ui/player.d.ts +51 -0
  38. package/dist/types/ui/review-look.d.ts +6 -0
  39. package/dist/types/ui/review-options.d.ts +28 -0
  40. package/dist/types/ui/selection-pill-css.d.ts +1 -0
  41. package/dist/types/ui/selection-pill.d.ts +106 -0
  42. package/dist/types/ui/timeline-model.d.ts +27 -0
  43. package/dist/types/ui/timeline.d.ts +16 -1
  44. package/dist/types/ui/tree.d.ts +3 -2
  45. package/dist/types/ui/walkthrough-annotations.d.ts +42 -0
  46. package/dist/types/ui/walkthrough-blocks.d.ts +87 -0
  47. package/dist/types/ui/walkthrough-media.d.ts +30 -1
  48. package/dist/types/ui/walkthrough-model.d.ts +469 -5
  49. package/dist/types/ui/walkthrough.d.ts +27 -5
  50. package/dist/ui-react.js +5124 -1031
  51. package/dist/ui-solid.js +5136 -1043
  52. package/dist/ui.js +6411 -2269
  53. package/package.json +13 -1
@@ -44,7 +44,7 @@ import {
44
44
  pushReadout,
45
45
  READOUT,
46
46
  storageKey
47
- } from "./chunk-3ygqgy1x.js";
47
+ } from "./chunk-mjdgzad5.js";
48
48
  import {
49
49
  resolveLook,
50
50
  themeOf,
@@ -116,7 +116,7 @@ import {
116
116
  createHintController,
117
117
  pageHints,
118
118
  toggleLayer
119
- } from "./chunk-8mabjeyk.js";
119
+ } from "./chunk-j57rhjtm.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 = {
@@ -994,6 +994,9 @@ function resolveActivePresetEntry(entries, currentValues, lastAppliedId, baseId
994
994
  isCustom: !0
995
995
  };
996
996
  }
997
+ function presetsTouch(entries, keys, defaults) {
998
+ return entries.some((e) => !(e.kind === "preset" && e.rawId === "default") && keys.some((k) => !same(e.values[k], defaults[k])));
999
+ }
997
1000
  function nextPresetIndex(count, activeIndex, direction) {
998
1001
  if (count <= 0)
999
1002
  return -1;
@@ -1001,7 +1004,7 @@ function nextPresetIndex(count, activeIndex, direction) {
1001
1004
  return direction > 0 ? 0 : count - 1;
1002
1005
  return ((activeIndex + direction) % count + count) % count;
1003
1006
  }
1004
- function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id) => controls.presetLabel(id)) {
1007
+ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id) => controls.presetLabel(id), relevant) {
1005
1008
  let wake = () => onAfterChange?.(), el = (tag, cls, text) => {
1006
1009
  let node = doc.createElement(tag);
1007
1010
  if (cls)
@@ -1018,7 +1021,13 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1018
1021
  saveButton.type = "button", saveButton.tabIndex = -1, saveButton.setAttribute("aria-label", "Save as variant"), saveButton.setAttribute("aria-keyshortcuts", "Shift+S"), saveButton.dataset.hint = "Save", saveButton.onclick = () => run(save);
1019
1022
  if (track.append(pill), root.append(track), saveButton)
1020
1023
  root.append(saveButton);
1021
- let pillEdges = new Edges, pillY = new Spring(0, 28, 0.86), pillAlpha = new Spring(1, 26, 0.9), entries = [], buttons = [], signature = "", lastVariantsRef = null, lastAppliedId = null, activeState = { activeId: "p:default", activeIndex: 0, isCustom: !1 }, prevActiveId = "", prevActiveIndex = -2, prevIsCustom = !1, prevDrifted = !1, initializedPill = !1, slideUntil = 0;
1024
+ let pillEdges = new Edges, pillY = new Spring(0, 28, 0.86), pillAlpha = new Spring(1, 26, 0.9), entries = [], buttons = [], signature = "", lastVariantsRef = null, lastAppliedId = null, activeState = { activeId: "p:default", activeIndex: 0, isCustom: !1 }, prevActiveId = "", prevActiveIndex = -2, prevIsCustom = !1, prevDrifted = !1, initializedPill = !1, slideUntil = 0, unpinned = (values) => {
1025
+ let out;
1026
+ for (let k of Object.keys(values))
1027
+ if (controls.isPinned(k) && !same(values[k], controls.locked[k]))
1028
+ out ??= { ...values }, out[k] = controls.locked[k];
1029
+ return out ?? values;
1030
+ };
1022
1031
  function buildEntries() {
1023
1032
  let presetEntries = Object.entries(controls.presets).map(([id, presetValues]) => ({
1024
1033
  id: `p:${id}`,
@@ -1035,7 +1044,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1035
1044
  kind: "variant",
1036
1045
  rawId: v.id,
1037
1046
  label: v.name ?? v.id.slice(0, 8),
1038
- values: v.values,
1047
+ values: unpinned(v.values),
1039
1048
  apply: (coalesce) => controls.variants.load(v.id, { coalesce })
1040
1049
  }));
1041
1050
  return [...presetEntries, ...variantEntries];
@@ -1070,7 +1079,12 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1070
1079
  if (pill.style.left = `${left}px`, pill.style.right = `${box.trackWidth - right}px`, pill.style.top = `${top}px`, pill.style.height = `${box.height}px`, pill.style.opacity = activeState.drifted ? String(DRIFTED_PILL) : "1", pill.style.scale = "", first)
1071
1080
  doc.defaultView?.requestAnimationFrame(() => pill.style.transition = "");
1072
1081
  }
1073
- let resolve = () => resolveActivePresetEntry(entries, controls.get(), lastAppliedId, activeEntryId(controls));
1082
+ let resolve = () => resolveActivePresetEntry(entries, unpinned(controls.get()), lastAppliedId, activeEntryId(controls)), wanted = (list) => (list.length > 1 || showVariants) && (relevant?.(list) ?? !0);
1083
+ function syncShown() {
1084
+ let show = wanted(entries);
1085
+ if (root.hidden === show && root.dataset.presence === void 0)
1086
+ root.hidden = !show;
1087
+ }
1074
1088
  function syncSelection(snap = !1) {
1075
1089
  if (activeState = resolve(), lastAppliedId !== null && activeState.activeId !== lastAppliedId)
1076
1090
  lastAppliedId = null;
@@ -1110,12 +1124,12 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1110
1124
  run(() => {
1111
1125
  let variantsRef = showVariants ? controls.variants.list() : null;
1112
1126
  if (signature !== "" && variantsRef === lastVariantsRef) {
1113
- syncSelection(!1);
1127
+ syncShown(), syncSelection(!1);
1114
1128
  return;
1115
1129
  }
1116
1130
  lastVariantsRef = variantsRef;
1117
- let nextEntries = buildEntries(), nextSig = JSON.stringify(nextEntries.map((e) => [e.id, e.label])), shouldShow = nextEntries.length > 1 || showVariants;
1118
- if (root.hidden = !shouldShow, nextSig !== signature)
1131
+ let nextEntries = buildEntries(), nextSig = JSON.stringify(nextEntries.map((e) => [e.id, e.label]));
1132
+ if (entries = nextEntries, syncShown(), nextSig !== signature)
1119
1133
  signature = nextSig, entries = nextEntries, buttons = entries.map((entry, idx) => {
1120
1134
  let b = el("button", "knobs-preset-chip", entry.label);
1121
1135
  return b.type = "button", b.tabIndex = -1, b.dataset.presetId = entry.id, b.dataset.presetKind = entry.kind, b.dataset.presetName = entry.label, b.setAttribute("role", "radio"), b.setAttribute("aria-label", entry.label), b.onclick = (e) => {
@@ -1286,6 +1300,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
1286
1300
  getEntries: () => entries,
1287
1301
  getButtons: () => buttons,
1288
1302
  getActiveState: () => activeState,
1303
+ shows: () => wanted(entries),
1289
1304
  cycle,
1290
1305
  selectById,
1291
1306
  save: () => {
@@ -3762,7 +3777,9 @@ class GpuOverlayManager {
3762
3777
  let ro = new ResizeObserver(() => {
3763
3778
  this.wake();
3764
3779
  });
3765
- ro.observe(panel.root), ro.observe(panel.body), ro.observe(panel.presetStrip.track);
3780
+ if (ro.observe(panel.root), ro.observe(panel.body), panel.pinned)
3781
+ ro.observe(panel.pinned);
3782
+ ro.observe(panel.presetStrip.track);
3766
3783
  let mo = new MutationObserver(() => {
3767
3784
  this.styleVersion++, this.wake();
3768
3785
  });
@@ -5838,8 +5855,9 @@ class GpuOverlayManager {
5838
5855
  ], this.maskCount * MASK_FLOATS), this.curMask = ++this.maskCount;
5839
5856
  }
5840
5857
  }
5841
- let ps = panel.presetStrip, hinting = panel.root.classList.contains("knobs-hinting");
5858
+ let ps = panel.presetStrip, hinting = panel.root.classList.contains("knobs-hinting"), stripAlpha = this.curAlpha;
5842
5859
  if (!ps.element.hidden) {
5860
+ this.curAlpha = stripAlpha * this.presence(ps.element);
5843
5861
  let psHovered = this.curPanelHovered && ps.element.matches(":hover");
5844
5862
  this.curSubHovered = psHovered;
5845
5863
  let psRect = this.rectOf(ps.element), trackRect = this.rectOf(ps.track);
@@ -5941,6 +5959,7 @@ class GpuOverlayManager {
5941
5959
  align: "center"
5942
5960
  });
5943
5961
  }
5962
+ this.curAlpha = stripAlpha;
5944
5963
  }
5945
5964
  let tabs = panel.tabs;
5946
5965
  if (tabs && !tabs.element.hidden)
@@ -6042,7 +6061,7 @@ class GpuOverlayManager {
6042
6061
  }
6043
6062
  if (bodyRevealing || pageMoving)
6044
6063
  this.curBypassRectCache = !0;
6045
- let bodyHovered = this.curPanelHovered && panel.body.matches(":hover");
6064
+ let bodyHovered = this.curPanelHovered && (panel.body.matches(":hover") || Boolean(panel.pinned?.matches(":hover")));
6046
6065
  this.curSubHovered = bodyHovered, this.curPointerRow = bodyHovered && panel.root.hasAttribute("data-k-row-hover");
6047
6066
  let panelAlpha = this.curAlpha;
6048
6067
  for (let g of panel.groups) {
@@ -11446,14 +11465,23 @@ var compareCss = `
11446
11465
  .knobs-ui.knobs-compare{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));--_ku-cmp-line:var(--_k-compare-divider);
11447
11466
  --_ku-cmp-stage:var(--_k-compare-stage);
11448
11467
  display:flex;flex-direction:column;gap:6px;width:100%;height:100%;min-height:160px;outline:none;user-select:none;-webkit-user-select:none;
11449
- font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
11468
+ font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
11450
11469
  :where(.knobs-ui) :is(.knobs-cmp-mode,.knobs-cmp-toggle){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}
11451
- .knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:2px;height:24px;padding:0 2px}
11452
- .knobs-cmp-modes{display:flex;align-items:center;gap:2px}
11453
- .knobs-ui .knobs-cmp-mode,.knobs-ui .knobs-cmp-toggle{height:22px;padding:0 7px;border-radius:max(0px,calc(var(--_k-radius) - 2px));color:var(--_k-fg-3);cursor:pointer;white-space:nowrap;
11454
- transition:color .15s,background .15s}
11455
- .knobs-ui .knobs-cmp-mode:is(:hover,:focus-visible),.knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){color:var(--_k-fg);background:var(--_ku-wash)}
11456
- .knobs-ui .knobs-cmp-mode[aria-checked="true"],.knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-fg);font-weight:var(--_k-weight-label)}
11470
+ .knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:var(--_k-gap);height:var(--_k-control-h)}
11471
+ /* The modes: one fill, the selection pill on the one picked; a mode isn't brighter for being
11472
+ picked: the pill's bright copy is. */
11473
+ .knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
11474
+ .knobs-cmp-modes[hidden]{display:none}
11475
+ /* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
11476
+ .knobs-ui .knobs-cmp-mode{display:inline-flex;align-items:center;height:100%;padding:0 8px;border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;white-space:nowrap;
11477
+ transition:background calc(var(--_k-hover-out) * 1ms)}
11478
+ .knobs-ui .knobs-cmp-mode:not([aria-checked="true"]):is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
11479
+ /* Toggles: icon buttons, the accent's wash when on. */
11480
+ .knobs-ui .knobs-cmp-toggle{display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);border-radius:var(--_k-radius);color:var(--_k-fg-2);cursor:default;
11481
+ transition:background calc(var(--_k-hover-out) * 1ms)}
11482
+ .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
11483
+ .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
11484
+ .knobs-cmp-toggle svg{display:block}
11457
11485
  .knobs-cmp-fill{flex:1}
11458
11486
  .knobs-cmp-toggle[hidden]{display:none}
11459
11487
  /* One surface: on a card (a walkthrough's step, a split's pane, anything .knobs-surface) or marked
@@ -11466,49 +11494,96 @@ var compareCss = `
11466
11494
  .knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
11467
11495
  .knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
11468
11496
  /* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
11469
- .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
11470
- .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
11471
- .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:ew-resize!important}
11497
+ /* Zoomed, it pans: a move cursor, never a hand. */
11498
+ .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:move;touch-action:none}
11499
+ .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:move!important}
11500
+ /* Wiping, the grip stands in for the cursor. */
11501
+ .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
11472
11502
  .knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
11473
11503
  .knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
11474
11504
  .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}
11475
11505
  .knobs-compare[data-renderer="gpu"] .knobs-cmp-media{opacity:0}
11476
- /* Glass: what's behind shows through, blurred, on the knobs tokens. */
11477
- .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);
11478
- 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)))}
11479
- .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 .15s}
11480
- .knobs-cmp-label[data-lit]{color:var(--_k-fg)}
11481
- .knobs-cmp-label[data-dim]{color:var(--_k-fg-3)}
11482
- /* The wipe: a hairline down the line with a grip at its middle; a wider strip takes the hand and
11483
- the keys (a slider). It fades with the mode. */
11484
- .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)}
11485
- .knobs-cmp-handle[data-on]{opacity:1;pointer-events:auto;transition:opacity .22s cubic-bezier(.2,.8,.2,1)}
11486
- .knobs-cmp-handle::before{content:"";position:absolute;top:0;bottom:0;left:11.5px;width:1px;background:var(--_ku-cmp-line);box-shadow:0 0 0 .5px rgba(0,0,0,.28),0 0 6px rgba(0,0,0,.25)}
11487
- .knobs-cmp-handle::after{content:"";position:absolute;top:50%;left:9px;width:6px;height:28px;margin-top:-14px;border-radius:3px;background:var(--_ku-cmp-line);
11488
- box-shadow:0 1px 3px rgba(0,0,0,.35),0 0 0 .5px rgba(0,0,0,.2);transition:scale .18s var(--_ku-ease),box-shadow .15s}
11489
- .knobs-compare[data-dragging] .knobs-cmp-handle::after{scale:1.12}
11490
- .knobs-cmp-handle:focus-visible::after,.knobs-compare[data-keyboard] .knobs-cmp-handle:focus::after{box-shadow:0 1px 3px rgba(0,0,0,.35),0 0 0 3px color-mix(in srgb,var(--_k-accent) 40%,transparent)}
11506
+ /* The media comes in with its first frames: a fade and a 4px blur on the default spring. */
11507
+ .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)}
11508
+ .knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}
11509
+ /* Over the pictures: a quiet scrim, dark in either appearance, blurred, with no ring. */
11510
+ .knobs-cmp-label,.knobs-cmp-zoom,.knobs-cmp-grip{background:var(--_ku-media-scrim);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}
11511
+ /* ✗ Before · ✓ After: Before quieter, After in full. Neutral: no colour of its own. */
11512
+ .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-weight:var(--_k-weight-label);
11513
+ white-space:nowrap;pointer-events:none;will-change:transform}
11514
+ .knobs-cmp-label[data-side="before"]{color:var(--_ku-on-media-2)}
11515
+ .knobs-cmp-label[data-side="after"]{color:var(--_ku-on-media)}
11516
+ /* Fusing, the mark folds away and the label is its name. */
11517
+ .knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}
11518
+ .knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}
11519
+ /* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */
11520
+ .knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;
11521
+ -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)}
11522
+ /* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height
11523
+ while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the
11524
+ fuse; wiping, line and grip brighten. */
11525
+ .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}
11526
+ .knobs-cmp-handle[data-on]{pointer-events:auto}
11527
+ .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 6px var(--_ku-media-scrim)}
11528
+ .knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
11529
+ .knobs-cmp-grip{position:absolute;top:0;left:4px;width:16px;height:28px;border-radius:min(8px,calc(var(--_k-radius) * 999));will-change:transform}
11530
+ .knobs-cmp-grip::before,.knobs-cmp-grip::after{content:"";position:absolute;top:10px;width:1px;height:8px;background:var(--_ku-on-media-2);transition:background calc(var(--_k-hover-out) * 1ms)}
11531
+ .knobs-cmp-grip::before{left:6px}
11532
+ .knobs-cmp-grip::after{left:9px}
11533
+ .knobs-compare[data-dragging] .knobs-cmp-grip::before,.knobs-compare[data-dragging] .knobs-cmp-grip::after{background:var(--_ku-on-media)}
11534
+ .knobs-cmp-handle:focus-visible .knobs-cmp-grip,.knobs-compare[data-keyboard] .knobs-cmp-handle:focus .knobs-cmp-grip{box-shadow:0 0 0 2px var(--_k-accent)}
11491
11535
  /* The split's hairline, down the middle side by side. */
11492
11536
  .knobs-cmp-split{position:absolute;left:0;top:0;width:1px;height:0;margin-left:-.5px;background:color-mix(in srgb,var(--_k-bg) 72%,transparent);pointer-events:none;will-change:transform,opacity}
11493
11537
  /* Zoomed: how far, and a way back, in the stage's corner; it fades in and out, nothing shifts. */
11494
11538
  .knobs-cmp-zoom{position:absolute;right:8px;bottom:8px;display:flex;align-items:center;gap:4px;height:26px;padding:0 3px 0 10px;border-radius:999px;
11495
- color:var(--_k-fg);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";white-space:nowrap;
11539
+ color:var(--_ku-on-media);font-variant-numeric:tabular-nums;font-feature-settings:var(--_kr-data,"tnum");white-space:nowrap;
11496
11540
  opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
11497
11541
  .knobs-cmp-zoom[data-on]{opacity:1;scale:1;pointer-events:auto;transition:opacity .2s cubic-bezier(.2,.8,.2,1),scale .2s cubic-bezier(.2,.8,.2,1)}
11498
11542
  :where(.knobs-ui) .knobs-cmp-zoom-reset{all:revert;box-sizing:border-box;font:inherit;letter-spacing:inherit;margin:0;border:0;outline:none;box-shadow:none;
11499
- height:20px;padding:0 8px;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:pointer;transition:color .15s,background .15s}
11500
- :where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){color:var(--_k-fg);background:color-mix(in srgb,var(--_k-fg) 12%,transparent)}
11501
- .knobs-cmp-note{position:absolute;left:50%;top:50%;translate:-50% -50%;color:var(--_k-fg-3);font-size:var(--_k-text-meta);pointer-events:none}
11543
+ display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border-radius:999px;background:transparent;color:var(--_ku-on-media-2);cursor:default;transition:background calc(var(--_k-hover-out) * 1ms),color calc(var(--_k-hover-out) * 1ms)}
11544
+ :where(.knobs-ui) .knobs-cmp-zoom-reset:is(:hover,:focus-visible){background:color-mix(in srgb,var(--_ku-on-media) 16%,transparent);color:var(--_ku-on-media);transition-duration:calc(var(--_k-hover-in) * 1ms)}
11545
+ .knobs-cmp-note{position:absolute;left:50%;top:50%;translate:-50% -50%;color:var(--_k-fg-3);pointer-events:none}
11502
11546
  .knobs-cmp-note:empty{display:none}
11503
11547
  .knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
11504
- .knobs-cmp-timeline-host{flex:none;height:56px;min-height:0}
11548
+ .knobs-cmp-timeline-host{flex:none;min-height:0}
11505
11549
  /* The frame strip: Before over After per column, a difference bar under them, a dot where it peaks. */
11506
11550
  .knobs-ui.knobs-cmp-strip{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));position:relative;width:100%;height:100%;min-height:40px;overflow:hidden;cursor:col-resize;touch-action:none;
11507
- font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg)}
11551
+ font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);color:var(--_k-fg)}
11508
11552
  .knobs-cmp-strip>canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
11509
11553
  @media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}
11510
11554
  `;
11511
11555
 
11556
+ // src/ui/player-css.ts
11557
+ var playerCss = `
11558
+ :where(.knobs-player,.knobs-player *){box-sizing:border-box}
11559
+ .knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
11560
+ font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
11561
+ :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)}
11562
+ .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)}
11563
+ .knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
11564
+ .knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
11565
+ 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)}
11566
+ .knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
11567
+ .knobs-player-line{flex:none;min-height:0}
11568
+ /* The timeline's own stylesheet is look.ts's; what the player's row and every timeline share
11569
+ about the hand is set here, after it. No hand cursor anywhere (the arrow, or a resize where it
11570
+ resizes); hover is a quiet fill (fill-hover), in at once (hover-in, 0 ms), out easing. */
11571
+ .knobs-ui .knobs-tl-play,.knobs-ui.knobs-timeline :is(.knobs-tl-pin,.knobs-tl-rlabel),
11572
+ .knobs-ui.knobs-timeline[data-dragging] .knobs-tl-marker :is(.knobs-tl-pin,.knobs-tl-mlabel){cursor:default}
11573
+ .knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
11574
+ .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
11575
+ /* The line's row: a minimal play/pause (the filled pair, a 28px target, no fill at rest), then
11576
+ the line, centred on it. */
11577
+ .knobs-ui.knobs-timeline[data-variant="line"]{gap:4px;align-items:center}
11578
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:flex}
11579
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play{width:28px;height:28px;min-width:28px;min-height:28px;border-radius:var(--_k-radius);background:transparent;color:var(--_k-fg-2);
11580
+ transition:background calc(var(--_k-hover-out) * 1ms),transform .15s var(--_ku-ease)}
11581
+ .knobs-ui.knobs-timeline[data-variant="line"][data-playing] .knobs-tl-play{background:transparent;color:var(--_k-fg-2)}
11582
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
11583
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track{cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
11584
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
11585
+ `;
11586
+
11512
11587
  // src/ui/overlay-css.ts
11513
11588
  var overlayCss = `
11514
11589
  .knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}
@@ -11640,7 +11715,7 @@ var snappy = springLinear(SPRINGS2.snappy), pickerCss = `
11640
11715
  .knobs-picker-suggested{flex:none;color:var(--_k-fg-3);font-size:var(--_k-text-meta)}
11641
11716
  .knobs-picker-check{flex:none;display:flex;width:14px;height:14px;color:var(--_k-accent);opacity:0;scale:.6;filter:blur(2px);
11642
11717
  transition:opacity .11s ease-in,scale .11s ease-in,filter .11s ease-in}
11643
- .knobs-picker-check svg{display:block;width:14px;height:14px}
11718
+ .knobs-picker-check svg{display:block;width:12px;height:12px}
11644
11719
  .knobs-picker-option[data-chosen] .knobs-picker-check{opacity:1;scale:1;filter:none;transition:opacity .15s ease-out,scale var(--kp-snappy-ms) var(--kp-snappy),filter .15s ease-out}
11645
11720
  .knobs-picker-body{position:relative;overflow:visible}
11646
11721
  .knobs-picker-body[data-moving]{overflow:hidden}
@@ -11742,6 +11817,15 @@ var readoutCss = `
11742
11817
  .knobs-panel[data-gpu-active="true"] .knobs-rd-find{position:relative;z-index:2147480003}
11743
11818
  @media(prefers-reduced-motion:reduce){.knobs-rd :where(*){transition:none!important}}
11744
11819
  `;
11820
+
11821
+ // src/ui/selection-pill-css.ts
11822
+ var selectionPillCss = `
11823
+ .knobs-pick-pill,.knobs-pick-copy{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;transition:opacity .1s linear}
11824
+ .knobs-pick-pill{background:var(--_k-fill-selected);will-change:transform}
11825
+ .knobs-pick-copy{overflow:hidden;will-change:transform}
11826
+ .knobs-pick-copy-rows{position:absolute;left:0;top:0;width:0;height:0}
11827
+ :is(.knobs-pick-pill,.knobs-pick-copy)[data-off]{opacity:0}
11828
+ `;
11745
11829
  // src/ui/look.ts
11746
11830
  var css = `
11747
11831
  :where(.knobs-ui){
@@ -11753,6 +11837,9 @@ var css = `
11753
11837
  --_ku-wash:var(--k-ui-wash,color-mix(in srgb,var(--_k-fill) 70%,transparent));
11754
11838
  --_ku-wash-hi:var(--k-ui-wash-hi,var(--_k-fill-selected));
11755
11839
  --_ku-ease:var(--_k-motion-snappy);--_ku-ease-ms:calc(var(--_k-motion-snappy-ms) * 1ms);
11840
+ /* Over a picture: a quiet dark scrim in either appearance (a picture is no lighter in light
11841
+ mode), light type on it, no ring. */
11842
+ --_ku-media-scrim:rgb(0 0 0/.5);--_ku-on-media:#fff;--_ku-on-media-2:color-mix(in srgb,var(--_ku-on-media) 64%,transparent);
11756
11843
  /* The card frame (knobs' own look): every leaf pane a rounded card on the page, half a gap
11757
11844
  inside its slot, so panes sit a gap apart with the dividers' grips between them; tree rows
11758
11845
  inset as soft rounded washes. --k-frame: flat (labPreset) puts panes edge to edge on
@@ -11777,6 +11864,12 @@ var css = `
11777
11864
  --_ku-pane-bg and --_ku-pane-ring so this reaches them. */
11778
11865
  :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;
11779
11866
  --_ku-divider:var(--_k-split-gutter);--_kd-bg:transparent}
11867
+ /* What draws its box without those: a picker's card keeps its fill as the highlight but drops its
11868
+ ring; a timeline's track drops its well for a hairline under the lane; an inline panel sheds its
11869
+ card (--k-chrome 0). A full-window step is the one raised exception (it is the surface). */
11870
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-picker-card,.knobs-ui[data-nested] .knobs-picker-card{box-shadow:none}
11871
+ :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)}
11872
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-panel.knobs-inline:not(.knobs-page){--k-chrome:0}
11780
11873
  /* Only the pieces' own elements take their type and resets: what you put in a pane is yours. */
11781
11874
  .knobs-ui{box-sizing:border-box}
11782
11875
  :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}
@@ -11911,11 +12004,9 @@ var css = `
11911
12004
  .knobs-tree:focus .knobs-tree-row[data-active][aria-selected="true"]::before{background:var(--_ku-select-focus)}
11912
12005
  .knobs-tree-row[data-drop="into"]::before{opacity:1;background:color-mix(in srgb,var(--_k-accent) 30%,transparent)}
11913
12006
  .knobs-tree-row[data-dragged]{opacity:.38}
11914
- /* glide: one highlight under the selected row that glides between rows (the row's own stays off). */
11915
- .knobs-tree-cursor{position:absolute;left:var(--_ku-row-x);right:var(--_ku-row-x);top:0;height:var(--_k-row);border-radius:var(--_ku-row-radius);background:var(--_ku-select);
11916
- opacity:0;pointer-events:none;will-change:transform;transition:opacity .15s ease-out,background .12s}
11917
- .knobs-tree-cursor[data-on]{opacity:1}
11918
- .knobs-tree:focus .knobs-tree-cursor{background:var(--_ku-select-focus)}
12007
+ /* glide: the selection pill on the selected row, in the selection's colour (the row's own stays off). */
12008
+ .knobs-tree>.knobs-pick-pill{background:var(--_ku-select)}
12009
+ .knobs-tree:focus>.knobs-pick-pill{background:var(--_ku-select-focus)}
11919
12010
  .knobs-tree[data-glide] .knobs-tree-row[aria-selected="true"]::before{opacity:0!important}
11920
12011
  .knobs-tree-row>*{position:relative}
11921
12012
  .knobs-tree-caret{flex:none;display:flex;align-items:center;justify-content:center;width:16px;height:100%;color:var(--_k-fg-3);transition:transform var(--_ku-ease-ms) var(--_ku-ease)}
@@ -11947,22 +12038,20 @@ var css = `
11947
12038
  box-shadow:0 0 0 1px var(--_k-border-strong),0 6px 18px rgba(0,0,0,calc(.35 * var(--_k-shadow-k,1)));pointer-events:none;white-space:nowrap;font:var(--_k-weight) var(--_k-font-size)/1.6 var(--_k-font)}
11948
12039
  .knobs-tree-empty{padding:10px var(--_k-inset);color:var(--_k-fg-3)}
11949
12040
 
11950
- /* Timeline: a transport (play, the time) beside a track: the ruler's canvas over a lane of
11951
- markers, ranges as soft bands, and the playhead, a hairline in the accent with a cap. */
12041
+ /* Timeline: play beside a track: the ruler's canvas over a lane of markers, ranges as soft bands,
12042
+ and the playhead, a hairline in the accent with a cap. No clock: the time shows under the
12043
+ pointer on the track (the tag), in seconds, while hovering or scrubbing. */
11952
12044
  :where(.knobs-timeline,.knobs-timeline *){box-sizing:border-box}
11953
12045
  .knobs-ui.knobs-timeline{display:flex;align-items:stretch;gap:10px;width:100%;height:100%;min-height:52px;padding:6px var(--_k-inset) 8px 8px;outline:none;user-select:none;-webkit-user-select:none;
11954
12046
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
11955
12047
  :where(.knobs-ui) :is(.knobs-tl-play,.knobs-tl-edit){all:revert;box-sizing:border-box;font:inherit;color:inherit;letter-spacing:inherit;background:transparent;border:0;margin:0;padding:0;outline:none;box-shadow:none}
11956
- .knobs-tl-transport{flex:none;display:flex;align-items:center;gap:8px;min-width:96px}
12048
+ .knobs-tl-transport{flex:none;display:flex;align-items:center}
11957
12049
  .knobs-ui .knobs-tl-play{flex:none;display:inline-flex;align-items:center;justify-content:center;width:var(--_k-control-h);height:var(--_k-control-h);min-width:22px;min-height:22px;border-radius:calc(var(--_k-radius) * 999);
11958
12050
  background:var(--_k-fill-selected);color:var(--_k-fg);cursor:pointer;transition:background .15s,transform .15s var(--_ku-ease)}
11959
12051
  .knobs-ui .knobs-tl-play:hover{background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
11960
12052
  .knobs-ui .knobs-tl-play:active{transform:scale(.94)}
11961
12053
  .knobs-tl-play svg{width:12px;height:12px}
11962
12054
  .knobs-timeline[data-playing] .knobs-tl-play{background:var(--_k-accent);color:var(--_k-bg)}
11963
- .knobs-tl-time{display:flex;flex-direction:column;line-height:1.2;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
11964
- .knobs-tl-now{font:var(--_k-weight) var(--_k-value-size)/1.2 var(--_k-value-font);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";color:var(--_k-fg);white-space:nowrap}
11965
- .knobs-tl-sub{font-size:var(--_k-text-meta);color:var(--_k-fg-3);white-space:nowrap}
11966
12055
  .knobs-tl-track{position:relative;flex:1;min-width:0;overflow:hidden;border-radius:var(--_k-radius);background:var(--_k-fill);cursor:col-resize;touch-action:none;
11967
12056
  transition:background calc(var(--_k-hover-out) * 1ms)}
11968
12057
  .knobs-timeline:focus-visible .knobs-tl-track,.knobs-timeline[data-keyboard]:focus .knobs-tl-track{background:color-mix(in srgb,var(--_k-fill-selected) 80%,transparent)}
@@ -11986,7 +12075,8 @@ var css = `
11986
12075
  .knobs-tl-pin{position:absolute;top:13px;left:-5px;width:10px;height:10px;pointer-events:auto;cursor:grab}
11987
12076
  .knobs-tl-pin::after{content:"";position:absolute;inset:1.5px;border-radius:min(2px,var(--_k-radius));background:var(--knobs-tl-c);rotate:45deg;transition:background .15s,scale .2s var(--_ku-ease)}
11988
12077
  .knobs-tl-mlabel{position:absolute;top:26px;left:3px;max-width:140px;padding:0 6px;border-radius:max(0px,calc(var(--_k-radius) - 3px));background:var(--_k-fill-selected);color:var(--_k-fg);
11989
- font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:auto;cursor:grab;transition:background .15s,color .15s}
12078
+ font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;pointer-events:auto;cursor:grab;transition:background .15s,color .15s}
12079
+ .knobs-tl-mtext{display:block;overflow:hidden;white-space:nowrap}
11990
12080
  .knobs-tl-marker[data-selected]{--knobs-tl-c:var(--_k-accent)}
11991
12081
  .knobs-tl-marker[data-selected] .knobs-tl-mlabel{background:color-mix(in srgb,var(--_k-accent) 24%,var(--_k-fill-selected))}
11992
12082
  .knobs-tl-marker[data-selected] .knobs-tl-pin::after{scale:1.2}
@@ -12005,9 +12095,42 @@ var css = `
12005
12095
  .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}
12006
12096
  .knobs-tl-track:hover .knobs-tl-hover{opacity:1}
12007
12097
  .knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
12098
+ .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}
12099
+ .knobs-tl-tag[data-on]{opacity:1}
12100
+ /* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
12101
+ faint chapter ticks, the current one in text colour; nothing to edit. */
12102
+ .knobs-ui.knobs-timeline[data-variant="lane"]{height:auto;min-height:0;padding:0;gap:var(--_k-gap)}
12103
+ .knobs-timeline[data-variant="lane"] .knobs-tl-track{height:var(--_k-control-h)}
12104
+ .knobs-timeline[data-variant="lane"] .knobs-tl-ruler{height:100%}
12105
+ .knobs-timeline[data-variant="lane"] .knobs-tl-lane{top:0}
12106
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker{top:0}
12107
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker::before{top:0;bottom:50%;left:-.75px;width:1.5px;background:var(--_k-fg-3)}
12108
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker[data-current]::before{background:var(--_k-fg)}
12109
+ .knobs-timeline[data-variant="lane"] :is(.knobs-tl-pin,.knobs-tl-mlabel,.knobs-tl-rlabel){display:none}
12110
+ .knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
12111
+ .knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
12112
+ .knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
12113
+ /* The line (variant: "line"): a player's progress under its media. One 2px line, fg-3 with fg
12114
+ filling it, a segment per chapter; it thickens under the pointer and while scrubbing. The
12115
+ chapter and time under the pointer float over it; nothing else shows. */
12116
+ .knobs-ui.knobs-timeline[data-variant="line"]{height:auto;min-height:0;padding:0;gap:0}
12117
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:none}
12118
+ .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}
12119
+ .knobs-timeline[data-variant="line"] :is(.knobs-tl-ruler,.knobs-tl-lane,.knobs-tl-hover,.knobs-tl-head,.knobs-tl-rlabel){display:none}
12120
+ .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)}
12121
+ .knobs-tl-seg{position:absolute;left:0;top:0;height:100%;overflow:hidden;border-radius:1px;background:var(--_k-fg-3);will-change:transform}
12122
+ .knobs-tl-seg-fill{position:absolute;inset:0;background:var(--_k-fg);transform-origin:0 0;will-change:transform}
12123
+ .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}
12124
+ .knobs-timeline[data-variant="line"] .knobs-tl-range{top:3px;bottom:3px;border-radius:3px}
12125
+ .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)}
12126
+ .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:var(--_ku-media-scrim);color:var(--_ku-on-media);
12127
+ font-size:var(--_k-type-small);line-height:var(--_k-leading-small);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}
12128
+ .knobs-tl-tag-label{color:var(--_k-fg-2)}
12129
+ .knobs-timeline[data-variant="line"] .knobs-tl-tag-label{color:var(--_ku-on-media-2)}
12130
+ .knobs-tl-tag-label:empty{display:none}
12008
12131
  @media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
12009
12132
  @media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
12010
- `, uiCss = () => aliased(css + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss) + rolling_css_default;
12133
+ `, uiCss = () => aliased(css + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + playerCss + selectionPillCss) + rolling_css_default;
12011
12134
  function injectUiStyles(doc) {
12012
12135
  if (injectTokens(doc), doc.querySelector("style[data-knobs-ui]"))
12013
12136
  return;
@@ -14752,7 +14875,7 @@ function buildPanel(controls, host, options) {
14752
14875
  if (helpButton?.hidden)
14753
14876
  titleEl.title = `${titleEl.textContent ? `${titleEl.textContent}
14754
14877
  ` : ""}Press ${show("help")} for keyboard shortcuts`;
14755
- let presetLabel = (id) => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : void 0) ?? controls.presetLabel(id), presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel), tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate)), activeTab, searchOptions = typeof options.search === "object" ? options.search : {}, chipTests = /* @__PURE__ */ new Map, search = options.search && !page ? createSearch(doc, {
14878
+ let presetLabel = (id) => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : void 0) ?? controls.presetLabel(id), presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries)), tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate)), activeTab, searchOptions = typeof options.search === "object" ? options.search : {}, chipTests = /* @__PURE__ */ new Map, search = options.search && !page ? createSearch(doc, {
14756
14879
  placeholder: searchOptions.placeholder ?? "Search",
14757
14880
  chips: (searchOptions.chips ?? ["modified"]).map((chip) => {
14758
14881
  if (chip === "modified")
@@ -14774,7 +14897,10 @@ function buildPanel(controls, host, options) {
14774
14897
  let allActions = [
14775
14898
  ...controls.actions ?? [],
14776
14899
  ...options.actions ?? []
14777
- ], body = el("div"), tabMotion = tabTransition(body, root, () => wakeGpu()), blocks = [], cards = [], groupResetButtons = /* @__PURE__ */ new Map, groupDice = /* @__PURE__ */ new Map, gpuGroups = [], gpuRows = [], layout, layoutTick = 0, foldSource = null, userFolding = !1, noteFold = (source) => {
14900
+ ], body = el("div"), tabMotion = tabTransition(body, root, () => wakeGpu()), pinnedBody = el("div");
14901
+ if (pinnedBody.className = "knobs-pinned", !page)
14902
+ body.append(presetStrip.element);
14903
+ let blocks = [], cards = [], groupResetButtons = /* @__PURE__ */ new Map, groupDice = /* @__PURE__ */ new Map, gpuGroups = [], gpuRows = [], layout, layoutTick = 0, foldSource = null, userFolding = !1, noteFold = (source) => {
14778
14904
  if (foldSource !== "user")
14779
14905
  foldSource = source;
14780
14906
  }, byUser = (act) => {
@@ -15091,8 +15217,11 @@ function buildPanel(controls, host, options) {
15091
15217
  section: field.section
15092
15218
  });
15093
15219
  }
15094
- return gatherSubsections(specs);
15095
- }, gatherSubsections = (specs) => {
15220
+ let planned = gatherSubsections(specs);
15221
+ if (page || !planned.some(pinnedRow))
15222
+ return planned;
15223
+ return [...planned.filter(pinnedRow), ...planned.filter((spec) => !pinnedRow(spec))];
15224
+ }, pinnedRow = (r) => !page && controls.isPinned(r.keys[0] ?? r.id), gatherSubsections = (specs) => {
15096
15225
  let out = [];
15097
15226
  for (let i = 0;i < specs.length; ) {
15098
15227
  let end = i;
@@ -15616,13 +15745,13 @@ function buildPanel(controls, host, options) {
15616
15745
  } else {
15617
15746
  let runs = [];
15618
15747
  for (let r of next) {
15619
- let last = runs.at(-1);
15620
- if (last && last.group === r.group)
15748
+ let last = runs.at(-1), pinned = pinnedRow(r);
15749
+ if (last && last.group === r.group && last.pinned === pinned)
15621
15750
  last.rows.push(r);
15622
15751
  else
15623
- runs.push({ group: r.group, rows: [r] });
15752
+ runs.push({ group: r.group, pinned, rows: [r] });
15624
15753
  }
15625
- let free = blocks.filter((b) => !b.leaving), chosen = [], usedSubheads = /* @__PURE__ */ new Set;
15754
+ let free = blocks.filter((b) => !b.leaving), chosen = [], pinnedBlocks = /* @__PURE__ */ new Set, usedSubheads = /* @__PURE__ */ new Set;
15626
15755
  for (let run of runs) {
15627
15756
  let at = free.findIndex((b) => b.group === run.group), block = at >= 0 ? free.splice(at, 1)[0] : newBlock(run.group);
15628
15757
  if (at < 0) {
@@ -15637,7 +15766,8 @@ function buildPanel(controls, host, options) {
15637
15766
  motion.push({ node: block.card, show: !0 });
15638
15767
  }
15639
15768
  }
15640
- chosen.push(block);
15769
+ if (chosen.push(block), run.pinned)
15770
+ pinnedBlocks.add(block);
15641
15771
  let nodes = [], under;
15642
15772
  for (let r of run.rows) {
15643
15773
  let name = r.field.subsection;
@@ -15662,7 +15792,8 @@ function buildPanel(controls, host, options) {
15662
15792
  gpuRows.splice(gi, 1);
15663
15793
  subheads.delete(id);
15664
15794
  }
15665
- arrange(body, chosen.flatMap((b) => b.head ? [b.head, b.card] : [b.card]));
15795
+ let nodesOf = (list) => list.flatMap((b) => b.head ? [b.head, b.card] : [b.card]);
15796
+ arrange(pinnedBody, nodesOf(chosen.filter((b) => pinnedBlocks.has(b)))), arrange(body, [presetStrip.element, ...nodesOf(chosen.filter((b) => !pinnedBlocks.has(b)))]);
15666
15797
  for (let block of free) {
15667
15798
  block.leaving = !0;
15668
15799
  let fold = () => {
@@ -15768,15 +15899,24 @@ function buildPanel(controls, host, options) {
15768
15899
  if (f.group === group && f.tab !== void 0)
15769
15900
  return f.tab;
15770
15901
  return;
15771
- }, tabOf = (r) => r.field.tab ?? (r.group !== void 0 ? groupTab(r.group) : void 0), tabList = () => {
15902
+ }, tabOf = (r) => pinnedRow(r) ? void 0 : r.field.tab ?? (r.group !== void 0 ? groupTab(r.group) : void 0), tabList = () => {
15772
15903
  if (page)
15773
15904
  return [];
15774
15905
  let list = (options.tabs ?? []).map((t) => typeof t === "string" ? { id: t, label: readableTitle(t) } : { id: t.id, label: t.label ?? readableTitle(t.id) }), seen = new Set(list.map((t) => t.id));
15775
- for (let f of Object.values(controls.schema))
15776
- if (f.tab !== void 0 && !seen.has(f.tab))
15906
+ for (let [key, f] of Object.entries(controls.schema))
15907
+ if (f.tab !== void 0 && !seen.has(f.tab) && !controls.isPinned(key))
15777
15908
  seen.add(f.tab), list.push({ id: f.tab, label: readableTitle(f.tab) });
15778
15909
  return list;
15779
15910
  };
15911
+ function presetsTouchTab(entries) {
15912
+ if (page || activeTab === void 0)
15913
+ return !0;
15914
+ let keys = rows.flatMap((r) => {
15915
+ let tab = tabOf(r);
15916
+ return !pinnedRow(r) && (tab === void 0 || tab === activeTab) ? r.keys : [];
15917
+ });
15918
+ return presetsTouch(entries, keys, controls.locked);
15919
+ }
15780
15920
  function syncTabs() {
15781
15921
  let list = tabList(), has = (id) => id !== void 0 && list.some((t) => t.id === id);
15782
15922
  if (!has(activeTab))
@@ -15830,7 +15970,7 @@ function buildPanel(controls, host, options) {
15830
15970
  want.set(b.head, any);
15831
15971
  want.set(b.card, any && !(b.group !== void 0 && folds.get(b.group) && !finding));
15832
15972
  }
15833
- return want;
15973
+ return want.set(presetStrip.element, presetStrip.shows()), want;
15834
15974
  }
15835
15975
  let shift = (node, show, delay) => {
15836
15976
  shifting.add(node), node.dataset.presence = show ? "in" : "out", reveal(node, show, () => {
@@ -15847,6 +15987,9 @@ function buildPanel(controls, host, options) {
15847
15987
  }
15848
15988
  if (tabMotion.running)
15849
15989
  tabMotion.finish();
15990
+ let strip = presetStrip.element;
15991
+ if (strip.hidden === want.get(strip))
15992
+ strip.hidden = !want.get(strip);
15850
15993
  let showing = (n) => !n.hidden && n.dataset.leaving !== "true", set = (node, show, animate) => {
15851
15994
  if (showing(node) === show)
15852
15995
  return;
@@ -16051,9 +16194,9 @@ function buildPanel(controls, host, options) {
16051
16194
  } else {
16052
16195
  if (root.append(header), showFooter)
16053
16196
  root.append(popover);
16054
- if (root.append(tabStrip.element), search)
16197
+ if (root.append(pinnedBody, tabStrip.element), search)
16055
16198
  root.append(search.element);
16056
- if (root.append(presetStrip.element, body), panelActions.length > 0)
16199
+ if (root.append(body), panelActions.length > 0)
16057
16200
  root.append(customActions);
16058
16201
  root.append(text, apply, status, live);
16059
16202
  }
@@ -16285,35 +16428,44 @@ function buildPanel(controls, host, options) {
16285
16428
  nodes.push(...r.editor?.stops() ?? [r.input]);
16286
16429
  return nodes.push(...body.querySelectorAll("[data-knobs-stop],.knobs-tile>[data-foldable],.knobs-section-head[data-foldable]")), nodes.filter(visible).sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
16287
16430
  }, actionBarButtons = () => [...customActions.querySelectorAll("button")].filter((b) => visible(b) && !b.disabled), lastActionButton, stops = () => {
16288
- let list = [];
16431
+ let list = [], strips = [];
16289
16432
  if (visible(tabStrip.element))
16290
- list.push({
16291
- contains: (n) => tabStrip.element.contains(n),
16292
- focus: (o) => tabStrip.focus(o)
16433
+ strips.push({
16434
+ at: tabStrip.element,
16435
+ stops: [{ contains: (n) => tabStrip.element.contains(n), focus: (o) => tabStrip.focus(o) }]
16293
16436
  });
16294
16437
  if (visible(presetStrip.element))
16295
- list.push({
16296
- contains: (n) => presetStrip.element.contains(n),
16297
- focus: (o) => presetStrip.element.focus(o)
16438
+ strips.push({
16439
+ at: presetStrip.element,
16440
+ stops: [
16441
+ {
16442
+ contains: (n) => presetStrip.element.contains(n),
16443
+ focus: (o) => presetStrip.element.focus(o)
16444
+ }
16445
+ ]
16298
16446
  });
16299
16447
  if (page) {
16448
+ for (let strip of strips)
16449
+ list.push(...strip.stops);
16300
16450
  for (let stop of pageStops())
16301
16451
  list.push({ contains: (n) => stop.contains(n), focus: (o) => stop.focus(o) });
16302
16452
  return list;
16303
16453
  }
16304
- let runs = rows.map((r) => ({
16454
+ let stopOf = (stop) => ({
16455
+ contains: (n) => stop.contains(n),
16456
+ focus: (o) => stop.focus(o)
16457
+ }), shown = (stops) => stops.filter((s) => !s.closest('[hidden],[data-presence="out"]')).map(stopOf), runs = rows.map((r) => ({
16305
16458
  at: r.row,
16306
- stops: r.editor?.stops() ?? [r.input]
16459
+ stops: shown(r.editor?.stops() ?? [r.input])
16307
16460
  }));
16308
16461
  for (let b of blocks)
16309
16462
  if (b.head?.hasAttribute("data-foldable") && !b.leaving)
16310
- runs.push({ at: b.head, stops: [b.head] });
16463
+ runs.push({ at: b.head, stops: shown([b.head]) });
16311
16464
  for (let sh of subheads.values())
16312
- runs.push({ at: sh.el, stops: [sh.el] });
16313
- runs.sort((a, b) => a.at.compareDocumentPosition(b.at) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
16465
+ runs.push({ at: sh.el, stops: shown([sh.el]) });
16466
+ runs.push(...strips), runs.sort((a, b) => a.at.compareDocumentPosition(b.at) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
16314
16467
  for (let run of runs)
16315
- for (let stop of run.stops.filter((s) => !s.closest('[hidden],[data-presence="out"]')))
16316
- list.push({ contains: (n) => stop.contains(n), focus: (o) => stop.focus(o) });
16468
+ list.push(...run.stops);
16317
16469
  let bar = actionBarButtons();
16318
16470
  if (bar.length)
16319
16471
  list.push({
@@ -17419,6 +17571,7 @@ function buildPanel(controls, host, options) {
17419
17571
  id: controls.id,
17420
17572
  root,
17421
17573
  body,
17574
+ pinned: pinnedBody,
17422
17575
  inline,
17423
17576
  header,
17424
17577
  titleEl,