@motion-proto/live-tokens 0.40.0 → 0.41.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 (89) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/dist-plugin/{chunk-D77VD4Z6.js → chunk-H4TRUINI.js} +102 -13
  3. package/dist-plugin/index.cjs +288 -67
  4. package/dist-plugin/index.js +171 -55
  5. package/dist-plugin/tokensCssMigrations/index.cjs +102 -13
  6. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  7. package/package.json +3 -2
  8. package/src/app/site.css +97 -20
  9. package/src/editor/core/palettes/colorHarmony.ts +228 -0
  10. package/src/editor/core/palettes/contrast.ts +128 -0
  11. package/src/editor/core/palettes/oklch.ts +8 -0
  12. package/src/editor/core/palettes/paletteDerivation.ts +267 -72
  13. package/src/editor/core/palettes/recommendText.ts +216 -0
  14. package/src/editor/core/palettes/solveTextContrast.ts +251 -0
  15. package/src/editor/core/store/editorCore.ts +2 -1
  16. package/src/editor/core/store/editorPersistence.ts +18 -1
  17. package/src/editor/core/store/editorStore.ts +20 -1
  18. package/src/editor/core/store/editorTypes.ts +4 -0
  19. package/src/editor/core/store/editorViewStore.ts +7 -3
  20. package/src/editor/core/themes/migrations/2026-06-05-palette-unification.ts +11 -9
  21. package/src/editor/core/themes/migrations/2026-07-21-palette-oklch-basis.ts +51 -0
  22. package/src/editor/core/themes/migrations/2026-07-24-base-anchor-placement.ts +37 -0
  23. package/src/editor/core/themes/migrations/2026-07-25-background-spot-to-base.ts +40 -0
  24. package/src/editor/core/themes/themeTypes.ts +23 -3
  25. package/src/editor/docs/CodeBlock.svelte +1 -1
  26. package/src/editor/docs/Docs.svelte +30 -13
  27. package/src/editor/pages/EditorShell.svelte +31 -3
  28. package/src/editor/ui/ColorEditPanel.svelte +22 -16
  29. package/src/editor/ui/EditorViewSwitcher.svelte +43 -9
  30. package/src/editor/ui/FontStackEditor.svelte +0 -1
  31. package/src/editor/ui/PaletteEditor.svelte +117 -155
  32. package/src/editor/ui/ProjectFontsSection.svelte +0 -3
  33. package/src/editor/ui/TextStylesSection.svelte +173 -0
  34. package/src/editor/ui/UILineHeightSelector.svelte +6 -5
  35. package/src/editor/ui/UIPaletteSelector.svelte +6 -1
  36. package/src/editor/ui/UITextTransformSelector.svelte +106 -0
  37. package/src/editor/ui/VariablesTab.svelte +23 -10
  38. package/src/editor/ui/colors/ColorReadouts.svelte +123 -0
  39. package/src/editor/ui/colors/ColorStory.svelte +375 -0
  40. package/src/editor/ui/colors/ColorWheel.svelte +740 -0
  41. package/src/editor/ui/colors/ColorsTab.svelte +508 -0
  42. package/src/editor/ui/colors/HarmonyAxesList.svelte +320 -0
  43. package/src/editor/ui/colors/LightnessBar.svelte +261 -0
  44. package/src/editor/ui/colors/PaletteStepStrip.svelte +142 -0
  45. package/src/editor/ui/colors/colorWheelMath.ts +9 -0
  46. package/src/editor/ui/colors/harmony-icons/analogous.svg +13 -0
  47. package/src/editor/ui/colors/harmony-icons/complementary.svg +10 -0
  48. package/src/editor/ui/colors/harmony-icons/compound.svg +15 -0
  49. package/src/editor/ui/colors/harmony-icons/custom.svg +10 -0
  50. package/src/editor/ui/colors/harmony-icons/monochromatic.svg +11 -0
  51. package/src/editor/ui/colors/harmony-icons/shades.svg +9 -0
  52. package/src/editor/ui/colors/harmony-icons/split-complementary.svg +13 -0
  53. package/src/editor/ui/colors/harmony-icons/square.svg +13 -0
  54. package/src/editor/ui/colors/harmony-icons/tetradic.svg +15 -0
  55. package/src/editor/ui/colors/harmony-icons/triadic.svg +13 -0
  56. package/src/editor/ui/colors/harmonyModeIcons.ts +23 -0
  57. package/src/editor/ui/colors/paletteBaseColor.ts +214 -0
  58. package/src/editor/ui/index.ts +0 -1
  59. package/src/editor/ui/palette/OverridesPanel.svelte +6 -3
  60. package/src/editor/ui/palette/PaletteBase.svelte +16 -8
  61. package/src/editor/ui/palette/dockMagnify.ts +22 -0
  62. package/src/editor/ui/palette/paletteEditorState.ts +5 -4
  63. package/src/editor/ui/palette/paletteMath.ts +93 -181
  64. package/src/editor/ui/sections/textStyles.ts +82 -0
  65. package/src/editor/ui/sections/tokenScales.ts +3 -3
  66. package/src/live-tokens/data/themes/default.json +3 -3
  67. package/src/live-tokens/data/tokens.generated.css +96 -95
  68. package/src/system/components/Badge.svelte +11 -11
  69. package/src/system/components/Button.svelte +7 -7
  70. package/src/system/components/Callout.svelte +8 -8
  71. package/src/system/components/Card.svelte +2 -2
  72. package/src/system/components/CodeSnippet.svelte +1 -1
  73. package/src/system/components/CollapsibleSection.svelte +7 -7
  74. package/src/system/components/CornerBadge.svelte +1 -1
  75. package/src/system/components/Dialog.svelte +2 -2
  76. package/src/system/components/FloatingTokenTags.css +6 -5
  77. package/src/system/components/Input.svelte +6 -6
  78. package/src/system/components/MenuSelect.svelte +4 -4
  79. package/src/system/components/Notification.svelte +8 -8
  80. package/src/system/components/ProgressBar.svelte +2 -2
  81. package/src/system/components/RadioButton.svelte +4 -4
  82. package/src/system/components/SectionDivider.svelte +6 -6
  83. package/src/system/components/SegmentedControl.svelte +5 -5
  84. package/src/system/components/SideNavigation.svelte +12 -12
  85. package/src/system/components/TabBar.svelte +4 -4
  86. package/src/system/components/Table.svelte +2 -2
  87. package/src/system/components/Tooltip.svelte +1 -1
  88. package/src/system/styles/tokens.css +59 -6
  89. package/src/editor/ui/TextTab.svelte +0 -203
