@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
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: "A page's title, 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: "Section titles (under their kicker), 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: "Mono inside body text: code and paths."
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}`;
@@ -3126,6 +3288,9 @@ ${GRAD_PROPS}
3126
3288
  .knobs-group-head{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:20px;margin:var(--k-group-gap) 0 var(--_k-gap);padding:0 var(--k-group-inset);color:var(--_k-group-label);font-size:var(--k-group-size);font-weight:var(--_k-weight-title);letter-spacing:var(--k-group-tracking);text-transform:var(--k-group-transform)}
3127
3289
  .knobs-panel:not(.knobs-page) .knobs-group-head:not(:first-child){border-top:1px solid var(--k-group-divider);padding-top:var(--k-group-divider-gap)}
3128
3290
  .knobs-group-head:first-child{margin-top:0}
3291
+ .knobs-panel:not(.knobs-page) .knobs-presets+.knobs-group-head{margin-top:0;border-top:0;padding-top:0}
3292
+ .knobs-pinned{margin-bottom:var(--k-group-gap)}
3293
+ .knobs-pinned:empty{display:none}
3129
3294
  .knobs-group-head>span:last-child{display:flex;align-items:center;gap:var(--_k-tray-pad);margin-right:calc((var(--k-icon-sm) - 20px) / 2)}
3130
3295
  .knobs-group-head button{height:20px;padding:0 6px;font-size:var(--_k-text-meta);font-weight:500;letter-spacing:0;text-transform:none;color:var(--_k-fg-2);background:transparent;border-radius:var(--_k-radius-sm);cursor:pointer}
3131
3296
  .knobs-group-head button:is(:hover:not([disabled]),:focus-visible){background:var(--_k-fill-selected);color:var(--_k-fg)}
@@ -6766,6 +6931,9 @@ function resolveActivePresetEntry(entries, currentValues, lastAppliedId, baseId
6766
6931
  isCustom: true
6767
6932
  };
6768
6933
  }
6934
+ function presetsTouch(entries, keys, defaults) {
6935
+ return entries.some((e) => !(e.kind === "preset" && e.rawId === "default") && keys.some((k) => !same2(e.values[k], defaults[k])));
6936
+ }
6769
6937
  function nextPresetIndex(count, activeIndex, direction) {
6770
6938
  if (count <= 0)
6771
6939
  return -1;
@@ -6773,7 +6941,7 @@ function nextPresetIndex(count, activeIndex, direction) {
6773
6941
  return direction > 0 ? 0 : count - 1;
6774
6942
  return ((activeIndex + direction) % count + count) % count;
6775
6943
  }
6776
- function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id) => controls.presetLabel(id)) {
6944
+ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, presetLabel = (id) => controls.presetLabel(id), relevant) {
6777
6945
  const wake = () => onAfterChange?.();
6778
6946
  const el = (tag, cls, text) => {
6779
6947
  const node = doc.createElement(tag);
@@ -6819,6 +6987,15 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
6819
6987
  let prevDrifted = false;
6820
6988
  let initializedPill = false;
6821
6989
  let slideUntil = 0;
6990
+ const unpinned = (values) => {
6991
+ let out;
6992
+ for (const k of Object.keys(values))
6993
+ if (controls.isPinned(k) && !same2(values[k], controls.locked[k])) {
6994
+ out ??= { ...values };
6995
+ out[k] = controls.locked[k];
6996
+ }
6997
+ return out ?? values;
6998
+ };
6822
6999
  function buildEntries() {
6823
7000
  const presetEntries = Object.entries(controls.presets).map(([id, presetValues]) => ({
6824
7001
  id: `p:${id}`,
@@ -6836,7 +7013,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
6836
7013
  kind: "variant",
6837
7014
  rawId: v.id,
6838
7015
  label: v.name ?? v.id.slice(0, 8),
6839
- values: v.values,
7016
+ values: unpinned(v.values),
6840
7017
  apply: (coalesce) => controls.variants.load(v.id, { coalesce })
6841
7018
  }));
6842
7019
  return [...presetEntries, ...variantEntries];
@@ -6888,7 +7065,13 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
6888
7065
  if (first)
6889
7066
  doc.defaultView?.requestAnimationFrame(() => pill.style.transition = "");
6890
7067
  }
6891
- const resolve = () => resolveActivePresetEntry(entries, controls.get(), lastAppliedId, activeEntryId(controls));
7068
+ const resolve = () => resolveActivePresetEntry(entries, unpinned(controls.get()), lastAppliedId, activeEntryId(controls));
7069
+ const wanted = (list) => (list.length > 1 || showVariants) && (relevant?.(list) ?? true);
7070
+ function syncShown() {
7071
+ const show = wanted(entries);
7072
+ if (root.hidden === show && root.dataset.presence === undefined)
7073
+ root.hidden = !show;
7074
+ }
6892
7075
  function syncSelection(snap = false) {
6893
7076
  activeState = resolve();
6894
7077
  if (lastAppliedId !== null && activeState.activeId !== lastAppliedId) {
@@ -6945,14 +7128,15 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
6945
7128
  run(() => {
6946
7129
  const variantsRef = showVariants ? controls.variants.list() : null;
6947
7130
  if (signature !== "" && variantsRef === lastVariantsRef) {
7131
+ syncShown();
6948
7132
  syncSelection(false);
6949
7133
  return;
6950
7134
  }
6951
7135
  lastVariantsRef = variantsRef;
6952
7136
  const nextEntries = buildEntries();
6953
7137
  const nextSig = JSON.stringify(nextEntries.map((e) => [e.id, e.label]));
6954
- const shouldShow = nextEntries.length > 1 || showVariants;
6955
- root.hidden = !shouldShow;
7138
+ entries = nextEntries;
7139
+ syncShown();
6956
7140
  if (nextSig !== signature) {
6957
7141
  signature = nextSig;
6958
7142
  entries = nextEntries;
@@ -7223,6 +7407,7 @@ function createPresetStrip(doc, controls, showVariants, run, onAfterChange, pres
7223
7407
  getEntries: () => entries,
7224
7408
  getButtons: () => buttons,
7225
7409
  getActiveState: () => activeState,
7410
+ shows: () => wanted(entries),
7226
7411
  cycle,
7227
7412
  selectById,
7228
7413
  save: () => {
@@ -10002,6 +10187,8 @@ class GpuOverlayManager {
10002
10187
  });
10003
10188
  ro.observe(panel.root);
10004
10189
  ro.observe(panel.body);
10190
+ if (panel.pinned)
10191
+ ro.observe(panel.pinned);
10005
10192
  ro.observe(panel.presetStrip.track);
10006
10193
  const mo = new MutationObserver(() => {
10007
10194
  this.styleVersion++;
@@ -12711,7 +12898,9 @@ class GpuOverlayManager {
12711
12898
  }
12712
12899
  const ps = panel.presetStrip;
12713
12900
  const hinting = panel.root.classList.contains("knobs-hinting");
12901
+ const stripAlpha = this.curAlpha;
12714
12902
  if (!ps.element.hidden) {
12903
+ this.curAlpha = stripAlpha * this.presence(ps.element);
12715
12904
  const psHovered = this.curPanelHovered && ps.element.matches(":hover");
12716
12905
  this.curSubHovered = psHovered;
12717
12906
  const psRect = this.rectOf(ps.element);
@@ -12835,6 +13024,7 @@ class GpuOverlayManager {
12835
13024
  align: "center"
12836
13025
  });
12837
13026
  }
13027
+ this.curAlpha = stripAlpha;
12838
13028
  }
12839
13029
  const tabs = panel.tabs;
12840
13030
  if (tabs && !tabs.element.hidden)
@@ -12950,7 +13140,7 @@ class GpuOverlayManager {
12950
13140
  }
12951
13141
  if (bodyRevealing || pageMoving)
12952
13142
  this.curBypassRectCache = true;
12953
- const bodyHovered = this.curPanelHovered && panel.body.matches(":hover");
13143
+ const bodyHovered = this.curPanelHovered && (panel.body.matches(":hover") || Boolean(panel.pinned?.matches(":hover")));
12954
13144
  this.curSubHovered = bodyHovered;
12955
13145
  this.curPointerRow = bodyHovered && panel.root.hasAttribute("data-k-row-hover");
12956
13146
  const panelAlpha = this.curAlpha;
@@ -20907,14 +21097,23 @@ var compareCss = `
20907
21097
  .knobs-ui.knobs-compare{--_ku-cmp-diff:var(--k-compare-diff,var(--_k-ok));--_ku-cmp-line:var(--_k-compare-divider);
20908
21098
  --_ku-cmp-stage:var(--_k-compare-stage);
20909
21099
  display:flex;flex-direction:column;gap:6px;width:100%;height:100%;min-height:160px;outline:none;user-select:none;-webkit-user-select:none;
20910
- font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
21100
+ 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}
20911
21101
  :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}
