@motion-proto/live-tokens 0.1.1 → 0.3.2

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 (224) hide show
  1. package/README.md +168 -21
  2. package/dist-plugin/index.cjs +823 -336
  3. package/dist-plugin/index.d.cts +9 -7
  4. package/dist-plugin/index.d.ts +9 -7
  5. package/dist-plugin/index.js +822 -335
  6. package/package.json +46 -20
  7. package/src/assets/newspaper.webp +0 -0
  8. package/src/assets/offering.webp +0 -0
  9. package/src/component-editor/BadgeEditor.svelte +170 -0
  10. package/src/component-editor/CalloutEditor.svelte +103 -0
  11. package/src/component-editor/CardEditor.svelte +184 -0
  12. package/src/component-editor/CollapsibleSectionEditor.svelte +167 -0
  13. package/src/component-editor/CornerBadgeEditor.svelte +207 -0
  14. package/src/component-editor/DialogEditor.svelte +172 -0
  15. package/src/component-editor/ImageEditor.svelte +72 -0
  16. package/src/component-editor/InlineEditActionsEditor.svelte +83 -0
  17. package/src/component-editor/NotificationEditor.svelte +160 -0
  18. package/src/component-editor/ProgressBarEditor.svelte +124 -0
  19. package/src/component-editor/RadioButtonEditor.svelte +140 -0
  20. package/src/component-editor/SectionDividerEditor.svelte +263 -0
  21. package/src/component-editor/SegmentedControlEditor.svelte +154 -0
  22. package/src/component-editor/StandardButtonsEditor.svelte +178 -0
  23. package/src/component-editor/TabBarEditor.svelte +137 -0
  24. package/src/component-editor/TableEditor.svelte +128 -0
  25. package/src/component-editor/TooltipEditor.svelte +122 -0
  26. package/src/component-editor/editorTokens.test.ts +93 -0
  27. package/src/component-editor/groupKeySlots.test.ts +67 -0
  28. package/src/component-editor/groupKeySnapshot.test.ts +52 -0
  29. package/src/component-editor/index.ts +5 -0
  30. package/src/component-editor/registry.ts +246 -0
  31. package/src/component-editor/scaffolding/AngleDial.svelte +185 -0
  32. package/src/component-editor/scaffolding/ComponentEditorBase.svelte +96 -0
  33. package/src/component-editor/scaffolding/ComponentFileManager.svelte +682 -0
  34. package/src/component-editor/scaffolding/ComponentFileMenu.svelte +312 -0
  35. package/src/component-editor/scaffolding/ComponentsTab.svelte +69 -0
  36. package/src/component-editor/scaffolding/CopyFromMenu.svelte +246 -0
  37. package/src/component-editor/scaffolding/DemoHeader.svelte +21 -0
  38. package/src/component-editor/scaffolding/DividerEditor.svelte +81 -0
  39. package/src/component-editor/scaffolding/FieldsetWrapper.svelte +46 -0
  40. package/src/component-editor/scaffolding/GradientCard.svelte +291 -0
  41. package/src/component-editor/scaffolding/LinkageChart.svelte +297 -0
  42. package/src/component-editor/scaffolding/LinkedBlock.svelte +418 -0
  43. package/src/component-editor/scaffolding/NonStylableConfig.svelte +57 -0
  44. package/src/component-editor/scaffolding/SaveAsDialog.svelte +177 -0
  45. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +25 -0
  46. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +56 -0
  47. package/src/component-editor/scaffolding/StateBlock.svelte +115 -0
  48. package/src/component-editor/scaffolding/TokenLayout.svelte +511 -0
  49. package/src/component-editor/scaffolding/TypeEditor.svelte +82 -0
  50. package/src/component-editor/scaffolding/VariantGroup.svelte +277 -0
  51. package/src/component-editor/scaffolding/buildTypeGroupTokens.ts +97 -0
  52. package/src/component-editor/scaffolding/componentSectionType.ts +8 -0
  53. package/src/component-editor/scaffolding/componentSources.ts +9 -0
  54. package/src/component-editor/scaffolding/defaultSections.ts +16 -0
  55. package/src/component-editor/scaffolding/editorContext.ts +44 -0
  56. package/src/component-editor/scaffolding/linkedBlock.ts +226 -0
  57. package/src/component-editor/scaffolding/siblings.ts +33 -0
  58. package/src/component-editor/scaffolding/types.ts +39 -0
  59. package/src/components/Badge.svelte +231 -42
  60. package/src/components/Button.svelte +324 -124
  61. package/src/components/Callout.svelte +145 -0
  62. package/src/components/Card.svelte +123 -25
  63. package/src/components/CollapsibleSection.svelte +213 -35
  64. package/src/components/CornerBadge.svelte +224 -0
  65. package/src/components/Dialog.svelte +137 -114
  66. package/src/components/Image.svelte +43 -0
  67. package/src/components/InlineEditActions.svelte +74 -14
  68. package/src/components/Notification.svelte +184 -163
  69. package/src/components/ProgressBar.svelte +216 -22
  70. package/src/components/RadioButton.svelte +110 -40
  71. package/src/components/SectionDivider.svelte +428 -74
  72. package/src/components/SegmentedControl.svelte +203 -0
  73. package/src/components/TabBar.svelte +146 -21
  74. package/src/components/Table.svelte +102 -0
  75. package/src/components/Tooltip.svelte +45 -19
  76. package/src/components/types.ts +51 -0
  77. package/src/data/google-fonts.json +75 -0
  78. package/src/lib/ColumnsOverlay.svelte +20 -7
  79. package/src/lib/LiveEditorOverlay.svelte +257 -78
  80. package/src/lib/columnsOverlay.ts +21 -17
  81. package/src/lib/componentConfig.test.ts +204 -0
  82. package/src/lib/componentConfigKeys.ts +19 -0
  83. package/src/lib/componentConfigService.ts +88 -0
  84. package/src/lib/copyPopover.ts +30 -0
  85. package/src/lib/cssVarSync.ts +59 -7
  86. package/src/lib/editorConfigStore.ts +0 -10
  87. package/src/lib/editorCore.ts +402 -0
  88. package/src/lib/editorKeybindings.ts +52 -0
  89. package/src/lib/editorPersistence.ts +106 -0
  90. package/src/lib/editorRenderer.ts +74 -0
  91. package/src/lib/editorStore.test.ts +328 -0
  92. package/src/lib/editorStore.ts +412 -0
  93. package/src/lib/editorTypes.ts +100 -0
  94. package/src/lib/editorViewStore.ts +55 -0
  95. package/src/lib/files/versionedFileResource.ts +140 -0
  96. package/src/lib/fontLoader.ts +130 -0
  97. package/src/lib/fontMigration.ts +140 -0
  98. package/src/lib/fontParse.ts +168 -0
  99. package/src/lib/index.ts +48 -30
  100. package/src/lib/lazyConfig.test.ts +54 -0
  101. package/src/lib/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +64 -0
  102. package/src/lib/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +71 -0
  103. package/src/lib/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +43 -0
  104. package/src/lib/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +68 -0
  105. package/src/lib/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +35 -0
  106. package/src/lib/migrations/2026-05-10-sectiondivider-gradient-stops.ts +50 -0
  107. package/src/lib/migrations/2026-05-13-primary-to-brand.ts +90 -0
  108. package/src/lib/migrations/index.ts +93 -0
  109. package/src/lib/migrations/migrations.test.ts +341 -0
  110. package/src/lib/navLinkTypes.ts +1 -0
  111. package/src/lib/overlayState.ts +3 -0
  112. package/src/lib/paletteDerivation.ts +300 -0
  113. package/src/lib/parentRouteStore.ts +42 -0
  114. package/src/lib/parsers/globalRootBlock.ts +32 -0
  115. package/src/lib/presetService.ts +94 -0
  116. package/src/lib/router.ts +42 -10
  117. package/src/lib/scrollSection.ts +45 -0
  118. package/src/lib/slices/columns.ts +59 -0
  119. package/src/lib/slices/components.ts +362 -0
  120. package/src/lib/slices/domainVars.ts +15 -0
  121. package/src/lib/slices/fonts.ts +30 -0
  122. package/src/lib/slices/gradients.ts +153 -0
  123. package/src/lib/slices/overlays.ts +132 -0
  124. package/src/lib/slices/palettes.ts +26 -0
  125. package/src/lib/slices/shadows.ts +123 -0
  126. package/src/lib/storage.ts +88 -0
  127. package/src/lib/themeInit.ts +74 -0
  128. package/src/lib/themeService.ts +101 -0
  129. package/src/lib/themeTypes.ts +146 -0
  130. package/src/lib/tokenRegistry.ts +148 -0
  131. package/src/pages/ComponentEditorPage.svelte +384 -0
  132. package/src/pages/ComponentEditorPage.svelte.d.ts +2 -0
  133. package/src/pages/Editor.svelte +98 -0
  134. package/src/pages/Editor.svelte.d.ts +2 -0
  135. package/src/pages/EditorShell.svelte +348 -0
  136. package/src/styles/_padding.scss +34 -0
  137. package/src/styles/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  138. package/src/styles/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  139. package/src/styles/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  140. package/src/styles/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  141. package/src/styles/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  142. package/src/styles/fonts/Manrope/Manrope-latin.woff2 +0 -0
  143. package/src/styles/fonts.css +22 -10
  144. package/src/styles/form-controls.css +14 -16
  145. package/src/styles/tokens.css +1322 -0
  146. package/src/styles/ui-editor.css +126 -0
  147. package/src/{showcase → ui}/BezierCurveEditor.svelte +14 -14
  148. package/src/{showcase → ui}/ColorEditPanel.svelte +42 -36
  149. package/src/ui/EditorViewSwitcher.svelte +180 -0
  150. package/src/ui/FontStackEditor.svelte +360 -0
  151. package/src/ui/GradientEditor.svelte +461 -0
  152. package/src/ui/GradientStopPicker.svelte +74 -0
  153. package/src/ui/PaletteEditor.svelte +1590 -0
  154. package/src/ui/PaletteEditor.test.ts +108 -0
  155. package/src/ui/PresetFileManager.svelte +567 -0
  156. package/src/ui/ProjectFontsSection.svelte +645 -0
  157. package/src/{showcase → ui}/SurfacesTab.svelte +39 -39
  158. package/src/{showcase → ui}/TextTab.svelte +27 -27
  159. package/src/{showcase/TokenFileManager.svelte → ui/ThemeFileManager.svelte} +196 -112
  160. package/src/ui/Toggle.svelte +108 -0
  161. package/src/ui/UICopyPopover.svelte +78 -0
  162. package/src/{showcase/EditorDialog.svelte → ui/UIDialog.svelte} +66 -25
  163. package/src/ui/UIFontFamilySelector.svelte +309 -0
  164. package/src/ui/UIFontSizeSelector.svelte +165 -0
  165. package/src/ui/UIFontWeightSelector.svelte +52 -0
  166. package/src/ui/UILineHeightSelector.svelte +47 -0
  167. package/src/ui/UILinkToggle.svelte +60 -0
  168. package/src/ui/UIOptionItem.svelte +74 -0
  169. package/src/ui/UIOptionList.svelte +27 -0
  170. package/src/ui/UIPaddingSelector.svelte +661 -0
  171. package/src/ui/UIPaletteSelector.svelte +1084 -0
  172. package/src/ui/UIRadio.svelte +72 -0
  173. package/src/ui/UIRadioGroup.svelte +59 -0
  174. package/src/ui/UIRelinkConfirmPopover.svelte +235 -0
  175. package/src/ui/UITokenSelector.svelte +509 -0
  176. package/src/ui/UIVariantSelector.svelte +145 -0
  177. package/src/ui/VariablesTab.svelte +252 -0
  178. package/src/ui/index.ts +31 -0
  179. package/src/ui/keepInViewport.ts +84 -0
  180. package/src/ui/palette/GradientStopEditor.svelte +482 -0
  181. package/src/ui/palette/OverridesPanel.svelte +526 -0
  182. package/src/ui/palette/PaletteBase.svelte +165 -0
  183. package/src/ui/palette/ScaleCurveEditor.svelte +38 -0
  184. package/src/ui/palette/paletteEditorState.ts +89 -0
  185. package/src/ui/sections/ColumnsSection.svelte +273 -0
  186. package/src/ui/sections/GradientsSection.svelte +147 -0
  187. package/src/ui/sections/OverlaysSection.svelte +670 -0
  188. package/src/ui/sections/ShadowsSection.svelte +1250 -0
  189. package/src/ui/sections/TokenScaleTable.svelte +332 -0
  190. package/src/ui/sections/tokenScales.ts +81 -0
  191. package/src/ui/variantScales.ts +108 -0
  192. package/src/components/DetailNav.svelte +0 -78
  193. package/src/components/Toggle.svelte +0 -86
  194. package/src/lib/tokenInit.ts +0 -29
  195. package/src/lib/tokenService.ts +0 -144
  196. package/src/lib/tokenTypes.ts +0 -45
  197. package/src/pages/Admin.svelte +0 -100
  198. package/src/pages/ShowcasePage.svelte +0 -144
  199. package/src/showcase/BackupBrowser.svelte +0 -617
  200. package/src/showcase/ComponentsTab.svelte +0 -105
  201. package/src/showcase/PaletteEditor.svelte +0 -2579
  202. package/src/showcase/PaletteSelector.svelte +0 -627
  203. package/src/showcase/TokenMap.svelte +0 -54
  204. package/src/showcase/VariablesTab.svelte +0 -2655
  205. package/src/showcase/VisualsTab.svelte +0 -231
  206. package/src/showcase/demos/BadgeDemo.svelte +0 -56
  207. package/src/showcase/demos/CardDemo.svelte +0 -50
  208. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -192
  209. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -54
  210. package/src/showcase/demos/DialogDemo.svelte +0 -42
  211. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -25
  212. package/src/showcase/demos/NotificationDemo.svelte +0 -147
  213. package/src/showcase/demos/ProgressBarDemo.svelte +0 -54
  214. package/src/showcase/demos/RadioButtonDemo.svelte +0 -56
  215. package/src/showcase/demos/SectionDividerDemo.svelte +0 -77
  216. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -455
  217. package/src/showcase/demos/TabBarDemo.svelte +0 -58
  218. package/src/showcase/demos/TooltipDemo.svelte +0 -52
  219. package/src/showcase/editor.css +0 -93
  220. package/src/showcase/index.ts +0 -17
  221. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  222. package/src/styles/fonts/Domine/OFL.txt +0 -97
  223. package/src/styles/fonts/Domine/README.txt +0 -66
  224. /package/src/{showcase → ui}/curveEngine.ts +0 -0
