@motion-proto/live-tokens 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -154,6 +154,25 @@ var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
154
154
  }
155
155
  };
156
156
 
157
+ // vite-plugin/tokensCssMigrations/migrations/2026-06-03-transform-scale-additions.ts
158
+ var tokensCssMigration_2026_06_03_transformScaleAdditions = {
159
+ id: "2026-06-03-transform-scale-additions",
160
+ description: "Add the --scale-{sm..2xl} transform-multiplier scale",
161
+ apply(css) {
162
+ return ensureScale(css, {
163
+ sectionComment: "Transform-scale multipliers (e.g. hover zoom). 5% per step.",
164
+ anchorPrefixes: ["--blur-", "--shadow-", "--radius-"],
165
+ entries: [
166
+ { name: "--scale-sm", value: "1.05" },
167
+ { name: "--scale-md", value: "1.1" },
168
+ { name: "--scale-lg", value: "1.15" },
169
+ { name: "--scale-xl", value: "1.2" },
170
+ { name: "--scale-2xl", value: "1.25" }
171
+ ]
172
+ });
173
+ }
174
+ };
175
+
157
176
  // vite-plugin/files/dataPaths.ts
158
177
  import fs from "fs";
159
178
  import path from "path";
@@ -203,7 +222,8 @@ function resolveDataDirs(opts = {}) {
203
222
  // vite-plugin/tokensCssMigrations/index.ts
204
223
  var TOKENS_CSS_MIGRATIONS = [
205
224
  tokensCssMigration_2026_05_29_typographyScaleAdditions,
206
- tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup
225
+ tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup,
226
+ tokensCssMigration_2026_06_03_transformScaleAdditions
207
227
  ];
208
228
  function runTokensCssMigrations(css) {
209
229
  let out = css;
@@ -778,10 +778,30 @@ var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
778
778
  }
779
779
  };
780
780
 
781
+ // vite-plugin/tokensCssMigrations/migrations/2026-06-03-transform-scale-additions.ts
782
+ var tokensCssMigration_2026_06_03_transformScaleAdditions = {
783
+ id: "2026-06-03-transform-scale-additions",
784
+ description: "Add the --scale-{sm..2xl} transform-multiplier scale",
785
+ apply(css) {
786
+ return ensureScale(css, {
787
+ sectionComment: "Transform-scale multipliers (e.g. hover zoom). 5% per step.",
788
+ anchorPrefixes: ["--blur-", "--shadow-", "--radius-"],
789
+ entries: [
790
+ { name: "--scale-sm", value: "1.05" },
791
+ { name: "--scale-md", value: "1.1" },
792
+ { name: "--scale-lg", value: "1.15" },
793
+ { name: "--scale-xl", value: "1.2" },
794
+ { name: "--scale-2xl", value: "1.25" }
795
+ ]
796
+ });
797
+ }
798
+ };
799
+
781
800
  // vite-plugin/tokensCssMigrations/index.ts
782
801
  var TOKENS_CSS_MIGRATIONS = [
783
802
  tokensCssMigration_2026_05_29_typographyScaleAdditions,
784
- tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup
803
+ tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup,
804
+ tokensCssMigration_2026_06_03_transformScaleAdditions
785
805
  ];
786
806
  function runTokensCssMigrations(css) {
787
807
  let out = css;
@@ -4,7 +4,7 @@ import {
4
4
  resolveDataDirs,
5
5
  runTokensCssMigrations,
6
6
  validateTokensCss
7
- } from "./chunk-CUC32QJ2.js";
7
+ } from "./chunk-2TW77U3O.js";
8
8
 
9
9
  // vite-plugin/themeFileApi.ts
10
10
  import fs2 from "fs";
@@ -199,6 +199,25 @@ var tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup = {
199
199
  }
200
200
  };
201
201
 
202
+ // vite-plugin/tokensCssMigrations/migrations/2026-06-03-transform-scale-additions.ts
203
+ var tokensCssMigration_2026_06_03_transformScaleAdditions = {
204
+ id: "2026-06-03-transform-scale-additions",
205
+ description: "Add the --scale-{sm..2xl} transform-multiplier scale",
206
+ apply(css) {
207
+ return ensureScale(css, {
208
+ sectionComment: "Transform-scale multipliers (e.g. hover zoom). 5% per step.",
209
+ anchorPrefixes: ["--blur-", "--shadow-", "--radius-"],
210
+ entries: [
211
+ { name: "--scale-sm", value: "1.05" },
212
+ { name: "--scale-md", value: "1.1" },
213
+ { name: "--scale-lg", value: "1.15" },
214
+ { name: "--scale-xl", value: "1.2" },
215
+ { name: "--scale-2xl", value: "1.25" }
216
+ ]
217
+ });
218
+ }
219
+ };
220
+
202
221
  // vite-plugin/files/dataPaths.ts
203
222
  var import_fs = __toESM(require("fs"), 1);
204
223
  var import_path = __toESM(require("path"), 1);