20912
- .knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:2px;height:24px;padding:0 2px}
20913
- .knobs-cmp-modes{display:flex;align-items:center;gap:2px}
20914
- .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;
20915
- transition:color .15s,background .15s}
20916
- .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)}
20917
- .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)}
21102
+ .knobs-cmp-bar{flex:none;display:flex;align-items:center;gap:var(--_k-gap);height:var(--_k-control-h)}
21103
+ /* The modes: one fill, the selection pill on the one picked; a mode isn't brighter for being
21104
+ picked: the pill's bright copy is. */
21105
+ .knobs-cmp-modes{position:relative;display:inline-flex;height:var(--_k-control-h);border-radius:var(--_k-radius);background:var(--_k-fill)}
21106
+ .knobs-cmp-modes[hidden]{display:none}
21107
+ /* Hover is a quiet fill under the arrow cursor (no hand, no brightening), there at once. */
21108
+ .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;
21109
+ transition:background calc(var(--_k-hover-out) * 1ms)}
21110
+ .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)}
21111
+ /* Toggles: icon buttons, the accent's wash when on. */
21112
+ .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;
21113
+ transition:background calc(var(--_k-hover-out) * 1ms)}
21114
+ .knobs-ui .knobs-cmp-toggle:is(:hover,:focus-visible){background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
21115
+ .knobs-ui .knobs-cmp-toggle[aria-pressed="true"]{color:var(--_k-accent-fg);background:var(--_k-accent-soft)}
21116
+ .knobs-cmp-toggle svg{display:block}
20918
21117
  .knobs-cmp-fill{flex:1}
20919
21118
  .knobs-cmp-toggle[hidden]{display:none}
20920
21119
  /* One surface: on a card (a walkthrough's step, a split's pane, anything .knobs-surface) or marked
@@ -20927,49 +21126,96 @@ var compareCss = `
20927
21126
  .knobs-compare[data-frame="flat"] .knobs-cmp-stage{border-radius:0}
20928
21127
  .knobs-compare[data-mode="wipe"] .knobs-cmp-stage{cursor:ew-resize}
20929
21128
  /* Unzoomed, a finger still scrolls the page up and down; zoomed, the stage takes every gesture. */
20930
- .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:grab;touch-action:none}
20931
- .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:grabbing!important}
20932
- .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:ew-resize!important}
21129
+ /* Zoomed, it pans: a move cursor, never a hand. */
21130
+ .knobs-compare[data-zoomed] .knobs-cmp-stage{cursor:move;touch-action:none}
21131
+ .knobs-compare[data-panning],.knobs-compare[data-panning] *{cursor:move!important}
21132
+ /* Wiping, the grip stands in for the cursor. */
21133
+ .knobs-compare[data-dragging],.knobs-compare[data-dragging] *{cursor:none!important}
20933
21134
  .knobs-cmp-sizer{position:absolute;left:0;right:0;top:0;height:0;visibility:hidden;pointer-events:none}
