@motion-proto/live-tokens 0.67.1 → 0.69.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 (138) hide show
  1. package/.claude/skills/live-tokens-build-page/SKILL.md +16 -5
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +43 -8
  3. package/.claude/skills/live-tokens-create-component/references/token-naming.md +30 -1
  4. package/.claude/skills/live-tokens-fix-findings/SKILL.md +131 -0
  5. package/.claude/skills/live-tokens-pick-component/SKILL.md +2 -1
  6. package/CHANGELOG.md +239 -0
  7. package/README.md +11 -4
  8. package/bin/check-component.mjs +367 -63
  9. package/bin/check-page.mjs +409 -0
  10. package/bin/cli.mjs +57 -8
  11. package/bin/generate-theme.mjs +5 -4
  12. package/bin/lib/cssValues.mjs +50 -0
  13. package/bin/lib/findings.mjs +106 -0
  14. package/bin/lib/tokenVocabulary.mjs +213 -0
  15. package/dist-plugin/adjust/index.cjs +174 -23
  16. package/dist-plugin/adjust/index.d.cts +1 -1
  17. package/dist-plugin/adjust/index.d.ts +1 -1
  18. package/dist-plugin/adjust/index.js +68 -23
  19. package/dist-plugin/{chunk-J2JT4UEA.js → chunk-2YNERPXY.js} +26 -26
  20. package/dist-plugin/{chunk-NE6N66EE.js → chunk-GPIBU44G.js} +107 -1
  21. package/dist-plugin/{chunk-ZHPX7ZYQ.js → chunk-RFVYPNRO.js} +39 -1
  22. package/dist-plugin/{dataPaths-DZUzVv8H.d.ts → dataPaths-bJTCEO4H.d.cts} +1 -1
  23. package/dist-plugin/{dataPaths-DZUzVv8H.d.cts → dataPaths-bJTCEO4H.d.ts} +1 -1
  24. package/dist-plugin/fontPairing/index.d.cts +1 -1
  25. package/dist-plugin/fontPairing/index.d.ts +1 -1
  26. package/dist-plugin/generateColorsAndType/index.cjs +107 -1
  27. package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
  28. package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
  29. package/dist-plugin/generateColorsAndType/index.js +1 -1
  30. package/dist-plugin/index.cjs +173 -29
  31. package/dist-plugin/index.d.cts +1 -1
  32. package/dist-plugin/index.d.ts +1 -1
  33. package/dist-plugin/index.js +7 -7
  34. package/dist-plugin/migrateData/index.cjs +132 -26
  35. package/dist-plugin/migrateData/index.d.cts +1 -1
  36. package/dist-plugin/migrateData/index.d.ts +1 -1
  37. package/dist-plugin/migrateData/index.js +2 -2
  38. package/dist-plugin/tokensCssMigrations/index.cjs +39 -1
  39. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  40. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  41. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  42. package/package.json +6 -5
  43. package/src/app/site.css +4 -4
  44. package/src/editor/bootstrap.ts +6 -6
  45. package/src/editor/component-editor/ButtonEditor.svelte +71 -4
  46. package/src/editor/component-editor/CardEditor.svelte +6 -6
  47. package/src/editor/component-editor/DialogEditor.svelte +3 -3
  48. package/src/editor/component-editor/IconButtonEditor.svelte +68 -3
  49. package/src/editor/component-editor/ImageEditor.svelte +8 -8
  50. package/src/editor/component-editor/ImageLightboxEditor.svelte +13 -2
  51. package/src/editor/component-editor/MenuSelectEditor.svelte +63 -3
  52. package/src/editor/component-editor/SegmentedControlEditor.svelte +63 -3
  53. package/src/editor/component-editor/SideNavigationEditor.svelte +67 -3
  54. package/src/editor/component-editor/SliderEditor.svelte +186 -0
  55. package/src/editor/component-editor/TabBarEditor.svelte +63 -3
  56. package/src/editor/component-editor/registry.ts +10 -0
  57. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +4 -4
  58. package/src/editor/core/components/aliasKinds.ts +51 -28
  59. package/src/editor/core/fonts/fontLoader.ts +2 -2
  60. package/src/editor/core/preview/{lookPreview.ts → themePreview.ts} +27 -27
  61. package/src/editor/core/productionPulse.ts +3 -3
  62. package/src/editor/core/sketch/index.ts +52 -44
  63. package/src/editor/core/sketch/maskField.ts +9 -9
  64. package/src/editor/core/sketch/sketchLayer.ts +25 -9
  65. package/src/editor/core/sketch/sketchRegistry.ts +34 -34
  66. package/src/editor/core/sketch/sketchStore.ts +98 -83
  67. package/src/editor/core/sketch/sketchStyleService.ts +4 -4
  68. package/src/editor/core/sketch/sketchStyles.ts +28 -28
  69. package/src/editor/core/store/editorPersistence.ts +44 -1
  70. package/src/editor/core/store/editorRenderer.ts +2 -2
  71. package/src/editor/core/store/editorStore.ts +18 -18
  72. package/src/editor/core/store/editorTypes.ts +7 -6
  73. package/src/editor/core/themes/colorsAndTypeService.ts +1 -1
  74. package/src/editor/core/themes/loadRows.ts +8 -8
  75. package/src/editor/core/themes/migrations/2026-09-01-gate-suffix-enabled.ts +31 -0
  76. package/src/editor/core/themes/migrations/2026-09-01-scrim-rename.ts +52 -0
  77. package/src/editor/core/themes/migrations/2026-09-01-tabbar-active-tint.ts +27 -0
  78. package/src/editor/core/themes/migrations/2026-09-01-tint-rename.ts +42 -0
  79. package/src/editor/core/themes/migrations/index.ts +16 -0
  80. package/src/editor/core/themes/slices/domainVars.ts +2 -2
  81. package/src/editor/core/themes/slices/washes.ts +107 -0
  82. package/src/editor/core/themes/themeDocumentSync.ts +2 -2
  83. package/src/editor/core/themes/themeInit.ts +2 -2
  84. package/src/editor/core/themes/themeService.ts +17 -17
  85. package/src/editor/core/themes/{lookSummary.ts → themeSummary.ts} +9 -9
  86. package/src/editor/core/themes/themeTypes.ts +10 -6
  87. package/src/editor/docs/content/editing-tokens.md +5 -3
  88. package/src/editor/docs/content/getting-started.md +1 -1
  89. package/src/editor/docs/content/sketch-mode.md +22 -17
  90. package/src/editor/docs/content/themes-workflow.md +11 -11
  91. package/src/editor/docs/content/where-themes-live.md +6 -6
  92. package/src/editor/docs/content.generated.ts +5 -5
  93. package/src/editor/index.ts +2 -2
  94. package/src/editor/pages/EditorShell.svelte +1 -1
  95. package/src/editor/ui/SurfacesTab.svelte +3 -3
  96. package/src/editor/ui/ThemePanel.svelte +52 -52
  97. package/src/editor/ui/UITokenSelector.svelte +1 -0
  98. package/src/editor/ui/VariablesTab.svelte +2 -2
  99. package/src/editor/ui/sections/{OverlaysSection.svelte → WashesSection.svelte} +44 -43
  100. package/src/editor/ui/sketch/SketchPreview.svelte +2 -2
  101. package/src/editor/ui/sketch/SketchTab.svelte +64 -38
  102. package/src/live-tokens/data/colors-and-type/autumn.json +6 -6
  103. package/src/live-tokens/data/colors-and-type/default.json +6 -6
  104. package/src/live-tokens/data/colors-and-type/halloween.json +6 -6
  105. package/src/live-tokens/data/colors-and-type/midnight-study.json +6 -6
  106. package/src/live-tokens/data/colors-and-type/ocean.json +6 -6
  107. package/src/live-tokens/data/colors-and-type/royal-velvet.json +6 -6
  108. package/src/live-tokens/data/colors-and-type/sketchy.json +6 -6
  109. package/src/live-tokens/data/colors-and-type/spring-meadow.json +6 -6
  110. package/src/live-tokens/data/colors-and-type/sunset.json +6 -6
  111. package/src/live-tokens/data/sketch-styles/dry.json +5 -5
  112. package/src/live-tokens/data/sketch-styles/hatched.json +9 -9
  113. package/src/live-tokens/data/themes/autumn.json +82 -14
  114. package/src/live-tokens/data/themes/halloween.json +82 -14
  115. package/src/live-tokens/data/themes/midnight-study.json +82 -14
  116. package/src/live-tokens/data/themes/ocean.json +82 -14
  117. package/src/live-tokens/data/themes/royal-velvet.json +82 -14
  118. package/src/live-tokens/data/themes/sketchy.json +82 -14
  119. package/src/live-tokens/data/themes/spring-meadow.json +82 -14
  120. package/src/live-tokens/data/themes/sunset.json +82 -14
  121. package/src/live-tokens/data/tokens.generated.css +6 -6
  122. package/src/system/backdrop/backdrop.ts +1 -1
  123. package/src/system/components/Button.svelte +28 -10
  124. package/src/system/components/Card.svelte +6 -6
  125. package/src/system/components/Dialog.svelte +3 -3
  126. package/src/system/components/IconButton.svelte +23 -7
  127. package/src/system/components/Image.svelte +6 -6
  128. package/src/system/components/MenuSelect.svelte +17 -1
  129. package/src/system/components/SectionDivider.svelte +1 -1
  130. package/src/system/components/SegmentedControl.svelte +20 -2
  131. package/src/system/components/SideNavigation.svelte +22 -2
  132. package/src/system/components/Slider.svelte +348 -0
  133. package/src/system/components/TabBar.svelte +20 -2
  134. package/src/system/styles/CONVENTIONS.md +2 -2
  135. package/src/system/styles/tokens.css +12 -4
  136. package/template/package.json +3 -2
  137. package/template/src/pages/Home.svelte +1 -1
  138. package/src/editor/core/themes/slices/overlays.ts +0 -101
