@teamofsilicons/silicon-ui 0.2.0 → 0.4.0

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 (163) hide show
  1. package/README.md +4 -3
  2. package/check.mjs +14 -5
  3. package/components/activity-heatmap/activity-heatmap.module.css +4 -4
  4. package/components/alert/alert.module.css +1 -1
  5. package/components/animated-counter/animated-counter.js +1 -1
  6. package/components/announcement-bar/announcement-bar.module.css +6 -6
  7. package/components/avatar/avatar.d.ts +2 -2
  8. package/components/avatar/avatar.js +1 -1
  9. package/components/avatar/avatar.module.css +5 -2
  10. package/components/avatar-group/avatar-group.module.css +4 -4
  11. package/components/badge/badge.module.css +4 -4
  12. package/components/bar-chart/bar-chart.js +1 -1
  13. package/components/bar-chart/bar-chart.module.css +4 -4
  14. package/components/billing-toggle/billing-toggle.js +2 -2
  15. package/components/billing-toggle/billing-toggle.module.css +10 -10
  16. package/components/bottom-sheet/bottom-sheet.js +1 -1
  17. package/components/bottom-sheet/bottom-sheet.module.css +4 -5
  18. package/components/breadcrumb/breadcrumb.d.ts +8 -2
  19. package/components/breadcrumb/breadcrumb.js +21 -9
  20. package/components/breadcrumb/breadcrumb.module.css +7 -3
  21. package/components/brush-chart/brush-chart.module.css +9 -9
  22. package/components/button/button.d.ts +7 -2
  23. package/components/button/button.js +3 -1
  24. package/components/button/button.module.css +10 -1
  25. package/components/button-group/button-group.module.css +7 -7
  26. package/components/calendar/calendar.js +15 -5
  27. package/components/calendar/calendar.module.css +13 -10
  28. package/components/card/card.js +4 -4
  29. package/components/card/card.module.css +2 -2
  30. package/components/card-stack/card-stack.js +2 -2
  31. package/components/card-stack/card-stack.module.css +2 -3
  32. package/components/carousel/carousel.module.css +3 -3
  33. package/components/chat-thread/chat-thread.module.css +41 -41
  34. package/components/checkbox/checkbox.module.css +1 -1
  35. package/components/chip-group/chip-group.js +1 -1
  36. package/components/chip-group/chip-group.module.css +2 -2
  37. package/components/code-block/code-block.module.css +7 -14
  38. package/components/color-picker/color-picker.module.css +20 -21
  39. package/components/combobox/combobox.js +5 -1
  40. package/components/combobox/combobox.module.css +2 -2
  41. package/components/command-palette/command-palette.js +18 -11
  42. package/components/command-palette/command-palette.module.css +18 -18
  43. package/components/comment-thread/comment-thread.module.css +35 -35
  44. package/components/confirm-morph/confirm-morph.js +5 -5
  45. package/components/confirm-morph/confirm-morph.module.css +9 -9
  46. package/components/context-menu/context-menu.js +10 -28
  47. package/components/context-menu/context-menu.module.css +3 -3
  48. package/components/copy-button/copy-button.js +4 -4
  49. package/components/countdown/countdown.module.css +4 -4
  50. package/components/date-picker/date-picker.js +3 -3
  51. package/components/date-picker/date-picker.module.css +7 -7
  52. package/components/date-range-picker/date-range-picker.js +13 -10
  53. package/components/date-range-picker/date-range-picker.module.css +23 -22
  54. package/components/dialog/dialog.module.css +2 -2
  55. package/components/donut-chart/donut-chart.module.css +5 -5
  56. package/components/drawer/drawer.module.css +7 -7
  57. package/components/dropdown-menu/dropdown-menu.d.ts +71 -1
  58. package/components/dropdown-menu/dropdown-menu.js +100 -6
  59. package/components/dropdown-menu/dropdown-menu.module.css +15 -5
  60. package/components/elastic-slider/elastic-slider.js +2 -2
  61. package/components/elastic-slider/elastic-slider.module.css +4 -4
  62. package/components/expandable-card/expandable-card.js +1 -1
  63. package/components/expandable-card/expandable-card.module.css +1 -1
  64. package/components/expanding-button-group/expanding-button-group.module.css +3 -3
  65. package/components/expanding-search/expanding-search.js +3 -3
  66. package/components/expanding-search/expanding-search.module.css +9 -9
  67. package/components/file-dropzone/file-dropzone.js +3 -3
  68. package/components/file-dropzone/file-dropzone.module.css +21 -21
  69. package/components/file-upload/file-upload.module.css +7 -7
  70. package/components/filter-toolbar/filter-toolbar.js +7 -3
  71. package/components/filter-toolbar/filter-toolbar.module.css +9 -9
  72. package/components/floating-button-group/floating-button-group.module.css +7 -7
  73. package/components/gauge/gauge.js +1 -1
  74. package/components/gauge/gauge.module.css +2 -2
  75. package/components/hover-card/hover-card.js +2 -2
  76. package/components/hover-card/hover-card.module.css +2 -2
  77. package/components/image-compare/image-compare.module.css +4 -4
  78. package/components/inline-edit/inline-edit.module.css +12 -12
  79. package/components/json-viewer/json-viewer.module.css +12 -12
  80. package/components/line-chart/line-chart.js +1 -1
  81. package/components/line-chart/line-chart.module.css +8 -8
  82. package/components/mention-input/mention-input.js +4 -2
  83. package/components/mention-input/mention-input.module.css +8 -8
  84. package/components/metric-card/metric-card.module.css +1 -1
  85. package/components/money-input/money-input.js +6 -3
  86. package/components/money-input/money-input.module.css +10 -10
  87. package/components/morph-select/morph-select.js +7 -5
  88. package/components/morph-select/morph-select.module.css +13 -13
  89. package/components/multi-select/multi-select.js +4 -2
  90. package/components/multi-select/multi-select.module.css +6 -6
  91. package/components/nav-list/nav-list.d.ts +64 -0
  92. package/components/nav-list/nav-list.js +96 -0
  93. package/components/nav-list/nav-list.module.css +25 -0
  94. package/components/notification-center/notification-center.module.css +27 -27
  95. package/components/number-field/number-field.js +3 -3
  96. package/components/number-field/number-field.module.css +3 -3
  97. package/components/otp-input/otp-input.module.css +4 -4
  98. package/components/pagination/pagination.module.css +1 -1
  99. package/components/password-field/password-field.module.css +2 -2
  100. package/components/password-strength/password-strength.module.css +8 -8
  101. package/components/phone-input/phone-input.js +9 -7
  102. package/components/phone-input/phone-input.module.css +19 -19
  103. package/components/popover/popover.module.css +2 -2
  104. package/components/progress/progress.module.css +1 -1
  105. package/components/radio-cards/radio-cards.module.css +8 -8
  106. package/components/radio-group/radio-group.js +4 -1
  107. package/components/radio-group/radio-group.module.css +4 -3
  108. package/components/resizable-panels/resizable-panels.module.css +5 -5
  109. package/components/rich-text-editor/rich-text-editor.js +3 -3
  110. package/components/rich-text-editor/rich-text-editor.module.css +12 -12
  111. package/components/ridgeline/ridgeline.module.css +7 -7
  112. package/components/scroll-area/scroll-area.module.css +8 -8
  113. package/components/segmented-control/segmented-control.d.ts +3 -1
  114. package/components/segmented-control/segmented-control.js +5 -2
  115. package/components/segmented-control/segmented-control.module.css +9 -4
  116. package/components/select/select.module.css +5 -5
  117. package/components/shortcut-recorder/shortcut-recorder.js +5 -5
  118. package/components/shortcut-recorder/shortcut-recorder.module.css +16 -16
  119. package/components/signature-pad/signature-pad.module.css +11 -11
  120. package/components/slider/slider.module.css +4 -4
  121. package/components/slope-chart/slope-chart.module.css +7 -7
  122. package/components/slot-text/slot-text.js +2 -1
  123. package/components/sortable-data-table/sortable-data-table.module.css +4 -4
  124. package/components/sparkline/sparkline.js +1 -1
  125. package/components/split-button/split-button.module.css +6 -6
  126. package/components/stepper/stepper.module.css +3 -3
  127. package/components/streamgraph/streamgraph.module.css +7 -7
  128. package/components/swipe-actions/swipe-actions.js +2 -2
  129. package/components/swipe-actions/swipe-actions.module.css +5 -5
  130. package/components/switch/switch.js +2 -2
  131. package/components/switch/switch.module.css +1 -1
  132. package/components/tabs/tabs.d.ts +9 -2
  133. package/components/tabs/tabs.js +11 -5
  134. package/components/tabs/tabs.module.css +10 -3
  135. package/components/tag-input/tag-input.module.css +4 -4
  136. package/components/text-reveal/text-reveal.js +1 -1
  137. package/components/time-picker/time-picker.js +4 -2
  138. package/components/time-picker/time-picker.module.css +3 -3
  139. package/components/timeline/timeline.js +4 -4
  140. package/components/timeline/timeline.module.css +3 -3
  141. package/components/toast/toast.js +1 -1
  142. package/components/toast/toast.module.css +5 -5
  143. package/components/toast-stack/toast-stack.js +1 -1
  144. package/components/toast-stack/toast-stack.module.css +5 -5
  145. package/components/tooltip/tooltip.module.css +1 -1
  146. package/components/tree-view/tree-view.js +16 -12
  147. package/components/tree-view/tree-view.module.css +10 -8
  148. package/components/treemap/treemap.module.css +13 -13
  149. package/components/usage-meter/usage-meter.js +1 -1
  150. package/components/usage-meter/usage-meter.module.css +5 -5
  151. package/components/user-menu/user-menu.js +16 -14
  152. package/components/user-menu/user-menu.module.css +22 -22
  153. package/components/waffle-chart/waffle-chart.module.css +7 -7
  154. package/components.json +416 -1
  155. package/eslint.js +35 -3
  156. package/lib/fluid-hover.d.ts +83 -0
  157. package/lib/fluid-hover.js +184 -0
  158. package/lib/menu-highlight.d.ts +24 -11
  159. package/lib/menu-highlight.js +29 -28
  160. package/lib/motion-tokens.d.ts +76 -18
  161. package/lib/motion-tokens.js +45 -10
  162. package/package.json +1 -1
  163. package/styles.css +50 -7