20934
21135
  .knobs-cmp-canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
20935
21136
  .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}
20936
21137
  .knobs-compare[data-renderer="gpu"] .knobs-cmp-media{opacity:0}
20937
- /* Glass: what's behind shows through, blurred, on the knobs tokens. */
20938
- .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);
20939
- 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)))}
20940
- .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}
20941
- .knobs-cmp-label[data-lit]{color:var(--_k-fg)}
20942
- .knobs-cmp-label[data-dim]{color:var(--_k-fg-3)}
20943
- /* The wipe: a hairline down the line with a grip at its middle; a wider strip takes the hand and
20944
- the keys (a slider). It fades with the mode. */
20945
- .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)}
20946
- .knobs-cmp-handle[data-on]{opacity:1;pointer-events:auto;transition:opacity .22s cubic-bezier(.2,.8,.2,1)}
20947
- .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)}
20948
- .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);
20949
- 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}
20950
- .knobs-compare[data-dragging] .knobs-cmp-handle::after{scale:1.12}
20951
- .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)}
21138
+ /* The media comes in with its first frames: a fade and a 4px blur on the default spring. */
21139
+ .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)}
21140
+ .knobs-compare:not([data-ready]) :is(.knobs-cmp-canvas,.knobs-cmp-media){opacity:0;filter:blur(4px)}
21141
+ /* Over the pictures: a quiet scrim, dark in either appearance, blurred, with no ring. */
21142
+ .knobs-cmp-label,.knobs-cmp-zoom,.knobs-cmp-grip{background:var(--_ku-media-scrim);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}
21143
+ /* ✗ Before · ✓ After: Before quieter, After in full. Neutral: no colour of its own. */
21144
+ .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);
21145
+ white-space:nowrap;pointer-events:none;will-change:transform}
21146
+ .knobs-cmp-label[data-side="before"]{color:var(--_ku-on-media-2)}
21147
+ .knobs-cmp-label[data-side="after"]{color:var(--_ku-on-media)}
21148
+ /* Fusing, the mark folds away and the label is its name. */
21149
+ .knobs-cmp-mark{flex:none;display:flex;justify-content:flex-end;overflow:hidden}
21150
+ .knobs-cmp-mark svg{display:block;flex:none;width:12px;height:12px}
21151
+ /* The fuse's seam: a blurred strip over where the sides meet, soft at both sides. */
21152
+ .knobs-cmp-seam{position:absolute;left:0;top:0;width:8px;height:0;opacity:0;pointer-events:none;will-change:transform,opacity;
21153
+ -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)}
21154
+ /* The wipe: a hairline down the line with a raised grip in its middle (at the pointer's height
21155
+ while dragged); a wider strip takes the hand and the keys (a slider). It fades in with the
21156
+ fuse; wiping, line and grip brighten. */
21157
+ .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}
21158
+ .knobs-cmp-handle[data-on]{pointer-events:auto}
21159
+ .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)}
21160
+ .knobs-compare[data-dragging] .knobs-cmp-handle::before{background:var(--_ku-cmp-line)}
21161
+ .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}
21162
+ .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)}
21163
+ .knobs-cmp-grip::before{left:6px}
21164
+ .knobs-cmp-grip::after{left:9px}
21165
+ .knobs-compare[data-dragging] .knobs-cmp-grip::before,.knobs-compare[data-dragging] .knobs-cmp-grip::after{background:var(--_ku-on-media)}
21166
+ .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)}
20952
21167
  /* The split's hairline, down the middle side by side. */
20953
21168
  .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}
20954
21169
  /* Zoomed: how far, and a way back, in the stage's corner; it fades in and out, nothing shifts. */
20955
21170
  .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;
20956
- color:var(--_k-fg);font-size:var(--_k-text-meta);font-variant-numeric:tabular-nums;font-feature-settings:"tnum";white-space:nowrap;
21171
+ color:var(--_ku-on-media);font-variant-numeric:tabular-nums;font-feature-settings:var(--_kr-data,"tnum");white-space:nowrap;
20957
21172
  opacity:0;scale:.96;pointer-events:none;transition:opacity .16s cubic-bezier(.4,0,1,1),scale .16s cubic-bezier(.4,0,1,1)}
20958
21173
  .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)}
20959
21174
  :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;
20960
- height:20px;padding:0 8px;border-radius:999px;background:transparent;color:var(--_k-fg-2);cursor:pointer;transition:color .15s,background .15s}
20961
- :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)}
20962
- .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}
21175
+ 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)}
21176
+ :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)}
21177
+ .knobs-cmp-note{position:absolute;left:50%;top:50%;translate:-50% -50%;color:var(--_k-fg-3);pointer-events:none}
20963
21178
  .knobs-cmp-note:empty{display:none}
20964
21179
  .knobs-cmp-strip-host{flex:none;height:60px;min-height:0}