@@ -0,0 +1,508 @@
1
+ <script lang="ts">
2
+ import { oklchToHexClamped } from '../../core/palettes/oklch';
3
+ import { PALETTE_SPECS } from '../../core/palettes/paletteDerivation';
4
+ import { editorState, beginSliderGesture, transaction } from '../../core/store/editorStore';
5
+ import { applyHarmonyToAxes, modeActiveAxes, tintNeutralsFromAnchor, type HarmonyMode } from '../../core/palettes/colorHarmony';
6
+ import ColorEditPanel from '../ColorEditPanel.svelte';
7
+ import ColorWheel from './ColorWheel.svelte';
8
+ import LightnessBar from './LightnessBar.svelte';
9
+ import ColorReadouts from './ColorReadouts.svelte';
10
+ import ColorStory from './ColorStory.svelte';
11
+ import PaletteStepStrip from './PaletteStepStrip.svelte';
12
+ import HarmonyAxesList from './HarmonyAxesList.svelte';
13
+ import UIPillButton from '../UIPillButton.svelte';
14
+ import { setBaseColor, setBaseColors, setAxisHues, applySolvedTextCurves } from './paletteBaseColor';
15
+ import { HARMONY_MODE_BUTTONS } from './harmonyModeIcons';
16
+
17
+ let selected = $state('Brand');
18
+ let activeMode = $state<HarmonyMode>('custom');
19
+ let hoverMode = $state<HarmonyMode | null>(null);
20
+ // Local UI only — deliberately NOT stored in PaletteConfig (shape unchanged).
21
+ let absoluteChroma = $state(false);
22
+ let infoOpen = $state(false);
23
+ let infoWrap: HTMLElement | undefined = $state();
24
+
25
+ function select(label: string) {
26
+ selected = label;
27
+ }
28
+
29
+ function toggleInfo() {
30
+ infoOpen = !infoOpen;
31
+ }
32
+
33
+ // Dismiss the info popover on an outside click. The listener is added after
34
+ // the opening click has finished propagating, so it never self-closes.
35
+ $effect(() => {
36
+ if (!infoOpen) return;
37
+ const onDoc = (e: MouseEvent) => {
38
+ if (infoWrap && !infoWrap.contains(e.target as Node)) infoOpen = false;
39
+ };
40
+ document.addEventListener('click', onDoc);
41
+ return () => document.removeEventListener('click', onDoc);
42
+ });
43
+
44
+ function applyMode(mode: HarmonyMode) {
45
+ if (mode === 'custom') {
46
+ activeMode = 'custom';
47
+ return;
48
+ }
49
+ activeMode = mode;
50
+ // Hue-only: every axis moves to its slot hue and each bound family follows in
51
+ // the same transaction. setAxisHues no-op-guards a re-applied geometry.
52
+ const target = applyHarmonyToAxes(mode, $editorState.harmonyAxes);
53
+ setAxisHues(target.map((a, i) => ({ index: i, hue: a.hue })), `colors: harmony ${mode}`);
54
+ }
55
+
56
+ function tintNeutrals() {
57
+ const patch = tintNeutralsFromAnchor($editorState.palettes, $editorState.harmonyAxes[0].hue);
58
+ if (Object.keys(patch).length) setBaseColors(patch, 'colors: tint neutrals from anchor');
59
+ }
60
+
61
+ function deriveAccessibleText() {
62
+ transaction('derive accessible text', applySolvedTextCurves);
63
+ }
64
+
65
+ let swatches = $derived.by(() => {
66
+ // A family bound to an inactive axis edits in dot mode, so it is not "on the wheel".
67
+ const active = modeActiveAxes(activeMode);
68
+ return PALETTE_SPECS.map((spec) => {
69
+ const { l, c, h } = $editorState.palettes[spec.label]?.baseColor ?? spec.initialColor;
70
+ return {
71
+ label: spec.label,
72
+ display: spec.displayLabel ?? spec.label,
73
+ hex: oklchToHexClamped(l, c, h),
74
+ onWheel: $editorState.harmonyAxes.some((a, i) => a.family === spec.label && active[i]),
75
+ };
76
+ });
77
+ });
78
+
79
+ const FUNCTIONAL_FAMILIES = new Set(['Info', 'Success', 'Warning', 'Danger']);
80
+ let coreSwatches = $derived(swatches.filter((sw) => !FUNCTIONAL_FAMILIES.has(sw.label)));
81
+ let functionalSwatches = $derived(swatches.filter((sw) => FUNCTIONAL_FAMILIES.has(sw.label)));
82
+
83
+ let shownModeLabel = $derived(
84
+ HARMONY_MODE_BUTTONS.find((b) => b.mode === (hoverMode ?? activeMode))?.label ?? '',
85
+ );
86
+
87
+ let selectedSpec = $derived(PALETTE_SPECS.find((s) => s.label === selected) ?? PALETTE_SPECS[0]);
88
+ let selectedOklch = $derived($editorState.palettes[selected]?.baseColor ?? selectedSpec.initialColor);
89
+ </script>
90
+
91
+ <div class="colors-tab">
92
+ <div class="pane">
93
+ <section id="colors-wheel" class="block">
94
+ <header class="block-head">
95
+ <span class="eyebrow">Theme</span>
96
+ <h2 class="title">Color Wheel</h2>
97
+ </header>
98
+
99
+ <ColorWheel
100
+ {selected}
101
+ {absoluteChroma}
102
+ {activeMode}
103
+ onSelect={select}
104
+ discLightness={selectedOklch.l}
105
+ onCustomize={() => (activeMode = 'custom')}
106
+ />
107
+
108
+ <LightnessBar {selected} {absoluteChroma} />
109
+
110
+ <div class="group">
111
+ <span class="eyebrow">Color Harmony <span class="mode-name">&middot; {shownModeLabel}</span></span>
112
+ <div class="mode-row" role="group" aria-label="Color harmony mode">
113
+ {#each HARMONY_MODE_BUTTONS as m (m.mode)}
114
+ <button
115
+ type="button"
116
+ class="mode-btn"
117
+ class:active={activeMode === m.mode}
118
+ title={m.label}
119
+ aria-label={m.label}
120
+ aria-pressed={activeMode === m.mode}
121
+ onclick={() => applyMode(m.mode)}
122
+ onpointerenter={() => (hoverMode = m.mode)}
123
+ onpointerleave={() => (hoverMode = null)}
124
+ onfocus={() => (hoverMode = m.mode)}
125
+ onblur={() => (hoverMode = null)}
126
+ >{@html m.svg}</button>
127
+ {/each}
128
+ </div>
129
+
130
+ <div class="harmony-actions">
131
+ <UIPillButton
132
+ size="compact"
133
+ icon="fa-fill-drip"
134
+ title="Re-hue Neutral and Alternate to the anchor color (their own chroma and lightness kept)"
135
+ onclick={tintNeutrals}
136
+ >Tint neutrals from anchor</UIPillButton>
137
+ </div>
138
+
139
+ <div class="wheel-opts">
140
+ <label class="opt">
141
+ <input type="checkbox" bind:checked={absoluteChroma} />
142
+ <span>Absolute Chroma</span>
143
+ </label>
144
+ <div class="info" bind:this={infoWrap}>
145
+ <button
146
+ type="button"
147
+ class="info-btn"
148
+ aria-expanded={infoOpen}
149
+ aria-label="About Absolute Chroma"
150
+ title="About Absolute Chroma"
151
+ onclick={toggleInfo}
152
+ ><i class="fas fa-circle-info" aria-hidden="true"></i></button>
153
+ {#if infoOpen}
154
+ <div class="info-box" role="region" aria-label="Absolute Chroma">
155
+ <strong class="info-title">Absolute Chroma</strong>
156
+ <p>When on, rotating a color keeps its exact chroma, its colorfulness. The reachable chroma changes from hue to hue, so the handle moves in and out as it orbits.</p>
157
+ <p>When off, rotating keeps the color's relative saturation, its fraction of the available gamut. The handle stays a constant distance from the center, and colorfulness shifts a little across hues to stay in gamut.</p>
158
+ </div>
159
+ {/if}
160
+ </div>
161
+ </div>
162
+ </div>
163
+
164
+ <div class="group">
165
+ <span class="eyebrow">Swatches</span>
166
+ {#snippet swatchButton(sw: (typeof swatches)[number])}
167
+ <button
168
+ type="button"
169
+ class="swatch"
170
+ class:active={selected === sw.label}
171
+ style="--swatch-fill: {sw.hex}"
172
+ title={sw.display}
173
+ aria-label={`Select ${sw.display}`}
174
+ aria-pressed={selected === sw.label}
175
+ onclick={() => select(sw.label)}
176
+ >
177
+ <span class="chip"></span>
178
+ <span class="swatch-label">{sw.label}</span>
179
+ {#if sw.onWheel}<span class="wheel-dot" title="On the wheel" aria-hidden="true"></span>{/if}
180
+ </button>
181
+ {/snippet}
182
+ <div class="swatch-rows">
183
+ <div class="swatch-row">
184
+ {#each coreSwatches as sw (sw.label)}
185
+ {@render swatchButton(sw)}
186
+ {/each}
187
+ </div>
188
+ <div class="swatch-row">
189
+ {#each functionalSwatches as sw (sw.label)}
190
+ {@render swatchButton(sw)}
191
+ {/each}
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="group">
197
+ <span class="eyebrow">{selectedSpec.displayLabel ?? selected}</span>
198
+ <ColorEditPanel
199
+ title={selectedSpec.displayLabel ?? selected}
200
+ hideActions
201
+ hue={selectedOklch.h}
202
+ chroma={selectedOklch.c}
203
+ lightness={selectedOklch.l * 100}
204
+ onHueChromaChange={(h, c, l) => setBaseColor(selected, { l: l / 100, c, h })}
205
+ onSliderStart={() => beginSliderGesture(`colors: ${selected} base`)}
206
+ />
207
+ </div>
208
+
209
+ <div class="group">
210
+ <span class="eyebrow">Derived scale</span>
211
+ <PaletteStepStrip spec={selectedSpec} />
212
+ </div>
213
+
214
+ <div class="group">
215
+ <ColorReadouts color={selectedOklch} />
216
+ </div>
217
+ </section>
218
+ </div>
219
+
220
+ <div class="pane">
221
+ <section id="colors-story" class="block">
222
+ <div class="head-row">
223
+ <header class="block-head">
224
+ <span class="eyebrow">Proportional preview</span>
225
+ <h2 class="title">Color Story</h2>
226
+ </header>
227
+ <UIPillButton
228
+ icon="fa-wand-magic-sparkles"
229
+ title="Solve each family's Text lightness curve so derived text meets WCAG AA against its surfaces (one undo)"
230
+ onclick={deriveAccessibleText}
231
+ >Derive accessible text</UIPillButton>
232
+ </div>
233
+ <ColorStory />
234
+ </section>
235
+
236
+ <section id="colors-axes" class="block">
237
+ <h2 class="title">Harmony axes</h2>
238
+ <p class="axes-desc">Each axis owns a hue. Drop a color on an axis to bind it. The color adopts the axis hue and follows the axis. Drag a color to Unassigned to let it float free.</p>
239
+ <HarmonyAxesList {activeMode} />
240
+ </section>
241
+ </div>
242
+ </div>
243
+
244
+ <style>
245
+ .colors-tab {
246
+ display: grid;
247
+ grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
248
+ gap: var(--ui-space-32);
249
+ align-items: start;
250
+ }
251
+
252
+ .pane {
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: var(--ui-space-32);
256
+ min-width: 0;
257
+ }
258
+
259
+ .axes-desc {
260
+ margin: 0;
261
+ font-size: var(--ui-font-size-sm);
262
+ line-height: 1.5;
263
+ color: var(--ui-text-secondary);
264
+ }
265
+
266
+ .block {
267
+ display: flex;
268
+ flex-direction: column;
269
+ gap: var(--ui-space-20);
270
+ min-width: 0;
271
+ }
272
+
273
+ .block-head {
274
+ display: flex;
275
+ flex-direction: column;
276
+ gap: var(--ui-space-2);
277
+ }
278
+
279
+ .eyebrow {
280
+ font-size: var(--ui-font-size-xs);
281
+ font-weight: var(--ui-font-weight-semibold);
282
+ color: var(--ui-text-tertiary);
283
+ text-transform: uppercase;
284
+ letter-spacing: 0.04em;
285
+ }
286
+
287
+ .eyebrow .mode-name {
288
+ color: var(--ui-text-secondary);
289
+ }
290
+
291
+ .title {
292
+ font-size: var(--ui-font-size-2xl);
293
+ font-weight: var(--ui-font-weight-semibold);
294
+ color: var(--ui-text-primary);
295
+ margin: 0;
296
+ }
297
+
298
+ .group {
299
+ display: flex;
300
+ flex-direction: column;
301
+ gap: var(--ui-space-8);
302
+ min-width: 0;
303
+ }
304
+
305
+ .mode-row {
306
+ display: flex;
307
+ gap: var(--ui-space-6);
308
+ flex-wrap: wrap;
309
+ }
310
+
311
+ .mode-btn {
312
+ display: flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ width: 3.375rem;
316
+ height: 3.375rem;
317
+ padding: 0;
318
+ background: var(--ui-surface-lowest);
319
+ border: 1px solid var(--ui-border-low);
320
+ border-radius: var(--ui-radius-md);
321
+ color: var(--ui-text-secondary);
322
+ cursor: pointer;
323
+ transition: background var(--ui-transition-fast), border-color var(--ui-transition-fast), color var(--ui-transition-fast);
324
+ }
325
+
326
+ .mode-btn :global(svg) {
327
+ width: 100%;
328
+ height: 100%;
329
+ display: block;
330
+ }
331
+
332
+ .mode-btn:hover {
333
+ background: var(--ui-surface-low);
334
+ border-color: var(--ui-border);
335
+ color: var(--ui-text-primary);
336
+ }
337
+
338
+ .mode-btn.active {
339
+ border-color: var(--ui-text-primary);
340
+ color: var(--ui-text-primary);
341
+ background: var(--ui-surface-high);
342
+ }
343
+
344
+ .wheel-opts {
345
+ display: flex;
346
+ align-items: center;
347
+ gap: var(--ui-space-8);
348
+ }
349
+
350
+ .opt {
351
+ display: flex;
352
+ align-items: center;
353
+ gap: var(--ui-space-6);
354
+ font-size: var(--ui-font-size-sm);
355
+ color: var(--ui-text-secondary);
356
+ cursor: pointer;
357
+ user-select: none;
358
+ }
359
+
360
+ .opt input {
361
+ margin: 0;
362
+ cursor: pointer;
363
+ }
364
+
365
+ .info {
366
+ position: relative;
367
+ display: flex;
368
+ }
369
+
370
+ .info-btn {
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: center;
374
+ width: 1.5rem;
375
+ height: 1.5rem;
376
+ padding: 0;
377
+ background: none;
378
+ border: none;
379
+ border-radius: var(--ui-radius-full);
380
+ color: var(--ui-text-tertiary);
381
+ cursor: pointer;
382
+ font-size: var(--ui-font-size-md);
383
+ transition: color var(--ui-transition-fast);
384
+ }
385
+
386
+ .info-btn:hover,
387
+ .info-btn[aria-expanded='true'] {
388
+ color: var(--ui-text-primary);
389
+ }
390
+
391
+ .info-box {
392
+ position: absolute;
393
+ top: calc(100% + var(--ui-space-6));
394
+ left: 0;
395
+ z-index: 10;
396
+ width: 20rem;
397
+ max-width: 80vw;
398
+ display: flex;
399
+ flex-direction: column;
400
+ gap: var(--ui-space-6);
401
+ padding: var(--ui-space-12);
402
+ background: var(--ui-surface-low);
403
+ border: 1px solid var(--ui-border);
404
+ border-radius: var(--ui-radius-md);
405
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
406
+ }
407
+
408
+ .info-title {
409
+ font-size: var(--ui-font-size-md);
410
+ font-weight: var(--ui-font-weight-semibold);
411
+ color: var(--ui-text-primary);
412
+ }
413
+
414
+ .info-box p {
415
+ margin: 0;
416
+ font-size: var(--ui-font-size-sm);
417
+ line-height: 1.5;
418
+ color: var(--ui-text-secondary);
419
+ }
420
+
421
+ .head-row {
422
+ display: flex;
423
+ align-items: flex-end;
424
+ justify-content: space-between;
425
+ gap: var(--ui-space-12);
426
+ flex-wrap: wrap;
427
+ }
428
+
429
+ .harmony-actions {
430
+ display: flex;
431
+ gap: var(--ui-space-8);
432
+ }
433
+
434
+ .swatch-rows {
435
+ display: flex;
436
+ flex-direction: column;
437
+ gap: var(--ui-space-8);
438
+ min-width: 0;
439
+ }
440
+
441
+ .swatch-row {
442
+ display: grid;
443
+ grid-template-columns: repeat(auto-fill, minmax(4rem, 1fr));
444
+ gap: var(--ui-space-8);
445
+ }
446
+
447
+ .swatch {
448
+ position: relative;
449
+ display: flex;
450
+ flex-direction: column;
451
+ align-items: stretch;
452
+ gap: var(--ui-space-4);
453
+ padding: 0;
454
+ background: none;
455
+ border: none;
456
+ cursor: pointer;
457
+ }
458
+
459
+ .chip {
460
+ display: block;
461
+ height: 2.75rem;
462
+ border-radius: var(--ui-radius-sm);
463
+ background: var(--swatch-fill);
464
+ border: 1px solid var(--ui-border-low);
465
+ transition: border-color var(--ui-transition-fast);
466
+ }
467
+
468
+ .swatch:hover .chip {
469
+ border-color: var(--ui-border-high);
470
+ }
471
+
472
+ .swatch.active .chip {
473
+ border-color: var(--ui-border-higher);
474
+ outline: 2px solid var(--ui-text-primary);
475
+ outline-offset: 1px;
476
+ }
477
+
478
+ .swatch-label {
479
+ font-size: var(--ui-font-size-xs);
480
+ color: var(--ui-text-secondary);
481
+ text-align: center;
482
+ white-space: nowrap;
483
+ overflow: hidden;
484
+ text-overflow: ellipsis;
485
+ }
486
+
487
+ .swatch.active .swatch-label {
488
+ color: var(--ui-text-primary);
489
+ }
490
+
491
+ .wheel-dot {
492
+ position: absolute;
493
+ top: var(--ui-space-4);
494
+ right: var(--ui-space-4);
495
+ width: 6px;
496
+ height: 6px;
497
+ border-radius: var(--ui-radius-full);
498
+ background: var(--ui-text-primary);
499
+ box-shadow: 0 0 0 1.5px var(--ui-surface-lowest);
500
+ }
501
+
502
+ @media (max-width: 1024px) {
503
+ .colors-tab {
504
+ grid-template-columns: minmax(0, 1fr);
505
+ gap: var(--ui-space-24);
506
+ }
507
+ }
508
+ </style>