@@ -14,17 +14,17 @@
14
14
  .trigger[data-state="open"] .chevron { transform: rotate(180deg); }
15
15
  .hint { color: var(--text-muted); font-size: var(--text-xs); }
16
16
  /* Radix holds the animation until the menu is positioned, so it always grows from the trigger edge. */
17
- .content { --menu-x: 0px; --menu-y: -6px; box-sizing: border-box; z-index: 1000; width: var(--radix-select-trigger-width); min-width: var(--radix-select-trigger-width); max-width: min(24rem, calc(100vw - 20px)); max-height: min(320px, var(--radix-select-content-available-height)); overflow: hidden; border: 1px solid var(--border); border-radius: calc(var(--radius-control) + 2px); padding: 6px; background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transform-origin: var(--radix-select-content-transform-origin); animation: select-in var(--duration-standard) var(--ease-enter) both; }
17
+ .content { --menu-x: 0px; --menu-y: -6px; box-sizing: border-box; z-index: var(--layer-dropdown); width: var(--radix-select-trigger-width); min-width: var(--radix-select-trigger-width); max-width: min(24rem, calc(100vw - 20px)); max-height: min(320px, var(--radix-select-content-available-height)); overflow: hidden; border: 1px solid var(--border); border-radius: calc(var(--radius-control) + var(--space-hair)); padding: var(--space-related); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transform-origin: var(--radix-select-content-transform-origin); animation: select-in var(--duration-standard) var(--ease-enter) both; }
18
18
  .content[data-side="top"] { --menu-y: 6px; }
19
19
  .content[data-side="left"] { --menu-x: 6px; --menu-y: 0px; }
20
20
  .content[data-side="right"] { --menu-x: -6px; --menu-y: 0px; }
21
21
  .content[data-state="closed"] { animation: select-out var(--duration-instant) var(--ease-standard) both; }
22
- .viewport { position: relative; padding: 2px 0; }
22
+ .viewport { position: relative; padding: var(--space-hair) 0; }
23
23
  /* One highlight glides between options for the pointer and jumps instantly for the keyboard (lib/menu-highlight). */
24
- .highlight { position: absolute; top: 0; right: 0; left: 0; border-radius: calc(var(--radius-control) - 4px); background: var(--surface-muted); opacity: 0; pointer-events: none; }
25
- .item { position: relative; display: flex; align-items: center; min-height: 36px; border-radius: calc(var(--radius-control) - 4px); padding: 0 34px 0 11px; color: var(--foreground); font-size: var(--text-sm); cursor: default; outline: none; user-select: none; transition: background-color var(--duration-instant) var(--ease-standard), color var(--duration-instant) var(--ease-standard); }
24
+ .highlight { position: absolute; top: 0; right: 0; left: 0; border-radius: calc(var(--radius-control) - var(--space-tight)); background: var(--surface-muted); opacity: 0; pointer-events: none; }
25
+ .item { position: relative; display: flex; align-items: center; min-height: 36px; border-radius: calc(var(--radius-control) - var(--space-tight)); padding: 0 var(--space-block) 0 var(--space-group); color: var(--foreground); font-size: var(--text-sm); cursor: default; outline: none; user-select: none; transition: background-color var(--duration-instant) var(--ease-standard), color var(--duration-instant) var(--ease-standard); }
26
26
  .item[data-disabled] { opacity: .45; }
27
- .indicator { position: absolute; right: 10px; display: inline-flex; align-items: center; color: var(--foreground); animation: indicator-in var(--duration-standard) var(--ease-enter) 40ms both; }
27
+ .indicator { position: absolute; right: var(--space-group); display: inline-flex; align-items: center; color: var(--foreground); animation: indicator-in var(--duration-standard) var(--ease-enter) 40ms both; }
28
28
  .scrollButton { display: grid; height: 28px; place-items: center; color: var(--text-muted); }
29
29
  @keyframes select-in { from { opacity: 0; transform: translate(var(--menu-x), var(--menu-y)) scale(.97); } }
30
30
  @keyframes select-out { to { opacity: 0; transform: translate(calc(var(--menu-x) / 2), calc(var(--menu-y) / 2)) scale(.98); } }
@@ -185,10 +185,10 @@ const RESERVED = [
185
185
  ];
186
186
  const chip = {
187
187
  enter: { opacity: 0, scale: .72, y: 4, filter: `blur(${motionTokens.blur.subtle}px)` },
188
- rest: (index) => ({ opacity: 1, scale: 1, y: 0, filter: "blur(0px)", transition: { ...motionTokens.spring.snappy, delay: index * motionTokens.stagger.item, opacity: { duration: .14, delay: index * motionTokens.stagger.item }, filter: { duration: .16, delay: index * motionTokens.stagger.item } } }),
189
- exit: { opacity: 0, scale: .8, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: .1 } },
188
+ rest: (index) => ({ opacity: 1, scale: 1, y: 0, filter: "blur(0px)", transition: { ...motionTokens.spring.snappy, delay: index * motionTokens.stagger.item, opacity: { duration: motionTokens.duration.fast, delay: index * motionTokens.stagger.item }, filter: { duration: motionTokens.duration.fast, delay: index * motionTokens.stagger.item } } }),
189
+ exit: { opacity: 0, scale: .8, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.instant } },
190
190
  };