20965
- .knobs-cmp-timeline-host{flex:none;height:56px;min-height:0}
21180
+ .knobs-cmp-timeline-host{flex:none;min-height:0}
20966
21181
  /* The frame strip: Before over After per column, a difference bar under them, a dot where it peaks. */
20967
21182
  .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;
20968
- font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg)}
21183
+ font:var(--_k-weight) var(--_k-type-small)/var(--_k-leading-small) var(--_k-font);color:var(--_k-fg)}
20969
21184
  .knobs-cmp-strip>canvas{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
20970
21185
  @media(prefers-reduced-motion:reduce){:is(.knobs-compare,.knobs-cmp-strip) :where(*,::before,::after){transition-duration:.12s!important}}
20971
21186
  `;
20972
21187
 
21188
+ // src/ui/player-css.ts
21189
+ var playerCss = `
21190
+ :where(.knobs-player,.knobs-player *){box-sizing:border-box}
21191
+ .knobs-ui.knobs-player{--_kp-stage:var(--_k-compare-stage);display:flex;flex-direction:column;gap:6px;width:100%;outline:none;
21192
+ 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}
21193
+ :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)}
21194
+ .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)}
21195
+ .knobs-player[data-frame="flat"] .knobs-player-frame{border-radius:0}
21196
+ .knobs-player-media{position:absolute;inset:0;display:block;width:100%;height:100%;margin:0;padding:0;border:0;object-fit:contain;
21197
+ 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)}
21198
+ .knobs-player[data-ready] .knobs-player-media{opacity:1;filter:none}
21199
+ .knobs-player-line{flex:none;min-height:0}
21200
+ /* The timeline's own stylesheet is look.ts's; what the player's row and every timeline share
21201
+ about the hand is set here, after it. No hand cursor anywhere (the arrow, or a resize where it
21202
+ resizes); hover is a quiet fill (fill-hover), in at once (hover-in, 0 ms), out easing. */
21203
+ .knobs-ui .knobs-tl-play,.knobs-ui.knobs-timeline :is(.knobs-tl-pin,.knobs-tl-rlabel),
21204
+ .knobs-ui.knobs-timeline[data-dragging] .knobs-tl-marker :is(.knobs-tl-pin,.knobs-tl-mlabel){cursor:default}
21205
+ .knobs-ui .knobs-tl-play:hover,.knobs-tl-edge:hover::after,.knobs-tl-rlabel:hover,.knobs-tl-track:hover .knobs-tl-hover,
21206
+ .knobs-timeline[data-variant="line"] .knobs-tl-track:hover .knobs-tl-segs{transition-duration:calc(var(--_k-hover-in) * 1ms)}
21207
+ /* The line's row: a minimal play/pause (the filled pair, a 28px target, no fill at rest), then
21208
+ the line, centred on it. */
21209
+ .knobs-ui.knobs-timeline[data-variant="line"]{gap:4px;align-items:center}
21210
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:flex}
21211
+ .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);
21212
+ transition:background calc(var(--_k-hover-out) * 1ms),transform .15s var(--_ku-ease)}
21213
+ .knobs-ui.knobs-timeline[data-variant="line"][data-playing] .knobs-tl-play{background:transparent;color:var(--_k-fg-2)}
21214
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-play:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
21215
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track{cursor:default;transition:background calc(var(--_k-hover-out) * 1ms)}
21216
+ .knobs-ui.knobs-timeline[data-variant="line"] .knobs-tl-track:hover{background:var(--_k-fill-hover);transition-duration:calc(var(--_k-hover-in) * 1ms)}
21217
+ `;
21218
+
20973
21219
  // src/ui/overlay-css.ts
20974
21220
  var overlayCss = `
20975
21221
  .knobs-layers{position:fixed;inset:0;z-index:2147480005;pointer-events:none}