@@ -235,7 +254,8 @@ function readLiveTokensConfig() {
235
254
  // vite-plugin/tokensCssMigrations/index.ts
236
255
  var TOKENS_CSS_MIGRATIONS = [
237
256
  tokensCssMigration_2026_05_29_typographyScaleAdditions,
238
- tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup
257
+ tokensCssMigration_2026_05_29_sectiondividerLegacyAxisCleanup,
258
+ tokensCssMigration_2026_06_03_transformScaleAdditions
239
259
  ];
240
260
  function runTokensCssMigrations(css) {
241
261
  let out = css;
@@ -9,7 +9,7 @@ import {
9
9
  renameToken,
10
10
  runTokensCssMigrations,
11
11
  validateTokensCss
12
- } from "../chunk-CUC32QJ2.js";
12
+ } from "../chunk-2TW77U3O.js";
13
13
  export {
14
14
  TOKENS_CSS_MIGRATIONS,
15
15
  collectDefinedTokens,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
6
6
  "keywords": [
@@ -31,9 +31,7 @@
31
31
  "template",
32
32
  "!**/*.test.ts",
33
33
  "!**/*.spec.ts",
34
- "!**/__tests__/**",
35
- "!src/system/components/Stat.svelte",
36
- "!src/system/components/StatEditor.svelte"
34
+ "!**/__tests__/**"
37
35
  ],
38
36
  "bin": {
39
37
  "live-tokens": "./bin/cli.mjs"
@@ -96,14 +94,17 @@
96
94
  "build:lib": "npm run build:plugin",
97
95
  "deploy:local": "bash scripts/deploy-local.sh",
98
96
  "check:no-style-imports": "node scripts/check-no-style-imports.mjs",
97
+ "check:slot-prose": "node scripts/check-slot-prose.mjs",
99
98
  "check:editor-font-isolation": "node scripts/check-editor-font-isolation.mjs",
100
99
  "check:component-defaults": "node scripts/sync-component-defaults.mjs --check",
101
100
  "check:production-is-default": "node scripts/check-production-is-default.mjs",
101
+ "check:docs-content": "node scripts/sync-docs.mjs --check",
102
102
  "sync:component-defaults": "node scripts/sync-component-defaults.mjs --write",
103
+ "sync:docs": "node scripts/sync-docs.mjs --write",
103
104
  "collapse:manifest": "node scripts/collapse-manifest-to-default.mjs",
104
105
  "check:smoke-install": "bash scripts/smoke-install.sh",
105
106
  "check:smoke-create": "bash scripts/smoke-create.sh",
106
- "prepublishOnly": "npm run check:no-style-imports && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:production-is-default && npm run build:lib && npm run check:smoke-install && npm run check:smoke-create"
107
+ "prepublishOnly": "npm run check:no-style-imports && npm run check:slot-prose && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:production-is-default && npm run check:docs-content && npm run build:lib && npm run check:smoke-install && npm run check:smoke-create"
107
108
  },
108
109
  "peerDependencies": {
109
110
  "@sveltejs/vite-plugin-svelte": "^7.0",
@@ -1,6 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
- import type { Token, TypeGroupConfig } from './scaffolding/types';
3
+ import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
4
 
5
5
  export const component = 'card';
6
6
 
@@ -83,13 +83,34 @@
83
83
  ...buildTypeGroupColorTokens(typeGroups, { component, variants: ['default'] }),
84
84
  ...typeGroupTokens,
85
85
  ];
86
+
87
+ // Global "Use hover" default. The two `*-active` vars flip together: off = resting
88
+ // tokens (no visible hover), on = the `--card-hover-*` tokens. Stored per-component.
89
+ const HOVER_OFF = { border: 'var(--card-default-border)', shadow: 'var(--card-default-shadow)' };
90
+ const HOVER_ON = { border: 'var(--card-hover-border)', shadow: 'var(--card-hover-shadow)' };
91
+ export const intrinsics: IntrinsicSpec[] = [
92
+ {
93
+ key: 'hover-border',
94
+ variants: ['default'],
95
+ variable: () => '--card-hover-border-active',
96
+ values: [HOVER_OFF.border, HOVER_ON.border],
97
+ default: { default: HOVER_OFF.border },
98
+ },
99
+ {
100
+ key: 'hover-shadow',
101
+ variants: ['default'],
102
+ variable: () => '--card-hover-shadow-active',
103
+ values: [HOVER_OFF.shadow, HOVER_ON.shadow],
104
+ default: { default: HOVER_OFF.shadow },
105
+ },
106
+ ];
86
107
  </script>
87
108
 
88
109
  <script lang="ts">
89
110
  import Card from '../../system/components/Card.svelte';
90
111
  import VariantGroup from './scaffolding/VariantGroup.svelte';
91
112
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
92
- import { editorState } from '../core/store/editorStore';
113
+ import { editorState, setComponentAlias } from '../core/store/editorStore';
93
114
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
94
115
  let linked = $derived(computeLinkedBlock(component, linkableContexts, allTokens, $editorState));
95
116
 
@@ -97,7 +118,20 @@
97
118
  Object.entries(states).map(([name, list]) => [name, withLinkedDisabled(list, linked.varSet)]),
98
119
  ) as Record<string, Token[]>);
99
120
 
100
- let hoverEnabled = $state(true);
121
+ let aliases = $derived(
122
+ ($editorState.components[component]?.aliases ?? {}) as Record<string, import('../core/store/editorTypes').CssVarRef>,
123
+ );
124
+ let hoverEnabled = $derived.by(() => {
125
+ const ref = aliases['--card-hover-border-active'];
126
+ const raw = ref?.kind === 'literal' ? ref.value : HOVER_OFF.border;
127
+ return raw === HOVER_ON.border;
128
+ });
129
+
130
+ function setHoverEnabled(checked: boolean) {
131
+ const v = checked ? HOVER_ON : HOVER_OFF;
132
+ setComponentAlias(component, '--card-hover-border-active', { kind: 'literal', value: v.border });
133
+ setComponentAlias(component, '--card-hover-shadow-active', { kind: 'literal', value: v.shadow });
134
+ }
101
135
  </script>
102
136
 
103
137
  <ComponentEditorBase {component} title="Card" description="Generic card with icon, title, and slotted body." tokens={allTokens} {linked}>
@@ -110,17 +144,23 @@
110
144
  >