191
- const chipFade = { enter: { opacity: 0 }, rest: { opacity: 1, scale: 1, y: 0, filter: "none", transition: { duration: .1 } }, exit: { opacity: 0, transition: { duration: .06 } } };
191
+ const chipFade = { enter: { opacity: 0 }, rest: { opacity: 1, scale: 1, y: 0, filter: "none", transition: { duration: motionTokens.duration.instant } }, exit: { opacity: 0, transition: { duration: motionTokens.duration.instant } } };
192
192
  /**
193
193
  * A field that records a keyboard shortcut. Press it, then press the combination: held modifiers light up as caps while you
194
194
  * build the chord, the finished shortcut settles into place, and a combination that is already taken asks before it is used.
@@ -339,7 +339,7 @@ export function ShortcutRecorder({ label, hideLabel = false, value, defaultValue
339
339
  } start(); }, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onBlur: () => { if (recording) {
340
340
  stop();
341
341
  setNudge(null);
342
- } }, children: [_jsx("span", { className: styles.srOnly, children: recording ? "Recording" : shown ? spoken(shown) : placeholder }), _jsxs("span", { className: styles.row, "aria-hidden": "true", children: [recording && _jsx("span", { className: styles.dot, "data-reduced": reduced || undefined }), _jsxs(AnimatePresence, { initial: false, mode: "popLayout", children: [tokens.map((token, index) => _jsx(motion.span, { layout: reduced ? false : "position", custom: recording ? 0 : index, variants: reduced ? chipFade : chip, initial: "enter", animate: "rest", exit: "exit", transition: reduced ? { duration: 0 } : { layout: motionTokens.spring.snappy }, className: styles.chipSlot, children: _jsx(ShortcutKbd, { pressed: recording || (settled > 0 && !pending), "data-tone": pending ? "warning" : undefined, children: token.label }) }, `${token.id}`)), tokens.length === 0 && _jsx(motion.span, { className: styles.placeholder, "data-recording": recording || undefined, initial: reduced ? { opacity: 0 } : { opacity: 0, y: 4, filter: `blur(${motionTokens.blur.subtle}px)` }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, exit: { opacity: 0, transition: { duration: .08 } }, transition: reduced ? { duration: .1 } : { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.enter] }, children: recording ? "Press keys" : placeholder }, recording ? "prompt" : "placeholder")] })] })] }), _jsx("span", { className: styles.actions, children: _jsxs(AnimatePresence, { initial: false, children: [canReset && _jsx(motion.button, { type: "button", className: styles.action, "aria-label": `Reset to ${restoreTo ? formatShortcut(restoreTo, platform).spoken : "none"}`, onClick: () => { commit(restoreTo ?? null); buttonRef.current?.focus(); }, initial: { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: .6, transition: { duration: .1 } }, transition: reduced ? { duration: 0 } : motionTokens.spring.snappy, children: _jsx(RotateCcw, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }, "reset"), canClear && _jsx(motion.button, { type: "button", className: styles.action, "aria-label": "Clear shortcut", onClick: () => { commit(null); buttonRef.current?.focus(); }, initial: { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: .6, transition: { duration: .1 } }, transition: reduced ? { duration: 0 } : motionTokens.spring.snappy, children: _jsx(X, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }, "clear")] }) })] }), _jsx(AnimatePresence, { initial: false, children: message && _jsx(motion.div, { className: styles.messageSlot, initial: reduced ? false : { height: 0, opacity: 0 }, animate: { height: messageHeight ?? "auto", opacity: 1 }, exit: { height: 0, opacity: 0, transition: reduced ? { duration: 0 } : { height: motionTokens.spring.smooth, opacity: { duration: .1 } } }, transition: reduced ? { duration: 0 } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.fast } }, children: _jsx("div", { ref: measureMessage, children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: _jsxs(motion.div, { id: messageId, className: styles.message, "data-tone": message.tone, initial: reduced ? { opacity: 0 } : { opacity: 0, y: "0.35em", filter: `blur(${motionTokens.blur.soft}px)` }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, exit: { opacity: 0, transition: { duration: .08 } }, transition: { duration: reduced ? .1 : motionTokens.duration.standard, ease: [...motionTokens.ease.enter] }, children: [_jsx("span", { className: styles.messageText, children: message.body }), pending && _jsxs("span", { className: styles.messageActions, children: [_jsx("button", { type: "button", className: styles.link, onClick: () => { const taken = pending; commit(taken.shortcut, taken.reserved ? undefined : taken.conflict); buttonRef.current?.focus(); }, children: "Use anyway" }), _jsx("button", { type: "button", className: styles.link, "data-quiet": "", onClick: () => { setPending(null); setAnnouncement("Kept the previous shortcut"); buttonRef.current?.focus(); }, children: "Cancel" })] })] }, message.key) }) }) }, "slot") }), _jsx("span", { className: styles.srOnly, role: "status", "aria-live": "polite", children: announcement })] });
342
+ } }, children: [_jsx("span", { className: styles.srOnly, children: recording ? "Recording" : shown ? spoken(shown) : placeholder }), _jsxs("span", { className: styles.row, "aria-hidden": "true", children: [recording && _jsx("span", { className: styles.dot, "data-reduced": reduced || undefined }), _jsxs(AnimatePresence, { initial: false, mode: "popLayout", children: [tokens.map((token, index) => _jsx(motion.span, { layout: reduced ? false : "position", custom: recording ? 0 : index, variants: reduced ? chipFade : chip, initial: "enter", animate: "rest", exit: "exit", transition: reduced ? { duration: 0 } : { layout: motionTokens.spring.snappy }, className: styles.chipSlot, children: _jsx(ShortcutKbd, { pressed: recording || (settled > 0 && !pending), "data-tone": pending ? "warning" : undefined, children: token.label }) }, `${token.id}`)), tokens.length === 0 && _jsx(motion.span, { className: styles.placeholder, "data-recording": recording || undefined, initial: reduced ? { opacity: 0 } : { opacity: 0, y: 4, filter: `blur(${motionTokens.blur.subtle}px)` }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, exit: { opacity: 0, transition: { duration: motionTokens.duration.instant } }, transition: reduced ? { duration: motionTokens.duration.instant } : { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.enter] }, children: recording ? "Press keys" : placeholder }, recording ? "prompt" : "placeholder")] })] })] }), _jsx("span", { className: styles.actions, children: _jsxs(AnimatePresence, { initial: false, children: [canReset && _jsx(motion.button, { type: "button", className: styles.action, "aria-label": `Reset to ${restoreTo ? formatShortcut(restoreTo, platform).spoken : "none"}`, onClick: () => { commit(restoreTo ?? null); buttonRef.current?.focus(); }, initial: { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: .6, transition: { duration: motionTokens.duration.instant } }, transition: reduced ? { duration: 0 } : motionTokens.spring.snappy, children: _jsx(RotateCcw, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }, "reset"), canClear && _jsx(motion.button, { type: "button", className: styles.action, "aria-label": "Clear shortcut", onClick: () => { commit(null); buttonRef.current?.focus(); }, initial: { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: .6, transition: { duration: motionTokens.duration.instant } }, transition: reduced ? { duration: 0 } : motionTokens.spring.snappy, children: _jsx(X, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }, "clear")] }) })] }), _jsx(AnimatePresence, { initial: false, children: message && _jsx(motion.div, { className: styles.messageSlot, initial: reduced ? false : { height: 0, opacity: 0 }, animate: { height: messageHeight ?? "auto", opacity: 1 }, exit: { height: 0, opacity: 0, transition: reduced ? { duration: 0 } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.instant } } }, transition: reduced ? { duration: 0 } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.fast } }, children: _jsx("div", { ref: measureMessage, children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: _jsxs(motion.div, { id: messageId, className: styles.message, "data-tone": message.tone, initial: reduced ? { opacity: 0 } : { opacity: 0, y: "0.35em", filter: `blur(${motionTokens.blur.soft}px)` }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, exit: { opacity: 0, transition: { duration: motionTokens.duration.instant } }, transition: { duration: reduced ? .1 : motionTokens.duration.standard, ease: [...motionTokens.ease.enter] }, children: [_jsx("span", { className: styles.messageText, children: message.body }), pending && _jsxs("span", { className: styles.messageActions, children: [_jsx("button", { type: "button", className: styles.link, onClick: () => { const taken = pending; commit(taken.shortcut, taken.reserved ? undefined : taken.conflict); buttonRef.current?.focus(); }, children: "Use anyway" }), _jsx("button", { type: "button", className: styles.link, "data-quiet": "", onClick: () => { setPending(null); setAnnouncement("Kept the previous shortcut"); buttonRef.current?.focus(); }, children: "Cancel" })] })] }, message.key) }) }) }, "slot") }), _jsx("span", { className: styles.srOnly, role: "status", "aria-live": "polite", children: announcement })] });
343
343
  }
344
344
  /**
345
345
  * A searchable, grouped cheatsheet. Holding a modifier lights that cap in every row and fades rows that do not use it, so people
@@ -369,7 +369,7 @@ export function ShortcutList({ groups, label = "Keyboard shortcuts", searchable
369
369
  return _jsxs("section", { className: [styles.list, className].filter(Boolean).join(" "), "aria-label": label, children: [searchable && _jsxs("div", { className: styles.searchRow, children: [_jsx(Search, { className: styles.searchIcon, size: 16, strokeWidth: 1.75, "aria-hidden": "true" }), _jsx("input", { ref: searchRef, className: styles.search, type: "search", placeholder: searchPlaceholder, value: query, "aria-label": searchPlaceholder, onChange: event => setQuery(event.target.value), onKeyDown: event => { if (event.key === "Escape" && query) {
370
370
  event.preventDefault();
371
371
  setQuery("");
372
- } }, autoComplete: "off", spellCheck: false }), _jsx("span", { className: styles.count, "aria-live": "polite", children: query ? `${total} ${total === 1 ? "match" : "matches"}` : "" }), _jsx(AnimatePresence, { initial: false, children: query && _jsx(motion.button, { type: "button", className: styles.clear, "aria-label": "Clear search", onClick: () => { setQuery(""); searchRef.current?.focus(); }, initial: { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: .6, transition: { duration: .1 } }, transition: reduced ? { duration: 0 } : motionTokens.spring.snappy, children: _jsx(X, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }, "clear") })] }), _jsxs("div", { className: styles.groups, children: [_jsx(AnimatePresence, { initial: false, children: filtered.map(group => _jsxs(motion.section, { className: styles.group, "aria-label": group.label, initial: reduced ? { opacity: 0 } : { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: reduced ? { opacity: 0 } : { opacity: 0, height: 0 }, transition: rowTransition, children: [_jsx("h4", { className: styles.groupLabel, children: group.label }), _jsx("ul", { className: styles.rows, children: _jsx(AnimatePresence, { initial: false, children: group.items.map(item => {
372
+ } }, autoComplete: "off", spellCheck: false }), _jsx("span", { className: styles.count, "aria-live": "polite", children: query ? `${total} ${total === 1 ? "match" : "matches"}` : "" }), _jsx(AnimatePresence, { initial: false, children: query && _jsx(motion.button, { type: "button", className: styles.clear, "aria-label": "Clear search", onClick: () => { setQuery(""); searchRef.current?.focus(); }, initial: { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: .6, transition: { duration: motionTokens.duration.instant } }, transition: reduced ? { duration: 0 } : motionTokens.spring.snappy, children: _jsx(X, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }, "clear") })] }), _jsxs("div", { className: styles.groups, children: [_jsx(AnimatePresence, { initial: false, children: filtered.map(group => _jsxs(motion.section, { className: styles.group, "aria-label": group.label, initial: reduced ? { opacity: 0 } : { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: reduced ? { opacity: 0 } : { opacity: 0, height: 0 }, transition: rowTransition, children: [_jsx("h4", { className: styles.groupLabel, children: group.label }), _jsx("ul", { className: styles.rows, children: _jsx(AnimatePresence, { initial: false, children: group.items.map(item => {
373
373
  const tokens = shortcutTokens(item.shortcut, platform);
374
374
  const ids = tokens.map(token => token.id);
375
375
  const match = held.size > 0 && ids.every(key => held.has(key));
@@ -1,11 +1,11 @@
1
1
  /* ---------------------------------------------- Kbd ---------------------------------------------- */
2
2
 
3
3
  /* A key cap: a one pixel edge with a slightly heavier bottom, so it reads as a key without a shadow. Held, it drops a pixel and takes the accent. */
4
- .kbd { display: inline-grid; min-width: 26px; height: 26px; box-sizing: border-box; place-items: center; padding: 0 7px 1px; border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); font-family: var(--font-body); font-size: var(--text-xs); font-weight: 500; line-height: 1; letter-spacing: 0; font-variant-numeric: tabular-nums; white-space: nowrap; transition: transform var(--duration-spring) var(--ease-spring), border-color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), border-bottom-width var(--duration-fast) var(--ease-standard); }
5
- .kbd[data-size="sm"] { min-width: 22px; height: 22px; padding: 0 5px 1px; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); font-size: var(--text-xs); }
6
- .kbd[data-pressed] { transform: translateY(1px); border-color: color-mix(in oklab, var(--accent) 55%, var(--border-strong)); border-bottom-width: 1px; background: var(--accent-subtle); color: var(--accent-strong); transition-duration: 90ms; }
4
+ .kbd { display: inline-grid; min-width: 26px; height: 26px; box-sizing: border-box; place-items: center; padding: 0 var(--space-related) 1px; border: 1px solid var(--border-strong); border-bottom-width: 2px; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); font-family: var(--font-body); font-size: var(--text-xs); font-weight: 500; line-height: 1; letter-spacing: 0; font-variant-numeric: tabular-nums; white-space: nowrap; transition: transform var(--duration-spring) var(--ease-spring), border-color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), border-bottom-width var(--duration-fast) var(--ease-standard); }
5
+ .kbd[data-size="sm"] { min-width: 22px; height: 22px; padding: 0 var(--space-tight) 1px; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); font-size: var(--text-xs); }
6
+ .kbd[data-pressed] { transform: translateY(1px); border-color: color-mix(in oklab, var(--accent) 55%, var(--border-strong)); border-bottom-width: 1px; background: var(--accent-subtle); color: var(--accent-strong); transition-duration: var(--duration-fast); }
7
7
  .kbd[data-tone="warning"] { border-color: color-mix(in oklab, var(--warning) 55%, var(--border-strong)); background: color-mix(in oklab, var(--warning) 12%, var(--surface-raised)); color: var(--foreground); }
8
- .keys { display: inline-flex; flex: none; align-items: center; gap: 4px; }
8
+ .keys { display: inline-flex; flex: none; align-items: center; gap: var(--space-tight); }
9
9
 
10
10
  /* ---------------------------------------------- Recorder ---------------------------------------------- */
11
11
 
@@ -19,27 +19,27 @@
19
19
  .control[data-state="conflict"] { border-color: var(--warning); }