@@ -21105,7 +21351,7 @@ var init_picker_css = __esm(() => {
21105
21351
  .knobs-picker-suggested{flex:none;color:var(--_k-fg-3);font-size:var(--_k-text-meta)}
21106
21352
  .knobs-picker-check{flex:none;display:flex;width:14px;height:14px;color:var(--_k-accent);opacity:0;scale:.6;filter:blur(2px);
21107
21353
  transition:opacity .11s ease-in,scale .11s ease-in,filter .11s ease-in}
21108
- .knobs-picker-check svg{display:block;width:14px;height:14px}
21354
+ .knobs-picker-check svg{display:block;width:12px;height:12px}
21109
21355
  .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}
21110
21356
  .knobs-picker-body{position:relative;overflow:visible}
21111
21357
  .knobs-picker-body[data-moving]{overflow:hidden}
@@ -21209,6 +21455,15 @@ var readoutCss = `
21209
21455
  @media(prefers-reduced-motion:reduce){.knobs-rd :where(*){transition:none!important}}
21210
21456
  `;
21211
21457
 
21458
+ // src/ui/selection-pill-css.ts
21459
+ var selectionPillCss = `
21460
+ .knobs-pick-pill,.knobs-pick-copy{position:absolute;left:0;top:0;width:0;height:0;pointer-events:none;transition:opacity .1s linear}
21461
+ .knobs-pick-pill{background:var(--_k-fill-selected);will-change:transform}
21462
+ .knobs-pick-copy{overflow:hidden;will-change:transform}
21463
+ .knobs-pick-copy-rows{position:absolute;left:0;top:0;width:0;height:0}
21464
+ :is(.knobs-pick-pill,.knobs-pick-copy)[data-off]{opacity:0}
21465
+ `;
21466
+
21212
21467
  // src/ui/look.ts
21213
21468
  function injectUiStyles(doc) {
21214
21469
  injectTokens(doc);
@@ -21271,6 +21526,9 @@ var css3 = `
21271
21526
  --_ku-wash:var(--k-ui-wash,color-mix(in srgb,var(--_k-fill) 70%,transparent));
21272
21527
  --_ku-wash-hi:var(--k-ui-wash-hi,var(--_k-fill-selected));
21273
21528
  --_ku-ease:var(--_k-motion-snappy);--_ku-ease-ms:calc(var(--_k-motion-snappy-ms) * 1ms);
21529
+ /* Over a picture: a quiet dark scrim in either appearance (a picture is no lighter in light
21530
+ mode), light type on it, no ring. */
21531
+ --_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);
21274
21532
  /* The card frame (knobs' own look): every leaf pane a rounded card on the page, half a gap
21275
21533
  inside its slot, so panes sit a gap apart with the dividers' grips between them; tree rows
21276
21534
  inset as soft rounded washes. --k-frame: flat (labPreset) puts panes edge to edge on
@@ -21295,6 +21553,12 @@ var css3 = `
21295
21553
  --_ku-pane-bg and --_ku-pane-ring so this reaches them. */
21296
21554
  :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;
21297
21555
  --_ku-divider:var(--_k-split-gutter);--_kd-bg:transparent}
21556
+ /* What draws its box without those: a picker's card keeps its fill as the highlight but drops its
21557
+ ring; a timeline's track drops its well for a hairline under the lane; an inline panel sheds its
21558
+ card (--k-chrome 0). A full-window step is the one raised exception (it is the surface). */
21559
+ :is(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-picker-card,.knobs-ui[data-nested] .knobs-picker-card{box-shadow:none}
21560
+ :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)}
21561
+ :where(.knobs-surface,.knobs-pane:not([data-nested])>.knobs-pane-clip) .knobs-panel.knobs-inline:not(.knobs-page){--k-chrome:0}
21298
21562
  /* Only the pieces' own elements take their type and resets: what you put in a pane is yours. */
21299
21563
  .knobs-ui{box-sizing:border-box}
21300
21564
  :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}
@@ -21429,11 +21693,9 @@ var css3 = `
21429
21693
  .knobs-tree:focus .knobs-tree-row[data-active][aria-selected="true"]::before{background:var(--_ku-select-focus)}
21430
21694
  .knobs-tree-row[data-drop="into"]::before{opacity:1;background:color-mix(in srgb,var(--_k-accent) 30%,transparent)}
21431
21695
  .knobs-tree-row[data-dragged]{opacity:.38}
21432
- /* glide: one highlight under the selected row that glides between rows (the row's own stays off). */
21433
- .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);
21434
- opacity:0;pointer-events:none;will-change:transform;transition:opacity .15s ease-out,background .12s}
21435
- .knobs-tree-cursor[data-on]{opacity:1}
21436
- .knobs-tree:focus .knobs-tree-cursor{background:var(--_ku-select-focus)}
21696
+ /* glide: the selection pill on the selected row, in the selection's colour (the row's own stays off). */
21697
+ .knobs-tree>.knobs-pick-pill{background:var(--_ku-select)}
21698
+ .knobs-tree:focus>.knobs-pick-pill{background:var(--_ku-select-focus)}
21437
21699
  .knobs-tree[data-glide] .knobs-tree-row[aria-selected="true"]::before{opacity:0!important}
21438
21700
  .knobs-tree-row>*{position:relative}
21439
21701
  .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)}
@@ -21465,22 +21727,20 @@ var css3 = `
21465
21727
  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)}
21466
21728
  .knobs-tree-empty{padding:10px var(--_k-inset);color:var(--_k-fg-3)}
21467
21729
 
21468
- /* Timeline: a transport (play, the time) beside a track: the ruler's canvas over a lane of
21469
- markers, ranges as soft bands, and the playhead, a hairline in the accent with a cap. */
21730
+ /* Timeline: play beside a track: the ruler's canvas over a lane of markers, ranges as soft bands,
21731
+ and the playhead, a hairline in the accent with a cap. No clock: the time shows under the
21732
+ pointer on the track (the tag), in seconds, while hovering or scrubbing. */
21470
21733
  :where(.knobs-timeline,.knobs-timeline *){box-sizing:border-box}
21471
21734
  .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;
21472
21735
  font:var(--_k-weight) var(--_k-font-size)/1.35 var(--_k-font);color:var(--_k-fg);letter-spacing:normal;text-align:left}
21473
21736
  :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}
21474
- .knobs-tl-transport{flex:none;display:flex;align-items:center;gap:8px;min-width:96px}
21737
+ .knobs-tl-transport{flex:none;display:flex;align-items:center}
21475
21738
  .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);
21476
21739
  background:var(--_k-fill-selected);color:var(--_k-fg);cursor:pointer;transition:background .15s,transform .15s var(--_ku-ease)}
21477
21740
  .knobs-ui .knobs-tl-play:hover{background:color-mix(in srgb,var(--_k-fg) 16%,transparent)}
21478
21741
  .knobs-ui .knobs-tl-play:active{transform:scale(.94)}
21479
21742
  .knobs-tl-play svg{width:12px;height:12px}
21480
21743
  .knobs-timeline[data-playing] .knobs-tl-play{background:var(--_k-accent);color:var(--_k-bg)}
21481
- .knobs-tl-time{display:flex;flex-direction:column;line-height:1.2;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
21482
- .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}
21483
- .knobs-tl-sub{font-size:var(--_k-text-meta);color:var(--_k-fg-3);white-space:nowrap}
21484
21744
  .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;
