@motion-proto/live-tokens 0.49.0 → 0.50.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/CHANGELOG.md +36 -0
- package/dist-plugin/generateColorsAndType/index.cjs +13 -2
- package/dist-plugin/generateColorsAndType/index.js +13 -2
- package/dist-plugin/index.cjs +2 -0
- package/dist-plugin/index.js +2 -0
- package/package.json +6 -1
- package/src/editor/bootstrap.ts +2 -0
- package/src/editor/component-editor/ButtonEditor.svelte +1 -1
- package/src/editor/component-editor/CardEditor.svelte +5 -2
- package/src/editor/component-editor/IconButtonEditor.svelte +1 -1
- package/src/editor/component-editor/ImageEditor.svelte +1 -1
- package/src/editor/component-editor/SectionDividerEditor.svelte +4 -1
- package/src/editor/component-editor/SideNavigationEditor.svelte +6 -0
- package/src/editor/component-editor/TabBarEditor.svelte +1 -5
- package/src/editor/component-editor/scaffolding/ComponentEditorBase.svelte +8 -1
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +10 -32
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +2 -0
- package/src/editor/component-editor/scaffolding/TypeEditor.svelte +3 -1
- package/src/editor/component-editor/scaffolding/types.ts +3 -0
- package/src/editor/core/components/componentConfigService.ts +25 -0
- package/src/editor/core/cssVarSync.ts +65 -7
- package/src/editor/core/fonts/fontLoader.ts +12 -3
- package/src/editor/core/fonts/fontWeightAvailability.ts +97 -0
- package/src/editor/core/preview/lookPreview.ts +23 -18
- package/src/editor/core/store/editorRenderer.ts +22 -7
- package/src/editor/core/store/editorStore.ts +31 -0
- package/src/editor/core/store/gradientSource.ts +5 -0
- package/src/editor/core/themes/colorsAndTypeService.ts +2 -12
- package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +4 -1
- package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +4 -1
- package/src/editor/core/themes/migrations/2026-05-25-cornerbadge-flatten-variants.ts +8 -0
- package/src/editor/core/themes/slices/fonts.ts +4 -5
- package/src/editor/core/themes/themeDocumentSync.ts +66 -0
- package/src/editor/core/themes/themeInit.ts +7 -10
- package/src/editor/core/themes/themeService.ts +10 -3
- package/src/editor/docs/content/themes-workflow.md +14 -11
- package/src/editor/docs/content/where-themes-live.md +7 -2
- package/src/editor/docs/content.generated.ts +2 -2
- package/src/editor/ui/FileLoadList.svelte +1 -0
- package/src/editor/ui/FontStackEditor.svelte +1 -2
- package/src/editor/ui/GradientEditor.svelte +5 -1
- package/src/editor/ui/ProjectFontsSection.svelte +0 -5
- package/src/editor/ui/ThemePanel.svelte +206 -42
- package/src/editor/ui/UIDialog.svelte +7 -1
- package/src/editor/ui/UIFontFamilySelector.svelte +13 -13
- package/src/editor/ui/UIFontSizeSelector.svelte +5 -5
- package/src/editor/ui/UIFontWeightSelector.svelte +52 -2
- package/src/editor/ui/UIOptionItem.svelte +14 -1
- package/src/editor/ui/UIPaddingSelector.svelte +4 -3
- package/src/editor/ui/UIPaletteSelector.svelte +20 -13
- package/src/editor/ui/UIRelinkConfirmDialog.svelte +18 -3
- package/src/editor/ui/UITextTransformSelector.svelte +5 -5
- package/src/editor/ui/UITokenSelector.svelte +29 -7
- package/src/live-tokens/data/themes/spring-meadow.json +14 -12
- package/src/system/components/Button.svelte +2 -1
- package/src/system/components/FloatingTokenTags.css +2 -1
- package/src/system/components/FloatingTokenTags.svelte +11 -0
- package/src/system/components/IconButton.svelte +2 -1
- package/src/system/components/Image.svelte +9 -3
- package/src/system/components/ImageLightbox.svelte +2 -0
- package/src/system/components/SectionDivider.svelte +20 -2
- package/src/system/components/SideNavigation.svelte +15 -1
- package/src/system/styles/fonts.css +6 -6
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { run } from 'svelte/legacy';
|
|
3
3
|
|
|
4
4
|
import { onMount, onDestroy } from 'svelte';
|
|
5
|
-
import {
|
|
5
|
+
import { CSS_VARS_CHANGE_EVENT, type CssVarsChangeDetail } from '../core/cssVarSync';
|
|
6
6
|
import UIVariantSelector from './UIVariantSelector.svelte';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function handleVarChange(e: Event) {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
77
|
+
const names = (e as CustomEvent<CssVarsChangeDetail>).detail?.names ?? [];
|
|
78
|
+
if (names.some((name) => name.startsWith('--font-size-') || name.startsWith('--icon-size-'))) readPxValues();
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// Re-read sizes when the target variable's scale changes
|
|
@@ -86,10 +86,10 @@
|
|
|
86
86
|
|
|
87
87
|
onMount(() => {
|
|
88
88
|
readPxValues();
|
|
89
|
-
document.addEventListener(
|
|
89
|
+
document.addEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange);
|
|
90
90
|
});
|
|
91
91
|
onDestroy(() => {
|
|
92
|
-
document.removeEventListener(
|
|
92
|
+
document.removeEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange);
|
|
93
93
|
});
|
|
94
94
|
</script>
|
|
95
95
|
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onDestroy, onMount } from 'svelte';
|
|
3
|
+
import { editorState } from '../core/store/editorStore';
|
|
4
|
+
import { CSS_VARS_CHANGE_EVENT, type CssVarsChangeDetail } from '../core/cssVarSync';
|
|
5
|
+
import {
|
|
6
|
+
fontWeightAvailability,
|
|
7
|
+
inferFontFamilyVariable,
|
|
8
|
+
} from '../core/fonts/fontWeightAvailability';
|
|
2
9
|
import UIVariantSelector from './UIVariantSelector.svelte';
|
|
3
10
|
import UIOptionItem from './UIOptionItem.svelte';
|
|
4
11
|
|
|
@@ -31,6 +38,44 @@
|
|
|
31
38
|
{ key: 'extrabold', label: 'Extra Bold', value: '800' },
|
|
32
39
|
{ key: 'black', label: 'Black', value: '900' },
|
|
33
40
|
] as const;
|
|
41
|
+
|
|
42
|
+
let availability = $state<ReturnType<typeof fontWeightAvailability>>(null);
|
|
43
|
+
let familyVariable = $derived(inferFontFamilyVariable(variable));
|
|
44
|
+
|
|
45
|
+
function refreshAvailability() {
|
|
46
|
+
if (!familyVariable) {
|
|
47
|
+
availability = null;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const familyValue = getComputedStyle(document.documentElement)
|
|
51
|
+
.getPropertyValue(familyVariable)
|
|
52
|
+
.trim();
|
|
53
|
+
availability = fontWeightAvailability(
|
|
54
|
+
familyValue,
|
|
55
|
+
$editorState.fonts.sources,
|
|
56
|
+
$editorState.fonts.stacks,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function handleVarChange(event: Event) {
|
|
61
|
+
const names = (event as CustomEvent<CssVarsChangeDetail>).detail?.names ?? [];
|
|
62
|
+
if (
|
|
63
|
+
(familyVariable && names.includes(familyVariable))
|
|
64
|
+
|| names.some((name) => /^--font-(?:display|sans|serif|mono)$/.test(name))
|
|
65
|
+
) {
|
|
66
|
+
refreshAvailability();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
$effect(() => {
|
|
71
|
+
variable;
|
|
72
|
+
$editorState.fonts.sources;
|
|
73
|
+
$editorState.fonts.stacks;
|
|
74
|
+
refreshAvailability();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
onMount(() => document.addEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange));
|
|
78
|
+
onDestroy(() => document.removeEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange));
|
|
34
79
|
</script>
|
|
35
80
|
|
|
36
81
|
<UIVariantSelector
|
|
@@ -44,8 +89,13 @@
|
|
|
44
89
|
{onchange}
|
|
45
90
|
>
|
|
46
91
|
{#snippet option({ opt, active, select })}
|
|
47
|
-
|
|
48
|
-
<UIOptionItem
|
|
92
|
+
{@const unsupported = availability !== null && !availability.weights.has(Number(opt.value))}
|
|
93
|
+
<UIOptionItem
|
|
94
|
+
{active}
|
|
95
|
+
disabled={unsupported}
|
|
96
|
+
title={unsupported ? `${opt.label} is not available for ${availability?.familyName}` : ''}
|
|
97
|
+
onclick={select}
|
|
98
|
+
>
|
|
49
99
|
{#snippet preview()}
|
|
50
100
|
<span class="weight-sample" style="font-weight: var(--font-weight-{opt.key});">A</span>
|
|
51
101
|
{/snippet}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
interface Props {
|
|
3
3
|
active?: boolean;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
title?: string;
|
|
4
6
|
preview?: import('svelte').Snippet;
|
|
5
7
|
label?: import('svelte').Snippet;
|
|
6
8
|
meta?: import('svelte').Snippet;
|
|
@@ -9,6 +11,8 @@
|
|
|
9
11
|
|
|
10
12
|
let {
|
|
11
13
|
active = false,
|
|
14
|
+
disabled = false,
|
|
15
|
+
title = '',
|
|
12
16
|
preview,
|
|
13
17
|
label,
|
|
14
18
|
meta,
|
|
@@ -16,7 +20,7 @@
|
|
|
16
20
|
}: Props = $props();
|
|
17
21
|
</script>
|
|
18
22
|
|
|
19
|
-
<button class="ui-option-item" class:active {onclick}>
|
|
23
|
+
<button class="ui-option-item" class:active {onclick} {disabled} {title}>
|
|
20
24
|
{#if preview}
|
|
21
25
|
<span class="ui-option-preview">
|
|
22
26
|
{@render preview?.()}
|
|
@@ -64,6 +68,15 @@
|
|
|
64
68
|
color: var(--ui-text-primary);
|
|
65
69
|
}
|
|
66
70
|
|
|
71
|
+
.ui-option-item:disabled {
|
|
72
|
+
opacity: 0.4;
|
|
73
|
+
cursor: not-allowed;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ui-option-item:disabled:hover {
|
|
77
|
+
background: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
.ui-option-preview {
|
|
68
81
|
flex-shrink: 0;
|
|
69
82
|
display: inline-flex;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import UITokenSelector from './UITokenSelector.svelte';
|
|
8
8
|
import UIOptionList from './UIOptionList.svelte';
|
|
9
9
|
import UIOptionItem from './UIOptionItem.svelte';
|
|
10
|
-
import { setCssVar, removeCssVar,
|
|
10
|
+
import { setCssVar, removeCssVar, CSS_VARS_CHANGE_EVENT } from '../core/cssVarSync';
|
|
11
11
|
import {
|
|
12
12
|
editorState,
|
|
13
13
|
setComponentAlias,
|
|
@@ -207,11 +207,11 @@
|
|
|
207
207
|
|
|
208
208
|
onMount(() => {
|
|
209
209
|
refreshFromState();
|
|
210
|
-
document.addEventListener(
|
|
210
|
+
document.addEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange);
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
onDestroy(() => {
|
|
214
|
-
document.removeEventListener(
|
|
214
|
+
document.removeEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange);
|
|
215
215
|
});
|
|
216
216
|
|
|
217
217
|
// Track `variable` alongside `$editorState` so a VariantGroup tabs view that
|
|
@@ -396,6 +396,7 @@
|
|
|
396
396
|
<button
|
|
397
397
|
type="button"
|
|
398
398
|
class="merge-btn"
|
|
399
|
+
data-padding-parent-variable={variable}
|
|
399
400
|
onclick={mergeToSingle}
|
|
400
401
|
title="Use the same value for all sides"
|
|
401
402
|
disabled={disabled || selectionsLocked}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { untrack } from 'svelte';
|
|
4
3
|
import { slide } from 'svelte/transition';
|
|
5
4
|
import { cubicOut, cubicIn } from 'svelte/easing';
|
|
6
5
|
import { resolveAliasChain } from '../core/palettes/tokenRegistry';
|
|
@@ -528,13 +527,14 @@
|
|
|
528
527
|
// The wrapper UITokenSelector forwards `var-change` only for the currently
|
|
529
528
|
// bound variable, so prop swaps wouldn't otherwise refresh `chosenCategory`
|
|
530
529
|
// / `chosenFamily` / `chosenStep` and the meta label drifts from the swatch.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
530
|
+
$effect(() => {
|
|
531
|
+
// Track only the prop. The initialization writes local display state and
|
|
532
|
+
// must not subscribe the effect to that same state.
|
|
533
|
+
variable;
|
|
534
|
+
untrack(() => {
|
|
535
535
|
initFromCurrent();
|
|
536
536
|
captureSelfDefault();
|
|
537
|
-
}
|
|
537
|
+
});
|
|
538
538
|
});
|
|
539
539
|
|
|
540
540
|
let chosenGradientToken = $derived(chosenGradient ? getGradientToken(chosenGradient) : undefined);
|
|
@@ -542,8 +542,11 @@
|
|
|
542
542
|
let effectiveAngle = $derived(chosenGradientToken
|
|
543
543
|
? (chosenAngle ?? chosenGradientToken.angle)
|
|
544
544
|
: 0);
|
|
545
|
-
|
|
546
|
-
|
|
545
|
+
$effect(() => {
|
|
546
|
+
const nextAngle = effectiveAngle;
|
|
547
|
+
untrack(() => {
|
|
548
|
+
angleInput = nextAngle;
|
|
549
|
+
});
|
|
547
550
|
});
|
|
548
551
|
|
|
549
552
|
let metaLabel = $derived(chosenNone
|
|
@@ -560,10 +563,14 @@
|
|
|
560
563
|
? allCategories.filter(c => c.id !== 'text' || familiesWithText.includes(selectedFamily!))
|
|
561
564
|
: allCategories);
|
|
562
565
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
566
|
+
$effect(() => {
|
|
567
|
+
const family = selectedFamily;
|
|
568
|
+
const tabs = availableTabs;
|
|
569
|
+
untrack(() => {
|
|
570
|
+
if (family && !tabs.find(t => t.id === selectedTab)) {
|
|
571
|
+
selectedTab = 'palette';
|
|
572
|
+
}
|
|
573
|
+
});
|
|
567
574
|
});
|
|
568
575
|
</script>
|
|
569
576
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, onDestroy } from 'svelte';
|
|
3
|
-
import { self } from 'svelte/legacy';
|
|
4
3
|
import UIRadio from './UIRadio.svelte';
|
|
5
4
|
|
|
6
5
|
type Candidate = { variable: string; alias: string };
|
|
@@ -73,8 +72,13 @@
|
|
|
73
72
|
});
|
|
74
73
|
</script>
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
<
|
|
75
|
+
<div class="ui-relink-backdrop">
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
class="ui-relink-dismiss"
|
|
79
|
+
aria-label="Cancel linking"
|
|
80
|
+
onclick={handleCancel}
|
|
81
|
+
></button>
|
|
78
82
|
<div
|
|
79
83
|
class="ui-relink-dialog"
|
|
80
84
|
role="dialog"
|
|
@@ -135,7 +139,18 @@
|
|
|
135
139
|
z-index: 10000;
|
|
136
140
|
}
|
|
137
141
|
|
|
142
|
+
.ui-relink-dismiss {
|
|
143
|
+
position: absolute;
|
|
144
|
+
inset: 0;
|
|
145
|
+
padding: 0;
|
|
146
|
+
border: 0;
|
|
147
|
+
background: transparent;
|
|
148
|
+
cursor: default;
|
|
149
|
+
}
|
|
150
|
+
|
|
138
151
|
.ui-relink-dialog {
|
|
152
|
+
position: relative;
|
|
153
|
+
z-index: 1;
|
|
139
154
|
background: var(--ui-surface-higher);
|
|
140
155
|
border: 1px solid var(--ui-border-high);
|
|
141
156
|
border-radius: var(--ui-radius-md);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, onDestroy } from 'svelte';
|
|
3
|
-
import {
|
|
3
|
+
import { CSS_VARS_CHANGE_EVENT, type CssVarsChangeDetail } from '../core/cssVarSync';
|
|
4
4
|
import UITokenSelector from './UITokenSelector.svelte';
|
|
5
5
|
import UIOptionList from './UIOptionList.svelte';
|
|
6
6
|
import UIOptionItem from './UIOptionItem.svelte';
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function handleVarChange(e: Event) {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
43
|
+
const names = (e as CustomEvent<CssVarsChangeDetail>).detail?.names ?? [];
|
|
44
|
+
if (names.includes(variable)) readResolved();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function select(keyword: string, close: () => void) {
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
|
|
59
59
|
onMount(() => {
|
|
60
60
|
readResolved();
|
|
61
|
-
document.addEventListener(
|
|
61
|
+
document.addEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange);
|
|
62
62
|
});
|
|
63
63
|
onDestroy(() => {
|
|
64
|
-
document.removeEventListener(
|
|
64
|
+
document.removeEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
let activeKey = $derived(options.find((o) => o.key === current)?.key ?? null);
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, onDestroy } from 'svelte';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
setCssVar,
|
|
6
|
+
removeCssVar,
|
|
7
|
+
CSS_VARS_CHANGE_EVENT,
|
|
8
|
+
type CssVarsChangeDetail,
|
|
9
|
+
} from '../core/cssVarSync';
|
|
5
10
|
import type { CssVarRef } from '../core/store/editorTypes';
|
|
6
11
|
import { cssStringToRef } from '../core/store/cssVarRef';
|
|
7
12
|
import {
|
|
@@ -98,6 +103,18 @@
|
|
|
98
103
|
let relinkOpen = $state(false);
|
|
99
104
|
let relinkCandidates: { variable: string; alias: string }[] = $state([]);
|
|
100
105
|
|
|
106
|
+
/** Keep overflow metadata available through the native title tooltip without
|
|
107
|
+
* attaching a mouse-only interaction handler to otherwise static text. */
|
|
108
|
+
function mirrorTextToTitle(node: HTMLElement) {
|
|
109
|
+
const sync = () => {
|
|
110
|
+
node.title = node.textContent?.trim() ?? '';
|
|
111
|
+
};
|
|
112
|
+
sync();
|
|
113
|
+
const observer = new MutationObserver(sync);
|
|
114
|
+
observer.observe(node, { childList: true, characterData: true, subtree: true });
|
|
115
|
+
return { destroy: () => observer.disconnect() };
|
|
116
|
+
}
|
|
117
|
+
|
|
101
118
|
let isLinkedFromData = $derived(canBeLinked && component && $editorState
|
|
102
119
|
? isComponentPropertyLinked(component, variable)
|
|
103
120
|
: false);
|
|
@@ -251,22 +268,27 @@
|
|
|
251
268
|
}
|
|
252
269
|
|
|
253
270
|
function handleVarChange(e: Event) {
|
|
254
|
-
const
|
|
255
|
-
if (
|
|
271
|
+
const names = (e as CustomEvent<CssVarsChangeDetail>).detail?.names ?? [];
|
|
272
|
+
if (names.includes(variable)) onvarChange?.();
|
|
256
273
|
}
|
|
257
274
|
|
|
258
275
|
onMount(() => {
|
|
259
276
|
document.addEventListener('click', handleClickOutside, true);
|
|
260
|
-
document.addEventListener(
|
|
277
|
+
document.addEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange);
|
|
261
278
|
});
|
|
262
279
|
|
|
263
280
|
onDestroy(() => {
|
|
264
281
|
document.removeEventListener('click', handleClickOutside, true);
|
|
265
|
-
document.removeEventListener(
|
|
282
|
+
document.removeEventListener(CSS_VARS_CHANGE_EVENT, handleVarChange);
|
|
266
283
|
});
|
|
267
284
|
</script>
|
|
268
285
|
|
|
269
|
-
<div
|
|
286
|
+
<div
|
|
287
|
+
class="ui-token-selector"
|
|
288
|
+
class:disabled
|
|
289
|
+
data-token-variable={variable}
|
|
290
|
+
bind:this={container}
|
|
291
|
+
>
|
|
270
292
|
<div class="ui-ts-trigger-wrap">
|
|
271
293
|
<button
|
|
272
294
|
class="ui-ts-trigger"
|
|
@@ -340,7 +362,7 @@
|
|
|
340
362
|
{#if triggerMeta}
|
|
341
363
|
<span
|
|
342
364
|
class="ui-ts-meta-text"
|
|
343
|
-
|
|
365
|
+
use:mirrorTextToTitle
|
|
344
366
|
>{@render triggerMeta()}</span>
|
|
345
367
|
{/if}
|
|
346
368
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Spring Meadow",
|
|
3
3
|
"createdAt": "2026-08-13T10:21:46.867Z",
|
|
4
|
-
"updatedAt": "2026-08-
|
|
4
|
+
"updatedAt": "2026-08-17T20:21:26.098Z",
|
|
5
5
|
"schemaVersion": 3,
|
|
6
6
|
"colorsAndType": {
|
|
7
7
|
"name": "Spring Meadow",
|
|
@@ -3433,10 +3433,10 @@
|
|
|
3433
3433
|
"sectiondivider": {
|
|
3434
3434
|
"name": "spring-meadow",
|
|
3435
3435
|
"component": "sectiondivider",
|
|
3436
|
-
"createdAt": "2026-
|
|
3437
|
-
"updatedAt": "2026-08-
|
|
3436
|
+
"createdAt": "2026-08-17T20:21:22.937Z",
|
|
3437
|
+
"updatedAt": "2026-08-17T20:21:22.937Z",
|
|
3438
3438
|
"aliases": {
|
|
3439
|
-
"--sectiondivider-lg-title-font-family": "--font-
|
|
3439
|
+
"--sectiondivider-lg-title-font-family": "--font-sans",
|
|
3440
3440
|
"--sectiondivider-lg-title-font-weight": "--font-weight-bold",
|
|
3441
3441
|
"--sectiondivider-lg-title-font-size": "--font-size-5xl",
|
|
3442
3442
|
"--sectiondivider-lg-title-line-height": "--line-height-normal",
|
|
@@ -3465,9 +3465,9 @@
|
|
|
3465
3465
|
"--sectiondivider-lg-border": "--color-transparent",
|
|
3466
3466
|
"--sectiondivider-lg-title-outline-color": "--surface-canvas-lowest",
|
|
3467
3467
|
"--sectiondivider-lg-hairline-color": "--border-brand-medium",
|
|
3468
|
-
"--sectiondivider-md-title-font-family": "--font-
|
|
3469
|
-
"--sectiondivider-md-title-font-weight": "--font-weight-
|
|
3470
|
-
"--sectiondivider-md-title-font-size": "--font-size-
|
|
3468
|
+
"--sectiondivider-md-title-font-family": "--font-sans",
|
|
3469
|
+
"--sectiondivider-md-title-font-weight": "--font-weight-extrabold",
|
|
3470
|
+
"--sectiondivider-md-title-font-size": "--font-size-5xl",
|
|
3471
3471
|
"--sectiondivider-md-title-line-height": "--line-height-normal",
|
|
3472
3472
|
"--sectiondivider-md-title-letter-spacing": "--letter-spacing-normal",
|
|
3473
3473
|
"--sectiondivider-md-title-outline-width": "--border-width-4",
|
|
@@ -3488,13 +3488,13 @@
|
|
|
3488
3488
|
"--sectiondivider-md-shadow": "--shadow-none",
|
|
3489
3489
|
"--sectiondivider-md-hairline-thickness": "--border-width-1",
|
|
3490
3490
|
"--sectiondivider-md-background": "--color-transparent",
|
|
3491
|
-
"--sectiondivider-md-title": "--
|
|
3491
|
+
"--sectiondivider-md-title": "--color-brand-700",
|
|
3492
3492
|
"--sectiondivider-md-description": "--text-secondary",
|
|
3493
3493
|
"--sectiondivider-md-eyebrow": "--text-tertiary",
|
|
3494
3494
|
"--sectiondivider-md-border": "--color-transparent",
|
|
3495
|
-
"--sectiondivider-md-title-outline-color": "--
|
|
3495
|
+
"--sectiondivider-md-title-outline-color": "color-mix(in srgb, var(--color-white) 34%, transparent)",
|
|
3496
3496
|
"--sectiondivider-md-hairline-color": "--border-brand-medium",
|
|
3497
|
-
"--sectiondivider-sm-title-font-family": "--font-
|
|
3497
|
+
"--sectiondivider-sm-title-font-family": "--font-sans",
|
|
3498
3498
|
"--sectiondivider-sm-title-font-weight": "--font-weight-medium",
|
|
3499
3499
|
"--sectiondivider-sm-title-font-size": "--font-size-3xl",
|
|
3500
3500
|
"--sectiondivider-sm-title-line-height": "--line-height-normal",
|
|
@@ -3523,7 +3523,9 @@
|
|
|
3523
3523
|
"--sectiondivider-sm-border": "--color-transparent",
|
|
3524
3524
|
"--sectiondivider-sm-title-outline-color": "color-mix(in srgb, var(--surface-neutral-lowest) 25%, transparent)",
|
|
3525
3525
|
"--sectiondivider-sm-hairline-color": "color-mix(in srgb, var(--border-brand-medium) 50%, transparent)"
|
|
3526
|
-
}
|
|
3526
|
+
},
|
|
3527
|
+
"config": {},
|
|
3528
|
+
"schemaVersion": 21
|
|
3527
3529
|
},
|
|
3528
3530
|
"segmentedcontrol": {
|
|
3529
3531
|
"name": "spring-meadow",
|
|
@@ -3901,4 +3903,4 @@
|
|
|
3901
3903
|
}
|
|
3902
3904
|
}
|
|
3903
3905
|
}
|
|
3904
|
-
}
|
|
3906
|
+
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
/* --- Central box ---------------------------------------------------------- */
|
|
19
19
|
.ftt-box {
|
|
20
|
+
--ftt-box-contrast-color: var(--color-white);
|
|
20
21
|
position: absolute;
|
|
21
22
|
left: 50%;
|
|
22
23
|
top: 50%;
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
font-family: Manrope, system-ui, -apple-system, sans-serif;
|
|
47
48
|
font-size: 1.25rem;
|
|
48
49
|
font-weight: 700;
|
|
49
|
-
color:
|
|
50
|
+
color: var(--ftt-box-contrast-color);
|
|
50
51
|
white-space: nowrap;
|
|
51
52
|
user-select: none;
|
|
52
53
|
pointer-events: none;
|
|
@@ -191,6 +191,7 @@
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
let lastPageBackground = '';
|
|
194
|
+
let lastBoxBackground = '';
|
|
194
195
|
function syncConnectorContrast() {
|
|
195
196
|
if (!stageEl) return;
|
|
196
197
|
const styles = getComputedStyle(stageEl);
|
|
@@ -201,11 +202,21 @@
|
|
|
201
202
|
stageEl.style.setProperty('--ftt-contrast-color', `var(${token})`);
|
|
202
203
|
}
|
|
203
204
|
|
|
205
|
+
function syncBoxLabelContrast() {
|
|
206
|
+
if (!boxEl) return;
|
|
207
|
+
const background = getComputedStyle(boxEl).backgroundColor;
|
|
208
|
+
if (background === lastBoxBackground) return;
|
|
209
|
+
lastBoxBackground = background;
|
|
210
|
+
const token = contrastTokenForBackground(background);
|
|
211
|
+
boxEl.style.setProperty('--ftt-box-contrast-color', `var(${token})`);
|
|
212
|
+
}
|
|
213
|
+
|
|
204
214
|
// Kite strings and energy balls are recomputed each frame from the box's
|
|
205
215
|
// measured rect so intrinsic sizing drives anchor placement.
|
|
206
216
|
function syncFrame() {
|
|
207
217
|
if (!stageEl) return;
|
|
208
218
|
syncConnectorContrast();
|
|
219
|
+
syncBoxLabelContrast();
|
|
209
220
|
const stageRect = stageEl.getBoundingClientRect();
|
|
210
221
|
if (stageRect.width === 0 || stageRect.height === 0) return;
|
|
211
222
|
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
/** Content images default to lazy; pass `'eager'` for above-the-fold heroes. */
|
|
18
18
|
loading?: 'lazy' | 'eager';
|
|
19
19
|
decoding?: 'async' | 'sync' | 'auto';
|
|
20
|
+
/** Editor preview hook: paint the hover transform without pointer input. */
|
|
21
|
+
forceHover?: boolean;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
let {
|
|
@@ -29,7 +31,8 @@
|
|
|
29
31
|
srcset = undefined,
|
|
30
32
|
sizes = undefined,
|
|
31
33
|
loading = 'lazy',
|
|
32
|
-
decoding = 'async'
|
|
34
|
+
decoding = 'async',
|
|
35
|
+
forceHover = false,
|
|
33
36
|
}: Props = $props();
|
|
34
37
|
|
|
35
38
|
const variantHeights: Record<string, string | undefined> = {
|
|
@@ -60,6 +63,7 @@
|
|
|
60
63
|
|
|
61
64
|
<div
|
|
62
65
|
class="image"
|
|
66
|
+
class:force-hover={forceHover}
|
|
63
67
|
style:height={resolvedHeight}
|
|
64
68
|
style:--image-zoom-hover={contentHover}
|
|
65
69
|
style:--image-grow-hover={frameHover}
|
|
@@ -100,11 +104,13 @@
|
|
|
100
104
|
transition: transform var(--duration-300) var(--ease-out-cubic);
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
.image:hover
|
|
107
|
+
.image:hover,
|
|
108
|
+
.image.force-hover {
|
|
104
109
|
transform: var(--image-grow-hover);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
.image:hover img
|
|
112
|
+
.image:hover img,
|
|
113
|
+
.image.force-hover img {
|
|
108
114
|
transform: var(--image-zoom-hover);
|
|
109
115
|
}
|
|
110
116
|
</style>
|
|
@@ -585,6 +585,8 @@
|
|
|
585
585
|
style:--imagelightbox-tile-object-fit={fit}
|
|
586
586
|
type="button"
|
|
587
587
|
aria-label={cover?.alt ? `Expand image: ${cover.alt}` : 'Expand image'}
|
|
588
|
+
aria-haspopup="dialog"
|
|
589
|
+
aria-expanded={open}
|
|
588
590
|
onclick={openLightbox}
|
|
589
591
|
>
|
|
590
592
|
<img src={cover?.src} alt={cover?.alt} draggable="false" onload={onCoverLoad} />
|
|
@@ -63,11 +63,28 @@
|
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
onMount(() => {
|
|
66
|
+
let mounted = true;
|
|
67
|
+
const sync = () => { measure(); syncFilter(); };
|
|
66
68
|
measure();
|
|
67
69
|
syncFilter();
|
|
68
|
-
const obs = new MutationObserver(
|
|
70
|
+
const obs = new MutationObserver(sync);
|
|
69
71
|
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] });
|
|
70
|
-
|
|
72
|
+
|
|
73
|
+
// A font-family variable changes before its webfont necessarily finishes
|
|
74
|
+
// loading. Re-run getBBox when the real face becomes available so the SVG
|
|
75
|
+
// viewBox and outline do not stay sized for the fallback font.
|
|
76
|
+
const fonts = document.fonts;
|
|
77
|
+
const onFontsLoaded = () => {
|
|
78
|
+
if (mounted) sync();
|
|
79
|
+
};
|
|
80
|
+
fonts?.addEventListener('loadingdone', onFontsLoaded);
|
|
81
|
+
void fonts?.ready.then(onFontsLoaded);
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
mounted = false;
|
|
85
|
+
obs.disconnect();
|
|
86
|
+
fonts?.removeEventListener('loadingdone', onFontsLoaded);
|
|
87
|
+
};
|
|
71
88
|
});
|
|
72
89
|
</script>
|
|
73
90
|
|
|
@@ -531,6 +548,7 @@
|
|
|
531
548
|
font-family: var(--_divider-title-font-family);
|
|
532
549
|
font-weight: var(--_divider-title-font-weight);
|
|
533
550
|
font-size: var(--_divider-title-font-size);
|
|
551
|
+
line-height: var(--_divider-title-line-height);
|
|
534
552
|
letter-spacing: var(--_divider-title-letter-spacing);
|
|
535
553
|
fill: currentColor;
|
|
536
554
|
}
|