20
20
  .field[data-disabled] .control { opacity: .5; background: var(--surface-muted); }
21
21
 
22
- .recorder { display: flex; min-width: 0; height: 100%; flex: 1; align-items: center; padding: 0 6px 0 9px; border: 0; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: transparent; color: var(--foreground); cursor: pointer; font: inherit; text-align: left; -webkit-tap-highlight-color: transparent; touch-action: manipulation; outline: none; }
22
+ .recorder { display: flex; min-width: 0; height: 100%; flex: 1; align-items: center; padding: 0 var(--space-related) 0 var(--space-related); border: 0; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: transparent; color: var(--foreground); cursor: pointer; font: inherit; text-align: left; -webkit-tap-highlight-color: transparent; touch-action: manipulation; outline: none; }
23
23
  .recorder:disabled { cursor: not-allowed; }
24
- .row { position: relative; display: flex; min-width: 0; align-items: center; gap: 4px; }
24
+ .row { position: relative; display: flex; min-width: 0; align-items: center; gap: var(--space-tight); }
25
25
  .chipSlot { display: inline-flex; }
26
- .placeholder { padding-left: 3px; color: var(--text-muted); font-size: var(--text-sm); white-space: nowrap; }
26
+ .placeholder { padding-left: var(--space-tight); color: var(--text-muted); font-size: var(--text-sm); white-space: nowrap; }
27
27
  .placeholder[data-recording] { color: var(--text-secondary); }
28
28
  /* A recording light: steady in reduced motion, a slow breath otherwise. */
29
- .dot { width: 7px; height: 7px; flex: none; margin: 0 6px 0 3px; border-radius: 50%; corner-shape: round; background: var(--accent); animation: breathe 1.4s var(--ease-in-out) infinite; }
29
+ .dot { width: 7px; height: 7px; flex: none; margin: 0 var(--space-related) 0 var(--space-tight); border-radius: 50%; corner-shape: round; background: var(--accent); animation: breathe 1.4s var(--ease-in-out) infinite; }
30
30
  .dot[data-reduced] { animation: none; }
31
31
  @keyframes breathe { 50% { opacity: .35; } }
32
32
 
33
- .actions { display: flex; flex: none; align-items: center; gap: 2px; padding-right: 4px; }
34
- .action { display: grid; width: 32px; height: 32px; place-items: center; padding: 0; border: 0; border-radius: calc(var(--radius-control) - 5px); corner-shape: var(--corner-shape); background: transparent; color: var(--text-muted); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard); }
33
+ .actions { display: flex; flex: none; align-items: center; gap: var(--space-hair); padding-right: var(--space-tight); }
34
+ .action { display: grid; width: 32px; height: 32px; place-items: center; padding: 0; border: 0; border-radius: calc(var(--radius-control) - var(--space-tight)); corner-shape: var(--corner-shape); background: transparent; color: var(--text-muted); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard); }
35
35
  @media (hover: hover) and (pointer: fine) { .action:hover { background: var(--surface-muted); color: var(--foreground); } }
36
36
  .action:active, .action:focus-visible { background: var(--surface-muted); color: var(--foreground); outline: none; }
37
37
 
38
38
  .messageSlot { position: relative; overflow: hidden; }
39
- .message { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 2px var(--space-group); padding-top: var(--space-related); font-size: var(--text-xs); line-height: var(--leading-body); }
39
+ .message { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-hair) var(--space-group); padding-top: var(--space-related); font-size: var(--text-xs); line-height: var(--leading-body); }
40
40
  .message[data-tone="hint"] { color: var(--text-muted); }
41
41
  .message[data-tone="warning"] { color: var(--text-secondary); }
42
- .message[data-tone="warning"] .messageText::before { content: ""; display: inline-block; width: 6px; height: 6px; margin: 0 7px 1px 1px; border-radius: 50%; corner-shape: round; background: var(--warning); vertical-align: middle; }
42
+ .message[data-tone="warning"] .messageText::before { content: ""; display: inline-block; width: 6px; height: 6px; margin: 0 var(--space-related) 1px 1px; border-radius: 50%; corner-shape: round; background: var(--warning); vertical-align: middle; }
43
43
  .strong { color: var(--foreground); font-weight: 500; }
44
44
  .messageActions { display: inline-flex; gap: var(--space-group); }
45
45
  .link { padding: 0; border: 0; background: none; color: var(--foreground); cursor: pointer; font: inherit; font-weight: 500; text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 3px; transition: text-decoration-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard); }
@@ -50,7 +50,7 @@
50
50
  /* ---------------------------------------------- Cheatsheet ---------------------------------------------- */
51
51
 
52
52
  .list { container-type: inline-size; display: grid; min-width: 0; gap: var(--space-group); font-family: var(--font-body); letter-spacing: var(--tracking-body); }
53
- .searchRow { display: flex; height: var(--control-height-sm); box-sizing: border-box; align-items: center; gap: var(--space-related); padding: 0 6px 0 12px; border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); transition: border-color var(--duration-fast) var(--ease-standard); }
53
+ .searchRow { display: flex; height: var(--control-height-sm); box-sizing: border-box; align-items: center; gap: var(--space-related); padding: 0 var(--space-related) 0 var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); transition: border-color var(--duration-fast) var(--ease-standard); }
54
54
  .searchRow:focus-within { border-color: var(--foreground); }
55
55
  .searchIcon { flex: none; color: var(--text-muted); }
56
56
  .search { min-width: 0; height: 100%; flex: 1; padding: 0; border: 0; background: transparent; color: var(--foreground); font: inherit; font-size: var(--text-sm); outline: none; -webkit-appearance: none; appearance: none; }
@@ -62,12 +62,12 @@
62
62
 
63
63
  .groups { display: grid; align-items: start; gap: 0 var(--space-section); }
64
64
  @container (min-width: 520px) { .groups { grid-template-columns: 1fr 1fr; } }
65
- .group { overflow: hidden; margin-inline: -8px; padding-inline: 8px; }
65
+ .group { overflow: hidden; margin-inline: calc(var(--space-related) * -1); padding-inline: var(--space-related); }
66
66
  .groupLabel { margin: 0; padding: var(--space-group) 0 var(--space-tight); color: var(--text-muted); font-size: var(--text-xs); font-weight: 400; }
67
67
  .rows { display: grid; margin: 0; padding: 0; list-style: none; }
68
- .item { overflow: hidden; margin-inline: -8px; }
68
+ .item { overflow: hidden; margin-inline: calc(var(--space-related) * -1); }
69
69
  /* Rows fade when a held modifier is not in them, and light when their whole combination is down. */
70
- .itemInner { display: flex; min-height: 36px; align-items: center; justify-content: space-between; gap: var(--space-group); padding: 0 8px; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); transition: opacity var(--duration-standard) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard); }
70
+ .itemInner { display: flex; min-height: 36px; align-items: center; justify-content: space-between; gap: var(--space-group); padding: 0 var(--space-related); border-radius: var(--radius-detail); corner-shape: var(--corner-shape); transition: opacity var(--duration-standard) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard); }
71
71
  .item + .item .itemInner { box-shadow: inset 0 1px 0 var(--border-subtle); }
72
72
  .item[data-dim] .itemInner { opacity: .38; }
73
73
  .item[data-match] .itemInner { background: var(--accent-subtle); box-shadow: none; }
@@ -2,7 +2,7 @@
2
2
  --ink-black: var(--foreground);
3
3
  --ink-blue: oklch(46% .17 262);
4
4
  --ink-violet: oklch(49% .2 292);
5
- display: grid; gap: 10px; width: 100%; min-width: 0; font-family: var(--font-body); letter-spacing: var(--tracking-body);
5
+ display: grid; gap: var(--space-group); width: 100%; min-width: 0; font-family: var(--font-body); letter-spacing: var(--tracking-body);
6
6
  }
7
7
  :global(:root[data-theme="dark"]) .root { --ink-blue: oklch(76% .12 258); --ink-violet: oklch(77% .13 295); }
8
8
 
@@ -17,7 +17,7 @@
17
17
  .guide { position: absolute; inset: 0; pointer-events: none; }
18
18
  .baseline { position: absolute; left: 7%; right: 7%; top: 72%; border-top: 1.5px dashed var(--border-strong); }
19
19
  .mark { position: absolute; left: 7%; top: 72%; color: var(--text-muted); translate: 0 calc(-100% - 6px); }
20
- .signer { position: absolute; left: 7%; top: calc(72% + 8px); color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); }
20
+ .signer { position: absolute; left: 7%; top: calc(72% + var(--space-related)); color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); }
21
21
  .hint {
22
22
  position: absolute; left: 50%; top: 72%; translate: -50% calc(-100% - 8px); color: var(--text-muted);
23
23
  font-size: var(--text-lg); line-height: var(--leading-body); white-space: nowrap;
@@ -37,15 +37,15 @@
37
37
  background: var(--foreground); opacity: .5;
38
38
  }
39
39
  .sweep::after {
40
- content: ""; position: absolute; top: 0; bottom: 0; left: -28px; width: 28px;
40
+ content: ""; position: absolute; top: 0; bottom: 0; left: calc(var(--space-block) * -1); width: 28px;
41
41
  background: linear-gradient(to right, transparent, color-mix(in oklab, var(--foreground) 5%, transparent));
42
42
  }
43
43
 
44
- .toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
45
- .group { display: flex; align-items: center; gap: 2px; }
46
- .divider { width: 1px; height: 20px; margin: 0 6px; background: var(--border); }
44
+ .toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-related); }
45
+ .group { display: flex; align-items: center; gap: var(--space-hair); }
46
+ .divider { width: 1px; height: 20px; margin: 0 var(--space-related); background: var(--border); }
47
47
 