21485
21745
  transition:background calc(var(--_k-hover-out) * 1ms)}
21486
21746
  .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)}
@@ -21504,7 +21764,8 @@ var css3 = `
21504
21764
  .knobs-tl-pin{position:absolute;top:13px;left:-5px;width:10px;height:10px;pointer-events:auto;cursor:grab}
21505
21765
  .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)}
21506
21766
  .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);
21507
- 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}
21767
+ font-size:var(--_k-text-meta);line-height:17px;white-space:nowrap;pointer-events:auto;cursor:grab;transition:background .15s,color .15s}
21768
+ .knobs-tl-mtext{display:block;overflow:hidden;white-space:nowrap}
21508
21769
  .knobs-tl-marker[data-selected]{--knobs-tl-c:var(--_k-accent)}
21509
21770
  .knobs-tl-marker[data-selected] .knobs-tl-mlabel{background:color-mix(in srgb,var(--_k-accent) 24%,var(--_k-fill-selected))}
21510
21771
  .knobs-tl-marker[data-selected] .knobs-tl-pin::after{scale:1.2}
@@ -21523,9 +21784,42 @@ var css3 = `
21523
21784
  .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}
21524
21785
  .knobs-tl-track:hover .knobs-tl-hover{opacity:1}
21525
21786
  .knobs-timeline[data-scrubbing] .knobs-tl-hover{opacity:0}
21787
+ .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}
21788
+ .knobs-tl-tag[data-on]{opacity:1}
21789
+ /* The lane (variant: "lane"): one slim track of ticks under a compare or a video; markers are
21790
+ faint chapter ticks, the current one in text colour; nothing to edit. */
21791
+ .knobs-ui.knobs-timeline[data-variant="lane"]{height:auto;min-height:0;padding:0;gap:var(--_k-gap)}
21792
+ .knobs-timeline[data-variant="lane"] .knobs-tl-track{height:var(--_k-control-h)}
21793
+ .knobs-timeline[data-variant="lane"] .knobs-tl-ruler{height:100%}
21794
+ .knobs-timeline[data-variant="lane"] .knobs-tl-lane{top:0}
21795
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker{top:0}
21796
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker::before{top:0;bottom:50%;left:-.75px;width:1.5px;background:var(--_k-fg-3)}
21797
+ .knobs-timeline[data-variant="lane"] .knobs-tl-marker[data-current]::before{background:var(--_k-fg)}
21798
+ .knobs-timeline[data-variant="lane"] :is(.knobs-tl-pin,.knobs-tl-mlabel,.knobs-tl-rlabel){display:none}
21799
+ .knobs-timeline[data-variant="lane"] .knobs-tl-head::before{top:0}
21800
+ .knobs-timeline[data-variant="lane"] .knobs-tl-head::after{content:none}
21801
+ .knobs-timeline[data-variant="lane"] .knobs-tl-tag{top:2px;bottom:auto}
21802
+ /* The line (variant: "line"): a player's progress under its media. One 2px line, fg-3 with fg
21803
+ filling it, a segment per chapter; it thickens under the pointer and while scrubbing. The
21804
+ chapter and time under the pointer float over it; nothing else shows. */
21805
+ .knobs-ui.knobs-timeline[data-variant="line"]{height:auto;min-height:0;padding:0;gap:0}
21806
+ .knobs-timeline[data-variant="line"] .knobs-tl-transport{display:none}
21807
+ .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}
21808
+ .knobs-timeline[data-variant="line"] :is(.knobs-tl-ruler,.knobs-tl-lane,.knobs-tl-hover,.knobs-tl-head,.knobs-tl-rlabel){display:none}
21809
+ .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)}
21810
+ .knobs-tl-seg{position:absolute;left:0;top:0;height:100%;overflow:hidden;border-radius:1px;background:var(--_k-fg-3);will-change:transform}
21811
+ .knobs-tl-seg-fill{position:absolute;inset:0;background:var(--_k-fg);transform-origin:0 0;will-change:transform}
21812
+ .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}
21813
+ .knobs-timeline[data-variant="line"] .knobs-tl-range{top:3px;bottom:3px;border-radius:3px}
21814
+ .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)}
21815
+ .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);
21816
+ font-size:var(--_k-type-small);line-height:var(--_k-leading-small);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}
21817
+ .knobs-tl-tag-label{color:var(--_k-fg-2)}
21818
+ .knobs-timeline[data-variant="line"] .knobs-tl-tag-label{color:var(--_ku-on-media-2)}
21819
+ .knobs-tl-tag-label:empty{display:none}
21526
21820
  @media(prefers-reduced-motion:reduce){.knobs-timeline :where(*,::before,::after){transition:none!important}}
21527
21821
  @media(prefers-reduced-motion:reduce){:is(.knobs-tree,.knobs-split>.knobs-divider,.knobs-pane-head,.knobs-pane-rail) :where(*,::before,::after){transition:none!important}}
