@motion-proto/live-tokens 0.6.2 → 0.7.1
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 +147 -136
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +145 -135
- package/package.json +25 -40
- 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 +3 -3
- 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 +64 -37
- package/src/{component-editor → editor/component-editor}/ProgressBarEditor.svelte +5 -4
- package/src/{component-editor → editor/component-editor}/RadioButtonEditor.svelte +3 -3
- package/src/{component-editor → editor/component-editor}/SectionDividerEditor.svelte +57 -84
- package/src/{component-editor → editor/component-editor}/SegmentedControlEditor.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/StandardButtonsEditor.svelte +16 -20
- 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/{component-editor → editor/component-editor}/registry.ts +28 -18
- package/src/{component-editor → editor/component-editor}/scaffolding/AngleDial.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentEditorBase.svelte +3 -51
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte +144 -416
- 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/DividerEditor.svelte +1 -1
- package/src/{component-editor → editor/component-editor}/scaffolding/FieldsetWrapper.svelte +1 -1
- package/src/{component-editor → editor/component-editor}/scaffolding/GradientCard.svelte +6 -6
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkageChart.svelte +6 -6
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkedBlock.svelte +6 -11
- package/src/editor/component-editor/scaffolding/NonStylableConfig.svelte +38 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/SaveAsDialog.svelte +66 -12
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +72 -0
- package/src/editor/component-editor/scaffolding/ShadowBackdropControls.svelte +132 -0
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +257 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/TokenLayout.svelte +9 -7
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +644 -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 +14 -0
- package/src/{lib → editor/core/components}/componentConfigService.ts +2 -2
- package/src/{lib → editor/core/components}/componentPersist.ts +5 -5
- 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 +116 -0
- package/src/{lib → editor/core/palettes}/paletteDerivation.ts +2 -2
- package/src/{lib → editor/core/palettes}/tokenRegistry.ts +5 -5
- 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 +17 -17
- package/src/{lib → editor/core/store}/editorTypes.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/columns.ts +2 -2
- package/src/{lib → editor/core/themes}/slices/components.ts +2 -2
- package/src/{lib → editor/core/themes}/slices/fonts.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/gradients.ts +2 -2
- 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 +6 -6
- package/src/{lib → editor/core/themes}/themeService.ts +6 -6
- package/src/{lib → editor/core/themes}/themeTypes.ts +11 -7
- package/src/editor/index.ts +69 -0
- package/src/{lib → editor/overlay}/LiveEditorOverlay.svelte +79 -125
- 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 +41 -21
- package/src/{styles → editor/styles}/ui-form-controls.css +8 -8
- package/src/{ui → editor/ui}/BezierCurveEditor.svelte +8 -8
- package/src/{ui → editor/ui}/ColorEditPanel.svelte +13 -13
- package/src/{ui → editor/ui}/EditorViewSwitcher.svelte +8 -6
- package/src/editor/ui/FileLoadList.svelte +350 -0
- package/src/editor/ui/FilePill.svelte +80 -0
- package/src/{ui → editor/ui}/FontStackEditor.svelte +7 -7
- package/src/{ui → editor/ui}/GradientEditor.svelte +11 -11
- package/src/{ui → editor/ui}/GradientStopPicker.svelte +1 -1
- package/src/editor/ui/ManifestFileManager.svelte +371 -0
- package/src/{ui → editor/ui}/PaletteEditor.svelte +132 -598
- package/src/{ui → editor/ui}/ProjectFontsSection.svelte +102 -144
- package/src/{ui → editor/ui}/SurfacesTab.svelte +3 -3
- package/src/{ui → editor/ui}/TextTab.svelte +3 -3
- package/src/{ui → editor/ui}/ThemeFileManager.svelte +286 -519
- package/src/{ui → editor/ui}/UICopyPopover.svelte +4 -4
- package/src/{ui → editor/ui}/UIFontFamilySelector.svelte +6 -6
- package/src/{ui → editor/ui}/UIFontSizeSelector.svelte +1 -1
- package/src/editor/ui/UIInfoPopover.svelte +244 -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 +26 -26
- package/src/editor/ui/UIPillButton.svelte +138 -0
- package/src/{ui → editor/ui}/UIRadio.svelte +2 -2
- package/src/{ui → editor/ui}/UIRelinkConfirmPopover.svelte +4 -4
- package/src/editor/ui/UISquareButton.svelte +172 -0
- package/src/{ui → editor/ui}/UITokenSelector.svelte +10 -10
- package/src/{ui → editor/ui}/UIVariantSelector.svelte +1 -1
- package/src/{ui → editor/ui}/VariablesTab.svelte +31 -8
- package/src/{ui → editor/ui}/palette/GradientStopEditor.svelte +13 -13
- package/src/{ui → editor/ui}/palette/OverridesPanel.svelte +13 -13
- package/src/{ui → editor/ui}/palette/PaletteBase.svelte +8 -5
- 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 -17
- package/src/{ui → editor/ui}/sections/GradientsSection.svelte +7 -7
- package/src/{ui → editor/ui}/sections/OverlaysSection.svelte +17 -17
- package/src/{ui → editor/ui}/sections/ShadowsSection.svelte +22 -22
- package/src/{ui → editor/ui}/sections/TokenScaleTable.svelte +3 -3
- package/src/{components → system/components}/Badge.svelte +0 -36
- package/src/{components → system/components}/Card.svelte +8 -62
- package/src/{components → system/components}/CornerBadge.svelte +8 -24
- package/src/{components → system/components}/Dialog.svelte +1 -1
- package/src/system/components/FloatingTokenTags.css +256 -0
- package/src/system/components/FloatingTokenTags.svelte +592 -0
- package/src/{components → system/components}/InlineEditActions.svelte +6 -4
- package/src/system/components/MenuSelect.svelte +229 -0
- package/src/{components → system/components}/ProgressBar.svelte +29 -11
- 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/{styles → system/styles}/fonts.css +6 -3
- package/src/{styles → system/styles}/tokens.css +149 -29
- package/src/component-editor/ImageEditor.svelte +0 -74
- 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/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/ui/PresetFileManager.svelte +0 -1116
- package/src/ui/UnsavedComponentsDialog.svelte +0 -315
- /package/src/{styles → app}/site.css +0 -0
- /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/TypeEditor.svelte +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/buildTypeGroupTokens.ts +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/components}/componentConfigKeys.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}/migrations/index.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}/ColumnsOverlay.svelte +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}/Button.svelte +0 -0
- /package/src/{components → system/components}/Callout.svelte +0 -0
- /package/src/{components → system/components}/CollapsibleSection.svelte +0 -0
- /package/src/{components → system/components}/Image.svelte +0 -0
- /package/src/{components → system/components}/Notification.svelte +0 -0
- /package/src/{components → system/components}/RadioButton.svelte +0 -0
- /package/src/{components → system/components}/SectionDivider.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
|
@@ -1,1116 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { stopPropagation } from 'svelte/legacy';
|
|
3
|
-
|
|
4
|
-
import { onMount, onDestroy } from 'svelte';
|
|
5
|
-
import type { Preset, PresetMeta } from '../lib/themeTypes';
|
|
6
|
-
import {
|
|
7
|
-
listPresets,
|
|
8
|
-
deletePreset,
|
|
9
|
-
captureCurrentAsPreset,
|
|
10
|
-
captureProductionAsPreset,
|
|
11
|
-
applyPreset,
|
|
12
|
-
applyPresetToProduction,
|
|
13
|
-
compareActiveToProduction,
|
|
14
|
-
getActivePreset,
|
|
15
|
-
} from '../lib/presetService';
|
|
16
|
-
import { sanitizeFileName } from '../lib/themeService';
|
|
17
|
-
import { dirty, componentDirty } from '../lib/editorStore';
|
|
18
|
-
import { productionRevision, presetProductionComparison } from '../lib/productionPulse';
|
|
19
|
-
import UIDialog from './UIDialog.svelte';
|
|
20
|
-
import UnsavedComponentsDialog from './UnsavedComponentsDialog.svelte';
|
|
21
|
-
import SaveAsDialog from '../component-editor/scaffolding/SaveAsDialog.svelte';
|
|
22
|
-
|
|
23
|
-
let files: PresetMeta[] = $state([]);
|
|
24
|
-
let showFileList = $state(false);
|
|
25
|
-
let saveAsDialog = $state(false);
|
|
26
|
-
let captureProdDialog = $state(false);
|
|
27
|
-
|
|
28
|
-
let activeFileName = $state('default');
|
|
29
|
-
let currentDisplayName = $state('Default Preset');
|
|
30
|
-
let activePreset = $state<Preset | null>(null);
|
|
31
|
-
|
|
32
|
-
let saveStatus: 'idle' | 'saving' | 'saved' | 'error' = $state('idle');
|
|
33
|
-
let applyStatus: 'idle' | 'applying' = $state('idle');
|
|
34
|
-
let prodApplyStatus: 'idle' | 'applying' | 'done' | 'error' = $state('idle');
|
|
35
|
-
let prodCaptureStatus: 'idle' | 'saving' | 'saved' | 'error' = $state('idle');
|
|
36
|
-
|
|
37
|
-
let unsavedDialog = $state(false);
|
|
38
|
-
/** Pending capture target while UnsavedComponentsDialog is open. The dialog
|
|
39
|
-
* resolves to either "save the preset using current on-disk files" (proceed)
|
|
40
|
-
* or "user cancelled / closed". */
|
|
41
|
-
let pendingCapture: { fileName: string; displayName: string } | null = null;
|
|
42
|
-
|
|
43
|
-
let infoOpen = $state(false);
|
|
44
|
-
let infoBtnEl = $state<HTMLButtonElement | undefined>(undefined);
|
|
45
|
-
let infoPopoverEl = $state<HTMLDivElement | undefined>(undefined);
|
|
46
|
-
let infoPopoverReady = $state(false);
|
|
47
|
-
|
|
48
|
-
let dirtyComponentIds = $derived(
|
|
49
|
-
Object.entries($componentDirty)
|
|
50
|
-
.filter(([, isDirty]) => isDirty)
|
|
51
|
-
.map(([id]) => id),
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
/** True when there are unsaved theme/component edits that won't make it into
|
|
55
|
-
* the next preset Save (capture reads on-disk files, not the editor). */
|
|
56
|
-
let presetStale = $derived(dirtyComponentIds.length > 0 || $dirty);
|
|
57
|
-
|
|
58
|
-
let isDefaultActive = $derived(activeFileName === 'default');
|
|
59
|
-
|
|
60
|
-
let prodStatus = $derived($presetProductionComparison?.status ?? 'editor-only');
|
|
61
|
-
let prodPresetName = $derived($presetProductionComparison?.productionPreset?.name ?? '—');
|
|
62
|
-
let prodIsInSync = $derived(prodStatus === 'in-production');
|
|
63
|
-
let prodIsDiverged = $derived(prodStatus === 'diverged');
|
|
64
|
-
/** Editor row is "live" when this preset IS the one in production AND
|
|
65
|
-
* nothing's pending — mirrors the cfm-row-editor.applied rule so the green
|
|
66
|
-
* dot vocabulary is consistent across components and presets. */
|
|
67
|
-
let editorIsApplied = $derived(prodIsInSync && !presetStale);
|
|
68
|
-
|
|
69
|
-
async function refreshFiles() {
|
|
70
|
-
try {
|
|
71
|
-
files = await listPresets();
|
|
72
|
-
const active = files.find((f) => f.isActive);
|
|
73
|
-
if (active) {
|
|
74
|
-
activeFileName = active.fileName;
|
|
75
|
-
currentDisplayName = active.name;
|
|
76
|
-
}
|
|
77
|
-
} catch {
|
|
78
|
-
// silent — empty list
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function refreshActive() {
|
|
83
|
-
try {
|
|
84
|
-
const active = await getActivePreset();
|
|
85
|
-
if (active) {
|
|
86
|
-
activePreset = active;
|
|
87
|
-
activeFileName = active._fileName ?? activeFileName;
|
|
88
|
-
currentDisplayName = active.name;
|
|
89
|
-
}
|
|
90
|
-
} catch {
|
|
91
|
-
// silent
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function refreshProductionComparison() {
|
|
96
|
-
if (!activePreset) return;
|
|
97
|
-
try {
|
|
98
|
-
$presetProductionComparison = await compareActiveToProduction(activePreset);
|
|
99
|
-
} catch {
|
|
100
|
-
// silent — leave previous comparison in place
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
onMount(async () => {
|
|
105
|
-
await refreshActive();
|
|
106
|
-
await refreshFiles();
|
|
107
|
-
await refreshProductionComparison();
|
|
108
|
-
window.addEventListener('keydown', handleKeydown);
|
|
109
|
-
document.addEventListener('mousedown', handleDocumentMousedown, true);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Re-compare when any production pointer ticks (per-component Adopt,
|
|
113
|
-
// theme production change, or our own apply). Skip the initial tick on
|
|
114
|
-
// mount — refreshProductionComparison runs there already. Plain closure
|
|
115
|
-
// variable (not $state) so writing it doesn't add a reactive dependency
|
|
116
|
-
// back to the effect.
|
|
117
|
-
let pulseInitialised = false;
|
|
118
|
-
$effect(() => {
|
|
119
|
-
void $productionRevision;
|
|
120
|
-
if (!pulseInitialised) {
|
|
121
|
-
pulseInitialised = true;
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
refreshProductionComparison();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
onDestroy(() => {
|
|
128
|
-
window.removeEventListener('keydown', handleKeydown);
|
|
129
|
-
document.removeEventListener('mousedown', handleDocumentMousedown, true);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
133
|
-
if (e.key === 'Escape' && infoOpen) {
|
|
134
|
-
infoOpen = false;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function handleDocumentMousedown(e: MouseEvent) {
|
|
139
|
-
if (!infoOpen) return;
|
|
140
|
-
const target = e.target as Element | null;
|
|
141
|
-
if (target && !target.closest('.pfm-info-btn, .pfm-info-popover')) {
|
|
142
|
-
infoOpen = false;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Anchor the fixed-position popover relative to the info button. Lives in
|
|
147
|
-
* the sidebar footer, so flip-up when there's no room below. */
|
|
148
|
-
function positionInfoPopover(): void {
|
|
149
|
-
const btn = infoBtnEl;
|
|
150
|
-
const pop = infoPopoverEl;
|
|
151
|
-
if (!btn || !pop) return;
|
|
152
|
-
const br = btn.getBoundingClientRect();
|
|
153
|
-
const pr = pop.getBoundingClientRect();
|
|
154
|
-
const margin = 8;
|
|
155
|
-
const vw = window.innerWidth;
|
|
156
|
-
const vh = window.innerHeight;
|
|
157
|
-
let left = br.right + margin;
|
|
158
|
-
if (left + pr.width > vw - margin) {
|
|
159
|
-
left = br.left + br.width / 2 - pr.width / 2;
|
|
160
|
-
if (left < margin) left = margin;
|
|
161
|
-
if (left + pr.width > vw - margin) left = vw - margin - pr.width;
|
|
162
|
-
}
|
|
163
|
-
let top = br.bottom + margin;
|
|
164
|
-
if (top + pr.height > vh - margin) {
|
|
165
|
-
top = br.top - margin - pr.height;
|
|
166
|
-
if (top < margin) top = margin;
|
|
167
|
-
}
|
|
168
|
-
pop.style.left = `${left}px`;
|
|
169
|
-
pop.style.top = `${top}px`;
|
|
170
|
-
infoPopoverReady = true;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
$effect(() => {
|
|
174
|
-
if (!infoOpen) {
|
|
175
|
-
infoPopoverReady = false;
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
let raf1 = requestAnimationFrame(() => {
|
|
179
|
-
raf1 = requestAnimationFrame(positionInfoPopover);
|
|
180
|
-
});
|
|
181
|
-
window.addEventListener('scroll', positionInfoPopover, true);
|
|
182
|
-
window.addEventListener('resize', positionInfoPopover);
|
|
183
|
-
return () => {
|
|
184
|
-
cancelAnimationFrame(raf1);
|
|
185
|
-
window.removeEventListener('scroll', positionInfoPopover, true);
|
|
186
|
-
window.removeEventListener('resize', positionInfoPopover);
|
|
187
|
-
};
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
/** Standard "save dirty editor first?" gate. If any component has unsaved
|
|
191
|
-
* edits, defer to UnsavedComponentsDialog (which can save them in place);
|
|
192
|
-
* callers receive `false` and the dialog's "proceed" path re-invokes the
|
|
193
|
-
* capture with the pending args. Returns `true` if the caller can capture
|
|
194
|
-
* immediately (no dirty components). */
|
|
195
|
-
function gateDirtyCapture(fileName: string, displayName: string): boolean {
|
|
196
|
-
if (dirtyComponentIds.length === 0 && !$dirty) return true;
|
|
197
|
-
pendingCapture = { fileName, displayName };
|
|
198
|
-
unsavedDialog = true;
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function handleUnsavedProceed() {
|
|
203
|
-
const target = pendingCapture;
|
|
204
|
-
pendingCapture = null;
|
|
205
|
-
if (target) doCapture(target.fileName, target.displayName);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function doCapture(fileName: string, displayName: string) {
|
|
209
|
-
saveStatus = 'saving';
|
|
210
|
-
try {
|
|
211
|
-
await captureCurrentAsPreset(fileName, displayName);
|
|
212
|
-
activeFileName = fileName;
|
|
213
|
-
currentDisplayName = displayName;
|
|
214
|
-
saveStatus = 'saved';
|
|
215
|
-
setTimeout(() => { saveStatus = 'idle'; }, 2000);
|
|
216
|
-
await refreshFiles();
|
|
217
|
-
await refreshActive();
|
|
218
|
-
await refreshProductionComparison();
|
|
219
|
-
} catch {
|
|
220
|
-
saveStatus = 'error';
|
|
221
|
-
setTimeout(() => { saveStatus = 'idle'; }, 3000);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async function handleApplyToProduction() {
|
|
226
|
-
if (!activeFileName) return;
|
|
227
|
-
prodApplyStatus = 'applying';
|
|
228
|
-
try {
|
|
229
|
-
await applyPresetToProduction(activeFileName);
|
|
230
|
-
// applyPresetToProduction bakes css; refresh comparison so the
|
|
231
|
-
// Production card flips to the live state.
|
|
232
|
-
await refreshProductionComparison();
|
|
233
|
-
prodApplyStatus = 'done';
|
|
234
|
-
setTimeout(() => { prodApplyStatus = 'idle'; }, 2000);
|
|
235
|
-
} catch {
|
|
236
|
-
prodApplyStatus = 'error';
|
|
237
|
-
setTimeout(() => { prodApplyStatus = 'idle'; }, 3000);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function openCaptureFromProduction() {
|
|
242
|
-
captureProdDialog = true;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async function confirmCaptureFromProduction(detail: { displayName: string; fileName: string }) {
|
|
246
|
-
const { displayName, fileName } = detail;
|
|
247
|
-
prodCaptureStatus = 'saving';
|
|
248
|
-
try {
|
|
249
|
-
await captureProductionAsPreset(fileName, displayName);
|
|
250
|
-
await refreshFiles();
|
|
251
|
-
prodCaptureStatus = 'saved';
|
|
252
|
-
setTimeout(() => { prodCaptureStatus = 'idle'; }, 2000);
|
|
253
|
-
} catch {
|
|
254
|
-
prodCaptureStatus = 'error';
|
|
255
|
-
setTimeout(() => { prodCaptureStatus = 'idle'; }, 3000);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function handleSave() {
|
|
260
|
-
// Default is the protected initial-distribution preset — disabled in the
|
|
261
|
-
// UI when active, but guard here too. Callers in that state should route
|
|
262
|
-
// through Save As (which seeds an incremented name).
|
|
263
|
-
if (isDefaultActive) return;
|
|
264
|
-
if (!gateDirtyCapture(activeFileName, currentDisplayName)) return;
|
|
265
|
-
await doCapture(activeFileName, currentDisplayName);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function openSaveAs() {
|
|
269
|
-
showFileList = false;
|
|
270
|
-
saveAsDialog = true;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async function confirmSaveAs(detail: { displayName: string; fileName: string }) {
|
|
274
|
-
const { displayName, fileName } = detail;
|
|
275
|
-
if (!gateDirtyCapture(fileName, displayName)) return;
|
|
276
|
-
await doCapture(fileName, displayName);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async function handleApply(file: PresetMeta) {
|
|
280
|
-
if ($dirty) {
|
|
281
|
-
const ok = window.confirm(
|
|
282
|
-
'Loading a preset will reload the editor and discard unsaved changes. Continue?',
|
|
283
|
-
);
|
|
284
|
-
if (!ok) return;
|
|
285
|
-
}
|
|
286
|
-
showFileList = false;
|
|
287
|
-
applyStatus = 'applying';
|
|
288
|
-
try {
|
|
289
|
-
await applyPreset(file.fileName);
|
|
290
|
-
// Page reload: simplest path — editor rehydrates from the now-active
|
|
291
|
-
// theme + component configs the server just pinned for us.
|
|
292
|
-
window.location.reload();
|
|
293
|
-
} catch (err) {
|
|
294
|
-
applyStatus = 'idle';
|
|
295
|
-
window.alert(`Failed to apply preset: ${(err as Error).message}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async function handleDelete(file: PresetMeta) {
|
|
300
|
-
if (file.fileName === 'default') return;
|
|
301
|
-
try {
|
|
302
|
-
await deletePreset(file.fileName);
|
|
303
|
-
await refreshFiles();
|
|
304
|
-
if (file.fileName === activeFileName) {
|
|
305
|
-
activeFileName = 'default';
|
|
306
|
-
currentDisplayName = 'Default Preset';
|
|
307
|
-
}
|
|
308
|
-
} catch {
|
|
309
|
-
// silent
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function toggleFileList() {
|
|
314
|
-
showFileList = !showFileList;
|
|
315
|
-
if (showFileList) refreshFiles();
|
|
316
|
-
}
|
|
317
|
-
</script>
|
|
318
|
-
|
|
319
|
-
<div class="preset-file-manager">
|
|
320
|
-
<div class="pfm-header">
|
|
321
|
-
<span class="pfm-header-label">Preset</span>
|
|
322
|
-
<button
|
|
323
|
-
type="button"
|
|
324
|
-
class="pfm-info-btn"
|
|
325
|
-
aria-label="About presets"
|
|
326
|
-
aria-expanded={infoOpen}
|
|
327
|
-
bind:this={infoBtnEl}
|
|
328
|
-
onclick={() => (infoOpen = !infoOpen)}
|
|
329
|
-
>
|
|
330
|
-
<i class="fas fa-circle-info"></i>
|
|
331
|
-
</button>
|
|
332
|
-
</div>
|
|
333
|
-
|
|
334
|
-
<div class="pfm-cards" class:in-sync={prodIsInSync}>
|
|
335
|
-
<div
|
|
336
|
-
class="pfm-card pfm-card-editor"
|
|
337
|
-
class:dirty={presetStale}
|
|
338
|
-
class:applied={editorIsApplied}
|
|
339
|
-
>
|
|
340
|
-
<span class="pfm-rail" aria-hidden="true"></span>
|
|
341
|
-
<div class="pfm-card-head">
|
|
342
|
-
<span class="pfm-card-label">Editor</span>
|
|
343
|
-
<span
|
|
344
|
-
class="pfm-card-status"
|
|
345
|
-
class:dirty={presetStale}
|
|
346
|
-
class:applied={editorIsApplied}
|
|
347
|
-
>
|
|
348
|
-
<i class="pfm-status-dot" aria-hidden="true"></i>
|
|
349
|
-
<span>{presetStale ? 'unsaved' : editorIsApplied ? 'live' : 'saved'}</span>
|
|
350
|
-
</span>
|
|
351
|
-
</div>
|
|
352
|
-
<div class="pfm-pill" class:dirty={presetStale} class:applied={editorIsApplied}>
|
|
353
|
-
<span class="pfm-pill-name" title={currentDisplayName}>{currentDisplayName}</span>
|
|
354
|
-
</div>
|
|
355
|
-
{#if presetStale}
|
|
356
|
-
<span class="pfm-stale-note" aria-live="polite">unsaved edits won't be captured</span>
|
|
357
|
-
{/if}
|
|
358
|
-
<div class="pfm-card-actions pfm-card-actions-stack">
|
|
359
|
-
<button
|
|
360
|
-
class="pfm-btn pfm-btn-row save-btn"
|
|
361
|
-
class:saving={saveStatus === 'saving'}
|
|
362
|
-
class:saved={saveStatus === 'saved'}
|
|
363
|
-
class:error={saveStatus === 'error'}
|
|
364
|
-
onclick={handleSave}
|
|
365
|
-
disabled={saveStatus === 'saving' || applyStatus === 'applying' || isDefaultActive}
|
|
366
|
-
title={isDefaultActive
|
|
367
|
-
? 'Default is read-only — use Save As to capture under a new name'
|
|
368
|
-
: 'Capture the current theme + component configs into this preset'}
|
|
369
|
-
>
|
|
370
|
-
<i
|
|
371
|
-
class="fas"
|
|
372
|
-
class:fa-save={saveStatus === 'idle'}
|
|
373
|
-
class:fa-spinner={saveStatus === 'saving'}
|
|
374
|
-
class:fa-check={saveStatus === 'saved'}
|
|
375
|
-
class:fa-times={saveStatus === 'error'}
|
|
376
|
-
></i>
|
|
377
|
-
<span>
|
|
378
|
-
{#if saveStatus === 'idle'}Save{:else if saveStatus === 'saving'}Saving{:else if saveStatus === 'saved'}Saved{:else}Error{/if}
|
|
379
|
-
</span>
|
|
380
|
-
</button>
|
|
381
|
-
<button class="pfm-btn pfm-btn-row" onclick={openSaveAs} title="Save as new preset">
|
|
382
|
-
<i class="fas fa-copy"></i>
|
|
383
|
-
<span>Save As…</span>
|
|
384
|
-
</button>
|
|
385
|
-
<button
|
|
386
|
-
class="pfm-btn pfm-btn-row"
|
|
387
|
-
class:active={showFileList}
|
|
388
|
-
onclick={toggleFileList}
|
|
389
|
-
disabled={applyStatus === 'applying'}
|
|
390
|
-
title="Load a preset"
|
|
391
|
-
>
|
|
392
|
-
<i class="fas fa-folder-open"></i>
|
|
393
|
-
<span>Load…</span>
|
|
394
|
-
</button>
|
|
395
|
-
</div>
|
|
396
|
-
</div>
|
|
397
|
-
|
|
398
|
-
<button
|
|
399
|
-
class="pfm-adopt-btn"
|
|
400
|
-
class:saving={prodApplyStatus === 'applying'}
|
|
401
|
-
class:saved={prodApplyStatus === 'done'}
|
|
402
|
-
class:error={prodApplyStatus === 'error'}
|
|
403
|
-
class:in-sync={prodIsInSync}
|
|
404
|
-
onclick={handleApplyToProduction}
|
|
405
|
-
disabled={prodApplyStatus === 'applying' || prodIsInSync}
|
|
406
|
-
title={prodIsInSync
|
|
407
|
-
? 'This preset is already in production'
|
|
408
|
-
: prodIsDiverged
|
|
409
|
-
? `Re-adopt "${currentDisplayName}" — discards individual component adopts`
|
|
410
|
-
: `Adopt "${currentDisplayName}" as the production preset`}
|
|
411
|
-
>
|
|
412
|
-
<i
|
|
413
|
-
class="fas"
|
|
414
|
-
class:fa-arrow-down={prodApplyStatus === 'idle'}
|
|
415
|
-
class:fa-spinner={prodApplyStatus === 'applying'}
|
|
416
|
-
class:fa-check={prodApplyStatus === 'done'}
|
|
417
|
-
class:fa-xmark={prodApplyStatus === 'error'}
|
|
418
|
-
></i>
|
|
419
|
-
<span>
|
|
420
|
-
{#if prodApplyStatus === 'idle'}Adopt{:else if prodApplyStatus === 'applying'}Adopting{:else if prodApplyStatus === 'done'}Adopted{:else}Error{/if}
|
|
421
|
-
</span>
|
|
422
|
-
</button>
|
|
423
|
-
|
|
424
|
-
<div
|
|
425
|
-
class="pfm-card pfm-card-production"
|
|
426
|
-
class:in-sync={prodIsInSync}
|
|
427
|
-
class:diverged={prodIsDiverged}
|
|
428
|
-
>
|
|
429
|
-
<span class="pfm-rail" aria-hidden="true"></span>
|
|
430
|
-
<div class="pfm-card-head">
|
|
431
|
-
<span class="pfm-card-label">Production</span>
|
|
432
|
-
<span
|
|
433
|
-
class="pfm-card-status"
|
|
434
|
-
class:applied={prodIsInSync}
|
|
435
|
-
class:diverged={prodIsDiverged}
|
|
436
|
-
>
|
|
437
|
-
<i class="pfm-status-dot" aria-hidden="true"></i>
|
|
438
|
-
<span>
|
|
439
|
-
{#if prodIsInSync}live{:else if prodIsDiverged}diverged{:else}out of sync{/if}
|
|
440
|
-
</span>
|
|
441
|
-
</span>
|
|
442
|
-
</div>
|
|
443
|
-
<div class="pfm-pill" class:applied={prodIsInSync} class:diverged={prodIsDiverged}>
|
|
444
|
-
<span class="pfm-pill-name" title={prodPresetName}>{prodPresetName}</span>
|
|
445
|
-
</div>
|
|
446
|
-
{#if prodIsDiverged && $presetProductionComparison}
|
|
447
|
-
<span class="pfm-diverged-note" aria-live="polite">
|
|
448
|
-
{$presetProductionComparison.driftedComponents.length > 0
|
|
449
|
-
? `${$presetProductionComparison.driftedComponents.length} component${
|
|
450
|
-
$presetProductionComparison.driftedComponents.length === 1 ? '' : 's'
|
|
451
|
-
} adopted individually`
|
|
452
|
-
: 'theme production differs from this preset'}
|
|
453
|
-
</span>
|
|
454
|
-
{/if}
|
|
455
|
-
{#if prodIsDiverged}
|
|
456
|
-
<div class="pfm-card-actions">
|
|
457
|
-
<button
|
|
458
|
-
class="pfm-btn pfm-btn-wide"
|
|
459
|
-
onclick={openCaptureFromProduction}
|
|
460
|
-
disabled={prodCaptureStatus === 'saving'}
|
|
461
|
-
title="Save the current production state as a new named preset"
|
|
462
|
-
>
|
|
463
|
-
<i
|
|
464
|
-
class="fas"
|
|
465
|
-
class:fa-bookmark={prodCaptureStatus === 'idle'}
|
|
466
|
-
class:fa-spinner={prodCaptureStatus === 'saving'}
|
|
467
|
-
class:fa-check={prodCaptureStatus === 'saved'}
|
|
468
|
-
class:fa-times={prodCaptureStatus === 'error'}
|
|
469
|
-
></i>
|
|
470
|
-
<span>
|
|
471
|
-
{#if prodCaptureStatus === 'idle'}Capture as preset{:else if prodCaptureStatus === 'saving'}Saving{:else if prodCaptureStatus === 'saved'}Saved{:else}Error{/if}
|
|
472
|
-
</span>
|
|
473
|
-
</button>
|
|
474
|
-
</div>
|
|
475
|
-
{/if}
|
|
476
|
-
</div>
|
|
477
|
-
</div>
|
|
478
|
-
|
|
479
|
-
{#if applyStatus === 'applying'}
|
|
480
|
-
<span class="apply-status">Applying preset…</span>
|
|
481
|
-
{/if}
|
|
482
|
-
</div>
|
|
483
|
-
|
|
484
|
-
{#if infoOpen}
|
|
485
|
-
<div
|
|
486
|
-
class="pfm-info-popover"
|
|
487
|
-
class:ready={infoPopoverReady}
|
|
488
|
-
role="dialog"
|
|
489
|
-
aria-label="About presets"
|
|
490
|
-
bind:this={infoPopoverEl}
|
|
491
|
-
>
|
|
492
|
-
<header class="pfm-info-header">
|
|
493
|
-
<span class="pfm-info-title">Presets</span>
|
|
494
|
-
<button
|
|
495
|
-
type="button"
|
|
496
|
-
class="pfm-info-close"
|
|
497
|
-
aria-label="Close"
|
|
498
|
-
onclick={() => (infoOpen = false)}
|
|
499
|
-
>
|
|
500
|
-
<i class="fas fa-xmark"></i>
|
|
501
|
-
</button>
|
|
502
|
-
</header>
|
|
503
|
-
<div class="pfm-info-body">
|
|
504
|
-
<p>
|
|
505
|
-
A <strong>preset</strong> is a manifest naming one theme file and one
|
|
506
|
-
config file per component.
|
|
507
|
-
</p>
|
|
508
|
-
<p>
|
|
509
|
-
The <strong>Editor</strong> row is the preset you're working under.
|
|
510
|
-
<strong>Save</strong> captures the currently active files into its
|
|
511
|
-
manifest.
|
|
512
|
-
</p>
|
|
513
|
-
<p>
|
|
514
|
-
The <strong>Production</strong> row is the preset currently baked into
|
|
515
|
-
<code>tokens.css</code>. <strong>Adopt</strong> sets every component's
|
|
516
|
-
production file from the editor preset.
|
|
517
|
-
</p>
|
|
518
|
-
<p>
|
|
519
|
-
Adopting a single component elsewhere can leave production
|
|
520
|
-
<em>diverged</em> from any preset. <strong>Capture</strong> saves that
|
|
521
|
-
state under a new name.
|
|
522
|
-
</p>
|
|
523
|
-
</div>
|
|
524
|
-
</div>
|
|
525
|
-
{/if}
|
|
526
|
-
|
|
527
|
-
<UIDialog bind:show={showFileList} title="Load Preset" cancelLabel="Close" width="420px">
|
|
528
|
-
<div class="load-list">
|
|
529
|
-
{#each files as file}
|
|
530
|
-
<div class="load-item" class:active={file.fileName === activeFileName}>
|
|
531
|
-
<button class="load-name-btn" onclick={() => handleApply(file)}>
|
|
532
|
-
{file.name}
|
|
533
|
-
</button>
|
|
534
|
-
{#if file.fileName === activeFileName}
|
|
535
|
-
<span class="active-badge">active</span>
|
|
536
|
-
{/if}
|
|
537
|
-
{#if file.fileName !== 'default'}
|
|
538
|
-
<button
|
|
539
|
-
class="file-delete-btn"
|
|
540
|
-
onclick={stopPropagation(() => handleDelete(file))}
|
|
541
|
-
title="Delete {file.name}"
|
|
542
|
-
>
|
|
543
|
-
<i class="fas fa-trash-alt"></i>
|
|
544
|
-
</button>
|
|
545
|
-
{/if}
|
|
546
|
-
</div>
|
|
547
|
-
{/each}
|
|
548
|
-
{#if files.length === 0}
|
|
549
|
-
<div class="load-item empty">No saved presets</div>
|
|
550
|
-
{/if}
|
|
551
|
-
</div>
|
|
552
|
-
</UIDialog>
|
|
553
|
-
|
|
554
|
-
<SaveAsDialog
|
|
555
|
-
bind:show={saveAsDialog}
|
|
556
|
-
{currentDisplayName}
|
|
557
|
-
{files}
|
|
558
|
-
title="Save Preset As"
|
|
559
|
-
placeholder="Preset name…"
|
|
560
|
-
reservedNameMessage='The name "default" is reserved for the initial distribution.'
|
|
561
|
-
onsave={confirmSaveAs}
|
|
562
|
-
/>
|
|
563
|
-
|
|
564
|
-
<SaveAsDialog
|
|
565
|
-
bind:show={captureProdDialog}
|
|
566
|
-
currentDisplayName={prodPresetName}
|
|
567
|
-
{files}
|
|
568
|
-
title="Capture Production as Preset"
|
|
569
|
-
placeholder="Preset name…"
|
|
570
|
-
reservedNameMessage='The name "default" is reserved for the initial distribution.'
|
|
571
|
-
onsave={confirmCaptureFromProduction}
|
|
572
|
-
/>
|
|
573
|
-
|
|
574
|
-
<UnsavedComponentsDialog
|
|
575
|
-
bind:show={unsavedDialog}
|
|
576
|
-
dirtyComponents={dirtyComponentIds}
|
|
577
|
-
onproceed={handleUnsavedProceed}
|
|
578
|
-
/>
|
|
579
|
-
|
|
580
|
-
<style>
|
|
581
|
-
.preset-file-manager {
|
|
582
|
-
--pfm-applied: #5aa85e;
|
|
583
|
-
--pfm-rail-neutral: var(--ui-border-default);
|
|
584
|
-
--pfm-rail-dirty: var(--ui-highlight);
|
|
585
|
-
--pfm-rail-applied: var(--pfm-applied);
|
|
586
|
-
|
|
587
|
-
display: flex;
|
|
588
|
-
flex-direction: column;
|
|
589
|
-
gap: var(--ui-space-8);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
.pfm-header {
|
|
593
|
-
display: flex;
|
|
594
|
-
align-items: center;
|
|
595
|
-
justify-content: space-between;
|
|
596
|
-
gap: var(--ui-space-4);
|
|
597
|
-
padding: 0 var(--ui-space-4);
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
.pfm-header-label {
|
|
601
|
-
font-size: var(--ui-font-size-xs);
|
|
602
|
-
color: var(--ui-text-secondary);
|
|
603
|
-
text-transform: uppercase;
|
|
604
|
-
letter-spacing: 0.05em;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/* Naked icon button — same affordance language as cfm-info-btn. */
|
|
608
|
-
.pfm-info-btn {
|
|
609
|
-
display: inline-flex;
|
|
610
|
-
align-items: center;
|
|
611
|
-
justify-content: center;
|
|
612
|
-
width: 22px;
|
|
613
|
-
height: 22px;
|
|
614
|
-
padding: 0;
|
|
615
|
-
background: transparent;
|
|
616
|
-
border: 0;
|
|
617
|
-
color: var(--ui-text-tertiary);
|
|
618
|
-
font-size: 0.95rem;
|
|
619
|
-
line-height: 1;
|
|
620
|
-
cursor: pointer;
|
|
621
|
-
transition: color var(--ui-transition-fast);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
.pfm-info-btn:hover,
|
|
625
|
-
.pfm-info-btn[aria-expanded='true'] {
|
|
626
|
-
color: var(--ui-text-primary);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/* ── two-card pipeline (Editor → Production) ─────────────────────────────
|
|
630
|
-
Mirrors the cfm-rows pattern in ComponentFileManager so the preset reads
|
|
631
|
-
as the same kind of artifact as themes and components, just one level up. */
|
|
632
|
-
.pfm-cards {
|
|
633
|
-
display: flex;
|
|
634
|
-
flex-direction: column;
|
|
635
|
-
gap: var(--ui-space-6);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
.pfm-card {
|
|
639
|
-
position: relative;
|
|
640
|
-
display: flex;
|
|
641
|
-
flex-direction: column;
|
|
642
|
-
gap: var(--ui-space-6);
|
|
643
|
-
padding: var(--ui-space-8) var(--ui-space-10) var(--ui-space-10) var(--ui-space-16);
|
|
644
|
-
background: var(--ui-surface-lower);
|
|
645
|
-
border: 1px solid var(--ui-border-subtle);
|
|
646
|
-
border-radius: var(--ui-radius-md);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
.pfm-rail {
|
|
650
|
-
position: absolute;
|
|
651
|
-
left: 0;
|
|
652
|
-
top: 0;
|
|
653
|
-
bottom: 0;
|
|
654
|
-
width: 3px;
|
|
655
|
-
border-radius: var(--ui-radius-md) 0 0 var(--ui-radius-md);
|
|
656
|
-
background: var(--pfm-rail-neutral);
|
|
657
|
-
transition: background var(--ui-transition-base);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
.pfm-card-editor.dirty .pfm-rail { background: var(--pfm-rail-dirty); }
|
|
661
|
-
.pfm-card-editor.applied .pfm-rail { background: var(--pfm-rail-applied); }
|
|
662
|
-
.pfm-card-production.in-sync .pfm-rail { background: var(--pfm-rail-applied); }
|
|
663
|
-
.pfm-card-production.diverged .pfm-rail { background: var(--ui-highlight); }
|
|
664
|
-
|
|
665
|
-
.pfm-card-head {
|
|
666
|
-
display: flex;
|
|
667
|
-
align-items: baseline;
|
|
668
|
-
justify-content: space-between;
|
|
669
|
-
gap: var(--ui-space-8);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
.pfm-card-label {
|
|
673
|
-
font-size: var(--ui-font-size-xs);
|
|
674
|
-
font-weight: var(--ui-font-weight-semibold);
|
|
675
|
-
text-transform: uppercase;
|
|
676
|
-
letter-spacing: 0.08em;
|
|
677
|
-
color: var(--ui-text-secondary);
|
|
678
|
-
line-height: 1.1;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
.pfm-card-status {
|
|
682
|
-
display: inline-flex;
|
|
683
|
-
align-items: center;
|
|
684
|
-
gap: var(--ui-space-4);
|
|
685
|
-
font-size: 0.7rem;
|
|
686
|
-
letter-spacing: 0.02em;
|
|
687
|
-
color: var(--ui-text-muted);
|
|
688
|
-
line-height: 1;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
.pfm-status-dot {
|
|
692
|
-
width: 5px;
|
|
693
|
-
height: 5px;
|
|
694
|
-
border-radius: 50%;
|
|
695
|
-
background: currentColor;
|
|
696
|
-
opacity: 0.7;
|
|
697
|
-
flex-shrink: 0;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
.pfm-card-status.dirty,
|
|
701
|
-
.pfm-card-status.diverged {
|
|
702
|
-
color: var(--ui-highlight);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
.pfm-card-status.dirty .pfm-status-dot,
|
|
706
|
-
.pfm-card-status.diverged .pfm-status-dot {
|
|
707
|
-
opacity: 1;
|
|
708
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-highlight) 22%, transparent);
|
|
709
|
-
animation: pfm-pulse 1.6s ease-in-out infinite;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
.pfm-card-status.applied {
|
|
713
|
-
color: var(--pfm-applied);
|
|
714
|
-
}
|
|
715
|
-
.pfm-card-status.applied .pfm-status-dot {
|
|
716
|
-
opacity: 1;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
/* filename pill — matches the cfm-pill vocabulary so the editor side and
|
|
720
|
-
the preset side use one visual idiom for "this is a named file". */
|
|
721
|
-
.pfm-pill {
|
|
722
|
-
display: flex;
|
|
723
|
-
align-items: center;
|
|
724
|
-
padding: var(--ui-space-6) var(--ui-space-10);
|
|
725
|
-
background: var(--ui-surface-lowest);
|
|
726
|
-
border: 1px solid var(--ui-border-subtle);
|
|
727
|
-
border-radius: var(--ui-radius-md);
|
|
728
|
-
transition: border-color var(--ui-transition-fast), box-shadow var(--ui-transition-fast);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
.pfm-pill.dirty {
|
|
732
|
-
border-color: color-mix(in srgb, var(--ui-highlight) 60%, var(--ui-border-subtle));
|
|
733
|
-
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ui-highlight) 35%, transparent);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
.pfm-pill.applied {
|
|
737
|
-
border-color: color-mix(in srgb, var(--pfm-applied) 50%, var(--ui-border-subtle));
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
.pfm-pill.diverged {
|
|
741
|
-
border-color: color-mix(in srgb, var(--ui-highlight) 50%, var(--ui-border-subtle));
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
.pfm-pill-name {
|
|
745
|
-
flex: 1;
|
|
746
|
-
min-width: 0;
|
|
747
|
-
font-size: var(--ui-font-size-md);
|
|
748
|
-
font-weight: var(--ui-font-weight-semibold);
|
|
749
|
-
color: var(--ui-text-primary);
|
|
750
|
-
white-space: nowrap;
|
|
751
|
-
overflow: hidden;
|
|
752
|
-
text-overflow: ellipsis;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
.pfm-stale-note,
|
|
756
|
-
.pfm-diverged-note {
|
|
757
|
-
font-size: 0.7rem;
|
|
758
|
-
letter-spacing: 0.02em;
|
|
759
|
-
color: var(--ui-highlight);
|
|
760
|
-
line-height: 1.2;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
.pfm-card-actions {
|
|
764
|
-
display: flex;
|
|
765
|
-
gap: var(--ui-space-4);
|
|
766
|
-
flex-wrap: wrap;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/* Stack variant — Save / Save As / Load as left-aligned rows. */
|
|
770
|
-
.pfm-card-actions-stack {
|
|
771
|
-
flex-direction: column;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
.pfm-btn-row {
|
|
775
|
-
width: 100%;
|
|
776
|
-
justify-content: flex-start;
|
|
777
|
-
gap: var(--ui-space-8);
|
|
778
|
-
flex: 0 0 auto;
|
|
779
|
-
text-align: left;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
.pfm-btn-row i {
|
|
783
|
-
width: 1rem;
|
|
784
|
-
text-align: center;
|
|
785
|
-
flex: 0 0 auto;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
.pfm-btn-row span {
|
|
789
|
-
flex: 1 1 auto;
|
|
790
|
-
text-align: left;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
/* Bridge button — lives between the Editor and Production cards so the
|
|
794
|
-
promote action visually IS the arrow that connects them. Matches the
|
|
795
|
-
.cfm-btn.primary geometry (--ui-radius-md, --ui-space-6/12 padding) so
|
|
796
|
-
it reads as the same affordance as the per-component Adopt. */
|
|
797
|
-
.pfm-adopt-btn {
|
|
798
|
-
align-self: stretch;
|
|
799
|
-
width: 100%;
|
|
800
|
-
display: flex;
|
|
801
|
-
align-items: center;
|
|
802
|
-
justify-content: center;
|
|
803
|
-
gap: var(--ui-space-6);
|
|
804
|
-
margin: calc(var(--ui-space-2) * -1) 0;
|
|
805
|
-
padding: var(--ui-space-6) var(--ui-space-12);
|
|
806
|
-
background: color-mix(in srgb, var(--pfm-applied) 18%, var(--ui-surface-high));
|
|
807
|
-
border: 1px solid color-mix(in srgb, var(--pfm-applied) 45%, var(--ui-border-medium));
|
|
808
|
-
border-radius: var(--ui-radius-md);
|
|
809
|
-
color: var(--ui-text-primary);
|
|
810
|
-
font-size: var(--ui-font-size-md);
|
|
811
|
-
font-weight: var(--ui-font-weight-medium);
|
|
812
|
-
cursor: pointer;
|
|
813
|
-
transition: all var(--ui-transition-fast);
|
|
814
|
-
white-space: nowrap;
|
|
815
|
-
position: relative;
|
|
816
|
-
z-index: 1;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
.pfm-adopt-btn i {
|
|
820
|
-
width: 1rem;
|
|
821
|
-
text-align: center;
|
|
822
|
-
font-size: 0.85em;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
.pfm-adopt-btn:hover:not(:disabled) {
|
|
826
|
-
background: color-mix(in srgb, var(--pfm-applied) 30%, var(--ui-surface-higher));
|
|
827
|
-
border-color: color-mix(in srgb, var(--pfm-applied) 70%, var(--ui-border-strong));
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
.pfm-adopt-btn:disabled {
|
|
831
|
-
cursor: not-allowed;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
.pfm-adopt-btn.in-sync {
|
|
835
|
-
background: transparent;
|
|
836
|
-
border-color: var(--ui-border-subtle);
|
|
837
|
-
color: var(--ui-text-muted);
|
|
838
|
-
opacity: 0.7;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
.pfm-adopt-btn.saving i { animation: spin 1s linear infinite; }
|
|
842
|
-
.pfm-adopt-btn.saved {
|
|
843
|
-
background: color-mix(in srgb, var(--pfm-applied) 30%, var(--ui-surface-high));
|
|
844
|
-
color: var(--pfm-applied);
|
|
845
|
-
}
|
|
846
|
-
.pfm-adopt-btn.error { color: var(--ui-text-muted); }
|
|
847
|
-
|
|
848
|
-
.pfm-btn {
|
|
849
|
-
display: inline-flex;
|
|
850
|
-
align-items: center;
|
|
851
|
-
justify-content: center;
|
|
852
|
-
gap: var(--ui-space-4);
|
|
853
|
-
padding: var(--ui-space-6) var(--ui-space-10);
|
|
854
|
-
background: var(--ui-surface);
|
|
855
|
-
border: 1px solid var(--ui-border-subtle);
|
|
856
|
-
border-radius: var(--ui-radius-md);
|
|
857
|
-
color: var(--ui-text-secondary);
|
|
858
|
-
font-size: var(--ui-font-size-md);
|
|
859
|
-
font-weight: var(--ui-font-weight-medium);
|
|
860
|
-
cursor: pointer;
|
|
861
|
-
transition: all var(--ui-transition-fast);
|
|
862
|
-
white-space: nowrap;
|
|
863
|
-
flex: 1 1 0;
|
|
864
|
-
min-width: 0;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
.pfm-btn i {
|
|
868
|
-
width: 1rem;
|
|
869
|
-
text-align: center;
|
|
870
|
-
font-size: 0.85em;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
.pfm-btn:hover:not(:disabled) {
|
|
874
|
-
background: var(--ui-surface-high);
|
|
875
|
-
color: var(--ui-text-primary);
|
|
876
|
-
border-color: var(--ui-border-default);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
.pfm-btn:disabled {
|
|
880
|
-
opacity: 0.45;
|
|
881
|
-
cursor: not-allowed;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
.pfm-btn.active {
|
|
885
|
-
background: var(--ui-surface);
|
|
886
|
-
border-color: var(--ui-border-default);
|
|
887
|
-
color: var(--ui-text-primary);
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
.pfm-btn-wide {
|
|
891
|
-
flex: 1 1 100%;
|
|
892
|
-
width: 100%;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
.save-btn {
|
|
896
|
-
background: var(--ui-surface-high);
|
|
897
|
-
border-color: var(--ui-border-medium);
|
|
898
|
-
color: var(--ui-text-primary);
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
.save-btn:hover:not(:disabled) {
|
|
902
|
-
background: var(--ui-surface-higher);
|
|
903
|
-
border-color: var(--ui-border-strong);
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
.save-btn.saving i { animation: spin 1s linear infinite; }
|
|
907
|
-
.save-btn.saved {
|
|
908
|
-
background: var(--ui-surface-highest);
|
|
909
|
-
color: var(--ui-text-success);
|
|
910
|
-
}
|
|
911
|
-
.save-btn.error {
|
|
912
|
-
background: var(--ui-surface-high);
|
|
913
|
-
color: var(--ui-text-muted);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
.load-list {
|
|
918
|
-
display: flex;
|
|
919
|
-
flex-direction: column;
|
|
920
|
-
max-height: 60vh;
|
|
921
|
-
overflow-y: auto;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
.load-item {
|
|
925
|
-
display: flex;
|
|
926
|
-
align-items: center;
|
|
927
|
-
gap: 6px;
|
|
928
|
-
padding: 4px 6px;
|
|
929
|
-
border-bottom: 1px solid #2a2a2a;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
.load-item:last-child {
|
|
933
|
-
border-bottom: none;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
.load-item.empty {
|
|
937
|
-
padding: 16px;
|
|
938
|
-
color: #888;
|
|
939
|
-
font-size: 14px;
|
|
940
|
-
text-align: center;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
.load-name-btn {
|
|
944
|
-
flex: 1;
|
|
945
|
-
min-width: 0;
|
|
946
|
-
overflow: hidden;
|
|
947
|
-
text-overflow: ellipsis;
|
|
948
|
-
white-space: nowrap;
|
|
949
|
-
padding: 6px 4px;
|
|
950
|
-
background: none;
|
|
951
|
-
border: none;
|
|
952
|
-
color: #aaa;
|
|
953
|
-
font-size: 14px;
|
|
954
|
-
cursor: pointer;
|
|
955
|
-
text-align: left;
|
|
956
|
-
border-radius: 3px;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
.load-name-btn:hover {
|
|
960
|
-
color: #e0e0e0;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
.load-item.active .load-name-btn {
|
|
964
|
-
color: #e0e0e0;
|
|
965
|
-
font-weight: 600;
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
.active-badge {
|
|
969
|
-
flex-shrink: 0;
|
|
970
|
-
font-size: 12px;
|
|
971
|
-
padding: 1px 6px;
|
|
972
|
-
border-radius: 3px;
|
|
973
|
-
background: #333;
|
|
974
|
-
color: #ccc;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
.file-delete-btn {
|
|
978
|
-
flex-shrink: 0;
|
|
979
|
-
display: flex;
|
|
980
|
-
align-items: center;
|
|
981
|
-
justify-content: center;
|
|
982
|
-
width: 24px;
|
|
983
|
-
height: 24px;
|
|
984
|
-
padding: 0;
|
|
985
|
-
background: none;
|
|
986
|
-
border: none;
|
|
987
|
-
color: #555;
|
|
988
|
-
font-size: 12px;
|
|
989
|
-
cursor: pointer;
|
|
990
|
-
opacity: 0;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
.load-item:hover .file-delete-btn {
|
|
994
|
-
opacity: 1;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
.file-delete-btn:hover {
|
|
998
|
-
color: #ccc;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
.apply-status {
|
|
1002
|
-
font-size: var(--ui-font-size-xs);
|
|
1003
|
-
color: var(--ui-text-secondary);
|
|
1004
|
-
padding: 0 var(--ui-space-4);
|
|
1005
|
-
letter-spacing: 0.02em;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
/* Info popover — fixed positioning escapes the sidebar's overflow and any
|
|
1009
|
-
parent stacking context. JS in this file anchors it to the right of the
|
|
1010
|
-
info button (the sidebar is on the left, so there's room to flow into
|
|
1011
|
-
the main content area without obscuring the button). */
|
|
1012
|
-
.pfm-info-popover {
|
|
1013
|
-
position: fixed;
|
|
1014
|
-
top: 0;
|
|
1015
|
-
left: 0;
|
|
1016
|
-
width: 22rem;
|
|
1017
|
-
max-width: calc(100vw - var(--ui-space-24));
|
|
1018
|
-
padding: 0;
|
|
1019
|
-
background: var(--ui-surface-higher);
|
|
1020
|
-
border: 1px solid var(--ui-border-medium);
|
|
1021
|
-
border-radius: var(--ui-radius-lg);
|
|
1022
|
-
box-shadow: var(--ui-shadow-lg);
|
|
1023
|
-
z-index: 1000;
|
|
1024
|
-
color: var(--ui-text-secondary);
|
|
1025
|
-
font-family: var(--ui-font-family, system-ui, sans-serif);
|
|
1026
|
-
overflow: hidden;
|
|
1027
|
-
visibility: hidden;
|
|
1028
|
-
animation: pfm-info-in 140ms ease-out;
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
.pfm-info-popover.ready {
|
|
1032
|
-
visibility: visible;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
.pfm-info-header {
|
|
1036
|
-
display: flex;
|
|
1037
|
-
align-items: center;
|
|
1038
|
-
justify-content: space-between;
|
|
1039
|
-
gap: var(--ui-space-8);
|
|
1040
|
-
padding: var(--ui-space-10) var(--ui-space-12) var(--ui-space-10) var(--ui-space-16);
|
|
1041
|
-
border-bottom: 1px solid var(--ui-border-subtle);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
.pfm-info-title {
|
|
1045
|
-
color: var(--ui-text-primary);
|
|
1046
|
-
font-size: var(--ui-font-size-sm);
|
|
1047
|
-
font-weight: var(--ui-font-weight-semibold);
|
|
1048
|
-
letter-spacing: -0.01em;
|
|
1049
|
-
line-height: 1.2;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
.pfm-info-close {
|
|
1053
|
-
display: inline-flex;
|
|
1054
|
-
align-items: center;
|
|
1055
|
-
justify-content: center;
|
|
1056
|
-
width: var(--ui-space-24);
|
|
1057
|
-
height: var(--ui-space-24);
|
|
1058
|
-
padding: 0;
|
|
1059
|
-
background: transparent;
|
|
1060
|
-
border: 0;
|
|
1061
|
-
border-radius: var(--ui-radius-sm);
|
|
1062
|
-
color: var(--ui-text-tertiary);
|
|
1063
|
-
font-size: var(--ui-font-size-xs);
|
|
1064
|
-
line-height: 1;
|
|
1065
|
-
cursor: pointer;
|
|
1066
|
-
transition: color var(--ui-transition-fast), background var(--ui-transition-fast);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
.pfm-info-close:hover {
|
|
1070
|
-
color: var(--ui-text-primary);
|
|
1071
|
-
background: var(--ui-hover);
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
.pfm-info-body {
|
|
1075
|
-
padding: var(--ui-space-16);
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
.pfm-info-popover p {
|
|
1079
|
-
margin: 0 0 var(--ui-space-12) 0;
|
|
1080
|
-
font-size: var(--ui-font-size-xs);
|
|
1081
|
-
line-height: 1.55;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
.pfm-info-popover p:last-child {
|
|
1085
|
-
margin-bottom: 0;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
.pfm-info-popover strong {
|
|
1089
|
-
color: var(--ui-text-primary);
|
|
1090
|
-
font-weight: var(--ui-font-weight-semibold);
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
.pfm-info-popover em {
|
|
1094
|
-
font-style: italic;
|
|
1095
|
-
color: var(--ui-text-primary);
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
@keyframes pfm-pulse {
|
|
1099
|
-
0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-highlight) 22%, transparent); }
|
|
1100
|
-
50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--ui-highlight) 10%, transparent); }
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
@keyframes pfm-info-in {
|
|
1104
|
-
from { opacity: 0; transform: translateY(-3px); }
|
|
1105
|
-
to { opacity: 1; transform: translateY(0); }
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
@keyframes spin {
|
|
1109
|
-
from {
|
|
1110
|
-
transform: rotate(0deg);
|
|
1111
|
-
}
|
|
1112
|
-
to {
|
|
1113
|
-
transform: rotate(360deg);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
</style>
|