@motion-proto/live-tokens 0.1.0 → 0.3.1

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 (225) hide show
  1. package/README.md +160 -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 +51 -23
  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 +265 -82
  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 -31
  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 +49 -0
  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 -41
  158. package/src/{showcase → ui}/TextTab.svelte +27 -29
  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/pageSource.ts +0 -6
  195. package/src/lib/tokenInit.ts +0 -29
  196. package/src/lib/tokenService.ts +0 -144
  197. package/src/lib/tokenTypes.ts +0 -45
  198. package/src/pages/Admin.svelte +0 -100
  199. package/src/pages/ShowcasePage.svelte +0 -146
  200. package/src/showcase/BackupBrowser.svelte +0 -617
  201. package/src/showcase/ComponentsTab.svelte +0 -107
  202. package/src/showcase/PaletteEditor.svelte +0 -2579
  203. package/src/showcase/PaletteSelector.svelte +0 -627
  204. package/src/showcase/TokenMap.svelte +0 -54
  205. package/src/showcase/VariablesTab.svelte +0 -2657
  206. package/src/showcase/VisualsTab.svelte +0 -233
  207. package/src/showcase/demos/BadgeDemo.svelte +0 -58
  208. package/src/showcase/demos/CardDemo.svelte +0 -52
  209. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -194
  210. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -56
  211. package/src/showcase/demos/DialogDemo.svelte +0 -42
  212. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -27
  213. package/src/showcase/demos/NotificationDemo.svelte +0 -149
  214. package/src/showcase/demos/ProgressBarDemo.svelte +0 -56
  215. package/src/showcase/demos/RadioButtonDemo.svelte +0 -58
  216. package/src/showcase/demos/SectionDividerDemo.svelte +0 -79
  217. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -457
  218. package/src/showcase/demos/TabBarDemo.svelte +0 -60
  219. package/src/showcase/demos/TooltipDemo.svelte +0 -54
  220. package/src/showcase/editor.css +0 -93
  221. package/src/showcase/index.ts +0 -17
  222. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  223. package/src/styles/fonts/Domine/OFL.txt +0 -97
  224. package/src/styles/fonts/Domine/README.txt +0 -66
  225. /package/src/{showcase → ui}/curveEngine.ts +0 -0