21528
- `, uiCss = () => aliased(css3 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss) + rolling_css_default, lookDefaults, svg = (body, box = 16) => `<svg viewBox="0 0 ${box} ${box}" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`, ICONS2;
21822
+ `, uiCss = () => aliased(css3 + readoutCss + overlayCss + bentoCss + activityCss + pickerCss + compareCss + playerCss + selectionPillCss) + rolling_css_default, lookDefaults, svg = (body, box = 16) => `<svg viewBox="0 0 ${box} ${box}" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`, ICONS2;
21529
21823
  var init_look = __esm(() => {
21530
21824
  init_appearance();
21531
21825
  init_activity_css();
@@ -25474,7 +25768,7 @@ function buildPanel(controls, host, options) {
25474
25768
  titleEl.title = `${titleEl.textContent ? `${titleEl.textContent}
25475
25769
  ` : ""}Press ${show("help")} for keyboard shortcuts`;
25476
25770
  const presetLabel = (id) => options.presetLabels?.[id] ?? (id === "default" ? options.defaultLabel : undefined) ?? controls.presetLabel(id);
25477
- const presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel);
25771
+ const presetStrip = createPresetStrip(doc, controls, showVariants, run, () => wakeGpu(), presetLabel, (entries) => presetsTouchTab(entries));
25478
25772
  const tabStrip = createTabStrip(doc, (id, animate) => selectTab(id, animate));
25479
25773
  let activeTab;
25480
25774
  const searchOptions = typeof options.search === "object" ? options.search : {};
@@ -25517,6 +25811,10 @@ function buildPanel(controls, host, options) {
25517
25811
  ];
25518
25812
  const body = el("div");
25519
25813
  const tabMotion = tabTransition(body, root, () => wakeGpu());
25814
+ const pinnedBody = el("div");
25815
+ pinnedBody.className = "knobs-pinned";
25816
+ if (!page)
25817
+ body.append(presetStrip.element);
25520
25818
  const blocks = [];
25521
25819
  const cards = [];
25522
25820
  const groupResetButtons = new Map;
@@ -25998,8 +26296,12 @@ function buildPanel(controls, host, options) {
25998
26296
  section: field.section
25999
26297
  });
26000
26298
  }
26001
- return gatherSubsections(specs);
26299
+ const planned = gatherSubsections(specs);
26300
+ if (page || !planned.some(pinnedRow))
26301
+ return planned;
26302
+ return [...planned.filter(pinnedRow), ...planned.filter((spec) => !pinnedRow(spec))];
26002
26303
  };