48
- .choice { display: flex; align-items: center; gap: 2px; }
48
+ .choice { display: flex; align-items: center; gap: var(--space-hair); }
49
49
  .option {
50
50
  position: relative; display: grid; place-items: center; width: 36px; height: 36px; padding: 0; border: 0; border-radius: var(--radius-full); corner-shape: round;
51
51
  background: transparent; color: var(--foreground); cursor: pointer; -webkit-tap-highlight-color: transparent;
@@ -66,16 +66,16 @@
66
66
  .iconButton[aria-pressed="true"] { background: var(--surface-muted); color: var(--foreground); }
67
67
  .swap { display: grid; place-items: center; }
68
68
 
69
- .exports { display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
70
- .exportLabel { margin-right: 4px; color: var(--text-muted); font-size: var(--text-xs); }
69
+ .exports { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-related); }
70
+ .exportLabel { margin-right: var(--space-tight); color: var(--text-muted); font-size: var(--text-xs); }
71
71
  .export {
72
- display: grid; min-width: 76px; height: var(--control-height-sm); padding: 0 12px; border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape);
72
+ display: grid; min-width: 76px; height: var(--control-height-sm); padding: 0 var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape);
73
73
  background: var(--surface); color: var(--foreground); font: inherit; font-size: var(--text-sm); cursor: pointer; -webkit-tap-highlight-color: transparent;
74
74
  transition: background-color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
75
75
  }
76
76
  .export:active:not(:disabled) { transform: scale(.97); }
77
77
  .export:disabled { color: var(--text-muted); cursor: default; }
78
- .exportContent { display: inline-flex; grid-area: 1 / 1; align-items: center; justify-content: center; gap: 6px; }
78
+ .exportContent { display: inline-flex; grid-area: 1 / 1; align-items: center; justify-content: center; gap: var(--space-related); }
79
79
 
80
80
  .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
81
81
 
@@ -13,19 +13,19 @@
13
13
  .track { position: absolute; top: 50%; right: calc(var(--slider-thumb) / 2); left: calc(var(--slider-thumb) / 2); height: var(--slider-track); margin-top: calc(var(--slider-track) / -2); border-radius: var(--radius-full); corner-shape: round; background: var(--control-track); }
14
14
  /* The fill spans the track and is revealed by a clip that follows the thumbs on the same frame. Ticks inside it are the "on" set, cut exactly at the thumb. */
15
15
  .fill { position: absolute; inset: 0; border-radius: inherit; corner-shape: inherit; background: var(--control-fill); }
16
- .tick { position: absolute; top: 50%; width: 4px; height: 4px; margin: -2px 0 0 -2px; border-radius: var(--radius-full); corner-shape: round; background: color-mix(in oklab, var(--foreground) 30%, transparent); }
16
+ .tick { position: absolute; top: 50%; width: 4px; height: 4px; margin: calc(var(--space-hair) * -1) 0 0 calc(var(--space-hair) * -1); border-radius: var(--radius-full); corner-shape: round; background: color-mix(in oklab, var(--foreground) 30%, transparent); }
17
17
  .fill .tick { background: color-mix(in oklab, var(--control-glyph) 60%, transparent); }
18
18
  .thumbs { position: absolute; inset: 0 calc(var(--slider-thumb) / 2); container-type: inline-size; pointer-events: none; }
19
19
  /* Each layer is as wide as the track and slides by a percentage of itself; it only ever overflows to the left, which never scrolls. */
20
20
  .thumbLayer { position: absolute; inset: 0; }
21
21
  /* Centering uses the translate property, so Motion's scale on transform composes with it instead of replacing it. */
22
22
  .thumb { position: absolute; top: 50%; right: 0; box-sizing: border-box; width: var(--slider-thumb); height: var(--slider-thumb); translate: 50% -50%; border: 0; border-radius: var(--radius-full); corner-shape: round; background: var(--control-thumb); box-shadow: var(--control-thumb-shadow); pointer-events: auto; cursor: grab; transition: box-shadow var(--duration-fast) var(--ease-standard); }
23
- .thumb::after { content: ""; position: absolute; inset: -11px; border-radius: inherit; corner-shape: inherit; }
23
+ .thumb::after { content: ""; position: absolute; inset: calc(var(--space-group) * -1); border-radius: inherit; corner-shape: inherit; }
24
24
  .root[data-dragging], .root[data-dragging] .control, .root[data-dragging] .thumb { cursor: grabbing; }
25
25
  /* The bubble centers on the thumb until it would pass the component's edge, then holds there. --at is the thumb position in percent of
26
26
  the track, written each frame, and cqw measures the track, so the clamp needs no JavaScript measuring. It floats higher on touch so a fingertip never hides it. */
27
- .bubbleAnchor { --edge: calc(var(--slider-thumb) / 2); --x: calc(var(--at, 0) * 1cqw); position: absolute; right: 0; bottom: calc(50% + var(--slider-thumb) / 2 + 8px); display: flex; translate: clamp(calc(100% - var(--edge) - var(--x)), 50%, calc(100cqw + var(--edge) - var(--x))) 0; pointer-events: none; }
28
- @media (pointer: coarse) { .bubbleAnchor { bottom: calc(50% + var(--slider-thumb) / 2 + 18px); } }
27
+ .bubbleAnchor { --edge: calc(var(--slider-thumb) / 2); --x: calc(var(--at, 0) * 1cqw); position: absolute; right: 0; bottom: calc(50% + var(--slider-thumb) / 2 + var(--space-related)); display: flex; translate: clamp(calc(100% - var(--edge) - var(--x)), 50%, calc(100cqw + var(--edge) - var(--x))) 0; pointer-events: none; }
28
+ @media (pointer: coarse) { .bubbleAnchor { bottom: calc(50% + var(--slider-thumb) / 2 + var(--space-comfort)); } }
29
29
  .bubble { display: inline-flex; min-height: 1.75rem; align-items: center; padding: 0 var(--space-group); border: 1px solid color-mix(in oklab, var(--background) 14%, var(--foreground)); border-radius: var(--radius-full); corner-shape: round; background: var(--foreground); color: var(--background); box-shadow: var(--shadow-raised); font-size: var(--text-sm); font-weight: 500; white-space: nowrap; transform-origin: 50% 100%; }
30
30
  /* Mark labels sit on their ticks; one at a limit keeps its outer edge inside the component. */
31
31
  .marks { position: relative; grid-area: 2 / 2; height: 1.25rem; margin: calc(var(--space-tight) * -1) calc(var(--slider-thumb) / 2) 0; color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
@@ -21,27 +21,27 @@
21
21
 
22
22
  .labels { transition: opacity var(--duration-standard) var(--ease-standard); }
23
23
  .labels[data-dim] { opacity: .3; }
24
- .start, .end { position: absolute; top: 0; display: flex; height: 20px; align-items: center; gap: 8px; margin-top: -10px; font-size: var(--text-sm); line-height: 20px; white-space: nowrap; pointer-events: none; }
24
+ .start, .end { position: absolute; top: 0; display: flex; height: 20px; align-items: center; gap: var(--space-related); margin-top: calc(var(--space-group) * -1); font-size: var(--text-sm); line-height: 20px; white-space: nowrap; pointer-events: none; }
25
25
  .start { justify-content: flex-end; max-width: 100%; }
26
26
  /* On a phone the labels step down a size so the longest names fit beside the first column instead of running off the edge. */
27
- .stage[data-narrow] .start, .stage[data-narrow] .end { gap: 6px; font-size: var(--text-xs); }
27
+ .stage[data-narrow] .start, .stage[data-narrow] .end { gap: var(--space-related); font-size: var(--text-xs); }
28
28
  .name { min-width: 0; overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; }
29
29
  .value { color: var(--foreground); font-variant-numeric: tabular-nums; }
30
30
  .labels[data-on] .name, .labels[data-accent] .name { color: var(--foreground); }
31
31
  /* Only the story item is medium; the item in focus changes color, not weight, so its label never shifts sideways. */
32
32
  .labels[data-accent] .value { font-weight: 500; }
33
33
 
34
- .rank { display: inline-flex; min-width: 30px; align-items: center; gap: 2px; color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
34
+ .rank { display: inline-flex; min-width: 30px; align-items: center; gap: var(--space-hair); color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
35
35
  .rank[data-move="up"] { color: var(--text-secondary); }
36
36
  .rankSwap { position: relative; display: inline-flex; overflow: hidden; }
37
37
 
38
- .tooltip { position: absolute; top: 0; left: 0; z-index: 2; display: grid; min-width: 168px; max-width: min(240px, 100%); gap: 3px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-control); background: var(--surface-raised); box-shadow: var(--shadow-floating); opacity: 0; pointer-events: none; scale: .96; transition: opacity var(--duration-fast) var(--ease-standard), scale var(--duration-fast) var(--ease-standard); }
38
+ .tooltip { position: absolute; top: 0; left: 0; z-index: 2; display: grid; min-width: 168px; max-width: min(240px, 100%); gap: var(--space-tight); padding: var(--space-group) var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-control); background: var(--surface-raised); box-shadow: var(--shadow-floating); opacity: 0; pointer-events: none; scale: .96; transition: opacity var(--duration-fast) var(--ease-standard), scale var(--duration-fast) var(--ease-standard); }
39
39
  .stage[data-active] .tooltip { opacity: 1; scale: 1; }
40
- .tipTitle { margin: 0 0 2px; color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); white-space: nowrap; }
41
- .tipRow { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0; font-size: var(--text-sm); line-height: 1.3; }
40
+ .tipTitle { margin: 0 0 var(--space-hair); color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); white-space: nowrap; }
41
+ .tipRow { display: flex; align-items: center; justify-content: space-between; gap: var(--space-group); margin: 0; font-size: var(--text-sm); line-height: 1.3; }
42
42
  .tipName { color: var(--text-secondary); }
43
43
  .tipValue { color: var(--foreground); font-weight: 500; font-variant-numeric: tabular-nums; }
44
- .tipMeta { margin: 2px 0 0; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); font-variant-numeric: tabular-nums; }
44
+ .tipMeta { margin: var(--space-hair) 0 0; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); font-variant-numeric: tabular-nums; }
45
45
 
46
46
  .message { display: grid; min-height: 160px; place-items: center; margin: 0; color: var(--text-muted); font-size: var(--text-sm); }
47
47
  .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { AnimatePresence, animate, motion, useMotionValue, usePresence, useReducedMotion, useTransform, useVelocity } from "motion/react";
5
+ import { motionTokens } from "../../lib/motion-tokens.js";
5
6
  import styles from "./slot-text.module.css";
6
7
  const DIGITS = "0123456789";
7
8
  const LOWER = "abcdefghijklmnopqrstuvwxyz";