@@ -0,0 +1,75 @@
1
+ {
2
+ "updatedAt": "2026-04-17",
3
+ "note": "Curated list of popular Google Fonts. Regenerate via scripts/fetch-google-fonts.ts to pull the full catalog.",
4
+ "fonts": [
5
+ { "family": "Inter", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
6
+ { "family": "Roboto", "category": "sans-serif", "variants": [100, 300, 400, 500, 700, 900] },
7
+ { "family": "Open Sans", "category": "sans-serif", "variants": [300, 400, 500, 600, 700, 800] },
8
+ { "family": "Lato", "category": "sans-serif", "variants": [100, 300, 400, 700, 900] },
9
+ { "family": "Montserrat", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
10
+ { "family": "Poppins", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
11
+ { "family": "Raleway", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
12
+ { "family": "Nunito", "category": "sans-serif", "variants": [200, 300, 400, 500, 600, 700, 800, 900] },
13
+ { "family": "Nunito Sans", "category": "sans-serif", "variants": [200, 300, 400, 600, 700, 800, 900] },
14
+ { "family": "Work Sans", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
15
+ { "family": "Fira Sans", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
16
+ { "family": "Source Sans 3", "category": "sans-serif", "variants": [200, 300, 400, 500, 600, 700, 800, 900] },
17
+ { "family": "DM Sans", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
18
+ { "family": "Manrope", "category": "sans-serif", "variants": [200, 300, 400, 500, 600, 700, 800] },
19
+ { "family": "Space Grotesk", "category": "sans-serif", "variants": [300, 400, 500, 600, 700] },
20
+ { "family": "Plus Jakarta Sans", "category": "sans-serif", "variants": [200, 300, 400, 500, 600, 700, 800] },
21
+ { "family": "Outfit", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
22
+ { "family": "Rubik", "category": "sans-serif", "variants": [300, 400, 500, 600, 700, 800, 900] },
23
+ { "family": "Mulish", "category": "sans-serif", "variants": [200, 300, 400, 500, 600, 700, 800, 900, 1000] },
24
+ { "family": "Figtree", "category": "sans-serif", "variants": [300, 400, 500, 600, 700, 800, 900] },
25
+ { "family": "Ubuntu", "category": "sans-serif", "variants": [300, 400, 500, 700] },
26
+ { "family": "Barlow", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
27
+ { "family": "Karla", "category": "sans-serif", "variants": [200, 300, 400, 500, 600, 700, 800] },
28
+ { "family": "Signika", "category": "sans-serif", "variants": [300, 400, 500, 600, 700] },
29
+ { "family": "Oswald", "category": "sans-serif", "variants": [200, 300, 400, 500, 600, 700] },
30
+ { "family": "PT Sans", "category": "sans-serif", "variants": [400, 700] },
31
+ { "family": "Archivo", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
32
+ { "family": "Hind", "category": "sans-serif", "variants": [300, 400, 500, 600, 700] },
33
+ { "family": "Noto Sans", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
34
+ { "family": "IBM Plex Sans", "category": "sans-serif", "variants": [100, 200, 300, 400, 500, 600, 700] },
35
+ { "family": "Faculty Glyphic", "category": "sans-serif", "variants": [400] },
36
+
37
+ { "family": "Playfair Display", "category": "serif", "variants": [400, 500, 600, 700, 800, 900] },
38
+ { "family": "Merriweather", "category": "serif", "variants": [300, 400, 700, 900] },
39
+ { "family": "Lora", "category": "serif", "variants": [400, 500, 600, 700] },
40
+ { "family": "PT Serif", "category": "serif", "variants": [400, 700] },
41
+ { "family": "Source Serif 4", "category": "serif", "variants": [200, 300, 400, 500, 600, 700, 800, 900] },
42
+ { "family": "Crimson Pro", "category": "serif", "variants": [200, 300, 400, 500, 600, 700, 800, 900] },
43
+ { "family": "Cormorant Garamond", "category": "serif", "variants": [300, 400, 500, 600, 700] },
44
+ { "family": "Libre Baskerville", "category": "serif", "variants": [400, 700] },
45
+ { "family": "EB Garamond", "category": "serif", "variants": [400, 500, 600, 700, 800] },
46
+ { "family": "Domine", "category": "serif", "variants": [400, 500, 600, 700] },
47
+ { "family": "Bitter", "category": "serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
48
+ { "family": "Noto Serif", "category": "serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
49
+ { "family": "Spectral", "category": "serif", "variants": [200, 300, 400, 500, 600, 700, 800] },
50
+ { "family": "Cardo", "category": "serif", "variants": [400, 700] },
51
+ { "family": "Fraunces", "category": "serif", "variants": [100, 200, 300, 400, 500, 600, 700, 800, 900] },
52
+ { "family": "DM Serif Display", "category": "serif", "variants": [400] },
53
+ { "family": "DM Serif Text", "category": "serif", "variants": [400] },
54
+
55
+ { "family": "Fira Code", "category": "monospace", "variants": [300, 400, 500, 600, 700] },
56
+ { "family": "JetBrains Mono", "category": "monospace", "variants": [100, 200, 300, 400, 500, 600, 700, 800] },
57
+ { "family": "Source Code Pro", "category": "monospace", "variants": [200, 300, 400, 500, 600, 700, 800, 900] },
58
+ { "family": "IBM Plex Mono", "category": "monospace", "variants": [100, 200, 300, 400, 500, 600, 700] },
59
+ { "family": "Inconsolata", "category": "monospace", "variants": [200, 300, 400, 500, 600, 700, 800, 900] },
60
+ { "family": "Roboto Mono", "category": "monospace", "variants": [100, 200, 300, 400, 500, 600, 700] },
61
+ { "family": "Space Mono", "category": "monospace", "variants": [400, 700] },
62
+ { "family": "DM Mono", "category": "monospace", "variants": [300, 400, 500] },
63
+
64
+ { "family": "Bebas Neue", "category": "display", "variants": [400] },
65
+ { "family": "Abril Fatface", "category": "display", "variants": [400] },
66
+ { "family": "Archivo Black", "category": "display", "variants": [400] },
67
+ { "family": "Alfa Slab One", "category": "display", "variants": [400] },
68
+ { "family": "Righteous", "category": "display", "variants": [400] },
69
+ { "family": "Anton", "category": "display", "variants": [400] },
70
+ { "family": "Permanent Marker", "category": "handwriting", "variants": [400] },
71
+ { "family": "Caveat", "category": "handwriting", "variants": [400, 500, 600, 700] },
72
+ { "family": "Dancing Script", "category": "handwriting", "variants": [400, 500, 600, 700] },
73
+ { "family": "Pacifico", "category": "handwriting", "variants": [400] }
74
+ ]
75
+ }
@@ -2,6 +2,10 @@
2
2
  import { onMount } from 'svelte';
3
3
  import { columnsVisible } from './columnsOverlay';
4
4
 
5
+ const isDev = import.meta.env.DEV;
6
+ const isInIframe = typeof window !== 'undefined' && window.parent !== window;
7
+ const enabled = isDev && !isInIframe;
8
+
5
9
  let count = 12;
6
10
  let gutter = '';
7
11
  let margin = '';
@@ -33,7 +37,7 @@
33
37
  });
34
38
  </script>
35
39
 
36
- {#if $columnsVisible}
40
+ {#if enabled && $columnsVisible}
37
41
  <div class="columns-overlay" aria-hidden="true">
38
42
  <div class="columns-overlay__inner">
39
43
  {#each Array(count) as _, i}
@@ -70,21 +74,30 @@
70
74
  height: 100%;
71
75
  display: grid;
72
76
  grid-template-columns: repeat(var(--columns-count, 12), 1fr);
77
+ /* Single implicit row that fills the container — without this, row
78
+ tracks default to `auto` and collapse to the content height (the
79
+ number span, ~12px), so the column fill and dashed borders are
80
+ only ~12px tall and look invisible against any backdrop. */
81
+ grid-template-rows: 1fr;
73
82
  gap: var(--columns-gutter);
74
83
  }
75
84
 
76
85
  .columns-overlay__col {
77
- background: rgba(239, 68, 68, 0.04);
78
- border-left: 1px dashed rgba(239, 68, 68, 0.22);
79
- border-right: 1px dashed rgba(239, 68, 68, 0.22);
86
+ /* Fallbacks let the overlay render on any host. --ui-* tokens are
87
+ declared in ui-editor.css which by convention is editor-scoped and
88
+ not loaded globally; without fallbacks the host site sees invisible
89
+ columns. */
90
+ background: var(--ui-overlay-fill, rgba(128, 128, 128, 0.06));
91
+ border-left: 1px dashed var(--ui-overlay-border, rgba(128, 128, 128, 0.32));
92
+ border-right: 1px dashed var(--ui-overlay-border, rgba(128, 128, 128, 0.32));
80
93
  }
81
94
 
82
95
  .columns-overlay__num {
83
96
  display: block;
84
- font-family: var(--font-mono);
97
+ font-family: var(--ui-font-mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace);
85
98
  font-size: 9px;
86
99
  line-height: 1;
87
- color: rgba(255, 255, 255, 0.4);
100
+ color: var(--ui-overlay-num, rgba(128, 128, 128, 0.75));
88
101
  text-align: center;
89
102
  padding-top: 3px;
90
103
  }
@@ -102,7 +115,7 @@
102
115
  -webkit-backdrop-filter: blur(6px);
103
116
  border: 1px solid rgba(255, 255, 255, 0.1);
104
117
  border-radius: 4px;
105
- font-family: var(--font-mono);
118
+ font-family: var(--ui-font-mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace);
106
119
  font-size: 10px;
107
120
  color: rgba(255, 255, 255, 0.75);
108
121
  letter-spacing: 0.02em;
@@ -1,26 +1,66 @@
1
+ <script context="module" lang="ts">
2
+ // __PROJECT_ROOT__ is injected by the themeFileApi Vite plugin as a `define`.
3
+ // Consumers don't need to configure it themselves. We declare it locally so
4
+ // this component's type-check passes in consumer projects that haven't added
5
+ // the ambient global to their tsconfig.
6
+ declare const __PROJECT_ROOT__: string | undefined;
7
+ const INJECTED_PROJECT_ROOT: string =
8
+ typeof __PROJECT_ROOT__ !== 'undefined' ? (__PROJECT_ROOT__ ?? '') : '';
9
+ </script>
10
+
1
11
  <script lang="ts">
2
12
  import { onMount, onDestroy } from 'svelte';
3
- import { route, navigate } from '../router';
4
- import { resolvePageSource } from './pageSource';
13
+ import { fade } from 'svelte/transition';
14
+ import { cubicInOut } from 'svelte/easing';
15
+ import { route, navigate } from './router';
5
16
  import { columnsVisible, toggleColumns } from './columnsOverlay';
6
17
  import { storageKey } from './editorConfig';
7
-
8
- const projectRoot = __PROJECT_ROOT__;
9
-
10
- $: sourceFile = resolvePageSource($route);
11
-
12
- // `open` controls the editor's visible state. The overlay chrome itself
13
- // is always rendered (in dev, on non-admin pages):
14
- // open === true → full panel (docked or floating) with the iframe
15
- // open === false → just the header bar pinned to top-right
16
- export let open: boolean = false;
18
+ import { overlayOpen } from './overlayState';
19
+ import { quietGet, quietSet } from './storage';
20
+ import { postParentRoute } from './parentRouteStore';
21
+ import type { NavLink } from './navLinkTypes';
22
+
23
+ export let open: boolean | undefined = undefined;
24
+ export let editorPath: string = '/editor';
25
+ export let navLinks: NavLink[] = [];
26
+ export let pageSources: Record<string, string> = {};
27
+ export let hidePageSourceOn: string[] = [];
28
+ export let projectRoot: string = INJECTED_PROJECT_ROOT;
29
+
30
+ // Self-gate: only render in dev, and never inside an iframe (the /editor
31
+ // page embeds this same app in an iframe and would otherwise recursively
32
+ // mount another overlay).
33
+ const isDev = import.meta.env.DEV;
34
+ const isInIframe = typeof window !== 'undefined' && window.parent !== window;
35
+ const enabled = isDev && !isInIframe;
36
+
37
+ // Self-manage `open` when the consumer doesn't bind it. When they do, their
38
+ // binding wins and we skip our own persistence.
39
+ const OPEN_KEY = storageKey('overlay-open');
40
+ const consumerControlsOpen = open !== undefined;
41
+ if (!consumerControlsOpen) {
42
+ open = enabled && quietGet(OPEN_KEY) === '1';
43
+ }
44
+ $: if (!consumerControlsOpen && typeof window !== 'undefined') {
45
+ quietSet(OPEN_KEY, open ? '1' : '0');
46
+ }
47
+ $: overlayOpen.set(!!open);
48
+
49
+ // Hide the overlay entirely when the user is already on the editor route
50
+ // (the editor page has its own chrome).
51
+ $: onEditorPath = $route === editorPath;
52
+ $: sourceFile = pageSources[$route];
53
+ $: showSource = !!sourceFile && !!projectRoot && !hidePageSourceOn.includes($route);
17
54
 
18
55
  // Mount the iframe the first time the editor is shown, then keep it mounted
19
56
  // across hide/show cycles so editor state (unsaved slider values, scroll
20
57
  // position, expanded sections) survives.
21
- let hasBeenOpen: boolean = open;
58
+ let hasBeenOpen: boolean = !!open;
22
59
  $: if (open) hasBeenOpen = true;
23
60
 
61
+ let editorFrame: HTMLIFrameElement | undefined;
62
+ $: postParentRoute(editorFrame?.contentWindow, $route);
63
+
24
64
  type Mode = 'docked' | 'floating';
25
65
 
26
66
  const STORAGE_KEY = storageKey('overlay-state');
@@ -41,23 +81,18 @@
41
81
  }
42
82
 
43
83
  function loadState(): OverlayState {
44
- try {
45
- const raw = localStorage.getItem(STORAGE_KEY);
46
- if (raw) {
47
- const parsed = JSON.parse(raw) as Partial<OverlayState>;
48
- return {
49
- mode: parsed.mode === 'floating' ? 'floating' : 'docked',
50
- dockedWidth: typeof parsed.dockedWidth === 'number' ? parsed.dockedWidth : DEFAULT_DOCKED_WIDTH,
51
- floating: {
52
- x: parsed.floating?.x ?? DEFAULT_FLOATING.x,
53
- y: parsed.floating?.y ?? DEFAULT_FLOATING.y,
54
- width: parsed.floating?.width ?? DEFAULT_FLOATING.width,
55
- height: parsed.floating?.height ?? DEFAULT_FLOATING.height,
56
- },
57
- };
58
- }
59
- } catch {
60
- // ignore
84
+ const parsed = quietGet<Partial<OverlayState>>(STORAGE_KEY, { parse: true });
85
+ if (parsed && typeof parsed === 'object') {
86
+ return {
87
+ mode: parsed.mode === 'floating' ? 'floating' : 'docked',
88
+ dockedWidth: typeof parsed.dockedWidth === 'number' ? parsed.dockedWidth : DEFAULT_DOCKED_WIDTH,
89
+ floating: {
90
+ x: parsed.floating?.x ?? DEFAULT_FLOATING.x,
91
+ y: parsed.floating?.y ?? DEFAULT_FLOATING.y,
92
+ width: parsed.floating?.width ?? DEFAULT_FLOATING.width,
93
+ height: parsed.floating?.height ?? DEFAULT_FLOATING.height,
94
+ },
95
+ };
61
96
  }
62
97
  return {
63
98
  mode: 'docked',
@@ -67,11 +102,7 @@
67
102
  }
68
103
 
69
104
  function persist() {
70
- try {
71
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ mode, dockedWidth, floating }));
72
- } catch {
73
- // ignore quota errors
74
- }
105
+ quietSet(STORAGE_KEY, JSON.stringify({ mode, dockedWidth, floating }));
75
106
  }
76
107
 
77
108
  const initial = loadState();
@@ -79,6 +110,20 @@
79
110
  let dockedWidth: number = Math.max(MIN_WIDTH, initial.dockedWidth);
80
111
  let floating = { ...initial.floating };
81
112
 
113
+ // Approximate natural size of the collapsed pill (Editor title + columns toggle).
114
+ // A few pixels of overshoot is fine — the panel has overflow:hidden.
115
+ const COLLAPSED_WIDTH = 184;
116
+ const COLLAPSED_HEIGHT = 38;
117
+
118
+ // Fade timing for the buttons that only render when open (float toggle, spacer,
119
+ // nav links, page-source). The bar's grow/shrink + iframe fade live in CSS vars
120
+ // at the top of the style block.
121
+ const BTN_FADE = { duration: 130, easing: cubicInOut };
122
+
123
+ // Suppress CSS transitions during gestures and mode swaps so dragging doesn't
124
+ // re-animate every frame, and floating↔docked swaps snap cleanly.
125
+ let suppressTransition = false;
126
+
82
127
  // Gesture state — a transparent scrim covers the iframe while any gesture is active
83
128
  // so pointer events land on the panel, not on content inside the iframe.
84
129
  let gesturing: 'drag' | 'resize-left' | 'resize-se' | null = null;
@@ -159,6 +204,7 @@
159
204
  }
160
205
 
161
206
  function toggleMode() {
207
+ suppressTransition = true;
162
208
  mode = mode === 'docked' ? 'floating' : 'docked';
163
209
  // Snap the floating rect back inside the viewport if it drifted off-screen since last use
164
210
  if (mode === 'floating') {
@@ -170,6 +216,7 @@
170
216
  };
171
217
  }
172
218
  persist();
219
+ requestAnimationFrame(() => requestAnimationFrame(() => { suppressTransition = false; }));
173
220
  }
174
221
 
175
222
  function toggleOpen() {
@@ -183,31 +230,25 @@
183
230
  toggleOpen();
184
231
  }
185
232
 
186
- // Messages from the editor iframe:
187
- // lt-overlay-close iframe's Close button (hides the editor)
188
- function handleMessage(ev: MessageEvent) {
189
- if (ev.origin !== window.location.origin) return;
190
- const data = ev.data;
191
- if (!data || typeof data !== 'object') return;
192
- if (data.type === 'lt-overlay-close') {
193
- open = false;
194
- }
233
+ function handleToggleRequest() {
234
+ open = !open;
195
235
  }
196
236
 
197
237
  onMount(() => {
198
- window.addEventListener('message', handleMessage);
238
+ window.addEventListener('lt-overlay-toggle', handleToggleRequest);
199
239
  });
200
240
  onDestroy(() => {
201
- window.removeEventListener('message', handleMessage);
241
+ window.removeEventListener('lt-overlay-toggle', handleToggleRequest);
202
242
  });
203
243
 
204
244
  $: panelStyle = !open
205
- ? 'position: fixed; top: 12px; right: 12px;'
245
+ ? `position: fixed; top: 12px; right: 12px; width: ${COLLAPSED_WIDTH}px; height: ${COLLAPSED_HEIGHT}px;`
206
246
  : mode === 'docked'
207
- ? `position: fixed; top: 0; right: 0; bottom: 0; width: ${dockedWidth}px;`
247
+ ? `position: fixed; top: 0; right: 0; width: ${dockedWidth}px; height: 100vh;`
208
248
  : `position: fixed; top: ${floating.y}px; left: ${floating.x}px; width: ${floating.width}px; height: ${floating.height}px;`;
209
249
  </script>
210
250
 
251
+ {#if enabled && !onEditorPath}
211
252
  <!-- svelte-ignore a11y-no-static-element-interactions -->
212
253
  <div
213
254
  class="lt-overlay"
@@ -216,6 +257,7 @@
216
257
  class:hidden={!open}
217
258
  class:docked={open && mode === 'docked'}
218
259
  class:floating={open && mode === 'floating'}
260
+ class:no-transition={!!gesturing || suppressTransition}
219
261
  >
220
262
  <div
221
263
  class="header"
@@ -223,23 +265,14 @@
223
265
  on:dblclick={handleHeaderDblClick}
224
266
  title={open ? 'Double-click to hide' : 'Double-click to show'}
225
267
  >
226
- {#if open}
227
- <span class="title">Design Editor</span>
228
- <div class="spacer"></div>
229
- {/if}
230
-
231
- {#if open}
232
- <div class="preview-nav">
233
- <button class="hdr-btn nav" class:active={$route === '/'} on:click={() => navigate('/')}>
234
- <i class="fas fa-home"></i>
235
- <span>Site</span>
236
- </button>
237
- <button class="hdr-btn nav" class:active={$route === '/components'} on:click={() => navigate('/components')}>
238
- <i class="fas fa-puzzle-piece"></i>
239
- <span>Components</span>
240
- </button>
241
- </div>
242
- {/if}
268
+ <button
269
+ class="hdr-btn text title"
270
+ on:click={toggleOpen}
271
+ title={open ? 'Hide Editor' : 'Show Editor'}
272
+ >
273
+ <i class="fas {open ? 'fa-chevron-right' : 'fa-chevron-left'}"></i>
274
+ <span>Editor</span>
275
+ </button>
243
276
 
244
277
  <button
245
278
  class="hdr-btn icon"
@@ -250,34 +283,64 @@
250
283
  <i class="fas fa-grip-lines-vertical"></i>
251
284
  </button>
252
285
 
253
- <button class="hdr-btn text" on:click={toggleOpen}>
254
- {open ? 'Hide Editor' : 'Editor'}
255
- </button>
256
-
257
286
  {#if open}
258
- <button class="hdr-btn icon" title={mode === 'docked' ? 'Float' : 'Dock to right'} on:click={toggleMode}>
287
+ <button
288
+ class="hdr-btn icon"
289
+ title={mode === 'docked' ? 'Float' : 'Dock to right'}
290
+ on:click={toggleMode}
291
+ transition:fade={BTN_FADE}
292
+ >
259
293
  <i class={mode === 'docked' ? 'fas fa-up-right-from-square' : 'fas fa-thumbtack'}></i>
260
294
  </button>
261
295
  {/if}
262
296
 
263
- {#if sourceFile}
297
+ {#if open}
298
+ <div class="spacer" transition:fade={BTN_FADE}></div>
299
+ {/if}
300
+
301
+ {#if open && showSource}
264
302
  <a
265
303
  class="hdr-btn text source"
266
304
  href="vscode://file/{projectRoot}/{sourceFile}"
267
305
  title="Open {sourceFile} in VS Code"
306
+ transition:fade={BTN_FADE}
268
307
  >
269
308
  <i class="fas fa-code"></i>
270
- Page Source
309
+ Show Source
271
310
  </a>
272
311
  {/if}
312
+
313
+ {#if open && navLinks.length > 0}
314
+ <div class="seg-group" transition:fade={BTN_FADE}>
315
+ <span class="seg-label">Active Page:</span>
316
+ <div class="seg-bar" role="tablist" aria-label="Underlying page">
317
+ {#each navLinks as link (link.path)}
318
+ <button
319
+ type="button"
320
+ role="tab"
321
+ class="seg-pill"
322
+ class:active={$route === link.path}
323
+ aria-selected={$route === link.path}
324
+ disabled={link.disabled}
325
+ on:click={() => navigate(link.path)}
326
+ >
327
+ {#if link.icon}<i class="fas {link.icon}"></i>{/if}
328
+ <span>{link.label}</span>
329
+ </button>
330
+ {/each}
331
+ </div>
332
+ </div>
333
+ {/if}
273
334
  </div>
274
335
 
275
336
  {#if hasBeenOpen}
276
337
  <div class="frame-wrap">
277
338
  <iframe
278
- src="/admin"
279
- title="Design editor"
339
+ src={editorPath}
340
+ title="Token editor"
280
341
  class="editor-frame"
342
+ bind:this={editorFrame}
343
+ on:load={() => postParentRoute(editorFrame?.contentWindow, $route)}
281
344
  ></iframe>
282
345
  {#if gesturing}
283
346
  <div class="gesture-scrim"></div>
@@ -291,9 +354,26 @@
291
354
  {/if}
292
355
  {/if}
293
356
  </div>
357
+ {/if}
294
358
 
295
359
  <style>
296
360
  .lt-overlay {
361
+ /* Animation knobs. bar = panel grow/shrink, pane = iframe fade.
362
+ open = collapsed to expanded, close = expanded to collapsed. */
363
+ --bar-open-dur: 240ms;
364
+ --bar-open-ease: cubic-bezier(0.65, 0, 0.35, 1);
365
+ --bar-open-delay: 0ms;
366
+ --bar-close-dur: 240ms;
367
+ --bar-close-ease: cubic-bezier(0.65, 0, 0.35, 1);
368
+ --bar-close-delay: 70ms;
369
+
370
+ --pane-open-dur: 140ms;
371
+ --pane-open-ease: cubic-bezier(0.65, 0, 0.35, 1);
372
+ --pane-open-delay: 140ms;
373
+ --pane-close-dur: 80ms;
374
+ --pane-close-ease: cubic-bezier(0.65, 0, 0.35, 1);
375
+ --pane-close-delay: 0ms;
376
+
297
377
  display: flex;
298
378
  flex-direction: column;
299
379
  background: #0a0a0a;
@@ -303,6 +383,12 @@
303
383
  overflow: hidden;
304
384
  font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
305
385
  color: #fff;
386
+ transition:
387
+ width var(--bar-open-dur) var(--bar-open-ease) var(--bar-open-delay),
388
+ height var(--bar-open-dur) var(--bar-open-ease) var(--bar-open-delay),
389
+ top var(--bar-open-dur) var(--bar-open-ease) var(--bar-open-delay),
390
+ right var(--bar-open-dur) var(--bar-open-ease) var(--bar-open-delay),
391
+ border-radius var(--bar-open-dur) var(--bar-open-ease) var(--bar-open-delay);
306
392
  }
307
393
 
308
394
  .lt-overlay.docked {
@@ -315,19 +401,28 @@
315
401
  }
316
402
 
317
403
  /* Hidden state: the editor panel is collapsed to just the header bar,
318
- pinned to the top-right. The iframe stays mounted (for instant show)
319
- but its container is display:none. */
404
+ pinned to the top-right. The iframe stays mounted; its container fades
405
+ to opacity 0 and the panel shrinks around it. */
320
406
  .lt-overlay.hidden {
321
407
  border-radius: 6px;
322
- width: auto;
408
+ transition:
409
+ width var(--bar-close-dur) var(--bar-close-ease) var(--bar-close-delay),
410
+ height var(--bar-close-dur) var(--bar-close-ease) var(--bar-close-delay),
411
+ top var(--bar-close-dur) var(--bar-close-ease) var(--bar-close-delay),
412
+ right var(--bar-close-dur) var(--bar-close-ease) var(--bar-close-delay),
413
+ border-radius var(--bar-close-dur) var(--bar-close-ease) var(--bar-close-delay);
323
414
  }
324
415
 
325
- .lt-overlay.hidden .frame-wrap,
326
416
  .lt-overlay.hidden .resize-left,
327
417
  .lt-overlay.hidden .resize-se {
328
418
  display: none;
329
419
  }
330
420
 
421
+ .lt-overlay.no-transition,
422
+ .lt-overlay.no-transition .frame-wrap {
423
+ transition: none !important;
424
+ }
425
+
331
426
  .header {
332
427
  display: flex;
333
428
  align-items: center;
@@ -349,7 +444,8 @@
349
444
  cursor: move;
350
445
  }
351
446
 
352
- .title {
447
+ .hdr-btn.title {
448
+ gap: 7px;
353
449
  font-size: 13px;
354
450
  font-weight: 600;
355
451
  color: rgba(255, 255, 255, 0.85);
@@ -387,6 +483,20 @@
387
483
  a.hdr-btn.source {
388
484
  gap: 6px;
389
485
  text-decoration: none;
486
+ background: rgba(255, 255, 255, 0.06);
487
+ border-color: rgba(255, 255, 255, 0.45);
488
+ border-radius: 999px;
489
+ padding: 0 14px;
490
+ color: rgba(255, 255, 255, 0.92);
491
+ }
492
+
493
+ a.hdr-btn.source:hover {
494
+ background: rgba(255, 255, 255, 0.14);
495
+ border-color: rgba(255, 255, 255, 0.6);
496
+ }
497
+
498
+ a.hdr-btn.source i {
499
+ color: rgba(255, 255, 255, 0.6);
390
500
  }
391
501
 
392
502
  .hdr-btn.nav {
@@ -408,17 +518,90 @@
408
518
  border-color: rgba(255, 255, 255, 0.18);
409
519
  }
410
520
 
411
- .preview-nav {
412
- display: flex;
413
- gap: 3px;
521
+ .seg-group {
522
+ display: inline-flex;
523
+ align-items: center;
524
+ gap: 8px;
525
+ margin-left: 18px;
414
526
  margin-right: 4px;
415
527
  }
416
528
 
529
+ .seg-label {
530
+ font-size: 11px;
531
+ font-weight: 600;
532
+ letter-spacing: 0.02em;
533
+ color: #fff;
534
+ }
535
+
536
+ .seg-bar {
537
+ display: inline-flex;
538
+ align-items: center;
539
+ padding: 3px;
540
+ background: rgba(0, 0, 0, 0.55);
541
+ border: 1px solid rgba(255, 255, 255, 0.28);
542
+ border-radius: 6px;
543
+ box-shadow:
544
+ inset 0 1px 0 rgba(0, 0, 0, 0.5),
545
+ 0 0 0 1px rgba(0, 0, 0, 0.4);
546
+ }
547
+
548
+ .seg-pill {
549
+ display: inline-flex;
550
+ align-items: center;
551
+ gap: 5px;
552
+ height: 22px;
553
+ padding: 0 9px;
554
+ background: transparent;
555
+ border: 1px solid transparent;
556
+ border-radius: 3px;
557
+ color: rgba(255, 255, 255, 0.6);
558
+ font-family: inherit;
559
+ font-size: 11px;
560
+ font-weight: 500;
561
+ cursor: pointer;
562
+ transition: background 0.1s, color 0.1s, border-color 0.1s;
563
+ }
564
+
565
+ .seg-pill i {
566
+ font-size: 10px;
567
+ opacity: 0.85;
568
+ }
569
+
570
+ .seg-pill:hover:not(:disabled) {
571
+ color: rgba(255, 255, 255, 0.9);
572
+ }
573
+
574
+ .seg-pill:disabled {
575
+ color: rgba(255, 255, 255, 0.28);
576
+ cursor: not-allowed;
577
+ }
578
+
579
+ .seg-pill:disabled i {
580
+ opacity: 0.5;
581
+ }
582
+
583
+ /* Outlined active — quieter than the iframe's filled switcher, so the two
584
+ segmented controls read as siblings, not twins. */
585
+ .seg-pill.active {
586
+ color: #fff;
587
+ border-color: rgba(255, 255, 255, 0.3);
588
+ background: rgba(255, 255, 255, 0.1);
589
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
590
+ }
591
+
417
592
  .frame-wrap {
418
593
  position: relative;
419
594
  flex: 1;
420
595
  min-height: 0;
421
596
  background: #000;
597
+ transition: opacity var(--pane-open-dur) var(--pane-open-ease) var(--pane-open-delay);
598
+ opacity: 1;
599
+ }
600
+
601
+ .lt-overlay.hidden .frame-wrap {
602
+ opacity: 0;
603
+ pointer-events: none;
604
+ transition: opacity var(--pane-close-dur) var(--pane-close-ease) var(--pane-close-delay);
422
605
  }
423
606
 
424
607
  .editor-frame {