26304
+ const pinnedRow = (r) => !page && controls.isPinned(r.keys[0] ?? r.id);
26003
26305
  const gatherSubsections = (specs) => {
26004
26306
  const out = [];
26005
26307
  for (let i = 0;i < specs.length; ) {
@@ -26645,13 +26947,15 @@ function buildPanel(controls, host, options) {
26645
26947
  const runs = [];
26646
26948
  for (const r of next) {
26647
26949
  const last = runs.at(-1);
26648
- if (last && last.group === r.group)
26950
+ const pinned = pinnedRow(r);
26951
+ if (last && last.group === r.group && last.pinned === pinned)
26649
26952
  last.rows.push(r);
26650
26953
  else
26651
- runs.push({ group: r.group, rows: [r] });
26954
+ runs.push({ group: r.group, pinned, rows: [r] });
26652
26955
  }
26653
26956
  const free = blocks.filter((b) => !b.leaving);
26654
26957
  const chosen = [];
26958
+ const pinnedBlocks = new Set;
26655
26959
  const usedSubheads = new Set;
26656
26960
  for (const run of runs) {
26657
26961
  const at = free.findIndex((b) => b.group === run.group);
@@ -26669,6 +26973,8 @@ function buildPanel(controls, host, options) {
26669
26973
  }
26670
26974
  }
26671
26975
  chosen.push(block);
26976
+ if (run.pinned)
26977
+ pinnedBlocks.add(block);
26672
26978
  const nodes = [];
26673
26979
  let under;
26674
26980
  for (const r of run.rows) {
@@ -26700,7 +27006,9 @@ function buildPanel(controls, host, options) {
26700
27006
  gpuRows.splice(gi, 1);
26701
27007
  subheads.delete(id);
26702
27008
  }
26703
- arrange(body, chosen.flatMap((b) => b.head ? [b.head, b.card] : [b.card]));
27009
+ const nodesOf = (list) => list.flatMap((b) => b.head ? [b.head, b.card] : [b.card]);
27010
+ arrange(pinnedBody, nodesOf(chosen.filter((b) => pinnedBlocks.has(b))));
27011
+ arrange(body, [presetStrip.element, ...nodesOf(chosen.filter((b) => !pinnedBlocks.has(b)))]);
26704
27012
  for (const block of free) {
26705
27013
  block.leaving = true;
26706
27014
  const fold = () => {
@@ -26831,19 +27139,28 @@ function buildPanel(controls, host, options) {
26831
27139
  return f.tab;
26832
27140
  return;
26833
27141
  };
26834
- const tabOf = (r) => r.field.tab ?? (r.group !== undefined ? groupTab(r.group) : undefined);
27142
+ const tabOf = (r) => pinnedRow(r) ? undefined : r.field.tab ?? (r.group !== undefined ? groupTab(r.group) : undefined);
26835
27143
  const tabList = () => {
26836
27144
  if (page)
26837
27145
  return [];
26838
27146
  const list = (options.tabs ?? []).map((t) => typeof t === "string" ? { id: t, label: readableTitle(t) } : { id: t.id, label: t.label ?? readableTitle(t.id) });
26839
27147
  const seen = new Set(list.map((t) => t.id));
26840
- for (const f of Object.values(controls.schema))
26841
- if (f.tab !== undefined && !seen.has(f.tab)) {
27148
+ for (const [key, f] of Object.entries(controls.schema))
27149
+ if (f.tab !== undefined && !seen.has(f.tab) && !controls.isPinned(key)) {
26842
27150
  seen.add(f.tab);
26843
27151
  list.push({ id: f.tab, label: readableTitle(f.tab) });
26844
27152
  }
26845
27153
  return list;
26846
27154
  };
27155
+ function presetsTouchTab(entries) {
27156
+ if (page || activeTab === undefined)
27157
+ return true;
27158
+ const keys = rows.flatMap((r) => {
27159
+ const tab = tabOf(r);
27160
+ return !pinnedRow(r) && (tab === undefined || tab === activeTab) ? r.keys : [];
27161
+ });
27162
+ return presetsTouch(entries, keys, controls.locked);
27163
+ }
26847
27164
  function syncTabs() {
26848
27165
  const list = tabList();
26849
27166
  const has = (id) => id !== undefined && list.some((t) => t.id === id);
@@ -26906,6 +27223,7 @@ function buildPanel(controls, host, options) {
26906
27223
  want.set(b.head, any);
26907
27224
  want.set(b.card, any && !(b.group !== undefined && folds.get(b.group) && !finding));
26908
27225
  }
27226
+ want.set(presetStrip.element, presetStrip.shows());
26909
27227
  return want;
26910
27228
  }
26911
27229
  const shift = (node, show, delay) => {
@@ -26929,6 +27247,9 @@ function buildPanel(controls, host, options) {
26929
27247
  }
26930
27248
  if (tabMotion.running)
26931
27249
  tabMotion.finish();
27250
+ const strip = presetStrip.element;
27251
+ if (strip.hidden === want.get(strip))
27252
+ strip.hidden = !want.get(strip);
26932
27253
  const showing = (n) => !n.hidden && n.dataset.leaving !== "true";
26933
27254
  const set = (node, show, animate) => {
26934
27255
  if (showing(node) === show)
@@ -27215,10 +27536,10 @@ function buildPanel(controls, host, options) {
27215
27536
  root.append(header);
27216
27537
  if (showFooter)
27217
27538
  root.append(popover);
27218
- root.append(tabStrip.element);
27539
+ root.append(pinnedBody, tabStrip.element);
27219
27540
  if (search)
27220
27541
  root.append(search.element);
27221
- root.append(presetStrip.element, body);
27542
+ root.append(body);
27222
27543
  if (panelActions.length > 0)
27223
27544
  root.append(customActions);
27224
27545
  root.append(text, apply, status, live);
@@ -27518,34 +27839,47 @@ function buildPanel(controls, host, options) {
27518
27839
  let lastActionButton;
27519
27840
  const stops = () => {
27520
27841
  const list = [];
27842
+ const strips = [];
27521
27843
  if (visible(tabStrip.element))
27522
- list.push({
27523
- contains: (n) => tabStrip.element.contains(n),
27524
- focus: (o) => tabStrip.focus(o)
27844
+ strips.push({
27845
+ at: tabStrip.element,
27846
+ stops: [{ contains: (n) => tabStrip.element.contains(n), focus: (o) => tabStrip.focus(o) }]
27525
27847
  });
27526
27848
  if (visible(presetStrip.element))
27527
- list.push({
27528
- contains: (n) => presetStrip.element.contains(n),
27529
- focus: (o) => presetStrip.element.focus(o)
27849
+ strips.push({
27850
+ at: presetStrip.element,
27851
+ stops: [
27852
+ {
27853
+ contains: (n) => presetStrip.element.contains(n),
27854
+ focus: (o) => presetStrip.element.focus(o)
27855
+ }
27856
+ ]
27530
27857
  });
27531
27858
  if (page) {
27859
+ for (const strip of strips)
27860
+ list.push(...strip.stops);
27532
27861
  for (const stop of pageStops())
27533
27862
  list.push({ contains: (n) => stop.contains(n), focus: (o) => stop.focus(o) });
27534
27863
  return list;
27535
27864
  }
27865
+ const stopOf = (stop) => ({
27866
+ contains: (n) => stop.contains(n),
27867
+ focus: (o) => stop.focus(o)
27868
+ });
27869
+ const shown = (stops) => stops.filter((s) => !s.closest('[hidden],[data-presence="out"]')).map(stopOf);
27536
27870
  const runs = rows.map((r) => ({
27537
27871
  at: r.row,
27538
- stops: r.editor?.stops() ?? [r.input]
27872
+ stops: shown(r.editor?.stops() ?? [r.input])
27539
27873
  }));
27540
27874
  for (const b of blocks)
27541
27875
  if (b.head?.hasAttribute("data-foldable") && !b.leaving)
27542
- runs.push({ at: b.head, stops: [b.head] });
27876
+ runs.push({ at: b.head, stops: shown([b.head]) });
27543
27877
  for (const sh of subheads.values())
27544
- runs.push({ at: sh.el, stops: [sh.el] });
27878
+ runs.push({ at: sh.el, stops: shown([sh.el]) });
27879
+ runs.push(...strips);
27545
27880
  runs.sort((a, b) => a.at.compareDocumentPosition(b.at) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
27546
27881
  for (const run of runs)
27547
- for (const stop of run.stops.filter((s) => !s.closest('[hidden],[data-presence="out"]')))
27548
- list.push({ contains: (n) => stop.contains(n), focus: (o) => stop.focus(o) });
27882
+ list.push(...run.stops);
27549
27883
  const bar = actionBarButtons();
27550
27884
  if (bar.length)
27551
27885
  list.push({
@@ -28999,6 +29333,7 @@ function buildPanel(controls, host, options) {
28999
29333
  id: controls.id,
29000
29334
  root,
29001
29335
  body,
29336
+ pinned: pinnedBody,
29002
29337
  inline,
29003
29338
  header,
29004
29339
  titleEl,