@@ -94,7 +95,7 @@ function Reel({ char, order, entering, rising, duration, stagger, spins, reduced
94
95
  safeToRemove();
95
96
  return;
96
97
  }
97
- const exit = animate(width, 0, { type: "spring", visualDuration: .35, bounce: 0, onComplete: safeToRemove });
98
+ const exit = animate(width, 0, { type: "spring", visualDuration: motionTokens.spring.moderate.visualDuration, bounce: 0, onComplete: safeToRemove });
98
99
  return () => exit.stop();
99
100
  }, [isPresent, safeToRemove, width, reduced]);
100
101
  return _jsxs(motion.span, { className: styles.slot, style: { width }, "data-exiting": isPresent ? undefined : "", children: [_jsx("span", { ref: sizer, className: styles.sizer, children: char === " " ? "\u00a0" : char }), _jsx("span", { className: styles.window, children: _jsx(motion.span, { className: styles.strip, style: { transform: y, filter: blur }, children: reel.strip.map((cell, i) => _jsx("span", { className: styles.cell, children: cell === " " ? "\u00a0" : cell }, i)) }) })] });
@@ -21,7 +21,7 @@
21
21
 
22
22
  .sortButton { display: flex; width: 100%; height: 44px; align-items: center; justify-content: flex-start; border: 0; padding: 0 var(--space-inset); background: transparent; color: inherit; font: inherit; letter-spacing: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
23
23
  .table th[data-numeric] .sortButton { justify-content: flex-end; }
24
- .sortInner { display: inline-flex; align-items: center; gap: 6px; border-radius: var(--radius-control); transition: color var(--duration-fast) var(--ease-standard); }
24
+ .sortInner { display: inline-flex; align-items: center; gap: var(--space-related); border-radius: var(--radius-control); transition: color var(--duration-fast) var(--ease-standard); }
25
25
  /* Right-aligned columns lead with the arrow so the label lines up with the digits below it. */
26
26
  .table th[data-numeric] .sortInner { flex-direction: row-reverse; }
27
27
  .sortIcon { position: relative; display: grid; width: 16px; height: 16px; flex: none; place-items: center; }
@@ -79,15 +79,15 @@
79
79
  .table th.selectCell .selectHit { height: 32px; }
80
80
  /* Once the strip scrolls, a fade off the pinned cell hides the word sliding under it. */
81
81
  .table th.selectCell::after { position: absolute; top: 0; bottom: 0; left: 100%; width: 36px; background: linear-gradient(to right, var(--surface) 45%, transparent); content: ""; opacity: 0; pointer-events: none; animation: stripFade linear both; animation-timeline: scroll(nearest inline); animation-range: 0 24px; }
82
- .table th .sortButton, .table[data-selectable] th[data-primary] .sortButton { width: auto; height: 32px; border-radius: var(--radius-control); padding: 0 10px; transition: background-color var(--duration-standard) var(--ease-standard), color var(--duration-standard) var(--ease-standard); }
82
+ .table th .sortButton, .table[data-selectable] th[data-primary] .sortButton { width: auto; height: 32px; border-radius: var(--radius-control); padding: 0 var(--space-group); transition: background-color var(--duration-standard) var(--ease-standard), color var(--duration-standard) var(--ease-standard); }
83
83
  .table th[data-sorted] .sortButton { background: var(--surface-muted); }
84
84
  .table th[data-numeric] .sortInner { flex-direction: row; }
85
85
  .sortButton:focus-visible { --focus-outline-offset: 1px; }
86
86
  .table tbody { display: block; width: 100%; }
87
87
  /* Rows fold into two lines: the first column with its number, then the remaining details. */
88
- .table tbody tr { --row-fill: var(--surface); position: relative; display: flex; width: 100%; min-height: 64px; box-sizing: border-box; flex-wrap: wrap; align-items: baseline; align-content: center; gap: 2px var(--space-related); padding: 10px var(--space-inset); border-bottom: 1px solid var(--border-subtle); background-color: var(--row-fill); transition: background-color var(--duration-standard) var(--ease-standard); }
88
+ .table tbody tr { --row-fill: var(--surface); position: relative; display: flex; width: 100%; min-height: 64px; box-sizing: border-box; flex-wrap: wrap; align-items: baseline; align-content: center; gap: var(--space-hair) var(--space-related); padding: var(--space-group) var(--space-inset); border-bottom: 1px solid var(--border-subtle); background-color: var(--row-fill); transition: background-color var(--duration-standard) var(--ease-standard); }
89
89
  .table tbody tr::after { order: 1; flex-basis: 100%; content: ""; }
90
- .table[data-selectable] tbody tr { padding-left: 44px; }
90
+ .table[data-selectable] tbody tr { padding-left: var(--space-page); }
91
91
  .table[data-selectable] tbody tr[data-selected] { --row-fill: color-mix(in oklab, var(--accent) 8%, var(--surface)); }
92
92
  .table tbody tr:last-child { border-bottom: 0; }
93
93
  .table td { order: 2; display: block; min-width: 0; height: auto; border: 0; padding: 0; background: transparent; color: var(--text-secondary); font-size: var(--text-xs); text-align: left; }
@@ -10,7 +10,7 @@ const fade = { hidden: { opacity: 0, y: 0, filter: "blur(0px)" }, shown: { opaci
10
10
  const amountIn = (text) => Number(text.replace(/,/g, "").replace(/−/g, "-").match(/-?\d+(?:\.\d+)?/)?.[0] ?? NaN);
11
11
  const grouped = new Intl.NumberFormat("en-US");
12
12
  /** The first draw: one smooth stroke, slow at both ends like a pen. */
13
- const draw = { duration: motionTokens.duration.considered * 1.75, ease: [...motionTokens.ease.inOut] };
13
+ const draw = { duration: motionTokens.duration.expressive * 1.75, ease: [...motionTokens.ease.inOut] };
14
14
  const quick = { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] };
15
15
  /** A number keeps its unchanged digits still, keyed by place from the right. Words key on everything up to them, so only the first changed word and what follows it roll. */
16
16
  function chunksOf(text, whole) {
@@ -2,13 +2,13 @@
2
2
  .group { display: inline-flex; align-items: stretch; border: 1px solid var(--foreground); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--foreground); transition: transform var(--duration-spring) var(--ease-spring); }
3
3
  .group:has(> .primary:active:not(:disabled)) { transform: scale(.97); transition-duration: var(--duration-instant); transition-timing-function: var(--ease-standard); }
4
4
  .primary, .trigger { border: 0; background: var(--foreground); color: var(--background); font: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard); }
5
- .primary { position: relative; display: inline-grid; min-height: var(--control-height-md); place-items: center; padding: 0 var(--space-inset); border-radius: calc(var(--radius-control) - 1px) 0 0 calc(var(--radius-control) - 1px); corner-shape: var(--corner-shape); font-size: var(--text-sm); font-weight: 500; }
5
+ .primary { position: relative; display: inline-grid; min-height: var(--control-height-md); place-items: center; padding: 0 var(--space-inset); border-radius: calc(var(--radius-control) - var(--space-hair)) 0 0 calc(var(--radius-control) - var(--space-hair)); corner-shape: var(--corner-shape); font-size: var(--text-sm); font-weight: 500; }
6
6
  /* TextMorph springs the label's width, so the main half grows and shrinks with its text. */
7
7
  .primarySlot { display: inline-flex; min-width: 0; align-items: center; }
8
8
  .primaryContent { display: inline-flex; flex: none; align-items: center; gap: var(--space-related); white-space: nowrap; }
9
9
  .mainIcon { display: inline-grid; flex: none; place-items: center; }
10
10
  .iconPhase { grid-area: 1 / 1; display: inline-flex; align-items: center; justify-content: center; }
11
- .trigger { display: grid; width: 38px; min-height: var(--control-height-md); place-items: center; border-left: 1px solid color-mix(in oklab, var(--background) 18%, var(--foreground)); border-radius: 0 calc(var(--radius-control) - 1px) calc(var(--radius-control) - 1px) 0; corner-shape: var(--corner-shape); }
11
+ .trigger { display: grid; width: 38px; min-height: var(--control-height-md); place-items: center; border-left: 1px solid color-mix(in oklab, var(--background) 18%, var(--foreground)); border-radius: 0 calc(var(--radius-control) - var(--space-hair)) calc(var(--radius-control) - var(--space-hair)) 0; corner-shape: var(--corner-shape); }
12
12
  .chevron { transition: transform var(--duration-spring) var(--ease-spring); }
13
13
  .trigger[data-state="open"] .chevron { transform: rotate(180deg); }
14
14
  @media (hover: hover) and (pointer: fine) {
@@ -26,7 +26,7 @@
26
26
  .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
27
27
  /* The menu grows from the trigger edge: offset toward the trigger, scale from the Radix origin, fade quickly; it leaves faster than it arrives.
28
28
  Transitions instead of keyframes, so a close that interrupts the open reverses from where the menu is instead of snapping to rest first. */
29
- .menu { --menu-x: 0px; --menu-y: -5px; position: relative; z-index: 60; min-width: min(12rem, var(--radix-dropdown-menu-content-available-width)); max-width: var(--radix-dropdown-menu-content-available-width); padding: 5px; border: 1px solid var(--border); border-radius: var(--radius-container); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transform-origin: var(--radix-dropdown-menu-content-transform-origin); transition: opacity var(--duration-fast) var(--ease-enter), transform var(--duration-spring) var(--ease-spring); }
29
+ .menu { --menu-x: 0px; --menu-y: -5px; position: relative; z-index: var(--layer-dropdown); min-width: min(12rem, var(--radix-dropdown-menu-content-available-width)); max-width: var(--radix-dropdown-menu-content-available-width); padding: var(--space-tight); border: 1px solid var(--border); border-radius: var(--radius-container); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transform-origin: var(--radix-dropdown-menu-content-transform-origin); transition: opacity var(--duration-fast) var(--ease-enter), transform var(--duration-spring) var(--ease-spring); }
30
30
  .menu[data-side="top"] { --menu-y: 5px; }
31
31
  .menu[data-side="left"] { --menu-x: 5px; --menu-y: 0px; }
32
32
  .menu[data-side="right"] { --menu-x: -5px; --menu-y: 0px; }
@@ -35,11 +35,11 @@
35
35
  .menu[data-state="open"] .item { opacity: 0; transform: translate(calc(var(--menu-x) * .4), calc(var(--menu-y) * .4)); }
36
36
  }
37
37
  /* Radix unmounts when the exit animation ends, so a no-op keyframe times the exit while the transition does the visual work. */
38
- .menu[data-state="closed"] { opacity: 0; transform: translate(calc(var(--menu-x) * .5), calc(var(--menu-y) * .5)) scale(.985); transition: opacity 130ms var(--ease-standard), transform 130ms var(--ease-standard); animation: menu-exit 130ms linear both; pointer-events: none; }
39
- .highlight { position: absolute; top: 0; right: 5px; left: 5px; border-radius: calc(var(--radius-container) - 6px); background: var(--surface-muted); opacity: 0; pointer-events: none; transition: background-color var(--duration-fast) var(--ease-standard); }
38
+ .menu[data-state="closed"] { opacity: 0; transform: translate(calc(var(--menu-x) * .5), calc(var(--menu-y) * .5)) scale(.985); transition: opacity var(--duration-instant) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); animation: menu-exit var(--duration-instant) linear both; pointer-events: none; }
39
+ .highlight { position: absolute; top: 0; right: var(--space-tight); left: var(--space-tight); border-radius: calc(var(--radius-container) - var(--space-related)); background: var(--surface-muted); opacity: 0; pointer-events: none; transition: background-color var(--duration-fast) var(--ease-standard); }
40
40
  .highlight[data-tone="danger"] { background: color-mix(in oklab, var(--danger) 8%, var(--surface)); }