111
145
  {#snippet stateActions(stateName)}
112
146
  {#if stateName === 'hover'}
113
- <label class="hover-enable">
114
- <input type="checkbox" bind:checked={hoverEnabled} />
115
- <span>Use hover</span>
116
- </label>
147
+ <div class="hover-control">
148
+ <label class="hover-enable">
149
+ <input type="checkbox" checked={hoverEnabled} onchange={(e) => setHoverEnabled(e.currentTarget.checked)} />
150
+ <span>Use hover</span>
151
+ </label>
152
+ <p class="hover-help">
153
+ Checked: every card lifts on hover. Unchecked: hover is off by default and a page turns it on
154
+ per card with the <code>hover</code> prop.
155
+ </p>
156
+ </div>
117
157
  {/if}
118
158
  {/snippet}
119
159
  {#snippet children({ activeState })}
120
- {@const previewClass = activeState === 'hover' ? 'force-hover' : (hoverEnabled ? '' : 'no-hover')}
160
+ {@const previewClass = activeState === 'hover' ? 'force-hover' : ''}
121
161
  <div class="card-demo">
122
162
  <Card title="Card title" class={previewClass}>
123
- <p style="margin: 0;">Slotted body content. Hover the card (or switch the editor to the Hover state) to preview hover styling.</p>
163
+ <div class="content-placeholder">Content Placeholder</div>
124
164
  </Card>
125
165
  </div>
126
166
  {/snippet}
@@ -129,6 +169,21 @@
129
169
 
130
170
  <style>
131
171
  .card-demo {
172
+ min-width: 16rem;
173
+ max-width: 28rem;
174
+ }
175
+ .content-placeholder {
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: center;
179
+ width: 100%;
180
+ min-height: 6rem;
181
+ background: color-mix(in srgb, white 8%, transparent);
182
+ border-radius: var(--ui-radius-sm);
183
+ font-size: var(--ui-font-size-sm);
184
+ color: var(--ui-text-secondary);
185
+ }
186
+ .hover-control {
132
187
  max-width: 28rem;
133
188
  }
134
189
  .hover-enable {
@@ -139,4 +194,12 @@
139
194
  color: var(--ui-text-secondary);
140
195
  cursor: pointer;
141
196
  }
197
+ .hover-help {
198
+ margin: var(--ui-space-4) 0 0;
199
+ font-size: var(--ui-font-size-sm);
200
+ color: var(--ui-text-tertiary);
201
+ }
202
+ .hover-help code {
203
+ font-family: var(--ui-font-mono);
204
+ }
142
205
  </style>
@@ -1,5 +1,5 @@
1
1
  <script module lang="ts">
2
- import type { Token } from './scaffolding/types';
2
+ import type { Token, IntrinsicSpec } from './scaffolding/types';
3
3
 
4
4
  export const component = 'image';
5
5
 
@@ -10,10 +10,23 @@
10
10
  { label: 'border width', groupKey: 'width', variable: '--image-default-border-width' },
11
11
  { label: 'corner radius', groupKey: 'radius', variable: '--image-default-radius' },
12
12
  { label: 'image shadow', groupKey: 'shadow', variable: '--image-default-shadow' },
13
+ { label: 'zoom amount', groupKey: 'scale', variable: '--image-zoom-scale' },
13
14
  ],
14
15
  };
15
16
 
16
17
  export const allTokens: Token[] = Object.values(states).flat();
18
+
19
+ // Global "Use zoom" default. `none` = off; `scale(...)` = every image zooms on hover.
20
+ const ZOOM_ON = 'scale(var(--image-zoom-scale))';
21
+ export const intrinsics: IntrinsicSpec[] = [
22
+ {
23
+ key: 'zoom',
24
+ variants: ['default'],
25
+ variable: () => '--image-zoom-hover',
26
+ values: ['none', ZOOM_ON],
27
+ default: { default: 'none' },
28
+ },
29
+ ];
17
30
  </script>
18
31
 
19
32
  <script lang="ts">
@@ -21,10 +34,53 @@
21
34
  import VariantGroup from './scaffolding/VariantGroup.svelte';
22
35
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
23
36
  import demoImageUrl from '../../system/assets/offering.webp';
37
+ import { editorState, setComponentAlias } from '../core/store/editorStore';
38
+
39
+ let aliases = $derived(
40
+ ($editorState.components[component]?.aliases ?? {}) as Record<string, import('../core/store/editorTypes').CssVarRef>,
41
+ );
42
+ let useZoom = $derived.by(() => {
43
+ const ref = aliases['--image-zoom-hover'];
44
+ const raw = ref?.kind === 'literal' ? ref.value : intrinsics[0].default.default;
45
+ return raw === ZOOM_ON;
46
+ });
47
+
48
+ function setUseZoom(checked: boolean) {
49
+ setComponentAlias(component, '--image-zoom-hover', { kind: 'literal', value: checked ? ZOOM_ON : 'none' });
50
+ }
24
51
  </script>
25
52
 
26
53
  <ComponentEditorBase {component} title="Image" description="Framed image with rounded corners, border, and shadow." tokens={allTokens}>
27
54
  <VariantGroup name="image" title="Image" {states} {component}>
28
- <Image src={demoImageUrl} alt="Demo" variant="banner" />
55
+ {#snippet stateActions()}
56
+ <label class="zoom-enable">
57
+ <input type="checkbox" checked={useZoom} onchange={(e) => setUseZoom(e.currentTarget.checked)} />
58
+ <span>Use zoom on hover</span>
59
+ </label>
60
+ {/snippet}
61
+ <Image src={demoImageUrl} alt="Demo" variant="banner" zoom={useZoom ? undefined : false} />
62
+ <p class="zoom-help">
63
+ Checked: every image zooms its contents on hover. Unchecked: zoom is off by default and a page turns it
64
+ on per image with the <code>zoom</code> prop.
65
+ </p>
29
66
  </VariantGroup>
30
67
  </ComponentEditorBase>
68
+
69
+ <style>
70
+ .zoom-enable {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ gap: var(--ui-space-4);
74
+ font-size: var(--ui-font-size-sm);
75
+ color: var(--ui-text-secondary);
76
+ cursor: pointer;
77
+ }
78
+ .zoom-help {
79
+ margin: var(--ui-space-8) 0 0;
80
+ font-size: var(--ui-font-size-sm);
81
+ color: var(--ui-text-tertiary);
82
+ }
83
+ .zoom-help code {
84
+ font-family: var(--ui-font-mono);
85
+ }
86
+ </style>
@@ -58,8 +58,7 @@
58
58
  {#snippet children()}
59
59
  <div class="panel-demo">
60
60
  <Panel minHeight="6rem">
61
- <span class="demo-chip">Stage content</span>
62
- <span class="demo-chip">Stage content</span>
61
+ <div class="content-placeholder">Content Placeholder</div>
63
62
  </Panel>
64
63
  </div>
65
64
  {/snippet}
@@ -71,9 +70,13 @@
71
70
  width: 100%;
72
71
  max-width: 34rem;
73
72
  }
74
- .demo-chip {
75
- padding: var(--ui-space-4) var(--ui-space-8);
76
- border: 1px solid var(--ui-border);
73
+ .content-placeholder {
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ align-self: stretch;
78
+ width: 100%;
79
+ background: color-mix(in srgb, white 8%, transparent);
77
80
  border-radius: var(--ui-radius-sm);
78
81
  font-size: var(--ui-font-size-sm);
79
82
  color: var(--ui-text-secondary);
@@ -6,11 +6,11 @@ import BadgeEditor, { allTokens as badgeTokens } from './BadgeEditor.svelte';
6
6
  import CalloutEditor, { allTokens as calloutTokens } from './CalloutEditor.svelte';
7
7
  import CornerBadgeEditor, { allTokens as cornerBadgeTokens } from './CornerBadgeEditor.svelte';
8
8
  import ButtonEditor, { allTokens as buttonTokens } from './ButtonEditor.svelte';
9
- import CardEditor, { allTokens as cardTokens } from './CardEditor.svelte';
9
+ import CardEditor, { allTokens as cardTokens, intrinsics as cardIntrinsics } from './CardEditor.svelte';
10
10
  import CodeSnippetEditor, { allTokens as codeSnippetTokens } from './CodeSnippetEditor.svelte';
11
11
  import CollapsibleSectionEditor, { allTokens as collapsibleSectionTokens } from './CollapsibleSectionEditor.svelte';
12
12
  import DialogEditor, { allTokens as dialogTokens } from './DialogEditor.svelte';
13
- import ImageEditor, { allTokens as imageTokens } from './ImageEditor.svelte';
13
+ import ImageEditor, { allTokens as imageTokens, intrinsics as imageIntrinsics } from './ImageEditor.svelte';
14
14
  import ImageLightboxEditor, { allTokens as imageLightboxTokens } from './ImageLightboxEditor.svelte';
15
15
  import InlineEditActionsEditor, { allTokens as inlineEditActionsTokens } from './InlineEditActionsEditor.svelte';
16
16
  import InputEditor, { allTokens as inputTokens } from './InputEditor.svelte';
@@ -142,6 +142,7 @@ const builtInRegistry: Readonly<Record<BuiltInComponentId, RegistryEntry>> = Obj
142
142
  sourceFile: 'src/system/components/Card.svelte',
143
143
  editorComponent: CardEditor,
144
144
  schema: cardTokens,
145
+ intrinsics: cardIntrinsics,
145
146
  origin: 'system',
146
147
  },
147
148
  badge: {
@@ -187,6 +188,7 @@ const builtInRegistry: Readonly<Record<BuiltInComponentId, RegistryEntry>> = Obj
187
188
  sourceFile: 'src/system/components/Image.svelte',
188
189
  editorComponent: ImageEditor,
189
190
  schema: imageTokens,
191
+ intrinsics: imageIntrinsics,
190
192
  origin: 'system',
191
193
  },
192
194
  imagelightbox: {
@@ -9,7 +9,7 @@
9
9
  import UIPaddingSelector from '../../ui/UIPaddingSelector.svelte';
10
10
  import UILetterSpacingSelector from '../../ui/UILetterSpacingSelector.svelte';
11
11
  import UIEasingSelector from '../../ui/UIEasingSelector.svelte';
12
- import { BLUR, BORDER_WIDTH, DOT_SIZE, DURATION, RADIUS, SHADOW, DIVIDER_HEIGHT, DIVIDER_INSET } from '../../ui/variantScales';
12
+ import { BLUR, BORDER_WIDTH, DOT_SIZE, DURATION, RADIUS, SCALE, SHADOW, DIVIDER_HEIGHT, DIVIDER_INSET } from '../../ui/variantScales';
13
13
  import {
14
14
  editorState,
15
15
  getComponentPropertySiblings,
@@ -31,6 +31,7 @@
31
31
  | 'divider-inset'
32
32
  | 'dot-size'
33
33
  | 'blur'
34
+ | 'scale'
34
35
  | 'shadow'
35
36
  | 'font-family'
36
37
  | 'font-weight'
@@ -99,6 +100,7 @@
99
100
  { kind: 'divider-inset', matches: (v) => v.endsWith('-divider-inset') },
100
101
  { kind: 'dot-size', matches: (v) => v.endsWith('-dot-size') },
101
102
  { kind: 'blur', matches: (v) => v.endsWith('-blur') || v.startsWith('--blur-') },
103
+ { kind: 'scale', matches: (v) => v.endsWith('-scale') || v.startsWith('--scale-') },
102
104
  { kind: 'shadow', matches: (v) => v.endsWith('-shadow') || v.startsWith('--shadow-') },
103
105
  { kind: 'padding', matches: (v) => v.endsWith('-padding') || v.endsWith('-margin') },
104
106
  { kind: 'gap', matches: (v) => v.endsWith('-gap') },
@@ -134,6 +136,7 @@
134
136
  'duration',
135
137
  'easing',
136
138
  'blur',
139
+ 'scale',
137
140
  'shadow',
138
141
  'text-color',
139
142
  'surface',
@@ -205,6 +208,7 @@
205
208
  'duration': { component: UIVariantSelector, extra: () => ({ ...DURATION }) },
206
209
  'easing': { component: UIEasingSelector },
207
210
  'blur': { component: UIVariantSelector, extra: () => ({ ...BLUR }) },
211
+ 'scale': { component: UIVariantSelector, extra: () => ({ ...SCALE }) },
208
212
  'shadow': { component: UIVariantSelector, extra: () => ({ ...SHADOW }) },
209
213
  'surface': { component: UIPaletteSelector },
210
214
  'border': { component: UIPaletteSelector },
@@ -234,6 +238,7 @@
234
238
  'duration',
235
239
  'easing',
236
240
  'blur',
241
+ 'scale',
237
242
  'shadow',
238
243
  'surface',
239
244
  'border-width',
@@ -804,15 +804,20 @@
804
804
  gap: var(--ui-space-8);
805
805
  }
806
806
 
807
+ /* Baseline (not center) so a tall stateActions block — e.g. a checkbox with a
808
+ wrapping help paragraph — keeps the part strip and its first line pinned at
809
+ the top and grows downward, pushing Properties down rather than recentering
810
+ the strip and shifting it on every state switch. */
807
811
  .tabs-selectors {
808
812
  display: flex;
809
- align-items: center;
813
+ align-items: baseline;
810
814
  gap: var(--ui-space-12);
811
815
  }
812
816
 
813
817
  /* Sub-strip sits flush under the parts strip when a part has interaction states.
814
818
  The eyebrow label distinguishes it from the parts row above. */
815
819
  .tabs-selectors.substrip {
820
+ align-items: center;
816
821
  margin-top: var(--ui-space-6);
817
822
  }
818
823
  .tabs-selectors.substrip .state-eyebrow {
@@ -1,3 +1,5 @@
1
+ import { docContent } from './content.generated';
2
+
1
3
  export interface Chapter {
2
4
  id: string;
3
5
  title: string;
@@ -17,21 +19,17 @@ export const chapters: Chapter[] = [
17
19
 
18
20
  export const chapterIds = chapters.map((c) => c.id);
19
21
 
20
- /* Vite resolves this glob at build time. The `?raw` query loads each .md
21
- file as a string, so the markdown lives next to the runtime page module
22
- and reloads on edit via HMR. */
23
- const docModules = import.meta.glob<string>(
24
- './content/*.md',
25
- { query: '?raw', import: 'default' },
26
- );
27
-
22
+ /* Bodies come from the generated module, NOT `import.meta.glob('./content/*.md',
23
+ '?raw')`: that glob is a Vite compile-time transform, and esbuild's
24
+ optimizeDeps doesn't expand it inside a pre-bundled node_modules dep, so the
25
+ guide threw "Chapter not found" in tarball consumers. Edit ./content/*.md
26
+ then run `npm run sync:docs` to regenerate. */
28
27
  export async function loadChapter(id: string): Promise<string> {
29
- const key = `./content/${id}.md`;
30
- const loader = docModules[key];
31
- if (!loader) {
32
- throw new Error(`Chapter not found: ${id} (expected at ${key}).`);
28
+ const md = docContent[id];
29
+ if (md === undefined) {
30
+ throw new Error(`Chapter not found: ${id}`);
33
31
  }
34
- return loader();
32
+ return md;
35
33
  }
36
34
 
37
35
  export function chapterNeighbours(id: string): { prev: Chapter | null; next: Chapter | null } {
@@ -0,0 +1,10 @@
1
+ // AUTOGENERATED by scripts/sync-docs.mjs — do not edit by hand.
2
+ // Source of truth: src/editor/docs/content/*.md · regenerate: npm run sync:docs
3
+
4
+ export const docContent: Record<string, string> = {
5
+ "01-overview": "# Overview\n\nLive Tokens is a design system for building Svelte microsites quickly. You\nstyle your site by editing tokens and components in a live editor. When it looks right, you save the manifest and ship it.\n\n## How it works\n\n- The editor runs in your dev server, on top of your real pages. You style in\n context, not in a separate sandbox.\n- Every change updates a CSS variable, so the page repaints instantly. No\n reload, no build step.\n- Saving writes a small JSON file into your project. Shipping bakes your chosen\n theme into a plain CSS file that the build bundles.\n- The editor is dev-only. Production ships plain CSS variables and the\n components you used, nothing else.\n\n## What you can edit\n\n- **Tokens**: the design-system primitives, colour palettes, type, spacing,\n radius, shadow, and gradients, that apply across your whole site.\n- **Components**: the package ships about 25 editable components (Button, Card,\n Dialog, Table, and more).You style components by changing the tokens assigned to each property.\n\n## Where to go next\n\n- **[Getting started](getting-started.md)**: scaffold a project and make your\n first edit.\n- **[Editing tokens](editing-tokens.md)**: a tour of the editor.\n- **[Themes](themes-workflow.md)**: save, switch, and ship.\n- **[Creating components](creating-components.md)**: make your own components\n editable.\n",
6
+ "creating-components": "# Creating components\n\nThe package ships about 25 editable components. When you need one it doesn't\nhave, you can make your own Svelte component editable, so anyone using the\neditor can re-point its colours, type, and spacing without touching code.\n\nThe simplest way is to ask Claude. The package bundles a Claude Code skill that\nknows the conventions, writes the files, and checks the result for you.\n\n## Install the skills\n\n```bash\nnpx @motion-proto/live-tokens setup-claude\n```\n\nThis copies the bundled skills into your project's `.claude/skills/`. Once\nthey're there, Claude Code picks them up automatically.\n\n## Ask for a component\n\nDescribe what you want in plain English. Phrases like these trigger the skill:\n\n- \"Add a Toggle component to live-tokens\"\n- \"Make this Svelte component editable in the live-tokens editor\"\n- \"Create a Stat component with a value and a label\"\n\nClaude asks any clarifying questions it needs (which variants, which states,\nwhich parts), then writes the component, registers it with the editor, and runs\nits verification checklist. When it finishes, open `/components` to see your new\ncomponent in the editor and confirm everything works.\n\n## What you get\n\n- A runtime component whose editable properties default to your theme tokens.\n- An editor entry that appears under **Custom** in the `/components` view.\n- The naming and wiring handled for you, so the component fits the system.\n\nAdvanced authors who want to write a component by hand can read the naming and\nstate-model conventions shipped in the package\n(`src/system/styles/CONVENTIONS.md` and the skill's own `SKILL.md`).\n",
7
+ "editing-tokens": "# Editing tokens\n\nA tour of the editor. The page behind it repaints on every change; saving\nwrites a theme file you can reload later.\n\nThe editor has two views:\n\n- **Tokens**: the design-system primitives (colour, type, spacing, and so on).\n They apply everywhere your site uses them.\n- **Components**: per-component editors. Re-Assign what tokens a component uses\n without changing the underlying system.\n\nThis page covers **Tokens**. For components, see\n[Creating components](creating-components.md).\n\n## Palettes\n\nMost colour work happens here. Each palette (Brand, Accent, Neutral, Canvas,\nSuccess, Warning, Info, Danger, and a few more) has:\n\n- **Base colour.** Pick a hex; the palette derives an 11-step ramp (100 to 950)\n from it.\n- **Curves.** Two curves shape how lightness and saturation fall off across the\n ramp. Drag the handles to bias it darker, lighter, or more saturated.\n- **Overrides.** Lock a single step to a hand-picked hex when the curve doesn't\n land where you want.\n\nEditing a palette base ripples through every colour that depends on it, in real\ntime. Colours use OKLCH, so the ramp stays perceptually even across hues\nwithout muddy mid-tones.\n\n## Type\n\n- **Fonts.** Add sources from Google Fonts, Adobe (Typekit), a CSS URL, or an\n inline `@font-face`. The font loads in the page as soon as you add it.\n- **Stacks.** Named font cascades you reference by token, such as a display\n stack and a body stack.\n- **Sizes and weights.** A t-shirt scale (xs, sm, md, lg, xl, 2xl…) for size and\n a numeric scale (100 to 900) for weight.\n\n## Spacing, radius, shadow\n\nNumeric scales with a slider per step.\n\n- **Spacing**: the padding, gap, and margin scale.\n- **Radius**: none through full.\n- **Shadow**: colour, offset, blur, spread, and opacity per step, with stacked\n shadows supported.\n\nChange a step and every element using it repaints.\n\n## Overlays and gradients\n\n- **Overlays** are translucent tints layered over surfaces, like the subtle\n tint a card gets on hover. Set a colour and opacity per state.\n- **Gradients** are reusable gradient tokens with a stop list and direction, for\n hero panels and accent backgrounds.\n\n## Columns\n\nThe page-grid overlay. Set column count, gutter, and outer margin, and toggle\nthe visual guide with `Cmd/Ctrl+G`. Pages built on the column system reflow\nlive.\n\n## Saving\n\nThe editor saves to your browser continuously, so work survives a reload\nmid-edit. **Save** is a separate step: it writes a named theme file under\n`src/live-tokens/data/themes/`.\n\nThe header gives you undo/redo (`Cmd/Ctrl+Z`, `Cmd/Ctrl+Shift+Z`) and a file\nmenu for New, Save, Save as, Switch, and Delete. You can keep many themes side\nby side; one is active at a time. See [Themes](themes-workflow.md) for the full\nlifecycle.\n",
8
+ "getting-started": "# Getting started\n\nScaffold a live token site in a moments. You need Node 20 or later, a\npackage manager (npm, pnpm, or yarn), and a browser. Open claude code in your repo and start building.\n\n## Scaffold a new app\n\n```bash\nnpm create @motion-proto/live-tokens@latest my-app\ncd my-app\nnpm install\nnpm run dev\n```\n\nOpen the URL Vite prints (usually `http://localhost:5173`). You get a\none-page Svelte + Vite app that depends on the published package, with the\neditor wired up and the full component set ready to import.\n\n`npx @motion-proto/live-tokens create my-app` runs the same scaffold without\nthe initialiser package.\n\n### What the scaffold gives you\n\nEvery editable file lives under `src/` and is committed, so `npm install` and\nversion upgrades never touch your styles. The package code stays in\n`node_modules`.\n\n| Path | What it is |\n|------|------------|\n| `src/pages/Home.svelte` | The starter page. Replace it with your own content. |\n| `src/App.svelte` | Your routes. `<LiveTokensRouter>` adds the dev-only `/editor`, `/components`, and `/docs` routes. |\n| `src/system/styles/tokens.css` | Your base token vocabulary, hand-authored. |\n| `src/styles/site.css` | Themed page typography, yours to edit. |\n\n## Your first edit\n\n1. Run `npm run dev` and open the home page.\n2. Click **Open Token Editor**, or visit `/editor`. The editor opens beside\n the page.\n3. Open **Palettes**, pick **Brand**, and change the base hex. The page\n repaints as you type.\n4. Open the file menu and choose **Save as**. A theme appears as JSON under\n `src/live-tokens/data/themes/`.\n5. Reload. Your saved theme is the active theme, so the page returns as you\n left it.\n\n## What you just changed\n\nEvery edit sets a CSS custom property on `:root`. Your components read those\nproperties through `var(--...)`. There is no token build step and no\npreprocessor rewriting your code: the page renders against plain CSS variables\nthe editor swaps live.\n\nTo ship, promote a theme to production in the editor. That bakes the theme's\nvariables into `src/live-tokens/data/tokens.generated.css`, which your build\nbundles alongside `tokens.css`. The editor itself never reaches production.\n\nAlready have a Svelte 5 + Vite app? The\n[README](https://github.com/motionproto/live-tokens#readme) covers installing\ninto an existing project.\n\n## Where to go next\n\n- **[Editing tokens](editing-tokens.md)**: a tour of the editor.\n- **[Themes](themes-workflow.md)**: save, switch, and ship.\n- **[Creating components](creating-components.md)**: make your own component\n editable.\n",
9
+ "themes-workflow": "# Themes\n\nSave your work, switch between themes, and ship one to production.\n\n## How themes work\n\n- **Your live edits** are what the page shows right now. They save to your\n browser automatically and survive a reload, but they are not yet a file.\n- **A saved theme** is a named JSON file in `src/live-tokens/data/themes/`. You\n create one with **Save as**.\n- **The active theme** is the saved theme the page loads at startup. Exactly one\n at a time.\n- **The production theme** is the one that ships. Promoting sets it.\n\n## Saving\n\nIn the editor header:\n\n- **Save** updates the current theme.\n- **Save as** names a new theme. Use it for your first save and for forking.\n\nNames are tidied to lowercase with underscores, so \"My Brand!\" becomes\n`my_brand`. There is a built-in `default` theme you can always return to; the\neditor never overwrites it.\n\n## Switching\n\nThe file menu lists every saved theme. Pick one to make it active; the page\nreloads with it applied. Your current edits are saved to the previous theme\nfirst, so you don't lose work.\n\n## Shipping\n\n**Promote to production** is the \"ship it\" step. It bakes the theme's variables\ninto `src/live-tokens/data/tokens.generated.css`, which your build bundles\nalongside `tokens.css`. Fonts regenerate to match.\n\nProduction builds (`npm run build`) ship only that plain CSS and your\ncomponents. No editor, no JSON loading, no runtime indirection. If you save\nwhile the production theme is active, the generated CSS updates immediately,\nwith no separate promote step.\n\n## Manifests\n\nA **manifest** bundles one theme plus a config for each component into a single\nnamed set. Useful when you run several brands and want each to apply its theme\nand component tweaks in one move. There is a protected default and an active\nmanifest; applying one swaps everything at once.\n\n## Keeping your work safe\n\nEverything under `src/live-tokens/data/` is plain JSON, so commit it. Themes\nshow up as readable diffs you can review per branch. There are no automatic\nbackups: git is your safety net. To experiment freely, **Save as** a new name\nfirst, then edit.\n\n## Where to go next\n\n- **[Creating components](creating-components.md)**: make your own components\n editable in the same editor.\n",
10
+ };
@@ -33,6 +33,18 @@ export const BLUR: VariantScaleEntry = {
33
33
  ],
34
34
  };
35
35
 
36
+ /** Transform-scale multipliers (e.g. image zoom-on-hover). 5% per step. */
37
+ export const SCALE: VariantScaleEntry = {
38
+ varPrefix: '--scale-',
39
+ options: [
40
+ { key: 'sm', label: 'Small', value: '1.05' },
41
+ { key: 'md', label: 'Medium', value: '1.1' },
42
+ { key: 'lg', label: 'Large', value: '1.15' },
43
+ { key: 'xl', label: 'X-Large', value: '1.2' },
44
+ { key: '2xl', label: '2X-Large', value: '1.25' },
45
+ ],
46
+ };
47
+
36
48
  export const BORDER_WIDTH: VariantScaleEntry = {
37
49
  varPrefix: '--border-width-',
38
50
  options: [
@@ -4,6 +4,11 @@
4
4
  iconColor?: string;
5
5
  title?: string;
6
6
  size?: 'default' | 'compact';
7
+ /** false → the card stops pinning body typography so the consumer fully owns slotted content's styling. */
8
+ prose?: boolean;
9
+ /** Apply hover styling on pointer-over. `undefined` inherits the editor's global "Use hover"
10
+ default; `true`/`false` force this instance on/off. */
11
+ hover?: boolean | undefined;
7
12
  class?: string;
8
13
  children?: import('svelte').Snippet;
9
14
  }
@@ -13,16 +18,27 @@
13
18
  iconColor = 'var(--text-secondary)',
14
19
  title = '',
15
20
  size = 'default',
21
+ prose = true,
22
+ hover = undefined,
16
23
  class: className = '',
17
24
  children
18
25
  }: Props = $props();
19
-
26
+
27
+ // Per-instance override of the global hover intrinsic; undefined leaves :root in charge.
28
+ let hoverBorder = $derived(
29
+ hover === undefined ? undefined : hover ? 'var(--card-hover-border)' : 'var(--card-default-border)',
30
+ );
31
+ let hoverShadow = $derived(
32
+ hover === undefined ? undefined : hover ? 'var(--card-hover-shadow)' : 'var(--card-default-shadow)',
33
+ );
20
34
  </script>
21
35
 
22
36
  <div
23
37
  class="card {className}"
24
38
  class:compact={size === 'compact'}
25
- style="--card-color: {iconColor};"
39
+ style:--card-color={iconColor}
40
+ style:--card-hover-border-active={hoverBorder}
41
+ style:--card-hover-shadow-active={hoverShadow}
26
42
  >
27
43
  {#if icon || title}
28
44
  <div class="card-header">
@@ -34,13 +50,14 @@
34
50
  {/if}
35
51
  </div>
36
52
  {/if}
37
- <div class="card-body">
53
+ <div class="card-body" class:prose>
38
54
  {@render children?.()}
39
55
  </div>
40
56
  </div>
41
57
 
42
58
  <style lang="scss">
43
59
  @use '../styles/padding' as *;
60
+ @use '../styles/slot-prose' as *;
44
61
 
45
62
  :global(:root) {
46
63
  --card-default-surface: color-mix(in srgb, var(--surface-neutral-lower) 70%, transparent);
@@ -78,6 +95,12 @@
78
95
 
79
96
  --card-hover-border: var(--border-neutral-strong);
80
97
  --card-hover-shadow: var(--shadow-md);
98
+
99
+ /* Global "Use hover" intrinsic: the active hover values default to the resting
100
+ tokens (hover off). The editor checkbox / a per-instance `hover` prop swap
101
+ these to the `--card-hover-*` tokens to turn hover on. */
102
+ --card-hover-border-active: var(--card-default-border);
103
+ --card-hover-shadow-active: var(--card-default-shadow);
81
104
  }
82
105
 
83
106
  .card {
@@ -92,7 +115,12 @@
92
115
  overflow: hidden;
93
116
  }
94
117
 
95
- .card:not(.no-hover):hover,
118
+ .card:hover {
119
+ border-color: var(--card-hover-border-active);
120
+ box-shadow: var(--card-hover-shadow-active);
121
+ }
122
+
123
+ /* Editor preview hook: paint hover tokens directly, ignoring the on/off gate. */
96
124
  .card.force-hover {
97
125
  border-color: var(--card-hover-border);
98
126
  box-shadow: var(--card-hover-shadow);
@@ -139,35 +167,8 @@
139
167
  font-weight: var(--card-default-body-font-weight);
140
168
  line-height: var(--card-default-body-line-height);
141
169
  @include themed-padding(--card-default-body-padding);
142
- }
143
-
144
- /* Slot content inherits the card's body typography so consumer-side global
145
- element rules (e.g. site.css `p { font-family: serif }`) don't override
146
- the card-body-font-family alias. */
147
- .card-body :global(p),
148
- .card-body :global(ul),
149
- .card-body :global(ol),
150
- .card-body :global(li) {
151
- font: inherit;
152
- color: inherit;
153
- }
154
-
155
- .card-body :global(p) {
156
- margin: 0 0 var(--space-12);
157
- }
158
-
159
- .card-body :global(p:last-child) {
160
- margin-bottom: 0;
161
- }
162
-
163
- .card-body :global(ul),
164
- .card-body :global(ol) {
165
- margin: var(--space-12) 0;
166
- padding-left: var(--space-24);
167
- }
168
170
 
169
- .card-body :global(li) {
170
- margin-bottom: var(--space-4);
171
+ @include slot-prose;
171
172
  }
172
173
 
173
174
  .card.compact .card-body {
@@ -6,6 +6,8 @@
6
6
  expanded?: boolean;
7
7
  href?: string | undefined;
8
8
  variant?: 'chromeless' | 'divider' | 'container';
9
+ /** false → the section stops pinning body typography so the consumer fully owns slotted content's styling. */
10
+ prose?: boolean;
9
11
  class?: string;
10
12
  /** Toggle callback. Preferred over `on:toggle` from 0.5.0 onward. */
11
13
  ontoggle?: () => void;
@@ -18,6 +20,7 @@
18
20
  expanded = false,
19
21
  href = undefined,
20
22
  variant = 'container',
23
+ prose = true,
21
24
  class: className = '',
22
25
  ontoggle,
23
26
  summary,
@@ -67,7 +70,7 @@
67
70
  </div>
68
71
  {/if}
69
72
  {#if expanded && children}
70
- <div class="section-content">
73
+ <div class="section-content" class:prose>
71
74
  {@render children?.()}
72
75
  </div>
73
76
  {/if}
@@ -75,6 +78,7 @@
75
78
 
76
79
  <style lang="scss">
77
80
  @use '../styles/padding' as *;
81
+ @use '../styles/slot-prose' as *;
78
82
 
79
83
  :global(:root) {
80
84
  /* Chromeless — default */
@@ -285,24 +289,7 @@
285
289
  color: var(--text-secondary);
286
290
  font-size: var(--font-size-md);
287
291
  line-height: var(--line-height-md);
288
- }
289
-
290
- /* Slot content inherits the section's body typography so consumer-side
291
- global element rules (e.g. site.css `p { font-family: serif }`) don't
292
- override the collapsible's intended type styling. */
293
- .section-content :global(p),
294
- .section-content :global(ul),
295
- .section-content :global(ol),
296
- .section-content :global(li) {
297
- font: inherit;
298
- color: inherit;
299
- }
300
-
301
- .section-content :global(p) {
302
- margin: 0 0 var(--space-12);
303
- }
304
292
 
305
- .section-content :global(p:last-child) {
306
- margin-bottom: 0;
293
+ @include slot-prose;
307
294
  }
308
295
  </style>
@@ -4,13 +4,17 @@
4
4
  alt: string;
5
5
  variant?: 'default' | 'banner' | 'medium' | 'compact';
6
6
  height?: string | undefined;
7
+ /** Zoom the contents on hover (frame stays fixed). `undefined` inherits the editor's
8
+ global "Use zoom" default; `true`/`false` force this instance on/off. */
9
+ zoom?: boolean | undefined;
7
10
  }
8
11
 
9
12
  let {
10
13
  src,
11
14
  alt,
12
15
  variant = 'default',
13
- height = undefined
16
+ height = undefined,
17
+ zoom = undefined
14
18
  }: Props = $props();
15
19
 
16
20
  const variantHeights: Record<string, string | undefined> = {
@@ -21,9 +25,14 @@
21
25
  };
22
26
 
23
27
  let resolvedHeight = $derived(height ?? variantHeights[variant]);
28
+
29
+ // Per-instance override of the global zoom intrinsic; undefined leaves :root in charge.
30
+ let zoomOverride = $derived(
31
+ zoom === undefined ? undefined : zoom ? 'scale(var(--image-zoom-scale))' : 'none',
32
+ );
24
33
  </script>
25
34
 
26
- <div class="image" style:height={resolvedHeight}>
35
+ <div class="image" style:height={resolvedHeight} style:--image-zoom-hover={zoomOverride}>
27
36
  <img {src} {alt} />
28
37
  </div>
29
38
 
@@ -33,6 +42,9 @@
33
42
  --image-default-border: var(--border-neutral);
34
43
  --image-default-border-width: var(--border-width-1);
35
44
  --image-default-shadow: var(--shadow-md);
45
+ --image-zoom-scale: var(--scale-sm);
46
+ /* Global "Use zoom" intrinsic: `none` (off) or `scale(var(--image-zoom-scale))` (on). */
47
+ --image-zoom-hover: none;
36
48
  }
37
49
 
38
50
  .image {
@@ -48,5 +60,11 @@
48
60
  display: block;
49
61
  object-fit: cover;
50
62
  object-position: center;
63
+ transform-origin: center;
64
+ transition: transform var(--duration-300) var(--ease-out-cubic);
65
+ }
66
+
67
+ .image:hover img {
68
+ transform: var(--image-zoom-hover);
51
69
  }
52
70
  </style>
@@ -24,7 +24,7 @@
24
24
 
25
25
  --panel-stage-surface: linear-gradient(0deg, color-mix(in srgb, var(--surface-neutral-low) 40%, transparent) 0.5%, color-mix(in srgb, var(--surface-neutral-lowest) 75%, transparent) 100%);
26
26
  --panel-stage-padding: var(--space-16);
27
- --panel-stage-inline-padding: var(--space-32);
27
+ --panel-stage-inline-padding: var(--space-16);
28
28
  --panel-stage-gap: var(--space-16);
29
29
  }
30
30
 
@@ -11,7 +11,7 @@ Each file plays a distinct role. Don't merge them without understanding why they
11
11
  | `tokens.css` | Themed pages | Defines `--color-*`, `--surface-*`, `--text-*`, `--space-*`, … | `main.ts` | **Runtime-edited.** The token editor rewrites this file via the `themeFileApi` Vite plugin. Starter content; consumers replace at will. |
12
12
  | `ui-editor.css` | Editor chrome only | Defines `--ui-*` (opaque grayscale, system fonts) | JS-imported by `Editor.svelte` and `ComponentEditorPage.svelte` | Deliberately isolated from the theme system so editor surfaces stay neutral while live theme edits flow through the components being edited. Never load globally. |
13
13
  | `ui-form-controls.css` | Editor chrome only | Consumes `--ui-*` only | JS-imported by `Editor.svelte` and `ComponentEditorPage.svelte` | `.ui-form-select` / `.ui-form-input` / `.ui-form-field-*` classes. Used by `ProjectFontsSection`, `FontStackEditor`, `DialogEditor`, `NotificationEditor`. Theme-immune — must not reference `--font-*`, `--surface-*`, etc. |
14
- | `site.css` | Themed pages | Consumes theme tokens | Page-imported by `Home.svelte` | Consumer-facing starter typography for the landing page (unscoped `h1`, `p`, `a`, …). Never load on editor pages. Replaceable starter content — users edit or replace this file to style their own site. Components that take slot content (Card body, CollapsibleSection content) defend their typography aliases against these global rules via `:global(p) { font: inherit; color: inherit; }`. The demo page no longer imports it; the demo uses scoped classes and component-owned slot typography instead. |
14
+ | `site.css` | Themed pages | Consumes theme tokens | Page-imported by `Home.svelte` | Consumer-facing starter typography for the landing page (unscoped `h1`, `p`, `a`, …). Never load on editor pages. Replaceable starter content — users edit or replace this file to style their own site. Components that slot consumer content (Card body, CollapsibleSection content, SectionKit) defend their body typography against these global rules with `@include slot-prose` (from `styles/_slot-prose.scss`): the mixin pins only the owned type axes (font-family, font-size, font-weight, line-height, color) plus list spacing onto slotted `p`/`ul`/`ol`/`li`, while `a`, `strong`, font-style, letter-spacing and text-transform are left to inherit the page (links stay links, a consumer's italics survive). `prose={false}` opts a slot out entirely, and the `check:slot-prose` guard fails the build on a hand-rolled copy of the pin. The demo page no longer imports it; the demo uses scoped classes and component-owned slot typography instead. |
15
15
  | `fonts.css` + `fonts/` | Themed pages only | n/a (`@font-face` only) | `main.ts` | Build-special-cased: copied directly to `dist/` without processing so Vite doesn't inline woff2 files as base64. Starter content; editor's font invariant means these never affect chrome. |
16
16
 
17
17
  ### Publishing layout
@@ -0,0 +1,52 @@
1
+ // Slot-prose defense.
2
+ //
3
+ // Components render the consumer's raw HTML as slot content in the *same* light
4
+ // DOM tree, so a consumer's global element rules (site.css `p`, `ul li`, …)
5
+ // directly match the slotted `<p>`/`<li>` and beat the container's inherited
6
+ // body typography. Svelte's scope class never reaches slotted content, so there
7
+ // is no automatic protection — the container must pin the type axes it owns.
8
+ //
9
+ // This mixin is the single definition of that pin. It covers p/ul/ol/li and
10
+ // pins ONLY the axes the component owns: font-family, font-size, font-weight,
11
+ // line-height, color, plus the component's spacing rhythm. font-style,
12
+ // letter-spacing and text-transform stay free, so a consumer's italics or
13
+ // tracking survive; `a` and `strong` are left untouched, so links keep looking
14
+ // like links inside a card. `&.prose` gates the whole thing: `prose={false}`
15
+ // drops the pin and the consumer fully owns the slot's content styling.
16
+ //
17
+ // Usage:
18
+ // .card-body { /* owns the baseline axes */ @include slot-prose; }
19
+ // <div class="card-body" class:prose>…</div>
20
+
21
+ @mixin slot-prose {
22
+ &.prose {
23
+ :global(p),
24
+ :global(ul),
25
+ :global(ol),
26
+ :global(li) {
27
+ font-family: inherit;
28
+ font-size: inherit;
29
+ font-weight: inherit;
30
+ line-height: inherit;
31
+ color: inherit;
32
+ }
33
+
34
+ :global(p) {
35
+ margin: 0 0 var(--space-12);
36
+ }
37
+
38
+ :global(p:last-child) {
39
+ margin-bottom: 0;
40
+ }
41
+
42
+ :global(ul),
43
+ :global(ol) {
44
+ margin: var(--space-12) 0;
45
+ padding-left: var(--space-24);
46
+ }
47
+
48
+ :global(li) {
49
+ margin-bottom: var(--space-4);
50
+ }
51
+ }
52
+ }
@@ -1,16 +1,14 @@
1
1
  /* Generated from the production theme by syncFontsToCss. Do not edit. */
2
+ /* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */
2
3
 
3
4
  /* Adobe Typekit — fira-code */
4
5
  @import url('https://use.typekit.net/jes8oow.css');
5
6
 
7
+ /* Google Fonts — Manrope */
8
+ @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap');
9
+
6
10
  /* Google Fonts — Arvo */
7
11
  @import url('https://fonts.googleapis.com/css2?family=Arvo:ital,wght@0,400;0,700;1,400;1,700&display=swap');
8
12
 
9
13
  /* Google Fonts — GFS Didot */
10
14
  @import url('https://fonts.googleapis.com/css2?family=GFS+Didot&display=swap');
11
-
12
- /* Google Fonts — Fraunces */
13
- @import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,100..900;1,9..144,100..900&display=swap');
14
-
15
- /* Google Fonts — Manrope */
16
- @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200..800&display=swap');
@@ -458,6 +458,13 @@
458
458
  --blur-xl: 24px;
459
459
  --blur-2xl: 32px;
460
460
 
461
+ /* Transform-scale multipliers (e.g. hover zoom). 5% per step. */
462
+ --scale-sm: 1.05;
463
+ --scale-md: 1.1;
464
+ --scale-lg: 1.15;
465
+ --scale-xl: 1.2;
466
+ --scale-2xl: 1.25;
467
+
461
468
  /* COLUMN LAYOUT — 12-col grid. Drives the dev overlay; consume via
462
469
  grid-template-columns: repeat(var(--columns-count), 1fr) etc.
463
470
  --columns-margin = side gutters (0 = flush). */