@@ -0,0 +1,348 @@
1
+ <!--
2
+ Slider.svelte — a number chosen by position on a track. `single` moves one
3
+ thumb to a value; `range` moves two thumbs to a low and a high bound. Both
4
+ variants share the track, fill, and thumb tokens, linked in the editor so an
5
+ edit to one moves the other unless deliberately unlinked.
6
+ -->
7
+ <script module lang="ts">
8
+ export const sliderVariants = ['single', 'range'] as const;
9
+ export type SliderVariant = typeof sliderVariants[number];
10
+ </script>
11
+
12
+ <script lang="ts">
13
+ interface Props {
14
+ variant?: SliderVariant;
15
+ /** The `single` thumb. */
16
+ value?: number;
17
+ /** The `range` thumbs. */
18
+ low?: number;
19
+ high?: number;
20
+ min?: number;
21
+ max?: number;
22
+ step?: number;
23
+ label?: string;
24
+ disabled?: boolean;
25
+ /** Editor preview hook — adds `.force-hover` so hover tokens paint without a real pointer. */
26
+ class?: string;
27
+ onchange?: (value: number) => void;
28
+ onrangechange?: (low: number, high: number) => void;
29
+ }
30
+
31
+ let {
32
+ variant = 'single',
33
+ value = 50,
34
+ low = 25,
35
+ high = 75,
36
+ min = 0,
37
+ max = 100,
38
+ step = 1,
39
+ label = '',
40
+ disabled = false,
41
+ class: className = '',
42
+ onchange,
43
+ onrangechange,
44
+ }: Props = $props();
45
+
46
+ const span = $derived(Math.max(max - min, Number.EPSILON));
47
+ const percent = (n: number) => Math.min(100, Math.max(0, ((n - min) / span) * 100));
48
+
49
+ const fillStart = $derived(variant === 'range' ? percent(low) : 0);
50
+ const fillEnd = $derived(variant === 'range' ? percent(high) : percent(value));
51
+ const readout = $derived(variant === 'range' ? `${low} – ${high}` : `${value}`);
52
+
53
+ function onSingleInput(event: Event) {
54
+ onchange?.(Number((event.currentTarget as HTMLInputElement).value));
55
+ }
56
+ // Each thumb is its own input, so a thumb dragged past its partner is held at
57
+ // the partner's value rather than crossing it.
58
+ function onLowInput(event: Event) {
59
+ const next = Math.min(Number((event.currentTarget as HTMLInputElement).value), high);
60
+ onrangechange?.(next, high);
61
+ }
62
+ function onHighInput(event: Event) {
63
+ const next = Math.max(Number((event.currentTarget as HTMLInputElement).value), low);
64
+ onrangechange?.(low, next);
65
+ }
66
+ </script>
67
+
68
+ <div class="slider {variant} {className}" class:disabled class:has-label={!!label}>
69
+ {#if label}
70
+ <div class="slider-label">
71
+ <span>{label}</span>
72
+ <span class="slider-value">{readout}</span>
73
+ </div>
74
+ {/if}
75
+ <div class="slider-track">
76
+ <div class="slider-fill" style:--_from={fillStart / 100} style:--_to={fillEnd / 100}></div>
77
+ {#if variant === 'range'}
78
+ <input
79
+ type="range"
80
+ class="thumb low"
81
+ {min}
82
+ {max}
83
+ {step}
84
+ value={low}
85
+ {disabled}
86
+ aria-label={label ? `${label} minimum` : 'Minimum'}
87
+ oninput={onLowInput}
88
+ />
89
+ <span class="cap" style:--_at={fillStart / 100} aria-hidden="true"></span>
90
+ <input
91
+ type="range"
92
+ class="thumb high"
93
+ {min}
94
+ {max}
95
+ {step}
96
+ value={high}
97
+ {disabled}
98
+ aria-label={label ? `${label} maximum` : 'Maximum'}
99
+ oninput={onHighInput}
100
+ />
101
+ <span class="cap" style:--_at={fillEnd / 100} aria-hidden="true"></span>
102
+ {:else}
103
+ <input
104
+ type="range"
105
+ class="thumb"
106
+ {min}
107
+ {max}
108
+ {step}
109
+ {value}
110
+ {disabled}
111
+ aria-label={label || 'Value'}
112
+ oninput={onSingleInput}
113
+ />
114
+ <span class="cap" style:--_at={fillEnd / 100} aria-hidden="true"></span>
115
+ {/if}
116
+ </div>
117
+ </div>
118
+
119
+ <style>
120
+ :global(:root) {
121
+ /* Single: one thumb. */
122
+ --slider-single-track-surface: var(--surface-neutral-lowest);
123
+ --slider-single-track-border: var(--border-neutral-subtle);
124
+ --slider-single-track-border-width: var(--border-width-1);
125
+ --slider-single-track-height: var(--space-8);
126
+ --slider-single-track-radius: var(--radius-full);
127
+ --slider-single-fill: var(--surface-brand-high);
128
+ --slider-single-thumb-surface: var(--surface-neutral-highest);
129
+ --slider-single-thumb-border: var(--border-brand);
130
+ --slider-single-thumb-border-width: var(--border-width-2);
131
+ --slider-single-thumb-size: var(--font-size-lg);
132
+ --slider-single-thumb-radius: var(--radius-full);
133
+ --slider-single-thumb-shadow: var(--shadow-sm);
134
+ --slider-single-hover-thumb-surface: var(--surface-brand-high);
135
+ --slider-single-hover-thumb-border: var(--border-brand-strong);
136
+ --slider-single-disabled-track-surface: var(--surface-neutral-lower);
137
+ --slider-single-disabled-fill: var(--surface-neutral);
138
+ --slider-single-disabled-thumb-surface: var(--surface-neutral-lower);
139
+ --slider-single-disabled-thumb-border: var(--border-neutral);
140
+
141
+ /* Range: two thumbs bounding a fill. */
142
+ --slider-range-track-surface: var(--surface-neutral-lowest);
143
+ --slider-range-track-border: var(--border-neutral-subtle);
144
+ --slider-range-track-border-width: var(--border-width-1);
145
+ --slider-range-track-height: var(--space-8);
146
+ --slider-range-track-radius: var(--radius-full);
147
+ --slider-range-fill: var(--surface-brand-high);
148
+ --slider-range-thumb-surface: var(--surface-neutral-highest);
149
+ --slider-range-thumb-border: var(--border-brand);
150
+ --slider-range-thumb-border-width: var(--border-width-2);
151
+ --slider-range-thumb-size: var(--font-size-lg);
152
+ --slider-range-thumb-radius: var(--radius-full);
153
+ --slider-range-thumb-shadow: var(--shadow-sm);
154
+ --slider-range-hover-thumb-surface: var(--surface-brand-high);
155
+ --slider-range-hover-thumb-border: var(--border-brand-strong);
156
+ --slider-range-disabled-track-surface: var(--surface-neutral-lower);
157
+ --slider-range-disabled-fill: var(--surface-neutral);
158
+ --slider-range-disabled-thumb-surface: var(--surface-neutral-lower);
159
+ --slider-range-disabled-thumb-border: var(--border-neutral);
160
+
161
+ /* Label and value readout, shared by both variants. */
162
+ --slider-label-gap: var(--space-6);
163
+ --slider-label: var(--text-secondary);
164
+ --slider-label-font-family: var(--font-sans);
165
+ --slider-label-font-size: var(--font-size-md);
166
+ --slider-label-font-weight: var(--font-weight-normal);
167
+ --slider-label-line-height: var(--line-height-normal);
168
+ --slider-value: var(--text-tertiary);
169
+ --slider-value-font-family: var(--font-mono);
170
+ --slider-value-font-size: var(--font-size-md);
171
+ --slider-value-font-weight: var(--font-weight-normal);
172
+ --slider-value-line-height: var(--line-height-normal);
173
+ }
174
+
175
+ .slider {
176
+ display: grid;
177
+ grid-template-columns: minmax(0, 1fr);
178
+ row-gap: var(--slider-label-gap);
179
+ width: 100%;
180
+ }
181
+
182
+ .slider-label {
183
+ display: flex;
184
+ justify-content: space-between;
185
+ align-items: center;
186
+ }
187
+
188
+ .slider-label > span {
189
+ color: var(--slider-label);
190
+ font-family: var(--slider-label-font-family);
191
+ font-size: var(--slider-label-font-size);
192
+ font-weight: var(--slider-label-font-weight);
193
+ line-height: var(--slider-label-line-height);
194
+ }
195
+
196
+ .slider-label > .slider-value {
197
+ color: var(--slider-value);
198
+ font-family: var(--slider-value-font-family);
199
+ font-size: var(--slider-value-font-size);
200
+ font-weight: var(--slider-value-font-weight);
201
+ line-height: var(--slider-value-line-height);
202
+ }
203
+
204
+ /* The inputs sit on top of the track and are invisible apart from their
205
+ native thumb, which is kept for the pointer and the keyboard but drawn
206
+ transparent; the visible thumb is the `.cap` beside each input, placed on
207
+ the same travel the native thumb has (it stays inside the input's box, so
208
+ its centre runs from half a thumb in to half a thumb from the end). */
209
+ .slider-track {
210
+ position: relative;
211
+ box-sizing: border-box;
212
+ width: 100%;
213
+ margin-block: calc((var(--_thumb-size) - var(--_track-height)) / 2);
214
+ border-style: solid;
215
+ transition: background var(--duration-150), border-color var(--duration-150);
216
+ --_origin: calc(var(--_thumb-size) / 2 - var(--_track-border-width));
217
+ --_travel: calc(100% + var(--_track-border-width) * 2 - var(--_thumb-size));
218
+ }
219
+
220
+ .slider.single .slider-track {
221
+ --_track-height: var(--slider-single-track-height);
222
+ --_track-border-width: var(--slider-single-track-border-width);
223
+ --_thumb-size: var(--slider-single-thumb-size);
224
+ --_thumb-surface: var(--slider-single-thumb-surface);
225
+ --_thumb-border: var(--slider-single-thumb-border);
226
+ --_thumb-border-width: var(--slider-single-thumb-border-width);
227
+ --_thumb-radius: var(--slider-single-thumb-radius);
228
+ --_thumb-shadow: var(--slider-single-thumb-shadow);
229
+ height: var(--slider-single-track-height);
230
+ background: var(--slider-single-track-surface);
231
+ border-color: var(--slider-single-track-border);
232
+ border-width: var(--slider-single-track-border-width);
233
+ border-radius: var(--slider-single-track-radius);
234
+ }
235
+ .slider.range .slider-track {
236
+ --_track-height: var(--slider-range-track-height);
237
+ --_track-border-width: var(--slider-range-track-border-width);
238
+ --_thumb-size: var(--slider-range-thumb-size);
239
+ --_thumb-surface: var(--slider-range-thumb-surface);
240
+ --_thumb-border: var(--slider-range-thumb-border);
241
+ --_thumb-border-width: var(--slider-range-thumb-border-width);
242
+ --_thumb-radius: var(--slider-range-thumb-radius);
243
+ --_thumb-shadow: var(--slider-range-thumb-shadow);
244
+ height: var(--slider-range-track-height);
245
+ background: var(--slider-range-track-surface);
246
+ border-color: var(--slider-range-track-border);
247
+ border-width: var(--slider-range-track-border-width);
248
+ border-radius: var(--slider-range-track-radius);
249
+ }
250
+ .slider.single.disabled .slider-track { background: var(--slider-single-disabled-track-surface); }
251
+ .slider.range.disabled .slider-track { background: var(--slider-range-disabled-track-surface); }
252
+
253
+ .slider-fill {
254
+ position: absolute;
255
+ top: calc(-1 * var(--_track-border-width));
256
+ bottom: calc(-1 * var(--_track-border-width));
257
+ left: calc(var(--_origin) + var(--_travel) * var(--_from));
258
+ width: calc(var(--_travel) * (var(--_to) - var(--_from)));
259
+ transition: background var(--duration-150);
260
+ }
261
+ .slider.single .slider-fill {
262
+ left: calc(-1 * var(--_track-border-width));
263
+ width: calc(var(--_origin) + var(--_track-border-width) + var(--_travel) * var(--_to));
264
+ background: var(--slider-single-fill);
265
+ border-radius: var(--slider-single-track-radius);
266
+ }
267
+ .slider.range .slider-fill {
268
+ background: var(--slider-range-fill);
269
+ border-radius: var(--slider-range-track-radius);
270
+ }
271
+ .slider.single.disabled .slider-fill { background: var(--slider-single-disabled-fill); }
272
+ .slider.range.disabled .slider-fill { background: var(--slider-range-disabled-fill); }
273
+
274
+ .thumb {
275
+ position: absolute;
276
+ top: 50%;
277
+ left: calc(-1 * var(--_track-border-width));
278
+ width: calc(100% + var(--_track-border-width) * 2);
279
+ height: var(--_thumb-size);
280
+ margin: 0;
281
+ padding: 0;
282
+ transform: translateY(-50%);
283
+ appearance: none;
284
+ background: transparent;
285
+ pointer-events: none;
286
+ cursor: pointer;
287
+ }
288
+ .thumb:disabled { cursor: not-allowed; }
289
+ .thumb::-webkit-slider-thumb {
290
+ appearance: none;
291
+ width: var(--_thumb-size);
292
+ height: var(--_thumb-size);
293
+ opacity: 0;
294
+ pointer-events: auto;
295
+ }
296
+ .thumb::-moz-range-thumb {
297
+ width: var(--_thumb-size);
298
+ height: var(--_thumb-size);
299
+ border: 0;
300
+ opacity: 0;
301
+ pointer-events: auto;
302
+ }
303
+ .thumb::-webkit-slider-runnable-track,
304
+ .thumb::-moz-range-track {
305
+ background: transparent;
306
+ border: 0;
307
+ }
308
+ .thumb:focus-visible { outline: none; }
309
+
310
+ .cap {
311
+ position: absolute;
312
+ top: 50%;
313
+ left: calc(var(--_origin) + var(--_travel) * var(--_at));
314
+ width: var(--_thumb-size);
315
+ height: var(--_thumb-size);
316
+ box-sizing: border-box;
317
+ transform: translate(-50%, -50%);
318
+ background: var(--_thumb-surface);
319
+ border: var(--_thumb-border-width) solid var(--_thumb-border);
320
+ border-radius: var(--_thumb-radius);
321
+ box-shadow: var(--_thumb-shadow);
322
+ pointer-events: none;
323
+ transition: background var(--duration-150), border-color var(--duration-150);
324
+ }
325
+ .thumb:focus-visible + .cap {
326
+ outline: var(--_thumb-border-width) solid var(--_thumb-border);
327
+ outline-offset: var(--_thumb-border-width);
328
+ }
329
+
330
+ .slider.single .thumb:hover + .cap,
331
+ .slider.single.force-hover .cap {
332
+ --_thumb-surface: var(--slider-single-hover-thumb-surface);
333
+ --_thumb-border: var(--slider-single-hover-thumb-border);
334
+ }
335
+ .slider.range .thumb:hover + .cap,
336
+ .slider.range.force-hover .cap {
337
+ --_thumb-surface: var(--slider-range-hover-thumb-surface);
338
+ --_thumb-border: var(--slider-range-hover-thumb-border);
339
+ }
340
+ .slider.single .thumb:disabled + .cap {
341
+ --_thumb-surface: var(--slider-single-disabled-thumb-surface);
342
+ --_thumb-border: var(--slider-single-disabled-thumb-border);
343
+ }
344
+ .slider.range .thumb:disabled + .cap {
345
+ --_thumb-surface: var(--slider-range-disabled-thumb-surface);
346
+ --_thumb-border: var(--slider-range-disabled-thumb-border);
347
+ }
348
+ </style>
@@ -9,6 +9,9 @@
9
9
  }
10
10
 
11
11
  interface Props {
12
+ /** Wash the hovered surface with the theme's tint. `undefined` inherits the
13
+ global default; `true`/`false` force this instance on/off. */
14
+ hoverTint?: boolean | undefined;
12
15
  tabs?: Tab[];
13
16
  selectedTab?: string;
14
17
  iconOnly?: boolean;
@@ -18,6 +21,7 @@
18
21
  }
19
22
 
20
23
  let {
24
+ hoverTint = undefined,
21
25
  tabs = [],
22
26
  selectedTab = '',
23
27
  iconOnly = false,
@@ -25,6 +29,11 @@
25
29
  ontabChange
26
30
  }: Props = $props();
27
31
 
32
+ // Per-instance override of the global hover-tint intrinsic; undefined leaves :root in charge.
33
+ let hoverTintValue = $derived(
34
+ hoverTint === undefined ? undefined : hoverTint ? 'var(--tabbar-hover-tint)' : 'var(--color-transparent)',
35
+ );
36
+
28
37
  // Dual-fire bridge — see Button.svelte for the deprecation timeline.
29
38
  const dispatch = createEventDispatcher<{
30
39
  tabChange: string;
@@ -38,7 +47,10 @@
38
47
  }
39
48
  </script>
40
49
 
41
- <div class="tab-bar {className}">
50
+ <div
51
+ class="tab-bar {className}"
52
+ style:--tabbar-hover-tint-enabled={hoverTintValue}
53
+ >
42
54
  {#each tabs as tab}
43
55
  <button
44
56
  class="tab"
@@ -61,6 +73,11 @@
61
73
  @use '../styles/padding' as *;
62
74
 
63
75
  :global(:root) {
76
+ /* Hover tint: an optional wash over the hovered surface, one stop for the
77
+ whole component. Off by default; the gate holds the tint when enabled. */
78
+ --tabbar-hover-tint: var(--tint);
79
+ --tabbar-hover-tint-enabled: var(--color-transparent);
80
+
64
81
  /* Bar */
65
82
  --tabbar-bar-divider: var(--border-neutral-subtle);
66
83
  --tabbar-bar-divider-thickness: var(--border-width-1);
@@ -104,7 +121,7 @@
104
121
  /* Active tab */
105
122
  --tabbar-active-text: var(--text-primary);
106
123
  --tabbar-active-border: var(--color-brand-500);
107
- --tabbar-active-surface: var(--overlay-low);
124
+ --tabbar-active-surface: var(--tint-low);
108
125
  --tabbar-active-text-font-family: var(--font-sans);
109
126
  --tabbar-active-text-font-size: var(--font-size-md);
110
127
  --tabbar-active-text-font-weight: var(--font-weight-light);
@@ -197,6 +214,7 @@
197
214
 
198
215
  .tab:hover:not(:disabled):not(.active),
199
216
  .tab-bar.force-hover .tab:not(:disabled):not(.active) {
217
+ background-image: linear-gradient(var(--tabbar-hover-tint-enabled), var(--tabbar-hover-tint-enabled));
200
218
  --_text-color: var(--tabbar-hover-text);
201
219
  --_text-family: var(--tabbar-hover-text-font-family);
202
220
  --_text-size: var(--tabbar-hover-text-font-size);
@@ -57,11 +57,11 @@ Every theme token starts with a **category prefix**. The category tells you what
57
57
  | `--ring-*` | Focus rings (separate namespace from shadows) |
58
58
  | `--border-width-*` | Stroke widths |
59
59
  | `--transition-*`, `--opacity-*`, `--z-*` | Animation, opacity, z-index scales |
60
- | `--overlay-*`, `--hover-*` | Semantic overlay tints |
60
+ | `--scrim-*`, `--tint-*` | Scrims dim what is behind them; tints shade a surface |
61
61
  | `--gradient-*` | Named gradients |
62
62
  | `--columns-*`, `--page-*` | Page-level layout primitives |
63
63
 
64
- **Rule: a bare one-word token belongs to *some* category.** If you find yourself writing `--overlay` or `--border`, make sure the category is obvious and it's the canonical default. Otherwise pick a longer name that slots it into its family (e.g. `--border-neutral`).
64
+ **Rule: a bare one-word token belongs to *some* category.** If you find yourself writing `--scrim` or `--border`, make sure the category is obvious and it's the canonical default. Otherwise pick a longer name that slots it into its family (e.g. `--border-neutral`).
65
65
 
66
66
  ### Families within a category
67
67
 
@@ -266,10 +266,18 @@
266
266
  --surface-danger-higher: oklch(0.5836 0.1757 24.75);
267
267
  --surface-danger-highest: oklch(0.6696 0.1423 24.72);
268
268
 
269
- /* Overlays — semi-transparent washes for backdrops and pressed-state fills. */
270
- --overlay-low: rgba(20, 3, 0, 0.38);
271
- --overlay: rgba(20, 3, 0, 0.51);
272
- --overlay-high: rgba(20, 3, 0, 0.64);
269
+ /* Scrims — translucent layers that dim what sits behind them. A scrim is not
270
+ a tint: it darkens the content underneath, it does not shade a surface. */
271
+ --scrim-low: rgba(20, 3, 0, 0.38);
272
+ --scrim: rgba(20, 3, 0, 0.51);
273
+ --scrim-high: rgba(20, 3, 0, 0.64);
274
+
275
+ /* Tints — translucent washes that shade the surface they sit on, for a hover
276
+ or an active row. The theme aliases these to the text color, so a tint
277
+ lightens a dark theme and darkens a light one without picking a direction. */
278
+ --tint-low: rgba(255, 255, 255, 0.05);
279
+ --tint: rgba(255, 255, 255, 0.1);
280
+ --tint-high: rgba(255, 255, 255, 0.15);
273
281
 
274
282
  /* BORDERS — 5-point scales, faint → strong. */
275
283
 
@@ -5,9 +5,10 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
8
- "build": "vite build",
8
+ "build": "npm run check:design && vite build",
9
9
  "preview": "vite preview",
10
- "check": "svelte-check --tsconfig ./tsconfig.json"
10
+ "check": "svelte-check --tsconfig ./tsconfig.json",
11
+ "check:design": "live-tokens check-page && live-tokens check-component"
11
12
  },
12
13
  "dependencies": {
13
14
  "@motion-proto/live-tokens": "__LT_VERSION__"
@@ -59,7 +59,7 @@
59
59
 
60
60
  code {
61
61
  background: var(--surface-neutral-high);
62
- padding: 2px 6px;
62
+ padding: var(--space-2) var(--space-6);
63
63
  border-radius: var(--radius-sm);
64
64
  font-family: var(--font-mono, monospace);
65
65
  font-size: 0.9em;
@@ -1,101 +0,0 @@
1
- /**
2
- * Overlays slice — overlay + hover stops, each stored as `{alias, opacity}`.
3
- * Emits as `color-mix(in srgb, var(<alias>) <pct>%, transparent)` so the
4
- * overlay tints automatically follow the aliased source token (a brand-color
5
- * shift propagates without re-editing every overlay).
6
- *
7
- * Defaults diverge from tokens.css by design: the editor starts from a
8
- * neutral alias and tokens.css continues to win until first edit (see
9
- * `overlaysEqualsDefault`).
10
- */
11
- import type { EditorState, OverlayToken } from '../../store/editorTypes';
12
-
13
- export function makeDefaultOverlayTokens(): OverlayToken[] {
14
- return [
15
- { variable: '--overlay-low', label: 'Low', alias: '--surface-neutral-lowest', opacity: 0.38 },
16
- { variable: '--overlay', label: 'Base', alias: '--surface-neutral-lowest', opacity: 0.51 },
17
- { variable: '--overlay-high', label: 'High', alias: '--surface-neutral-lowest', opacity: 0.64 },
18
- ];
19
- }
20
-
21
- export function makeDefaultHoverTokens(): OverlayToken[] {
22
- return [
23
- { variable: '--hover-low', label: 'Low', alias: '--text-primary', opacity: 0.05 },
24
- { variable: '--hover', label: 'Base', alias: '--text-primary', opacity: 0.1 },
25
- { variable: '--hover-high', label: 'High', alias: '--text-primary', opacity: 0.15 },
26
- ];
27
- }
28
-
29
- export function makeDefaultOverlaysState(): EditorState['overlays'] {
30
- return {
31
- tokens: makeDefaultOverlayTokens(),
32
- hoverTokens: makeDefaultHoverTokens(),
33
- };
34
- }
35
-
36
- export const OVERLAY_VAR_NAMES = [
37
- '--overlay-low', '--overlay', '--overlay-high',
38
- '--hover-low', '--hover', '--hover-high',
39
- ] as const;
40
-
41
- export function overlayTokenToCss(t: OverlayToken): string {
42
- const pct = Math.round(t.opacity * 100);
43
- if (pct >= 100) return `var(${t.alias})`;
44
- return `color-mix(in srgb, var(${t.alias}) ${pct}%, transparent)`;
45
- }
46
-
47
- const COLOR_MIX_RE = /^color-mix\(in srgb,\s*var\((--[a-z0-9-]+)\)\s+(\d+)%,\s*transparent\)$/i;
48
- const PLAIN_VAR_RE = /^var\((--[a-z0-9-]+)\)$/i;
49
-
50
- export function parseOverlayCss(raw: string): { alias: string; opacity: number } | null {
51
- const s = raw.trim();
52
- const mix = s.match(COLOR_MIX_RE);
53
- if (mix) {
54
- const pct = parseInt(mix[2], 10);
55
- if (!Number.isFinite(pct)) return null;
56
- return { alias: mix[1], opacity: Math.max(0, Math.min(100, pct)) / 100 };
57
- }
58
- const plain = s.match(PLAIN_VAR_RE);
59
- if (plain) return { alias: plain[1], opacity: 1 };
60
- return null;
61
- }
62
-
63
- export function overlaysToVars(o: EditorState['overlays']): Record<string, string> {
64
- const out: Record<string, string> = {};
65
- for (const t of o.tokens) out[t.variable] = overlayTokenToCss(t);
66
- for (const t of o.hoverTokens) out[t.variable] = overlayTokenToCss(t);
67
- return out;
68
- }
69
-
70
- export function applyOverlayVarsToState(overlays: EditorState['overlays'], vars: Record<string, string>): void {
71
- const applyTo = (list: OverlayToken[]) => {
72
- for (const t of list) {
73
- const raw = vars[t.variable];
74
- if (!raw) continue;
75
- const parsed = parseOverlayCss(raw);
76
- if (!parsed) continue;
77
- t.alias = parsed.alias;
78
- t.opacity = parsed.opacity;
79
- }
80
- };
81
- applyTo(overlays.tokens);
82
- applyTo(overlays.hoverTokens);
83
- }
84
-
85
- /**
86
- * Loader: route overlay/hover entries from a freshly-loaded theme's vars
87
- * bag into `next.overlays` and remove them from the bag — but only when the
88
- * value parses as the new format (color-mix / plain var). Legacy rgba values
89
- * pass through to the cssVars bag so the DOM still paints them; the user's
90
- * next edit in the picker promotes them to the typed slice.
91
- */
92
- export function loadOverlaysFromVars(
93
- next: EditorState,
94
- rawVars: Record<string, string>,
95
- ): void {
96
- applyOverlayVarsToState(next.overlays, rawVars);
97
- for (const name of OVERLAY_VAR_NAMES) {
98
- const raw = rawVars[name];
99
- if (raw && parseOverlayCss(raw) !== null) delete rawVars[name];
100
- }
101
- }