41
41
  /* One highlight glides between items for the pointer and jumps instantly for the keyboard (lib/menu-highlight). */
42
- .item { position: relative; display: flex; min-height: var(--control-height-sm); align-items: center; gap: 10px; padding: 0 11px; border-radius: calc(var(--radius-container) - 6px); corner-shape: var(--corner-shape); font-size: var(--text-sm); cursor: pointer; outline: none; transition: opacity var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms), transform var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms); }
42
+ .item { position: relative; display: flex; min-height: var(--control-height-sm); align-items: center; gap: var(--space-group); padding: 0 var(--space-group); border-radius: calc(var(--radius-container) - var(--space-related)); corner-shape: var(--corner-shape); font-size: var(--text-sm); cursor: pointer; outline: none; transition: opacity var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms), transform var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms); }
43
43
  .item[data-disabled] { cursor: default; opacity: .45; }
44
44
  .destructive { color: var(--danger); }
45
45
  @keyframes menu-exit { to { --menu-exit: 1; } }
@@ -31,7 +31,7 @@
31
31
  .connector { position: absolute; overflow: hidden; border-radius: var(--radius-full); corner-shape: round; background: var(--border); pointer-events: none; }
32
32
  .horizontal .connector { top: calc(var(--marker) / 2 - 1px); inset-inline: calc(var(--marker) + var(--halo) + var(--space-tight)) calc(var(--halo) + var(--space-tight)); height: 2px; }
33
33
  /* Both ends keep a clear gap from the markers, and from the current step's ring. */
34
- .vertical .connector { top: calc(var(--marker) + var(--halo) + 2px); bottom: calc(var(--halo) + 2px); inset-inline-start: calc(var(--marker) / 2 - 1px); width: 2px; }
34
+ .vertical .connector { top: calc(var(--marker) + var(--halo) + var(--space-hair)); bottom: calc(var(--halo) + var(--space-hair)); inset-inline-start: calc(var(--marker) / 2 - 1px); width: 2px; }
35
35
  .fill { position: absolute; inset: 0; border-radius: inherit; corner-shape: inherit; background: var(--accent); }
36
36
  .horizontal .fill { transform-origin: left center; }
37
37
  .horizontal .fill:dir(rtl) { transform-origin: right center; }
@@ -39,14 +39,14 @@
39
39
 
40
40
  .text { display: grid; min-width: 0; }
41
41
  /* Centers the first line of the label on the marker. */
42
- .vertical .text { padding-top: .25rem; }
42
+ .vertical .text { padding-top: var(--space-tight); }
43
43
  .label { display: block; color: var(--text-muted); font-size: var(--text-sm); font-weight: 500; line-height: 1.25rem; overflow-wrap: break-word; transition: color var(--duration-standard) var(--ease-standard); }
44
44
  .item[data-status="complete"] .label { color: var(--text-secondary); }
45
45
  .item[data-status="current"] .label, .item[data-status="error"] .label { color: var(--foreground); }
46
46
  /* Changing text pops out of flow against this box while it leaves, so it must be the positioned parent. */
47
47
  .slot { display: block; }
48
48
  .slotInner { position: relative; display: block; }
49
- .description, .error { display: block; padding-top: 2px; font-size: var(--text-xs); font-weight: 400; line-height: var(--leading-body); overflow-wrap: break-word; }
49
+ .description, .error { display: block; padding-top: var(--space-hair); font-size: var(--text-xs); font-weight: 400; line-height: var(--leading-body); overflow-wrap: break-word; }
50
50
  .description { color: var(--text-muted); }
51
51
  .error { color: var(--danger); }
52
52
 
@@ -3,8 +3,8 @@
3
3
  /* Series colors (--series-1 to --series-4) come from registry/foundation.css. */
4
4
 
5
5
  /* Layer toggles: a rect swatch mirrors the area it names. Hover or focus previews that layer in the plot. */
6
- .legend { display: flex; flex-wrap: wrap; gap: 2px 4px; min-width: 0; margin-inline: -8px; }
7
- .toggle { display: inline-flex; min-height: 32px; align-items: center; gap: 8px; padding: 0 10px 0 8px; border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; color: var(--text-secondary); cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: var(--leading-body); -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
6
+ .legend { display: flex; flex-wrap: wrap; gap: var(--space-hair) var(--space-tight); min-width: 0; margin-inline: calc(var(--space-related) * -1); }
7
+ .toggle { display: inline-flex; min-height: 32px; align-items: center; gap: var(--space-related); padding: 0 var(--space-group) 0 var(--space-related); border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; color: var(--text-secondary); cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: var(--leading-body); -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
8
8
  .toggle:active { transform: scale(.97); }
9
9
  .toggle[data-active] { color: var(--foreground); background: color-mix(in oklab, var(--foreground) 5%, transparent); }
10
10
  .toggle[data-dim] { opacity: .56; }
@@ -31,20 +31,20 @@
31
31
  .label[data-hidden] { opacity: 0 !important; }
32
32
  .chart[data-layer] .label[data-dim] { opacity: 0 !important; }
33
33
 
34
- .tooltip { position: absolute; top: 0; left: 0; z-index: 1; display: grid; min-width: 156px; max-width: min(256px, 100%); gap: 4px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface-raised); box-shadow: var(--shadow-floating); opacity: 0; pointer-events: none; scale: .96; transition: opacity var(--duration-fast) var(--ease-standard), scale var(--duration-fast) var(--ease-standard); }
34
+ .tooltip { position: absolute; top: 0; left: 0; z-index: 1; display: grid; min-width: 156px; max-width: min(256px, 100%); gap: var(--space-tight); padding: var(--space-group) var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface-raised); box-shadow: var(--shadow-floating); opacity: 0; pointer-events: none; scale: .96; transition: opacity var(--duration-fast) var(--ease-standard), scale var(--duration-fast) var(--ease-standard); }
35
35
  .chart[data-active] .tooltip { opacity: 1; scale: 1; }
36
- .tipTitle { margin: 0 0 2px; color: var(--text-secondary); font-size: var(--text-xs); line-height: var(--leading-body); white-space: nowrap; font-variant-numeric: tabular-nums; }
37
- .tipRow { display: flex; min-width: 0; align-items: center; gap: 8px; margin: 0; font-size: var(--text-sm); line-height: var(--leading-body); transition: opacity var(--duration-fast) var(--ease-standard); }
36
+ .tipTitle { margin: 0 0 var(--space-hair); color: var(--text-secondary); font-size: var(--text-xs); line-height: var(--leading-body); white-space: nowrap; font-variant-numeric: tabular-nums; }
37
+ .tipRow { display: flex; min-width: 0; align-items: center; gap: var(--space-related); margin: 0; font-size: var(--text-sm); line-height: var(--leading-body); transition: opacity var(--duration-fast) var(--ease-standard); }
38
38
  .tipRow[data-dim] { opacity: .5; }
39
39
  .tipSwatch { width: 10px; height: 2.5px; flex: none; border-radius: var(--radius-full); corner-shape: round; background: var(--series); }
40
40
  .tipName { min-width: 0; flex: 1; overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; white-space: nowrap; }
41
41
  .tipRow[data-active] .tipName { color: var(--foreground); }
42
- .tipValue { flex: none; margin-left: 8px; color: var(--foreground); font-weight: 500; font-variant-numeric: tabular-nums; white-space: nowrap; }
42
+ .tipValue { flex: none; margin-left: var(--space-related); color: var(--foreground); font-weight: 500; font-variant-numeric: tabular-nums; white-space: nowrap; }
43
43
 
44
44
  .message { position: absolute; inset: 0; display: grid; place-items: center; margin: 0; color: var(--text-muted); font-size: var(--text-sm); pointer-events: none; }
45
45
 
46
46
  .axis { position: relative; min-width: 0; }
47
- .axisLabel { position: absolute; top: 8px; color: var(--text-muted); font-size: var(--text-xs); line-height: 1; white-space: nowrap; font-variant-numeric: tabular-nums; transition: opacity var(--duration-standard) var(--ease-standard); }
47
+ .axisLabel { position: absolute; top: var(--space-related); color: var(--text-muted); font-size: var(--text-xs); line-height: 1; white-space: nowrap; font-variant-numeric: tabular-nums; transition: opacity var(--duration-standard) var(--ease-standard); }
48
48
  /* New labels fade in while the stream morphs to a new range instead of popping in a single frame. */
49
49
  @starting-style { .axisLabel { opacity: 0; } }
50
50
 
@@ -281,9 +281,9 @@ export function SwipeActionsRow({ label, leading = [], trailing = [], fullSwipe
281
281
  returnHome(); }, [present]);
