@motion-proto/live-tokens 0.94.1 → 0.94.3
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 +26 -0
- package/package.json +1 -1
- package/src/demo/ChromeSelect.svelte +111 -0
- package/src/demo/Demo.svelte +0 -12
- package/src/demo/DemoBar.svelte +10 -9
- package/src/demo/SketchSelect.svelte +36 -12
- package/src/demo/ThemeSelect.svelte +34 -11
- package/src/demo/sections/SectionOffering.svelte +3 -45
- package/src/editor/ui/PaletteEditor.svelte +4 -1
- package/src/system/styles/CONVENTIONS.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.94.3 — Demo bar chrome
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The demo's Theme and Sketch bar stays legible in every theme.** The bar
|
|
8
|
+
painted from the open theme's scrim, and its selects were themed Buttons, so
|
|
9
|
+
a theme or sketchstyle could leave its labels unreadable. The bar now paints
|
|
10
|
+
as editor chrome from the `--ui-*` tokens, like the editor overlay, and
|
|
11
|
+
sketch mode leaves it alone. `ThemeSelect` and `SketchSelect` take a
|
|
12
|
+
`chrome` prop for the bar. Elsewhere they stay themed.
|
|
13
|
+
|
|
14
|
+
## 0.94.2 — Palette step confirm
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **Confirming a palette step after a curve change saves the override.** A
|
|
19
|
+
step left open while a lightness, saturation, or hue curve moved held its
|
|
20
|
+
draft in a deep reactive proxy. Confirm wrote that proxy into the store,
|
|
21
|
+
and the next edit failed to clone the state. The editor now keeps the draft
|
|
22
|
+
as a plain value.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **The demo page's closing quote stands on its own.** The illustration beside
|
|
27
|
+
it is gone, and the quote sits centered between Layers and Get Started.
|
|
28
|
+
|
|
3
29
|
## 0.94.1 — Adopt after a save
|
|
4
30
|
|
|
5
31
|
### Fixed
|
package/package.json
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import UIMenuButton from '../editor/ui/UIMenuButton.svelte';
|
|
3
|
+
import UIOptionList from '../editor/ui/UIOptionList.svelte';
|
|
4
|
+
import UIOptionItem from '../editor/ui/UIOptionItem.svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
label: string;
|
|
8
|
+
items: { value: string; label: string }[];
|
|
9
|
+
value: string;
|
|
10
|
+
/** Trigger text when `value` matches no item. Defaults to the label. */
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
onchange: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
label: heading,
|
|
19
|
+
items,
|
|
20
|
+
value,
|
|
21
|
+
placeholder = heading,
|
|
22
|
+
disabled = false,
|
|
23
|
+
error = '',
|
|
24
|
+
onchange,
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
const selectedLabel = $derived(items.find((item) => item.value === value)?.label ?? placeholder);
|
|
28
|
+
|
|
29
|
+
function select(next: string, close: () => void): void {
|
|
30
|
+
close();
|
|
31
|
+
if (next === value) return;
|
|
32
|
+
onchange(next);
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<!-- LabeledSelect's twin in the editor's --ui-* chrome. It needs an
|
|
37
|
+
.editor-page ancestor, which scopes those tokens and hosts the menu. -->
|
|
38
|
+
<div class="chrome-select" inert={disabled || items.length === 0}>
|
|
39
|
+
<span class="chrome-select-label">{heading}</span>
|
|
40
|
+
|
|
41
|
+
<UIMenuButton header={heading} triggerClass="chrome-select-trigger" menuMinWidth="12rem">
|
|
42
|
+
{#snippet trigger()}
|
|
43
|
+
<span>{selectedLabel}</span>
|
|
44
|
+
<i class="fas fa-chevron-down" aria-hidden="true"></i>
|
|
45
|
+
{/snippet}
|
|
46
|
+
{#snippet children({ close })}
|
|
47
|
+
<UIOptionList>
|
|
48
|
+
{#each items as item (item.value)}
|
|
49
|
+
<UIOptionItem active={item.value === value} onclick={() => select(item.value, close)}>
|
|
50
|
+
{#snippet label()}{item.label}{/snippet}
|
|
51
|
+
</UIOptionItem>
|
|
52
|
+
{/each}
|
|
53
|
+
</UIOptionList>
|
|
54
|
+
{/snippet}
|
|
55
|
+
</UIMenuButton>
|
|
56
|
+
|
|
57
|
+
{#if error}
|
|
58
|
+
<span class="error" aria-live="polite">{error}</span>
|
|
59
|
+
{/if}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<style>
|
|
63
|
+
.chrome-select {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-wrap: wrap;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: var(--ui-space-8);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.chrome-select[inert] {
|
|
71
|
+
opacity: var(--ui-opacity-disabled);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.chrome-select-label {
|
|
75
|
+
font-size: var(--ui-font-size-xs);
|
|
76
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
77
|
+
color: var(--ui-text-secondary);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.chrome-select :global(.chrome-select-trigger) {
|
|
81
|
+
min-width: 9rem;
|
|
82
|
+
justify-content: space-between;
|
|
83
|
+
gap: var(--ui-space-12);
|
|
84
|
+
padding: var(--ui-space-6) var(--ui-space-10);
|
|
85
|
+
font-size: var(--ui-font-size-xs);
|
|
86
|
+
color: var(--ui-text-primary);
|
|
87
|
+
background: var(--ui-surface-high);
|
|
88
|
+
border: 1px solid var(--ui-border);
|
|
89
|
+
border-radius: var(--ui-radius-md);
|
|
90
|
+
transition:
|
|
91
|
+
background var(--ui-transition-fast),
|
|
92
|
+
border-color var(--ui-transition-fast);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.chrome-select :global(.chrome-select-trigger:hover),
|
|
96
|
+
.chrome-select :global(.chrome-select-trigger[aria-expanded='true']) {
|
|
97
|
+
background: var(--ui-surface-highest);
|
|
98
|
+
border-color: var(--ui-border-higher);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.chrome-select :global(.chrome-select-trigger .fa-chevron-down) {
|
|
102
|
+
font-size: 0.625rem;
|
|
103
|
+
color: var(--ui-text-secondary);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.error {
|
|
107
|
+
max-width: 20rem;
|
|
108
|
+
font-size: var(--ui-font-size-xs);
|
|
109
|
+
color: var(--ui-link-broken);
|
|
110
|
+
}
|
|
111
|
+
</style>
|
package/src/demo/Demo.svelte
CHANGED
|
@@ -64,14 +64,6 @@
|
|
|
64
64
|
margin-top: var(--space-32);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/* Overlap the offering image up into Layers (top) and butt Get Started
|
|
68
|
-
against its bottom. -space-64 cancels the grid row-gap; the extra
|
|
69
|
-
-space-64 on top is the actual overlap — tune that second term to taste. */
|
|
70
|
-
.kit > :global(.offering) {
|
|
71
|
-
margin-top: calc(var(--space-64) * -2 + var(--space-8));
|
|
72
|
-
margin-bottom: calc(var(--space-64) * -1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
67
|
/* Inline code styling cascades into every section via :global descendants. */
|
|
76
68
|
.kit :global(p > code),
|
|
77
69
|
.kit :global(.callout code) {
|
|
@@ -89,9 +81,5 @@
|
|
|
89
81
|
padding: var(--space-32) var(--space-16);
|
|
90
82
|
row-gap: var(--space-48);
|
|
91
83
|
}
|
|
92
|
-
.kit > :global(.offering) {
|
|
93
|
-
margin-top: calc(var(--space-48) * -2);
|
|
94
|
-
margin-bottom: calc(var(--space-48) * -1);
|
|
95
|
-
}
|
|
96
84
|
}
|
|
97
85
|
</style>
|
package/src/demo/DemoBar.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import '../editor/styles/ui-editor.css';
|
|
2
3
|
import ThemeSelect from './ThemeSelect.svelte';
|
|
3
4
|
import SketchSelect from './SketchSelect.svelte';
|
|
4
5
|
|
|
@@ -11,11 +12,14 @@
|
|
|
11
12
|
} = $props();
|
|
12
13
|
</script>
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
<!-- Editor chrome, like the overlay: the bar paints from --ui-*, so every
|
|
16
|
+
theme leaves it legible, and data-live-tokens-chrome keeps sketch mode off
|
|
17
|
+
it. .editor-page scopes the --ui-* tokens and hosts the selects' menus. -->
|
|
18
|
+
<div class="demo-bar editor-page" data-live-tokens-chrome>
|
|
15
19
|
<div class="bar">
|
|
16
20
|
<div class="pickers">
|
|
17
|
-
<ThemeSelect
|
|
18
|
-
<SketchSelect
|
|
21
|
+
<ThemeSelect chrome onpick={onThemePick} />
|
|
22
|
+
<SketchSelect chrome onpick={onSketchPick} />
|
|
19
23
|
</div>
|
|
20
24
|
</div>
|
|
21
25
|
</div>
|
|
@@ -27,9 +31,9 @@
|
|
|
27
31
|
left: 0;
|
|
28
32
|
right: 0;
|
|
29
33
|
z-index: var(--z-sticky);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
border-bottom:
|
|
34
|
+
--sketch-icon-off: none;
|
|
35
|
+
background: var(--ui-surface-lower);
|
|
36
|
+
border-bottom: 1px solid var(--ui-border-low);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/* The bar repeats the demo's .kit grid, so its content lines up with the
|
|
@@ -64,9 +68,6 @@
|
|
|
64
68
|
@media (max-width: 600px) {
|
|
65
69
|
.demo-bar {
|
|
66
70
|
position: static;
|
|
67
|
-
background: none;
|
|
68
|
-
backdrop-filter: none;
|
|
69
|
-
border-bottom: none;
|
|
70
71
|
}
|
|
71
72
|
.bar {
|
|
72
73
|
padding: var(--space-64) var(--space-16) 0;
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import LabeledSelect from './LabeledSelect.svelte';
|
|
3
|
+
import ChromeSelect from './ChromeSelect.svelte';
|
|
3
4
|
import { setSketch, sketchPick, sketchStyles, unsavedSketchStyle } from '../editor/core/sketch';
|
|
4
5
|
|
|
5
|
-
let {
|
|
6
|
+
let {
|
|
7
|
+
inline = false,
|
|
8
|
+
size = 'default',
|
|
9
|
+
chrome = false,
|
|
10
|
+
onpick,
|
|
11
|
+
}: {
|
|
12
|
+
inline?: boolean;
|
|
13
|
+
size?: 'default' | 'small';
|
|
14
|
+
/** Paint in the editor's --ui-* chrome; needs an .editor-page ancestor. */
|
|
15
|
+
chrome?: boolean;
|
|
16
|
+
onpick?: (id: string | null) => void;
|
|
17
|
+
} = $props();
|
|
6
18
|
|
|
7
19
|
// The empty string is the placeholder's value, so off needs a name of its own.
|
|
8
20
|
const OFF = 'none';
|
|
@@ -45,14 +57,26 @@
|
|
|
45
57
|
}
|
|
46
58
|
</script>
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
{#if chrome}
|
|
61
|
+
<ChromeSelect
|
|
62
|
+
label="Sketch"
|
|
63
|
+
{items}
|
|
64
|
+
value={selected}
|
|
65
|
+
placeholder="Custom"
|
|
66
|
+
disabled={busy}
|
|
67
|
+
{error}
|
|
68
|
+
onchange={changeSketch}
|
|
69
|
+
/>
|
|
70
|
+
{:else}
|
|
71
|
+
<LabeledSelect
|
|
72
|
+
{inline}
|
|
73
|
+
{size}
|
|
74
|
+
label="Sketch"
|
|
75
|
+
{items}
|
|
76
|
+
value={selected}
|
|
77
|
+
placeholder="Custom"
|
|
78
|
+
disabled={busy}
|
|
79
|
+
{error}
|
|
80
|
+
onchange={changeSketch}
|
|
81
|
+
/>
|
|
82
|
+
{/if}
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import LabeledSelect from './LabeledSelect.svelte';
|
|
4
|
+
import ChromeSelect from './ChromeSelect.svelte';
|
|
4
5
|
import { listThemes, applyTheme } from '../editor/core/themes/themeService';
|
|
5
6
|
import { openThemeSlug } from '../editor/core/store/editorConfigStore';
|
|
6
7
|
import { carrySketch } from './sketchCarry';
|
|
7
8
|
|
|
8
|
-
let {
|
|
9
|
+
let {
|
|
10
|
+
inline = false,
|
|
11
|
+
size = 'default',
|
|
12
|
+
chrome = false,
|
|
13
|
+
onpick,
|
|
14
|
+
}: {
|
|
15
|
+
inline?: boolean;
|
|
16
|
+
size?: 'default' | 'small';
|
|
17
|
+
/** Paint in the editor's --ui-* chrome; needs an .editor-page ancestor. */
|
|
18
|
+
chrome?: boolean;
|
|
19
|
+
onpick?: (fileName: string) => void;
|
|
20
|
+
} = $props();
|
|
9
21
|
|
|
10
22
|
let themes = $state<{ value: string; label: string }[]>([]);
|
|
11
23
|
let busy = $state(false);
|
|
@@ -40,13 +52,24 @@
|
|
|
40
52
|
|
|
41
53
|
<!-- The open theme drives the selection, so applies from the Themes panel or
|
|
42
54
|
another tab show up here too. -->
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
{#if chrome}
|
|
56
|
+
<ChromeSelect
|
|
57
|
+
label="Theme"
|
|
58
|
+
items={themes}
|
|
59
|
+
value={$openThemeSlug}
|
|
60
|
+
disabled={busy}
|
|
61
|
+
{error}
|
|
62
|
+
onchange={changeTheme}
|
|
63
|
+
/>
|
|
64
|
+
{:else}
|
|
65
|
+
<LabeledSelect
|
|
66
|
+
{inline}
|
|
67
|
+
{size}
|
|
68
|
+
label="Theme"
|
|
69
|
+
items={themes}
|
|
70
|
+
value={$openThemeSlug}
|
|
71
|
+
disabled={busy}
|
|
72
|
+
{error}
|
|
73
|
+
onchange={changeTheme}
|
|
74
|
+
/>
|
|
75
|
+
{/if}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import offeringUrl from '../../system/assets/offering.webp';
|
|
3
|
-
</script>
|
|
1
|
+
<script lang="ts"></script>
|
|
4
2
|
|
|
5
3
|
<section class="offering">
|
|
6
|
-
<img class="offering-image" src={offeringUrl} alt="" />
|
|
7
|
-
|
|
8
4
|
<figure class="offering-quote">
|
|
9
5
|
<blockquote>
|
|
10
6
|
<p>
|
|
@@ -23,37 +19,11 @@
|
|
|
23
19
|
display: grid;
|
|
24
20
|
grid-template-columns: repeat(var(--columns-count), 1fr);
|
|
25
21
|
column-gap: var(--columns-gutter);
|
|
26
|
-
position: relative;
|
|
27
|
-
z-index: 1;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/* Roughly a third of the content width, parked on the left edge so it
|
|
31
|
-
overlaps the left-hand card above without dominating the section. */
|
|
32
|
-
.offering-image {
|
|
33
|
-
grid-column: 2 / 7;
|
|
34
|
-
grid-row: 1;
|
|
35
|
-
display: block;
|
|
36
|
-
width: 100%;
|
|
37
|
-
height: auto;
|
|
38
|
-
border-radius: var(--radius-lg);
|
|
39
|
-
z-index: 1;
|
|
40
22
|
}
|
|
41
23
|
|
|
42
|
-
/* Sits in the open space to the right of the image. No glass background.
|
|
43
|
-
align-self centers it on the image, then translateY drops it by half the
|
|
44
|
-
image's upward overlap (the extra -space-64 in Demo.svelte) so it lands in
|
|
45
|
-
the middle of the actual gap between the cards above and Get Started below. */
|
|
46
24
|
.offering-quote {
|
|
47
|
-
grid-column:
|
|
48
|
-
grid-row: 1;
|
|
49
|
-
align-self: center;
|
|
50
|
-
transform: translateY(calc(var(--space-64) / 2));
|
|
25
|
+
grid-column: 3 / -3;
|
|
51
26
|
margin: 0;
|
|
52
|
-
/* Align body text to the card text above. The cards split this same span
|
|
53
|
-
in half, so the right card's left edge sits half a gutter right of line 7;
|
|
54
|
-
its body text is then inset by the card's --space-20 padding. Match both,
|
|
55
|
-
and the open quote dangles left into this padding (text-indent on p). */
|
|
56
|
-
padding-left: calc(var(--columns-gutter) / 2 + var(--space-20));
|
|
57
27
|
}
|
|
58
28
|
|
|
59
29
|
.offering-quote blockquote {
|
|
@@ -104,11 +74,8 @@
|
|
|
104
74
|
}
|
|
105
75
|
|
|
106
76
|
@media (max-width: 960px) {
|
|
107
|
-
.offering-image {
|
|
108
|
-
grid-column: 2 / 7;
|
|
109
|
-
}
|
|
110
77
|
.offering-quote {
|
|
111
|
-
grid-column:
|
|
78
|
+
grid-column: 2 / -2;
|
|
112
79
|
}
|
|
113
80
|
.offering-quote p {
|
|
114
81
|
font-size: var(--font-size-xl);
|
|
@@ -116,17 +83,8 @@
|
|
|
116
83
|
}
|
|
117
84
|
|
|
118
85
|
@media (max-width: 600px) {
|
|
119
|
-
.offering-image {
|
|
120
|
-
grid-column: 1 / -1;
|
|
121
|
-
grid-row: 1;
|
|
122
|
-
}
|
|
123
86
|
.offering-quote {
|
|
124
87
|
grid-column: 1 / -1;
|
|
125
|
-
grid-row: 2;
|
|
126
|
-
align-self: auto;
|
|
127
|
-
width: auto;
|
|
128
|
-
margin: var(--space-16) 0 0;
|
|
129
|
-
padding-left: 0;
|
|
130
88
|
}
|
|
131
89
|
.offering-quote p {
|
|
132
90
|
font-size: var(--font-size-4xl);
|
|
@@ -174,7 +174,10 @@
|
|
|
174
174
|
edit('curveAutoSmooth', { ...curveAutoSmooth, [key]: value });
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
// `$state.raw`: confirmEdit writes the draft into the store, which
|
|
178
|
+
// structuredClones on every edit, and a deep `$state` proxy is not cloneable.
|
|
179
|
+
// Only ever reassigned whole.
|
|
180
|
+
let editing: EditingState = $state.raw(idleState);
|
|
178
181
|
|
|
179
182
|
function computePaletteOklch(index: number, base: Oklch): Oklch {
|
|
180
183
|
return computePaletteOklchPure(index, base, lightnessCurve, saturationCurve, curveOffset, hueCurve);
|
|
@@ -9,7 +9,7 @@ Each file plays a distinct role. Don't merge them without understanding why they
|
|
|
9
9
|
| File | Scope | Tokens used | Loaded by | Notes |
|
|
10
10
|
|---|---|---|---|---|
|
|
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
|
-
| `ui-editor.css` | Editor chrome only | Defines `--ui-*` (opaque grayscale, system fonts) | JS-imported by `Editor.svelte` and `
|
|
12
|
+
| `ui-editor.css` | Editor chrome only | Defines `--ui-*` (opaque grayscale, system fonts) | JS-imported by `Editor.svelte`, `ComponentEditorPage.svelte`, `ColorsPage.svelte`, and the demo's `DemoBar.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
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. |
|