@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,11 +1,11 @@
1
1
  <script lang="ts">
2
2
  import { createEventDispatcher, onMount } from 'svelte';
3
- import type { TokenFileMeta } from '../lib/tokenTypes';
4
- import { listTokenFiles, deleteTokenFile, setActiveFile, sanitizeFileName, getProductionInfo, setProductionFile } from '../lib/tokenService';
5
- import type { ProductionInfo } from '../lib/tokenService';
6
- import { activeFileName, editorConfigs, configsLoadedFromFile } from '../lib/editorConfigStore';
7
- import BackupBrowser from './BackupBrowser.svelte';
8
- import EditorDialog from './EditorDialog.svelte';
3
+ import type { ThemeMeta } from '../lib/themeTypes';
4
+ import { listThemes, deleteTheme, setActiveFile, sanitizeFileName, getProductionInfo, setProductionFile } from '../lib/themeService';
5
+ import type { ProductionInfo } from '../lib/themeService';
6
+ import { activeFileName } from '../lib/editorConfigStore';
7
+ import { dirty } from '../lib/editorStore';
8
+ import UIDialog from './UIDialog.svelte';
9
9
 
10
10
  const dispatch = createEventDispatcher<{
11
11
  save: { fileName: string; displayName: string };
@@ -14,28 +14,20 @@
14
14
 
15
15
  export let saveStatus: 'idle' | 'saving' | 'saved' | 'error' = 'idle';
16
16
 
17
- let files: TokenFileMeta[] = [];
17
+ let files: ThemeMeta[] = [];
18
18
  let showFileList = false;
19
19
  let saveAsEditing = false;
20
20
  let saveAsName = '';
21
21
  let saveAsInput: HTMLInputElement;
22
22
  let currentDisplayName = 'Default';
23
- let showBackups = false;
24
23
 
25
24
  // --- Production state ---
26
25
  let productionInfo: ProductionInfo | null = null;
27
26
  let productionUpdateStatus: 'idle' | 'updating' | 'done' | 'error' = 'idle';
28
27
 
29
- // --- Dirty state tracking ---
30
- let savedConfigHash = '';
31
- let initialized = false;
32
-
33
- $: configHash = JSON.stringify($editorConfigs);
34
- $: unsaved = initialized && configHash !== savedConfigHash;
35
-
36
28
  async function refreshFiles() {
37
29
  try {
38
- files = await listTokenFiles();
30
+ files = await listThemes();
39
31
  const active = files.find(f => f.isActive);
40
32
  if (active) {
41
33
  $activeFileName = active.fileName;
@@ -70,16 +62,10 @@
70
62
  onMount(async () => {
71
63
  await refreshFiles();
72
64
  await refreshProduction();
73
- // Delay snapshot until editors have initialized
74
- setTimeout(() => {
75
- savedConfigHash = JSON.stringify($editorConfigs);
76
- initialized = true;
77
- }, 500);
78
65
  });
79
66
 
80
67
  function handleSave() {
81
68
  dispatch('save', { fileName: $activeFileName, displayName: currentDisplayName });
82
- setTimeout(() => { savedConfigHash = JSON.stringify($editorConfigs); }, 300);
83
69
  }
84
70
 
85
71
  function handleSaveIncrement() {
@@ -100,10 +86,7 @@
100
86
  dispatch('save', { fileName, displayName });
101
87
  $activeFileName = fileName;
102
88
  currentDisplayName = displayName;
103
- setTimeout(() => {
104
- refreshFiles();
105
- savedConfigHash = JSON.stringify($editorConfigs);
106
- }, 500);
89
+ setTimeout(() => refreshFiles(), 500);
107
90
  }
108
91
 
109
92
  function openSaveAs() {
@@ -126,10 +109,7 @@
126
109
  dispatch('save', { fileName, displayName });
127
110
  $activeFileName = fileName;
128
111
  currentDisplayName = displayName;
129
- setTimeout(() => {
130
- refreshFiles();
131
- savedConfigHash = JSON.stringify($editorConfigs);
132
- }, 500);
112
+ setTimeout(() => refreshFiles(), 500);
133
113
  }
134
114
 
135
115
  function cancelSaveAs() {
@@ -137,22 +117,19 @@
137
117
  saveAsName = '';
138
118
  }
139
119
 
140
- async function handleLoad(file: TokenFileMeta) {
120
+ async function handleLoad(file: ThemeMeta) {
141
121
  showFileList = false;
142
122
  await setActiveFile(file.fileName);
143
123
  $activeFileName = file.fileName;
144
124
  currentDisplayName = file.name;
145
125
  dispatch('load', { fileName: file.fileName });
146
- // Snapshot after load completes
147
- setTimeout(() => {
148
- savedConfigHash = JSON.stringify($editorConfigs);
149
- }, 500);
126
+ // editorStore.loadFromFile clears history and resets dirty — no snapshot needed here.
150
127
  }
151
128
 
152
- async function handleDelete(file: TokenFileMeta) {
129
+ async function handleDelete(file: ThemeMeta) {
153
130
  if (file.fileName === 'default') return;
154
131
  try {
155
- await deleteTokenFile(file.fileName);
132
+ await deleteTheme(file.fileName);
156
133
  await refreshFiles();
157
134
  // If we deleted the active file, it reverts to default on the server
158
135
  if (file.fileName === $activeFileName) {
@@ -176,9 +153,46 @@
176
153
  if (showFileList) refreshFiles();
177
154
  }
178
155
 
156
+ const dateFormatter = new Intl.DateTimeFormat(undefined, {
157
+ month: 'short',
158
+ day: 'numeric',
159
+ hour: 'numeric',
160
+ minute: '2-digit',
161
+ });
162
+
163
+ function formatUpdatedAt(iso: string): string {
164
+ if (!iso) return '';
165
+ const d = new Date(iso);
166
+ if (Number.isNaN(d.getTime())) return '';
167
+ return dateFormatter.format(d);
168
+ }
169
+
170
+ type SortKey = 'name' | 'updatedAt';
171
+ let sortKey: SortKey = 'updatedAt';
172
+ let sortDir: 'asc' | 'desc' = 'desc';
173
+
174
+ function toggleSort(key: SortKey) {
175
+ if (sortKey === key) {
176
+ sortDir = sortDir === 'asc' ? 'desc' : 'asc';
177
+ } else {
178
+ sortKey = key;
179
+ // Default: names asc, dates desc (most-recent first)
180
+ sortDir = key === 'name' ? 'asc' : 'desc';
181
+ }
182
+ }
183
+
184
+ $: sortedFiles = [...files].sort((a, b) => {
185
+ let cmp = 0;
186
+ if (sortKey === 'name') {
187
+ cmp = a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
188
+ } else {
189
+ cmp = (a.updatedAt || '').localeCompare(b.updatedAt || '');
190
+ }
191
+ return sortDir === 'asc' ? cmp : -cmp;
192
+ });
179
193
  </script>
180
194
 
181
- <div class="token-file-manager">
195
+ <div class="theme-file-manager">
182
196
  <div class="active-file">
183
197
  <span class="active-label">Theme</span>
184
198
  <span class="active-name">{currentDisplayName}</span>
@@ -193,8 +207,8 @@
193
207
  class:saved={saveStatus === 'saved'}
194
208
  class:error={saveStatus === 'error'}
195
209
  on:click={handleSave}
196
- disabled={saveStatus === 'saving' || !$configsLoadedFromFile}
197
- title={$configsLoadedFromFile ? "Save to current file" : "Load a token file first"}
210
+ disabled={saveStatus === 'saving'}
211
+ title="Save to current file"
198
212
  >
199
213
  <i class="fas" class:fa-save={saveStatus === 'idle'} class:fa-spinner={saveStatus === 'saving'} class:fa-check={saveStatus === 'saved'} class:fa-times={saveStatus === 'error'}></i>
200
214
  <span>
@@ -204,7 +218,7 @@
204
218
  <button
205
219
  class="tfm-btn increment-btn"
206
220
  on:click={handleSaveIncrement}
207
- disabled={saveStatus === 'saving' || !$configsLoadedFromFile}
221
+ disabled={saveStatus === 'saving'}
208
222
  title="Save as incremented copy"
209
223
  >
210
224
  <i class="fas fa-plus"></i>
@@ -245,26 +259,18 @@
245
259
  class="tfm-btn"
246
260
  class:active={showFileList}
247
261
  on:click={toggleFileList}
248
- title="Load a token file"
262
+ title="Load a theme"
249
263
  >
250
264
  <i class="fas fa-folder-open"></i>
251
265
  <span>Load</span>
252
266
  </button>
253
267
 
254
- <button
255
- class="tfm-btn history-btn"
256
- on:click={() => showBackups = true}
257
- title="View backup history and restore"
258
- >
259
- <i class="fas fa-history"></i>
260
- <span>History</span>
261
- </button>
262
268
 
263
269
  </div>
264
270
 
265
271
  <div class="status-labels">
266
- <span class="status-label" class:dirty={unsaved} class:clean={!unsaved}>
267
- {unsaved ? 'Unsaved changes' : 'Saved'}
272
+ <span class="status-label" class:dirty={$dirty} class:clean={!$dirty}>
273
+ {$dirty ? 'Unsaved changes' : 'Saved'}
268
274
  </span>
269
275
  </div>
270
276
 
@@ -282,12 +288,12 @@
282
288
  class:done={productionUpdateStatus === 'done'}
283
289
  class:error={productionUpdateStatus === 'error'}
284
290
  on:click={handleUpdateProduction}
285
- disabled={productionUpdateStatus === 'updating' || !$configsLoadedFromFile || (productionInfo?.fileName === $activeFileName)}
291
+ disabled={productionUpdateStatus === 'updating' || (productionInfo?.fileName === $activeFileName)}
286
292
  title={productionInfo?.fileName === $activeFileName ? 'Already in production' : `Set "${currentDisplayName}" as production`}
287
293
  >
288
- <i class="fas" class:fa-sync-alt={productionUpdateStatus === 'idle'} class:fa-spinner={productionUpdateStatus === 'updating'} class:fa-check={productionUpdateStatus === 'done'} class:fa-times={productionUpdateStatus === 'error'}></i>
294
+ <i class="fas" class:fa-upload={productionUpdateStatus === 'idle'} class:fa-spinner={productionUpdateStatus === 'updating'} class:fa-check={productionUpdateStatus === 'done'} class:fa-times={productionUpdateStatus === 'error'}></i>
289
295
  <span>
290
- {#if productionUpdateStatus === 'idle'}Sync Theme{:else if productionUpdateStatus === 'updating'}Syncing{:else if productionUpdateStatus === 'done'}Synced{:else}Error{/if}
296
+ {#if productionUpdateStatus === 'idle'}Apply Theme{:else if productionUpdateStatus === 'updating'}Applying{:else if productionUpdateStatus === 'done'}Applied{:else}Error{/if}
291
297
  </span>
292
298
  </button>
293
299
 
@@ -300,18 +306,42 @@
300
306
 
301
307
  </div>
302
308
 
303
- <EditorDialog
309
+ <UIDialog
304
310
  bind:show={showFileList}
305
311
  title="Load Theme"
306
312
  cancelLabel="Close"
307
313
  width="420px"
308
314
  >
309
315
  <div class="load-list">
310
- {#each files as file}
316
+ <div class="load-header">
317
+ <button
318
+ class="sort-btn name-col"
319
+ class:active-sort={sortKey === 'name'}
320
+ on:click={() => toggleSort('name')}
321
+ >
322
+ <span>Name</span>
323
+ {#if sortKey === 'name'}
324
+ <i class="fas {sortDir === 'asc' ? 'fa-caret-up' : 'fa-caret-down'}"></i>
325
+ {/if}
326
+ </button>
327
+ <button
328
+ class="sort-btn date-col"
329
+ class:active-sort={sortKey === 'updatedAt'}
330
+ on:click={() => toggleSort('updatedAt')}
331
+ >
332
+ <span>Date</span>
333
+ {#if sortKey === 'updatedAt'}
334
+ <i class="fas {sortDir === 'asc' ? 'fa-caret-up' : 'fa-caret-down'}"></i>
335
+ {/if}
336
+ </button>
337
+ <span class="header-spacer"></span>
338
+ </div>
339
+ {#each sortedFiles as file}
311
340
  <div class="load-item" class:active={file.fileName === $activeFileName}>
312
341
  <button class="load-name-btn" on:click={() => handleLoad(file)}>
313
342
  {file.name}
314
343
  </button>
344
+ <span class="updated-at" title={file.updatedAt}>{formatUpdatedAt(file.updatedAt)}</span>
315
345
  {#if file.fileName === $activeFileName}
316
346
  <span class="active-badge">active</span>
317
347
  {/if}
@@ -330,65 +360,55 @@
330
360
  <div class="load-item empty">No saved files</div>
331
361
  {/if}
332
362
  </div>
333
- </EditorDialog>
334
-
335
- <BackupBrowser
336
- bind:open={showBackups}
337
- on:restored={(e) => {
338
- if (e.detail.type === 'tokens') {
339
- dispatch('load', { fileName: $activeFileName });
340
- }
341
- refreshFiles();
342
- }}
343
- />
363
+ </UIDialog>
344
364
 
345
365
 
346
366
  <style>
347
- .token-file-manager {
367
+ .theme-file-manager {
348
368
  display: flex;
349
369
  flex-direction: column;
350
- gap: var(--space-8);
370
+ gap: var(--ui-space-8);
351
371
  }
352
372
 
353
373
  .active-file {
354
374
  display: flex;
355
375
  flex-direction: column;
356
- gap: var(--space-2);
357
- padding: 0 var(--space-4);
376
+ gap: var(--ui-space-2);
377
+ padding: 0 var(--ui-space-4);
358
378
  }
359
379
 
360
380
  .active-label {
361
- font-size: var(--font-xs);
362
- color: var(--ui-text-muted);
381
+ font-size: var(--ui-font-size-xs);
382
+ color: var(--ui-text-secondary);
363
383
  text-transform: uppercase;
364
384
  letter-spacing: 0.05em;
365
385
  }
366
386
 
367
387
  .active-name {
368
- font-size: var(--font-md);
369
- font-weight: var(--font-weight-semibold);
388
+ font-size: var(--ui-font-size-md);
389
+ font-weight: var(--ui-font-weight-semibold);
370
390
  color: var(--ui-text-primary);
371
391
  }
372
392
 
373
393
  .button-grid {
374
394
  display: flex;
375
395
  flex-direction: column;
376
- gap: var(--space-4);
396
+ gap: var(--ui-space-4);
377
397
  }
378
398
 
379
399
  .tfm-btn {
380
400
  display: flex;
381
401
  align-items: center;
382
- gap: var(--space-4);
402
+ gap: var(--ui-space-4);
383
403
  width: 100%;
384
- padding: var(--space-6) var(--space-8);
404
+ padding: var(--ui-space-6) var(--ui-space-8);
385
405
  background: var(--ui-surface-low);
386
406
  border: 1px solid var(--ui-border-subtle);
387
- border-radius: var(--radius-md);
407
+ border-radius: var(--ui-radius-md);
388
408
  color: var(--ui-text-secondary);
389
- font-size: var(--font-md);
409
+ font-size: var(--ui-font-size-md);
390
410
  cursor: pointer;
391
- transition: all var(--transition-fast);
411
+ transition: all var(--ui-transition-fast);
392
412
  white-space: nowrap;
393
413
  }
394
414
 
@@ -400,7 +420,7 @@
400
420
  .tfm-btn:hover:not(:disabled) {
401
421
  background: var(--ui-surface);
402
422
  color: var(--ui-text-primary);
403
- border-color: var(--border);
423
+ border-color: var(--ui-border-default);
404
424
  }
405
425
 
406
426
  .tfm-btn:disabled {
@@ -416,7 +436,7 @@
416
436
 
417
437
  .save-row {
418
438
  display: flex;
419
- gap: var(--space-4);
439
+ gap: var(--ui-space-4);
420
440
  }
421
441
 
422
442
  .save-row .save-btn {
@@ -451,24 +471,24 @@
451
471
  .save-as-inline {
452
472
  display: flex;
453
473
  flex-direction: column;
454
- gap: var(--space-4);
474
+ gap: var(--ui-space-4);
455
475
  }
456
476
 
457
477
  .save-as-actions {
458
478
  display: flex;
459
- gap: var(--space-4);
479
+ gap: var(--ui-space-4);
460
480
  justify-content: flex-end;
461
481
  }
462
482
 
463
483
  .save-as-input {
464
484
  flex: 1;
465
485
  min-width: 0;
466
- padding: var(--space-6) var(--space-8);
486
+ padding: var(--ui-space-6) var(--ui-space-8);
467
487
  background: var(--ui-surface-lowest);
468
488
  border: 1px solid var(--ui-border-subtle);
469
- border-radius: var(--radius-md);
489
+ border-radius: var(--ui-radius-md);
470
490
  color: var(--ui-text-primary);
471
- font-size: var(--font-md);
491
+ font-size: var(--ui-font-size-md);
472
492
  outline: none;
473
493
  }
474
494
 
@@ -490,17 +510,17 @@
490
510
  padding: 0;
491
511
  background: var(--ui-surface-low);
492
512
  border: 1px solid var(--ui-border-subtle);
493
- border-radius: var(--radius-md);
513
+ border-radius: var(--ui-radius-md);
494
514
  color: var(--ui-text-secondary);
495
- font-size: var(--font-sm);
515
+ font-size: var(--ui-font-size-sm);
496
516
  cursor: pointer;
497
- transition: all var(--transition-fast);
517
+ transition: all var(--ui-transition-fast);
498
518
  }
499
519
 
500
520
  .inline-btn:hover:not(:disabled) {
501
521
  background: var(--ui-surface);
502
522
  color: var(--ui-text-primary);
503
- border-color: var(--border);
523
+ border-color: var(--ui-border-default);
504
524
  }
505
525
 
506
526
  .inline-btn:disabled {
@@ -521,6 +541,62 @@
521
541
  overflow-y: auto;
522
542
  }
523
543
 
544
+ .load-header {
545
+ display: flex;
546
+ align-items: center;
547
+ gap: 6px;
548
+ padding: 4px 6px;
549
+ border-bottom: 1px solid #3a3a3a;
550
+ position: sticky;
551
+ top: 0;
552
+ background: var(--ui-surface, #1a1a1a);
553
+ z-index: 1;
554
+ }
555
+
556
+ .sort-btn {
557
+ display: inline-flex;
558
+ align-items: center;
559
+ gap: 4px;
560
+ padding: 4px 0;
561
+ background: none;
562
+ border: none;
563
+ color: #888;
564
+ font-size: 11px;
565
+ font-weight: 600;
566
+ text-transform: uppercase;
567
+ letter-spacing: 0.04em;
568
+ cursor: pointer;
569
+ text-align: left;
570
+ }
571
+
572
+ .sort-btn:hover {
573
+ color: #ccc;
574
+ }
575
+
576
+ .sort-btn.active-sort {
577
+ color: #e0e0e0;
578
+ }
579
+
580
+ .sort-btn i {
581
+ font-size: 10px;
582
+ opacity: 0.85;
583
+ }
584
+
585
+ .sort-btn.name-col {
586
+ flex: 1;
587
+ min-width: 0;
588
+ padding-left: 4px;
589
+ }
590
+
591
+ .sort-btn.date-col {
592
+ flex-shrink: 0;
593
+ }
594
+
595
+ .header-spacer {
596
+ flex-shrink: 0;
597
+ width: 24px; /* reserve space matching .file-delete-btn */
598
+ }
599
+
524
600
  .load-item {
525
601
  display: flex;
526
602
  align-items: center;
@@ -565,6 +641,14 @@
565
641
  font-weight: 600;
566
642
  }
567
643
 
644
+ .updated-at {
645
+ flex-shrink: 0;
646
+ font-size: 12px;
647
+ color: #777;
648
+ font-variant-numeric: tabular-nums;
649
+ white-space: nowrap;
650
+ }
651
+
568
652
  .active-badge {
569
653
  flex-shrink: 0;
570
654
  font-size: 12px;
@@ -600,21 +684,21 @@
600
684
 
601
685
  .status-labels {
602
686
  display: flex;
603
- gap: var(--space-8);
604
- padding: 0 var(--space-4);
687
+ gap: var(--ui-space-8);
688
+ padding: 0 var(--ui-space-4);
605
689
  }
606
690
 
607
691
  .status-label {
608
- font-size: var(--font-xs);
692
+ font-size: var(--ui-font-size-xs);
609
693
  letter-spacing: 0.02em;
610
694
  }
611
695
 
612
696
  .status-label.clean {
613
- color: var(--ui-text-muted);
697
+ color: var(--ui-text-secondary);
614
698
  }
615
699
 
616
700
  .status-label.dirty {
617
- color: var(--ui-text-warning, #e6a030);
701
+ color: var(--ui-highlight);
618
702
  }
619
703
 
620
704
  /* ── Production section ── */
@@ -622,28 +706,28 @@
622
706
  .production-section {
623
707
  display: flex;
624
708
  flex-direction: column;
625
- gap: var(--space-4);
626
- padding-top: var(--space-8);
709
+ gap: var(--ui-space-4);
710
+ padding-top: var(--ui-space-8);
627
711
  border-top: 1px solid var(--ui-border-subtle);
628
712
  }
629
713
 
630
714
  .production-header {
631
715
  display: flex;
632
716
  flex-direction: column;
633
- gap: var(--space-2);
634
- padding: 0 var(--space-4);
717
+ gap: var(--ui-space-2);
718
+ padding: 0 var(--ui-space-4);
635
719
  }
636
720
 
637
721
  .production-label {
638
- font-size: var(--font-xs);
639
- color: var(--ui-text-muted);
722
+ font-size: var(--ui-font-size-xs);
723
+ color: var(--ui-text-secondary);
640
724
  text-transform: uppercase;
641
725
  letter-spacing: 0.05em;
642
726
  }
643
727
 
644
728
  .production-name {
645
- font-size: var(--font-md);
646
- font-weight: var(--font-weight-semibold);
729
+ font-size: var(--ui-font-size-md);
730
+ font-weight: var(--ui-font-weight-semibold);
647
731
  color: var(--ui-text-primary);
648
732
  }
649
733
 
@@ -663,16 +747,16 @@
663
747
  .production-update-btn.error { color: var(--ui-text-muted); }
664
748
 
665
749
  .production-match {
666
- font-size: var(--font-xs);
667
- color: var(--ui-text-muted);
668
- padding: 0 var(--space-4);
750
+ font-size: var(--ui-font-size-xs);
751
+ color: var(--ui-text-secondary);
752
+ padding: 0 var(--ui-space-4);
669
753
  letter-spacing: 0.02em;
670
754
  }
671
755
 
672
756
  .production-diff {
673
- font-size: var(--font-xs);
674
- color: var(--ui-text-warning, #e6a030);
675
- padding: 0 var(--space-4);
757
+ font-size: var(--ui-font-size-xs);
758
+ color: var(--ui-highlight);
759
+ padding: 0 var(--ui-space-4);
676
760
  letter-spacing: 0.02em;
677
761
  }
678
762
 
@@ -0,0 +1,108 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+
4
+ export let checked: boolean = false;
5
+ export let disabled: boolean = false;
6
+ export let label: string = '';
7
+
8
+ const dispatch = createEventDispatcher<{ change: boolean }>();
9
+
10
+ function toggle() {
11
+ if (disabled) return;
12
+ checked = !checked;
13
+ dispatch('change', checked);
14
+ }
15
+ </script>
16
+
17
+ <label class="toggle" class:disabled>
18
+ <button
19
+ type="button"
20
+ role="switch"
21
+ aria-checked={checked}
22
+ {disabled}
23
+ class="toggle-track"
24
+ class:on={checked}
25
+ on:click={toggle}
26
+ >
27
+ <span class="toggle-thumb" />
28
+ </button>
29
+ {#if label}
30
+ <span class="toggle-label">{label}</span>
31
+ {/if}
32
+ </label>
33
+
34
+ <style lang="scss">
35
+ .toggle {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: var(--space-8);
39
+ cursor: pointer;
40
+ user-select: none;
41
+
42
+ &.disabled {
43
+ cursor: not-allowed;
44
+
45
+ .toggle-track {
46
+ background: var(--surface-neutral-lower);
47
+ border-color: var(--border-neutral-faint);
48
+ }
49
+
50
+ .toggle-thumb {
51
+ background: var(--text-disabled);
52
+ }
53
+
54
+ .toggle-label {
55
+ color: var(--text-disabled);
56
+ }
57
+ }
58
+ }
59
+
60
+ .toggle-track {
61
+ position: relative;
62
+ width: 2.25rem;
63
+ height: var(--space-20);
64
+ border-radius: var(--radius-2xl);
65
+ border: var(--border-width-1) solid var(--border-neutral);
66
+ background: var(--surface-neutral-low);
67
+ padding: 0;
68
+ cursor: inherit;
69
+ transition: background var(--duration-150), border-color var(--duration-150);
70
+ flex-shrink: 0;
71
+
72
+ &.on {
73
+ background: var(--ui-toggle);
74
+ border-color: var(--ui-toggle);
75
+ }
76
+
77
+ &:hover:not(:disabled) {
78
+ border-color: var(--border-neutral-medium);
79
+ }
80
+
81
+ &:focus-visible {
82
+ outline: var(--border-width-2) solid var(--border-brand);
83
+ outline-offset: var(--space-2);
84
+ }
85
+ }
86
+
87
+ .toggle-thumb {
88
+ position: absolute;
89
+ top: var(--space-2);
90
+ left: var(--space-2);
91
+ width: 0.875rem;
92
+ height: 0.875rem;
93
+ border-radius: var(--radius-full);
94
+ background: var(--text-secondary);
95
+ transition: transform var(--duration-150), background var(--duration-150);
96
+
97
+ .on & {
98
+ transform: translateX(var(--space-16));
99
+ background: var(--text-primary);
100
+ }
101
+ }
102
+
103
+ .toggle-label {
104
+ font-size: var(--font-size-md);
105
+ color: var(--text-secondary);
106
+ line-height: 1;
107
+ }
108
+ </style>