@motion-proto/live-tokens 0.7.1 → 0.9.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/.claude/skills/live-tokens-add-component/SKILL.md +488 -0
- package/README.md +34 -0
- 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 +6 -2
- 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/index.ts +16 -1
- package/src/editor/component-editor/registry.ts +103 -26
- 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/ComponentsTab.svelte +2 -2
- 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/componentSources.ts +3 -3
- package/src/editor/component-editor/scaffolding/defaultSections.ts +15 -10
- package/src/editor/component-editor/scaffolding/types.ts +11 -0
- package/src/editor/core/components/componentConfigKeys.ts +22 -3
- 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 +27 -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/index.ts +10 -1
- package/src/editor/overlay/ColumnsOverlay.svelte +0 -1
- package/src/editor/overlay/LiveEditorOverlay.svelte +1 -4
- package/src/editor/pages/ComponentEditorPage.svelte +53 -3
- package/src/editor/pages/EditorShell.svelte +53 -3
- 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/Dialog.svelte +24 -4
- 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 +532 -381
- 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,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { FontSource } from '../core/themes/themeTypes';
|
|
2
|
+
import type { FontFamily, FontSource } from '../core/themes/themeTypes';
|
|
3
3
|
import { editorState, setFontSources, transaction } from '../core/store/editorStore';
|
|
4
4
|
import { applyFontSources, applyFontStacks } from '../core/fonts/fontLoader';
|
|
5
5
|
import {
|
|
@@ -10,34 +10,42 @@
|
|
|
10
10
|
type ParsedFamily,
|
|
11
11
|
} from '../core/fonts/fontParse';
|
|
12
12
|
import UIPillButton from './UIPillButton.svelte';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let
|
|
21
|
-
let
|
|
13
|
+
import UISegmentedControl from './UISegmentedControl.svelte';
|
|
14
|
+
import UIInfoPopover from './UIInfoPopover.svelte';
|
|
15
|
+
|
|
16
|
+
type AddMode = 'closed' | 'name' | 'paste';
|
|
17
|
+
let addMode: AddMode = $state('closed');
|
|
18
|
+
|
|
19
|
+
// By-name (Google Fonts) — type a family name; we build the CSS2 URL.
|
|
20
|
+
let nameInput = $state('');
|
|
21
|
+
let nameError = $state('');
|
|
22
|
+
let nameDiscovering = $state(false);
|
|
23
|
+
let nameParsed: ParsedFamily[] | null = $state(null);
|
|
24
|
+
|
|
25
|
+
// Unified paste — accepts a bare URL, `<link>` tag, `@import url(...)`,
|
|
26
|
+
// or one or more `@font-face { ... }` rules. We sniff which on Detect.
|
|
27
|
+
let pasteInput = $state('');
|
|
28
|
+
let pasteError = $state('');
|
|
29
|
+
let pasteDiscovering = $state(false);
|
|
22
30
|
let urlParsed: ParsedFamily[] | null = $state(null);
|
|
23
31
|
let urlPickedNames = $state(new Set<string>());
|
|
24
32
|
let urlNeedsManualFamilies = $state(false);
|
|
25
33
|
let urlManualFamilies = $state('');
|
|
26
|
-
|
|
27
|
-
// @font-face paste
|
|
28
|
-
let fontFaceText = $state('');
|
|
29
34
|
let fontFaceParsed: ParsedFamily[] = $state([]);
|
|
30
35
|
|
|
31
36
|
function reset() {
|
|
32
37
|
addMode = 'closed';
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
nameInput = '';
|
|
39
|
+
nameError = '';
|
|
40
|
+
nameDiscovering = false;
|
|
41
|
+
nameParsed = null;
|
|
42
|
+
pasteInput = '';
|
|
43
|
+
pasteError = '';
|
|
44
|
+
pasteDiscovering = false;
|
|
36
45
|
urlParsed = null;
|
|
37
46
|
urlPickedNames = new Set();
|
|
38
47
|
urlNeedsManualFamilies = false;
|
|
39
48
|
urlManualFamilies = '';
|
|
40
|
-
fontFaceText = '';
|
|
41
49
|
fontFaceParsed = [];
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -65,16 +73,31 @@
|
|
|
65
73
|
applyFontStacks(fontStacksList, next);
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
/** One paste field → sniff whether it's @font-face or a URL/embed and
|
|
77
|
+
* populate the matching detected-families state. */
|
|
78
|
+
async function detectPaste() {
|
|
79
|
+
pasteError = '';
|
|
70
80
|
urlParsed = null;
|
|
71
81
|
urlNeedsManualFamilies = false;
|
|
72
|
-
|
|
82
|
+
fontFaceParsed = [];
|
|
83
|
+
const text = pasteInput.trim();
|
|
84
|
+
if (!text) {
|
|
85
|
+
pasteError = 'Paste a URL, embed, or @font-face rule';
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (/@font-face/i.test(text)) {
|
|
89
|
+
fontFaceParsed = parseFontFaceText(text);
|
|
90
|
+
if (fontFaceParsed.length === 0) {
|
|
91
|
+
pasteError = "Couldn't parse @font-face rules";
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const url = extractFontsUrl(text);
|
|
73
96
|
if (!url) {
|
|
74
|
-
|
|
97
|
+
pasteError = "Couldn't find a fonts URL or @font-face rule in that paste";
|
|
75
98
|
return;
|
|
76
99
|
}
|
|
77
|
-
|
|
100
|
+
pasteDiscovering = true;
|
|
78
101
|
try {
|
|
79
102
|
const found = await discoverFamiliesFromUrl(url);
|
|
80
103
|
if (found && found.length > 0) {
|
|
@@ -84,21 +107,83 @@
|
|
|
84
107
|
urlNeedsManualFamilies = true;
|
|
85
108
|
}
|
|
86
109
|
} catch (e) {
|
|
87
|
-
|
|
110
|
+
pasteError = 'Discovery failed';
|
|
88
111
|
urlNeedsManualFamilies = true;
|
|
89
112
|
}
|
|
90
|
-
|
|
113
|
+
pasteDiscovering = false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Build a Google Fonts CSS2 URL for a family name, requesting a wide
|
|
117
|
+
* weight range with italics. Works for variable fonts and most static
|
|
118
|
+
* multi-weight families. Single-weight static fonts (e.g. GFS Didot) will
|
|
119
|
+
* reject the range axis with 400 Bad Request — Chrome then CORBs the
|
|
120
|
+
* response. Such fonts must be persisted as `?family=Name&display=swap`. */
|
|
121
|
+
function googleUrlForName(name: string): string {
|
|
122
|
+
const family = name.trim().replace(/\s+/g, '+');
|
|
123
|
+
return `https://fonts.googleapis.com/css2?family=${family}:ital,wght@0,100..900;1,100..900&display=swap`;
|
|
91
124
|
}
|
|
92
125
|
|
|
126
|
+
async function discoverByName() {
|
|
127
|
+
nameError = '';
|
|
128
|
+
nameParsed = null;
|
|
129
|
+
if (!nameInput.trim()) {
|
|
130
|
+
nameError = 'Enter a family name';
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
nameDiscovering = true;
|
|
134
|
+
try {
|
|
135
|
+
const found = await discoverFamiliesFromUrl(googleUrlForName(nameInput));
|
|
136
|
+
if (found && found.length > 0) {
|
|
137
|
+
nameParsed = found;
|
|
138
|
+
} else {
|
|
139
|
+
nameError = `Couldn't find "${nameInput.trim()}" on Google Fonts`;
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
nameError = `Couldn't reach Google Fonts for "${nameInput.trim()}"`;
|
|
143
|
+
}
|
|
144
|
+
nameDiscovering = false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function addNameSource() {
|
|
148
|
+
if (!nameParsed || nameParsed.length === 0) return;
|
|
149
|
+
if (nameDuplicate) return;
|
|
150
|
+
// $state.snapshot() unwraps the reactive proxy. Without it the FontSource
|
|
151
|
+
// we hand to the store carries proxy arrays (weights, etc.) and the next
|
|
152
|
+
// `mutate()` call fails with DataCloneError inside structuredClone.
|
|
153
|
+
const families = $state.snapshot(nameParsed) as ParsedFamily[];
|
|
154
|
+
const source = buildSourceFromUrl(googleUrlForName(nameInput), families);
|
|
155
|
+
commitSources([...fontSourcesList, source]);
|
|
156
|
+
reset();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Case-insensitive family-name match against existing sources. Used to
|
|
160
|
+
* block duplicate adds and surface a notice under the Add button. */
|
|
161
|
+
function findExistingFamilyByName(name: string): string | null {
|
|
162
|
+
const lower = name.trim().toLowerCase();
|
|
163
|
+
if (!lower) return null;
|
|
164
|
+
for (const src of fontSourcesList) {
|
|
165
|
+
for (const fam of src.families) {
|
|
166
|
+
if (fam.name.toLowerCase() === lower) return fam.name;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let nameDuplicate = $derived.by(() => {
|
|
173
|
+
if (!nameParsed || nameParsed.length === 0) return null;
|
|
174
|
+
return findExistingFamilyByName(nameParsed[0].name);
|
|
175
|
+
});
|
|
176
|
+
|
|
93
177
|
function addUrlSource() {
|
|
94
|
-
const url = extractFontsUrl(
|
|
178
|
+
const url = extractFontsUrl(pasteInput);
|
|
95
179
|
if (!url) {
|
|
96
|
-
|
|
180
|
+
pasteError = "Couldn't find a fonts URL in that paste";
|
|
97
181
|
return;
|
|
98
182
|
}
|
|
99
183
|
let families: ParsedFamily[] = [];
|
|
100
184
|
if (urlParsed) {
|
|
101
|
-
|
|
185
|
+
// Snapshot to drop the $state proxy — see comment in addNameSource.
|
|
186
|
+
families = ($state.snapshot(urlParsed) as ParsedFamily[]).filter((f) => urlPickedNames.has(f.name));
|
|
102
187
|
} else if (urlNeedsManualFamilies) {
|
|
103
188
|
families = urlManualFamilies
|
|
104
189
|
.split(',')
|
|
@@ -107,7 +192,7 @@
|
|
|
107
192
|
.map((name) => ({ name }));
|
|
108
193
|
}
|
|
109
194
|
if (families.length === 0) {
|
|
110
|
-
|
|
195
|
+
pasteError = 'Pick at least one family';
|
|
111
196
|
return;
|
|
112
197
|
}
|
|
113
198
|
const source = buildSourceFromUrl(url, families);
|
|
@@ -115,18 +200,10 @@
|
|
|
115
200
|
reset();
|
|
116
201
|
}
|
|
117
202
|
|
|
118
|
-
function parseFontFaceTextInput() {
|
|
119
|
-
fontFaceParsed = parseFontFaceText(fontFaceText);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
203
|
function addFontFaceSource() {
|
|
123
|
-
if (
|
|
124
|
-
const families =
|
|
125
|
-
|
|
126
|
-
fontFaceParsed = parseFontFaceText(fontFaceText);
|
|
127
|
-
if (fontFaceParsed.length === 0) return;
|
|
128
|
-
}
|
|
129
|
-
const source = buildSourceFromFontFaceText(fontFaceText, families.length > 0 ? families : fontFaceParsed);
|
|
204
|
+
if (fontFaceParsed.length === 0) return;
|
|
205
|
+
const families = $state.snapshot(fontFaceParsed) as ParsedFamily[];
|
|
206
|
+
const source = buildSourceFromFontFaceText(pasteInput, families);
|
|
130
207
|
commitSources([...fontSourcesList, source]);
|
|
131
208
|
reset();
|
|
132
209
|
}
|
|
@@ -147,127 +224,168 @@
|
|
|
147
224
|
applyFontStacks(updatedStacks, next);
|
|
148
225
|
}
|
|
149
226
|
|
|
227
|
+
/** Resolve a clickable target for the row. We prefer the human-readable
|
|
228
|
+
* specimen/family page (Google Fonts, Adobe Fonts) over the raw CSS/font
|
|
229
|
+
* file — those are rarely what a user wants to look at. */
|
|
230
|
+
function familyHref(source: FontSource, family: FontFamily): string | null {
|
|
231
|
+
if (source.kind === 'google') {
|
|
232
|
+
return `https://fonts.google.com/specimen/${family.name.trim().replace(/\s+/g, '+')}`;
|
|
233
|
+
}
|
|
234
|
+
if (source.kind === 'typekit') {
|
|
235
|
+
const slug = family.name.trim().toLowerCase().replace(/\s+/g, '-');
|
|
236
|
+
return `https://fonts.adobe.com/fonts/${slug}`;
|
|
237
|
+
}
|
|
238
|
+
if (source.kind === 'css-url') return source.url ?? null;
|
|
239
|
+
return null; // font-face: no public page exists
|
|
240
|
+
}
|
|
241
|
+
|
|
150
242
|
function sourceKindLabel(source: FontSource): string {
|
|
151
243
|
if (source.kind === 'google') return 'Google';
|
|
152
244
|
if (source.kind === 'typekit') return 'Typekit';
|
|
153
245
|
if (source.kind === 'font-face') return 'Local';
|
|
154
246
|
return 'CSS URL';
|
|
155
247
|
}
|
|
156
|
-
|
|
157
|
-
function stacksReferencing(familyId: string): string[] {
|
|
158
|
-
return fontStacksList
|
|
159
|
-
.filter((s) => s.slots.some((slot) => slot.kind === 'project' && slot.familyId === familyId))
|
|
160
|
-
.map((s) => s.variable);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
let expanded = $state(new Set<string>());
|
|
164
|
-
function toggleExpanded(familyId: string) {
|
|
165
|
-
const next = new Set(expanded);
|
|
166
|
-
if (next.has(familyId)) next.delete(familyId); else next.add(familyId);
|
|
167
|
-
expanded = next;
|
|
168
|
-
}
|
|
169
248
|
</script>
|
|
170
249
|
|
|
171
250
|
<section class="project-fonts">
|
|
172
251
|
<header class="pf-header">
|
|
173
|
-
<
|
|
252
|
+
<div class="pf-title-row">
|
|
253
|
+
<h3 class="group-title">Project Fonts</h3>
|
|
254
|
+
<UIInfoPopover title="Installing fonts" ariaLabel="How to install fonts">
|
|
255
|
+
<p>Three ways to install a font, depending on where it lives:</p>
|
|
256
|
+
<p>
|
|
257
|
+
<strong>Google Fonts</strong> — use <em>By name</em> and type the family
|
|
258
|
+
(e.g. <code>Inter</code>). We fetch the CSS2 URL for you.
|
|
259
|
+
</p>
|
|
260
|
+
<p>
|
|
261
|
+
<strong>Hosted CDN (Adobe, Fontshare, custom)</strong> — use <em>Paste</em>
|
|
262
|
+
with a fonts URL, a <code><link></code> tag, or an <code>@import url(...)</code> line.
|
|
263
|
+
</p>
|
|
264
|
+
<p>
|
|
265
|
+
<strong>Local files</strong> — drop your <code>.woff2</code> files into
|
|
266
|
+
<code>src/system/styles/fonts/<Family>/</code>, then paste the matching
|
|
267
|
+
<code>@font-face { ... }</code> rules into <em>Paste</em>. The folder ships
|
|
268
|
+
with the production build, so <code>src/...</code> paths resolve at runtime.
|
|
269
|
+
</p>
|
|
270
|
+
</UIInfoPopover>
|
|
271
|
+
</div>
|
|
272
|
+
<UIPillButton
|
|
273
|
+
variant="primary"
|
|
274
|
+
icon="fa-plus"
|
|
275
|
+
onclick={() => { addMode = addMode === 'closed' ? 'name' : 'closed'; }}
|
|
276
|
+
>Add Font</UIPillButton>
|
|
174
277
|
</header>
|
|
175
278
|
|
|
176
279
|
{#if fontSourcesList.length === 0}
|
|
177
280
|
<p class="pf-empty">No fonts loaded yet. Use the add button below.</p>
|
|
178
281
|
{/if}
|
|
179
282
|
|
|
180
|
-
<
|
|
283
|
+
<ul class="pf-family-list">
|
|
181
284
|
{#each fontSourcesList as source (source.id)}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<span class="pf-
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
{
|
|
193
|
-
{
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
aria-expanded={isOpen}
|
|
205
|
-
><i class="fas fa-chevron-right" aria-hidden="true"></i></button>
|
|
206
|
-
{:else}
|
|
207
|
-
<span class="pf-family-disclosure-placeholder" aria-hidden="true"></span>
|
|
208
|
-
{/if}
|
|
209
|
-
<span class="pf-family-preview" style="font-family: {fam.cssName}, sans-serif;">Ag</span>
|
|
210
|
-
<span class="pf-family-name">{fam.name}</span>
|
|
211
|
-
<button
|
|
212
|
-
type="button"
|
|
213
|
-
class="pf-family-remove"
|
|
214
|
-
onclick={() => removeFamily(source.id, fam.id)}
|
|
215
|
-
aria-label={`Remove ${fam.name}`}
|
|
216
|
-
title="Remove family"
|
|
217
|
-
><i class="fas fa-xmark" aria-hidden="true"></i></button>
|
|
218
|
-
</div>
|
|
219
|
-
{#if refs.length > 0}
|
|
220
|
-
<div class="pf-family-meta">
|
|
221
|
-
<span class="pf-meta-label">Used by</span>
|
|
222
|
-
<span class="pf-meta-value">
|
|
223
|
-
{#each refs as r (r)}<span class="pf-meta-pill">{r}</span>{/each}
|
|
224
|
-
</span>
|
|
225
|
-
</div>
|
|
226
|
-
{/if}
|
|
227
|
-
{#if isOpen && hasMultipleWeights && fam.weights}
|
|
228
|
-
<div class="pf-family-meta">
|
|
229
|
-
<span class="pf-meta-label">Weights</span>
|
|
230
|
-
<span class="pf-meta-value pf-meta-weights">{fam.weights.join(', ')}</span>
|
|
231
|
-
</div>
|
|
232
|
-
{/if}
|
|
233
|
-
</li>
|
|
234
|
-
{/each}
|
|
235
|
-
</ul>
|
|
236
|
-
</div>
|
|
285
|
+
{#each source.families as fam (fam.id)}
|
|
286
|
+
{@const href = familyHref(source, fam)}
|
|
287
|
+
<li class="pf-family">
|
|
288
|
+
<span class="pf-family-preview" style="font-family: {fam.cssName}, sans-serif;">Ag</span>
|
|
289
|
+
<span class="pf-family-name">{fam.name}</span>
|
|
290
|
+
<UIPillButton
|
|
291
|
+
variant="outline"
|
|
292
|
+
size="compact"
|
|
293
|
+
href={href ?? undefined}
|
|
294
|
+
target={href ? '_blank' : undefined}
|
|
295
|
+
disabled={!href}
|
|
296
|
+
title={href ? `Open ${fam.name} on ${sourceKindLabel(source)}` : 'No public page for local fonts'}
|
|
297
|
+
>{sourceKindLabel(source)}</UIPillButton>
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
class="pf-family-remove"
|
|
301
|
+
onclick={() => removeFamily(source.id, fam.id)}
|
|
302
|
+
aria-label={`Remove ${fam.name}`}
|
|
303
|
+
title="Remove family"
|
|
304
|
+
><i class="fas fa-xmark" aria-hidden="true"></i></button>
|
|
305
|
+
</li>
|
|
306
|
+
{/each}
|
|
237
307
|
{/each}
|
|
238
|
-
</
|
|
308
|
+
</ul>
|
|
239
309
|
|
|
240
|
-
{#if addMode
|
|
241
|
-
<div class="pf-add-toggle-row">
|
|
242
|
-
<UIPillButton icon="fa-plus" onclick={() => (addMode = 'url')}>Add Font</UIPillButton>
|
|
243
|
-
</div>
|
|
244
|
-
{:else}
|
|
310
|
+
{#if addMode !== 'closed'}
|
|
245
311
|
<div class="pf-add-panel">
|
|
246
|
-
<div class="pf-add-
|
|
247
|
-
<
|
|
248
|
-
<
|
|
312
|
+
<div class="pf-add-head">
|
|
313
|
+
<span class="pf-add-eyebrow">Browse</span>
|
|
314
|
+
<div class="pf-browse-row">
|
|
315
|
+
<UIPillButton variant="outline" href="https://fonts.google.com/" target="_blank" icon="fa-arrow-up-right-from-square">
|
|
316
|
+
Google Fonts
|
|
317
|
+
</UIPillButton>
|
|
318
|
+
<UIPillButton variant="outline" href="https://fonts.adobe.com/" target="_blank" icon="fa-arrow-up-right-from-square">
|
|
319
|
+
Adobe Fonts
|
|
320
|
+
</UIPillButton>
|
|
321
|
+
<UIPillButton variant="outline" href="https://www.fontshare.com/" target="_blank" icon="fa-arrow-up-right-from-square">
|
|
322
|
+
Fontshare
|
|
323
|
+
</UIPillButton>
|
|
324
|
+
</div>
|
|
249
325
|
<button type="button" class="pf-add-close" onclick={reset} aria-label="Cancel">×</button>
|
|
250
326
|
</div>
|
|
251
327
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
328
|
+
<div class="pf-add-divider"><span>or add directly</span></div>
|
|
329
|
+
|
|
330
|
+
<UISegmentedControl
|
|
331
|
+
value={addMode}
|
|
332
|
+
options={[
|
|
333
|
+
{ value: 'name', label: 'By name (Google)' },
|
|
334
|
+
{ value: 'paste', label: 'Paste URL or @font-face' },
|
|
335
|
+
] as const}
|
|
336
|
+
ariaLabel="Add font by"
|
|
337
|
+
onchange={(v) => (addMode = v)}
|
|
338
|
+
/>
|
|
339
|
+
|
|
340
|
+
{#if addMode === 'name'}
|
|
341
|
+
<div class="pf-row">
|
|
342
|
+
<input
|
|
343
|
+
type="text"
|
|
344
|
+
class="ui-form-input pf-name-input"
|
|
345
|
+
placeholder="e.g. Inter, Fraunces, Space Mono"
|
|
346
|
+
bind:value={nameInput}
|
|
347
|
+
onkeydown={(e) => { if (e.key === 'Enter' && !nameParsed) discoverByName(); }}
|
|
348
|
+
/>
|
|
349
|
+
{#if nameParsed}
|
|
350
|
+
<UIPillButton variant="primary" onclick={addNameSource} disabled={!!nameDuplicate}>Add</UIPillButton>
|
|
351
|
+
{:else}
|
|
352
|
+
<UIPillButton variant="secondary" onclick={discoverByName} disabled={!nameInput.trim() || nameDiscovering}>
|
|
353
|
+
{nameDiscovering ? 'Checking…' : 'Find'}
|
|
354
|
+
</UIPillButton>
|
|
355
|
+
{/if}
|
|
356
|
+
</div>
|
|
357
|
+
{#if nameError}<div class="pf-error">{nameError}</div>{/if}
|
|
358
|
+
{#if nameParsed}
|
|
359
|
+
{#if nameDuplicate}
|
|
360
|
+
<div class="pf-notice">
|
|
361
|
+
<strong>{nameDuplicate}</strong> is already in your project fonts.
|
|
362
|
+
</div>
|
|
363
|
+
{:else}
|
|
364
|
+
<div class="pf-detected">
|
|
365
|
+
Found <strong>{nameParsed[0].name}</strong>
|
|
366
|
+
{#if nameParsed[0].weights && nameParsed[0].weights.length > 0}
|
|
367
|
+
<span class="pf-check-meta">({nameParsed[0].weights.length} weights)</span>
|
|
368
|
+
{/if}
|
|
369
|
+
</div>
|
|
370
|
+
{/if}
|
|
371
|
+
{/if}
|
|
372
|
+
{:else if addMode === 'paste'}
|
|
258
373
|
<textarea
|
|
259
374
|
class="ui-form-input pf-textarea pf-url-input"
|
|
260
|
-
placeholder={'<link
|
|
261
|
-
rows="
|
|
262
|
-
bind:value={
|
|
375
|
+
placeholder={'A fonts URL, <link> tag, or @import url(...)\n\nor\n\none or more @font-face { ... } rules'}
|
|
376
|
+
rows="5"
|
|
377
|
+
bind:value={pasteInput}
|
|
263
378
|
></textarea>
|
|
264
379
|
<div class="pf-row">
|
|
265
|
-
<UIPillButton variant="secondary" onclick={
|
|
266
|
-
{
|
|
380
|
+
<UIPillButton variant="secondary" onclick={detectPaste} disabled={!pasteInput.trim() || pasteDiscovering}>
|
|
381
|
+
{pasteDiscovering ? 'Checking…' : 'Detect'}
|
|
267
382
|
</UIPillButton>
|
|
268
383
|
</div>
|
|
269
|
-
{#if
|
|
270
|
-
{#if
|
|
384
|
+
{#if pasteError}<div class="pf-error">{pasteError}</div>{/if}
|
|
385
|
+
{#if fontFaceParsed.length > 0}
|
|
386
|
+
<div class="pf-detected">Detected @font-face: {fontFaceParsed.map((f) => f.name).join(', ')}</div>
|
|
387
|
+
<UIPillButton variant="primary" onclick={addFontFaceSource}>Add</UIPillButton>
|
|
388
|
+
{:else if urlParsed}
|
|
271
389
|
<div class="pf-detected">Detected families — pick which to add:</div>
|
|
272
390
|
<ul class="pf-checklist">
|
|
273
391
|
{#each urlParsed as f (f.name)}
|
|
@@ -302,18 +420,6 @@
|
|
|
302
420
|
/>
|
|
303
421
|
<UIPillButton variant="primary" onclick={addUrlSource} disabled={!urlManualFamilies.trim()}>Add</UIPillButton>
|
|
304
422
|
{/if}
|
|
305
|
-
{:else if addMode === 'fontface'}
|
|
306
|
-
<textarea
|
|
307
|
-
class="ui-form-input pf-textarea"
|
|
308
|
-
placeholder={'Paste one or more @font-face { ... } rules'}
|
|
309
|
-
rows="6"
|
|
310
|
-
bind:value={fontFaceText}
|
|
311
|
-
oninput={parseFontFaceTextInput}
|
|
312
|
-
></textarea>
|
|
313
|
-
{#if fontFaceParsed.length > 0}
|
|
314
|
-
<div class="pf-detected">Detected: {fontFaceParsed.map((f) => f.name).join(', ')}</div>
|
|
315
|
-
{/if}
|
|
316
|
-
<UIPillButton variant="primary" onclick={addFontFaceSource} disabled={fontFaceParsed.length === 0}>Add</UIPillButton>
|
|
317
423
|
{/if}
|
|
318
424
|
</div>
|
|
319
425
|
{/if}
|
|
@@ -323,8 +429,7 @@
|
|
|
323
429
|
.project-fonts {
|
|
324
430
|
display: flex;
|
|
325
431
|
flex-direction: column;
|
|
326
|
-
gap: var(--ui-space-
|
|
327
|
-
max-width: 56rem;
|
|
432
|
+
gap: var(--ui-space-12);
|
|
328
433
|
}
|
|
329
434
|
|
|
330
435
|
.pf-header {
|
|
@@ -334,11 +439,17 @@
|
|
|
334
439
|
justify-content: space-between;
|
|
335
440
|
}
|
|
336
441
|
|
|
442
|
+
.pf-title-row {
|
|
443
|
+
display: flex;
|
|
444
|
+
align-items: center;
|
|
445
|
+
gap: var(--ui-space-4);
|
|
446
|
+
}
|
|
447
|
+
|
|
337
448
|
.group-title {
|
|
338
449
|
margin: 0;
|
|
339
|
-
font-size: var(--ui-font-size-
|
|
340
|
-
font-weight: var(--ui-font-weight-
|
|
341
|
-
color: var(--ui-text-
|
|
450
|
+
font-size: var(--ui-font-size-xl);
|
|
451
|
+
font-weight: var(--ui-font-weight-bold);
|
|
452
|
+
color: var(--ui-text-primary);
|
|
342
453
|
}
|
|
343
454
|
|
|
344
455
|
.pf-empty {
|
|
@@ -348,84 +459,32 @@
|
|
|
348
459
|
font-size: var(--ui-font-size-sm);
|
|
349
460
|
}
|
|
350
461
|
|
|
351
|
-
.pf-sources {
|
|
352
|
-
display: flex;
|
|
353
|
-
flex-direction: column;
|
|
354
|
-
gap: var(--ui-space-8);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
.pf-source {
|
|
358
|
-
border: 1px solid var(--ui-border-low);
|
|
359
|
-
border-radius: var(--ui-radius-md);
|
|
360
|
-
display: flex;
|
|
361
|
-
flex-direction: column;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
.pf-source-head {
|
|
365
|
-
display: flex;
|
|
366
|
-
align-items: center;
|
|
367
|
-
gap: var(--ui-space-8);
|
|
368
|
-
padding: var(--ui-space-8) var(--ui-space-12);
|
|
369
|
-
border-bottom: 1px solid var(--ui-border-low);
|
|
370
|
-
background: var(--ui-surface-subtle, rgba(255,255,255,0.02));
|
|
371
|
-
border-radius: var(--ui-radius-md) var(--ui-radius-md) 0 0;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
.pf-kind-badge {
|
|
375
|
-
font-size: var(--ui-font-size-xs);
|
|
376
|
-
color: var(--ui-text-tertiary);
|
|
377
|
-
border: 1px solid var(--ui-border-low);
|
|
378
|
-
padding: 0 var(--ui-space-4);
|
|
379
|
-
border-radius: var(--ui-radius-sm);
|
|
380
|
-
font-family: var(--ui-font-mono);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
.pf-source-label {
|
|
384
|
-
font-size: var(--ui-font-size-lg);
|
|
385
|
-
color: var(--ui-text-primary);
|
|
386
|
-
font-weight: var(--ui-font-weight-medium);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
.pf-source-url {
|
|
390
|
-
font-family: var(--ui-font-mono);
|
|
391
|
-
font-size: var(--ui-font-size-xs);
|
|
392
|
-
color: var(--ui-text-secondary);
|
|
393
|
-
overflow: hidden;
|
|
394
|
-
text-overflow: ellipsis;
|
|
395
|
-
white-space: nowrap;
|
|
396
|
-
flex: 1;
|
|
397
|
-
min-width: 0;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
462
|
.pf-family-list {
|
|
401
463
|
list-style: none;
|
|
402
464
|
margin: 0;
|
|
403
|
-
padding:
|
|
465
|
+
padding: 0;
|
|
404
466
|
display: grid;
|
|
405
|
-
grid-template-columns: repeat(
|
|
406
|
-
gap: var(--ui-space-
|
|
467
|
+
grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
|
|
468
|
+
gap: var(--ui-space-6);
|
|
469
|
+
align-items: start;
|
|
407
470
|
}
|
|
408
471
|
|
|
409
472
|
.pf-family {
|
|
410
|
-
display: flex;
|
|
411
|
-
flex-direction: column;
|
|
412
|
-
min-width: 0;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
.pf-family-row {
|
|
416
473
|
display: grid;
|
|
417
|
-
grid-template-columns:
|
|
474
|
+
grid-template-columns: 1.75rem 1fr auto 24px;
|
|
418
475
|
align-items: center;
|
|
419
|
-
gap: var(--ui-space-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
.pf-family-row:hover {
|
|
476
|
+
gap: var(--ui-space-8);
|
|
477
|
+
min-width: 0;
|
|
478
|
+
padding: var(--ui-space-4) var(--ui-space-8);
|
|
479
|
+
border: 1px solid var(--ui-border-low);
|
|
480
|
+
border-radius: var(--ui-radius-md);
|
|
425
481
|
background: var(--ui-surface-subtle, rgba(255,255,255,0.02));
|
|
482
|
+
min-height: 36px;
|
|
483
|
+
}
|
|
484
|
+
.pf-family:hover {
|
|
485
|
+
background: var(--ui-surface-hover, rgba(255,255,255,0.04));
|
|
426
486
|
}
|
|
427
487
|
|
|
428
|
-
.pf-family-disclosure,
|
|
429
488
|
.pf-family-remove {
|
|
430
489
|
display: inline-flex;
|
|
431
490
|
align-items: center;
|
|
@@ -441,20 +500,10 @@
|
|
|
441
500
|
font-size: var(--ui-font-size-sm);
|
|
442
501
|
line-height: 1;
|
|
443
502
|
}
|
|
444
|
-
.pf-family-disclosure:hover,
|
|
445
503
|
.pf-family-remove:hover {
|
|
446
504
|
color: var(--ui-text-primary);
|
|
447
505
|
background: var(--ui-surface-hover, rgba(255,255,255,0.06));
|
|
448
506
|
}
|
|
449
|
-
.pf-family-disclosure i {
|
|
450
|
-
transition: transform var(--ui-transition-fast);
|
|
451
|
-
}
|
|
452
|
-
.pf-family-disclosure.open i { transform: rotate(90deg); }
|
|
453
|
-
.pf-family-disclosure-placeholder {
|
|
454
|
-
width: 24px;
|
|
455
|
-
height: 24px;
|
|
456
|
-
display: inline-block;
|
|
457
|
-
}
|
|
458
507
|
|
|
459
508
|
.pf-family-preview {
|
|
460
509
|
font-size: var(--ui-font-size-md);
|
|
@@ -473,68 +522,70 @@
|
|
|
473
522
|
white-space: nowrap;
|
|
474
523
|
}
|
|
475
524
|
|
|
476
|
-
.pf-
|
|
525
|
+
.pf-add-panel {
|
|
526
|
+
display: flex;
|
|
527
|
+
flex-direction: column;
|
|
528
|
+
gap: var(--ui-space-10);
|
|
529
|
+
padding: var(--ui-space-12);
|
|
530
|
+
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
531
|
+
border-radius: var(--ui-radius-md);
|
|
532
|
+
background: rgba(255, 255, 255, 0.15);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.pf-add-head {
|
|
477
536
|
display: grid;
|
|
478
|
-
grid-template-columns:
|
|
537
|
+
grid-template-columns: auto 1fr auto;
|
|
479
538
|
align-items: center;
|
|
480
|
-
gap: var(--ui-space-
|
|
481
|
-
padding: var(--ui-space-2) var(--ui-space-4) var(--ui-space-2) calc(24px + var(--ui-space-6) + 1.75rem + var(--ui-space-6));
|
|
482
|
-
font-size: var(--ui-font-size-xs);
|
|
483
|
-
font-family: var(--ui-font-mono);
|
|
539
|
+
gap: var(--ui-space-12);
|
|
484
540
|
}
|
|
485
|
-
.pf-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
flex-wrap: wrap;
|
|
489
|
-
gap: var(--ui-space-2);
|
|
541
|
+
.pf-add-eyebrow {
|
|
542
|
+
font-size: var(--ui-font-size-xs);
|
|
543
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
490
544
|
color: var(--ui-text-tertiary);
|
|
491
|
-
|
|
545
|
+
text-transform: uppercase;
|
|
546
|
+
letter-spacing: 0.04em;
|
|
492
547
|
}
|
|
493
|
-
.pf-
|
|
494
|
-
padding: 0 var(--ui-space-4);
|
|
495
|
-
border-radius: var(--ui-radius-sm);
|
|
496
|
-
background: var(--ui-surface-hover, rgba(255,255,255,0.06));
|
|
497
|
-
color: var(--ui-text-secondary, var(--ui-text-primary));
|
|
498
|
-
}
|
|
499
|
-
.pf-meta-weights { word-break: break-word; }
|
|
500
|
-
|
|
501
|
-
.pf-add-toggle-row {
|
|
548
|
+
.pf-browse-row {
|
|
502
549
|
display: flex;
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
.pf-add-panel {
|
|
506
|
-
display: flex;
|
|
507
|
-
flex-direction: column;
|
|
550
|
+
flex-wrap: wrap;
|
|
508
551
|
gap: var(--ui-space-8);
|
|
509
|
-
padding: var(--ui-space-8);
|
|
510
|
-
border: 1px solid var(--ui-border-low);
|
|
511
|
-
border-radius: var(--ui-radius-sm);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
.pf-add-tabs {
|
|
515
|
-
display: flex;
|
|
516
|
-
gap: var(--ui-space-4);
|
|
517
552
|
}
|
|
518
|
-
.pf-add-
|
|
553
|
+
.pf-add-close {
|
|
519
554
|
background: none;
|
|
520
|
-
border:
|
|
521
|
-
color: var(--ui-text-
|
|
522
|
-
font-size: var(--ui-font-size-
|
|
523
|
-
|
|
555
|
+
border: none;
|
|
556
|
+
color: var(--ui-text-muted);
|
|
557
|
+
font-size: var(--ui-font-size-lg);
|
|
558
|
+
line-height: 1;
|
|
559
|
+
padding: var(--ui-space-2) var(--ui-space-6);
|
|
524
560
|
border-radius: var(--ui-radius-sm);
|
|
525
561
|
cursor: pointer;
|
|
562
|
+
justify-self: end;
|
|
526
563
|
}
|
|
527
|
-
.pf-add-
|
|
564
|
+
.pf-add-close:hover {
|
|
528
565
|
color: var(--ui-text-primary);
|
|
529
|
-
|
|
566
|
+
background: var(--ui-surface-hover, rgba(255,255,255,0.06));
|
|
530
567
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
568
|
+
|
|
569
|
+
.pf-add-divider {
|
|
570
|
+
display: flex;
|
|
571
|
+
align-items: center;
|
|
572
|
+
gap: var(--ui-space-8);
|
|
573
|
+
color: var(--ui-text-tertiary);
|
|
574
|
+
font-size: var(--ui-font-size-xs);
|
|
575
|
+
text-transform: uppercase;
|
|
576
|
+
letter-spacing: 0.04em;
|
|
535
577
|
}
|
|
536
|
-
.pf-add-
|
|
537
|
-
|
|
578
|
+
.pf-add-divider::before,
|
|
579
|
+
.pf-add-divider::after {
|
|
580
|
+
content: '';
|
|
581
|
+
flex: 1;
|
|
582
|
+
height: 1px;
|
|
583
|
+
background: var(--ui-border-low);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.pf-name-input {
|
|
587
|
+
flex: 1;
|
|
588
|
+
min-width: 0;
|
|
538
589
|
}
|
|
539
590
|
|
|
540
591
|
.pf-row {
|
|
@@ -547,6 +598,12 @@
|
|
|
547
598
|
|
|
548
599
|
.pf-detected { color: var(--ui-text-secondary); font-size: var(--ui-font-size-sm); }
|
|
549
600
|
|
|
601
|
+
.pf-notice {
|
|
602
|
+
color: var(--ui-text-secondary);
|
|
603
|
+
font-size: var(--ui-font-size-sm);
|
|
604
|
+
}
|
|
605
|
+
.pf-notice strong { color: var(--ui-text-primary); }
|
|
606
|
+
|
|
550
607
|
.pf-checklist {
|
|
551
608
|
list-style: none;
|
|
552
609
|
margin: 0;
|
|
@@ -578,26 +635,4 @@
|
|
|
578
635
|
overflow-x: auto;
|
|
579
636
|
}
|
|
580
637
|
|
|
581
|
-
.pf-hint {
|
|
582
|
-
margin: 0;
|
|
583
|
-
color: var(--ui-text-tertiary);
|
|
584
|
-
font-size: var(--ui-font-size-sm);
|
|
585
|
-
line-height: 1.4;
|
|
586
|
-
}
|
|
587
|
-
.pf-hint a {
|
|
588
|
-
color: var(--ui-text-primary);
|
|
589
|
-
text-decoration: underline;
|
|
590
|
-
text-underline-offset: 2px;
|
|
591
|
-
text-decoration-thickness: 1px;
|
|
592
|
-
white-space: nowrap;
|
|
593
|
-
}
|
|
594
|
-
.pf-hint a:hover { text-decoration-thickness: 2px; }
|
|
595
|
-
.pf-hint a i { font-size: 0.75em; margin-left: var(--ui-space-2); }
|
|
596
|
-
.pf-hint code {
|
|
597
|
-
font-family: var(--ui-font-mono);
|
|
598
|
-
font-size: 0.92em;
|
|
599
|
-
padding: 0 var(--ui-space-2);
|
|
600
|
-
background: var(--ui-surface-hover, rgba(255,255,255,0.06));
|
|
601
|
-
border-radius: var(--ui-radius-sm);
|
|
602
|
-
}
|
|
603
638
|
</style>
|