@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
@@ -0,0 +1,360 @@
1
+ <script lang="ts">
2
+ import type {
3
+ FontFamily,
4
+ FontSource,
5
+ FontStack,
6
+ FontStackSlot,
7
+ FontStackVariable,
8
+ GenericFamily,
9
+ SystemCascadePreset,
10
+ } from '../lib/themeTypes';
11
+ import { editorState, setFontStacks } from '../lib/editorStore';
12
+ import { applyFontStacks, SYSTEM_CASCADES } from '../lib/fontLoader';
13
+
14
+ const SYSTEM_PRESETS: SystemCascadePreset[] = ['system-ui-sans', 'system-ui-serif', 'system-ui-mono'];
15
+ const GENERIC_VALUES: GenericFamily[] = ['sans-serif', 'serif', 'monospace', 'cursive', 'fantasy'];
16
+
17
+ const STACK_VARIABLES: FontStackVariable[] = [
18
+ '--font-display',
19
+ '--font-sans',
20
+ '--font-serif',
21
+ '--font-mono',
22
+ ];
23
+
24
+ $: fontSourcesList = $editorState.fonts.sources;
25
+ $: fontStacksList = $editorState.fonts.stacks;
26
+ $: allFamilies = (fontSourcesList as FontSource[]).flatMap((s) => s.families.map((f) => ({ ...f, sourceLabel: s.label ?? s.kind })));
27
+ $: familyById = new Map<string, FontFamily>(allFamilies.map((f) => [f.id, f]));
28
+
29
+ function ensureAllStacksPresent(current: FontStack[]): FontStack[] {
30
+ const byVar = new Map(current.map((s) => [s.variable, s]));
31
+ return STACK_VARIABLES.map((v) => byVar.get(v) ?? { variable: v, slots: [{ kind: 'generic', value: 'sans-serif' } as FontStackSlot] });
32
+ }
33
+
34
+ $: stacks = ensureAllStacksPresent(fontStacksList);
35
+
36
+ function slotKey(slot: FontStackSlot): string {
37
+ if (slot.kind === 'project') return `project:${slot.familyId}`;
38
+ if (slot.kind === 'system') return `system:${slot.preset}`;
39
+ return `generic:${slot.value}`;
40
+ }
41
+
42
+ function slotFromKey(key: string): FontStackSlot | null {
43
+ const [kind, ...rest] = key.split(':');
44
+ const value = rest.join(':');
45
+ if (kind === 'project') return { kind: 'project', familyId: value };
46
+ if (kind === 'system') return { kind: 'system', preset: value as SystemCascadePreset };
47
+ if (kind === 'generic') return { kind: 'generic', value: value as GenericFamily };
48
+ return null;
49
+ }
50
+
51
+ function slotDisplayName(slot: FontStackSlot): string {
52
+ if (slot.kind === 'project') return familyById.get(slot.familyId)?.name ?? '(missing)';
53
+ if (slot.kind === 'system') {
54
+ return slot.preset === 'system-ui-sans' ? 'System UI (sans)'
55
+ : slot.preset === 'system-ui-serif' ? 'System UI (serif)'
56
+ : 'System UI (mono)';
57
+ }
58
+ return slot.value;
59
+ }
60
+
61
+ function slotCssValue(slot: FontStackSlot): string {
62
+ if (slot.kind === 'project') return familyById.get(slot.familyId)?.cssName ?? 'sans-serif';
63
+ if (slot.kind === 'system') return SYSTEM_CASCADES[slot.preset];
64
+ return slot.value;
65
+ }
66
+
67
+ function updateStack(variable: FontStackVariable, updater: (slots: FontStackSlot[]) => FontStackSlot[]) {
68
+ const next = stacks.map((s) => (s.variable === variable ? { ...s, slots: updater([...s.slots]) } : s));
69
+ setFontStacks(next);
70
+ applyFontStacks(next, fontSourcesList);
71
+ }
72
+
73
+ function handleSelectChange(variable: FontStackVariable, index: number, value: string) {
74
+ const slot = slotFromKey(value);
75
+ if (!slot) return;
76
+ updateStack(variable, (slots) => {
77
+ slots[index] = slot;
78
+ return slots;
79
+ });
80
+ }
81
+
82
+ function onSelectChange(event: Event, variable: FontStackVariable, index: number) {
83
+ const target = event.currentTarget as HTMLSelectElement;
84
+ handleSelectChange(variable, index, target.value);
85
+ }
86
+
87
+ function removeSlot(variable: FontStackVariable, index: number) {
88
+ updateStack(variable, (slots) => {
89
+ slots.splice(index, 1);
90
+ return slots;
91
+ });
92
+ }
93
+
94
+ function addSlot(variable: FontStackVariable) {
95
+ const stack = stacks.find((s) => s.variable === variable);
96
+ const generic: GenericFamily =
97
+ variable === '--font-mono' ? 'monospace' : variable === '--font-serif' ? 'serif' : 'sans-serif';
98
+ const existing = new Set((stack?.slots ?? []).map(slotKey));
99
+ let newSlot: FontStackSlot = { kind: 'generic', value: generic };
100
+ if (existing.has(slotKey(newSlot))) {
101
+ const preset: SystemCascadePreset =
102
+ variable === '--font-mono' ? 'system-ui-mono' : variable === '--font-serif' ? 'system-ui-serif' : 'system-ui-sans';
103
+ newSlot = { kind: 'system', preset };
104
+ }
105
+ updateStack(variable, (slots) => {
106
+ slots.push(newSlot);
107
+ return slots;
108
+ });
109
+ }
110
+
111
+ let dragSource: { variable: FontStackVariable; index: number } | null = null;
112
+ let dragOver: { variable: FontStackVariable; index: number; position: 'before' | 'on' | 'after' } | null = null;
113
+
114
+ function onDragStart(e: DragEvent, variable: FontStackVariable, index: number) {
115
+ if (!e.dataTransfer) return;
116
+ dragSource = { variable, index };
117
+ e.dataTransfer.effectAllowed = 'move';
118
+ e.dataTransfer.setData('application/x-font-slot', JSON.stringify({ variable, index }));
119
+ }
120
+
121
+ function onDragOver(e: DragEvent, variable: FontStackVariable, index: number) {
122
+ const types = e.dataTransfer?.types ?? [];
123
+ if (!types.includes('application/x-font-slot')) return;
124
+ e.preventDefault();
125
+ if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
126
+ const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
127
+ const y = e.clientY - rect.top;
128
+ const third = rect.height / 3;
129
+ const position = y < third ? 'before' : y > rect.height - third ? 'after' : 'on';
130
+ dragOver = { variable, index, position };
131
+ }
132
+
133
+ function onDragLeave() {
134
+ dragOver = null;
135
+ }
136
+
137
+ function onDrop(e: DragEvent, variable: FontStackVariable, index: number) {
138
+ e.preventDefault();
139
+ const slotPayload = e.dataTransfer?.getData('application/x-font-slot');
140
+ const position = dragOver?.position ?? 'on';
141
+ dragOver = null;
142
+ if (!slotPayload) return;
143
+ const src = JSON.parse(slotPayload) as { variable: FontStackVariable; index: number };
144
+ if (src.variable !== variable) return;
145
+ if (src.index === index && position === 'on') return;
146
+ updateStack(variable, (slots) => {
147
+ const [moved] = slots.splice(src.index, 1);
148
+ let target = index;
149
+ if (src.index < index) target -= 1;
150
+ if (position === 'after') target += 1;
151
+ slots.splice(target, 0, moved);
152
+ return slots;
153
+ });
154
+ }
155
+
156
+ function onDragEnd() {
157
+ dragSource = null;
158
+ dragOver = null;
159
+ }
160
+ </script>
161
+
162
+ <div class="font-stacks-columns">
163
+ {#each stacks as stack (stack.variable)}
164
+ <div class="font-stack">
165
+ <div class="stack-header">
166
+ <span class="stack-variable">{stack.variable}</span>
167
+ </div>
168
+ <div class="font-stack-list">
169
+ {#each stack.slots as slot, i (i + ':' + slotKey(slot))}
170
+ <div
171
+ class="slot-row"
172
+ class:drop-on={dragOver?.variable === stack.variable && dragOver?.index === i && dragOver?.position === 'on'}
173
+ class:drop-before={dragOver?.variable === stack.variable && dragOver?.index === i && dragOver?.position === 'before'}
174
+ class:drop-after={dragOver?.variable === stack.variable && dragOver?.index === i && dragOver?.position === 'after'}
175
+ class:dragging={dragSource?.variable === stack.variable && dragSource?.index === i}
176
+ draggable="true"
177
+ on:dragstart={(e) => onDragStart(e, stack.variable, i)}
178
+ on:dragover={(e) => onDragOver(e, stack.variable, i)}
179
+ on:dragleave={onDragLeave}
180
+ on:drop={(e) => onDrop(e, stack.variable, i)}
181
+ on:dragend={onDragEnd}
182
+ >
183
+ <span class="drag-handle" aria-hidden="true">⋮⋮</span>
184
+ <span class="slot-position">{i + 1}.</span>
185
+ <div class="slot-main">
186
+ <span
187
+ class="slot-preview"
188
+ style="font-family: {slotCssValue(slot)};{stack.variable === '--font-display' ? ' font-size: var(--ui-font-size-2xl);' : ''}"
189
+ >The quick brown fox jumps over the lazy dog</span>
190
+ <select
191
+ class="form-select slot-select"
192
+ value={slotKey(slot)}
193
+ on:change={(e) => onSelectChange(e, stack.variable, i)}
194
+ >
195
+ {#if allFamilies.length > 0}
196
+ <optgroup label="Project fonts">
197
+ {#each allFamilies as fam}
198
+ <option value={`project:${fam.id}`}>{fam.name}</option>
199
+ {/each}
200
+ </optgroup>
201
+ {/if}
202
+ <optgroup label="System cascade">
203
+ {#each SYSTEM_PRESETS as p}
204
+ <option value={`system:${p}`}>{p === 'system-ui-sans' ? 'System UI (sans)' : p === 'system-ui-serif' ? 'System UI (serif)' : 'System UI (mono)'}</option>
205
+ {/each}
206
+ </optgroup>
207
+ <optgroup label="Generic">
208
+ {#each GENERIC_VALUES as g}
209
+ <option value={`generic:${g}`}>{g}</option>
210
+ {/each}
211
+ </optgroup>
212
+ </select>
213
+ </div>
214
+ <button
215
+ type="button"
216
+ class="slot-remove"
217
+ aria-label="Remove slot"
218
+ title="Remove"
219
+ on:click={() => removeSlot(stack.variable, i)}
220
+ disabled={stack.slots.length <= 1}
221
+ >×</button>
222
+ </div>
223
+ {/each}
224
+ </div>
225
+ <button type="button" class="add-fallback" on:click={() => addSlot(stack.variable)}>
226
+ + add fallback
227
+ </button>
228
+ </div>
229
+ {/each}
230
+ </div>
231
+
232
+ <style>
233
+ .font-stacks-columns {
234
+ display: grid;
235
+ grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
236
+ gap: var(--ui-space-8);
237
+ }
238
+
239
+ .font-stack {
240
+ display: flex;
241
+ flex-direction: column;
242
+ gap: var(--ui-space-6);
243
+ padding: var(--ui-space-12);
244
+ background: none;
245
+ border: 1px solid var(--ui-border-faint);
246
+ border-radius: var(--ui-radius-md);
247
+ }
248
+
249
+ .stack-header {
250
+ display: flex;
251
+ align-items: center;
252
+ }
253
+
254
+ .stack-variable {
255
+ font-family: var(--ui-font-mono);
256
+ font-size: var(--ui-font-size-md);
257
+ color: var(--ui-text-primary);
258
+ }
259
+
260
+ .font-stack-list {
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: var(--ui-space-4);
264
+ }
265
+
266
+ .slot-row {
267
+ display: grid;
268
+ grid-template-columns: auto auto 1fr auto;
269
+ align-items: center;
270
+ gap: var(--ui-space-6);
271
+ padding: var(--ui-space-4) 0;
272
+ border-bottom: 1px solid var(--ui-border-faint);
273
+ position: relative;
274
+ }
275
+ .slot-row:last-child { border-bottom: none; }
276
+
277
+ .drag-handle {
278
+ cursor: grab;
279
+ user-select: none;
280
+ color: var(--ui-text-muted);
281
+ font-size: var(--ui-font-size-md);
282
+ line-height: 1;
283
+ letter-spacing: -2px;
284
+ }
285
+ .slot-row.dragging .drag-handle { cursor: grabbing; }
286
+ .slot-row.dragging { opacity: 0.55; }
287
+
288
+ .slot-row.drop-on { outline: 2px solid var(--ui-focus, #5eb2ff); outline-offset: -2px; }
289
+ .slot-row.drop-before::before,
290
+ .slot-row.drop-after::after {
291
+ content: '';
292
+ position: absolute;
293
+ left: 0; right: 0;
294
+ height: 2px;
295
+ background: var(--ui-focus, #5eb2ff);
296
+ }
297
+ .slot-row.drop-before::before { top: -1px; }
298
+ .slot-row.drop-after::after { bottom: -1px; }
299
+
300
+ .slot-position {
301
+ font-size: var(--ui-font-size-md);
302
+ color: var(--ui-text-muted);
303
+ min-width: 1.25rem;
304
+ text-align: right;
305
+ }
306
+
307
+ .slot-main {
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: var(--ui-space-2);
311
+ min-width: 0;
312
+ }
313
+
314
+ .slot-preview {
315
+ font-size: var(--ui-font-size-md);
316
+ color: var(--ui-text-primary);
317
+ line-height: var(--ui-line-height-normal);
318
+ overflow: hidden;
319
+ text-overflow: ellipsis;
320
+ white-space: nowrap;
321
+ }
322
+
323
+ .slot-select {
324
+ width: 100%;
325
+ font-family: var(--ui-font-mono);
326
+ font-size: var(--ui-font-size-sm);
327
+ }
328
+
329
+ .slot-remove {
330
+ background: none;
331
+ border: 1px solid var(--ui-border-faint);
332
+ color: var(--ui-text-muted);
333
+ font-size: var(--ui-font-size-md);
334
+ line-height: 1;
335
+ width: 1.5rem;
336
+ height: 1.5rem;
337
+ border-radius: var(--ui-radius-sm);
338
+ cursor: pointer;
339
+ }
340
+ .slot-remove:hover:not(:disabled) {
341
+ color: var(--ui-text-primary);
342
+ border-color: var(--ui-border);
343
+ }
344
+ .slot-remove:disabled { opacity: 0.35; cursor: not-allowed; }
345
+
346
+ .add-fallback {
347
+ align-self: flex-start;
348
+ background: none;
349
+ border: 1px dashed var(--ui-border-faint);
350
+ color: var(--ui-text-muted);
351
+ font-size: var(--ui-font-size-sm);
352
+ padding: var(--ui-space-4) var(--ui-space-8);
353
+ border-radius: var(--ui-radius-sm);
354
+ cursor: pointer;
355
+ }
356
+ .add-fallback:hover {
357
+ color: var(--ui-text-primary);
358
+ border-color: var(--ui-border);
359
+ }
360
+ </style>