@motion-proto/live-tokens 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/dist-plugin/index.cjs +854 -226
- package/dist-plugin/index.d.cts +2 -1
- package/dist-plugin/index.d.ts +2 -1
- package/dist-plugin/index.js +852 -225
- package/package.json +26 -40
- package/src/{styles → app}/site.css +1 -1
- package/src/{component-editor → editor/component-editor}/BadgeEditor.svelte +8 -82
- package/src/{component-editor → editor/component-editor}/CalloutEditor.svelte +4 -4
- package/src/{component-editor → editor/component-editor}/CardEditor.svelte +28 -76
- package/src/{component-editor → editor/component-editor}/CollapsibleSectionEditor.svelte +37 -30
- package/src/{component-editor → editor/component-editor}/CornerBadgeEditor.svelte +31 -93
- package/src/{component-editor → editor/component-editor}/DialogEditor.svelte +60 -57
- package/src/editor/component-editor/ImageEditor.svelte +30 -0
- package/src/{component-editor → editor/component-editor}/InlineEditActionsEditor.svelte +6 -4
- package/src/editor/component-editor/MenuSelectEditor.svelte +160 -0
- package/src/{component-editor → editor/component-editor}/NotificationEditor.svelte +67 -38
- package/src/{component-editor → editor/component-editor}/ProgressBarEditor.svelte +5 -4
- package/src/{component-editor → editor/component-editor}/RadioButtonEditor.svelte +3 -3
- package/src/editor/component-editor/SectionDividerEditor.svelte +565 -0
- package/src/{component-editor → editor/component-editor}/SegmentedControlEditor.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/StandardButtonsEditor.svelte +29 -21
- package/src/{component-editor → editor/component-editor}/TabBarEditor.svelte +9 -14
- package/src/{component-editor → editor/component-editor}/TableEditor.svelte +9 -18
- package/src/{component-editor → editor/component-editor}/TooltipEditor.svelte +11 -47
- package/src/editor/component-editor/editors.d.ts +10 -0
- package/src/{component-editor → editor/component-editor}/registry.ts +28 -18
- package/src/{component-editor → editor/component-editor}/scaffolding/AngleDial.svelte +54 -15
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentEditorBase.svelte +3 -51
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte +151 -424
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileMenu.svelte +18 -170
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentsTab.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/CopyFromMenu.svelte +44 -4
- package/src/{component-editor → editor/component-editor}/scaffolding/FieldsetWrapper.svelte +1 -1
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkageChart.svelte +6 -6
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkedBlock.svelte +6 -12
- package/src/editor/component-editor/scaffolding/NonStylableConfig.svelte +38 -0
- package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/SaveAsDialog.svelte +66 -12
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +85 -0
- package/src/editor/component-editor/scaffolding/ShadowBackdropControls.svelte +132 -0
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +345 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/TokenLayout.svelte +17 -12
- package/src/{component-editor → editor/component-editor}/scaffolding/TypeEditor.svelte +13 -1
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +858 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/buildTypeGroupTokens.ts +1 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/editorContext.ts +19 -9
- package/src/{component-editor → editor/component-editor}/scaffolding/linkedBlock.ts +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/types.ts +25 -0
- package/src/{lib → editor/core/components}/componentConfigKeys.ts +8 -0
- package/src/{lib → editor/core/components}/componentConfigService.ts +3 -3
- package/src/{lib → editor/core/components}/componentPersist.ts +11 -9
- package/src/editor/core/flashStatus.ts +30 -0
- package/src/{lib → editor/core/fonts}/fontLoader.ts +2 -2
- package/src/{lib → editor/core/fonts}/fontMigration.ts +4 -4
- package/src/{lib → editor/core/fonts}/fontParse.ts +1 -1
- package/src/editor/core/manifests/manifestService.ts +171 -0
- package/src/editor/core/palettes/familySwap.ts +99 -0
- package/src/{lib → editor/core/palettes}/paletteDerivation.ts +71 -2
- package/src/{lib → editor/core/palettes}/tokenRegistry.ts +9 -6
- package/src/editor/core/productionPulse.ts +37 -0
- package/src/{lib → editor/core/routing}/router.ts +1 -1
- package/src/{lib/files/versionedFileResource.ts → editor/core/storage/files/versionedFileResourceClient.ts} +8 -1
- package/src/{lib → editor/core/store}/editorCore.ts +24 -8
- package/src/{lib → editor/core/store}/editorPersistence.ts +3 -3
- package/src/{lib → editor/core/store}/editorRenderer.ts +2 -2
- package/src/{lib → editor/core/store}/editorStore.ts +222 -28
- package/src/{lib → editor/core/store}/editorTypes.ts +56 -13
- package/src/editor/core/store/gradientSource.ts +192 -0
- package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
- package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
- package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
- package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
- package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
- package/src/{lib → editor/core/themes}/migrations/index.ts +10 -0
- package/src/{lib → editor/core/themes}/slices/columns.ts +2 -2
- package/src/{lib → editor/core/themes}/slices/components.ts +20 -6
- package/src/{lib → editor/core/themes}/slices/fonts.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/gradients.ts +89 -14
- package/src/{lib → editor/core/themes}/slices/overlays.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/palettes.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/shadows.ts +3 -3
- package/src/{lib → editor/core/themes}/themeInit.ts +8 -8
- package/src/{lib → editor/core/themes}/themeService.ts +6 -6
- package/src/{lib → editor/core/themes}/themeTypes.ts +67 -8
- package/src/editor/index.ts +69 -0
- package/src/{lib → editor/overlay}/ColumnsOverlay.svelte +0 -1
- package/src/{lib → editor/overlay}/LiveEditorOverlay.svelte +80 -129
- package/src/{lib → editor/overlay}/columnsOverlay.ts +2 -2
- package/src/{pages → editor/pages}/ComponentEditorPage.svelte +12 -12
- package/src/{pages → editor/pages}/Editor.svelte +4 -4
- package/src/{pages → editor/pages}/EditorShell.svelte +18 -36
- package/src/{styles → editor/styles}/ui-editor.css +43 -22
- package/src/{styles → editor/styles}/ui-form-controls.css +23 -24
- package/src/{ui → editor/ui}/BezierCurveEditor.svelte +119 -68
- package/src/{ui → editor/ui}/ColorEditPanel.svelte +13 -13
- package/src/{ui → editor/ui}/EditorViewSwitcher.svelte +7 -6
- package/src/editor/ui/FileLoadList.svelte +367 -0
- package/src/editor/ui/FilePill.svelte +80 -0
- package/src/editor/ui/FontStackEditor.svelte +499 -0
- package/src/editor/ui/GradientEditor.svelte +690 -0
- package/src/{ui → editor/ui}/GradientStopPicker.svelte +12 -4
- package/src/editor/ui/ManifestFileManager.svelte +438 -0
- package/src/{ui → editor/ui}/PaletteEditor.svelte +180 -673
- package/src/editor/ui/ProjectFontsSection.svelte +638 -0
- package/src/{ui → editor/ui}/SurfacesTab.svelte +3 -3
- package/src/{ui → editor/ui}/TextTab.svelte +3 -3
- package/src/editor/ui/ThemeFileManager.svelte +783 -0
- package/src/{ui → editor/ui}/UICopyPopover.svelte +4 -4
- package/src/{ui → editor/ui}/UIFontFamilySelector.svelte +6 -7
- package/src/{ui → editor/ui}/UIFontSizeSelector.svelte +4 -1
- package/src/editor/ui/UIInfoPopover.svelte +243 -0
- package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
- package/src/{ui → editor/ui}/UILineHeightSelector.svelte +5 -5
- package/src/{ui → editor/ui}/UILinkToggle.svelte +2 -2
- package/src/{ui → editor/ui}/UIPaddingSelector.svelte +6 -6
- package/src/{ui → editor/ui}/UIPaletteSelector.svelte +57 -30
- package/src/editor/ui/UIPillButton.svelte +168 -0
- package/src/{ui → editor/ui}/UIRadio.svelte +2 -2
- package/src/{ui → editor/ui}/UIRelinkConfirmPopover.svelte +4 -4
- package/src/editor/ui/UISegmentedControl.svelte +114 -0
- package/src/editor/ui/UISquareButton.svelte +172 -0
- package/src/{ui → editor/ui}/UITokenSelector.svelte +14 -11
- package/src/{ui → editor/ui}/UIVariantSelector.svelte +1 -1
- package/src/{ui → editor/ui}/VariablesTab.svelte +46 -17
- package/src/{ui → editor/ui}/palette/GradientStopEditor.svelte +13 -13
- package/src/{ui → editor/ui}/palette/OverridesPanel.svelte +24 -47
- package/src/{ui → editor/ui}/palette/PaletteBase.svelte +11 -8
- package/src/{ui → editor/ui}/palette/paletteEditorState.ts +1 -1
- package/src/editor/ui/palette/paletteMath.ts +275 -0
- package/src/{ui → editor/ui}/sections/ColumnsSection.svelte +137 -18
- package/src/{ui → editor/ui}/sections/GradientsSection.svelte +8 -8
- package/src/{ui → editor/ui}/sections/OverlaysSection.svelte +18 -18
- package/src/{ui → editor/ui}/sections/ShadowsSection.svelte +23 -23
- package/src/{ui → editor/ui}/sections/TokenScaleTable.svelte +3 -3
- package/src/{components → system/components}/Badge.svelte +0 -36
- package/src/{components → system/components}/Button.svelte +2 -2
- package/src/{components → system/components}/Card.svelte +34 -60
- package/src/{components → system/components}/CollapsibleSection.svelte +25 -2
- package/src/{components → system/components}/CornerBadge.svelte +8 -24
- package/src/{components → system/components}/Dialog.svelte +1 -1
- package/src/system/components/FloatingTokenTags.css +275 -0
- package/src/system/components/FloatingTokenTags.svelte +543 -0
- package/src/{components → system/components}/InlineEditActions.svelte +6 -4
- package/src/system/components/MenuSelect.svelte +229 -0
- package/src/{components → system/components}/Notification.svelte +8 -1
- package/src/{components → system/components}/ProgressBar.svelte +29 -11
- package/src/system/components/SectionDivider.svelte +560 -0
- package/src/{components → system/components}/SegmentedControl.svelte +49 -43
- package/src/{components → system/components}/TabBar.svelte +81 -65
- package/src/{components → system/components}/Table.svelte +17 -3
- package/src/{components → system/components}/Tooltip.svelte +6 -4
- package/src/system/styles/CONVENTIONS.md +178 -0
- package/src/system/styles/fonts.css +20 -0
- package/src/system/styles/tokens.css +601 -0
- package/src/system/styles/tokens.generated.css +544 -0
- package/src/component-editor/ImageEditor.svelte +0 -74
- package/src/component-editor/SectionDividerEditor.svelte +0 -265
- package/src/component-editor/scaffolding/DividerEditor.svelte +0 -94
- package/src/component-editor/scaffolding/GradientCard.svelte +0 -296
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +0 -62
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +0 -37
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +0 -61
- package/src/component-editor/scaffolding/StateBlock.svelte +0 -132
- package/src/component-editor/scaffolding/VariantGroup.svelte +0 -310
- package/src/components/SectionDivider.svelte +0 -483
- package/src/data/google-fonts.json +0 -75
- package/src/lib/index.ts +0 -68
- package/src/lib/presetService.ts +0 -214
- package/src/lib/productionPulse.ts +0 -32
- package/src/styles/fonts.css +0 -30
- package/src/styles/tokens.css +0 -1324
- package/src/ui/FontStackEditor.svelte +0 -361
- package/src/ui/GradientEditor.svelte +0 -470
- package/src/ui/PresetFileManager.svelte +0 -1116
- package/src/ui/ProjectFontsSection.svelte +0 -645
- package/src/ui/ThemeFileManager.svelte +0 -1020
- package/src/ui/UnsavedComponentsDialog.svelte +0 -315
- /package/src/{component-editor → editor/component-editor}/index.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/DemoHeader.svelte +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/componentSectionType.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/componentSources.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/defaultSections.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/siblings.ts +0 -0
- /package/src/{lib → editor/core}/cssVarSync.ts +0 -0
- /package/src/{lib → editor/core/palettes}/oklch.ts +0 -0
- /package/src/{lib → editor/core/routing}/navLinkTypes.ts +0 -0
- /package/src/{lib → editor/core/routing}/parentRouteStore.ts +0 -0
- /package/src/{lib → editor/core/storage}/storage.ts +0 -0
- /package/src/{lib → editor/core/store}/editorConfig.ts +0 -0
- /package/src/{lib → editor/core/store}/editorConfigStore.ts +0 -0
- /package/src/{lib → editor/core/store}/editorKeybindings.ts +0 -0
- /package/src/{lib → editor/core/store}/editorViewStore.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-10-sectiondivider-gradient-stops.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-13-primary-to-brand.ts +0 -0
- /package/src/{lib → editor/core/themes}/parsers/globalRootBlock.ts +0 -0
- /package/src/{lib → editor/core/themes}/slices/domainVars.ts +0 -0
- /package/src/{lib → editor/overlay}/overlayState.ts +0 -0
- /package/src/{pages → editor/pages}/ComponentEditorPage.svelte.d.ts +0 -0
- /package/src/{pages → editor/pages}/Editor.svelte.d.ts +0 -0
- /package/src/{ui → editor/ui}/Toggle.svelte +0 -0
- /package/src/{ui → editor/ui}/UIDialog.svelte +0 -0
- /package/src/{ui → editor/ui}/UIFontWeightSelector.svelte +0 -0
- /package/src/{ui → editor/ui}/UIOptionItem.svelte +0 -0
- /package/src/{ui → editor/ui}/UIOptionList.svelte +0 -0
- /package/src/{ui → editor/ui}/UIRadioGroup.svelte +0 -0
- /package/src/{lib → editor/ui}/copyPopover.ts +0 -0
- /package/src/{ui → editor/ui}/curveEngine.ts +0 -0
- /package/src/{ui → editor/ui}/index.ts +0 -0
- /package/src/{ui → editor/ui}/keepInViewport.ts +0 -0
- /package/src/{ui → editor/ui}/palette/ScaleCurveEditor.svelte +0 -0
- /package/src/{lib → editor/ui}/scrollSection.ts +0 -0
- /package/src/{ui → editor/ui}/sections/tokenScales.ts +0 -0
- /package/src/{ui → editor/ui}/variantScales.ts +0 -0
- /package/src/{assets → system/assets}/newspaper.webp +0 -0
- /package/src/{assets → system/assets}/offering.webp +0 -0
- /package/src/{components → system/components}/Callout.svelte +0 -0
- /package/src/{components → system/components}/Image.svelte +0 -0
- /package/src/{components → system/components}/RadioButton.svelte +0 -0
- /package/src/{components → system/components}/types.ts +0 -0
- /package/src/{styles → system/styles}/_padding.scss +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin.woff2 +0 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { run } from 'svelte/legacy';
|
|
3
|
+
|
|
4
|
+
// Visual gradient editor: draggable stop diamonds on a live ribbon.
|
|
5
|
+
// Bound to a GradientSource (theme via `variable`, or component via `source`).
|
|
6
|
+
import { tick, onMount } from 'svelte';
|
|
7
|
+
import { get } from 'svelte/store';
|
|
8
|
+
import type { GradientType, GradientTokenStop } from '../core/store/editorTypes';
|
|
9
|
+
import {
|
|
10
|
+
themeGradientSource,
|
|
11
|
+
snapshotGradient,
|
|
12
|
+
type GradientSource,
|
|
13
|
+
type GradientSourceSnapshot,
|
|
14
|
+
} from '../core/store/gradientSource';
|
|
15
|
+
import GradientStopPicker from './GradientStopPicker.svelte';
|
|
16
|
+
import AngleDial from '../component-editor/scaffolding/AngleDial.svelte';
|
|
17
|
+
import RadialShapePad from '../component-editor/scaffolding/RadialShapePad.svelte';
|
|
18
|
+
import UISegmentedControl from './UISegmentedControl.svelte';
|
|
19
|
+
import UIPillButton from './UIPillButton.svelte';
|
|
20
|
+
import { snapTokenToFamily } from '../core/palettes/familySwap';
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
/** Theme-gradient mode: variable name (e.g. `--gradient-1`). */
|
|
24
|
+
variable?: string;
|
|
25
|
+
/** Component-gradient mode: source adapter. Wins over `variable`. */
|
|
26
|
+
source?: GradientSource;
|
|
27
|
+
/** Header label above the ribbon; turns the editor into a 2-col grid. */
|
|
28
|
+
sectionLabel?: string;
|
|
29
|
+
/** Stable id for per-stop picker scratch vars. */
|
|
30
|
+
stopIdPrefix?: string;
|
|
31
|
+
/** Greys out tokens outside this family prefix in the stop picker. */
|
|
32
|
+
familyFilter?: string | null;
|
|
33
|
+
/** Show the "None" segment so the user can clear the fill outright. */
|
|
34
|
+
showNone?: boolean;
|
|
35
|
+
/** Called when the user picks "None" so the parent can zero ancillary tokens. */
|
|
36
|
+
onNone?: () => void;
|
|
37
|
+
onsave?: () => void;
|
|
38
|
+
oncancel?: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let {
|
|
42
|
+
variable,
|
|
43
|
+
source,
|
|
44
|
+
sectionLabel,
|
|
45
|
+
stopIdPrefix,
|
|
46
|
+
familyFilter = null,
|
|
47
|
+
showNone = false,
|
|
48
|
+
onNone,
|
|
49
|
+
onsave,
|
|
50
|
+
oncancel,
|
|
51
|
+
}: Props = $props();
|
|
52
|
+
|
|
53
|
+
// Captured once: callers remount when the target gradient changes.
|
|
54
|
+
// svelte-ignore state_referenced_locally
|
|
55
|
+
const gradientSource: GradientSource = source ?? themeGradientSource(variable!);
|
|
56
|
+
// Local const so Svelte 5's `$<store>` auto-subscription works.
|
|
57
|
+
const gradientSourceCurrent = gradientSource.current;
|
|
58
|
+
// svelte-ignore state_referenced_locally
|
|
59
|
+
const stopKeyPrefix: string = stopIdPrefix ?? variable ?? 'gradient-edit';
|
|
60
|
+
|
|
61
|
+
// Snapshot at open, restored on Cancel.
|
|
62
|
+
let snapshot: GradientSourceSnapshot | null = null;
|
|
63
|
+
onMount(() => {
|
|
64
|
+
snapshot = snapshotGradient(gradientSource);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function save() { onsave?.(); }
|
|
68
|
+
function cancel() {
|
|
69
|
+
if (snapshot) gradientSource.setAll(snapshot);
|
|
70
|
+
oncancel?.();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let gradient = $derived($gradientSourceCurrent);
|
|
74
|
+
let stopCount = $derived(gradient?.stops.length ?? 0);
|
|
75
|
+
|
|
76
|
+
let selected = $state(0);
|
|
77
|
+
// Keep `selected` in range as stops are added/removed.
|
|
78
|
+
run(() => {
|
|
79
|
+
if (selected >= stopCount) selected = Math.max(0, stopCount - 1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function setType(type: GradientType) {
|
|
83
|
+
gradientSource.setType(type);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function onAngleChange(detail: { value: number }) {
|
|
87
|
+
gradientSource.setAngle(detail.value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function onAspectChange(detail: { x: number; y: number }) {
|
|
91
|
+
gradientSource.setAspect(detail);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setPosition(i: number, pct: number) {
|
|
95
|
+
const clamped = Math.max(0, Math.min(100, Math.round(pct * 10) / 10));
|
|
96
|
+
gradientSource.setStop(i, { position: clamped });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function onPositionInput(e: Event) {
|
|
100
|
+
const v = parseFloat((e.target as HTMLInputElement).value);
|
|
101
|
+
if (Number.isFinite(v)) setPosition(selected, v);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function handleStopChange(i: number, payload: { color: string; opacity: number }) {
|
|
105
|
+
// Picking a real color while `none` promotes to `solid`; `transparent` keeps `none`.
|
|
106
|
+
if (gradient?.type === 'none' && payload.color !== 'transparent') {
|
|
107
|
+
gradientSource.setType('solid');
|
|
108
|
+
}
|
|
109
|
+
gradientSource.setStop(i, { color: payload.color, opacity: payload.opacity });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Mono on: snap to family. Mono off: mark off-palette so family swaps skip it.
|
|
113
|
+
function handleMonoToggle(i: number, mono: boolean) {
|
|
114
|
+
if (mono && familyFilter) {
|
|
115
|
+
const stop = gradient?.stops[i];
|
|
116
|
+
if (stop) {
|
|
117
|
+
const snapped = snapTokenToFamily(stop.color, familyFilter);
|
|
118
|
+
gradientSource.setStop(i, { monochrome: true, color: snapped });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
gradientSource.setStop(i, { monochrome: mono });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function stopValueLabel(stop: GradientTokenStop): string {
|
|
126
|
+
const op = stop.opacity ?? 100;
|
|
127
|
+
const base = stop.color.startsWith('--') ? stop.color.slice(2) : stop.color;
|
|
128
|
+
return op < 100 ? `${base} (${Math.round(op)}%)` : base;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Inserts at pct inheriting from source stop, then selects after the sort settles.
|
|
132
|
+
async function insertStopAt(pct: number, sourceStop: GradientTokenStop) {
|
|
133
|
+
const clamped = Math.max(0, Math.min(100, Math.round(pct * 10) / 10));
|
|
134
|
+
gradientSource.addStop({
|
|
135
|
+
position: clamped,
|
|
136
|
+
color: sourceStop.color,
|
|
137
|
+
opacity: sourceStop.opacity ?? 100,
|
|
138
|
+
});
|
|
139
|
+
await tick();
|
|
140
|
+
const after = get(gradientSource.current);
|
|
141
|
+
if (after) {
|
|
142
|
+
const idx = after.stops.findIndex((s) => s.position === clamped && s.color === sourceStop.color);
|
|
143
|
+
if (idx >= 0) selected = idx;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function addStop() {
|
|
148
|
+
if (!gradient) return;
|
|
149
|
+
const stops = gradient.stops;
|
|
150
|
+
// Midway to the right neighbour, or to 100% if last.
|
|
151
|
+
const anchor = stops[selected] ?? stops[stops.length - 1];
|
|
152
|
+
const next = stops[selected + 1];
|
|
153
|
+
const newPos = next
|
|
154
|
+
? (anchor.position + next.position) / 2
|
|
155
|
+
: (anchor.position + 100) / 2;
|
|
156
|
+
insertStopAt(newPos, anchor);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Inserts a stop at click position, inheriting from the nearest existing stop.
|
|
160
|
+
function onRibbonClick(e: MouseEvent) {
|
|
161
|
+
if (!gradient || e.button !== 0) return;
|
|
162
|
+
const rect = barEl!.getBoundingClientRect();
|
|
163
|
+
const x = e.clientX - rect.left;
|
|
164
|
+
let pct: number;
|
|
165
|
+
if (gradient.type === 'radial') {
|
|
166
|
+
// Radial: both halves map to the same distance from center.
|
|
167
|
+
const half = rect.width / 2;
|
|
168
|
+
pct = (Math.abs(x - half) / half) * 100;
|
|
169
|
+
} else {
|
|
170
|
+
pct = (x / rect.width) * 100;
|
|
171
|
+
}
|
|
172
|
+
const nearest = gradient.stops.reduce(
|
|
173
|
+
(best, s) => (Math.abs(s.position - pct) < Math.abs(best.position - pct) ? s : best),
|
|
174
|
+
gradient.stops[0],
|
|
175
|
+
);
|
|
176
|
+
insertStopAt(pct, nearest);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function removeSelected() {
|
|
180
|
+
if (!gradient || gradient.stops.length <= 2) return;
|
|
181
|
+
gradientSource.removeStop(selected);
|
|
182
|
+
if (selected > 0) selected -= 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Ribbon handle drag
|
|
186
|
+
let barEl: HTMLDivElement | undefined = $state();
|
|
187
|
+
let dragIndex: number | null = $state(null);
|
|
188
|
+
// Drag origin side on radial ribbon: lets pointer x map symmetrically to stop position.
|
|
189
|
+
let dragSide: 'left' | 'right' | null = $state(null);
|
|
190
|
+
|
|
191
|
+
function pctFromEvent(e: PointerEvent): number {
|
|
192
|
+
const rect = barEl!.getBoundingClientRect();
|
|
193
|
+
const x = e.clientX - rect.left;
|
|
194
|
+
if (gradient?.type === 'radial') {
|
|
195
|
+
const half = rect.width / 2;
|
|
196
|
+
return dragSide === 'left'
|
|
197
|
+
? ((half - x) / half) * 100
|
|
198
|
+
: ((x - half) / half) * 100;
|
|
199
|
+
}
|
|
200
|
+
return (x / rect.width) * 100;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function onHandleDown(e: PointerEvent, i: number, side: 'left' | 'right' | null = null) {
|
|
204
|
+
selected = i;
|
|
205
|
+
dragIndex = i;
|
|
206
|
+
dragSide = side;
|
|
207
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
208
|
+
setPosition(i, pctFromEvent(e));
|
|
209
|
+
}
|
|
210
|
+
function onHandleMove(e: PointerEvent) {
|
|
211
|
+
if (dragIndex === null) return;
|
|
212
|
+
setPosition(dragIndex, pctFromEvent(e));
|
|
213
|
+
}
|
|
214
|
+
function onHandleUp(e: PointerEvent) {
|
|
215
|
+
if (dragIndex === null) return;
|
|
216
|
+
(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
|
|
217
|
+
dragIndex = null;
|
|
218
|
+
dragSide = null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Synthesise ribbon background from the snapshot so it renders before the
|
|
222
|
+
// CSS var has been pushed to :root. Radial mirrors stops across 50%.
|
|
223
|
+
function stopColorCss(s: GradientTokenStop): string {
|
|
224
|
+
const base = s.color.startsWith('--') ? `var(${s.color})` : s.color;
|
|
225
|
+
const op = s.opacity ?? 100;
|
|
226
|
+
return op >= 100 ? base : `color-mix(in srgb, ${base} ${Math.round(op)}%, transparent)`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let ribbonBg = $derived.by(() => {
|
|
230
|
+
if (!gradient) return 'transparent';
|
|
231
|
+
if (gradient.type === 'none') return 'transparent';
|
|
232
|
+
if (gradient.type === 'solid') {
|
|
233
|
+
const first = gradient.stops[0];
|
|
234
|
+
return first ? stopColorCss(first) : 'transparent';
|
|
235
|
+
}
|
|
236
|
+
const sorted = gradient.stops.slice().sort((a, b) => a.position - b.position);
|
|
237
|
+
if (gradient.type === 'radial') {
|
|
238
|
+
const leftStops = sorted.slice().reverse().map((s) => `${stopColorCss(s)} ${50 - s.position / 2}%`);
|
|
239
|
+
const rightStops = sorted.map((s) => `${stopColorCss(s)} ${50 + s.position / 2}%`);
|
|
240
|
+
return `linear-gradient(90deg, ${[...leftStops, ...rightStops].join(', ')})`;
|
|
241
|
+
}
|
|
242
|
+
const stopsCss = sorted.map((s) => `${stopColorCss(s)} ${s.position}%`).join(', ');
|
|
243
|
+
return `linear-gradient(90deg, ${stopsCss})`;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Flat (solid/none) = single-stop passive UI. Linear/radial show full chrome.
|
|
247
|
+
let isFlat = $derived(gradient?.type === 'solid' || gradient?.type === 'none');
|
|
248
|
+
let isNone = $derived(gradient?.type === 'none');
|
|
249
|
+
let isRadial = $derived(gradient?.type === 'radial');
|
|
250
|
+
let isLinear = $derived(gradient?.type === 'linear');
|
|
251
|
+
// Right column carries the radial pad or angle dial.
|
|
252
|
+
let hasAside = $derived(isRadial || isLinear);
|
|
253
|
+
|
|
254
|
+
let stopSwatches = $derived((gradient?.stops ?? []).map((s) => {
|
|
255
|
+
const base = s.color.startsWith('--') ? `var(${s.color})` : s.color;
|
|
256
|
+
const op = s.opacity ?? 100;
|
|
257
|
+
return op >= 100 ? base : `color-mix(in srgb, ${base} ${Math.round(op)}%, transparent)`;
|
|
258
|
+
}));
|
|
259
|
+
|
|
260
|
+
type TypeChoice = 'none' | 'solid' | 'linear' | 'radial';
|
|
261
|
+
let typeOptions = $derived(
|
|
262
|
+
[
|
|
263
|
+
...(showNone ? [{ value: 'none' as TypeChoice, label: 'None' }] : []),
|
|
264
|
+
{ value: 'solid' as TypeChoice, label: 'Solid' },
|
|
265
|
+
{ value: 'linear' as TypeChoice, label: 'Linear' },
|
|
266
|
+
{ value: 'radial' as TypeChoice, label: 'Radial' },
|
|
267
|
+
],
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
function onTypeSelect(next: TypeChoice) {
|
|
271
|
+
setType(next as GradientType);
|
|
272
|
+
if (next === 'none') onNone?.();
|
|
273
|
+
}
|
|
274
|
+
</script>
|
|
275
|
+
|
|
276
|
+
{#if gradient}
|
|
277
|
+
<div class="gradient-editor" class:has-pad={hasAside}>
|
|
278
|
+
{#if sectionLabel || hasAside}
|
|
279
|
+
<div class="editor-header editor-section-left">
|
|
280
|
+
<span class="editor-section-label">{sectionLabel ?? ''}</span>
|
|
281
|
+
{#if !isFlat}
|
|
282
|
+
<div class="stop-actions">
|
|
283
|
+
<UIPillButton
|
|
284
|
+
variant="secondary"
|
|
285
|
+
size="compact"
|
|
286
|
+
icon="fa-plus"
|
|
287
|
+
title="Add stop"
|
|
288
|
+
onclick={addStop}
|
|
289
|
+
>Add stop</UIPillButton>
|
|
290
|
+
<UIPillButton
|
|
291
|
+
variant="secondary"
|
|
292
|
+
size="compact"
|
|
293
|
+
icon="fa-times"
|
|
294
|
+
title={gradient.stops.length <= 2 ? 'Gradient needs at least two stops' : 'Remove selected stop'}
|
|
295
|
+
disabled={gradient.stops.length <= 2}
|
|
296
|
+
onclick={removeSelected}
|
|
297
|
+
>Remove stop</UIPillButton>
|
|
298
|
+
</div>
|
|
299
|
+
{/if}
|
|
300
|
+
</div>
|
|
301
|
+
{#if isRadial}
|
|
302
|
+
<span class="editor-section-label editor-section-right">Gradient shape</span>
|
|
303
|
+
{:else if isLinear}
|
|
304
|
+
<span class="editor-section-label editor-section-right">Gradient angle</span>
|
|
305
|
+
{/if}
|
|
306
|
+
{/if}
|
|
307
|
+
<div class="ribbon-stack">
|
|
308
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
309
|
+
<div
|
|
310
|
+
class="ribbon"
|
|
311
|
+
class:solid={isFlat}
|
|
312
|
+
class:none={isNone}
|
|
313
|
+
class:radial={isRadial}
|
|
314
|
+
bind:this={barEl}
|
|
315
|
+
style="background: {ribbonBg};"
|
|
316
|
+
onclick={isFlat ? undefined : onRibbonClick}
|
|
317
|
+
role={isFlat ? 'presentation' : 'button'}
|
|
318
|
+
tabindex="-1"
|
|
319
|
+
aria-label={isNone ? 'No fill' : isFlat ? 'Solid color preview' : isRadial ? 'Click the right side to add a gradient stop' : 'Click to add a gradient stop'}
|
|
320
|
+
>
|
|
321
|
+
{#if isRadial}<span class="center-divider" aria-hidden="true"></span>{/if}
|
|
322
|
+
</div>
|
|
323
|
+
{#if !isFlat}
|
|
324
|
+
<div class="handles" class:radial={isRadial}>
|
|
325
|
+
{#if isRadial}
|
|
326
|
+
{#each gradient.stops as stop, i (`mirror-${i}`)}
|
|
327
|
+
<button
|
|
328
|
+
type="button"
|
|
329
|
+
class="handle"
|
|
330
|
+
class:selected={selected === i}
|
|
331
|
+
class:dragging={dragIndex === i && dragSide === 'left'}
|
|
332
|
+
style="left: {50 - stop.position / 2}%; --stop-color: {stopSwatches[i]};"
|
|
333
|
+
onpointerdown={(e) => onHandleDown(e, i, 'left')}
|
|
334
|
+
onpointermove={onHandleMove}
|
|
335
|
+
onpointerup={onHandleUp}
|
|
336
|
+
onpointercancel={onHandleUp}
|
|
337
|
+
title={`Stop ${i + 1} (${stop.position}%) — linked pair`}
|
|
338
|
+
aria-label={`Gradient stop ${i + 1}, mirrored side`}
|
|
339
|
+
>
|
|
340
|
+
<span class="handle-diamond"></span>
|
|
341
|
+
</button>
|
|
342
|
+
{/each}
|
|
343
|
+
{/if}
|
|
344
|
+
{#each gradient.stops as stop, i (i)}
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
class="handle"
|
|
348
|
+
class:selected={selected === i}
|
|
349
|
+
class:dragging={dragIndex === i && dragSide !== 'left'}
|
|
350
|
+
style="left: {isRadial ? 50 + stop.position / 2 : stop.position}%; --stop-color: {stopSwatches[i]};"
|
|
351
|
+
onpointerdown={(e) => onHandleDown(e, i, isRadial ? 'right' : null)}
|
|
352
|
+
onpointermove={onHandleMove}
|
|
353
|
+
onpointerup={onHandleUp}
|
|
354
|
+
onpointercancel={onHandleUp}
|
|
355
|
+
title={`Stop ${i + 1} (${stop.position}%)`}
|
|
356
|
+
aria-label={`Gradient stop ${i + 1}`}
|
|
357
|
+
>
|
|
358
|
+
<span class="handle-diamond"></span>
|
|
359
|
+
</button>
|
|
360
|
+
{/each}
|
|
361
|
+
</div>
|
|
362
|
+
{/if}
|
|
363
|
+
</div>
|
|
364
|
+
{#if gradient.type === 'radial'}
|
|
365
|
+
<div class="ribbon-pad">
|
|
366
|
+
<RadialShapePad
|
|
367
|
+
x={gradient.aspectX ?? 1}
|
|
368
|
+
y={gradient.aspectY ?? 1}
|
|
369
|
+
onchange={onAspectChange}
|
|
370
|
+
/>
|
|
371
|
+
</div>
|
|
372
|
+
{:else if gradient.type === 'linear'}
|
|
373
|
+
<div class="ribbon-pad ribbon-pad-linear">
|
|
374
|
+
<AngleDial value={gradient.angle} size={64} orientation="vertical" label="" onchange={onAngleChange} />
|
|
375
|
+
</div>
|
|
376
|
+
{/if}
|
|
377
|
+
|
|
378
|
+
<div class="lower-row">
|
|
379
|
+
<UISegmentedControl
|
|
380
|
+
value={gradient.type as TypeChoice}
|
|
381
|
+
options={typeOptions}
|
|
382
|
+
ariaLabel="Gradient fill type"
|
|
383
|
+
onchange={onTypeSelect}
|
|
384
|
+
/>
|
|
385
|
+
{#if gradient.stops[selected]}
|
|
386
|
+
{@const stop = gradient.stops[selected]}
|
|
387
|
+
{@const stopMono = stop.monochrome !== false}
|
|
388
|
+
<div class="stop-edit-row">
|
|
389
|
+
<span class="row-label">{isFlat ? 'Color' : `Stop ${selected + 1}`}</span>
|
|
390
|
+
{#if !isFlat}
|
|
391
|
+
<label class="pos-input">
|
|
392
|
+
<input
|
|
393
|
+
type="number"
|
|
394
|
+
min="0"
|
|
395
|
+
max="100"
|
|
396
|
+
step="0.1"
|
|
397
|
+
value={stop.position}
|
|
398
|
+
onchange={onPositionInput}
|
|
399
|
+
/>
|
|
400
|
+
<span class="suffix">%</span>
|
|
401
|
+
</label>
|
|
402
|
+
{/if}
|
|
403
|
+
<div class="picker-column">
|
|
404
|
+
<div class="picker-slot">
|
|
405
|
+
<GradientStopPicker
|
|
406
|
+
stopId={`${stopKeyPrefix}-${selected}`}
|
|
407
|
+
color={stop.color}
|
|
408
|
+
opacity={stop.opacity ?? 100}
|
|
409
|
+
familyFilter={stopMono ? familyFilter : null}
|
|
410
|
+
onchange={(payload) => handleStopChange(selected, payload)}
|
|
411
|
+
/>
|
|
412
|
+
</div>
|
|
413
|
+
{#if familyFilter !== null}
|
|
414
|
+
<label class="stop-mono-check">
|
|
415
|
+
<input
|
|
416
|
+
type="checkbox"
|
|
417
|
+
checked={stopMono}
|
|
418
|
+
onchange={(e) => handleMonoToggle(selected, (e.currentTarget as HTMLInputElement).checked)}
|
|
419
|
+
/>
|
|
420
|
+
<span>Monochrome</span>
|
|
421
|
+
</label>
|
|
422
|
+
{/if}
|
|
423
|
+
</div>
|
|
424
|
+
<span class="stop-value-text" title={stopValueLabel(stop)}>{stopValueLabel(stop)}</span>
|
|
425
|
+
</div>
|
|
426
|
+
{/if}
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
{#if onsave || oncancel}
|
|
430
|
+
<div class="footer-row">
|
|
431
|
+
<UIPillButton variant="secondary" size="compact" onclick={cancel}>Cancel</UIPillButton>
|
|
432
|
+
<UIPillButton variant="primary" size="compact" onclick={save}>Save</UIPillButton>
|
|
433
|
+
</div>
|
|
434
|
+
{/if}
|
|
435
|
+
</div>
|
|
436
|
+
{/if}
|
|
437
|
+
|
|
438
|
+
<style>
|
|
439
|
+
/* Header labels share grid tracks with the ribbon + pad below them. */
|
|
440
|
+
.gradient-editor {
|
|
441
|
+
display: grid;
|
|
442
|
+
grid-template-columns: minmax(0, 1fr);
|
|
443
|
+
row-gap: var(--ui-space-12);
|
|
444
|
+
width: 100%;
|
|
445
|
+
min-width: 0;
|
|
446
|
+
}
|
|
447
|
+
.gradient-editor.has-pad {
|
|
448
|
+
grid-template-columns: minmax(0, 1fr) max-content;
|
|
449
|
+
column-gap: var(--ui-space-16);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.editor-section-label {
|
|
453
|
+
font-size: var(--ui-font-size-md);
|
|
454
|
+
font-weight: 500;
|
|
455
|
+
color: var(--ui-text-primary);
|
|
456
|
+
line-height: 1;
|
|
457
|
+
}
|
|
458
|
+
.editor-section-left { grid-column: 1; }
|
|
459
|
+
.editor-section-right { grid-column: 2; }
|
|
460
|
+
|
|
461
|
+
/* Header doubles as a toolbar: label left, Add/Remove flush right. */
|
|
462
|
+
.editor-header {
|
|
463
|
+
display: flex;
|
|
464
|
+
align-items: center;
|
|
465
|
+
justify-content: space-between;
|
|
466
|
+
gap: var(--ui-space-12);
|
|
467
|
+
min-width: 0;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.ribbon-stack {
|
|
471
|
+
grid-column: 1;
|
|
472
|
+
display: flex;
|
|
473
|
+
flex-direction: column;
|
|
474
|
+
gap: var(--ui-space-8);
|
|
475
|
+
min-width: 0;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* min-height reserves the radial pad's full height so swapping types doesn't shift the lower row. */
|
|
479
|
+
.ribbon-pad {
|
|
480
|
+
grid-column: 2;
|
|
481
|
+
align-self: start;
|
|
482
|
+
min-height: 94px;
|
|
483
|
+
}
|
|
484
|
+
.ribbon-pad-linear {
|
|
485
|
+
display: flex;
|
|
486
|
+
justify-content: center;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.ribbon {
|
|
490
|
+
position: relative;
|
|
491
|
+
height: 3rem;
|
|
492
|
+
border-radius: var(--ui-radius-md);
|
|
493
|
+
border: 1px solid var(--ui-border-low);
|
|
494
|
+
cursor: copy;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* Flat ribbon: passive swatch, no click affordance. */
|
|
498
|
+
.ribbon.solid {
|
|
499
|
+
cursor: default;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* Radial: left half is a mirror, so suppress the copy cursor across the whole ribbon. */
|
|
503
|
+
.ribbon.radial {
|
|
504
|
+
cursor: default;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/* Marks the radial center (position 0). */
|
|
508
|
+
.center-divider {
|
|
509
|
+
position: absolute;
|
|
510
|
+
top: 0;
|
|
511
|
+
bottom: 0;
|
|
512
|
+
left: 50%;
|
|
513
|
+
width: 1px;
|
|
514
|
+
background: var(--ui-border);
|
|
515
|
+
pointer-events: none;
|
|
516
|
+
transform: translateX(-0.5px);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
.handles {
|
|
521
|
+
position: relative;
|
|
522
|
+
height: 1.25rem;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* 1.25rem hit target; visible marker is the inner .handle-diamond. */
|
|
526
|
+
.handle {
|
|
527
|
+
position: absolute;
|
|
528
|
+
top: 0;
|
|
529
|
+
width: 1.25rem;
|
|
530
|
+
height: 1.25rem;
|
|
531
|
+
margin-left: -0.625rem;
|
|
532
|
+
padding: 0;
|
|
533
|
+
background: transparent;
|
|
534
|
+
border: none;
|
|
535
|
+
cursor: ew-resize;
|
|
536
|
+
touch-action: none;
|
|
537
|
+
display: flex;
|
|
538
|
+
align-items: center;
|
|
539
|
+
justify-content: center;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.handle-diamond {
|
|
543
|
+
width: 0.7rem;
|
|
544
|
+
height: 0.7rem;
|
|
545
|
+
background: var(--stop-color, var(--ui-surface-high));
|
|
546
|
+
border: 1px solid var(--ui-border);
|
|
547
|
+
transform: rotate(45deg);
|
|
548
|
+
border-radius: 1px;
|
|
549
|
+
transition: border-color var(--ui-transition-fast), box-shadow var(--ui-transition-fast);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.handle:hover .handle-diamond {
|
|
553
|
+
border-color: var(--ui-text-secondary);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.handle.selected .handle-diamond {
|
|
557
|
+
border-color: var(--ui-text-primary);
|
|
558
|
+
box-shadow: 0 0 0 1px var(--ui-text-primary);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.handle.dragging {
|
|
562
|
+
z-index: 2;
|
|
563
|
+
}
|
|
564
|
+
.handle.dragging .handle-diamond {
|
|
565
|
+
border-color: var(--ui-text-primary);
|
|
566
|
+
box-shadow: 0 0 0 2px var(--ui-text-primary);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
/* Per-gradient controls under the ribbon: type selector + stop edit row. */
|
|
571
|
+
.lower-row {
|
|
572
|
+
grid-column: 1 / -1;
|
|
573
|
+
display: flex;
|
|
574
|
+
align-items: flex-start;
|
|
575
|
+
gap: var(--ui-space-12);
|
|
576
|
+
flex-wrap: wrap;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.stop-actions {
|
|
580
|
+
display: inline-flex;
|
|
581
|
+
align-items: center;
|
|
582
|
+
gap: var(--ui-space-6);
|
|
583
|
+
flex: 0 0 auto;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* Row 1: label / percent / picker / slug. Row 2: Monochrome under picker. */
|
|
587
|
+
.stop-edit-row {
|
|
588
|
+
display: flex;
|
|
589
|
+
flex-wrap: wrap;
|
|
590
|
+
align-items: flex-start;
|
|
591
|
+
gap: var(--ui-space-12);
|
|
592
|
+
flex: 1 1 18rem;
|
|
593
|
+
min-width: 0;
|
|
594
|
+
}
|
|
595
|
+
.stop-edit-row > .row-label,
|
|
596
|
+
.stop-edit-row > .pos-input,
|
|
597
|
+
.stop-edit-row > .stop-value-text {
|
|
598
|
+
line-height: 1.75rem;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.picker-column {
|
|
602
|
+
display: flex;
|
|
603
|
+
flex-direction: column;
|
|
604
|
+
align-items: flex-start;
|
|
605
|
+
gap: var(--ui-space-6);
|
|
606
|
+
flex: 0 0 auto;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/* Hide picker's built-in meta; we render the slug ourselves. */
|
|
610
|
+
.stop-edit-row :global(.ui-ts-meta-text) {
|
|
611
|
+
display: none;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* Stop slug, mirrors UITokenSelector meta typography. */
|
|
615
|
+
.stop-value-text {
|
|
616
|
+
flex: 1 1 0;
|
|
617
|
+
min-width: 0;
|
|
618
|
+
color: var(--ui-text-tertiary);
|
|
619
|
+
font-family: var(--ui-font-mono);
|
|
620
|
+
font-size: var(--ui-font-size-sm);
|
|
621
|
+
overflow: hidden;
|
|
622
|
+
text-overflow: ellipsis;
|
|
623
|
+
white-space: nowrap;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.stop-mono-check {
|
|
627
|
+
display: inline-flex;
|
|
628
|
+
align-items: center;
|
|
629
|
+
gap: var(--ui-space-6);
|
|
630
|
+
font-size: var(--ui-font-size-sm);
|
|
631
|
+
color: var(--ui-text-secondary);
|
|
632
|
+
cursor: pointer;
|
|
633
|
+
user-select: none;
|
|
634
|
+
flex: 0 0 auto;
|
|
635
|
+
}
|
|
636
|
+
.stop-mono-check:hover { color: var(--ui-text-primary); }
|
|
637
|
+
.stop-mono-check input { margin: 0; cursor: pointer; }
|
|
638
|
+
|
|
639
|
+
/* Peers the section label; 1.5rem left-pad aligns with the ribbon's content edge. */
|
|
640
|
+
.row-label {
|
|
641
|
+
font-size: var(--ui-font-size-md);
|
|
642
|
+
font-weight: 500;
|
|
643
|
+
color: var(--ui-text-primary);
|
|
644
|
+
line-height: 1;
|
|
645
|
+
padding-left: 1.5rem;
|
|
646
|
+
white-space: nowrap;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.pos-input {
|
|
650
|
+
display: inline-flex;
|
|
651
|
+
align-items: center;
|
|
652
|
+
gap: var(--ui-space-4);
|
|
653
|
+
font-size: var(--ui-font-size-xs);
|
|
654
|
+
color: var(--ui-text-secondary);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.pos-input input {
|
|
658
|
+
width: 2.25rem;
|
|
659
|
+
padding: var(--ui-space-2) var(--ui-space-6);
|
|
660
|
+
background: var(--ui-surface-lowest);
|
|
661
|
+
border: 1px solid var(--ui-border-low);
|
|
662
|
+
border-radius: var(--ui-radius-sm);
|
|
663
|
+
color: var(--ui-text-primary);
|
|
664
|
+
font-family: var(--ui-font-mono);
|
|
665
|
+
font-size: var(--ui-font-size-sm);
|
|
666
|
+
text-align: right;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.pos-input input::-webkit-outer-spin-button,
|
|
670
|
+
.pos-input input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
|
671
|
+
|
|
672
|
+
.suffix {
|
|
673
|
+
font-size: var(--ui-font-size-xs);
|
|
674
|
+
color: var(--ui-text-tertiary);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* 8rem matches the property-row token selector width (see TokenLayout). */
|
|
678
|
+
.picker-slot {
|
|
679
|
+
flex: 0 0 auto;
|
|
680
|
+
width: 8rem;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.footer-row {
|
|
684
|
+
grid-column: 1 / -1;
|
|
685
|
+
display: flex;
|
|
686
|
+
justify-content: flex-end;
|
|
687
|
+
gap: var(--ui-space-8);
|
|
688
|
+
padding-top: var(--ui-space-4);
|
|
689
|
+
}
|
|
690
|
+
</style>
|