@@ -1,2655 +0,0 @@
1
- <script lang="ts">
2
- import { onMount, onDestroy } from 'svelte';
3
- import PaletteEditor from './PaletteEditor.svelte';
4
- import { setCssVar, removeCssVar } from '../lib/cssVarSync';
5
- import { storageKey } from '../lib/editorConfig';
6
- import type { PaletteConfig } from '../lib/tokenTypes';
7
-
8
- export let saveSignal: number = 0;
9
-
10
- // Column-layout tokens. Values are kept as plain numbers for the editor
11
- // controls; the corresponding CSS vars (--columns-*) are written back on
12
- // every change so the live overlay (see ColumnsOverlay.svelte) updates
13
- // immediately.
14
- let columnsCount = 12;
15
- let columnsMaxWidth = 1440;
16
- let columnsGutter = 16;
17
- let columnsMargin = 0;
18
-
19
- function readColumnsTokens() {
20
- const cs = getComputedStyle(document.documentElement);
21
- const cols = parseInt(cs.getPropertyValue('--columns-count').trim(), 10);
22
- if (!Number.isNaN(cols) && cols > 0) columnsCount = cols;
23
- const maxW = parseFloat(cs.getPropertyValue('--columns-max-width'));
24
- if (!Number.isNaN(maxW)) columnsMaxWidth = Math.round(maxW);
25
- const gutter = parseFloat(cs.getPropertyValue('--columns-gutter'));
26
- if (!Number.isNaN(gutter)) columnsGutter = Math.round(gutter);
27
- const margin = parseFloat(cs.getPropertyValue('--columns-margin'));
28
- if (!Number.isNaN(margin)) columnsMargin = Math.round(margin);
29
- }
30
-
31
- function clampNum(v: number, lo: number, hi: number): number {
32
- return Math.max(lo, Math.min(hi, Math.round(v)));
33
- }
34
-
35
- function setColumnsCount(n: number) {
36
- columnsCount = clampNum(n, 1, 24);
37
- setCssVar('--columns-count', String(columnsCount));
38
- }
39
- function setColumnsMaxWidth(px: number) {
40
- columnsMaxWidth = clampNum(px, 320, 2560);
41
- setCssVar('--columns-max-width', `${columnsMaxWidth}px`);
42
- }
43
- function setColumnsGutter(px: number) {
44
- columnsGutter = clampNum(px, 0, 200);
45
- setCssVar('--columns-gutter', `${columnsGutter}px`);
46
- }
47
- function setColumnsMargin(px: number) {
48
- columnsMargin = clampNum(px, 0, 400);
49
- setCssVar('--columns-margin', `${columnsMargin}px`);
50
- }
51
-
52
- let columnsObserver: MutationObserver | null = null;
53
-
54
- // Snapshot of the inline --columns-* values at mount time. A null entry
55
- // means the property was *not* inline on :root when the editor opened, so
56
- // reset should remove the inline override (letting variables.css win).
57
- const COLUMNS_TOKENS = [
58
- '--columns-count',
59
- '--columns-max-width',
60
- '--columns-gutter',
61
- '--columns-margin',
62
- ] as const;
63
- let columnsInitialSnapshot: Record<string, string | null> = {};
64
-
65
- function resetColumns() {
66
- for (const name of COLUMNS_TOKENS) {
67
- const initial = columnsInitialSnapshot[name];
68
- if (initial) {
69
- setCssVar(name, initial);
70
- } else {
71
- removeCssVar(name);
72
- }
73
- }
74
- // MutationObserver will pick up the style change and re-sync the numeric
75
- // state via readColumnsTokens().
76
- }
77
-
78
- onMount(() => {
79
- const style = document.documentElement.style;
80
- for (const name of COLUMNS_TOKENS) {
81
- columnsInitialSnapshot[name] = style.getPropertyValue(name) || null;
82
- }
83
- readColumnsTokens();
84
- columnsObserver = new MutationObserver(readColumnsTokens);
85
- columnsObserver.observe(document.documentElement, {
86
- attributes: true,
87
- attributeFilter: ['style'],
88
- });
89
- });
90
-
91
- onDestroy(() => {
92
- columnsObserver?.disconnect();
93
- });
94
-
95
- let editors: PaletteEditor[] = [];
96
-
97
- // Map of editor label → array index (matches template order)
98
- const EDITOR_LABELS = ['Neutral', 'Alternate', 'Background', 'Primary', 'Accent', 'Special', 'Success', 'Warning', 'Info', 'Danger'];
99
-
100
- /** Called by VisualsTab when a token file is loaded. */
101
- export function loadAllConfigs(configs: Record<string, PaletteConfig>) {
102
- for (let i = 0; i < EDITOR_LABELS.length; i++) {
103
- const config = configs[EDITOR_LABELS[i]];
104
- if (config && editors[i]) {
105
- editors[i].loadConfig(config);
106
- }
107
- }
108
- }
109
-
110
- interface TokenItem {
111
- variable: string;
112
- value: string;
113
- }
114
-
115
- interface TokenGroup {
116
- title: string;
117
- tokens: TokenItem[];
118
- }
119
-
120
- const spacingTokens: TokenItem[] = [
121
- { variable: '--space-2', value: '0.125rem (2px)' },
122
- { variable: '--space-4', value: '0.25rem (4px)' },
123
- { variable: '--space-6', value: '0.375rem (6px)' },
124
- { variable: '--space-8', value: '0.5rem (8px)' },
125
- { variable: '--space-10', value: '0.625rem (10px)' },
126
- { variable: '--space-12', value: '0.75rem (12px)' },
127
- { variable: '--space-16', value: '1rem (16px)' },
128
- { variable: '--space-20', value: '1.25rem (20px)' },
129
- { variable: '--space-24', value: '1.5rem (24px)' },
130
- { variable: '--space-32', value: '2rem (32px)' },
131
- { variable: '--space-48', value: '3rem (48px)' }
132
- ];
133
-
134
- const radiusTokens: TokenItem[] = [
135
- { variable: '--radius-sm', value: '0.125rem (2px)' },
136
- { variable: '--radius-md', value: '0.25rem (4px)' },
137
- { variable: '--radius-lg', value: '0.375rem (6px)' },
138
- { variable: '--radius-xl', value: '0.5rem (8px)' },
139
- { variable: '--radius-2xl', value: '0.625rem (10px)' },
140
- { variable: '--radius-3xl', value: '0.75rem (12px)' },
141
- { variable: '--radius-full', value: '1.25rem (20px)' }
142
- ];
143
-
144
- const fontSizeTokens: TokenItem[] = [
145
- { variable: '--font-xs', value: '0.875rem (14px)' },
146
- { variable: '--font-sm', value: '1rem (16px)' },
147
- { variable: '--font-md', value: '1.125rem (18px)' },
148
- { variable: '--font-lg', value: '1.25rem (20px)' },
149
- { variable: '--font-xl', value: '1.375rem (22px)' },
150
- { variable: '--font-2xl', value: '1.625rem (26px)' },
151
- { variable: '--font-3xl', value: '2rem (32px)' },
152
- { variable: '--font-4xl', value: '2.5rem (40px)' },
153
- { variable: '--font-5xl', value: '3rem (48px)' },
154
- { variable: '--font-6xl', value: '3.5rem (56px)' }
155
- ];
156
-
157
- const fontWeightTokens: TokenItem[] = [
158
- { variable: '--font-weight-thin', value: '100' },
159
- { variable: '--font-weight-light', value: '200' },
160
- { variable: '--font-weight-medium', value: '300' },
161
- { variable: '--font-weight-semibold', value: '500' },
162
- { variable: '--font-weight-bold', value: '800' }
163
- ];
164
-
165
- const lineHeightTokens: TokenItem[] = [
166
- { variable: '--line-height-tight', value: '1' },
167
- { variable: '--line-height-snug', value: '1.2' },
168
- { variable: '--line-height-normal', value: '1.4' },
169
- { variable: '--line-height-relaxed', value: '1.5' },
170
- { variable: '--line-height-loose', value: '2' }
171
- ];
172
-
173
- interface ShadowToken {
174
- variable: string;
175
- value: string;
176
- x: number;
177
- y: number;
178
- blur: number;
179
- spread: number;
180
- opacity: number;
181
- hue: number;
182
- saturation: number;
183
- lightness: number;
184
- angle: number;
185
- distance: number;
186
- }
187
-
188
- function computeXY(angle: number, distance: number): { x: number; y: number } {
189
- const rad = angle * (Math.PI / 180);
190
- return {
191
- x: Math.round(-distance * Math.cos(rad)),
192
- y: Math.round(distance * Math.sin(rad))
193
- };
194
- }
195
-
196
- function computeAngleDistance(x: number, y: number): { angle: number; distance: number } {
197
- const distance = Math.round(Math.sqrt(x * x + y * y));
198
- if (distance === 0) return { angle: 135, distance: 0 };
199
- let angle = Math.atan2(y, -x) * (180 / Math.PI);
200
- if (angle < 0) angle += 360;
201
- return { angle: Math.round(angle), distance };
202
- }
203
-
204
- function initShadow(variable: string, value: string, x: number, y: number, blur: number, spread: number, opacity: number, hue: number, saturation: number, lightness: number): ShadowToken {
205
- const { angle, distance } = computeAngleDistance(x, y);
206
- return { variable, value, x, y, blur, spread, opacity, hue, saturation, lightness, angle, distance };
207
- }
208
-
209
- function parseShadowFromCss(variable: string): ShadowToken | null {
210
- const raw = getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
211
- if (!raw) return null;
212
- const m = raw.match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(-?\d+)px\s+hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
213
- if (!m) return null;
214
- const x = parseInt(m[1], 10);
215
- const y = parseInt(m[2], 10);
216
- const blur = parseInt(m[3], 10);
217
- const spread = parseInt(m[4], 10);
218
- const hue = Math.round(parseFloat(m[5]));
219
- const saturation = Math.round(parseFloat(m[6]));
220
- const lightness = Math.round(parseFloat(m[7]));
221
- const opacity = parseFloat(m[8]);
222
- const { angle, distance } = computeAngleDistance(x, y);
223
- return { variable, value: `${x} ${y} ${blur}px`, x, y, blur, spread, opacity, hue, saturation, lightness, angle, distance };
224
- }
225
-
226
- const shadowVariableNames = [
227
- '--shadow-sm', '--shadow-md', '--shadow-lg', '--shadow-xl', '--shadow-2xl',
228
- '--shadow-app', '--shadow-focus', '--shadow-glow-green', '--shadow-card', '--shadow-overlay'
229
- ];
230
-
231
- let shadowTokens: ShadowToken[] = shadowVariableNames.map(v =>
232
- parseShadowFromCss(v) ?? initShadow(v, '0 0 0', 0, 0, 0, 0, 0.1, 0, 0, 0)
233
- );
234
-
235
- // Scale tokens that interpolate between global min/max
236
- const scaleVariables = new Set(['--shadow-sm', '--shadow-md', '--shadow-lg', '--shadow-xl', '--shadow-2xl']);
237
-
238
- // Global shadow settings — persisted to localStorage so HMR / remounts don't reset
239
- const SHADOW_STORAGE_KEY = storageKey('shadow-globals');
240
-
241
- interface ShadowGlobals {
242
- globalAngle: number;
243
- globalOpacityMin: number;
244
- globalOpacityMax: number;
245
- opacityLocked: boolean;
246
- globalDistanceMin: number;
247
- globalDistanceMax: number;
248
- globalBlurMin: number;
249
- globalBlurMax: number;
250
- blurLocked: boolean;
251
- globalSizeMin: number;
252
- globalSizeMax: number;
253
- sizeLocked: boolean;
254
- globalHue: number;
255
- globalSaturation: number;
256
- globalLightness: number;
257
- overrides: Record<string, { angle: boolean; opacity: boolean; color: boolean; distance: boolean; blur: boolean; size: boolean }>;
258
- tokens: Array<{ variable: string; x: number; y: number; blur: number; spread: number; opacity: number; hue: number; saturation: number; lightness: number; angle: number; distance: number }>;
259
- }
260
-
261
- function loadShadowGlobals(): Partial<ShadowGlobals> {
262
- try {
263
- const raw = localStorage.getItem(SHADOW_STORAGE_KEY);
264
- return raw ? JSON.parse(raw) : {};
265
- } catch { return {}; }
266
- }
267
-
268
- const saved = loadShadowGlobals();
269
-
270
- let globalAngle = saved.globalAngle ?? 90;
271
- let globalOpacityMin = saved.globalOpacityMin ?? 0.15;
272
- let globalOpacityMax = saved.globalOpacityMax ?? 0.15;
273
- let opacityLocked = saved.opacityLocked ?? true;
274
- let globalDistanceMin = saved.globalDistanceMin ?? 1;
275
- let globalDistanceMax = saved.globalDistanceMax ?? 25;
276
- let globalBlurMin = saved.globalBlurMin ?? 2;
277
- let globalBlurMax = saved.globalBlurMax ?? 50;
278
- let blurLocked = saved.blurLocked ?? false;
279
- let globalSizeMin = saved.globalSizeMin ?? 0;
280
- let globalSizeMax = saved.globalSizeMax ?? 0;
281
- let sizeLocked = saved.sizeLocked ?? true;
282
- let globalHue = saved.globalHue ?? 0;
283
- let globalSaturation = saved.globalSaturation ?? 0;
284
- let globalLightness = saved.globalLightness ?? 0;
285
- let globalDialDrag = false;
286
-
287
- // Per-token overrides: when true, the token ignores global values
288
- let overrides: Record<string, { angle: boolean; opacity: boolean; color: boolean; distance: boolean; blur: boolean; size: boolean }> = saved.overrides ?? {};
289
-
290
- // Restore per-token values from localStorage
291
- if (saved.tokens) {
292
- for (const st of saved.tokens) {
293
- const token = shadowTokens.find(t => t.variable === st.variable);
294
- if (token) {
295
- token.x = st.x; token.y = st.y; token.blur = st.blur; token.spread = st.spread;
296
- token.opacity = st.opacity; token.hue = st.hue; token.saturation = st.saturation;
297
- token.lightness = st.lightness; token.angle = st.angle; token.distance = st.distance;
298
- token.value = `${st.x} ${st.y} ${st.blur}px`;
299
- }
300
- }
301
- }
302
-
303
- function saveShadowGlobals() {
304
- const data: ShadowGlobals = {
305
- globalAngle, globalOpacityMin, globalOpacityMax, opacityLocked,
306
- globalDistanceMin, globalDistanceMax,
307
- globalBlurMin, globalBlurMax, blurLocked,
308
- globalSizeMin, globalSizeMax, sizeLocked,
309
- globalHue, globalSaturation, globalLightness, overrides,
310
- tokens: shadowTokens.map(t => ({
311
- variable: t.variable, x: t.x, y: t.y, blur: t.blur, spread: t.spread,
312
- opacity: t.opacity, hue: t.hue, saturation: t.saturation, lightness: t.lightness,
313
- angle: t.angle, distance: t.distance,
314
- })),
315
- };
316
- localStorage.setItem(SHADOW_STORAGE_KEY, JSON.stringify(data));
317
- }
318
-
319
- function getOverride(variable: string) {
320
- if (!overrides[variable]) overrides[variable] = { angle: false, opacity: false, color: false, distance: false, blur: false, size: false };
321
- return overrides[variable];
322
- }
323
-
324
- function applyGlobalAngle() {
325
- for (const t of shadowTokens) {
326
- if (scaleVariables.has(t.variable) && !getOverride(t.variable).angle) {
327
- t.angle = globalAngle;
328
- const { x, y } = computeXY(t.angle, t.distance);
329
- t.x = x;
330
- t.y = y;
331
- applyShadow(t);
332
- }
333
- }
334
- shadowTokens = shadowTokens;
335
- saveShadowGlobals();
336
- }
337
-
338
- function applyGlobalOpacity() {
339
- const eligible = shadowTokens.filter(t => scaleVariables.has(t.variable) && !getOverride(t.variable).opacity);
340
- if (eligible.length === 0) return;
341
- const last = eligible.length - 1;
342
- eligible.forEach((t, i) => {
343
- const frac = last > 0 ? i / last : 0.5;
344
- t.opacity = Math.round((globalOpacityMin + frac * (globalOpacityMax - globalOpacityMin)) * 100) / 100;
345
- applyShadow(t);
346
- });
347
- shadowTokens = shadowTokens;
348
- saveShadowGlobals();
349
- }
350
-
351
- function applyGlobalDistance() {
352
- const eligible = shadowTokens.filter(t => scaleVariables.has(t.variable) && !getOverride(t.variable).distance);
353
- if (eligible.length === 0) return;
354
- const last = eligible.length - 1;
355
- eligible.forEach((t, i) => {
356
- const frac = last > 0 ? i / last : 0.5;
357
- t.distance = Math.round(globalDistanceMin + frac * (globalDistanceMax - globalDistanceMin));
358
- const { x, y } = computeXY(t.angle, t.distance);
359
- t.x = x;
360
- t.y = y;
361
- applyShadow(t);
362
- });
363
- shadowTokens = shadowTokens;
364
- saveShadowGlobals();
365
- }
366
-
367
- function applyGlobalBlur() {
368
- const eligible = shadowTokens.filter(t => scaleVariables.has(t.variable) && !getOverride(t.variable).blur);
369
- if (eligible.length === 0) return;
370
- const last = eligible.length - 1;
371
- eligible.forEach((t, i) => {
372
- const frac = last > 0 ? i / last : 0.5;
373
- t.blur = Math.round(globalBlurMin + frac * (globalBlurMax - globalBlurMin));
374
- applyShadow(t);
375
- });
376
- shadowTokens = shadowTokens;
377
- saveShadowGlobals();
378
- }
379
-
380
- function applyGlobalSize() {
381
- const eligible = shadowTokens.filter(t => scaleVariables.has(t.variable) && !getOverride(t.variable).size);
382
- if (eligible.length === 0) return;
383
- const last = eligible.length - 1;
384
- eligible.forEach((t, i) => {
385
- const frac = last > 0 ? i / last : 0.5;
386
- t.spread = Math.round(globalSizeMin + frac * (globalSizeMax - globalSizeMin));
387
- applyShadow(t);
388
- });
389
- shadowTokens = shadowTokens;
390
- saveShadowGlobals();
391
- }
392
-
393
- function applyGlobalColor() {
394
- for (const t of shadowTokens) {
395
- if (scaleVariables.has(t.variable) && !getOverride(t.variable).color) {
396
- t.hue = globalHue;
397
- t.saturation = globalSaturation;
398
- t.lightness = globalLightness;
399
- applyShadow(t);
400
- }
401
- }
402
- shadowTokens = shadowTokens;
403
- saveShadowGlobals();
404
- }
405
-
406
- function handleGlobalDialDown(event: PointerEvent) {
407
- globalDialDrag = true;
408
- const svg = event.currentTarget as SVGSVGElement;
409
- svg.setPointerCapture(event.pointerId);
410
- globalAngle = angleFromPointer(event, svg);
411
- applyGlobalAngle();
412
- }
413
-
414
- function handleGlobalDialMove(event: PointerEvent) {
415
- if (!globalDialDrag) return;
416
- const svg = event.currentTarget as SVGSVGElement;
417
- globalAngle = angleFromPointer(event, svg);
418
- applyGlobalAngle();
419
- }
420
-
421
- function handleGlobalDialUp() {
422
- globalDialDrag = false;
423
- }
424
-
425
- // HSL gradient helpers for shadow color picker
426
- function shadowHueGrad(): string {
427
- return `linear-gradient(to right, ${
428
- [0, 60, 120, 180, 240, 300, 360].map(h => `hsl(${h},${globalSaturation}%,${globalLightness}%)`).join(',')
429
- })`;
430
- }
431
- function shadowSatGrad(): string {
432
- return `linear-gradient(to right, hsl(${globalHue},0%,${globalLightness}%), hsl(${globalHue},100%,${globalLightness}%))`;
433
- }
434
- function shadowLightGrad(): string {
435
- return `linear-gradient(to right, hsl(${globalHue},${globalSaturation}%,0%), hsl(${globalHue},${globalSaturation}%,50%), hsl(${globalHue},${globalSaturation}%,100%))`;
436
- }
437
-
438
- let editingShadow: string | null = null;
439
- let dialDragIdx: number | null = null;
440
-
441
- function getShadowCss(t: ShadowToken): string {
442
- return `${t.x}px ${t.y}px ${t.blur}px ${t.spread}px hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
443
- }
444
-
445
- function applyShadow(t: ShadowToken, persist = false) {
446
- t.value = `${t.x} ${t.y} ${t.blur}px`;
447
- setCssVar(t.variable, getShadowCss(t));
448
- if (persist) saveShadowGlobals();
449
- }
450
-
451
- // Apply restored CSS vars on init
452
- for (const t of shadowTokens) applyShadow(t);
453
-
454
- function handleAngleChange(idx: number, newAngle: number) {
455
- const t = shadowTokens[idx];
456
- t.angle = ((Math.round(newAngle) % 360) + 360) % 360;
457
- const { x, y } = computeXY(t.angle, t.distance);
458
- t.x = x;
459
- t.y = y;
460
- applyShadow(t);
461
- shadowTokens = shadowTokens;
462
- saveShadowGlobals();
463
- }
464
-
465
- function handleDistanceChange(idx: number, newDist: number) {
466
- const t = shadowTokens[idx];
467
- t.distance = Math.max(0, Math.round(newDist));
468
- const { x, y } = computeXY(t.angle, t.distance);
469
- t.x = x;
470
- t.y = y;
471
- applyShadow(t);
472
- shadowTokens = shadowTokens;
473
- saveShadowGlobals();
474
- }
475
-
476
- function angleFromPointer(event: PointerEvent, dialEl: SVGSVGElement): number {
477
- const rect = dialEl.getBoundingClientRect();
478
- const cx = rect.left + rect.width / 2;
479
- const cy = rect.top + rect.height / 2;
480
- const dx = event.clientX - cx;
481
- const dy = event.clientY - cy;
482
- let angle = Math.atan2(-dy, dx) * (180 / Math.PI);
483
- if (angle < 0) angle += 360;
484
- return Math.round(angle);
485
- }
486
-
487
- function handleDialDown(event: PointerEvent, idx: number) {
488
- dialDragIdx = idx;
489
- const svg = event.currentTarget as SVGSVGElement;
490
- svg.setPointerCapture(event.pointerId);
491
- handleAngleChange(idx, angleFromPointer(event, svg));
492
- }
493
-
494
- function handleDialMove(event: PointerEvent, idx: number) {
495
- if (dialDragIdx !== idx) return;
496
- const svg = event.currentTarget as SVGSVGElement;
497
- handleAngleChange(idx, angleFromPointer(event, svg));
498
- }
499
-
500
- function handleDialUp() {
501
- dialDragIdx = null;
502
- }
503
-
504
- $: editingToken = editingShadow ? shadowTokens.find(t => t.variable === editingShadow) ?? null : null;
505
- $: editingIdx = editingToken ? shadowTokens.indexOf(editingToken) : -1;
506
- $: editingIsScale = editingToken ? scaleVariables.has(editingToken.variable) : false;
507
-
508
- function resetToGlobal() {
509
- if (!editingToken || !editingIsScale) return;
510
- const eligible = shadowTokens.filter(t => scaleVariables.has(t.variable));
511
- const pos = eligible.indexOf(editingToken);
512
- const last = eligible.length - 1;
513
- const frac = last > 0 ? pos / last : 0.5;
514
- const ov = getOverride(editingToken.variable);
515
- // Reset color
516
- editingToken.hue = globalHue;
517
- editingToken.saturation = globalSaturation;
518
- editingToken.lightness = globalLightness;
519
- ov.color = false;
520
- // Reset angle
521
- editingToken.angle = globalAngle;
522
- ov.angle = false;
523
- // Reset distance (interpolated)
524
- editingToken.distance = Math.round(globalDistanceMin + frac * (globalDistanceMax - globalDistanceMin));
525
- ov.distance = false;
526
- // Reset blur (interpolated)
527
- editingToken.blur = Math.round(globalBlurMin + frac * (globalBlurMax - globalBlurMin));
528
- ov.blur = false;
529
- // Reset spread (interpolated)
530
- editingToken.spread = Math.round(globalSizeMin + frac * (globalSizeMax - globalSizeMin));
531
- ov.size = false;
532
- // Reset opacity (interpolated)
533
- editingToken.opacity = Math.round((globalOpacityMin + frac * (globalOpacityMax - globalOpacityMin)) * 100) / 100;
534
- ov.opacity = false;
535
- const { x, y } = computeXY(editingToken.angle, editingToken.distance);
536
- editingToken.x = x;
537
- editingToken.y = y;
538
- applyShadow(editingToken);
539
- overrides = overrides;
540
- shadowTokens = shadowTokens;
541
- saveShadowGlobals();
542
- }
543
-
544
- // Background picker for shadows section
545
- interface ColorGroup {
546
- label: string;
547
- colors: { label: string; value: string }[];
548
- }
549
-
550
- const bgColorGroups: ColorGroup[] = [
551
- {
552
- label: 'Surfaces',
553
- colors: [
554
- { label: 'Neutral Lowest', value: 'var(--surface-neutral-lowest)' },
555
- { label: 'Neutral Lower', value: 'var(--surface-neutral-lower)' },
556
- { label: 'Neutral Low', value: 'var(--surface-neutral-low)' },
557
- { label: 'Neutral', value: 'var(--surface-neutral)' },
558
- { label: 'Neutral High', value: 'var(--surface-neutral-high)' },
559
- { label: 'Neutral Higher', value: 'var(--surface-neutral-higher)' },
560
- { label: 'Neutral Highest', value: 'var(--surface-neutral-highest)' },
561
- ]
562
- },
563
- {
564
- label: 'Alternate',
565
- colors: [
566
- { label: 'Lowest', value: 'var(--surface-alternate-lowest)' },
567
- { label: 'Lower', value: 'var(--surface-alternate-lower)' },
568
- { label: 'Low', value: 'var(--surface-alternate-low)' },
569
- { label: 'Base', value: 'var(--surface-alternate)' },
570
- { label: 'High', value: 'var(--surface-alternate-high)' },
571
- { label: 'Higher', value: 'var(--surface-alternate-higher)' },
572
- { label: 'Highest', value: 'var(--surface-alternate-highest)' },
573
- ]
574
- },
575
- {
576
- label: 'Background',
577
- colors: [
578
- { label: 'Lowest', value: 'var(--surface-bg-lowest)' },
579
- { label: 'Lower', value: 'var(--surface-bg-lower)' },
580
- { label: 'Low', value: 'var(--surface-bg-low)' },
581
- { label: 'Base', value: 'var(--surface-bg)' },
582
- { label: 'High', value: 'var(--surface-bg-high)' },
583
- { label: 'Higher', value: 'var(--surface-bg-higher)' },
584
- { label: 'Highest', value: 'var(--surface-bg-highest)' },
585
- ]
586
- },
587
- {
588
- label: 'Primary',
589
- colors: [
590
- { label: 'Lowest', value: 'var(--surface-primary-lowest)' },
591
- { label: 'Lower', value: 'var(--surface-primary-lower)' },
592
- { label: 'Low', value: 'var(--surface-primary-low)' },
593
- { label: 'Base', value: 'var(--surface-primary)' },
594
- { label: 'High', value: 'var(--surface-primary-high)' },
595
- { label: 'Higher', value: 'var(--surface-primary-higher)' },
596
- { label: 'Highest', value: 'var(--surface-primary-highest)' },
597
- ]
598
- },
599
- {
600
- label: 'Accent',
601
- colors: [
602
- { label: 'Lowest', value: 'var(--surface-accent-lowest)' },
603
- { label: 'Lower', value: 'var(--surface-accent-lower)' },
604
- { label: 'Low', value: 'var(--surface-accent-low)' },
605
- { label: 'Base', value: 'var(--surface-accent)' },
606
- { label: 'High', value: 'var(--surface-accent-high)' },
607
- { label: 'Higher', value: 'var(--surface-accent-higher)' },
608
- { label: 'Highest', value: 'var(--surface-accent-highest)' },
609
- ]
610
- },
611
- {
612
- label: 'Special',
613
- colors: [
614
- { label: 'Lowest', value: 'var(--surface-special-lowest)' },
615
- { label: 'Lower', value: 'var(--surface-special-lower)' },
616
- { label: 'Low', value: 'var(--surface-special-low)' },
617
- { label: 'Base', value: 'var(--surface-special)' },
618
- { label: 'High', value: 'var(--surface-special-high)' },
619
- { label: 'Higher', value: 'var(--surface-special-higher)' },
620
- { label: 'Highest', value: 'var(--surface-special-highest)' },
621
- ]
622
- },
623
- {
624
- label: 'Color Ramps',
625
- colors: [
626
- { label: 'Neutral 800', value: 'var(--color-neutral-800)' },
627
- { label: 'Neutral 700', value: 'var(--color-neutral-700)' },
628
- { label: 'Neutral 600', value: 'var(--color-neutral-600)' },
629
- { label: 'Neutral 500', value: 'var(--color-neutral-500)' },
630
- { label: 'Neutral 400', value: 'var(--color-neutral-400)' },
631
- { label: 'Neutral 300', value: 'var(--color-neutral-300)' },
632
- { label: 'Neutral 200', value: 'var(--color-neutral-200)' },
633
- { label: 'Neutral 100', value: 'var(--color-neutral-100)' },
634
- { label: 'White', value: '#ffffff' },
635
- ]
636
- },
637
- ];
638
-
639
- let shadowBg = 'var(--ui-surface-highest)';
640
- let bgPickerOpen = false;
641
- let expandedGroup: string | null = null;
642
-
643
- function pickBg(value: string) {
644
- shadowBg = value;
645
- bgPickerOpen = false;
646
- expandedGroup = null;
647
- }
648
-
649
- function toggleBgPicker() {
650
- bgPickerOpen = !bgPickerOpen;
651
- if (!bgPickerOpen) expandedGroup = null;
652
- }
653
-
654
- // ── Overlay tokens ──
655
- interface OverlayToken {
656
- variable: string;
657
- label: string;
658
- r: number;
659
- g: number;
660
- b: number;
661
- opacity: number;
662
- }
663
-
664
- function initOverlay(variable: string, label: string, r: number, g: number, b: number, opacity: number): OverlayToken {
665
- return { variable, label, r, g, b, opacity };
666
- }
667
-
668
- let overlayTokens: OverlayToken[] = [
669
- initOverlay('--overlay-lowest', 'Lowest', 0, 0, 0, 0.05),
670
- initOverlay('--overlay-lower', 'Lower', 0, 0, 0, 0.1),
671
- initOverlay('--overlay-low', 'Low', 0, 0, 0, 0.2),
672
- initOverlay('--overlay', 'Base', 0, 0, 0, 0.3),
673
- initOverlay('--overlay-high', 'High', 0, 0, 0, 0.5),
674
- initOverlay('--overlay-higher', 'Higher', 0, 0, 0, 0.7),
675
- initOverlay('--overlay-highest', 'Highest', 0, 0, 0, 0.95),
676
- ];
677
-
678
- let hoverTokens: OverlayToken[] = [
679
- initOverlay('--hover-low', 'Low', 255, 255, 255, 0.05),
680
- initOverlay('--hover', 'Base', 255, 255, 255, 0.1),
681
- initOverlay('--hover-high', 'High', 255, 255, 255, 0.15),
682
- ];
683
-
684
- // Global overlay settings
685
- let overlayHue = 0;
686
- let overlaySaturation = 0;
687
- let overlayLightness = 0;
688
- let overlayOpacityMin = 0.05;
689
- let overlayOpacityMax = 0.95;
690
-
691
- let hoverHue = 0;
692
- let hoverSaturation = 0;
693
- let hoverLightness = 100;
694
- let hoverOpacityMin = 0.05;
695
- let hoverOpacityMax = 0.15;
696
-
697
- let editingOverlay: string | null = null;
698
-
699
- function getOverlayCss(t: OverlayToken): string {
700
- return `rgba(${t.r}, ${t.g}, ${t.b}, ${t.opacity})`;
701
- }
702
-
703
- function applyOverlay(t: OverlayToken) {
704
- setCssVar(t.variable, getOverlayCss(t));
705
- }
706
-
707
- function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
708
- s /= 100; l /= 100;
709
- const c = (1 - Math.abs(2 * l - 1)) * s;
710
- const x = c * (1 - Math.abs((h / 60) % 2 - 1));
711
- const m = l - c / 2;
712
- let r1 = 0, g1 = 0, b1 = 0;
713
- if (h < 60) { r1 = c; g1 = x; }
714
- else if (h < 120) { r1 = x; g1 = c; }
715
- else if (h < 180) { g1 = c; b1 = x; }
716
- else if (h < 240) { g1 = x; b1 = c; }
717
- else if (h < 300) { r1 = x; b1 = c; }
718
- else { r1 = c; b1 = x; }
719
- return {
720
- r: Math.round((r1 + m) * 255),
721
- g: Math.round((g1 + m) * 255),
722
- b: Math.round((b1 + m) * 255),
723
- };
724
- }
725
-
726
- function applyGlobalOverlayColor() {
727
- const { r, g, b } = hslToRgb(overlayHue, overlaySaturation, overlayLightness);
728
- for (const t of overlayTokens) {
729
- t.r = r; t.g = g; t.b = b;
730
- applyOverlay(t);
731
- }
732
- overlayTokens = overlayTokens;
733
- }
734
-
735
- function applyGlobalOverlayOpacity() {
736
- const last = overlayTokens.length - 1;
737
- overlayTokens.forEach((t, i) => {
738
- const frac = last > 0 ? i / last : 0.5;
739
- t.opacity = Math.round((overlayOpacityMin + frac * (overlayOpacityMax - overlayOpacityMin)) * 100) / 100;
740
- applyOverlay(t);
741
- });
742
- overlayTokens = overlayTokens;
743
- }
744
-
745
- function applyGlobalHoverColor() {
746
- const { r, g, b } = hslToRgb(hoverHue, hoverSaturation, hoverLightness);
747
- for (const t of hoverTokens) {
748
- t.r = r; t.g = g; t.b = b;
749
- applyOverlay(t);
750
- }
751
- hoverTokens = hoverTokens;
752
- }
753
-
754
- function applyGlobalHoverOpacity() {
755
- const last = hoverTokens.length - 1;
756
- hoverTokens.forEach((t, i) => {
757
- const frac = last > 0 ? i / last : 0.5;
758
- t.opacity = Math.round((hoverOpacityMin + frac * (hoverOpacityMax - hoverOpacityMin)) * 100) / 100;
759
- applyOverlay(t);
760
- });
761
- hoverTokens = hoverTokens;
762
- }
763
-
764
- function overlayHueGrad(): string {
765
- return `linear-gradient(to right, ${
766
- [0, 60, 120, 180, 240, 300, 360].map(h => `hsl(${h},${overlaySaturation}%,${overlayLightness}%)`).join(',')
767
- })`;
768
- }
769
- function overlaySatGrad(): string {
770
- return `linear-gradient(to right, hsl(${overlayHue},0%,${overlayLightness}%), hsl(${overlayHue},100%,${overlayLightness}%))`;
771
- }
772
- function overlayLightGrad(): string {
773
- return `linear-gradient(to right, hsl(${overlayHue},${overlaySaturation}%,0%), hsl(${overlayHue},${overlaySaturation}%,50%), hsl(${overlayHue},${overlaySaturation}%,100%))`;
774
- }
775
-
776
- function hoverHueGrad(): string {
777
- return `linear-gradient(to right, ${
778
- [0, 60, 120, 180, 240, 300, 360].map(h => `hsl(${h},${hoverSaturation}%,${hoverLightness}%)`).join(',')
779
- })`;
780
- }
781
- function hoverSatGrad(): string {
782
- return `linear-gradient(to right, hsl(${hoverHue},0%,${hoverLightness}%), hsl(${hoverHue},100%,${hoverLightness}%))`;
783
- }
784
- function hoverLightGrad(): string {
785
- return `linear-gradient(to right, hsl(${hoverHue},${hoverSaturation}%,0%), hsl(${hoverHue},${hoverSaturation}%,50%), hsl(${hoverHue},${hoverSaturation}%,100%))`;
786
- }
787
-
788
- const textShadowTokens: TokenItem[] = [
789
- { variable: '--text-shadow-sm', value: '1px 1px 2px' },
790
- { variable: '--text-shadow-md', value: '2px 2px 4px' }
791
- ];
792
-
793
- const transitionTokens: TokenItem[] = [
794
- { variable: '--transition-fast', value: '150ms cubic-bezier' },
795
- { variable: '--transition-base', value: '200ms ease' },
796
- { variable: '--transition-slow', value: '300ms ease' }
797
- ];
798
-
799
- const zIndexTokens: TokenItem[] = [
800
- { variable: '--z-base', value: '0' },
801
- { variable: '--z-dropdown', value: '100' },
802
- { variable: '--z-sticky', value: '200' },
803
- { variable: '--z-overlay', value: '1000' },
804
- { variable: '--z-modal', value: '1100' },
805
- { variable: '--z-popover', value: '1200' },
806
- { variable: '--z-tooltip', value: '1300' }
807
- ];
808
-
809
- const opacityTokens: TokenItem[] = [
810
- { variable: '--opacity-disabled', value: '0.6' },
811
- { variable: '--opacity-hover', value: '0.8' },
812
- { variable: '--opacity-muted', value: '0.5' }
813
- ];
814
-
815
- const gradientTokens: TokenItem[] = [
816
- { variable: '--gradient-progress', value: 'crimson → gold (90deg)' }
817
- ];
818
-
819
- let copiedVar: string | null = null;
820
- function copyVariable(v: string) {
821
- navigator.clipboard.writeText(v);
822
- copiedVar = v;
823
- setTimeout(() => { copiedVar = null; }, 1000);
824
- }
825
-
826
- interface FontStack {
827
- variable: string;
828
- fonts: Array<{ name: string; family: string }>;
829
- }
830
-
831
- const fontStacks: FontStack[] = [
832
- {
833
- variable: '--font-display',
834
- fonts: [
835
- { name: 'Astounder Squared BB', family: '"astounder-squared-bb", sans-serif' },
836
- { name: 'Astounder Squared LC BB', family: '"astounder-squared-lc-bb", sans-serif' },
837
- { name: 'Signika', family: '"Signika", sans-serif' },
838
- { name: 'sans-serif', family: 'sans-serif' }
839
- ]
840
- },
841
- {
842
- variable: '--font-sans',
843
- fonts: [
844
- { name: 'Montserrat', family: '"Montserrat", sans-serif' },
845
- { name: 'Segoe UI', family: '"Segoe UI", sans-serif' },
846
- { name: 'Open Sans', family: '"Open Sans", sans-serif' },
847
- { name: 'Helvetica Neue', family: '"Helvetica Neue", sans-serif' },
848
- { name: 'sans-serif', family: 'sans-serif' }
849
- ]
850
- },
851
- {
852
- variable: '--font-serif',
853
- fonts: [
854
- { name: 'Domine', family: '"Domine", serif' },
855
- { name: 'serif', family: 'serif' }
856
- ]
857
- },
858
- {
859
- variable: '--font-mono',
860
- fonts: [
861
- { name: 'Fira Code', family: '"fira-code", monospace' },
862
- { name: 'Source Code Pro', family: '"Source Code Pro", monospace' },
863
- { name: 'Courier New', family: '"Courier New", monospace' },
864
- { name: 'monospace', family: 'monospace' }
865
- ]
866
- }
867
- ];
868
-
869
- </script>
870
-
871
- <div class="variables-container">
872
- <!-- Palette Editor -->
873
- <section class="section" id="palette-editor">
874
- <h2 class="section-title">Palette Editor</h2>
875
- <p class="editor-intro">Derived palettes via <code>color-mix(in oklch)</code>. Change a base color to update all derived steps. Click any derived swatch to add a manual override.</p>
876
- <div class="palette-editors">
877
- <PaletteEditor bind:this={editors[0]} mode="gray" label="Neutral" cssNamespace="neutral" {saveSignal} />
878
- <PaletteEditor bind:this={editors[1]} mode="gray" label="Alternate" cssNamespace="alternate" {saveSignal} />
879
- <PaletteEditor bind:this={editors[2]} label="Background" initialColor="#1a1a2e" cssNamespace="bg" emptySelector {saveSignal} />
880
- <PaletteEditor bind:this={editors[3]} label="Primary" initialColor="#c93636" cssNamespace="primary" {saveSignal} />
881
- <PaletteEditor bind:this={editors[4]} label="Accent" initialColor="#f49e0b" cssNamespace="accent" {saveSignal} />
882
- <PaletteEditor bind:this={editors[5]} label="Special" initialColor="#8b5cf6" cssNamespace="special" {saveSignal} />
883
- <PaletteEditor bind:this={editors[6]} label="Success" initialColor="#21c45d" cssNamespace="success" {saveSignal} />
884
- <PaletteEditor bind:this={editors[7]} label="Warning" initialColor="#e66e1a" cssNamespace="warning" {saveSignal} />
885
- <PaletteEditor bind:this={editors[8]} label="Info" initialColor="#3077e8" cssNamespace="info" {saveSignal} />
886
- <PaletteEditor bind:this={editors[9]} label="Danger" initialColor="#e8304f" cssNamespace="danger" {saveSignal} />
887
- </div>
888
- </section>
889
-
890
- <!-- Spacing -->
891
- <section class="section" id="spacing">
892
- <h2 class="section-title">Spacing</h2>
893
- <div class="spacing-grid">
894
- {#each spacingTokens as token}
895
- <div class="spacing-item">
896
- <div class="spacing-bar" style="width: var({token.variable}); min-width: 2px;"></div>
897
- <div class="token-info">
898
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
899
- <span class="token-value">{token.value}</span>
900
- </div>
901
- </div>
902
- {/each}
903
- </div>
904
- </section>
905
-
906
- <!-- Columns -->
907
- <section class="section" id="columns">
908
- <h2 class="section-title">Columns</h2>
909
- <p class="columns-intro">
910
- Layout grid for page content. Toggle the live overlay from the admin bar's
911
- <i class="fas fa-grip-lines-vertical"></i> button to visualize.
912
- </p>
913
-
914
- <div class="columns-controls">
915
- <div class="global-shadow-row">
916
- <span class="shadow-slider-label" title="Number of columns">Cols</span>
917
- <input type="range" min="1" max="24" value={columnsCount}
918
- on:input={(e) => setColumnsCount(+e.currentTarget.value)} />
919
- <input class="shadow-slider-input" type="number" min="1" max="24"
920
- value={columnsCount}
921
- on:change={(e) => setColumnsCount(+e.currentTarget.value)} />
922
- <span class="shadow-slider-unit"></span>
923
- </div>
924
- <div class="global-shadow-row">
925
- <span class="shadow-slider-label" title="Maximum content width">Max-Width</span>
926
- <input type="range" min="480" max="2560" step="10" value={columnsMaxWidth}
927
- on:input={(e) => setColumnsMaxWidth(+e.currentTarget.value)} />
928
- <input class="shadow-slider-input columns-input-wide" type="number" min="320" max="2560"
929
- value={columnsMaxWidth}
930
- on:change={(e) => setColumnsMaxWidth(+e.currentTarget.value)} />
931
- <span class="shadow-slider-unit">px</span>
932
- </div>
933
- <div class="global-shadow-row">
934
- <span class="shadow-slider-label" title="Space between columns">Gutter</span>
935
- <input type="range" min="0" max="80" value={columnsGutter}
936
- on:input={(e) => setColumnsGutter(+e.currentTarget.value)} />
937
- <input class="shadow-slider-input" type="number" min="0" max="200"
938
- value={columnsGutter}
939
- on:change={(e) => setColumnsGutter(+e.currentTarget.value)} />
940
- <span class="shadow-slider-unit">px</span>
941
- </div>
942
- <div class="global-shadow-row">
943
- <span class="shadow-slider-label" title="Outer page margin (side gutters)">Margin</span>
944
- <input type="range" min="0" max="200" value={columnsMargin}
945
- on:input={(e) => setColumnsMargin(+e.currentTarget.value)} />
946
- <input class="shadow-slider-input columns-input-wide" type="number" min="0" max="400"
947
- value={columnsMargin}
948
- on:change={(e) => setColumnsMargin(+e.currentTarget.value)} />
949
- <span class="shadow-slider-unit">px</span>
950
- </div>
951
-
952
- <div class="columns-controls-footer">
953
- <button class="columns-reset" on:click={resetColumns} title="Restore values from when this editor session opened">
954
- <i class="fas fa-rotate-left"></i>
955
- Reset to initial
956
- </button>
957
- </div>
958
- </div>
959
-
960
- <div class="columns-preview">
961
- <div
962
- class="columns-preview-inner"
963
- style="gap: {columnsGutter}px; padding-inline: {columnsMargin}px; grid-template-columns: repeat({columnsCount}, 1fr);"
964
- >
965
- {#each Array(columnsCount) as _, i}
966
- <div class="columns-preview-col"><span>{i + 1}</span></div>
967
- {/each}
968
- </div>
969
- </div>
970
- </section>
971
-
972
- <!-- Border Radius -->
973
- <section class="section" id="border-radius">
974
- <h2 class="section-title">Border Radius</h2>
975
- <div class="radius-grid">
976
- {#each radiusTokens as token}
977
- <div class="radius-item">
978
- <div class="radius-box" style="border-radius: var({token.variable});"></div>
979
- <div class="token-info">
980
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
981
- <span class="token-value">{token.value}</span>
982
- </div>
983
- </div>
984
- {/each}
985
- </div>
986
- </section>
987
-
988
- <!-- Typography -->
989
- <section class="section" id="typography">
990
- <h2 class="section-title">Typography</h2>
991
-
992
- <div class="typography-columns">
993
- <div class="typography-group font-families-group">
994
- <h3 class="group-title">Font Families</h3>
995
- <div class="font-stacks-columns">
996
- {#each fontStacks as stack}
997
- <div class="font-stack">
998
- <button class="token-variable copyable" class:copied={copiedVar === stack.variable} on:click={() => copyVariable(stack.variable)}>{copiedVar === stack.variable ? 'copied!' : stack.variable}</button>
999
- <div class="font-stack-list">
1000
- {#each stack.fonts as font, i}
1001
- <div class="font-stack-item">
1002
- <span class="font-stack-position">{i + 1}.</span>
1003
- <div class="font-stack-preview-block">
1004
- <span class="font-stack-preview" style="font-family: {font.family};{stack.variable === '--font-display' ? ' font-size: var(--font-2xl);' : ''}">The quick brown fox jumps over the lazy dog</span>
1005
- <span class="font-stack-name">{font.name}</span>
1006
- </div>
1007
- </div>
1008
- {/each}
1009
- </div>
1010
- </div>
1011
- {/each}
1012
- </div>
1013
- </div>
1014
-
1015
- <div class="typography-group">
1016
- <h3 class="group-title">Font Sizes</h3>
1017
- <div class="font-size-demos">
1018
- {#each fontSizeTokens as token}
1019
- <div class="font-size-item">
1020
- <span class="font-size-preview" style="font-size: var({token.variable});">Ag</span>
1021
- <div class="token-info">
1022
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1023
- <span class="token-value">{token.value}</span>
1024
- </div>
1025
- </div>
1026
- {/each}
1027
- </div>
1028
- </div>
1029
-
1030
- <div class="typography-group">
1031
- <h3 class="group-title">Font Weights</h3>
1032
- <div class="font-weight-demos">
1033
- {#each fontWeightTokens as token}
1034
- <div class="font-weight-item">
1035
- <span class="font-weight-preview" style="font-weight: var({token.variable});">Ag</span>
1036
- <div class="token-info">
1037
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1038
- <span class="token-value">{token.value}</span>
1039
- </div>
1040
- </div>
1041
- {/each}
1042
- </div>
1043
- </div>
1044
-
1045
- <div class="typography-group">
1046
- <h3 class="group-title">Line Heights</h3>
1047
- <div class="token-table">
1048
- {#each lineHeightTokens as token}
1049
- <div class="token-row">
1050
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1051
- <span class="token-value">{token.value}</span>
1052
- </div>
1053
- {/each}
1054
- </div>
1055
- </div>
1056
- </div>
1057
- </section>
1058
-
1059
- <!-- Shadows -->
1060
- <section class="section shadows-section" id="shadows" style="background: {shadowBg};">
1061
- <h2 class="section-title">Shadows</h2>
1062
-
1063
- <div class="shadows-layout">
1064
- <div class="shadows-main">
1065
- <div class="shadows-grid">
1066
- {#each shadowTokens.filter(t => scaleVariables.has(t.variable)) as token}
1067
- <div class="shadow-item" class:active={editingShadow === token.variable}>
1068
- <div class="shadow-box" style="box-shadow: {getShadowCss(token)};"></div>
1069
- <div class="token-info">
1070
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1071
- <span class="token-value">{token.value}</span>
1072
- </div>
1073
- <button class="shadow-edit-btn" on:click={() => editingShadow = editingShadow === token.variable ? null : token.variable}>
1074
- {editingShadow === token.variable ? 'Close' : 'Edit'}
1075
- </button>
1076
- </div>
1077
- {/each}
1078
- </div>
1079
-
1080
- <div class="shadows-grid">
1081
- {#each shadowTokens.filter(t => !scaleVariables.has(t.variable)) as token}
1082
- <div class="shadow-item" class:active={editingShadow === token.variable}>
1083
- <div class="shadow-box" style="box-shadow: {getShadowCss(token)};"></div>
1084
- <div class="token-info">
1085
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1086
- <span class="token-value">{token.value}</span>
1087
- </div>
1088
- <button class="shadow-edit-btn" on:click={() => editingShadow = editingShadow === token.variable ? null : token.variable}>
1089
- {editingShadow === token.variable ? 'Close' : 'Edit'}
1090
- </button>
1091
- </div>
1092
- {/each}
1093
- </div>
1094
-
1095
- <h3 class="group-title" style="margin-top: var(--space-16);">Text Shadows</h3>
1096
- <div class="text-shadow-demos">
1097
- {#each textShadowTokens as token}
1098
- <div class="text-shadow-item">
1099
- <span class="text-shadow-preview" style="text-shadow: var({token.variable});">Sample Text</span>
1100
- <div class="token-info">
1101
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1102
- <span class="token-value">{token.value}</span>
1103
- </div>
1104
- </div>
1105
- {/each}
1106
- </div>
1107
- </div><!-- /.shadows-main -->
1108
-
1109
- <!-- Shadow editor sidebar -->
1110
- <div class="global-shadow-editor">
1111
- {#if editingToken}
1112
- <div class="editor-header">
1113
- <h4 class="global-shadow-title">{editingToken.variable}</h4>
1114
- <button class="shadow-edit-btn" on:click={() => editingShadow = null}>Close</button>
1115
- </div>
1116
- {#if editingIsScale}
1117
- <button class="reset-btn" on:click={resetToGlobal}>Reset to Global</button>
1118
- {/if}
1119
- <div class="global-shadow-row">
1120
- <span class="shadow-slider-label" title="Direction the light source is coming from — controls which side the shadow falls on">Angle</span>
1121
- <svg class="angle-dial" viewBox="0 0 48 48" width="48" height="48"
1122
- on:pointerdown={(e) => handleDialDown(e, editingIdx)}
1123
- on:pointermove={(e) => handleDialMove(e, editingIdx)}
1124
- on:pointerup={handleDialUp}
1125
- >
1126
- <circle cx="24" cy="24" r="20" class="dial-ring" />
1127
- <line x1="24" y1="24"
1128
- x2={24 + 18 * Math.cos(editingToken.angle * Math.PI / 180)}
1129
- y2={24 - 18 * Math.sin(editingToken.angle * Math.PI / 180)}
1130
- class="dial-line" />
1131
- <circle
1132
- cx={24 + 18 * Math.cos(editingToken.angle * Math.PI / 180)}
1133
- cy={24 - 18 * Math.sin(editingToken.angle * Math.PI / 180)}
1134
- r="3" class="dial-handle" />
1135
- </svg>
1136
- <input class="shadow-slider-input" type="number" min="0" max="360"
1137
- value={editingToken.angle}
1138
- on:change={(e) => handleAngleChange(editingIdx, +e.currentTarget.value)} />
1139
- <span class="shadow-slider-unit">&deg;</span>
1140
- </div>
1141
- <div class="global-shadow-row">
1142
- <span class="shadow-slider-label" title="How far the shadow is cast from the element — simulates height off the surface">Dist</span>
1143
- <input type="range" min="0" max="60" value={editingToken.distance}
1144
- on:input={(e) => handleDistanceChange(editingIdx, +e.currentTarget.value)} />
1145
- <input class="shadow-slider-input" type="number" min="0" max="100"
1146
- value={editingToken.distance}
1147
- on:change={(e) => handleDistanceChange(editingIdx, +e.currentTarget.value)} />
1148
- <span class="shadow-slider-unit">px</span>
1149
- </div>
1150
- <div class="global-shadow-row">
1151
- <span class="shadow-slider-label" title="Grows or shrinks the shadow before blurring — positive makes it larger than the element, negative makes it smaller">Spread</span>
1152
- <input type="range" min="-50" max="50" value={editingToken.spread}
1153
- on:input={(e) => { editingToken.spread = +e.currentTarget.value; applyShadow(editingToken); shadowTokens = shadowTokens; }} />
1154
- <input class="shadow-slider-input" type="number" min="-50" max="50"
1155
- value={editingToken.spread}
1156
- on:change={(e) => { editingToken.spread = Math.max(-50, Math.min(50, +e.currentTarget.value)); applyShadow(editingToken, true); shadowTokens = shadowTokens; }} />
1157
- <span class="shadow-slider-unit">px</span>
1158
- </div>
1159
- <div class="global-shadow-row">
1160
- <span class="shadow-slider-label" title="How soft the shadow edge is — higher values make a wider, more diffused shadow">Blur</span>
1161
- <input type="range" min="0" max="100" value={editingToken.blur}
1162
- on:input={(e) => { editingToken.blur = +e.currentTarget.value; applyShadow(editingToken); shadowTokens = shadowTokens; }} />
1163
- <input class="shadow-slider-input" type="number" min="0" max="100"
1164
- value={editingToken.blur}
1165
- on:change={(e) => { editingToken.blur = Math.max(0, +e.currentTarget.value); applyShadow(editingToken, true); shadowTokens = shadowTokens; }} />
1166
- <span class="shadow-slider-unit">px</span>
1167
- </div>
1168
- <div class="global-shadow-row">
1169
- <span class="shadow-slider-label" title="How visible the shadow is — 0% is invisible, 100% is fully opaque">Op.</span>
1170
- <input type="range" min="0" max="100" value={Math.round(editingToken.opacity * 100)}
1171
- on:input={(e) => { editingToken.opacity = +e.currentTarget.value / 100; applyShadow(editingToken); shadowTokens = shadowTokens; }} />
1172
- <input class="shadow-slider-input" type="number" min="0" max="100"
1173
- value={Math.round(editingToken.opacity * 100)}
1174
- on:change={(e) => { editingToken.opacity = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyShadow(editingToken, true); shadowTokens = shadowTokens; }} />
1175
- <span class="shadow-slider-unit">%</span>
1176
- </div>
1177
- <div class="global-color-group">
1178
- <div class="global-color-swatch" style="background: hsl({editingToken.hue}, {editingToken.saturation}%, {editingToken.lightness}%);"></div>
1179
- <div class="global-color-sliders">
1180
- <div class="global-shadow-row">
1181
- <span class="shadow-slider-label" title="Hue — the base color of the shadow (0°=red, 120°=green, 240°=blue)">H</span>
1182
- <div class="slider-track" style="background: {shadowHueGrad()}">
1183
- <input type="range" min="0" max="360" value={editingToken.hue}
1184
- on:input={(e) => { editingToken.hue = +e.currentTarget.value; applyShadow(editingToken); shadowTokens = shadowTokens; }} />
1185
- </div>
1186
- <input class="shadow-slider-input" type="number" min="0" max="360"
1187
- value={editingToken.hue}
1188
- on:change={(e) => { editingToken.hue = Math.min(360, Math.max(0, +e.currentTarget.value)); applyShadow(editingToken, true); shadowTokens = shadowTokens; }} />
1189
- <span class="shadow-slider-unit">&deg;</span>
1190
- </div>
1191
- <div class="global-shadow-row">
1192
- <span class="shadow-slider-label" title="Saturation — 0% is gray, 100% is full color intensity">S</span>
1193
- <div class="slider-track" style="background: linear-gradient(to right, hsl({editingToken.hue},0%,{editingToken.lightness}%), hsl({editingToken.hue},100%,{editingToken.lightness}%))">
1194
- <input type="range" min="0" max="100" value={editingToken.saturation}
1195
- on:input={(e) => { editingToken.saturation = +e.currentTarget.value; applyShadow(editingToken); shadowTokens = shadowTokens; }} />
1196
- </div>
1197
- <input class="shadow-slider-input" type="number" min="0" max="100"
1198
- value={editingToken.saturation}
1199
- on:change={(e) => { editingToken.saturation = Math.min(100, Math.max(0, +e.currentTarget.value)); applyShadow(editingToken, true); shadowTokens = shadowTokens; }} />
1200
- <span class="shadow-slider-unit">%</span>
1201
- </div>
1202
- <div class="global-shadow-row">
1203
- <span class="shadow-slider-label" title="Lightness — 0% is black, 100% is white">L</span>
1204
- <div class="slider-track" style="background: linear-gradient(to right, hsl({editingToken.hue},{editingToken.saturation}%,0%), hsl({editingToken.hue},{editingToken.saturation}%,50%), hsl({editingToken.hue},{editingToken.saturation}%,100%))">
1205
- <input type="range" min="0" max="100" value={editingToken.lightness}
1206
- on:input={(e) => { editingToken.lightness = +e.currentTarget.value; applyShadow(editingToken); shadowTokens = shadowTokens; }} />
1207
- </div>
1208
- <input class="shadow-slider-input" type="number" min="0" max="100"
1209
- value={editingToken.lightness}
1210
- on:change={(e) => { editingToken.lightness = Math.min(100, Math.max(0, +e.currentTarget.value)); applyShadow(editingToken, true); shadowTokens = shadowTokens; }} />
1211
- <span class="shadow-slider-unit">%</span>
1212
- </div>
1213
- </div>
1214
- </div>
1215
- <div class="shadow-css-output">
1216
- <code>{getShadowCss(editingToken)}</code>
1217
- <button class="shadow-copy-btn" on:click={() => copyVariable(getShadowCss(editingToken))}>
1218
- {copiedVar === getShadowCss(editingToken) ? 'Copied!' : 'Copy CSS'}
1219
- </button>
1220
- </div>
1221
- {:else}
1222
- <h4 class="global-shadow-title">Global Light</h4>
1223
- <div class="global-shadow-row">
1224
- <span class="shadow-slider-label" title="Direction the light source is coming from — controls which side the shadow falls on">Angle</span>
1225
- <svg class="angle-dial" viewBox="0 0 48 48" width="48" height="48"
1226
- on:pointerdown={handleGlobalDialDown}
1227
- on:pointermove={handleGlobalDialMove}
1228
- on:pointerup={handleGlobalDialUp}
1229
- >
1230
- <circle cx="24" cy="24" r="20" class="dial-ring" />
1231
- <line x1="24" y1="24"
1232
- x2={24 + 18 * Math.cos(globalAngle * Math.PI / 180)}
1233
- y2={24 - 18 * Math.sin(globalAngle * Math.PI / 180)}
1234
- class="dial-line" />
1235
- <circle
1236
- cx={24 + 18 * Math.cos(globalAngle * Math.PI / 180)}
1237
- cy={24 - 18 * Math.sin(globalAngle * Math.PI / 180)}
1238
- r="3" class="dial-handle" />
1239
- </svg>
1240
- <input class="shadow-slider-input" type="number" min="0" max="360"
1241
- value={globalAngle}
1242
- on:change={(e) => { globalAngle = ((+e.currentTarget.value % 360) + 360) % 360; applyGlobalAngle(); }} />
1243
- <span class="shadow-slider-unit">&deg;</span>
1244
- </div>
1245
- <div class="global-shadow-row">
1246
- <span class="shadow-slider-label" title="How far the shadow is cast — simulates height off the surface">Dist Min</span>
1247
- <input type="range" min="0" max="60" value={globalDistanceMin}
1248
- on:input={(e) => { globalDistanceMin = +e.currentTarget.value; applyGlobalDistance(); }} />
1249
- <input class="shadow-slider-input" type="number" min="0" max="100"
1250
- value={globalDistanceMin}
1251
- on:change={(e) => { globalDistanceMin = Math.max(0, +e.currentTarget.value); applyGlobalDistance(); }} />
1252
- <span class="shadow-slider-unit">px</span>
1253
- </div>
1254
- <div class="global-shadow-row">
1255
- <span class="shadow-slider-label" title="How far the shadow is cast — simulates height off the surface">Dist Max</span>
1256
- <input type="range" min="0" max="60" value={globalDistanceMax}
1257
- on:input={(e) => { globalDistanceMax = +e.currentTarget.value; applyGlobalDistance(); }} />
1258
- <input class="shadow-slider-input" type="number" min="0" max="100"
1259
- value={globalDistanceMax}
1260
- on:change={(e) => { globalDistanceMax = Math.max(0, +e.currentTarget.value); applyGlobalDistance(); }} />
1261
- <span class="shadow-slider-unit">px</span>
1262
- </div>
1263
- {#if sizeLocked}
1264
- <div class="global-shadow-row">
1265
- <span class="shadow-slider-label" title="Grows or shrinks the shadow before blurring — positive makes it larger than the element, negative makes it smaller">Spread</span>
1266
- <input type="range" min="-50" max="50" value={globalSizeMin}
1267
- on:input={(e) => { globalSizeMin = globalSizeMax = +e.currentTarget.value; applyGlobalSize(); }} />
1268
- <input class="shadow-slider-input" type="number" min="-50" max="50"
1269
- value={globalSizeMin}
1270
- on:change={(e) => { globalSizeMin = globalSizeMax = Math.max(-50, Math.min(50, +e.currentTarget.value)); applyGlobalSize(); }} />
1271
- <span class="shadow-slider-unit">px</span>
1272
- <button class="lock-btn" title="Unlock min/max" on:click={() => { sizeLocked = false; }}>
1273
- <svg viewBox="0 0 16 16" width="14" height="14"><path d="M4 7V5a4 4 0 118 0v2h1a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1V8a1 1 0 011-1h1zm2 0h4V5a2 2 0 10-4 0v2z" fill="currentColor"/></svg>
1274
- </button>
1275
- </div>
1276
- {:else}
1277
- <div class="global-shadow-row">
1278
- <span class="shadow-slider-label" title="Grows or shrinks the shadow before blurring — positive makes it larger than the element, negative makes it smaller">Spread Min</span>
1279
- <input type="range" min="-50" max="50" value={globalSizeMin}
1280
- on:input={(e) => { globalSizeMin = +e.currentTarget.value; applyGlobalSize(); }} />
1281
- <input class="shadow-slider-input" type="number" min="-50" max="50"
1282
- value={globalSizeMin}
1283
- on:change={(e) => { globalSizeMin = Math.max(-50, Math.min(50, +e.currentTarget.value)); applyGlobalSize(); }} />
1284
- <span class="shadow-slider-unit">px</span>
1285
- <button class="lock-btn unlocked" title="Lock to single value" on:click={() => { sizeLocked = true; globalSizeMax = globalSizeMin; applyGlobalSize(); }}>
1286
- <svg viewBox="0 0 16 16" width="14" height="14"><path d="M10 7V5a2 2 0 10-4 0v.5H4V5a4 4 0 118 0v2h1a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1V8a1 1 0 011-1h7z" fill="currentColor"/></svg>
1287
- </button>
1288
- </div>
1289
- <div class="global-shadow-row">
1290
- <span class="shadow-slider-label" title="Grows or shrinks the shadow before blurring — positive makes it larger than the element, negative makes it smaller">Spread Max</span>
1291
- <input type="range" min="-50" max="50" value={globalSizeMax}
1292
- on:input={(e) => { globalSizeMax = +e.currentTarget.value; applyGlobalSize(); }} />
1293
- <input class="shadow-slider-input" type="number" min="-50" max="50"
1294
- value={globalSizeMax}
1295
- on:change={(e) => { globalSizeMax = Math.max(-50, Math.min(50, +e.currentTarget.value)); applyGlobalSize(); }} />
1296
- <span class="shadow-slider-unit">px</span>
1297
- </div>
1298
- {/if}
1299
- {#if blurLocked}
1300
- <div class="global-shadow-row">
1301
- <span class="shadow-slider-label" title="How soft the shadow edge is — higher values make a wider, more diffused shadow">Blur</span>
1302
- <input type="range" min="0" max="100" value={globalBlurMin}
1303
- on:input={(e) => { globalBlurMin = globalBlurMax = +e.currentTarget.value; applyGlobalBlur(); }} />
1304
- <input class="shadow-slider-input" type="number" min="0" max="100"
1305
- value={globalBlurMin}
1306
- on:change={(e) => { globalBlurMin = globalBlurMax = Math.max(0, +e.currentTarget.value); applyGlobalBlur(); }} />
1307
- <span class="shadow-slider-unit">px</span>
1308
- <button class="lock-btn" title="Unlock min/max" on:click={() => { blurLocked = false; }}>
1309
- <svg viewBox="0 0 16 16" width="14" height="14"><path d="M4 7V5a4 4 0 118 0v2h1a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1V8a1 1 0 011-1h1zm2 0h4V5a2 2 0 10-4 0v2z" fill="currentColor"/></svg>
1310
- </button>
1311
- </div>
1312
- {:else}
1313
- <div class="global-shadow-row">
1314
- <span class="shadow-slider-label" title="How soft the shadow edge is — higher values make a wider, more diffused shadow">Blur Min</span>
1315
- <input type="range" min="0" max="100" value={globalBlurMin}
1316
- on:input={(e) => { globalBlurMin = +e.currentTarget.value; applyGlobalBlur(); }} />
1317
- <input class="shadow-slider-input" type="number" min="0" max="100"
1318
- value={globalBlurMin}
1319
- on:change={(e) => { globalBlurMin = Math.max(0, +e.currentTarget.value); applyGlobalBlur(); }} />
1320
- <span class="shadow-slider-unit">px</span>
1321
- <button class="lock-btn unlocked" title="Lock to single value" on:click={() => { blurLocked = true; globalBlurMax = globalBlurMin; applyGlobalBlur(); }}>
1322
- <svg viewBox="0 0 16 16" width="14" height="14"><path d="M10 7V5a2 2 0 10-4 0v.5H4V5a4 4 0 118 0v2h1a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1V8a1 1 0 011-1h7z" fill="currentColor"/></svg>
1323
- </button>
1324
- </div>
1325
- <div class="global-shadow-row">
1326
- <span class="shadow-slider-label" title="How soft the shadow edge is — higher values make a wider, more diffused shadow">Blur Max</span>
1327
- <input type="range" min="0" max="100" value={globalBlurMax}
1328
- on:input={(e) => { globalBlurMax = +e.currentTarget.value; applyGlobalBlur(); }} />
1329
- <input class="shadow-slider-input" type="number" min="0" max="100"
1330
- value={globalBlurMax}
1331
- on:change={(e) => { globalBlurMax = Math.max(0, +e.currentTarget.value); applyGlobalBlur(); }} />
1332
- <span class="shadow-slider-unit">px</span>
1333
- </div>
1334
- {/if}
1335
- {#if opacityLocked}
1336
- <div class="global-shadow-row">
1337
- <span class="shadow-slider-label" title="How visible the shadow is — 0% is invisible, 100% is fully opaque">Op.</span>
1338
- <input type="range" min="0" max="100" value={Math.round(globalOpacityMin * 100)}
1339
- on:input={(e) => { globalOpacityMin = globalOpacityMax = +e.currentTarget.value / 100; applyGlobalOpacity(); }} />
1340
- <input class="shadow-slider-input" type="number" min="0" max="100"
1341
- value={Math.round(globalOpacityMin * 100)}
1342
- on:change={(e) => { globalOpacityMin = globalOpacityMax = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyGlobalOpacity(); }} />
1343
- <span class="shadow-slider-unit">%</span>
1344
- <button class="lock-btn" title="Unlock min/max" on:click={() => { opacityLocked = false; }}>
1345
- <svg viewBox="0 0 16 16" width="14" height="14"><path d="M4 7V5a4 4 0 118 0v2h1a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1V8a1 1 0 011-1h1zm2 0h4V5a2 2 0 10-4 0v2z" fill="currentColor"/></svg>
1346
- </button>
1347
- </div>
1348
- {:else}
1349
- <div class="global-shadow-row">
1350
- <span class="shadow-slider-label" title="How visible the shadow is — 0% is invisible, 100% is fully opaque">Op. Min</span>
1351
- <input type="range" min="0" max="100" value={Math.round(globalOpacityMin * 100)}
1352
- on:input={(e) => { globalOpacityMin = +e.currentTarget.value / 100; applyGlobalOpacity(); }} />
1353
- <input class="shadow-slider-input" type="number" min="0" max="100"
1354
- value={Math.round(globalOpacityMin * 100)}
1355
- on:change={(e) => { globalOpacityMin = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyGlobalOpacity(); }} />
1356
- <span class="shadow-slider-unit">%</span>
1357
- <button class="lock-btn unlocked" title="Lock to single value" on:click={() => { opacityLocked = true; globalOpacityMax = globalOpacityMin; applyGlobalOpacity(); }}>
1358
- <svg viewBox="0 0 16 16" width="14" height="14"><path d="M10 7V5a2 2 0 10-4 0v.5H4V5a4 4 0 118 0v2h1a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1V8a1 1 0 011-1h7z" fill="currentColor"/></svg>
1359
- </button>
1360
- </div>
1361
- <div class="global-shadow-row">
1362
- <span class="shadow-slider-label" title="How visible the shadow is — 0% is invisible, 100% is fully opaque">Op. Max</span>
1363
- <input type="range" min="0" max="100" value={Math.round(globalOpacityMax * 100)}
1364
- on:input={(e) => { globalOpacityMax = +e.currentTarget.value / 100; applyGlobalOpacity(); }} />
1365
- <input class="shadow-slider-input" type="number" min="0" max="100"
1366
- value={Math.round(globalOpacityMax * 100)}
1367
- on:change={(e) => { globalOpacityMax = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyGlobalOpacity(); }} />
1368
- <span class="shadow-slider-unit">%</span>
1369
- </div>
1370
- {/if}
1371
- <div class="global-color-group">
1372
- <div class="global-color-swatch" style="background: hsl({globalHue}, {globalSaturation}%, {globalLightness}%);"></div>
1373
- <div class="global-color-sliders">
1374
- <div class="global-shadow-row">
1375
- <span class="shadow-slider-label" title="Hue — the base color of the shadow (0°=red, 120°=green, 240°=blue)">H</span>
1376
- <div class="slider-track" style="background: {shadowHueGrad()}">
1377
- <input type="range" min="0" max="360" value={globalHue}
1378
- on:input={(e) => { globalHue = +e.currentTarget.value; applyGlobalColor(); }} />
1379
- </div>
1380
- <input class="shadow-slider-input" type="number" min="0" max="360"
1381
- value={globalHue}
1382
- on:change={(e) => { globalHue = Math.min(360, Math.max(0, +e.currentTarget.value)); applyGlobalColor(); }} />
1383
- <span class="shadow-slider-unit">&deg;</span>
1384
- </div>
1385
- <div class="global-shadow-row">
1386
- <span class="shadow-slider-label" title="Saturation — 0% is gray, 100% is full color intensity">S</span>
1387
- <div class="slider-track" style="background: {shadowSatGrad()}">
1388
- <input type="range" min="0" max="100" value={globalSaturation}
1389
- on:input={(e) => { globalSaturation = +e.currentTarget.value; applyGlobalColor(); }} />
1390
- </div>
1391
- <input class="shadow-slider-input" type="number" min="0" max="100"
1392
- value={globalSaturation}
1393
- on:change={(e) => { globalSaturation = Math.min(100, Math.max(0, +e.currentTarget.value)); applyGlobalColor(); }} />
1394
- <span class="shadow-slider-unit">%</span>
1395
- </div>
1396
- <div class="global-shadow-row">
1397
- <span class="shadow-slider-label" title="Lightness — 0% is black, 100% is white">L</span>
1398
- <div class="slider-track" style="background: {shadowLightGrad()}">
1399
- <input type="range" min="0" max="100" value={globalLightness}
1400
- on:input={(e) => { globalLightness = +e.currentTarget.value; applyGlobalColor(); }} />
1401
- </div>
1402
- <input class="shadow-slider-input" type="number" min="0" max="100"
1403
- value={globalLightness}
1404
- on:change={(e) => { globalLightness = Math.min(100, Math.max(0, +e.currentTarget.value)); applyGlobalColor(); }} />
1405
- <span class="shadow-slider-unit">%</span>
1406
- </div>
1407
- </div>
1408
- </div>
1409
- <button class="bg-picker-btn" on:click={toggleBgPicker}>BG</button>
1410
- {#if bgPickerOpen}
1411
- <div class="bg-picker-menu">
1412
- {#each bgColorGroups as group}
1413
- <button class="bg-group-header" on:click={() => expandedGroup = expandedGroup === group.label ? null : group.label}>
1414
- <span>{group.label}</span>
1415
- <span class="bg-group-arrow">{expandedGroup === group.label ? '\u25B4' : '\u25BE'}</span>
1416
- </button>
1417
- {#if expandedGroup === group.label}
1418
- <div class="bg-group-colors">
1419
- {#each group.colors as color}
1420
- <button class="bg-color-option" on:click={() => pickBg(color.value)}>
1421
- <span class="bg-color-swatch" style="background: {color.value};"></span>
1422
- <span>{color.label}</span>
1423
- </button>
1424
- {/each}
1425
- </div>
1426
- {/if}
1427
- {/each}
1428
- </div>
1429
- {/if}
1430
- {/if}
1431
- </div>
1432
- </div><!-- /.shadows-layout -->
1433
- </section>
1434
-
1435
- <!-- Overlays -->
1436
- <section class="section" id="overlays">
1437
- <h2 class="section-title">Overlays</h2>
1438
-
1439
- <h3 class="group-title">Dark Overlays</h3>
1440
- <div class="overlays-grid">
1441
- {#each overlayTokens as token, i}
1442
- <div class="overlay-item">
1443
- <div class="overlay-swatch-wrap">
1444
- <div class="overlay-swatch" style="background: {getOverlayCss(token)};"></div>
1445
- </div>
1446
- <div class="token-info">
1447
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1448
- <span class="token-value">{token.label} — {Math.round(token.opacity * 100)}%</span>
1449
- </div>
1450
- <button class="shadow-edit-btn" on:click={() => editingOverlay = editingOverlay === token.variable ? null : token.variable}>
1451
- {editingOverlay === token.variable ? 'Close' : 'Edit'}
1452
- </button>
1453
- {#if editingOverlay === token.variable}
1454
- <div class="shadow-editor">
1455
- <div class="shadow-slider-row">
1456
- <span class="shadow-slider-label">Opacity</span>
1457
- <input type="range" min="0" max="100" value={Math.round(token.opacity * 100)}
1458
- on:input={(e) => { token.opacity = +e.currentTarget.value / 100; applyOverlay(token); overlayTokens = overlayTokens; }} />
1459
- <input class="shadow-slider-input" type="number" min="0" max="100"
1460
- value={Math.round(token.opacity * 100)}
1461
- on:change={(e) => { token.opacity = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyOverlay(token); overlayTokens = overlayTokens; }} />
1462
- <span class="shadow-slider-unit">%</span>
1463
- </div>
1464
- <div class="shadow-css-output">
1465
- <code>{getOverlayCss(token)}</code>
1466
- <button class="shadow-copy-btn" on:click={() => copyVariable(getOverlayCss(token))}>
1467
- {copiedVar === getOverlayCss(token) ? 'Copied!' : 'Copy CSS'}
1468
- </button>
1469
- </div>
1470
- </div>
1471
- {/if}
1472
- </div>
1473
- {/each}
1474
- </div>
1475
-
1476
- <!-- Global overlay editor -->
1477
- <div class="overlay-global-editor">
1478
- <h4 class="global-shadow-title">Global Overlay Controls</h4>
1479
- <div class="overlay-global-columns">
1480
- <div class="overlay-global-col">
1481
- <div class="global-color-group">
1482
- <div class="global-color-swatch" style="background: hsl({overlayHue}, {overlaySaturation}%, {overlayLightness}%);"></div>
1483
- <div class="global-color-sliders">
1484
- <div class="global-shadow-row">
1485
- <span class="shadow-slider-label">H</span>
1486
- <div class="slider-track" style="background: {overlayHueGrad()}">
1487
- <input type="range" min="0" max="360" value={overlayHue}
1488
- on:input={(e) => { overlayHue = +e.currentTarget.value; applyGlobalOverlayColor(); }} />
1489
- </div>
1490
- <input class="shadow-slider-input" type="number" min="0" max="360"
1491
- value={overlayHue}
1492
- on:change={(e) => { overlayHue = Math.min(360, Math.max(0, +e.currentTarget.value)); applyGlobalOverlayColor(); }} />
1493
- <span class="shadow-slider-unit">&deg;</span>
1494
- </div>
1495
- <div class="global-shadow-row">
1496
- <span class="shadow-slider-label">S</span>
1497
- <div class="slider-track" style="background: {overlaySatGrad()}">
1498
- <input type="range" min="0" max="100" value={overlaySaturation}
1499
- on:input={(e) => { overlaySaturation = +e.currentTarget.value; applyGlobalOverlayColor(); }} />
1500
- </div>
1501
- <input class="shadow-slider-input" type="number" min="0" max="100"
1502
- value={overlaySaturation}
1503
- on:change={(e) => { overlaySaturation = Math.min(100, Math.max(0, +e.currentTarget.value)); applyGlobalOverlayColor(); }} />
1504
- <span class="shadow-slider-unit">%</span>
1505
- </div>
1506
- <div class="global-shadow-row">
1507
- <span class="shadow-slider-label">L</span>
1508
- <div class="slider-track" style="background: {overlayLightGrad()}">
1509
- <input type="range" min="0" max="100" value={overlayLightness}
1510
- on:input={(e) => { overlayLightness = +e.currentTarget.value; applyGlobalOverlayColor(); }} />
1511
- </div>
1512
- <input class="shadow-slider-input" type="number" min="0" max="100"
1513
- value={overlayLightness}
1514
- on:change={(e) => { overlayLightness = Math.min(100, Math.max(0, +e.currentTarget.value)); applyGlobalOverlayColor(); }} />
1515
- <span class="shadow-slider-unit">%</span>
1516
- </div>
1517
- </div>
1518
- </div>
1519
- </div>
1520
- <div class="overlay-global-col">
1521
- <div class="global-shadow-row">
1522
- <span class="shadow-slider-label">Op. Min</span>
1523
- <input type="range" min="0" max="100" value={Math.round(overlayOpacityMin * 100)}
1524
- on:input={(e) => { overlayOpacityMin = +e.currentTarget.value / 100; applyGlobalOverlayOpacity(); }} />
1525
- <input class="shadow-slider-input" type="number" min="0" max="100"
1526
- value={Math.round(overlayOpacityMin * 100)}
1527
- on:change={(e) => { overlayOpacityMin = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyGlobalOverlayOpacity(); }} />
1528
- <span class="shadow-slider-unit">%</span>
1529
- </div>
1530
- <div class="global-shadow-row">
1531
- <span class="shadow-slider-label">Op. Max</span>
1532
- <input type="range" min="0" max="100" value={Math.round(overlayOpacityMax * 100)}
1533
- on:input={(e) => { overlayOpacityMax = +e.currentTarget.value / 100; applyGlobalOverlayOpacity(); }} />
1534
- <input class="shadow-slider-input" type="number" min="0" max="100"
1535
- value={Math.round(overlayOpacityMax * 100)}
1536
- on:change={(e) => { overlayOpacityMax = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyGlobalOverlayOpacity(); }} />
1537
- <span class="shadow-slider-unit">%</span>
1538
- </div>
1539
- </div>
1540
- </div>
1541
- </div>
1542
-
1543
- <h3 class="group-title" style="margin-top: var(--space-16);">Hover Overlays</h3>
1544
- <div class="overlays-grid">
1545
- {#each hoverTokens as token, i}
1546
- <div class="overlay-item">
1547
- <div class="overlay-swatch-wrap overlay-swatch-wrap--dark">
1548
- <div class="overlay-swatch" style="background: {getOverlayCss(token)};"></div>
1549
- </div>
1550
- <div class="token-info">
1551
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1552
- <span class="token-value">{token.label} — {Math.round(token.opacity * 100)}%</span>
1553
- </div>
1554
- <button class="shadow-edit-btn" on:click={() => editingOverlay = editingOverlay === token.variable ? null : token.variable}>
1555
- {editingOverlay === token.variable ? 'Close' : 'Edit'}
1556
- </button>
1557
- {#if editingOverlay === token.variable}
1558
- <div class="shadow-editor">
1559
- <div class="shadow-slider-row">
1560
- <span class="shadow-slider-label">Opacity</span>
1561
- <input type="range" min="0" max="100" value={Math.round(token.opacity * 100)}
1562
- on:input={(e) => { token.opacity = +e.currentTarget.value / 100; applyOverlay(token); hoverTokens = hoverTokens; }} />
1563
- <input class="shadow-slider-input" type="number" min="0" max="100"
1564
- value={Math.round(token.opacity * 100)}
1565
- on:change={(e) => { token.opacity = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyOverlay(token); hoverTokens = hoverTokens; }} />
1566
- <span class="shadow-slider-unit">%</span>
1567
- </div>
1568
- <div class="shadow-css-output">
1569
- <code>{getOverlayCss(token)}</code>
1570
- <button class="shadow-copy-btn" on:click={() => copyVariable(getOverlayCss(token))}>
1571
- {copiedVar === getOverlayCss(token) ? 'Copied!' : 'Copy CSS'}
1572
- </button>
1573
- </div>
1574
- </div>
1575
- {/if}
1576
- </div>
1577
- {/each}
1578
- </div>
1579
-
1580
- <!-- Global hover editor -->
1581
- <div class="overlay-global-editor">
1582
- <h4 class="global-shadow-title">Global Hover Controls</h4>
1583
- <div class="overlay-global-columns">
1584
- <div class="overlay-global-col">
1585
- <div class="global-color-group">
1586
- <div class="global-color-swatch" style="background: hsl({hoverHue}, {hoverSaturation}%, {hoverLightness}%);"></div>
1587
- <div class="global-color-sliders">
1588
- <div class="global-shadow-row">
1589
- <span class="shadow-slider-label">H</span>
1590
- <div class="slider-track" style="background: {hoverHueGrad()}">
1591
- <input type="range" min="0" max="360" value={hoverHue}
1592
- on:input={(e) => { hoverHue = +e.currentTarget.value; applyGlobalHoverColor(); }} />
1593
- </div>
1594
- <input class="shadow-slider-input" type="number" min="0" max="360"
1595
- value={hoverHue}
1596
- on:change={(e) => { hoverHue = Math.min(360, Math.max(0, +e.currentTarget.value)); applyGlobalHoverColor(); }} />
1597
- <span class="shadow-slider-unit">&deg;</span>
1598
- </div>
1599
- <div class="global-shadow-row">
1600
- <span class="shadow-slider-label">S</span>
1601
- <div class="slider-track" style="background: {hoverSatGrad()}">
1602
- <input type="range" min="0" max="100" value={hoverSaturation}
1603
- on:input={(e) => { hoverSaturation = +e.currentTarget.value; applyGlobalHoverColor(); }} />
1604
- </div>
1605
- <input class="shadow-slider-input" type="number" min="0" max="100"
1606
- value={hoverSaturation}
1607
- on:change={(e) => { hoverSaturation = Math.min(100, Math.max(0, +e.currentTarget.value)); applyGlobalHoverColor(); }} />
1608
- <span class="shadow-slider-unit">%</span>
1609
- </div>
1610
- <div class="global-shadow-row">
1611
- <span class="shadow-slider-label">L</span>
1612
- <div class="slider-track" style="background: {hoverLightGrad()}">
1613
- <input type="range" min="0" max="100" value={hoverLightness}
1614
- on:input={(e) => { hoverLightness = +e.currentTarget.value; applyGlobalHoverColor(); }} />
1615
- </div>
1616
- <input class="shadow-slider-input" type="number" min="0" max="100"
1617
- value={hoverLightness}
1618
- on:change={(e) => { hoverLightness = Math.min(100, Math.max(0, +e.currentTarget.value)); applyGlobalHoverColor(); }} />
1619
- <span class="shadow-slider-unit">%</span>
1620
- </div>
1621
- </div>
1622
- </div>
1623
- </div>
1624
- <div class="overlay-global-col">
1625
- <div class="global-shadow-row">
1626
- <span class="shadow-slider-label">Op. Min</span>
1627
- <input type="range" min="0" max="100" value={Math.round(hoverOpacityMin * 100)}
1628
- on:input={(e) => { hoverOpacityMin = +e.currentTarget.value / 100; applyGlobalHoverOpacity(); }} />
1629
- <input class="shadow-slider-input" type="number" min="0" max="100"
1630
- value={Math.round(hoverOpacityMin * 100)}
1631
- on:change={(e) => { hoverOpacityMin = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyGlobalHoverOpacity(); }} />
1632
- <span class="shadow-slider-unit">%</span>
1633
- </div>
1634
- <div class="global-shadow-row">
1635
- <span class="shadow-slider-label">Op. Max</span>
1636
- <input type="range" min="0" max="100" value={Math.round(hoverOpacityMax * 100)}
1637
- on:input={(e) => { hoverOpacityMax = +e.currentTarget.value / 100; applyGlobalHoverOpacity(); }} />
1638
- <input class="shadow-slider-input" type="number" min="0" max="100"
1639
- value={Math.round(hoverOpacityMax * 100)}
1640
- on:change={(e) => { hoverOpacityMax = Math.min(100, Math.max(0, +e.currentTarget.value)) / 100; applyGlobalHoverOpacity(); }} />
1641
- <span class="shadow-slider-unit">%</span>
1642
- </div>
1643
- </div>
1644
- </div>
1645
- </div>
1646
- </section>
1647
-
1648
- <!-- Gradients -->
1649
- <section class="section" id="gradients">
1650
- <h2 class="section-title">Gradients</h2>
1651
- <div class="gradients-grid">
1652
- {#each gradientTokens as token}
1653
- <div class="gradient-item">
1654
- <div class="gradient-box" style="background: var({token.variable});"></div>
1655
- <div class="token-info">
1656
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1657
- <span class="token-value">{token.value}</span>
1658
- </div>
1659
- </div>
1660
- {/each}
1661
- </div>
1662
- </section>
1663
-
1664
- <!-- Utility Tokens -->
1665
- <section class="section" id="utility-tokens">
1666
- <h2 class="section-title">Utility Tokens</h2>
1667
- <div class="utility-columns">
1668
- <div class="utility-group">
1669
- <h3 class="group-title">Transitions</h3>
1670
- <div class="token-table">
1671
- {#each transitionTokens as token}
1672
- <div class="token-row">
1673
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1674
- <span class="token-value">{token.value}</span>
1675
- </div>
1676
- {/each}
1677
- </div>
1678
- </div>
1679
-
1680
- <div class="utility-group">
1681
- <h3 class="group-title">Z-Index Layers</h3>
1682
- <div class="token-table">
1683
- {#each zIndexTokens as token}
1684
- <div class="token-row">
1685
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1686
- <span class="token-value">{token.value}</span>
1687
- </div>
1688
- {/each}
1689
- </div>
1690
- </div>
1691
-
1692
- <div class="utility-group">
1693
- <h3 class="group-title">Opacity</h3>
1694
- <div class="token-table">
1695
- {#each opacityTokens as token}
1696
- <div class="token-row">
1697
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copyVariable(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
1698
- <span class="token-value">{token.value}</span>
1699
- </div>
1700
- {/each}
1701
- </div>
1702
- </div>
1703
- </div>
1704
- </section>
1705
- </div>
1706
-
1707
- <style>
1708
- .variables-container {
1709
- display: flex;
1710
- flex-direction: column;
1711
- gap: var(--space-32);
1712
- }
1713
-
1714
- .section {
1715
- display: flex;
1716
- flex-direction: column;
1717
- gap: var(--space-16);
1718
- }
1719
-
1720
- .section-title {
1721
- font-size: var(--font-lg);
1722
- font-weight: var(--font-weight-semibold);
1723
- color: var(--ui-text-primary);
1724
- margin: 0;
1725
- padding-bottom: var(--space-8);
1726
- border-bottom: 1px solid var(--ui-border-subtle);
1727
- }
1728
-
1729
- .group-title {
1730
- font-size: var(--font-md);
1731
- font-weight: var(--font-weight-semibold);
1732
- color: var(--ui-text-secondary);
1733
- margin: 0;
1734
- }
1735
-
1736
- .token-info {
1737
- display: flex;
1738
- flex-direction: column;
1739
- gap: var(--space-2);
1740
- }
1741
-
1742
- .token-variable {
1743
- font-size: var(--font-md);
1744
- color: var(--ui-text-tertiary);
1745
- font-family: var(--ui-font-mono);
1746
- }
1747
-
1748
- .token-variable.copyable {
1749
- all: unset;
1750
- font-size: var(--font-md);
1751
- color: var(--ui-text-tertiary);
1752
- font-family: var(--ui-font-mono);
1753
- cursor: pointer;
1754
- transition: color var(--transition-fast);
1755
- }
1756
-
1757
- .token-variable.copyable:hover {
1758
- color: var(--ui-text-accent);
1759
- }
1760
-
1761
- .token-variable.copyable.copied {
1762
- color: var(--ui-text-success);
1763
- }
1764
-
1765
- .token-value {
1766
- font-size: var(--font-md);
1767
- color: var(--ui-text-muted);
1768
- }
1769
-
1770
- /* Columns */
1771
- .columns-intro {
1772
- font-size: var(--font-sm);
1773
- color: var(--ui-text-muted);
1774
- margin: 0;
1775
- line-height: var(--line-height-relaxed);
1776
- }
1777
-
1778
- .columns-intro i {
1779
- margin-inline: 2px;
1780
- color: var(--ui-text-tertiary);
1781
- }
1782
-
1783
- .columns-controls {
1784
- display: flex;
1785
- flex-direction: column;
1786
- gap: var(--space-8);
1787
- padding: var(--space-12) var(--space-16);
1788
- background: var(--ui-surface-low);
1789
- border: 1px solid var(--ui-border-faint);
1790
- border-radius: var(--radius-md);
1791
- }
1792
-
1793
- .columns-controls .shadow-slider-label {
1794
- width: 5rem;
1795
- text-align: left;
1796
- }
1797
-
1798
- .columns-input-wide {
1799
- width: 3.5rem;
1800
- }
1801
-
1802
- .columns-controls-footer {
1803
- display: flex;
1804
- justify-content: flex-end;
1805
- padding-top: var(--space-8);
1806
- margin-top: var(--space-4);
1807
- border-top: 1px solid var(--ui-border-faint);
1808
- }
1809
-
1810
- .columns-reset {
1811
- display: inline-flex;
1812
- align-items: center;
1813
- gap: var(--space-6);
1814
- padding: var(--space-4) var(--space-10);
1815
- background: transparent;
1816
- border: 1px solid var(--ui-border-subtle);
1817
- border-radius: var(--radius-md);
1818
- color: var(--ui-text-tertiary);
1819
- font-family: inherit;
1820
- font-size: var(--font-xs);
1821
- cursor: pointer;
1822
- transition: color var(--transition-fast), border-color var(--transition-fast);
1823
- }
1824
-
1825
- .columns-reset:hover {
1826
- color: var(--ui-text-primary);
1827
- border-color: var(--ui-border-medium);
1828
- }
1829
-
1830
- .columns-reset i {
1831
- font-size: 10px;
1832
- }
1833
-
1834
- .columns-preview {
1835
- background: var(--ui-surface-lowest);
1836
- border: 1px solid var(--ui-border-faint);
1837
- border-radius: var(--radius-md);
1838
- padding: var(--space-12) 0;
1839
- overflow: hidden;
1840
- }
1841
-
1842
- .columns-preview-inner {
1843
- display: grid;
1844
- min-height: 64px;
1845
- }
1846
-
1847
- .columns-preview-col {
1848
- background: rgba(239, 68, 68, 0.08);
1849
- border-left: 1px dashed rgba(239, 68, 68, 0.3);
1850
- border-right: 1px dashed rgba(239, 68, 68, 0.3);
1851
- display: flex;
1852
- align-items: flex-start;
1853
- justify-content: center;
1854
- min-height: 48px;
1855
- }
1856
-
1857
- .columns-preview-col span {
1858
- font-family: var(--ui-font-mono);
1859
- font-size: 9px;
1860
- color: var(--ui-text-muted);
1861
- padding-top: 4px;
1862
- }
1863
-
1864
- /* Spacing */
1865
- .spacing-grid {
1866
- display: flex;
1867
- flex-direction: column;
1868
- gap: var(--space-6);
1869
- }
1870
-
1871
- .spacing-item {
1872
- display: flex;
1873
- align-items: center;
1874
- gap: var(--space-12);
1875
- }
1876
-
1877
- .spacing-bar {
1878
- height: 1.25rem;
1879
- background: var(--ui-text-accent);
1880
- border-radius: var(--radius-sm);
1881
- flex-shrink: 0;
1882
- }
1883
-
1884
- .spacing-item .token-info {
1885
- flex-direction: row;
1886
- gap: var(--space-8);
1887
- align-items: baseline;
1888
- }
1889
-
1890
- /* Border Radius */
1891
- .radius-grid {
1892
- display: grid;
1893
- grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
1894
- gap: var(--space-16);
1895
- }
1896
-
1897
- .radius-item {
1898
- display: flex;
1899
- flex-direction: column;
1900
- align-items: center;
1901
- gap: var(--space-8);
1902
- }
1903
-
1904
- .radius-box {
1905
- width: 3.5rem;
1906
- height: 3.5rem;
1907
- background: none;
1908
- border: 2px solid var(--ui-border-medium);
1909
- }
1910
-
1911
- /* Typography */
1912
- .typography-columns {
1913
- display: grid;
1914
- grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
1915
- gap: var(--space-24);
1916
- align-items: start;
1917
- }
1918
-
1919
- .typography-group {
1920
- display: flex;
1921
- flex-direction: column;
1922
- gap: var(--space-8);
1923
- min-width: 0;
1924
- }
1925
-
1926
- .font-families-group {
1927
- grid-column: 1 / -1;
1928
- }
1929
-
1930
- .font-stacks-columns {
1931
- display: grid;
1932
- grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
1933
- gap: var(--space-8);
1934
- }
1935
-
1936
- .font-stack {
1937
- display: flex;
1938
- flex-direction: column;
1939
- gap: var(--space-6);
1940
- padding: var(--space-12);
1941
- background: none;
1942
- border: 1px solid var(--ui-border-faint);
1943
- border-radius: var(--radius-md);
1944
- }
1945
-
1946
- .font-stack-list {
1947
- display: flex;
1948
- flex-direction: column;
1949
- gap: var(--space-4);
1950
- }
1951
-
1952
- .font-stack-item {
1953
- display: flex;
1954
- align-items: baseline;
1955
- gap: var(--space-8);
1956
- padding: var(--space-4) 0;
1957
- border-bottom: 1px solid var(--ui-border-faint);
1958
- }
1959
-
1960
- .font-stack-item:last-child {
1961
- border-bottom: none;
1962
- }
1963
-
1964
- .font-stack-position {
1965
- font-size: var(--font-md);
1966
- color: var(--ui-text-muted);
1967
- min-width: 1.25rem;
1968
- text-align: right;
1969
- }
1970
-
1971
- .font-stack-preview-block {
1972
- display: flex;
1973
- flex-direction: column;
1974
- gap: var(--space-2);
1975
- }
1976
-
1977
- .font-stack-preview {
1978
- font-size: var(--font-md);
1979
- color: var(--ui-text-primary);
1980
- line-height: var(--line-height-normal);
1981
- }
1982
-
1983
- .font-stack-name {
1984
- font-size: var(--font-md);
1985
- color: var(--ui-text-tertiary);
1986
- font-family: var(--ui-font-mono);
1987
- }
1988
-
1989
- .font-size-demos {
1990
- display: flex;
1991
- flex-direction: column;
1992
- gap: var(--space-4);
1993
- }
1994
-
1995
- .font-size-item {
1996
- display: flex;
1997
- align-items: baseline;
1998
- gap: var(--space-12);
1999
- }
2000
-
2001
- .font-size-preview {
2002
- color: var(--ui-text-primary);
2003
- font-family: var(--ui-font-sans);
2004
- line-height: 1;
2005
- min-width: 3rem;
2006
- }
2007
-
2008
- .font-size-item .token-info {
2009
- flex-direction: row;
2010
- gap: var(--space-8);
2011
- align-items: baseline;
2012
- }
2013
-
2014
- .font-weight-demos {
2015
- display: flex;
2016
- flex-direction: column;
2017
- gap: var(--space-4);
2018
- }
2019
-
2020
- .font-weight-item {
2021
- display: flex;
2022
- align-items: baseline;
2023
- gap: var(--space-12);
2024
- }
2025
-
2026
- .font-weight-preview {
2027
- font-size: var(--font-xl);
2028
- font-family: var(--ui-font-sans);
2029
- color: var(--ui-text-primary);
2030
- line-height: 1;
2031
- min-width: 2rem;
2032
- }
2033
-
2034
- .font-weight-item .token-info {
2035
- flex-direction: row;
2036
- gap: var(--space-8);
2037
- align-items: baseline;
2038
- }
2039
-
2040
- /* Token Table */
2041
- .token-table {
2042
- display: flex;
2043
- flex-direction: column;
2044
- gap: var(--space-4);
2045
- }
2046
-
2047
- .token-row {
2048
- display: flex;
2049
- align-items: baseline;
2050
- gap: var(--space-12);
2051
- padding: var(--space-4) 0;
2052
- border-bottom: 1px solid var(--ui-border-faint);
2053
- }
2054
-
2055
- .token-row:last-child {
2056
- border-bottom: none;
2057
- }
2058
-
2059
- /* Shadows */
2060
- .shadows-section {
2061
- background: var(--ui-surface-highest);
2062
- padding: var(--space-16);
2063
- border-radius: var(--radius-lg);
2064
- position: relative;
2065
- }
2066
-
2067
- .shadows-layout {
2068
- display: flex;
2069
- gap: var(--space-16);
2070
- align-items: flex-start;
2071
- }
2072
-
2073
- .shadows-main {
2074
- flex: 1;
2075
- min-width: 0;
2076
- }
2077
-
2078
- .shadows-grid {
2079
- display: grid;
2080
- grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
2081
- gap: var(--space-16);
2082
- }
2083
-
2084
- .shadow-item {
2085
- display: flex;
2086
- flex-direction: column;
2087
- align-items: center;
2088
- gap: var(--space-8);
2089
- }
2090
-
2091
- .shadow-item.active {
2092
- outline: 2px solid var(--ui-text-accent);
2093
- outline-offset: var(--space-4);
2094
- border-radius: var(--radius-md);
2095
- }
2096
-
2097
- .editor-header {
2098
- display: flex;
2099
- align-items: center;
2100
- justify-content: space-between;
2101
- gap: var(--space-8);
2102
- }
2103
-
2104
- .reset-btn {
2105
- all: unset;
2106
- font-size: var(--font-xs);
2107
- color: var(--ui-text-muted);
2108
- cursor: pointer;
2109
- padding: var(--space-4) var(--space-8);
2110
- border: 1px solid var(--ui-border-subtle);
2111
- border-radius: var(--radius-sm);
2112
- text-align: center;
2113
- transition: color var(--transition-fast), border-color var(--transition-fast);
2114
- }
2115
-
2116
- .reset-btn:hover {
2117
- color: var(--ui-text-primary);
2118
- border-color: var(--ui-border-medium);
2119
- }
2120
-
2121
- .shadow-box {
2122
- width: 4rem;
2123
- height: 4rem;
2124
- background: var(--ui-surface-high);
2125
- border-radius: var(--radius-md);
2126
- }
2127
-
2128
- .shadow-item .token-info {
2129
- align-items: center;
2130
- text-align: center;
2131
- }
2132
-
2133
- .shadow-edit-btn {
2134
- all: unset;
2135
- font-size: var(--font-xs);
2136
- color: var(--ui-text-muted);
2137
- cursor: pointer;
2138
- padding: var(--space-2) var(--space-8);
2139
- border-radius: var(--radius-sm);
2140
- transition: color var(--transition-fast), background var(--transition-fast);
2141
- }
2142
-
2143
- .shadow-edit-btn:hover {
2144
- color: var(--ui-text-accent);
2145
- background: var(--ui-surface-low);
2146
- }
2147
-
2148
- .shadow-editor {
2149
- display: flex;
2150
- flex-direction: column;
2151
- gap: var(--space-8);
2152
- padding: var(--space-12);
2153
- background: var(--ui-surface-lowest);
2154
- border: 1px solid var(--ui-border-subtle);
2155
- border-radius: var(--radius-md);
2156
- width: 100%;
2157
- min-width: 14rem;
2158
- }
2159
-
2160
- .shadow-slider-row {
2161
- display: flex;
2162
- align-items: center;
2163
- gap: var(--space-8);
2164
- }
2165
-
2166
- .shadow-slider-label {
2167
- font-size: var(--font-xs);
2168
- font-weight: var(--font-weight-semibold);
2169
- color: var(--ui-text-tertiary);
2170
- width: 4rem;
2171
- text-align: right;
2172
- flex-shrink: 0;
2173
- }
2174
-
2175
- .shadow-slider-row input[type="range"] {
2176
- flex: 1;
2177
- min-width: 5rem;
2178
- accent-color: var(--ui-text-accent);
2179
- height: 4px;
2180
- cursor: pointer;
2181
- }
2182
-
2183
- .shadow-slider-input {
2184
- font-size: var(--font-xs);
2185
- color: var(--ui-text-primary);
2186
- font-family: var(--ui-font-mono);
2187
- width: 2.5rem;
2188
- text-align: right;
2189
- flex-shrink: 0;
2190
- background: var(--ui-surface-lowest);
2191
- border: 1px solid var(--ui-border-subtle);
2192
- border-radius: var(--radius-sm);
2193
- padding: var(--space-2) var(--space-4);
2194
- -moz-appearance: textfield;
2195
- }
2196
-
2197
- .shadow-slider-input::-webkit-inner-spin-button,
2198
- .shadow-slider-input::-webkit-outer-spin-button {
2199
- -webkit-appearance: none;
2200
- margin: 0;
2201
- }
2202
-
2203
- .shadow-slider-input:focus {
2204
- outline: none;
2205
- border-color: var(--ui-border-medium);
2206
- }
2207
-
2208
- .shadow-slider-unit {
2209
- font-size: var(--font-xs);
2210
- color: var(--ui-text-muted);
2211
- font-family: var(--ui-font-mono);
2212
- width: 1rem;
2213
- flex-shrink: 0;
2214
- }
2215
-
2216
- /* Angle dial */
2217
- .angle-dial {
2218
- cursor: pointer;
2219
- touch-action: none;
2220
- flex-shrink: 0;
2221
- }
2222
-
2223
- .dial-ring {
2224
- fill: none;
2225
- stroke: var(--ui-border-subtle);
2226
- stroke-width: 1.5;
2227
- }
2228
-
2229
- .dial-line {
2230
- stroke: var(--ui-text-secondary);
2231
- stroke-width: 1.5;
2232
- stroke-linecap: round;
2233
- }
2234
-
2235
- .dial-handle {
2236
- fill: var(--ui-text-accent);
2237
- stroke: none;
2238
- }
2239
-
2240
- .angle-dial:hover .dial-ring {
2241
- stroke: var(--ui-border-medium);
2242
- }
2243
-
2244
- .angle-dial:hover .dial-handle {
2245
- fill: var(--ui-text-primary);
2246
- }
2247
-
2248
- .shadow-css-output {
2249
- display: flex;
2250
- flex-direction: column;
2251
- gap: var(--space-4);
2252
- margin-top: var(--space-4);
2253
- padding-top: var(--space-8);
2254
- border-top: 1px solid var(--ui-border-faint);
2255
- }
2256
-
2257
- .shadow-css-output code {
2258
- font-size: var(--font-xs);
2259
- color: var(--ui-text-accent);
2260
- font-family: var(--ui-font-mono);
2261
- word-break: break-all;
2262
- }
2263
-
2264
- .shadow-copy-btn {
2265
- all: unset;
2266
- font-size: var(--font-xs);
2267
- color: var(--ui-text-muted);
2268
- cursor: pointer;
2269
- padding: var(--space-2) var(--space-6);
2270
- border: 1px solid var(--ui-border-subtle);
2271
- border-radius: var(--radius-sm);
2272
- text-align: center;
2273
- transition: color var(--transition-fast), border-color var(--transition-fast);
2274
- }
2275
-
2276
- .shadow-copy-btn:hover {
2277
- color: var(--ui-text-primary);
2278
- border-color: var(--ui-border-medium);
2279
- }
2280
-
2281
- /* Global shadow editor */
2282
- .global-shadow-editor {
2283
- position: sticky;
2284
- top: var(--space-12);
2285
- flex-shrink: 0;
2286
- display: flex;
2287
- flex-direction: column;
2288
- gap: var(--space-6);
2289
- padding: var(--space-12);
2290
- background: var(--ui-surface-lowest);
2291
- border: 1px solid var(--ui-border-subtle);
2292
- border-radius: var(--radius-md);
2293
- width: 18rem;
2294
- }
2295
-
2296
- .global-shadow-title {
2297
- font-size: var(--font-xs);
2298
- font-weight: var(--font-weight-semibold);
2299
- color: var(--ui-text-secondary);
2300
- margin: 0;
2301
- }
2302
-
2303
- .global-shadow-row {
2304
- display: flex;
2305
- align-items: center;
2306
- gap: var(--space-8);
2307
- }
2308
-
2309
- .lock-btn {
2310
- all: unset;
2311
- cursor: pointer;
2312
- color: var(--ui-text-muted);
2313
- flex-shrink: 0;
2314
- display: flex;
2315
- align-items: center;
2316
- padding: var(--space-2);
2317
- border-radius: var(--radius-sm);
2318
- transition: color var(--transition-fast), background var(--transition-fast);
2319
- }
2320
-
2321
- .lock-btn:hover {
2322
- color: var(--ui-text-primary);
2323
- background: var(--ui-surface-low);
2324
- }
2325
-
2326
- .lock-btn.unlocked {
2327
- color: var(--ui-text-accent);
2328
- }
2329
-
2330
- .global-shadow-row .shadow-slider-label {
2331
- width: 3rem;
2332
- }
2333
-
2334
- .global-shadow-row input[type="range"] {
2335
- flex: 1;
2336
- min-width: 4rem;
2337
- accent-color: var(--ui-text-accent);
2338
- height: 4px;
2339
- cursor: pointer;
2340
- }
2341
-
2342
- .slider-track {
2343
- flex: 1;
2344
- min-width: 4rem;
2345
- position: relative;
2346
- height: 12px;
2347
- border-radius: var(--radius-sm);
2348
- border: 1px solid var(--ui-border-subtle);
2349
- }
2350
-
2351
- .slider-track input[type="range"] {
2352
- position: absolute;
2353
- top: 0;
2354
- left: 0;
2355
- width: 100%;
2356
- height: 100%;
2357
- cursor: pointer;
2358
- margin: 0;
2359
- -webkit-appearance: none;
2360
- appearance: none;
2361
- background: transparent;
2362
- }
2363
-
2364
- .slider-track input[type="range"]::-webkit-slider-thumb {
2365
- -webkit-appearance: none;
2366
- width: 10px;
2367
- height: 14px;
2368
- border-radius: 2px;
2369
- background: white;
2370
- border: 1px solid var(--ui-border-subtle);
2371
- box-shadow: 0 1px 3px rgba(0,0,0,0.4);
2372
- cursor: pointer;
2373
- }
2374
-
2375
- .slider-track input[type="range"]::-moz-range-thumb {
2376
- width: 10px;
2377
- height: 14px;
2378
- border-radius: 2px;
2379
- background: white;
2380
- border: 1px solid var(--ui-border-subtle);
2381
- box-shadow: 0 1px 3px rgba(0,0,0,0.4);
2382
- cursor: pointer;
2383
- }
2384
-
2385
- .slider-track input[type="range"]::-moz-range-track {
2386
- background: transparent;
2387
- border: none;
2388
- }
2389
-
2390
- .global-color-group {
2391
- display: flex;
2392
- gap: var(--space-8);
2393
- align-items: stretch;
2394
- }
2395
-
2396
- .global-color-swatch {
2397
- width: 2rem;
2398
- flex-shrink: 0;
2399
- border-radius: var(--radius-sm);
2400
- border: 1px solid var(--ui-border-subtle);
2401
- }
2402
-
2403
- .global-color-sliders {
2404
- flex: 1;
2405
- display: flex;
2406
- flex-direction: column;
2407
- gap: var(--space-6);
2408
- }
2409
-
2410
- .bg-picker-btn {
2411
- all: unset;
2412
- font-size: var(--font-xs);
2413
- font-family: var(--ui-font-mono);
2414
- color: var(--ui-text-muted);
2415
- cursor: pointer;
2416
- padding: var(--space-4) var(--space-8);
2417
- border: 1px solid var(--ui-border-subtle);
2418
- border-radius: var(--radius-md);
2419
- background: var(--ui-surface-low);
2420
- transition: color var(--transition-fast), border-color var(--transition-fast);
2421
- }
2422
-
2423
- .bg-picker-btn:hover {
2424
- color: var(--ui-text-primary);
2425
- border-color: var(--ui-border-medium);
2426
- }
2427
-
2428
- .bg-picker-menu {
2429
- position: absolute;
2430
- bottom: calc(100% + var(--space-4));
2431
- left: 0;
2432
- width: 14rem;
2433
- max-width: calc(100vw - 2rem);
2434
- max-height: 24rem;
2435
- overflow-y: auto;
2436
- background: var(--ui-surface-low);
2437
- border: 1px solid var(--ui-border-subtle);
2438
- border-radius: var(--radius-md);
2439
- padding: var(--space-4);
2440
- display: flex;
2441
- flex-direction: column;
2442
- z-index: 10;
2443
- }
2444
-
2445
- .bg-group-header {
2446
- all: unset;
2447
- display: flex;
2448
- justify-content: space-between;
2449
- align-items: center;
2450
- padding: var(--space-6) var(--space-8);
2451
- font-size: var(--font-xs);
2452
- color: var(--ui-text-secondary);
2453
- cursor: pointer;
2454
- border-radius: var(--radius-sm);
2455
- transition: background var(--transition-fast);
2456
- }
2457
-
2458
- .bg-group-header:hover {
2459
- background: var(--ui-surface-high);
2460
- }
2461
-
2462
- .bg-group-arrow {
2463
- font-size: 0.65rem;
2464
- color: var(--ui-text-muted);
2465
- }
2466
-
2467
- .bg-group-colors {
2468
- display: flex;
2469
- flex-direction: column;
2470
- padding-left: var(--space-8);
2471
- }
2472
-
2473
- .bg-color-option {
2474
- all: unset;
2475
- display: flex;
2476
- align-items: center;
2477
- gap: var(--space-8);
2478
- padding: var(--space-4) var(--space-8);
2479
- font-size: var(--font-xs);
2480
- color: var(--ui-text-tertiary);
2481
- cursor: pointer;
2482
- border-radius: var(--radius-sm);
2483
- transition: background var(--transition-fast);
2484
- }
2485
-
2486
- .bg-color-option:hover {
2487
- background: var(--ui-surface-high);
2488
- color: var(--ui-text-primary);
2489
- }
2490
-
2491
- .bg-color-swatch {
2492
- width: 1rem;
2493
- height: 1rem;
2494
- border-radius: var(--radius-sm);
2495
- border: 1px solid var(--ui-border-subtle);
2496
- flex-shrink: 0;
2497
- }
2498
-
2499
- .text-shadow-demos {
2500
- display: flex;
2501
- gap: var(--space-24);
2502
- flex-wrap: wrap;
2503
- }
2504
-
2505
- .text-shadow-item {
2506
- display: flex;
2507
- flex-direction: column;
2508
- align-items: center;
2509
- gap: var(--space-8);
2510
- }
2511
-
2512
- .text-shadow-preview {
2513
- font-size: var(--font-2xl);
2514
- font-family: var(--ui-font-sans);
2515
- font-weight: var(--font-weight-semibold);
2516
- color: var(--ui-text-primary);
2517
- }
2518
-
2519
- /* Gradients */
2520
- .gradients-grid {
2521
- display: grid;
2522
- grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
2523
- gap: var(--space-16);
2524
- }
2525
-
2526
- .gradient-item {
2527
- display: flex;
2528
- flex-direction: column;
2529
- gap: var(--space-8);
2530
- }
2531
-
2532
- .gradient-box {
2533
- height: 3rem;
2534
- border-radius: var(--radius-md);
2535
- }
2536
-
2537
- /* Utility Tokens */
2538
- .utility-columns {
2539
- display: grid;
2540
- grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
2541
- gap: var(--space-24);
2542
- align-items: start;
2543
- }
2544
-
2545
- .utility-group {
2546
- display: flex;
2547
- flex-direction: column;
2548
- gap: var(--space-8);
2549
- }
2550
-
2551
- /* Palette Editor */
2552
- .editor-intro {
2553
- font-size: var(--font-md);
2554
- color: var(--ui-text-tertiary);
2555
- margin: 0;
2556
- }
2557
-
2558
- .editor-intro code {
2559
- font-size: var(--font-md);
2560
- color: var(--ui-text-accent);
2561
- background: var(--ui-surface-lowest);
2562
- padding: var(--space-2) var(--space-4);
2563
- border-radius: var(--radius-sm);
2564
- font-family: var(--ui-font-mono);
2565
- }
2566
-
2567
- .palette-editors {
2568
- display: flex;
2569
- flex-direction: column;
2570
- gap: var(--space-16);
2571
- }
2572
-
2573
- /* Overlays */
2574
- .overlays-grid {
2575
- display: grid;
2576
- grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
2577
- gap: var(--space-16);
2578
- }
2579
-
2580
- .overlay-item {
2581
- display: flex;
2582
- flex-direction: column;
2583
- align-items: center;
2584
- gap: var(--space-8);
2585
- }
2586
-
2587
- .overlay-swatch-wrap {
2588
- width: 4rem;
2589
- height: 4rem;
2590
- border-radius: var(--radius-md);
2591
- position: relative;
2592
- overflow: hidden;
2593
- border: 1px solid var(--ui-border-subtle);
2594
- background-image:
2595
- linear-gradient(45deg, #ccc 25%, transparent 25%),
2596
- linear-gradient(-45deg, #ccc 25%, transparent 25%),
2597
- linear-gradient(45deg, transparent 75%, #ccc 75%),
2598
- linear-gradient(-45deg, transparent 75%, #ccc 75%);
2599
- background-size: 12px 12px;
2600
- background-position: 0 0, 0 6px, 6px -6px, -6px 0px;
2601
- background-color: #fff;
2602
- }
2603
-
2604
- .overlay-swatch-wrap--dark {
2605
- background-color: #222;
2606
- background-image:
2607
- linear-gradient(45deg, #333 25%, transparent 25%),
2608
- linear-gradient(-45deg, #333 25%, transparent 25%),
2609
- linear-gradient(45deg, transparent 75%, #333 75%),
2610
- linear-gradient(-45deg, transparent 75%, #333 75%);
2611
- background-size: 12px 12px;
2612
- background-position: 0 0, 0 6px, 6px -6px, -6px 0px;
2613
- }
2614
-
2615
- .overlay-swatch {
2616
- position: absolute;
2617
- inset: 0;
2618
- }
2619
-
2620
- .overlay-item .token-info {
2621
- align-items: center;
2622
- text-align: center;
2623
- }
2624
-
2625
- .overlay-global-editor {
2626
- display: flex;
2627
- flex-direction: column;
2628
- gap: var(--space-8);
2629
- padding: var(--space-12);
2630
- background: var(--ui-surface-lowest);
2631
- border: 1px solid var(--ui-border-subtle);
2632
- border-radius: var(--radius-md);
2633
- margin-top: var(--space-8);
2634
- }
2635
-
2636
- .overlay-global-columns {
2637
- display: grid;
2638
- grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
2639
- gap: var(--space-16);
2640
- align-items: start;
2641
- }
2642
-
2643
- .overlay-global-col {
2644
- display: flex;
2645
- flex-direction: column;
2646
- gap: var(--space-6);
2647
- min-width: 0;
2648
- }
2649
-
2650
- /* Gradients and utility fills tighten at narrow widths */
2651
- .gradients-grid,
2652
- .utility-columns {
2653
- grid-template-columns: repeat(auto-fill, minmax(min(14rem, 100%), 1fr));
2654
- }
2655
- </style>