@motion-proto/live-tokens 0.7.1 → 0.8.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.
- package/dist-plugin/index.cjs +707 -90
- package/dist-plugin/index.d.cts +1 -0
- package/dist-plugin/index.d.ts +1 -0
- package/dist-plugin/index.js +707 -90
- package/package.json +2 -1
- package/src/app/site.css +1 -1
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +34 -27
- package/src/editor/component-editor/DialogEditor.svelte +4 -4
- package/src/editor/component-editor/NotificationEditor.svelte +3 -1
- package/src/editor/component-editor/SectionDividerEditor.svelte +439 -112
- package/src/editor/component-editor/StandardButtonsEditor.svelte +13 -1
- package/src/editor/component-editor/editors.d.ts +10 -0
- package/src/editor/component-editor/scaffolding/AngleDial.svelte +52 -13
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +10 -11
- package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +0 -1
- package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +15 -2
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +103 -15
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +9 -6
- package/src/editor/component-editor/scaffolding/TypeEditor.svelte +13 -1
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +239 -25
- package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -0
- package/src/editor/component-editor/scaffolding/types.ts +11 -0
- package/src/editor/core/components/componentConfigKeys.ts +8 -0
- package/src/editor/core/components/componentConfigService.ts +2 -2
- package/src/editor/core/components/componentPersist.ts +7 -5
- package/src/editor/core/manifests/manifestService.ts +58 -3
- package/src/editor/core/palettes/familySwap.ts +99 -0
- package/src/editor/core/palettes/paletteDerivation.ts +69 -0
- package/src/editor/core/palettes/tokenRegistry.ts +4 -1
- package/src/editor/core/store/editorStore.ts +206 -12
- package/src/editor/core/store/editorTypes.ts +55 -12
- package/src/editor/core/store/gradientSource.ts +192 -0
- package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
- package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
- package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
- package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
- package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
- package/src/editor/core/themes/migrations/index.ts +10 -0
- package/src/editor/core/themes/slices/components.ts +18 -4
- package/src/editor/core/themes/slices/gradients.ts +88 -13
- package/src/editor/core/themes/themeInit.ts +2 -2
- package/src/editor/core/themes/themeTypes.ts +56 -1
- package/src/editor/overlay/ColumnsOverlay.svelte +0 -1
- package/src/editor/overlay/LiveEditorOverlay.svelte +1 -4
- package/src/editor/styles/ui-editor.css +1 -0
- package/src/editor/styles/ui-form-controls.css +19 -20
- package/src/editor/ui/BezierCurveEditor.svelte +114 -63
- package/src/editor/ui/EditorViewSwitcher.svelte +0 -1
- package/src/editor/ui/FileLoadList.svelte +22 -5
- package/src/editor/ui/FontStackEditor.svelte +214 -76
- package/src/editor/ui/GradientEditor.svelte +435 -215
- package/src/editor/ui/GradientStopPicker.svelte +11 -3
- package/src/editor/ui/ManifestFileManager.svelte +71 -4
- package/src/editor/ui/PaletteEditor.svelte +52 -79
- package/src/editor/ui/ProjectFontsSection.svelte +328 -293
- package/src/editor/ui/ThemeFileManager.svelte +0 -4
- package/src/editor/ui/UIFontFamilySelector.svelte +0 -1
- package/src/editor/ui/UIFontSizeSelector.svelte +3 -0
- package/src/editor/ui/UIInfoPopover.svelte +0 -1
- package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
- package/src/editor/ui/UIPaletteSelector.svelte +31 -4
- package/src/editor/ui/UIPillButton.svelte +33 -3
- package/src/editor/ui/UISegmentedControl.svelte +114 -0
- package/src/editor/ui/UITokenSelector.svelte +4 -1
- package/src/editor/ui/VariablesTab.svelte +41 -35
- package/src/editor/ui/palette/OverridesPanel.svelte +14 -37
- package/src/editor/ui/palette/PaletteBase.svelte +3 -3
- package/src/editor/ui/sections/ColumnsSection.svelte +1 -2
- package/src/editor/ui/sections/GradientsSection.svelte +1 -1
- package/src/editor/ui/sections/OverlaysSection.svelte +1 -1
- package/src/editor/ui/sections/ShadowsSection.svelte +1 -1
- package/src/system/components/Button.svelte +2 -2
- package/src/system/components/Card.svelte +29 -1
- package/src/system/components/CollapsibleSection.svelte +25 -2
- package/src/system/components/FloatingTokenTags.css +43 -24
- package/src/system/components/FloatingTokenTags.svelte +88 -137
- package/src/system/components/Notification.svelte +8 -1
- package/src/system/components/SectionDivider.svelte +456 -379
- package/src/system/styles/CONVENTIONS.md +1 -1
- package/src/system/styles/fonts.css +3 -16
- package/src/system/styles/tokens.css +356 -1199
- package/src/system/styles/tokens.generated.css +544 -0
- package/src/editor/component-editor/scaffolding/DividerEditor.svelte +0 -94
- package/src/editor/component-editor/scaffolding/GradientCard.svelte +0 -296
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { flip } from 'svelte/animate';
|
|
3
|
+
import { cubicOut } from 'svelte/easing';
|
|
2
4
|
import type {
|
|
3
5
|
FontFamily,
|
|
4
6
|
FontSource,
|
|
@@ -12,7 +14,11 @@
|
|
|
12
14
|
import { applyFontStacks, SYSTEM_CASCADES } from '../core/fonts/fontLoader';
|
|
13
15
|
|
|
14
16
|
const SYSTEM_PRESETS: SystemCascadePreset[] = ['system-ui-sans', 'system-ui-serif', 'system-ui-mono'];
|
|
15
|
-
|
|
17
|
+
// `cursive` and `fantasy` are CSS-spec generics whose rendering varies wildly
|
|
18
|
+
// across OSes (cursive → Comic Sans / Snell Roundhand; fantasy → Impact /
|
|
19
|
+
// Papyrus). They're rarely what a designer means by "fallback," so we don't
|
|
20
|
+
// offer them in the editor.
|
|
21
|
+
const GENERIC_VALUES: GenericFamily[] = ['sans-serif', 'serif', 'monospace'];
|
|
16
22
|
|
|
17
23
|
const STACK_VARIABLES: FontStackVariable[] = [
|
|
18
24
|
'--font-display',
|
|
@@ -21,18 +27,55 @@
|
|
|
21
27
|
'--font-mono',
|
|
22
28
|
];
|
|
23
29
|
|
|
30
|
+
// Each stack's terminal fallback (bottom slot). It's locked to system or
|
|
31
|
+
// generic so text always renders even if every project font 404s. The
|
|
32
|
+
// fallback maps by stack variable; chosen to match the stack's purpose.
|
|
33
|
+
const TERMINAL_FALLBACK_BY_VAR: Record<FontStackVariable, GenericFamily> = {
|
|
34
|
+
'--font-display': 'serif',
|
|
35
|
+
'--font-sans': 'sans-serif',
|
|
36
|
+
'--font-serif': 'serif',
|
|
37
|
+
'--font-mono': 'monospace',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// The single matching System UI preset paired with each stack — the only two
|
|
41
|
+
// options offered in the terminal slot's dropdown.
|
|
42
|
+
const TERMINAL_SYSTEM_BY_VAR: Record<FontStackVariable, SystemCascadePreset> = {
|
|
43
|
+
'--font-display': 'system-ui-serif',
|
|
44
|
+
'--font-sans': 'system-ui-sans',
|
|
45
|
+
'--font-serif': 'system-ui-serif',
|
|
46
|
+
'--font-mono': 'system-ui-mono',
|
|
47
|
+
};
|
|
48
|
+
|
|
24
49
|
let fontSourcesList = $derived($editorState.fonts.sources);
|
|
25
50
|
let fontStacksList = $derived($editorState.fonts.stacks);
|
|
26
51
|
let allFamilies = $derived((fontSourcesList as FontSource[]).flatMap((s) => s.families.map((f) => ({ ...f, sourceLabel: s.label ?? s.kind }))));
|
|
27
52
|
let familyById = $derived(new Map<string, FontFamily>(allFamilies.map((f) => [f.id, f])));
|
|
28
53
|
|
|
54
|
+
/** Ensure the slot list ends in a system/generic terminal. If it doesn't,
|
|
55
|
+
* append the variable's default generic so the stack is always renderable. */
|
|
56
|
+
function withTerminalFallback(variable: FontStackVariable, slots: FontStackSlot[]): FontStackSlot[] {
|
|
57
|
+
const fallback: FontStackSlot = { kind: 'generic', value: TERMINAL_FALLBACK_BY_VAR[variable] };
|
|
58
|
+
if (slots.length === 0) return [fallback];
|
|
59
|
+
const last = slots[slots.length - 1];
|
|
60
|
+
if (last.kind === 'project') return [...slots, fallback];
|
|
61
|
+
return slots;
|
|
62
|
+
}
|
|
63
|
+
|
|
29
64
|
function ensureAllStacksPresent(current: FontStack[]): FontStack[] {
|
|
30
65
|
const byVar = new Map(current.map((s) => [s.variable, s]));
|
|
31
|
-
return STACK_VARIABLES.map((v) =>
|
|
66
|
+
return STACK_VARIABLES.map((v) => {
|
|
67
|
+
const stack = byVar.get(v);
|
|
68
|
+
const slots = withTerminalFallback(v, stack?.slots ?? []);
|
|
69
|
+
return stack ? { ...stack, slots } : { variable: v, slots };
|
|
70
|
+
});
|
|
32
71
|
}
|
|
33
72
|
|
|
34
73
|
let stacks = $derived(ensureAllStacksPresent(fontStacksList));
|
|
35
74
|
|
|
75
|
+
function variableLabel(v: string): string {
|
|
76
|
+
return v.replace(/^--/, '').split('-').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
77
|
+
}
|
|
78
|
+
|
|
36
79
|
function slotKey(slot: FontStackSlot): string {
|
|
37
80
|
if (slot.kind === 'project') return `project:${slot.familyId}`;
|
|
38
81
|
if (slot.kind === 'system') return `system:${slot.preset}`;
|
|
@@ -103,19 +146,37 @@
|
|
|
103
146
|
newSlot = { kind: 'system', preset };
|
|
104
147
|
}
|
|
105
148
|
updateStack(variable, (slots) => {
|
|
106
|
-
|
|
149
|
+
// Insert above the terminal fallback (always the last slot) so the
|
|
150
|
+
// terminal stays at the bottom.
|
|
151
|
+
const insertAt = Math.max(0, slots.length - 1);
|
|
152
|
+
slots.splice(insertAt, 0, newSlot);
|
|
107
153
|
return slots;
|
|
108
154
|
});
|
|
109
155
|
}
|
|
110
156
|
|
|
157
|
+
/* Drag UX: the source row lifts (opacity, shadow); a white insertion bar
|
|
158
|
+
sits in the gap between rows at the projected drop position. The array
|
|
159
|
+
is only mutated on drop. animate:flip then slides every row to its new
|
|
160
|
+
spot, so the commit doesn't snap.
|
|
161
|
+
|
|
162
|
+
Only the drag handle is `draggable="true"` — putting it on the whole row
|
|
163
|
+
swallowed clicks on the inner <button>/<select> in real browsers because
|
|
164
|
+
mousedown started a drag gesture before `click` could fire. The handle
|
|
165
|
+
starts the drag and calls setDragImage(rowEl, ...) so the visual drag
|
|
166
|
+
image is still the whole row. */
|
|
111
167
|
let dragSource: { variable: FontStackVariable; index: number } | null = $state(null);
|
|
112
|
-
let dragOver: { variable: FontStackVariable; index: number; position: 'before' | '
|
|
168
|
+
let dragOver: { variable: FontStackVariable; index: number; position: 'before' | 'after' } | null = $state(null);
|
|
113
169
|
|
|
114
170
|
function onDragStart(e: DragEvent, variable: FontStackVariable, index: number) {
|
|
115
171
|
if (!e.dataTransfer) return;
|
|
116
172
|
dragSource = { variable, index };
|
|
117
173
|
e.dataTransfer.effectAllowed = 'move';
|
|
118
174
|
e.dataTransfer.setData('application/x-font-slot', JSON.stringify({ variable, index }));
|
|
175
|
+
const rowEl = (e.currentTarget as HTMLElement).closest('.slot-row') as HTMLElement | null;
|
|
176
|
+
if (rowEl) {
|
|
177
|
+
const rect = rowEl.getBoundingClientRect();
|
|
178
|
+
e.dataTransfer.setDragImage(rowEl, e.clientX - rect.left, e.clientY - rect.top);
|
|
179
|
+
}
|
|
119
180
|
}
|
|
120
181
|
|
|
121
182
|
function onDragOver(e: DragEvent, variable: FontStackVariable, index: number) {
|
|
@@ -124,9 +185,12 @@
|
|
|
124
185
|
e.preventDefault();
|
|
125
186
|
if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
|
|
126
187
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
188
|
+
let position: 'before' | 'after' = e.clientY - rect.top < rect.height / 2 ? 'before' : 'after';
|
|
189
|
+
// Terminal fallback stays at the bottom — never accept a drop after it.
|
|
190
|
+
const stack = stacks.find((s) => s.variable === variable);
|
|
191
|
+
if (stack && index === stack.slots.length - 1 && position === 'after') {
|
|
192
|
+
position = 'before';
|
|
193
|
+
}
|
|
130
194
|
dragOver = { variable, index, position };
|
|
131
195
|
}
|
|
132
196
|
|
|
@@ -137,12 +201,12 @@
|
|
|
137
201
|
function onDrop(e: DragEvent, variable: FontStackVariable, index: number) {
|
|
138
202
|
e.preventDefault();
|
|
139
203
|
const slotPayload = e.dataTransfer?.getData('application/x-font-slot');
|
|
140
|
-
const position = dragOver?.position ?? '
|
|
204
|
+
const position = dragOver?.position ?? 'before';
|
|
141
205
|
dragOver = null;
|
|
142
206
|
if (!slotPayload) return;
|
|
143
207
|
const src = JSON.parse(slotPayload) as { variable: FontStackVariable; index: number };
|
|
144
208
|
if (src.variable !== variable) return;
|
|
145
|
-
if (src.index === index
|
|
209
|
+
if (src.index === index) return;
|
|
146
210
|
updateStack(variable, (slots) => {
|
|
147
211
|
const [moved] = slots.splice(src.index, 1);
|
|
148
212
|
let target = index;
|
|
@@ -163,63 +227,82 @@
|
|
|
163
227
|
{#each stacks as stack (stack.variable)}
|
|
164
228
|
<div class="font-stack">
|
|
165
229
|
<div class="stack-header">
|
|
166
|
-
<span class="stack-variable">{stack.variable}</span>
|
|
230
|
+
<span class="stack-variable">{variableLabel(stack.variable)}</span>
|
|
167
231
|
</div>
|
|
168
232
|
<div class="font-stack-list">
|
|
169
|
-
{#each stack.slots as slot, i (
|
|
233
|
+
{#each stack.slots as slot, i (slotKey(slot))}
|
|
234
|
+
{@const isTerminal = i === stack.slots.length - 1}
|
|
170
235
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
171
236
|
<div
|
|
172
237
|
class="slot-row"
|
|
173
|
-
class:
|
|
238
|
+
class:terminal={isTerminal}
|
|
174
239
|
class:drop-before={dragOver?.variable === stack.variable && dragOver?.index === i && dragOver?.position === 'before'}
|
|
175
240
|
class:drop-after={dragOver?.variable === stack.variable && dragOver?.index === i && dragOver?.position === 'after'}
|
|
176
241
|
class:dragging={dragSource?.variable === stack.variable && dragSource?.index === i}
|
|
177
|
-
draggable="true"
|
|
178
|
-
ondragstart={(e) => onDragStart(e, stack.variable, i)}
|
|
179
242
|
ondragover={(e) => onDragOver(e, stack.variable, i)}
|
|
180
243
|
ondragleave={onDragLeave}
|
|
181
244
|
ondrop={(e) => onDrop(e, stack.variable, i)}
|
|
182
245
|
ondragend={onDragEnd}
|
|
246
|
+
animate:flip={{ duration: 220, easing: cubicOut }}
|
|
183
247
|
>
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
248
|
+
<div class="slot-controls">
|
|
249
|
+
{#if isTerminal}
|
|
250
|
+
<i class="fas fa-lock slot-locked-glyph" aria-hidden="true" title="Final fallback — can't be removed"></i>
|
|
251
|
+
{:else}
|
|
252
|
+
<span
|
|
253
|
+
class="drag-handle"
|
|
254
|
+
aria-hidden="true"
|
|
255
|
+
draggable="true"
|
|
256
|
+
ondragstart={(e) => onDragStart(e, stack.variable, i)}
|
|
257
|
+
>⋮⋮</span>
|
|
258
|
+
{/if}
|
|
259
|
+
<span class="slot-position">{i + 1}.</span>
|
|
191
260
|
<select
|
|
192
261
|
class="ui-form-select slot-select"
|
|
193
262
|
value={slotKey(slot)}
|
|
194
263
|
onchange={(e) => onSelectChange(e, stack.variable, i)}
|
|
195
264
|
>
|
|
196
|
-
{#if
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
265
|
+
{#if isTerminal}
|
|
266
|
+
{@const sys = TERMINAL_SYSTEM_BY_VAR[stack.variable]}
|
|
267
|
+
{@const gen = TERMINAL_FALLBACK_BY_VAR[stack.variable]}
|
|
268
|
+
<option value={`system:${sys}`}>{sys === 'system-ui-sans' ? 'System UI (sans)' : sys === 'system-ui-serif' ? 'System UI (serif)' : 'System UI (mono)'}</option>
|
|
269
|
+
<option value={`generic:${gen}`}>{gen}</option>
|
|
270
|
+
{:else}
|
|
271
|
+
{#if allFamilies.length > 0}
|
|
272
|
+
<optgroup label="Project fonts">
|
|
273
|
+
{#each allFamilies as fam}
|
|
274
|
+
<option value={`project:${fam.id}`}>{fam.name}</option>
|
|
275
|
+
{/each}
|
|
276
|
+
</optgroup>
|
|
277
|
+
{/if}
|
|
278
|
+
<optgroup label="System cascade">
|
|
279
|
+
{#each SYSTEM_PRESETS as p}
|
|
280
|
+
<option value={`system:${p}`}>{p === 'system-ui-sans' ? 'System UI (sans)' : p === 'system-ui-serif' ? 'System UI (serif)' : 'System UI (mono)'}</option>
|
|
281
|
+
{/each}
|
|
282
|
+
</optgroup>
|
|
283
|
+
<optgroup label="Generic">
|
|
284
|
+
{#each GENERIC_VALUES as g}
|
|
285
|
+
<option value={`generic:${g}`}>{g}</option>
|
|
200
286
|
{/each}
|
|
201
287
|
</optgroup>
|
|
202
288
|
{/if}
|
|
203
|
-
<optgroup label="System cascade">
|
|
204
|
-
{#each SYSTEM_PRESETS as p}
|
|
205
|
-
<option value={`system:${p}`}>{p === 'system-ui-sans' ? 'System UI (sans)' : p === 'system-ui-serif' ? 'System UI (serif)' : 'System UI (mono)'}</option>
|
|
206
|
-
{/each}
|
|
207
|
-
</optgroup>
|
|
208
|
-
<optgroup label="Generic">
|
|
209
|
-
{#each GENERIC_VALUES as g}
|
|
210
|
-
<option value={`generic:${g}`}>{g}</option>
|
|
211
|
-
{/each}
|
|
212
|
-
</optgroup>
|
|
213
289
|
</select>
|
|
290
|
+
{#if isTerminal}
|
|
291
|
+
<span class="slot-remove-placeholder" aria-hidden="true"></span>
|
|
292
|
+
{:else}
|
|
293
|
+
<button
|
|
294
|
+
type="button"
|
|
295
|
+
class="slot-remove"
|
|
296
|
+
aria-label="Remove slot"
|
|
297
|
+
title="Remove"
|
|
298
|
+
onclick={() => removeSlot(stack.variable, i)}
|
|
299
|
+
>×</button>
|
|
300
|
+
{/if}
|
|
214
301
|
</div>
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
title="Remove"
|
|
220
|
-
onclick={() => removeSlot(stack.variable, i)}
|
|
221
|
-
disabled={stack.slots.length <= 1}
|
|
222
|
-
>×</button>
|
|
302
|
+
<span
|
|
303
|
+
class="slot-preview"
|
|
304
|
+
style="font-family: {slotCssValue(slot)};{stack.variable === '--font-display' ? ' font-size: var(--ui-font-size-2xl);' : ''}"
|
|
305
|
+
>The quick brown fox jumps over the lazy dog</span>
|
|
223
306
|
</div>
|
|
224
307
|
{/each}
|
|
225
308
|
</div>
|
|
@@ -233,47 +316,104 @@
|
|
|
233
316
|
<style>
|
|
234
317
|
.font-stacks-columns {
|
|
235
318
|
display: grid;
|
|
236
|
-
grid-template-columns: repeat(
|
|
237
|
-
gap: var(--ui-space-
|
|
319
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
320
|
+
gap: var(--ui-space-24);
|
|
238
321
|
}
|
|
239
322
|
|
|
323
|
+
@media (max-width: 720px) {
|
|
324
|
+
.font-stacks-columns {
|
|
325
|
+
grid-template-columns: 1fr;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Track: each font family is a bordered channel that the slot cards drop into.
|
|
330
|
+
The interior sits a step deeper than the page surface, so the slot cards
|
|
331
|
+
(surface-low) read as raised inside the depression. */
|
|
240
332
|
.font-stack {
|
|
333
|
+
position: relative;
|
|
241
334
|
display: flex;
|
|
242
335
|
flex-direction: column;
|
|
243
|
-
gap: var(--ui-space-
|
|
244
|
-
padding: var(--ui-space-12);
|
|
245
|
-
background:
|
|
336
|
+
gap: var(--ui-space-12);
|
|
337
|
+
padding: var(--ui-space-20) var(--ui-space-12) var(--ui-space-16);
|
|
338
|
+
background: var(--ui-surface-lowest);
|
|
246
339
|
border: 1px solid var(--ui-border-low);
|
|
247
|
-
border-radius: var(--ui-radius-
|
|
340
|
+
border-radius: var(--ui-radius-lg);
|
|
248
341
|
}
|
|
249
342
|
|
|
343
|
+
/* Legend: knock through the top border like a native <fieldset>'s <legend>.
|
|
344
|
+
Editor content bg is solid black; the header paints over the border line
|
|
345
|
+
to cut it. */
|
|
250
346
|
.stack-header {
|
|
347
|
+
position: absolute;
|
|
348
|
+
top: 0;
|
|
349
|
+
left: var(--ui-space-12);
|
|
350
|
+
transform: translateY(-50%);
|
|
251
351
|
display: flex;
|
|
252
352
|
align-items: center;
|
|
353
|
+
padding: 0 var(--ui-space-6);
|
|
354
|
+
background: black;
|
|
355
|
+
line-height: 1;
|
|
253
356
|
}
|
|
254
357
|
|
|
255
358
|
.stack-variable {
|
|
256
|
-
font-
|
|
257
|
-
font-
|
|
359
|
+
font-size: var(--ui-font-size-lg);
|
|
360
|
+
font-weight: var(--ui-font-weight-bold);
|
|
258
361
|
color: var(--ui-text-primary);
|
|
259
362
|
}
|
|
260
363
|
|
|
261
364
|
.font-stack-list {
|
|
262
365
|
display: flex;
|
|
263
366
|
flex-direction: column;
|
|
264
|
-
gap: var(--ui-space-
|
|
367
|
+
gap: var(--ui-space-8);
|
|
265
368
|
}
|
|
266
369
|
|
|
267
370
|
.slot-row {
|
|
371
|
+
display: flex;
|
|
372
|
+
flex-direction: column;
|
|
373
|
+
gap: var(--ui-space-8);
|
|
374
|
+
padding: var(--ui-space-10);
|
|
375
|
+
border: 1px solid var(--ui-border-low);
|
|
376
|
+
border-radius: var(--ui-radius-md);
|
|
377
|
+
background: var(--ui-surface-low);
|
|
378
|
+
position: relative;
|
|
379
|
+
transition:
|
|
380
|
+
opacity var(--ui-transition-fast),
|
|
381
|
+
transform var(--ui-transition-fast),
|
|
382
|
+
border-color var(--ui-transition-fast);
|
|
383
|
+
}
|
|
384
|
+
.slot-row:hover { border-color: var(--ui-border); }
|
|
385
|
+
|
|
386
|
+
.slot-controls {
|
|
268
387
|
display: grid;
|
|
269
388
|
grid-template-columns: auto auto 1fr auto;
|
|
270
389
|
align-items: center;
|
|
271
|
-
gap: var(--ui-space-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
390
|
+
gap: var(--ui-space-8);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/* Reorder UX: a thin white bar sits in the existing 8px gap between rows
|
|
394
|
+
as the insertion indicator. The bar is absolutely positioned and consumes
|
|
395
|
+
no layout space, so the parent track stays the same height. On drop, the
|
|
396
|
+
array commits and animate:flip slides each row to its new position. */
|
|
397
|
+
.slot-row.drop-before::before,
|
|
398
|
+
.slot-row.drop-after::after {
|
|
399
|
+
content: '';
|
|
400
|
+
position: absolute;
|
|
401
|
+
left: 0;
|
|
402
|
+
right: 0;
|
|
403
|
+
height: 2px;
|
|
404
|
+
background: var(--ui-text-primary);
|
|
405
|
+
border-radius: 1px;
|
|
406
|
+
box-shadow: 0 0 6px rgba(255, 255, 255, 0.45);
|
|
407
|
+
}
|
|
408
|
+
.slot-row.drop-before::before { top: -5px; }
|
|
409
|
+
.slot-row.drop-after::after { bottom: -5px; }
|
|
410
|
+
|
|
411
|
+
.slot-row.dragging {
|
|
412
|
+
opacity: 0.55;
|
|
413
|
+
z-index: 2;
|
|
414
|
+
border-color: var(--ui-border-high);
|
|
415
|
+
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
|
|
275
416
|
}
|
|
276
|
-
.slot-row:last-child { border-bottom: none; }
|
|
277
417
|
|
|
278
418
|
.drag-handle {
|
|
279
419
|
cursor: grab;
|
|
@@ -281,22 +421,27 @@
|
|
|
281
421
|
color: var(--ui-text-muted);
|
|
282
422
|
font-size: var(--ui-font-size-md);
|
|
283
423
|
line-height: 1;
|
|
284
|
-
letter-spacing: -2px;
|
|
285
424
|
}
|
|
286
425
|
.slot-row.dragging .drag-handle { cursor: grabbing; }
|
|
287
|
-
.slot-row.dragging { opacity: 0.55; }
|
|
288
426
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
427
|
+
/* Terminal-row lock glyph sits in the drag-handle's grid track; the row's
|
|
428
|
+
right-hand X column is left empty (see .slot-remove-placeholder) so the
|
|
429
|
+
lock is the only chrome and reads as "this row is fixed." */
|
|
430
|
+
.slot-locked-glyph {
|
|
431
|
+
display: inline-flex;
|
|
432
|
+
align-items: center;
|
|
433
|
+
justify-content: center;
|
|
434
|
+
color: var(--ui-text-muted);
|
|
435
|
+
font-size: var(--ui-font-size-sm);
|
|
436
|
+
opacity: 0.55;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Empty grid track on terminal rows where the X button sits on others,
|
|
440
|
+
so columns stay aligned. */
|
|
441
|
+
.slot-remove-placeholder {
|
|
442
|
+
width: 1.5rem;
|
|
443
|
+
height: 1.5rem;
|
|
297
444
|
}
|
|
298
|
-
.slot-row.drop-before::before { top: -1px; }
|
|
299
|
-
.slot-row.drop-after::after { bottom: -1px; }
|
|
300
445
|
|
|
301
446
|
.slot-position {
|
|
302
447
|
font-size: var(--ui-font-size-md);
|
|
@@ -305,13 +450,6 @@
|
|
|
305
450
|
text-align: right;
|
|
306
451
|
}
|
|
307
452
|
|
|
308
|
-
.slot-main {
|
|
309
|
-
display: flex;
|
|
310
|
-
flex-direction: column;
|
|
311
|
-
gap: var(--ui-space-2);
|
|
312
|
-
min-width: 0;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
453
|
.slot-preview {
|
|
316
454
|
font-size: var(--ui-font-size-md);
|
|
317
455
|
color: var(--ui-text-primary);
|