@motion-proto/live-tokens 0.6.2 → 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/README.md +14 -13
- package/dist-plugin/index.cjs +854 -226
- package/dist-plugin/index.d.cts +2 -1
- package/dist-plugin/index.d.ts +2 -1
- package/dist-plugin/index.js +852 -225
- package/package.json +26 -40
- package/src/{styles → app}/site.css +1 -1
- package/src/{component-editor → editor/component-editor}/BadgeEditor.svelte +8 -82
- package/src/{component-editor → editor/component-editor}/CalloutEditor.svelte +4 -4
- package/src/{component-editor → editor/component-editor}/CardEditor.svelte +28 -76
- package/src/{component-editor → editor/component-editor}/CollapsibleSectionEditor.svelte +37 -30
- package/src/{component-editor → editor/component-editor}/CornerBadgeEditor.svelte +31 -93
- package/src/{component-editor → editor/component-editor}/DialogEditor.svelte +60 -57
- package/src/editor/component-editor/ImageEditor.svelte +30 -0
- package/src/{component-editor → editor/component-editor}/InlineEditActionsEditor.svelte +6 -4
- package/src/editor/component-editor/MenuSelectEditor.svelte +160 -0
- package/src/{component-editor → editor/component-editor}/NotificationEditor.svelte +67 -38
- package/src/{component-editor → editor/component-editor}/ProgressBarEditor.svelte +5 -4
- package/src/{component-editor → editor/component-editor}/RadioButtonEditor.svelte +3 -3
- package/src/editor/component-editor/SectionDividerEditor.svelte +565 -0
- package/src/{component-editor → editor/component-editor}/SegmentedControlEditor.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/StandardButtonsEditor.svelte +29 -21
- package/src/{component-editor → editor/component-editor}/TabBarEditor.svelte +9 -14
- package/src/{component-editor → editor/component-editor}/TableEditor.svelte +9 -18
- package/src/{component-editor → editor/component-editor}/TooltipEditor.svelte +11 -47
- package/src/editor/component-editor/editors.d.ts +10 -0
- package/src/{component-editor → editor/component-editor}/registry.ts +28 -18
- package/src/{component-editor → editor/component-editor}/scaffolding/AngleDial.svelte +54 -15
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentEditorBase.svelte +3 -51
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte +151 -424
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileMenu.svelte +18 -170
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentsTab.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/CopyFromMenu.svelte +44 -4
- package/src/{component-editor → editor/component-editor}/scaffolding/FieldsetWrapper.svelte +1 -1
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkageChart.svelte +6 -6
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkedBlock.svelte +6 -12
- package/src/editor/component-editor/scaffolding/NonStylableConfig.svelte +38 -0
- package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/SaveAsDialog.svelte +66 -12
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +85 -0
- package/src/editor/component-editor/scaffolding/ShadowBackdropControls.svelte +132 -0
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +345 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/TokenLayout.svelte +17 -12
- package/src/{component-editor → editor/component-editor}/scaffolding/TypeEditor.svelte +13 -1
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +858 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/buildTypeGroupTokens.ts +1 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/editorContext.ts +19 -9
- package/src/{component-editor → editor/component-editor}/scaffolding/linkedBlock.ts +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/types.ts +25 -0
- package/src/{lib → editor/core/components}/componentConfigKeys.ts +8 -0
- package/src/{lib → editor/core/components}/componentConfigService.ts +3 -3
- package/src/{lib → editor/core/components}/componentPersist.ts +11 -9
- package/src/editor/core/flashStatus.ts +30 -0
- package/src/{lib → editor/core/fonts}/fontLoader.ts +2 -2
- package/src/{lib → editor/core/fonts}/fontMigration.ts +4 -4
- package/src/{lib → editor/core/fonts}/fontParse.ts +1 -1
- package/src/editor/core/manifests/manifestService.ts +171 -0
- package/src/editor/core/palettes/familySwap.ts +99 -0
- package/src/{lib → editor/core/palettes}/paletteDerivation.ts +71 -2
- package/src/{lib → editor/core/palettes}/tokenRegistry.ts +9 -6
- package/src/editor/core/productionPulse.ts +37 -0
- package/src/{lib → editor/core/routing}/router.ts +1 -1
- package/src/{lib/files/versionedFileResource.ts → editor/core/storage/files/versionedFileResourceClient.ts} +8 -1
- package/src/{lib → editor/core/store}/editorCore.ts +24 -8
- package/src/{lib → editor/core/store}/editorPersistence.ts +3 -3
- package/src/{lib → editor/core/store}/editorRenderer.ts +2 -2
- package/src/{lib → editor/core/store}/editorStore.ts +222 -28
- package/src/{lib → editor/core/store}/editorTypes.ts +56 -13
- 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/{lib → editor/core/themes}/migrations/index.ts +10 -0
- package/src/{lib → editor/core/themes}/slices/columns.ts +2 -2
- package/src/{lib → editor/core/themes}/slices/components.ts +20 -6
- package/src/{lib → editor/core/themes}/slices/fonts.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/gradients.ts +89 -14
- package/src/{lib → editor/core/themes}/slices/overlays.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/palettes.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/shadows.ts +3 -3
- package/src/{lib → editor/core/themes}/themeInit.ts +8 -8
- package/src/{lib → editor/core/themes}/themeService.ts +6 -6
- package/src/{lib → editor/core/themes}/themeTypes.ts +67 -8
- package/src/editor/index.ts +69 -0
- package/src/{lib → editor/overlay}/ColumnsOverlay.svelte +0 -1
- package/src/{lib → editor/overlay}/LiveEditorOverlay.svelte +80 -129
- package/src/{lib → editor/overlay}/columnsOverlay.ts +2 -2
- package/src/{pages → editor/pages}/ComponentEditorPage.svelte +12 -12
- package/src/{pages → editor/pages}/Editor.svelte +4 -4
- package/src/{pages → editor/pages}/EditorShell.svelte +18 -36
- package/src/{styles → editor/styles}/ui-editor.css +43 -22
- package/src/{styles → editor/styles}/ui-form-controls.css +23 -24
- package/src/{ui → editor/ui}/BezierCurveEditor.svelte +119 -68
- package/src/{ui → editor/ui}/ColorEditPanel.svelte +13 -13
- package/src/{ui → editor/ui}/EditorViewSwitcher.svelte +7 -6
- package/src/editor/ui/FileLoadList.svelte +367 -0
- package/src/editor/ui/FilePill.svelte +80 -0
- package/src/editor/ui/FontStackEditor.svelte +499 -0
- package/src/editor/ui/GradientEditor.svelte +690 -0
- package/src/{ui → editor/ui}/GradientStopPicker.svelte +12 -4
- package/src/editor/ui/ManifestFileManager.svelte +438 -0
- package/src/{ui → editor/ui}/PaletteEditor.svelte +180 -673
- package/src/editor/ui/ProjectFontsSection.svelte +638 -0
- package/src/{ui → editor/ui}/SurfacesTab.svelte +3 -3
- package/src/{ui → editor/ui}/TextTab.svelte +3 -3
- package/src/editor/ui/ThemeFileManager.svelte +783 -0
- package/src/{ui → editor/ui}/UICopyPopover.svelte +4 -4
- package/src/{ui → editor/ui}/UIFontFamilySelector.svelte +6 -7
- package/src/{ui → editor/ui}/UIFontSizeSelector.svelte +4 -1
- package/src/editor/ui/UIInfoPopover.svelte +243 -0
- package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
- package/src/{ui → editor/ui}/UILineHeightSelector.svelte +5 -5
- package/src/{ui → editor/ui}/UILinkToggle.svelte +2 -2
- package/src/{ui → editor/ui}/UIPaddingSelector.svelte +6 -6
- package/src/{ui → editor/ui}/UIPaletteSelector.svelte +57 -30
- package/src/editor/ui/UIPillButton.svelte +168 -0
- package/src/{ui → editor/ui}/UIRadio.svelte +2 -2
- package/src/{ui → editor/ui}/UIRelinkConfirmPopover.svelte +4 -4
- package/src/editor/ui/UISegmentedControl.svelte +114 -0
- package/src/editor/ui/UISquareButton.svelte +172 -0
- package/src/{ui → editor/ui}/UITokenSelector.svelte +14 -11
- package/src/{ui → editor/ui}/UIVariantSelector.svelte +1 -1
- package/src/{ui → editor/ui}/VariablesTab.svelte +46 -17
- package/src/{ui → editor/ui}/palette/GradientStopEditor.svelte +13 -13
- package/src/{ui → editor/ui}/palette/OverridesPanel.svelte +24 -47
- package/src/{ui → editor/ui}/palette/PaletteBase.svelte +11 -8
- package/src/{ui → editor/ui}/palette/paletteEditorState.ts +1 -1
- package/src/editor/ui/palette/paletteMath.ts +275 -0
- package/src/{ui → editor/ui}/sections/ColumnsSection.svelte +137 -18
- package/src/{ui → editor/ui}/sections/GradientsSection.svelte +8 -8
- package/src/{ui → editor/ui}/sections/OverlaysSection.svelte +18 -18
- package/src/{ui → editor/ui}/sections/ShadowsSection.svelte +23 -23
- package/src/{ui → editor/ui}/sections/TokenScaleTable.svelte +3 -3
- package/src/{components → system/components}/Badge.svelte +0 -36
- package/src/{components → system/components}/Button.svelte +2 -2
- package/src/{components → system/components}/Card.svelte +34 -60
- package/src/{components → system/components}/CollapsibleSection.svelte +25 -2
- package/src/{components → system/components}/CornerBadge.svelte +8 -24
- package/src/{components → system/components}/Dialog.svelte +1 -1
- package/src/system/components/FloatingTokenTags.css +275 -0
- package/src/system/components/FloatingTokenTags.svelte +543 -0
- package/src/{components → system/components}/InlineEditActions.svelte +6 -4
- package/src/system/components/MenuSelect.svelte +229 -0
- package/src/{components → system/components}/Notification.svelte +8 -1
- package/src/{components → system/components}/ProgressBar.svelte +29 -11
- package/src/system/components/SectionDivider.svelte +560 -0
- package/src/{components → system/components}/SegmentedControl.svelte +49 -43
- package/src/{components → system/components}/TabBar.svelte +81 -65
- package/src/{components → system/components}/Table.svelte +17 -3
- package/src/{components → system/components}/Tooltip.svelte +6 -4
- package/src/system/styles/CONVENTIONS.md +178 -0
- package/src/system/styles/fonts.css +20 -0
- package/src/system/styles/tokens.css +601 -0
- package/src/system/styles/tokens.generated.css +544 -0
- package/src/component-editor/ImageEditor.svelte +0 -74
- package/src/component-editor/SectionDividerEditor.svelte +0 -265
- package/src/component-editor/scaffolding/DividerEditor.svelte +0 -94
- package/src/component-editor/scaffolding/GradientCard.svelte +0 -296
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +0 -62
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +0 -37
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +0 -61
- package/src/component-editor/scaffolding/StateBlock.svelte +0 -132
- package/src/component-editor/scaffolding/VariantGroup.svelte +0 -310
- package/src/components/SectionDivider.svelte +0 -483
- package/src/data/google-fonts.json +0 -75
- package/src/lib/index.ts +0 -68
- package/src/lib/presetService.ts +0 -214
- package/src/lib/productionPulse.ts +0 -32
- package/src/styles/fonts.css +0 -30
- package/src/styles/tokens.css +0 -1324
- package/src/ui/FontStackEditor.svelte +0 -361
- package/src/ui/GradientEditor.svelte +0 -470
- package/src/ui/PresetFileManager.svelte +0 -1116
- package/src/ui/ProjectFontsSection.svelte +0 -645
- package/src/ui/ThemeFileManager.svelte +0 -1020
- package/src/ui/UnsavedComponentsDialog.svelte +0 -315
- /package/src/{component-editor → editor/component-editor}/index.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/DemoHeader.svelte +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/componentSectionType.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/componentSources.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/defaultSections.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/siblings.ts +0 -0
- /package/src/{lib → editor/core}/cssVarSync.ts +0 -0
- /package/src/{lib → editor/core/palettes}/oklch.ts +0 -0
- /package/src/{lib → editor/core/routing}/navLinkTypes.ts +0 -0
- /package/src/{lib → editor/core/routing}/parentRouteStore.ts +0 -0
- /package/src/{lib → editor/core/storage}/storage.ts +0 -0
- /package/src/{lib → editor/core/store}/editorConfig.ts +0 -0
- /package/src/{lib → editor/core/store}/editorConfigStore.ts +0 -0
- /package/src/{lib → editor/core/store}/editorKeybindings.ts +0 -0
- /package/src/{lib → editor/core/store}/editorViewStore.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-10-sectiondivider-gradient-stops.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-13-primary-to-brand.ts +0 -0
- /package/src/{lib → editor/core/themes}/parsers/globalRootBlock.ts +0 -0
- /package/src/{lib → editor/core/themes}/slices/domainVars.ts +0 -0
- /package/src/{lib → editor/overlay}/overlayState.ts +0 -0
- /package/src/{pages → editor/pages}/ComponentEditorPage.svelte.d.ts +0 -0
- /package/src/{pages → editor/pages}/Editor.svelte.d.ts +0 -0
- /package/src/{ui → editor/ui}/Toggle.svelte +0 -0
- /package/src/{ui → editor/ui}/UIDialog.svelte +0 -0
- /package/src/{ui → editor/ui}/UIFontWeightSelector.svelte +0 -0
- /package/src/{ui → editor/ui}/UIOptionItem.svelte +0 -0
- /package/src/{ui → editor/ui}/UIOptionList.svelte +0 -0
- /package/src/{ui → editor/ui}/UIRadioGroup.svelte +0 -0
- /package/src/{lib → editor/ui}/copyPopover.ts +0 -0
- /package/src/{ui → editor/ui}/curveEngine.ts +0 -0
- /package/src/{ui → editor/ui}/index.ts +0 -0
- /package/src/{ui → editor/ui}/keepInViewport.ts +0 -0
- /package/src/{ui → editor/ui}/palette/ScaleCurveEditor.svelte +0 -0
- /package/src/{lib → editor/ui}/scrollSection.ts +0 -0
- /package/src/{ui → editor/ui}/sections/tokenScales.ts +0 -0
- /package/src/{ui → editor/ui}/variantScales.ts +0 -0
- /package/src/{assets → system/assets}/newspaper.webp +0 -0
- /package/src/{assets → system/assets}/offering.webp +0 -0
- /package/src/{components → system/components}/Callout.svelte +0 -0
- /package/src/{components → system/components}/Image.svelte +0 -0
- /package/src/{components → system/components}/RadioButton.svelte +0 -0
- /package/src/{components → system/components}/types.ts +0 -0
- /package/src/{styles → system/styles}/_padding.scss +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin.woff2 +0 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { FontFamily, FontSource } from '../core/themes/themeTypes';
|
|
3
|
+
import { editorState, setFontSources, transaction } from '../core/store/editorStore';
|
|
4
|
+
import { applyFontSources, applyFontStacks } from '../core/fonts/fontLoader';
|
|
5
|
+
import {
|
|
6
|
+
buildSourceFromFontFaceText,
|
|
7
|
+
buildSourceFromUrl,
|
|
8
|
+
discoverFamiliesFromUrl,
|
|
9
|
+
parseFontFaceText,
|
|
10
|
+
type ParsedFamily,
|
|
11
|
+
} from '../core/fonts/fontParse';
|
|
12
|
+
import UIPillButton from './UIPillButton.svelte';
|
|
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);
|
|
30
|
+
let urlParsed: ParsedFamily[] | null = $state(null);
|
|
31
|
+
let urlPickedNames = $state(new Set<string>());
|
|
32
|
+
let urlNeedsManualFamilies = $state(false);
|
|
33
|
+
let urlManualFamilies = $state('');
|
|
34
|
+
let fontFaceParsed: ParsedFamily[] = $state([]);
|
|
35
|
+
|
|
36
|
+
function reset() {
|
|
37
|
+
addMode = 'closed';
|
|
38
|
+
nameInput = '';
|
|
39
|
+
nameError = '';
|
|
40
|
+
nameDiscovering = false;
|
|
41
|
+
nameParsed = null;
|
|
42
|
+
pasteInput = '';
|
|
43
|
+
pasteError = '';
|
|
44
|
+
pasteDiscovering = false;
|
|
45
|
+
urlParsed = null;
|
|
46
|
+
urlPickedNames = new Set();
|
|
47
|
+
urlNeedsManualFamilies = false;
|
|
48
|
+
urlManualFamilies = '';
|
|
49
|
+
fontFaceParsed = [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Pull a fonts URL out of whatever the user pastes: bare URL, link tag,
|
|
53
|
+
// @import url(...), or a full style-tag wrapper around an @import.
|
|
54
|
+
function extractFontsUrl(raw: string): string | null {
|
|
55
|
+
const text = raw.trim();
|
|
56
|
+
if (!text) return null;
|
|
57
|
+
const href = text.match(/href\s*=\s*["']([^"']+)["']/i);
|
|
58
|
+
if (href) return href[1];
|
|
59
|
+
const importUrl = text.match(/@import\s+url\(\s*['"]?([^'")]+)['"]?\s*\)/i);
|
|
60
|
+
if (importUrl) return importUrl[1];
|
|
61
|
+
const importBare = text.match(/@import\s+['"]([^'"]+)['"]/i);
|
|
62
|
+
if (importBare) return importBare[1];
|
|
63
|
+
if (/^https?:\/\//i.test(text)) return text;
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let fontSourcesList = $derived($editorState.fonts.sources);
|
|
68
|
+
let fontStacksList = $derived($editorState.fonts.stacks);
|
|
69
|
+
|
|
70
|
+
function commitSources(next: FontSource[]) {
|
|
71
|
+
setFontSources(next);
|
|
72
|
+
applyFontSources(next);
|
|
73
|
+
applyFontStacks(fontStacksList, next);
|
|
74
|
+
}
|
|
75
|
+
|
|
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 = '';
|
|
80
|
+
urlParsed = null;
|
|
81
|
+
urlNeedsManualFamilies = false;
|
|
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);
|
|
96
|
+
if (!url) {
|
|
97
|
+
pasteError = "Couldn't find a fonts URL or @font-face rule in that paste";
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
pasteDiscovering = true;
|
|
101
|
+
try {
|
|
102
|
+
const found = await discoverFamiliesFromUrl(url);
|
|
103
|
+
if (found && found.length > 0) {
|
|
104
|
+
urlParsed = found;
|
|
105
|
+
urlPickedNames = new Set(found.map((f) => f.name));
|
|
106
|
+
} else {
|
|
107
|
+
urlNeedsManualFamilies = true;
|
|
108
|
+
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
pasteError = 'Discovery failed';
|
|
111
|
+
urlNeedsManualFamilies = true;
|
|
112
|
+
}
|
|
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`;
|
|
124
|
+
}
|
|
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
|
+
|
|
177
|
+
function addUrlSource() {
|
|
178
|
+
const url = extractFontsUrl(pasteInput);
|
|
179
|
+
if (!url) {
|
|
180
|
+
pasteError = "Couldn't find a fonts URL in that paste";
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
let families: ParsedFamily[] = [];
|
|
184
|
+
if (urlParsed) {
|
|
185
|
+
// Snapshot to drop the $state proxy — see comment in addNameSource.
|
|
186
|
+
families = ($state.snapshot(urlParsed) as ParsedFamily[]).filter((f) => urlPickedNames.has(f.name));
|
|
187
|
+
} else if (urlNeedsManualFamilies) {
|
|
188
|
+
families = urlManualFamilies
|
|
189
|
+
.split(',')
|
|
190
|
+
.map((s) => s.trim())
|
|
191
|
+
.filter(Boolean)
|
|
192
|
+
.map((name) => ({ name }));
|
|
193
|
+
}
|
|
194
|
+
if (families.length === 0) {
|
|
195
|
+
pasteError = 'Pick at least one family';
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const source = buildSourceFromUrl(url, families);
|
|
199
|
+
commitSources([...fontSourcesList, source]);
|
|
200
|
+
reset();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function addFontFaceSource() {
|
|
204
|
+
if (fontFaceParsed.length === 0) return;
|
|
205
|
+
const families = $state.snapshot(fontFaceParsed) as ParsedFamily[];
|
|
206
|
+
const source = buildSourceFromFontFaceText(pasteInput, families);
|
|
207
|
+
commitSources([...fontSourcesList, source]);
|
|
208
|
+
reset();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function removeFamily(sourceId: string, familyId: string) {
|
|
212
|
+
const next = fontSourcesList
|
|
213
|
+
.map((s) => (s.id === sourceId ? { ...s, families: s.families.filter((f) => f.id !== familyId) } : s))
|
|
214
|
+
.filter((s) => s.families.length > 0);
|
|
215
|
+
const updatedStacks = fontStacksList.map((stack) => ({
|
|
216
|
+
...stack,
|
|
217
|
+
slots: stack.slots.filter((slot) => !(slot.kind === 'project' && slot.familyId === familyId)),
|
|
218
|
+
}));
|
|
219
|
+
transaction('remove font family', (s) => {
|
|
220
|
+
s.fonts.sources = next;
|
|
221
|
+
s.fonts.stacks = updatedStacks;
|
|
222
|
+
});
|
|
223
|
+
applyFontSources(next);
|
|
224
|
+
applyFontStacks(updatedStacks, next);
|
|
225
|
+
}
|
|
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
|
+
|
|
242
|
+
function sourceKindLabel(source: FontSource): string {
|
|
243
|
+
if (source.kind === 'google') return 'Google';
|
|
244
|
+
if (source.kind === 'typekit') return 'Typekit';
|
|
245
|
+
if (source.kind === 'font-face') return 'Local';
|
|
246
|
+
return 'CSS URL';
|
|
247
|
+
}
|
|
248
|
+
</script>
|
|
249
|
+
|
|
250
|
+
<section class="project-fonts">
|
|
251
|
+
<header class="pf-header">
|
|
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>
|
|
277
|
+
</header>
|
|
278
|
+
|
|
279
|
+
{#if fontSourcesList.length === 0}
|
|
280
|
+
<p class="pf-empty">No fonts loaded yet. Use the add button below.</p>
|
|
281
|
+
{/if}
|
|
282
|
+
|
|
283
|
+
<ul class="pf-family-list">
|
|
284
|
+
{#each fontSourcesList as source (source.id)}
|
|
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}
|
|
307
|
+
{/each}
|
|
308
|
+
</ul>
|
|
309
|
+
|
|
310
|
+
{#if addMode !== 'closed'}
|
|
311
|
+
<div class="pf-add-panel">
|
|
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>
|
|
325
|
+
<button type="button" class="pf-add-close" onclick={reset} aria-label="Cancel">×</button>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
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'}
|
|
373
|
+
<textarea
|
|
374
|
+
class="ui-form-input pf-textarea pf-url-input"
|
|
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}
|
|
378
|
+
></textarea>
|
|
379
|
+
<div class="pf-row">
|
|
380
|
+
<UIPillButton variant="secondary" onclick={detectPaste} disabled={!pasteInput.trim() || pasteDiscovering}>
|
|
381
|
+
{pasteDiscovering ? 'Checking…' : 'Detect'}
|
|
382
|
+
</UIPillButton>
|
|
383
|
+
</div>
|
|
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}
|
|
389
|
+
<div class="pf-detected">Detected families — pick which to add:</div>
|
|
390
|
+
<ul class="pf-checklist">
|
|
391
|
+
{#each urlParsed as f (f.name)}
|
|
392
|
+
<li>
|
|
393
|
+
<label>
|
|
394
|
+
<input
|
|
395
|
+
type="checkbox"
|
|
396
|
+
checked={urlPickedNames.has(f.name)}
|
|
397
|
+
onchange={(e) => {
|
|
398
|
+
const target = e.currentTarget;
|
|
399
|
+
const s = new Set(urlPickedNames);
|
|
400
|
+
if (target.checked) s.add(f.name); else s.delete(f.name);
|
|
401
|
+
urlPickedNames = s;
|
|
402
|
+
}}
|
|
403
|
+
/>
|
|
404
|
+
<span class="pf-check-name">{f.name}</span>
|
|
405
|
+
{#if f.weights && f.weights.length > 0}
|
|
406
|
+
<span class="pf-check-meta">{f.weights.length}w</span>
|
|
407
|
+
{/if}
|
|
408
|
+
</label>
|
|
409
|
+
</li>
|
|
410
|
+
{/each}
|
|
411
|
+
</ul>
|
|
412
|
+
<UIPillButton variant="primary" onclick={addUrlSource}>Add {urlPickedNames.size} selected</UIPillButton>
|
|
413
|
+
{:else if urlNeedsManualFamilies}
|
|
414
|
+
<div class="pf-detected">Couldn't auto-detect families (CORS or no metadata). Name them:</div>
|
|
415
|
+
<input
|
|
416
|
+
type="text"
|
|
417
|
+
class="ui-form-input"
|
|
418
|
+
placeholder="Comma-separated family names"
|
|
419
|
+
bind:value={urlManualFamilies}
|
|
420
|
+
/>
|
|
421
|
+
<UIPillButton variant="primary" onclick={addUrlSource} disabled={!urlManualFamilies.trim()}>Add</UIPillButton>
|
|
422
|
+
{/if}
|
|
423
|
+
{/if}
|
|
424
|
+
</div>
|
|
425
|
+
{/if}
|
|
426
|
+
</section>
|
|
427
|
+
|
|
428
|
+
<style>
|
|
429
|
+
.project-fonts {
|
|
430
|
+
display: flex;
|
|
431
|
+
flex-direction: column;
|
|
432
|
+
gap: var(--ui-space-12);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.pf-header {
|
|
436
|
+
display: flex;
|
|
437
|
+
align-items: baseline;
|
|
438
|
+
gap: var(--ui-space-12);
|
|
439
|
+
justify-content: space-between;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.pf-title-row {
|
|
443
|
+
display: flex;
|
|
444
|
+
align-items: center;
|
|
445
|
+
gap: var(--ui-space-4);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.group-title {
|
|
449
|
+
margin: 0;
|
|
450
|
+
font-size: var(--ui-font-size-xl);
|
|
451
|
+
font-weight: var(--ui-font-weight-bold);
|
|
452
|
+
color: var(--ui-text-primary);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.pf-empty {
|
|
456
|
+
margin: 0;
|
|
457
|
+
padding: var(--ui-space-8) 0;
|
|
458
|
+
color: var(--ui-text-muted);
|
|
459
|
+
font-size: var(--ui-font-size-sm);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.pf-family-list {
|
|
463
|
+
list-style: none;
|
|
464
|
+
margin: 0;
|
|
465
|
+
padding: 0;
|
|
466
|
+
display: grid;
|
|
467
|
+
grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
|
|
468
|
+
gap: var(--ui-space-6);
|
|
469
|
+
align-items: start;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.pf-family {
|
|
473
|
+
display: grid;
|
|
474
|
+
grid-template-columns: 1.75rem 1fr auto 24px;
|
|
475
|
+
align-items: center;
|
|
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);
|
|
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));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.pf-family-remove {
|
|
489
|
+
display: inline-flex;
|
|
490
|
+
align-items: center;
|
|
491
|
+
justify-content: center;
|
|
492
|
+
width: 24px;
|
|
493
|
+
height: 24px;
|
|
494
|
+
padding: 0;
|
|
495
|
+
background: none;
|
|
496
|
+
border: none;
|
|
497
|
+
border-radius: var(--ui-radius-sm);
|
|
498
|
+
color: var(--ui-text-muted);
|
|
499
|
+
cursor: pointer;
|
|
500
|
+
font-size: var(--ui-font-size-sm);
|
|
501
|
+
line-height: 1;
|
|
502
|
+
}
|
|
503
|
+
.pf-family-remove:hover {
|
|
504
|
+
color: var(--ui-text-primary);
|
|
505
|
+
background: var(--ui-surface-hover, rgba(255,255,255,0.06));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.pf-family-preview {
|
|
509
|
+
font-size: var(--ui-font-size-md);
|
|
510
|
+
color: var(--ui-text-primary);
|
|
511
|
+
min-width: 1.75rem;
|
|
512
|
+
text-align: center;
|
|
513
|
+
line-height: 1.2;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.pf-family-name {
|
|
517
|
+
font-size: var(--ui-font-size-sm);
|
|
518
|
+
color: var(--ui-text-primary);
|
|
519
|
+
min-width: 0;
|
|
520
|
+
overflow: hidden;
|
|
521
|
+
text-overflow: ellipsis;
|
|
522
|
+
white-space: nowrap;
|
|
523
|
+
}
|
|
524
|
+
|
|
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 {
|
|
536
|
+
display: grid;
|
|
537
|
+
grid-template-columns: auto 1fr auto;
|
|
538
|
+
align-items: center;
|
|
539
|
+
gap: var(--ui-space-12);
|
|
540
|
+
}
|
|
541
|
+
.pf-add-eyebrow {
|
|
542
|
+
font-size: var(--ui-font-size-xs);
|
|
543
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
544
|
+
color: var(--ui-text-tertiary);
|
|
545
|
+
text-transform: uppercase;
|
|
546
|
+
letter-spacing: 0.04em;
|
|
547
|
+
}
|
|
548
|
+
.pf-browse-row {
|
|
549
|
+
display: flex;
|
|
550
|
+
flex-wrap: wrap;
|
|
551
|
+
gap: var(--ui-space-8);
|
|
552
|
+
}
|
|
553
|
+
.pf-add-close {
|
|
554
|
+
background: none;
|
|
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);
|
|
560
|
+
border-radius: var(--ui-radius-sm);
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
justify-self: end;
|
|
563
|
+
}
|
|
564
|
+
.pf-add-close:hover {
|
|
565
|
+
color: var(--ui-text-primary);
|
|
566
|
+
background: var(--ui-surface-hover, rgba(255,255,255,0.06));
|
|
567
|
+
}
|
|
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;
|
|
577
|
+
}
|
|
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;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.pf-row {
|
|
592
|
+
display: flex;
|
|
593
|
+
gap: var(--ui-space-8);
|
|
594
|
+
align-items: center;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.pf-error { color: var(--ui-text-danger, #ff6b6b); font-size: var(--ui-font-size-sm); }
|
|
598
|
+
|
|
599
|
+
.pf-detected { color: var(--ui-text-secondary); font-size: var(--ui-font-size-sm); }
|
|
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
|
+
|
|
607
|
+
.pf-checklist {
|
|
608
|
+
list-style: none;
|
|
609
|
+
margin: 0;
|
|
610
|
+
padding: 0;
|
|
611
|
+
display: flex;
|
|
612
|
+
flex-direction: column;
|
|
613
|
+
gap: var(--ui-space-2);
|
|
614
|
+
}
|
|
615
|
+
.pf-checklist label {
|
|
616
|
+
display: flex;
|
|
617
|
+
align-items: center;
|
|
618
|
+
gap: var(--ui-space-6);
|
|
619
|
+
font-size: var(--ui-font-size-sm);
|
|
620
|
+
cursor: pointer;
|
|
621
|
+
}
|
|
622
|
+
.pf-check-name { color: var(--ui-text-primary); }
|
|
623
|
+
.pf-check-meta { color: var(--ui-text-tertiary); font-family: var(--ui-font-mono); font-size: var(--ui-font-size-xs); }
|
|
624
|
+
|
|
625
|
+
.pf-textarea {
|
|
626
|
+
font-family: var(--ui-font-mono);
|
|
627
|
+
font-size: var(--ui-font-size-xs);
|
|
628
|
+
resize: vertical;
|
|
629
|
+
color: var(--ui-text-primary);
|
|
630
|
+
}
|
|
631
|
+
.pf-textarea::placeholder { color: var(--ui-text-muted); opacity: 1; }
|
|
632
|
+
|
|
633
|
+
.pf-url-input {
|
|
634
|
+
white-space: pre;
|
|
635
|
+
overflow-x: auto;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
</style>
|
|
@@ -272,12 +272,12 @@
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
.section-title {
|
|
275
|
-
font-size: var(--ui-font-size-
|
|
275
|
+
font-size: var(--ui-font-size-2xl);
|
|
276
276
|
font-weight: var(--ui-font-weight-semibold);
|
|
277
277
|
color: var(--ui-text-primary);
|
|
278
278
|
margin: 0;
|
|
279
279
|
padding-bottom: var(--ui-space-8);
|
|
280
|
-
border-bottom:
|
|
280
|
+
border-bottom: 2px solid var(--ui-border-high);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
.swatch-groups-grid {
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
.group-title {
|
|
297
|
-
font-size: var(--ui-font-size-
|
|
297
|
+
font-size: var(--ui-font-size-lg);
|
|
298
298
|
font-weight: var(--ui-font-weight-semibold);
|
|
299
299
|
color: var(--ui-text-secondary);
|
|
300
300
|
margin: 0;
|
|
@@ -125,12 +125,12 @@
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
.group-title {
|
|
128
|
-
font-size: var(--ui-font-size-
|
|
128
|
+
font-size: var(--ui-font-size-lg);
|
|
129
129
|
font-weight: var(--ui-font-weight-semibold);
|
|
130
130
|
color: var(--ui-text-secondary);
|
|
131
131
|
margin: 0;
|
|
132
132
|
padding-bottom: var(--ui-space-4);
|
|
133
|
-
border-bottom: 1px solid var(--ui-border-
|
|
133
|
+
border-bottom: 1px solid var(--ui-border-low);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
.text-colors-grid {
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
|
|
142
142
|
.text-color-card {
|
|
143
143
|
background: var(--ui-surface-low);
|
|
144
|
-
border: 1px solid var(--ui-border-
|
|
144
|
+
border: 1px solid var(--ui-border-low);
|
|
145
145
|
border-radius: var(--ui-radius-md);
|
|
146
146
|
padding: var(--ui-space-12);
|
|
147
147
|
display: flex;
|