282
282
  const menuItems = [...leading.map((action, index) => ({ action, side: "leading", index })), ...trailing.map((action, index) => ({ action, side: "trailing", index }))];
283
283
  const collapse = reduced
284
- ? { opacity: 0, transition: { duration: .15 } }
284
+ ? { opacity: 0, transition: { duration: motionTokens.duration.fast } }
285
285
  : { height: 0, opacity: 0, transition: { height: { ...motionTokens.spring.smooth, delay: .12 }, opacity: { duration: motionTokens.duration.instant, delay: .34 } } };
286
- return _jsxs(motion.li, { ref: rowRef, className: [styles.row, className].filter(Boolean).join(" "), style: { "--swipe-action-width": `${ACTION}px` }, initial: reduced ? { opacity: 0 } : { height: 0, opacity: 0 }, animate: { height: "auto", opacity: 1 }, exit: collapse, transition: reduced ? { duration: .15 } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] } }, children: [["leading", "trailing"].map(side => actionsOf(side).map((action, index) => {
286
+ return _jsxs(motion.li, { ref: rowRef, className: [styles.row, className].filter(Boolean).join(" "), style: { "--swipe-action-width": `${ACTION}px` }, initial: reduced ? { opacity: 0 } : { height: 0, opacity: 0 }, animate: { height: "auto", opacity: 1 }, exit: collapse, transition: reduced ? { duration: motionTokens.duration.fast } : { height: motionTokens.spring.smooth, opacity: { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] } }, children: [["leading", "trailing"].map(side => actionsOf(side).map((action, index) => {
287
287
  const count = actionsOf(side).length;
288
288
  const rank = side === "leading" ? count - 1 - index : index;
289
289
  const coverRank = covering?.side === side ? (side === "leading" ? count - 1 - covering.index : covering.index) : null;
@@ -8,11 +8,11 @@
8
8
  .list:focus-visible { outline: none; }
9
9
  .row { position: relative; isolation: isolate; overflow: hidden; }
10
10
  /* The content follows the finger by transform only; vertical pans stay with the page. */
11
- .content { position: relative; z-index: 10; display: flex; min-height: 64px; align-items: center; gap: var(--space-related); padding: var(--space-group) var(--space-related) var(--space-group) var(--space-inset); background: var(--surface); touch-action: pan-y; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
11
+ .content { position: relative; z-index: var(--layer-raised); display: flex; min-height: 64px; align-items: center; gap: var(--space-related); padding: var(--space-group) var(--space-related) var(--space-group) var(--space-inset); background: var(--surface); touch-action: pan-y; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
12
12
  .content::after { position: absolute; right: 0; bottom: 0; left: var(--swipe-actions-inset, var(--space-inset)); height: 1px; background: var(--border); content: ""; pointer-events: none; }
13
13
  .content img { -webkit-user-drag: none; }
14
14
  .body { min-width: 0; flex: 1 1 auto; }
15
- .more { display: grid; width: 32px; height: 32px; flex: none; place-items: center; border: 0; border-radius: calc(var(--radius-control) - 6px); background: transparent; color: var(--text-muted); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard); }
15
+ .more { display: grid; width: 32px; height: 32px; flex: none; place-items: center; border: 0; border-radius: calc(var(--radius-control) - var(--space-related)); background: transparent; color: var(--text-muted); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard); }
16
16
  /* The button anchors a menu, so a press answers with colour, never scale. */
17
17
  .more:active, .more[data-state="open"] { background: var(--surface-muted); color: var(--foreground); }
18
18
  .more:focus-visible { outline: none; }
@@ -33,18 +33,18 @@
33
33
  .anchor { position: absolute; top: 0; bottom: 0; width: 0; }
34
34
  .layer[data-side="trailing"] .anchor { left: 0; }
35
35
  .layer[data-side="leading"] .anchor { right: 0; }
36
- .glyph { position: absolute; top: 0; bottom: 0; left: calc(var(--swipe-action-width) / -2); display: flex; width: var(--swipe-action-width); flex-direction: column; align-items: center; justify-content: center; gap: 3px; }
36
+ .glyph { position: absolute; top: 0; bottom: 0; left: calc(var(--swipe-action-width) / -2); display: flex; width: var(--swipe-action-width); flex-direction: column; align-items: center; justify-content: center; gap: var(--space-tight); }
37
37
  .icon { display: grid; place-items: center; }
38
38
  .icon svg { width: 20px; height: 20px; }
39
39
  .label { font-size: var(--text-xs); font-weight: 500; letter-spacing: var(--tracking-body); line-height: var(--leading-body); white-space: nowrap; }
40
40
 
41
41
  /* The menu grows from its trigger, matching the library dropdown. */
42
- .menu { --menu-x: 0px; --menu-y: -5px; position: relative; z-index: 60; min-width: 11rem; padding: 5px; border: 1px solid var(--border); border-radius: var(--radius-container); background: var(--surface-raised); box-shadow: var(--shadow-floating); transform-origin: var(--radix-dropdown-menu-content-transform-origin); }
42
+ .menu { --menu-x: 0px; --menu-y: -5px; position: relative; z-index: var(--layer-dropdown); min-width: 11rem; padding: var(--space-tight); border: 1px solid var(--border); border-radius: var(--radius-container); background: var(--surface-raised); box-shadow: var(--shadow-floating); transform-origin: var(--radix-dropdown-menu-content-transform-origin); }
43
43
  .menu[data-side="top"] { --menu-y: 5px; }
44
44
  .menu[data-state="open"] { animation: menu-fade-in var(--duration-fast) var(--ease-enter) both, menu-rise-in var(--duration-spring) var(--ease-spring) both; }
45
45
  .menu[data-state="closed"] { animation: menu-out var(--duration-instant) var(--ease-standard) both; }
46
46
  .menu[data-state="open"] .item { animation: menu-item-in var(--duration-standard) var(--ease-enter) both; animation-delay: calc(min(var(--i, 0), 4) * 35ms); }
47
- .item { position: relative; display: flex; min-height: 36px; align-items: center; gap: 10px; padding: 0 11px; border-radius: calc(var(--radius-container) - 6px); color: var(--foreground); font-size: var(--text-sm); cursor: pointer; outline: none; transition: background-color var(--duration-instant) var(--ease-standard); }
47
+ .item { position: relative; display: flex; min-height: 36px; align-items: center; gap: var(--space-group); padding: 0 var(--space-group); border-radius: calc(var(--radius-container) - var(--space-related)); color: var(--foreground); font-size: var(--text-sm); cursor: pointer; outline: none; transition: background-color var(--duration-instant) var(--ease-standard); }
48
48
  .item[data-highlighted] { background: var(--surface-muted); }
49
49
  .item[data-tone="danger"] { color: var(--danger); }
50
50
  .item[data-tone="danger"][data-highlighted] { background: color-mix(in oklab, var(--danger) 8%, var(--surface-raised)); }
@@ -11,7 +11,7 @@ const travel = 18;
11
11
  /** How far the thumb widens toward the other side while pressed. */
12
12
  const stretch = 5;
13
13
  /** Critically damped: the thumb lands on its end without overshooting the state it reports. */
14
- const glide = { type: "spring", visualDuration: 0.3, bounce: 0 };
14
+ const glide = { type: "spring", visualDuration: motionTokens.spring.fast.visualDuration, bounce: 0 };
15
15
  export const Switch = forwardRef(function Switch({ label, className, checked, defaultChecked, onCheckedChange, onPointerDown, onPointerUp, onPointerLeave, onPointerCancel, onKeyDown, onKeyUp, onBlur, ...props }, ref) {
16
16
  const reduceMotion = useReducedMotion();
17
17
  const [internal, setInternal] = useState(defaultChecked ?? false);
@@ -29,7 +29,7 @@ export const Switch = forwardRef(function Switch({ label, className, checked, de
29
29
  const fromPress = performance.now() - releasedAt.current < 250;
30
30
  if (reduceMotion || fromPress)
31
31
  return;
32
- const controls = animate(scaleX, [1, 1.16, 1], { duration: 0.34, times: [0, 0.4, 1], ease: ["easeOut", "easeInOut"] });
32
+ const controls = animate(scaleX, [1, 1.16, 1], { duration: motionTokens.duration.standard, times: [0, 0.4, 1], ease: ["easeOut", "easeInOut"] });
33
33
  return () => controls.stop();
34
34
  }, [on, reduceMotion, scaleX]);
35
35
  const extra = pressed && !reduceMotion && !props.disabled ? stretch : 0;
@@ -1,7 +1,7 @@
1
1
  .switch { display: inline-flex; min-height: var(--control-height-md); align-items: center; gap: var(--space-group); padding: 0; border: 0; color: var(--foreground); background: transparent; font: inherit; font-size: var(--text-sm); cursor: pointer; -webkit-tap-highlight-color: transparent; }
2
2
  .switch:disabled { cursor: not-allowed; opacity: .5; }
3
3
  /* Fixed box: the thumb moves inside it with transforms, so nothing around the switch shifts. The track crossfades between the two fills. */
4
- .track { position: relative; box-sizing: border-box; display: flex; flex: 0 0 auto; width: 42px; height: 24px; align-items: center; padding: 3px; border-radius: var(--radius-full); corner-shape: round; background: var(--control-track); isolation: isolate; transition: background-color var(--duration-fast) var(--ease-standard); }
4
+ .track { position: relative; box-sizing: border-box; display: flex; flex: 0 0 auto; width: 42px; height: 24px; align-items: center; padding: var(--space-tight); border-radius: var(--radius-full); corner-shape: round; background: var(--control-track); isolation: isolate; transition: background-color var(--duration-fast) var(--ease-standard); }
5
5
  .track::before { content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; corner-shape: inherit; background: var(--control-on); opacity: 0; transition: opacity var(--duration-standard) var(--ease-standard); }
6
6
  .switch[data-state="checked"] .track::before { opacity: 1; }
7
7
  .thumb { flex: 0 0 auto; width: 18px; height: 18px; border-radius: var(--radius-full); corner-shape: round; background: var(--control-thumb); box-shadow: var(--control-thumb-shadow); will-change: transform; transition: background-color var(--duration-standard) var(--ease-standard); }