@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
package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte
RENAMED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import { onMount, onDestroy } from 'svelte';
|
|
7
|
+
import UIInfoPopover from '../../ui/UIInfoPopover.svelte';
|
|
7
8
|
import { get } from 'svelte/store';
|
|
8
|
-
import type { ComponentConfig, ComponentConfigMeta } from '../../
|
|
9
|
+
import type { ComponentConfig, ComponentConfigMeta } from '../../core/themes/themeTypes';
|
|
9
10
|
import { componentSourceFile } from './componentSources';
|
|
10
11
|
import {
|
|
11
12
|
loadComponentConfig,
|
|
@@ -14,19 +15,25 @@
|
|
|
14
15
|
setActiveComponentFile,
|
|
15
16
|
setComponentProductionFile,
|
|
16
17
|
type ComponentProductionInfo,
|
|
17
|
-
} from '../../
|
|
18
|
+
} from '../../core/components/componentConfigService';
|
|
18
19
|
import {
|
|
19
20
|
editorState,
|
|
20
21
|
componentDirty,
|
|
21
22
|
loadComponentActive,
|
|
22
23
|
markComponentSaved,
|
|
23
|
-
} from '../../
|
|
24
|
-
import { bumpProductionRevision } from '../../
|
|
25
|
-
import {
|
|
26
|
-
import type {
|
|
27
|
-
import {
|
|
24
|
+
} from '../../core/store/editorStore';
|
|
25
|
+
import { bumpProductionRevision } from '../../core/productionPulse';
|
|
26
|
+
import { listManifests, saveAsManifest } from '../../core/manifests/manifestService';
|
|
27
|
+
import type { ManifestMeta } from '../../core/themes/themeTypes';
|
|
28
|
+
import { CURRENT_COMPONENT_SCHEMA_VERSION } from '../../core/themes/migrations';
|
|
29
|
+
import type { CssVarRef } from '../../core/store/editorTypes';
|
|
30
|
+
import { safeFetch } from '../../core/storage/storage';
|
|
31
|
+
import { flashStatus } from '../../core/flashStatus';
|
|
28
32
|
import ComponentFileMenu from './ComponentFileMenu.svelte';
|
|
29
33
|
import SaveAsDialog from './SaveAsDialog.svelte';
|
|
34
|
+
import FilePill from '../../ui/FilePill.svelte';
|
|
35
|
+
import UIPillButton from '../../ui/UIPillButton.svelte';
|
|
36
|
+
import UISquareButton from '../../ui/UISquareButton.svelte';
|
|
30
37
|
|
|
31
38
|
|
|
32
39
|
|
|
@@ -50,74 +57,29 @@
|
|
|
50
57
|
|
|
51
58
|
type SaveStatus = 'idle' | 'saving' | 'saved' | 'error';
|
|
52
59
|
let saveStatus: SaveStatus = 'idle';
|
|
60
|
+
const setSaveStatus = (s: SaveStatus) => (saveStatus = s);
|
|
53
61
|
|
|
54
|
-
/** Show a transient saveStatus (saved/error) and revert to idle after 2s.
|
|
55
|
-
Centralises the timing so all flash sites stay in sync. */
|
|
56
|
-
function flashStatus(state: Exclude<SaveStatus, 'idle'>) {
|
|
57
|
-
saveStatus = state;
|
|
58
|
-
setTimeout(() => (saveStatus = 'idle'), 2000);
|
|
59
|
-
}
|
|
60
62
|
let files: ComponentConfigMeta[] = $state([]);
|
|
61
63
|
let activeFileName = $state('default');
|
|
62
64
|
let currentDisplayName = $state('Default');
|
|
63
65
|
let saveAsDialog = $state(false);
|
|
66
|
+
// Title + description re-set per trigger so the auto-prompts (Save on
|
|
67
|
+
// default, Adopt on default+dirty) explain why they appeared, while the
|
|
68
|
+
// manual Save As button stays terse.
|
|
69
|
+
let saveAsTitle = $state('Save Component As');
|
|
70
|
+
let saveAsDescription = $state('');
|
|
64
71
|
|
|
65
72
|
let productionInfo = $state<ComponentProductionInfo | null>(null);
|
|
66
73
|
type ProductionStatus = 'idle' | 'updating' | 'done' | 'error';
|
|
67
74
|
let productionUpdateStatus: ProductionStatus = $state('idle');
|
|
68
75
|
let adoptFeedback = $state('');
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
let
|
|
72
|
-
let
|
|
73
|
-
let
|
|
74
|
-
|
|
75
|
-
/** Anchor the fixed-position popover centered below the info button.
|
|
76
|
-
Uses position: fixed so it escapes the sticky header's stacking
|
|
77
|
-
context (which was letting the side panels paint over it). */
|
|
78
|
-
function positionInfoPopover(): void {
|
|
79
|
-
const btn = infoBtnEl;
|
|
80
|
-
const pop = infoPopoverEl;
|
|
81
|
-
if (!btn || !pop) return;
|
|
82
|
-
const br = btn.getBoundingClientRect();
|
|
83
|
-
const pr = pop.getBoundingClientRect();
|
|
84
|
-
const margin = 8;
|
|
85
|
-
let left = br.left + br.width / 2 - pr.width / 2;
|
|
86
|
-
const vw = window.innerWidth;
|
|
87
|
-
if (left < margin) left = margin;
|
|
88
|
-
if (left + pr.width > vw - margin) left = vw - margin - pr.width;
|
|
89
|
-
pop.style.left = `${left}px`;
|
|
90
|
-
pop.style.top = `${br.bottom + margin}px`;
|
|
91
|
-
infoPopoverReady = true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
$effect(() => {
|
|
95
|
-
if (!infoOpen) {
|
|
96
|
-
infoPopoverReady = false;
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
// Two rAFs: first so Svelte mounts the popover and the bind: ref is set,
|
|
100
|
-
// second so its rendered width is measurable before we anchor it.
|
|
101
|
-
let raf1 = requestAnimationFrame(() => {
|
|
102
|
-
raf1 = requestAnimationFrame(positionInfoPopover);
|
|
103
|
-
});
|
|
104
|
-
window.addEventListener('scroll', positionInfoPopover, true);
|
|
105
|
-
window.addEventListener('resize', positionInfoPopover);
|
|
106
|
-
return () => {
|
|
107
|
-
cancelAnimationFrame(raf1);
|
|
108
|
-
window.removeEventListener('scroll', positionInfoPopover, true);
|
|
109
|
-
window.removeEventListener('resize', positionInfoPopover);
|
|
110
|
-
};
|
|
111
|
-
});
|
|
77
|
+
// Manifest SaveAs prompt for the "Adopt while default manifest is active" case.
|
|
78
|
+
let manifestSaveAsDialog = $state(false);
|
|
79
|
+
let manifests: ManifestMeta[] = $state([]);
|
|
80
|
+
let retryAdoptAfterManifestSave = false;
|
|
112
81
|
|
|
113
|
-
|
|
114
|
-
function flashProductionStatus(state: Exclude<ProductionStatus, 'idle'>) {
|
|
115
|
-
productionUpdateStatus = state;
|
|
116
|
-
setTimeout(() => {
|
|
117
|
-
productionUpdateStatus = 'idle';
|
|
118
|
-
adoptFeedback = '';
|
|
119
|
-
}, 2000);
|
|
120
|
-
}
|
|
82
|
+
const setProductionStatus = (s: ProductionStatus) => (productionUpdateStatus = s);
|
|
121
83
|
|
|
122
84
|
let compDirty = $derived($componentDirty[component] ?? false);
|
|
123
85
|
let isApplied = $derived(!!productionInfo && productionInfo.fileName === activeFileName && !compDirty);
|
|
@@ -150,28 +112,16 @@
|
|
|
150
112
|
await refreshFiles();
|
|
151
113
|
await refreshProduction();
|
|
152
114
|
window.addEventListener('keydown', handleKeydown);
|
|
153
|
-
document.addEventListener('mousedown', handleDocumentMousedown, true);
|
|
154
115
|
});
|
|
155
116
|
|
|
156
117
|
onDestroy(() => {
|
|
157
118
|
window.removeEventListener('keydown', handleKeydown);
|
|
158
|
-
document.removeEventListener('mousedown', handleDocumentMousedown, true);
|
|
159
119
|
});
|
|
160
120
|
|
|
161
121
|
function handleKeydown(e: KeyboardEvent) {
|
|
162
122
|
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
|
|
163
123
|
e.preventDefault();
|
|
164
124
|
handleSave();
|
|
165
|
-
} else if (e.key === 'Escape' && infoOpen) {
|
|
166
|
-
infoOpen = false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function handleDocumentMousedown(e: MouseEvent) {
|
|
171
|
-
if (!infoOpen) return;
|
|
172
|
-
const target = e.target as Element | null;
|
|
173
|
-
if (target && !target.closest('.cfm-info-btn, .cfm-info-popover')) {
|
|
174
|
-
infoOpen = false;
|
|
175
125
|
}
|
|
176
126
|
}
|
|
177
127
|
|
|
@@ -211,21 +161,24 @@
|
|
|
211
161
|
|
|
212
162
|
async function handleSave() {
|
|
213
163
|
if (activeFileName === 'default') {
|
|
214
|
-
// Default is regenerated from source
|
|
215
|
-
|
|
164
|
+
// Default is regenerated from source, so the first Save quietly opens
|
|
165
|
+
// Save As — same dialog as the Save As button, no scolding description.
|
|
166
|
+
openSaveAs();
|
|
216
167
|
return;
|
|
217
168
|
}
|
|
218
169
|
saveStatus = 'saving';
|
|
219
170
|
try {
|
|
220
171
|
await persist(activeFileName, currentDisplayName);
|
|
221
|
-
flashStatus('saved');
|
|
172
|
+
flashStatus(setSaveStatus, 'saved');
|
|
222
173
|
await refreshFiles();
|
|
223
174
|
} catch {
|
|
224
|
-
flashStatus('error');
|
|
175
|
+
flashStatus(setSaveStatus, 'error');
|
|
225
176
|
}
|
|
226
177
|
}
|
|
227
178
|
|
|
228
179
|
function openSaveAs() {
|
|
180
|
+
saveAsTitle = 'Save Component As';
|
|
181
|
+
saveAsDescription = '';
|
|
229
182
|
saveAsDialog = true;
|
|
230
183
|
}
|
|
231
184
|
|
|
@@ -234,10 +187,10 @@
|
|
|
234
187
|
saveStatus = 'saving';
|
|
235
188
|
try {
|
|
236
189
|
await persist(fileName, displayName);
|
|
237
|
-
flashStatus('saved');
|
|
190
|
+
flashStatus(setSaveStatus, 'saved');
|
|
238
191
|
await refreshFiles();
|
|
239
192
|
} catch {
|
|
240
|
-
flashStatus('error');
|
|
193
|
+
flashStatus(setSaveStatus, 'error');
|
|
241
194
|
}
|
|
242
195
|
}
|
|
243
196
|
|
|
@@ -262,11 +215,16 @@
|
|
|
262
215
|
// Multi-step service flow (delete + reload-default-on-active-removal).
|
|
263
216
|
// Silent by design — see handleLoad.
|
|
264
217
|
try {
|
|
218
|
+
// Capture before refreshFiles() reads the server's reverted active back
|
|
219
|
+
// into local state — otherwise the "was this the active file?" check
|
|
220
|
+
// below sees the post-revert value and skips the reload.
|
|
221
|
+
const wasActive = file.fileName === activeFileName;
|
|
265
222
|
await deleteComponentConfig(component, file.fileName);
|
|
266
223
|
await refreshFiles();
|
|
267
224
|
await refreshProduction();
|
|
268
|
-
if (
|
|
269
|
-
// Server reverts active to default; reload default aliases into the store
|
|
225
|
+
if (wasActive) {
|
|
226
|
+
// Server reverts active to default; reload default aliases into the store
|
|
227
|
+
// so the deleted file's CSS vars are replaced by default's.
|
|
270
228
|
const defaultCfg = await loadComponentConfig(component, 'default');
|
|
271
229
|
loadComponentActive(component, 'default', defaultCfg.aliases, defaultCfg.config, defaultCfg.schemaVersion ?? 0);
|
|
272
230
|
activeFileName = 'default';
|
|
@@ -283,6 +241,9 @@
|
|
|
283
241
|
// regenerated from source and can't be overwritten, so route to Save As
|
|
284
242
|
// and bail; the user can re-trigger Adopt after the new file is saved.
|
|
285
243
|
if (compDirty && activeFileName === 'default') {
|
|
244
|
+
saveAsTitle = 'Save Component As';
|
|
245
|
+
saveAsDescription =
|
|
246
|
+
'Adopting pushes this component to production. The default config is read-only, so save your edits to a new file first.';
|
|
286
247
|
saveAsDialog = true;
|
|
287
248
|
return;
|
|
288
249
|
}
|
|
@@ -300,10 +261,38 @@
|
|
|
300
261
|
adoptFeedback = wasDirty
|
|
301
262
|
? `Saved "${adoptingName}" and adopted`
|
|
302
263
|
: `Adopted "${adoptingName}"`;
|
|
303
|
-
|
|
304
|
-
} catch {
|
|
264
|
+
flashStatus(setProductionStatus, 'done', { onIdle: () => (adoptFeedback = '') });
|
|
265
|
+
} catch (err) {
|
|
266
|
+
const e = err as Error & { code?: string };
|
|
267
|
+
if (e.code === 'ACTIVE_IS_PROTECTED') {
|
|
268
|
+
adoptFeedback = '';
|
|
269
|
+
productionUpdateStatus = 'idle';
|
|
270
|
+
retryAdoptAfterManifestSave = true;
|
|
271
|
+
try {
|
|
272
|
+
manifests = await listManifests();
|
|
273
|
+
} catch {
|
|
274
|
+
manifests = [];
|
|
275
|
+
}
|
|
276
|
+
manifestSaveAsDialog = true;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
305
279
|
adoptFeedback = '';
|
|
306
|
-
|
|
280
|
+
flashStatus(setProductionStatus, 'error', { onIdle: () => (adoptFeedback = '') });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function onManifestSaveAs(detail: { displayName: string; fileName: string }) {
|
|
285
|
+
manifestSaveAsDialog = false;
|
|
286
|
+
try {
|
|
287
|
+
await saveAsManifest(detail.fileName, detail.displayName);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
window.alert(`Failed to create manifest: ${(err as Error).message}`);
|
|
290
|
+
retryAdoptAfterManifestSave = false;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (retryAdoptAfterManifestSave) {
|
|
294
|
+
retryAdoptAfterManifestSave = false;
|
|
295
|
+
await handleUpdateProduction();
|
|
307
296
|
}
|
|
308
297
|
}
|
|
309
298
|
|
|
@@ -324,14 +313,11 @@
|
|
|
324
313
|
<h2 class="cfm-title">{title}</h2>
|
|
325
314
|
{/if}
|
|
326
315
|
{#if sourceFile && projectRoot}
|
|
327
|
-
<
|
|
328
|
-
|
|
316
|
+
<UIPillButton
|
|
317
|
+
icon="fa-code"
|
|
329
318
|
href="vscode://file/{projectRoot}/{sourceFile}"
|
|
330
319
|
title="Open {sourceFile} in VS Code"
|
|
331
|
-
>
|
|
332
|
-
<i class="fas fa-code"></i>
|
|
333
|
-
<span>Show component source</span>
|
|
334
|
-
</a>
|
|
320
|
+
>Show component source</UIPillButton>
|
|
335
321
|
{/if}
|
|
336
322
|
</div>
|
|
337
323
|
|
|
@@ -345,18 +331,19 @@
|
|
|
345
331
|
<span>{compDirty ? 'unsaved' : isApplied ? 'live' : 'saved'}</span>
|
|
346
332
|
</span>
|
|
347
333
|
</div>
|
|
348
|
-
<
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
334
|
+
<FilePill
|
|
335
|
+
name={currentDisplayName}
|
|
336
|
+
isProtected={activeFileName === 'default'}
|
|
337
|
+
dirty={compDirty}
|
|
338
|
+
applied={isApplied}
|
|
339
|
+
protectedTitle="Protected system config"
|
|
352
340
|
title={compDirty
|
|
353
341
|
? 'Unsaved changes'
|
|
354
342
|
: isApplied
|
|
355
343
|
? 'Active config is applied to production'
|
|
356
344
|
: ''}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
</span>
|
|
345
|
+
style="flex: 0 0 11.25rem; width: 11.25rem;"
|
|
346
|
+
/>
|
|
360
347
|
<div class="cfm-actions">
|
|
361
348
|
<ComponentFileMenu
|
|
362
349
|
{component}
|
|
@@ -369,15 +356,12 @@
|
|
|
369
356
|
ondelete={handleDelete}
|
|
370
357
|
/>
|
|
371
358
|
{#if resetVariables}
|
|
372
|
-
<
|
|
373
|
-
|
|
359
|
+
<UISquareButton
|
|
360
|
+
icon="fa-rotate-left"
|
|
374
361
|
onclick={handleReset}
|
|
375
362
|
disabled={!resetDirty}
|
|
376
363
|
title="Revert unsaved changes to {currentDisplayName}"
|
|
377
|
-
>
|
|
378
|
-
<i class="fas fa-rotate-left"></i>
|
|
379
|
-
<span>Reset</span>
|
|
380
|
-
</button>
|
|
364
|
+
>Reset</UISquareButton>
|
|
381
365
|
{/if}
|
|
382
366
|
</div>
|
|
383
367
|
</div>
|
|
@@ -395,15 +379,22 @@
|
|
|
395
379
|
<span>live</span>
|
|
396
380
|
</span>
|
|
397
381
|
</div>
|
|
398
|
-
<
|
|
399
|
-
|
|
400
|
-
|
|
382
|
+
<FilePill
|
|
383
|
+
name={productionInfo?.name ?? '—'}
|
|
384
|
+
isProtected={productionInfo?.fileName === 'default'}
|
|
385
|
+
protectedTitle="Protected system config"
|
|
386
|
+
style="flex: 0 0 11.25rem; width: 11.25rem;"
|
|
387
|
+
/>
|
|
401
388
|
<div class="cfm-actions">
|
|
402
|
-
<
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
389
|
+
<UISquareButton
|
|
390
|
+
variant="success"
|
|
391
|
+
icon={productionUpdateStatus === 'updating'
|
|
392
|
+
? 'fa-spinner'
|
|
393
|
+
: productionUpdateStatus === 'done'
|
|
394
|
+
? 'fa-check'
|
|
395
|
+
: productionUpdateStatus === 'error'
|
|
396
|
+
? 'fa-xmark'
|
|
397
|
+
: 'fa-arrow-down'}
|
|
407
398
|
onclick={handleUpdateProduction}
|
|
408
399
|
disabled={productionUpdateStatus === 'updating' || !productionInfo || (productionInfo.fileName === activeFileName && !compDirty)}
|
|
409
400
|
title={!productionInfo
|
|
@@ -416,55 +407,21 @@
|
|
|
416
407
|
? `Save "${currentDisplayName}" and adopt`
|
|
417
408
|
: `Adopt "${currentDisplayName}" from editor`}
|
|
418
409
|
>
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
</
|
|
434
|
-
{#if infoOpen}
|
|
435
|
-
<div
|
|
436
|
-
class="cfm-info-popover"
|
|
437
|
-
class:ready={infoPopoverReady}
|
|
438
|
-
role="dialog"
|
|
439
|
-
aria-label="About Save and Adopt"
|
|
440
|
-
bind:this={infoPopoverEl}
|
|
441
|
-
>
|
|
442
|
-
<header class="cfm-info-header">
|
|
443
|
-
<span class="cfm-info-title">Component Configuration</span>
|
|
444
|
-
<button
|
|
445
|
-
type="button"
|
|
446
|
-
class="cfm-info-close"
|
|
447
|
-
aria-label="Close"
|
|
448
|
-
onclick={() => (infoOpen = false)}
|
|
449
|
-
>
|
|
450
|
-
<i class="fas fa-xmark"></i>
|
|
451
|
-
</button>
|
|
452
|
-
</header>
|
|
453
|
-
<div class="cfm-info-body">
|
|
454
|
-
<p>
|
|
455
|
-
Editor and Prod both use a saved file. When they share the
|
|
456
|
-
<em>same</em> file, <strong>Saved changes</strong> go to into production
|
|
457
|
-
immediately. They are sharing the configuration.
|
|
458
|
-
</p>
|
|
459
|
-
<p>
|
|
460
|
-
To experiment without changing production,<strong>Save As</strong> a new file first.
|
|
461
|
-
</p>
|
|
462
|
-
<p>
|
|
463
|
-
When ready, click <strong>Adopt</strong> to use the new file on prod.
|
|
464
|
-
</p>
|
|
465
|
-
</div>
|
|
466
|
-
</div>
|
|
467
|
-
{/if}
|
|
410
|
+
{#if productionUpdateStatus === 'idle'}Adopt{:else if productionUpdateStatus === 'updating'}Adopting{:else if productionUpdateStatus === 'done'}Adopted{:else}Error{/if}
|
|
411
|
+
</UISquareButton>
|
|
412
|
+
<UIInfoPopover title="Component Configuration" ariaLabel="About Save and Adopt">
|
|
413
|
+
<p>
|
|
414
|
+
Editor and Prod both use a saved file. When they share the
|
|
415
|
+
<em>same</em> file, <strong>Saved changes</strong> go to into production
|
|
416
|
+
immediately. They are sharing the configuration.
|
|
417
|
+
</p>
|
|
418
|
+
<p>
|
|
419
|
+
To experiment without changing production,<strong>Save As</strong> a new file first.
|
|
420
|
+
</p>
|
|
421
|
+
<p>
|
|
422
|
+
When ready, click <strong>Adopt</strong> to use the new file on prod.
|
|
423
|
+
</p>
|
|
424
|
+
</UIInfoPopover>
|
|
468
425
|
{#if adoptFeedback}
|
|
469
426
|
<span class="cfm-feedback" aria-live="polite">{adoptFeedback}</span>
|
|
470
427
|
{/if}
|
|
@@ -477,33 +434,47 @@
|
|
|
477
434
|
bind:show={saveAsDialog}
|
|
478
435
|
{currentDisplayName}
|
|
479
436
|
{files}
|
|
437
|
+
currentFileName={activeFileName}
|
|
438
|
+
reservedDisplayNames={files.filter((f) => f.fileName === 'default').map((f) => f.name)}
|
|
439
|
+
title={saveAsTitle}
|
|
440
|
+
description={saveAsDescription}
|
|
441
|
+
placeholder="Component config name…"
|
|
442
|
+
branchFromDefaultName={`my-${(title || 'component').toLowerCase().trim().replace(/\s+/g, '-')}`}
|
|
480
443
|
onsave={confirmSaveAs}
|
|
481
444
|
/>
|
|
482
445
|
|
|
446
|
+
<SaveAsDialog
|
|
447
|
+
bind:show={manifestSaveAsDialog}
|
|
448
|
+
currentDisplayName="my-manifest"
|
|
449
|
+
files={manifests}
|
|
450
|
+
reservedDisplayNames={manifests.filter((m) => m.fileName === 'default').map((m) => m.name)}
|
|
451
|
+
title="Save Manifest As"
|
|
452
|
+
placeholder="Manifest name…"
|
|
453
|
+
description="Adopting a component change updates the active manifest, The default manifest is locked. Name a new manifest for the site."
|
|
454
|
+
reservedNameMessage='That name is reserved for the protected default manifest.'
|
|
455
|
+
onsave={onManifestSaveAs}
|
|
456
|
+
/>
|
|
457
|
+
|
|
483
458
|
<style>
|
|
484
459
|
.cfm-bar {
|
|
485
460
|
--cfm-applied: #5aa85e;
|
|
486
|
-
--cfm-rail-neutral: var(--ui-border
|
|
461
|
+
--cfm-rail-neutral: var(--ui-border);
|
|
487
462
|
--cfm-rail-dirty: var(--ui-highlight);
|
|
488
463
|
--cfm-rail-applied: var(--cfm-applied);
|
|
489
464
|
|
|
490
|
-
position: sticky;
|
|
491
|
-
top: 0;
|
|
492
|
-
z-index: 5;
|
|
493
465
|
display: flex;
|
|
494
466
|
flex-direction: column;
|
|
495
467
|
gap: var(--ui-space-8);
|
|
496
|
-
padding: var(--ui-space-12);
|
|
497
|
-
background: var(--ui-surface-low);
|
|
498
|
-
border: 1px solid var(--ui-border-faint);
|
|
499
|
-
border-radius: var(--ui-radius-lg);
|
|
500
468
|
}
|
|
501
469
|
|
|
502
470
|
.cfm-title-row {
|
|
503
471
|
display: flex;
|
|
504
472
|
align-items: center;
|
|
505
|
-
|
|
473
|
+
justify-content: space-between;
|
|
474
|
+
gap: var(--ui-space-16);
|
|
506
475
|
flex-wrap: wrap;
|
|
476
|
+
max-width: 34rem;
|
|
477
|
+
margin-bottom: var(--ui-space-16);
|
|
507
478
|
}
|
|
508
479
|
|
|
509
480
|
.cfm-title {
|
|
@@ -516,33 +487,12 @@
|
|
|
516
487
|
line-height: 1.1;
|
|
517
488
|
}
|
|
518
489
|
|
|
519
|
-
.source-link {
|
|
520
|
-
display: inline-flex;
|
|
521
|
-
align-items: center;
|
|
522
|
-
gap: var(--ui-space-6);
|
|
523
|
-
margin-left: 2.5rem;
|
|
524
|
-
height: 26px;
|
|
525
|
-
padding: 0 14px;
|
|
526
|
-
font-size: var(--ui-font-size-xs);
|
|
527
|
-
font-weight: 500;
|
|
528
|
-
color: var(--ui-text-secondary);
|
|
529
|
-
text-decoration: none;
|
|
530
|
-
border: 1px solid var(--ui-border-default);
|
|
531
|
-
border-radius: 999px;
|
|
532
|
-
transition: all var(--ui-transition-fast);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
.source-link:hover {
|
|
536
|
-
color: var(--ui-text-primary);
|
|
537
|
-
border-color: var(--ui-border-strong);
|
|
538
|
-
background: var(--ui-hover);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
490
|
/* ── two-row pipeline ─────────────────────────────────────── */
|
|
542
491
|
.cfm-rows {
|
|
543
492
|
display: flex;
|
|
544
493
|
flex-direction: column;
|
|
545
494
|
gap: var(--ui-space-6);
|
|
495
|
+
max-width: 34rem;
|
|
546
496
|
}
|
|
547
497
|
|
|
548
498
|
.cfm-row {
|
|
@@ -553,7 +503,7 @@
|
|
|
553
503
|
gap: var(--ui-space-10);
|
|
554
504
|
padding: var(--ui-space-8) var(--ui-space-10) var(--ui-space-8) var(--ui-space-16);
|
|
555
505
|
background: var(--ui-surface-lower);
|
|
556
|
-
border: 1px solid var(--ui-border-
|
|
506
|
+
border: 1px solid var(--ui-border-low);
|
|
557
507
|
border-radius: var(--ui-radius-md);
|
|
558
508
|
}
|
|
559
509
|
|
|
@@ -626,41 +576,6 @@
|
|
|
626
576
|
opacity: 1;
|
|
627
577
|
}
|
|
628
578
|
|
|
629
|
-
/* filename pill — fixed width so editor and production pills
|
|
630
|
-
stack with their left and right edges perfectly aligned. */
|
|
631
|
-
.cfm-pill {
|
|
632
|
-
display: inline-flex;
|
|
633
|
-
align-items: center;
|
|
634
|
-
gap: var(--ui-space-6);
|
|
635
|
-
flex: 0 0 7.5rem;
|
|
636
|
-
width: 7.5rem;
|
|
637
|
-
padding: var(--ui-space-6) var(--ui-space-10);
|
|
638
|
-
background: var(--ui-surface-lowest);
|
|
639
|
-
border: 1px solid var(--ui-border-subtle);
|
|
640
|
-
border-radius: var(--ui-radius-md);
|
|
641
|
-
transition: border-color var(--ui-transition-fast), box-shadow var(--ui-transition-fast);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
.cfm-pill.dirty {
|
|
645
|
-
border-color: color-mix(in srgb, var(--ui-highlight) 60%, var(--ui-border-subtle));
|
|
646
|
-
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ui-highlight) 35%, transparent);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
.cfm-pill.applied {
|
|
650
|
-
border-color: color-mix(in srgb, var(--cfm-applied) 50%, var(--ui-border-subtle));
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
.cfm-pill-name {
|
|
654
|
-
flex: 1;
|
|
655
|
-
min-width: 0;
|
|
656
|
-
font-size: var(--ui-font-size-md);
|
|
657
|
-
font-weight: var(--ui-font-weight-semibold);
|
|
658
|
-
color: var(--ui-text-primary);
|
|
659
|
-
white-space: nowrap;
|
|
660
|
-
overflow: hidden;
|
|
661
|
-
text-overflow: ellipsis;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
579
|
/* actions cluster — sits directly next to the filename pill so the
|
|
665
580
|
buttons stay near the input and don't drift under the open editor panel.
|
|
666
581
|
position: relative anchors the info popover below the cluster. */
|
|
@@ -714,195 +629,8 @@
|
|
|
714
629
|
line-height: 1;
|
|
715
630
|
}
|
|
716
631
|
|
|
717
|
-
/* buttons */
|
|
718
|
-
.cfm-btn {
|
|
719
|
-
display: inline-flex;
|
|
720
|
-
align-items: center;
|
|
721
|
-
gap: var(--ui-space-6);
|
|
722
|
-
padding: var(--ui-space-6) var(--ui-space-12);
|
|
723
|
-
background: var(--ui-surface);
|
|
724
|
-
border: 1px solid var(--ui-border-subtle);
|
|
725
|
-
border-radius: var(--ui-radius-md);
|
|
726
|
-
color: var(--ui-text-secondary);
|
|
727
|
-
font-size: var(--ui-font-size-md);
|
|
728
|
-
font-weight: var(--ui-font-weight-medium);
|
|
729
|
-
cursor: pointer;
|
|
730
|
-
transition: all var(--ui-transition-fast);
|
|
731
|
-
white-space: nowrap;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
.cfm-btn i {
|
|
735
|
-
width: 1rem;
|
|
736
|
-
text-align: center;
|
|
737
|
-
font-size: 0.85em;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
.cfm-btn:hover:not(:disabled) {
|
|
741
|
-
background: var(--ui-surface-high);
|
|
742
|
-
color: var(--ui-text-primary);
|
|
743
|
-
border-color: var(--ui-border-default);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
.cfm-btn:disabled {
|
|
747
|
-
opacity: 0.45;
|
|
748
|
-
cursor: not-allowed;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
.cfm-btn.primary {
|
|
752
|
-
background: color-mix(in srgb, var(--cfm-applied) 18%, var(--ui-surface-high));
|
|
753
|
-
border-color: color-mix(in srgb, var(--cfm-applied) 45%, var(--ui-border-medium));
|
|
754
|
-
color: var(--ui-text-primary);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
.cfm-btn.primary:hover:not(:disabled) {
|
|
758
|
-
background: color-mix(in srgb, var(--cfm-applied) 30%, var(--ui-surface-higher));
|
|
759
|
-
border-color: color-mix(in srgb, var(--cfm-applied) 70%, var(--ui-border-strong));
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
.cfm-btn.primary:disabled {
|
|
763
|
-
background: var(--ui-surface);
|
|
764
|
-
border-color: var(--ui-border-subtle);
|
|
765
|
-
color: var(--ui-text-muted);
|
|
766
|
-
opacity: 1;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
.cfm-btn.primary.saving i { animation: cfm-spin 1s linear infinite; }
|
|
770
|
-
.cfm-btn.primary.saved {
|
|
771
|
-
background: color-mix(in srgb, var(--cfm-applied) 30%, var(--ui-surface-high));
|
|
772
|
-
color: var(--cfm-applied);
|
|
773
|
-
}
|
|
774
|
-
.cfm-btn.primary.error {
|
|
775
|
-
color: var(--ui-text-muted);
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
@keyframes cfm-spin {
|
|
779
|
-
from { transform: rotate(0deg); }
|
|
780
|
-
to { transform: rotate(360deg); }
|
|
781
|
-
}
|
|
782
|
-
|
|
783
632
|
@keyframes cfm-pulse {
|
|
784
633
|
0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ui-highlight) 22%, transparent); }
|
|
785
634
|
50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--ui-highlight) 10%, transparent); }
|
|
786
635
|
}
|
|
787
|
-
|
|
788
|
-
/* info button — naked icon, no chrome. The icon itself carries the
|
|
789
|
-
affordance; hover/active simply brighten its color. */
|
|
790
|
-
.cfm-info-btn {
|
|
791
|
-
display: inline-flex;
|
|
792
|
-
align-items: center;
|
|
793
|
-
justify-content: center;
|
|
794
|
-
padding: var(--ui-space-2) var(--ui-space-4);
|
|
795
|
-
background: transparent;
|
|
796
|
-
border: 0;
|
|
797
|
-
color: var(--ui-text-tertiary);
|
|
798
|
-
font-size: 1.15rem;
|
|
799
|
-
line-height: 1;
|
|
800
|
-
cursor: pointer;
|
|
801
|
-
transition: color var(--ui-transition-fast);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
.cfm-info-btn:hover,
|
|
805
|
-
.cfm-info-btn[aria-expanded='true'] {
|
|
806
|
-
color: var(--ui-text-primary);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
.cfm-info-popover {
|
|
810
|
-
/* Fixed positioning escapes the sticky header's stacking context,
|
|
811
|
-
so the popover paints over the side panels. JS in this file
|
|
812
|
-
anchors it centered below the info button. */
|
|
813
|
-
position: fixed;
|
|
814
|
-
top: 0;
|
|
815
|
-
left: 0;
|
|
816
|
-
width: 22rem;
|
|
817
|
-
max-width: calc(100vw - var(--ui-space-24));
|
|
818
|
-
padding: 0;
|
|
819
|
-
background: var(--ui-surface-higher);
|
|
820
|
-
border: 1px solid var(--ui-border-medium);
|
|
821
|
-
border-radius: var(--ui-radius-lg);
|
|
822
|
-
box-shadow: var(--ui-shadow-lg);
|
|
823
|
-
z-index: 1000;
|
|
824
|
-
color: var(--ui-text-secondary);
|
|
825
|
-
font-family: var(--ui-font-family, system-ui, sans-serif);
|
|
826
|
-
overflow: hidden;
|
|
827
|
-
visibility: hidden;
|
|
828
|
-
animation: cfm-info-in 140ms ease-out;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
.cfm-info-popover.ready {
|
|
832
|
-
visibility: visible;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
.cfm-info-header {
|
|
836
|
-
display: flex;
|
|
837
|
-
align-items: center;
|
|
838
|
-
justify-content: space-between;
|
|
839
|
-
gap: var(--ui-space-8);
|
|
840
|
-
padding: var(--ui-space-10) var(--ui-space-12) var(--ui-space-10) var(--ui-space-16);
|
|
841
|
-
border-bottom: 1px solid var(--ui-border-subtle);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
.cfm-info-title {
|
|
845
|
-
color: var(--ui-text-primary);
|
|
846
|
-
font-size: var(--ui-font-size-sm);
|
|
847
|
-
font-weight: var(--ui-font-weight-semibold);
|
|
848
|
-
letter-spacing: -0.01em;
|
|
849
|
-
line-height: 1.2;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
.cfm-info-close {
|
|
853
|
-
display: inline-flex;
|
|
854
|
-
align-items: center;
|
|
855
|
-
justify-content: center;
|
|
856
|
-
width: var(--ui-space-24);
|
|
857
|
-
height: var(--ui-space-24);
|
|
858
|
-
padding: 0;
|
|
859
|
-
background: transparent;
|
|
860
|
-
border: 0;
|
|
861
|
-
border-radius: var(--ui-radius-sm);
|
|
862
|
-
color: var(--ui-text-tertiary);
|
|
863
|
-
font-size: var(--ui-font-size-xs);
|
|
864
|
-
line-height: 1;
|
|
865
|
-
cursor: pointer;
|
|
866
|
-
transition: color var(--ui-transition-fast), background var(--ui-transition-fast);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
.cfm-info-close:hover {
|
|
870
|
-
color: var(--ui-text-primary);
|
|
871
|
-
background: var(--ui-hover);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
.cfm-info-body {
|
|
875
|
-
padding: var(--ui-space-16);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
.cfm-info-popover p {
|
|
879
|
-
margin: 0 0 var(--ui-space-12) 0;
|
|
880
|
-
font-size: var(--ui-font-size-xs);
|
|
881
|
-
line-height: 1.55;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
.cfm-info-popover p:last-child {
|
|
885
|
-
margin-bottom: 0;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
.cfm-info-popover strong {
|
|
889
|
-
color: var(--ui-text-primary);
|
|
890
|
-
font-weight: var(--ui-font-weight-semibold);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
.cfm-info-popover em {
|
|
894
|
-
font-style: italic;
|
|
895
|
-
color: var(--ui-text-primary);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
@keyframes cfm-info-in {
|
|
899
|
-
from { opacity: 0; transform: translateY(-3px); }
|
|
900
|
-
to { opacity: 1; transform: translateY(0); }
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/* narrow viewports: hide button text, keep icons visible */
|
|
904
|
-
@media (max-width: 640px) {
|
|
905
|
-
.cfm-btn span { display: none; }
|
|
906
|
-
.cfm-btn { padding: var(--ui-space-6) var(--ui-space-10); }
|
|
907
|
-
}
|
|
908
636
|
</style>
|