@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
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { stopPropagation, createBubbler } from 'svelte/legacy';
|
|
3
3
|
|
|
4
4
|
const bubble = createBubbler();
|
|
5
|
-
import { onMount, onDestroy, tick } from 'svelte';
|
|
6
|
-
import { hexToOklch
|
|
7
|
-
import { type CurveAnchor,
|
|
5
|
+
import { onMount, onDestroy, tick, untrack } from 'svelte';
|
|
6
|
+
import { hexToOklch } from '../core/palettes/oklch';
|
|
7
|
+
import { type CurveAnchor, lightnessCurveConfig, saturationCurveConfig } from './curveEngine';
|
|
8
8
|
import ColorEditPanel from './ColorEditPanel.svelte';
|
|
9
9
|
import OverridesPanel from './palette/OverridesPanel.svelte';
|
|
10
|
+
import UIPillButton from './UIPillButton.svelte';
|
|
10
11
|
import GradientStopEditor from './palette/GradientStopEditor.svelte';
|
|
11
12
|
import ScaleCurveEditor from './palette/ScaleCurveEditor.svelte';
|
|
12
13
|
import PaletteBase from './palette/PaletteBase.svelte';
|
|
13
14
|
import { type EditingState, idleState, BASE_KEY, isEditingBase as isBaseEdit } from './palette/paletteEditorState';
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
import {
|
|
16
|
+
type Step, type Scale, type GrayStep, type CurveOffset, type ScaleCurves,
|
|
17
|
+
GRAY_FALLBACK, DEFAULT_TINT_CHROMA,
|
|
18
|
+
DEFAULT_PALETTE_LIGHTNESS, DEFAULT_PALETTE_SATURATION, DEFAULT_GRAY_LIGHTNESS, DEFAULT_GRAY_SATURATION,
|
|
19
|
+
defaultScaleCurves, defaultScaleCurvesObject,
|
|
20
|
+
paletteStepLightness, graySteps, scales,
|
|
21
|
+
paletteStepKey, grayStepKey, stepKey, scaleCurveKey as getScaleCurveKey,
|
|
22
|
+
stepIndexToX,
|
|
23
|
+
injectLockedAnchor, removeLockedAnchor,
|
|
24
|
+
computeGrayColor as computeGrayColorPure,
|
|
25
|
+
computePaletteColor as computePaletteColorPure,
|
|
26
|
+
computeDerivedColor as computeDerivedColorPure,
|
|
27
|
+
snapScaleToPalette as snapScaleToPalettePure,
|
|
28
|
+
} from './palette/paletteMath';
|
|
29
|
+
import type { PaletteConfig, GradientStop } from '../core/themes/themeTypes';
|
|
30
|
+
import { editorState, mutate, setPaletteConfig, beginSliderGesture, beginScope, commitScope, cancelScope, type Scope } from '../core/store/editorStore';
|
|
31
|
+
import { showCopyPopover } from './copyPopover';
|
|
22
32
|
|
|
23
33
|
interface Props {
|
|
24
34
|
label: string;
|
|
@@ -56,14 +66,6 @@
|
|
|
56
66
|
};
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
function defaultScaleCurvesObject() {
|
|
60
|
-
return {
|
|
61
|
-
Surfaces: { lightness: defaultScaleCurves.Surfaces.lightness(), saturation: defaultScaleCurves.Surfaces.saturation() },
|
|
62
|
-
Borders: { lightness: defaultScaleCurves.Borders.lightness(), saturation: defaultScaleCurves.Borders.saturation() },
|
|
63
|
-
Text: { lightness: defaultScaleCurves.Text.lightness(), saturation: defaultScaleCurves.Text.saturation() },
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
69
|
function edit<K extends keyof PaletteConfig>(field: K, value: PaletteConfig[K]): void {
|
|
68
70
|
mutate(`${label}: ${String(field)}`, (s) => {
|
|
69
71
|
if (!s.palettes[label]) s.palettes[label] = defaultPaletteConfig();
|
|
@@ -78,156 +80,64 @@
|
|
|
78
80
|
});
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
let lockedLightnessIdx: number | null = $derived.by(() => {
|
|
84
|
+
if (!anchorToBase) return null;
|
|
85
|
+
const x500 = stepIndexToX(4);
|
|
86
|
+
const idx = lightnessCurve.findIndex(a => Math.abs(a.x - x500) < 0.5);
|
|
87
|
+
return idx >= 0 ? idx : null;
|
|
88
|
+
});
|
|
89
|
+
let lockedSaturationIdx: number | null = $derived.by(() => {
|
|
90
|
+
if (!anchorToBase) return null;
|
|
91
|
+
const x500 = stepIndexToX(4);
|
|
92
|
+
const idx = saturationCurve.findIndex(a => Math.abs(a.x - x500) < 0.5);
|
|
93
|
+
return idx >= 0 ? idx : null;
|
|
94
|
+
});
|
|
84
95
|
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// scope so the inline header-swatch handlers and the function handlers
|
|
88
|
-
// share the same handle for commitScope/cancelScope.
|
|
96
|
+
// Held at component scope so inline header-swatch handlers and the function
|
|
97
|
+
// handlers share one handle for commit/cancel.
|
|
89
98
|
let paletteEditScope: Scope | null = null;
|
|
90
99
|
|
|
100
|
+
function openSession() {
|
|
101
|
+
paletteEditScope = beginScope({ label: 'palette session', collapseToOne: true, clipUndoFloor: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
91
104
|
function stopColor(stop: GradientStop, pc: typeof paletteComputed): string {
|
|
92
105
|
const ps = pc?.find(p => p.label === stop.paletteLabel);
|
|
93
106
|
return ps ? ps.effective : '#000000';
|
|
94
107
|
}
|
|
95
108
|
|
|
96
|
-
let gradientColorStops = $state('');
|
|
97
|
-
let gradientCssValue = $state('');
|
|
98
|
-
let gradientBarPreview = $state('');
|
|
99
|
-
|
|
100
109
|
function onEmptyModeChange(e: Event) {
|
|
101
110
|
edit('emptyMode', (e.currentTarget as HTMLInputElement).checked ? 'gradient' : 'solid');
|
|
102
111
|
}
|
|
103
112
|
|
|
104
|
-
// --- Gray mode ---
|
|
105
|
-
|
|
106
|
-
interface GrayStep {
|
|
107
|
-
label: string;
|
|
108
|
-
hue: number;
|
|
109
|
-
saturation: number;
|
|
110
|
-
lightness: number;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const graySteps: GrayStep[] = [
|
|
114
|
-
{ label: '100', hue: 240, saturation: 5, lightness: 92 },
|
|
115
|
-
{ label: '200', hue: 220, saturation: 13, lightness: 84 },
|
|
116
|
-
{ label: '300', hue: 216, saturation: 12, lightness: 72 },
|
|
117
|
-
{ label: '400', hue: 240, saturation: 5, lightness: 61 },
|
|
118
|
-
{ label: '500', hue: 240, saturation: 5, lightness: 50 },
|
|
119
|
-
{ label: '600', hue: 240, saturation: 5, lightness: 42 },
|
|
120
|
-
{ label: '700', hue: 240, saturation: 5, lightness: 34 },
|
|
121
|
-
{ label: '800', hue: 240, saturation: 10, lightness: 25 },
|
|
122
|
-
{ label: '850', hue: 229, saturation: 20, lightness: 18 },
|
|
123
|
-
{ label: '900', hue: 240, saturation: 30, lightness: 10 },
|
|
124
|
-
{ label: '950', hue: 229, saturation: 34, lightness: 3 },
|
|
125
|
-
];
|
|
126
|
-
|
|
127
113
|
let grayEditorOpen = $state(false);
|
|
128
114
|
let showDerived = $state(false);
|
|
129
|
-
|
|
130
|
-
// --- Palette curve editors (lightness + saturation) ---
|
|
131
115
|
let paletteEditorOpen = $state(false);
|
|
132
116
|
|
|
133
|
-
// Default curve anchors (used for initial state and reset)
|
|
134
|
-
const DEFAULT_PALETTE_LIGHTNESS = () => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
|
|
135
|
-
const DEFAULT_PALETTE_SATURATION = () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
|
|
136
|
-
const DEFAULT_GRAY_LIGHTNESS = () => [makeAnchor(0, 92, 5), makeAnchor(100, 3, 5)];
|
|
137
|
-
const DEFAULT_GRAY_SATURATION = () => [makeAnchor(0, 20, 30), makeAnchor(100, 20, 30)];
|
|
138
|
-
|
|
139
117
|
function setLightnessCurve(a: CurveAnchor[]) { edit('lightnessCurve', a); }
|
|
140
118
|
function setSaturationCurve(a: CurveAnchor[]) { edit('saturationCurve', a); }
|
|
141
119
|
function setGrayLightnessCurve(a: CurveAnchor[]) { edit('grayLightnessCurve', a); }
|
|
142
120
|
function setGraySaturationCurve(a: CurveAnchor[]) { edit('graySaturationCurve', a); }
|
|
143
121
|
|
|
144
|
-
// --- Curve offset + clipboard (shared across all curve editors) ---
|
|
145
|
-
|
|
146
122
|
function handleOffset(key: string, value: number) {
|
|
147
123
|
edit('curveOffset', { ...curveOffset, [key]: value });
|
|
148
124
|
}
|
|
149
125
|
|
|
150
|
-
// Gray step index to curve x-position
|
|
151
|
-
function grayStepToX(index: number): number {
|
|
152
|
-
return graySteps.length > 1 ? (index / (graySteps.length - 1)) * 100 : 50;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Base chroma for gray tinting (editable via the color panel's chroma slider)
|
|
156
|
-
const DEFAULT_TINT_CHROMA = 0.04;
|
|
157
|
-
|
|
158
|
-
// --- Editing-state machine (M4 fold) ---
|
|
159
|
-
//
|
|
160
|
-
// Single discriminated union replaces five independent `let` decls
|
|
161
|
-
// (`editingKey`, `editingSnapshot`, `editingDraft`, `snapshotTintHue`,
|
|
162
|
-
// `snapshotTintChroma`). The compatibility `$:` derivations below preserve
|
|
163
|
-
// existing read sites while writes go through `editing = { kind: ... }`.
|
|
164
126
|
let editing: EditingState = $state(idleState);
|
|
165
127
|
|
|
128
|
+
let injectedLightness = false;
|
|
129
|
+
let injectedSaturation = false;
|
|
166
130
|
|
|
167
131
|
function computeGrayColor(index: number, hue: number, chroma: number = tintChroma): string {
|
|
168
|
-
|
|
169
|
-
const lOff = curveOffset['gray-lightness'] ?? 0;
|
|
170
|
-
const sOff = curveOffset['gray-saturation'] ?? 0;
|
|
171
|
-
|
|
172
|
-
const targetL = Math.max(0, Math.min(100, sampleCurve(grayLightnessCurve, xPos) + lOff)) / 100;
|
|
173
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(graySaturationCurve, xPos) + sOff) / 100));
|
|
174
|
-
const targetC = chroma * satMul;
|
|
175
|
-
|
|
176
|
-
const clamped = gamutClamp(targetL, targetC, hue);
|
|
177
|
-
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function grayStepKey(label: string): string {
|
|
181
|
-
return `gray-${label}`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// --- Chromatic palette steps ---
|
|
189
|
-
|
|
190
|
-
const paletteStepLightness = [
|
|
191
|
-
{ label: '100', lightness: 95 },
|
|
192
|
-
{ label: '200', lightness: 88 },
|
|
193
|
-
{ label: '300', lightness: 78 },
|
|
194
|
-
{ label: '400', lightness: 68 },
|
|
195
|
-
{ label: '500', lightness: 57 },
|
|
196
|
-
{ label: '600', lightness: 49 },
|
|
197
|
-
{ label: '700', lightness: 41 },
|
|
198
|
-
{ label: '800', lightness: 32 },
|
|
199
|
-
{ label: '850', lightness: 25 },
|
|
200
|
-
{ label: '900', lightness: 17 },
|
|
201
|
-
{ label: '950', lightness: 8 },
|
|
202
|
-
];
|
|
203
|
-
|
|
204
|
-
function paletteStepKey(label: string): string {
|
|
205
|
-
return `Palette-${label}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function stepIndexToX(index: number): number {
|
|
209
|
-
return (index / (paletteStepLightness.length - 1)) * 100;
|
|
132
|
+
return computeGrayColorPure(index, hue, chroma, grayLightnessCurve, graySaturationCurve, curveOffset);
|
|
210
133
|
}
|
|
211
134
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
let injectedLightness = false;
|
|
215
|
-
let injectedSaturation = false;
|
|
216
|
-
|
|
217
|
-
function injectLockedAnchor(curve: CurveAnchor[], x: number, y: number): { curve: CurveAnchor[], idx: number, injected: boolean } {
|
|
218
|
-
const existing = curve.findIndex(a => Math.abs(a.x - x) < 0.5);
|
|
219
|
-
if (existing >= 0) {
|
|
220
|
-
if (curve[existing].x === x && Math.abs(curve[existing].y - y) < 0.01) return { curve, idx: existing, injected: false };
|
|
221
|
-
return { curve: curve.map((a, i) => i === existing ? { ...a, x, y } : a), idx: existing, injected: false };
|
|
222
|
-
}
|
|
223
|
-
let insertAt = curve.findIndex(a => a.x > x);
|
|
224
|
-
if (insertAt < 0) insertAt = curve.length;
|
|
225
|
-
return { curve: [...curve.slice(0, insertAt), makeAnchor(x, y, 15), ...curve.slice(insertAt)], idx: insertAt, injected: true };
|
|
135
|
+
function computePaletteColor(index: number, base: string): string {
|
|
136
|
+
return computePaletteColorPure(index, base, lightnessCurve, saturationCurve, curveOffset);
|
|
226
137
|
}
|
|
227
138
|
|
|
228
|
-
function
|
|
229
|
-
|
|
230
|
-
return curve.filter((_, i) => i !== idx);
|
|
139
|
+
function computeDerivedColor(step: Step, base: string, scaleTitle: string): string {
|
|
140
|
+
return computeDerivedColorPure(step, base, scaleTitle, scaleCurves, curveOffset);
|
|
231
141
|
}
|
|
232
142
|
|
|
233
143
|
/**
|
|
@@ -268,26 +178,12 @@
|
|
|
268
178
|
|
|
269
179
|
|
|
270
180
|
|
|
271
|
-
function computePaletteColor(index: number, base: string): string {
|
|
272
|
-
const { c: baseC, h } = hexToOklch(base);
|
|
273
|
-
const xPos = stepIndexToX(index);
|
|
274
|
-
|
|
275
|
-
const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset['lightness'] ?? 0))) / 100;
|
|
276
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset['saturation'] ?? 0)) / 100));
|
|
277
|
-
const targetC = baseC * satMul;
|
|
278
|
-
|
|
279
|
-
const clamped = gamutClamp(targetL, targetC, h);
|
|
280
|
-
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
181
|
function startBaseEdit() {
|
|
286
182
|
if (editing.kind === 'editingBase') { confirmEdit(); return; }
|
|
287
183
|
editing = mode === 'gray'
|
|
288
184
|
? { kind: 'editingBase', snapshotHex: gray500Hex, snapshotTintHue: tintHue, snapshotTintChroma: tintChroma }
|
|
289
185
|
: { kind: 'editingBase', snapshotHex: baseColor, snapshotTintHue: null, snapshotTintChroma: null };
|
|
290
|
-
|
|
186
|
+
openSession();
|
|
291
187
|
}
|
|
292
188
|
|
|
293
189
|
function handlePaletteClick(ps: { label: string; lightness: number; index: number }) {
|
|
@@ -298,80 +194,9 @@
|
|
|
298
194
|
}
|
|
299
195
|
const current = (k in overrides) ? overrides[k] : computePaletteColor(ps.index, baseColor);
|
|
300
196
|
editing = { kind: 'editingStep', stepKey: k, snapshot: current, draft: current };
|
|
301
|
-
|
|
197
|
+
openSession();
|
|
302
198
|
}
|
|
303
199
|
|
|
304
|
-
// --- Scale types ---
|
|
305
|
-
|
|
306
|
-
interface Step {
|
|
307
|
-
name: string;
|
|
308
|
-
position: number;
|
|
309
|
-
lightness?: number;
|
|
310
|
-
saturation?: number;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
interface Scale {
|
|
314
|
-
title: string;
|
|
315
|
-
isText: boolean;
|
|
316
|
-
steps: Step[];
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const scales: Scale[] = [
|
|
320
|
-
{
|
|
321
|
-
title: 'Surfaces',
|
|
322
|
-
isText: false,
|
|
323
|
-
steps: [
|
|
324
|
-
{ name: 'lowest', position: -1 },
|
|
325
|
-
{ name: 'lower', position: -2/3 },
|
|
326
|
-
{ name: 'low', position: -1/3 },
|
|
327
|
-
{ name: 'default', position: 0 },
|
|
328
|
-
{ name: 'high', position: 1/3 },
|
|
329
|
-
{ name: 'higher', position: 2/3 },
|
|
330
|
-
{ name: 'highest', position: 1 },
|
|
331
|
-
]
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
title: 'Borders',
|
|
335
|
-
isText: false,
|
|
336
|
-
steps: [
|
|
337
|
-
{ name: 'faint', position: -1 },
|
|
338
|
-
{ name: 'subtle', position: -0.5 },
|
|
339
|
-
{ name: 'default', position: 0 },
|
|
340
|
-
{ name: 'medium', position: 0.5 },
|
|
341
|
-
{ name: 'strong', position: 1 },
|
|
342
|
-
]
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
title: 'Text',
|
|
346
|
-
isText: true,
|
|
347
|
-
steps: [
|
|
348
|
-
{ name: 'primary', position: 0 },
|
|
349
|
-
{ name: 'secondary', position: 0 },
|
|
350
|
-
{ name: 'tertiary', position: 0 },
|
|
351
|
-
{ name: 'muted', position: 0 },
|
|
352
|
-
{ name: 'disabled', position: 0 },
|
|
353
|
-
]
|
|
354
|
-
}
|
|
355
|
-
];
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// --- Per-scale curve state (Surfaces & Borders) ---
|
|
359
|
-
|
|
360
|
-
const defaultScaleCurves: Record<string, { lightness: () => CurveAnchor[]; saturation: () => CurveAnchor[] }> = {
|
|
361
|
-
Surfaces: {
|
|
362
|
-
lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
|
|
363
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)],
|
|
364
|
-
},
|
|
365
|
-
Borders: {
|
|
366
|
-
lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
|
|
367
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)],
|
|
368
|
-
},
|
|
369
|
-
Text: {
|
|
370
|
-
lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
|
|
371
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)],
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
|
|
375
200
|
let scaleEditorOpen: Record<string, boolean> = $state({ Surfaces: false, Borders: false, Text: false });
|
|
376
201
|
|
|
377
202
|
function toggleScaleEditor(title: string) {
|
|
@@ -384,77 +209,9 @@
|
|
|
384
209
|
edit('scaleCurves', { ...scaleCurves, [title]: { ...cur, [channel]: a } });
|
|
385
210
|
}
|
|
386
211
|
|
|
387
|
-
function getScaleCurveKey(scaleTitle: string, channel: 'lightness' | 'saturation'): string {
|
|
388
|
-
return `${scaleTitle}-${channel}`;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
interface ScaleConfig {
|
|
392
|
-
lightnessLow: number;
|
|
393
|
-
lightnessHigh: number;
|
|
394
|
-
saturation: number;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function configForScale(title: string): ScaleConfig {
|
|
398
|
-
return { lightnessLow: 0, lightnessHigh: 100, saturation: 100 };
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
function stepKey(scaleTitle: string, stepName: string): string {
|
|
403
|
-
return `${scaleTitle}-${stepName}`;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
function derivedHex(step: Step, base: string, scaleTitle: string, _version?: string): string {
|
|
408
|
-
return computeDerivedColor(step, base, configForScale(scaleTitle), scaleTitle);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
// --- Compute derived color via OKLCH ---
|
|
418
|
-
|
|
419
|
-
function scaleStepToX(step: Step, scale: Scale): number {
|
|
420
|
-
const idx = scale.steps.indexOf(step);
|
|
421
|
-
return scale.steps.length > 1 ? (idx / (scale.steps.length - 1)) * 100 : 50;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function computeDerivedColor(step: Step, base: string, config: ScaleConfig, scaleTitle: string): string {
|
|
425
|
-
const { l: baseL, c: baseC, h: baseH } = hexToOklch(base);
|
|
426
|
-
const scale = scales.find(s => s.title === scaleTitle)!;
|
|
427
|
-
const xPos = scaleStepToX(step, scale);
|
|
428
|
-
|
|
429
|
-
const lCurve = scaleCurves[scaleTitle]?.lightness ?? [];
|
|
430
|
-
const sCurve = scaleCurves[scaleTitle]?.saturation ?? [];
|
|
431
|
-
const lKey = getScaleCurveKey(scaleTitle, 'lightness');
|
|
432
|
-
const sKey = getScaleCurveKey(scaleTitle, 'saturation');
|
|
433
|
-
const lOff = curveOffset[lKey] ?? 0;
|
|
434
|
-
const sOff = curveOffset[sKey] ?? 0;
|
|
435
|
-
|
|
436
|
-
let targetL: number;
|
|
437
|
-
if (scale.isText) {
|
|
438
|
-
// Text: lightness curve is a multiplier (100 = 1x base lightness)
|
|
439
|
-
const lMul = Math.max(0, Math.min(2, (sampleCurve(lCurve, xPos) + lOff) / 100));
|
|
440
|
-
targetL = Math.max(0, Math.min(1, baseL * lMul));
|
|
441
|
-
} else {
|
|
442
|
-
targetL = Math.max(0, Math.min(100, sampleCurve(lCurve, xPos) + lOff)) / 100;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
|
|
446
|
-
const targetC = baseC * satMul;
|
|
447
|
-
|
|
448
|
-
const clamped = gamutClamp(targetL, targetC, baseH);
|
|
449
|
-
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// --- Interaction handlers ---
|
|
453
|
-
|
|
454
212
|
function handleColorChange(hex: string) {
|
|
455
213
|
if (isEditingBase) {
|
|
456
|
-
// Gray mode's base is derived from tintHue/tintChroma
|
|
457
|
-
// writes those). The raw hex path only applies to chromatic palettes.
|
|
214
|
+
// Gray mode's base is derived from tintHue/tintChroma; raw hex only applies to chromatic.
|
|
458
215
|
if (mode === 'chromatic') edit('baseColor', hex);
|
|
459
216
|
return;
|
|
460
217
|
}
|
|
@@ -477,9 +234,9 @@
|
|
|
477
234
|
confirmEdit();
|
|
478
235
|
return;
|
|
479
236
|
}
|
|
480
|
-
const current = (k in overrides) ? overrides[k] : computeDerivedColor(step, gray500Hex,
|
|
237
|
+
const current = (k in overrides) ? overrides[k] : computeDerivedColor(step, gray500Hex, scaleTitle);
|
|
481
238
|
editing = { kind: 'editingStep', stepKey: k, snapshot: current, draft: current };
|
|
482
|
-
|
|
239
|
+
openSession();
|
|
483
240
|
}
|
|
484
241
|
|
|
485
242
|
function handleGrayClick(gStep: GrayStep, index: number) {
|
|
@@ -490,13 +247,13 @@
|
|
|
490
247
|
}
|
|
491
248
|
const current = (k in overrides) ? overrides[k] : computeGrayColor(index, tintHue, tintChroma);
|
|
492
249
|
editing = { kind: 'editingStep', stepKey: k, snapshot: current, draft: current };
|
|
493
|
-
|
|
250
|
+
openSession();
|
|
494
251
|
}
|
|
495
252
|
|
|
496
253
|
async function confirmEdit() {
|
|
497
254
|
if (editingKey) {
|
|
498
|
-
// Accumulate
|
|
499
|
-
//
|
|
255
|
+
// Accumulate override changes into one patch so the session commit sees
|
|
256
|
+
// a single final state (no intermediate reactive round-trips).
|
|
500
257
|
let nextOverrides = { ...overrides };
|
|
501
258
|
if (editingDraft !== null) {
|
|
502
259
|
const computed = computedValueForKey(editingKey);
|
|
@@ -525,8 +282,7 @@
|
|
|
525
282
|
|
|
526
283
|
function cancelEdit() {
|
|
527
284
|
editing = idleState;
|
|
528
|
-
// Restoring the session snapshot
|
|
529
|
-
// which pulls baseColor/tintHue/tintChroma/overrides/… back to pre-open.
|
|
285
|
+
// Restoring the session snapshot pulls baseColor/tintHue/overrides/… back to pre-open.
|
|
530
286
|
if (paletteEditScope) { cancelScope(paletteEditScope); paletteEditScope = null; }
|
|
531
287
|
}
|
|
532
288
|
|
|
@@ -547,7 +303,7 @@
|
|
|
547
303
|
for (const scale of scales) {
|
|
548
304
|
for (const step of scale.steps) {
|
|
549
305
|
if (stepKey(scale.title, step.name) === key) {
|
|
550
|
-
return computeDerivedColor(step, gray500Hex,
|
|
306
|
+
return computeDerivedColor(step, gray500Hex, scale.title);
|
|
551
307
|
}
|
|
552
308
|
}
|
|
553
309
|
}
|
|
@@ -556,33 +312,7 @@
|
|
|
556
312
|
|
|
557
313
|
function effectiveColor(k: string, step: Step, scaleTitle: string, _version?: string): string {
|
|
558
314
|
if (editingKey === k && editingDraft !== null) return editingDraft;
|
|
559
|
-
return (k in overrides) ? overrides[k] : computeDerivedColor(step, gray500Hex,
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// --- Brightness/Saturation gradient helpers for scale editor ---
|
|
563
|
-
|
|
564
|
-
function lightnessGrad(base: string): string {
|
|
565
|
-
const { c, h } = hexToOklch(base);
|
|
566
|
-
const points: string[] = [];
|
|
567
|
-
for (let i = 0; i <= 8; i++) {
|
|
568
|
-
const l = i / 8;
|
|
569
|
-
const clamped = gamutClamp(l, c, h);
|
|
570
|
-
points.push(`${oklchToHex(clamped.l, clamped.c, clamped.h)} ${Math.round((i / 8) * 100)}%`);
|
|
571
|
-
}
|
|
572
|
-
return `linear-gradient(to right, ${points.join(', ')})`;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function saturationGrad(base: string): string {
|
|
576
|
-
const { l, c, h } = hexToOklch(base);
|
|
577
|
-
const midL = Math.max(0.3, Math.min(0.7, l));
|
|
578
|
-
const points: string[] = [];
|
|
579
|
-
for (let i = 0; i <= 8; i++) {
|
|
580
|
-
const scale = (i / 8) * 2;
|
|
581
|
-
const targetC = c * scale;
|
|
582
|
-
const clamped = gamutClamp(midL, targetC, h);
|
|
583
|
-
points.push(`${oklchToHex(clamped.l, clamped.c, clamped.h)} ${Math.round((i / 8) * 100)}%`);
|
|
584
|
-
}
|
|
585
|
-
return `linear-gradient(to right, ${points.join(', ')})`;
|
|
315
|
+
return (k in overrides) ? overrides[k] : computeDerivedColor(step, gray500Hex, scaleTitle);
|
|
586
316
|
}
|
|
587
317
|
|
|
588
318
|
let copiedKey: string | null = $state(null);
|
|
@@ -601,42 +331,8 @@
|
|
|
601
331
|
setTimeout(() => { copiedLabelKey = null; }, 1500);
|
|
602
332
|
}
|
|
603
333
|
|
|
604
|
-
// --- Snap-all: constrain an entire scale to unique palette steps ---
|
|
605
|
-
|
|
606
334
|
function snapScaleToPalette(scale: Scale): Record<string, string> {
|
|
607
|
-
|
|
608
|
-
const n = scale.steps.length;
|
|
609
|
-
|
|
610
|
-
const stepL = scale.steps.map(step => {
|
|
611
|
-
const derived = computeDerivedColor(step, baseColor, cfg, scale.title);
|
|
612
|
-
return hexToOklch(derived).l;
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
const palL = paletteComputed.map(ps => hexToOklch(ps.hex).l);
|
|
616
|
-
|
|
617
|
-
const palDarkFirst = [...paletteComputed].reverse();
|
|
618
|
-
const palLDarkFirst = [...palL].reverse();
|
|
619
|
-
|
|
620
|
-
let bestStart = 0;
|
|
621
|
-
let bestCost = Infinity;
|
|
622
|
-
for (let start = 0; start <= palDarkFirst.length - n; start++) {
|
|
623
|
-
let cost = 0;
|
|
624
|
-
for (let i = 0; i < n; i++) {
|
|
625
|
-
const d = stepL[i] - palLDarkFirst[start + i];
|
|
626
|
-
cost += d * d;
|
|
627
|
-
}
|
|
628
|
-
if (cost < bestCost) {
|
|
629
|
-
bestCost = cost;
|
|
630
|
-
bestStart = start;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const assigned: Record<string, string> = {};
|
|
635
|
-
for (let i = 0; i < n; i++) {
|
|
636
|
-
const k = stepKey(scale.title, scale.steps[i].name);
|
|
637
|
-
assigned[k] = palDarkFirst[bestStart + i].hex;
|
|
638
|
-
}
|
|
639
|
-
return assigned;
|
|
335
|
+
return snapScaleToPalettePure(scale, baseColor, scaleCurves, curveOffset, paletteComputed);
|
|
640
336
|
}
|
|
641
337
|
|
|
642
338
|
function toggleSnapAll(scale: Scale) {
|
|
@@ -668,10 +364,6 @@
|
|
|
668
364
|
edit('overrides', next);
|
|
669
365
|
}
|
|
670
366
|
|
|
671
|
-
function scaleHasOverrides(scale: Scale): boolean {
|
|
672
|
-
return scale.steps.some(s => stepKey(scale.title, s.name) in overrides);
|
|
673
|
-
}
|
|
674
|
-
|
|
675
367
|
function clearScaleOverrides(scale: Scale) {
|
|
676
368
|
snapPickerKey = null;
|
|
677
369
|
const nextOverrides = { ...overrides };
|
|
@@ -726,63 +418,38 @@
|
|
|
726
418
|
if (changed) edit('overrides', next);
|
|
727
419
|
}
|
|
728
420
|
|
|
729
|
-
|
|
730
|
-
// CSS-var emission lives in `paletteDerivation` → `editorRenderer`; the store
|
|
731
|
-
// is the single source of truth for palette config and the renderer
|
|
732
|
-
// subscription writes the derived `--color-*` / `--surface-*` / `--border-*`
|
|
733
|
-
// / `--text-*` / `--page-bg` variables to :root.
|
|
734
|
-
|
|
735
|
-
// --- Load external config ---
|
|
736
|
-
//
|
|
737
|
-
// External file loads come through editorStore.loadFromFile, which
|
|
738
|
-
// overwrites $editorState.palettes — no component-side mirroring needed.
|
|
739
|
-
// This export is kept only for callers that want to push a config in
|
|
740
|
-
// directly.
|
|
741
421
|
export function loadConfig(config: PaletteConfig) {
|
|
742
422
|
setPaletteConfig(label, config);
|
|
743
423
|
}
|
|
744
424
|
|
|
745
|
-
|
|
746
|
-
//
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
let
|
|
756
|
-
let
|
|
757
|
-
let
|
|
758
|
-
let
|
|
759
|
-
let
|
|
760
|
-
let
|
|
761
|
-
let
|
|
762
|
-
let
|
|
763
|
-
let
|
|
764
|
-
let
|
|
765
|
-
let
|
|
766
|
-
let
|
|
767
|
-
let anchorToBase = $derived(paletteConfig?.anchorToBase ?? true);
|
|
768
|
-
let emptyMode = $derived(paletteConfig?.emptyMode ?? 'solid');
|
|
769
|
-
let emptyStep = $derived(paletteConfig?.emptyStep ?? '850');
|
|
770
|
-
let gradientStyle = $derived(paletteConfig?.gradientStyle ?? 'linear');
|
|
771
|
-
let gradientAngle = $derived(paletteConfig?.gradientAngle ?? 180);
|
|
772
|
-
let gradientReverse = $derived(paletteConfig?.gradientReverse ?? false);
|
|
773
|
-
let gradientStops = $derived(paletteConfig?.gradientStops ?? [
|
|
425
|
+
// Each field reads `$editorState.palettes[label]` directly. A chained
|
|
426
|
+
// `$derived` of the whole config would return the same object reference on
|
|
427
|
+
// every mutate-in-place store update (Svelte 5 uses `===` equality) and
|
|
428
|
+
// short-circuit the downstream chain — swatches would freeze.
|
|
429
|
+
let baseColor = $derived($editorState.palettes[label]?.baseColor ?? initialColor);
|
|
430
|
+
let tintHue = $derived($editorState.palettes[label]?.tintHue ?? 240);
|
|
431
|
+
let tintChroma = $derived($editorState.palettes[label]?.tintChroma ?? DEFAULT_TINT_CHROMA);
|
|
432
|
+
let lightnessCurve = $derived($editorState.palettes[label]?.lightnessCurve ?? DEFAULT_PALETTE_LIGHTNESS());
|
|
433
|
+
let saturationCurve = $derived($editorState.palettes[label]?.saturationCurve ?? DEFAULT_PALETTE_SATURATION());
|
|
434
|
+
let grayLightnessCurve = $derived($editorState.palettes[label]?.grayLightnessCurve ?? DEFAULT_GRAY_LIGHTNESS());
|
|
435
|
+
let graySaturationCurve = $derived($editorState.palettes[label]?.graySaturationCurve ?? DEFAULT_GRAY_SATURATION());
|
|
436
|
+
let scaleCurves = $derived($editorState.palettes[label]?.scaleCurves ?? defaultScaleCurvesObject());
|
|
437
|
+
let curveOffset = $derived($editorState.palettes[label]?.curveOffset ?? { lightness: 0, saturation: 0 });
|
|
438
|
+
let overrides = $derived($editorState.palettes[label]?.overrides ?? {});
|
|
439
|
+
let snappedScales = $derived(new Set($editorState.palettes[label]?.snappedScales ?? []));
|
|
440
|
+
let anchorToBase = $derived($editorState.palettes[label]?.anchorToBase ?? true);
|
|
441
|
+
let emptyMode = $derived($editorState.palettes[label]?.emptyMode ?? 'solid');
|
|
442
|
+
let emptyStep = $derived($editorState.palettes[label]?.emptyStep ?? '850');
|
|
443
|
+
let gradientStyle = $derived($editorState.palettes[label]?.gradientStyle ?? 'linear');
|
|
444
|
+
let gradientAngle = $derived($editorState.palettes[label]?.gradientAngle ?? 180);
|
|
445
|
+
let gradientReverse = $derived($editorState.palettes[label]?.gradientReverse ?? false);
|
|
446
|
+
let gradientStops = $derived($editorState.palettes[label]?.gradientStops ?? [
|
|
774
447
|
{ position: 0, paletteLabel: '800' },
|
|
775
448
|
{ position: 100, paletteLabel: '950' },
|
|
776
449
|
]);
|
|
777
|
-
let gradientSize = $derived(
|
|
778
|
-
// Read-side compat: existing `editingKey === ...` etc. comparisons keep
|
|
779
|
-
// working. New code should narrow on `editing.kind` directly.
|
|
450
|
+
let gradientSize = $derived($editorState.palettes[label]?.gradientSize ?? 'page');
|
|
780
451
|
let editingKey = $derived(editing.kind === 'idle' ? null : editing.kind === 'editingBase' ? BASE_KEY : editing.stepKey);
|
|
781
452
|
let editingDraft = $derived(editing.kind === 'editingStep' ? editing.draft : null);
|
|
782
|
-
let editingSnapshot = $derived(editing.kind === 'idle' ? null : editing.kind === 'editingBase' ? editing.snapshotHex : editing.snapshot);
|
|
783
|
-
let snapshotTintHue = $derived(editing.kind === 'editingBase' ? editing.snapshotTintHue : null);
|
|
784
|
-
let snapshotTintChroma = $derived(editing.kind === 'editingBase' ? editing.snapshotTintChroma : null);
|
|
785
|
-
// Reactive map of computed gray colors
|
|
786
453
|
let grayComputed = $derived((() => {
|
|
787
454
|
const _gl = grayLightnessCurve, _gs = graySaturationCurve, _co = curveOffset, _tc = tintChroma, _th = tintHue;
|
|
788
455
|
return graySteps.map((step, index) => ({
|
|
@@ -799,39 +466,26 @@
|
|
|
799
466
|
effective: (_ek === g.key && _ed !== null) ? _ed : (g.key in _ov) ? _ov[g.key] : g.hex,
|
|
800
467
|
}));
|
|
801
468
|
})());
|
|
802
|
-
//
|
|
803
|
-
//
|
|
469
|
+
// Always use the computed (curve-derived) value so derived scales update in
|
|
470
|
+
// realtime when tint changes.
|
|
804
471
|
let gray500Hex = $derived(mode === 'gray'
|
|
805
472
|
? (grayComputed.find(g => g.step.label === '500')?.hex ?? GRAY_FALLBACK)
|
|
806
473
|
: baseColor);
|
|
807
|
-
//
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* Keep the locked lightness anchor y in sync with baseColor. Idempotent —
|
|
822
|
-
* only writes when the curve's anchor y differs from the baseColor-derived
|
|
823
|
-
* target. During a baseColor drag (inside a slider transaction) this
|
|
824
|
-
* additional curve edit merges into the same history entry. On undo/redo
|
|
825
|
-
* the curve already has the correct y (they're saved together), so this
|
|
826
|
-
* is a no-op.
|
|
827
|
-
*/
|
|
828
|
-
run(() => {
|
|
829
|
-
if (anchorToBase && lockedLightnessIdx !== null && baseColor) {
|
|
830
|
-
const targetY = hexToOklch(baseColor).l * 100;
|
|
831
|
-
if (lightnessCurve[lockedLightnessIdx] && Math.abs(lightnessCurve[lockedLightnessIdx].y - targetY) > 0.01) {
|
|
832
|
-
edit('lightnessCurve', lightnessCurve.map((a, i) => i === lockedLightnessIdx ? { ...a, y: targetY } : a));
|
|
474
|
+
// Keep the locked lightness anchor y in sync with baseColor. Idempotent.
|
|
475
|
+
// `lightnessCurve` is read via `untrack` so writing it back via `edit` does
|
|
476
|
+
// not retrigger this effect (Svelte 5 flags the read+write pattern as
|
|
477
|
+
// recursive).
|
|
478
|
+
$effect(() => {
|
|
479
|
+
if (!anchorToBase || lockedLightnessIdx === null || !baseColor) return;
|
|
480
|
+
const targetY = hexToOklch(baseColor).l * 100;
|
|
481
|
+
untrack(() => {
|
|
482
|
+
const idx = lockedLightnessIdx;
|
|
483
|
+
if (idx === null) return;
|
|
484
|
+
const curve = lightnessCurve;
|
|
485
|
+
if (curve[idx] && Math.abs(curve[idx].y - targetY) > 0.01) {
|
|
486
|
+
edit('lightnessCurve', curve.map((a, i) => i === idx ? { ...a, y: targetY } : a));
|
|
833
487
|
}
|
|
834
|
-
}
|
|
488
|
+
});
|
|
835
489
|
});
|
|
836
490
|
let paletteComputed = $derived((() => {
|
|
837
491
|
const _bc = baseColor, _lc = lightnessCurve, _sc = saturationCurve, _co = curveOffset, _ed = editingDraft, _ek = editingKey, _ov = overrides, _ab = anchorToBase;
|
|
@@ -849,43 +503,19 @@
|
|
|
849
503
|
};
|
|
850
504
|
});
|
|
851
505
|
})());
|
|
852
|
-
|
|
853
|
-
run(() => {
|
|
506
|
+
let gradientBarPreview = $derived.by(() => {
|
|
854
507
|
const pc = paletteComputed;
|
|
855
508
|
const sorted = [...gradientStops].sort((a, b) => gradientReverse ? b.position - a.position : a.position - b.position);
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
if (emptySelector && emptyMode === 'gradient') {
|
|
859
|
-
switch (gradientStyle) {
|
|
860
|
-
case 'radial': gradientCssValue = `radial-gradient(circle, ${gradientColorStops})`; break;
|
|
861
|
-
case 'conic': gradientCssValue = `conic-gradient(from ${gradientAngle}deg, ${gradientColorStops})`; break;
|
|
862
|
-
default: gradientCssValue = `linear-gradient(${gradientAngle}deg, ${gradientColorStops})`;
|
|
863
|
-
}
|
|
864
|
-
} else {
|
|
865
|
-
gradientCssValue = '';
|
|
866
|
-
}
|
|
509
|
+
const stops = sorted.map(s => `${stopColor(s, pc)} ${s.position}%`).join(', ');
|
|
510
|
+
return `linear-gradient(to right, ${stops})`;
|
|
867
511
|
});
|
|
868
|
-
|
|
869
|
-
let grayScales = $derived(mode === 'gray' ? scales.filter(scale => {
|
|
870
|
-
if (scale.title === 'Surfaces') return true;
|
|
871
|
-
if (scale.title === 'Borders') return true;
|
|
872
|
-
if (scale.title === 'Text') return true;
|
|
873
|
-
return false;
|
|
874
|
-
}) : []);
|
|
512
|
+
let grayScales = $derived(mode === 'gray' ? scales : []);
|
|
875
513
|
let curveVersion = $derived(JSON.stringify(scaleCurves) + JSON.stringify(curveOffset) + gray500Hex);
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
*
|
|
881
|
-
* Note: `effectiveColor` itself always uses `gray500Hex` for non-override
|
|
882
|
-
* derivation (pre-existing); the chromatic vs gray distinction here is
|
|
883
|
-
* only for the `derivedHex` (the "Ag" preview / border-color base).
|
|
884
|
-
*/
|
|
885
|
-
let derivedHexForBase = $derived((step: Step, scaleTitle: string) => derivedHex(step, baseColor, scaleTitle, curveVersion));
|
|
886
|
-
let derivedHexForGray = $derived((step: Step, scaleTitle: string) => derivedHex(step, gray500Hex, scaleTitle, curveVersion));
|
|
514
|
+
// Chromatic vs gray distinction is only for the `derivedHex` ("Ag" preview /
|
|
515
|
+
// border-color base); `effectiveColor` always uses `gray500Hex` for non-override derivation.
|
|
516
|
+
let derivedHexForBase = $derived((step: Step, scaleTitle: string) => computeDerivedColor(step, baseColor, scaleTitle));
|
|
517
|
+
let derivedHexForGray = $derived((step: Step, scaleTitle: string) => computeDerivedColor(step, gray500Hex, scaleTitle));
|
|
887
518
|
let effectiveHexAny = $derived((k: string, step: Step, scaleTitle: string) => effectiveColor(k, step, scaleTitle, curveVersion));
|
|
888
|
-
// --- Reactive editing state ---
|
|
889
519
|
|
|
890
520
|
let isEditingBase = $derived(isBaseEdit(editing));
|
|
891
521
|
let editingColor = $derived(isEditingBase
|
|
@@ -915,8 +545,7 @@
|
|
|
915
545
|
? `${editingStepInfo.scale} \u203A ${editingStepInfo.step}`
|
|
916
546
|
: null);
|
|
917
547
|
$effect(() => {
|
|
918
|
-
//
|
|
919
|
-
// tracks them explicitly (resnapScales() reads them indirectly).
|
|
548
|
+
// Touch each input so the effect tracks them (resnapScales reads indirectly).
|
|
920
549
|
void baseColor;
|
|
921
550
|
void scaleCurves;
|
|
922
551
|
void lightnessCurve;
|
|
@@ -967,12 +596,10 @@
|
|
|
967
596
|
<span>Gradient</span>
|
|
968
597
|
</label>
|
|
969
598
|
{/if}
|
|
970
|
-
<
|
|
971
|
-
<
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
onclick={() => paletteEditorOpen = !paletteEditorOpen}
|
|
975
|
-
>{paletteEditorOpen ? 'Close' : 'Edit'}</button>
|
|
599
|
+
<UIPillButton size="compact" variant="outline" onclick={clearPaletteOverrides}>Clear Overrides</UIPillButton>
|
|
600
|
+
<UIPillButton size="compact" variant="outline" onclick={() => paletteEditorOpen = !paletteEditorOpen}>
|
|
601
|
+
{paletteEditorOpen ? 'Close' : 'Edit'}
|
|
602
|
+
</UIPillButton>
|
|
976
603
|
</div>
|
|
977
604
|
<div class="swatch-grid" style="--swatch-cols: {paletteStepLightness.length + 2}">
|
|
978
605
|
<div class="step-column">
|
|
@@ -1073,98 +700,16 @@
|
|
|
1073
700
|
|
|
1074
701
|
</div>
|
|
1075
702
|
|
|
1076
|
-
<button class="derived-toggle" type="button" onclick={() => showDerived = !showDerived}>
|
|
1077
|
-
<i class="fas" class:fa-chevron-right={!showDerived} class:fa-chevron-down={showDerived}></i>
|
|
1078
|
-
<span>Text, Surfaces & Borders</span>
|
|
1079
|
-
</button>
|
|
1080
|
-
|
|
1081
|
-
{#if showDerived}
|
|
1082
|
-
<div class="scales-row">
|
|
1083
|
-
{#each scales.filter(s => s.isText) as scale}
|
|
1084
|
-
<OverridesPanel
|
|
1085
|
-
{scale}
|
|
1086
|
-
editorOpen={scaleEditorOpen[scale.title] ?? false}
|
|
1087
|
-
snapped={snappedScales.has(scale.title)}
|
|
1088
|
-
supportsSnap={true}
|
|
1089
|
-
{cssNamespace}
|
|
1090
|
-
{scaleCurves}
|
|
1091
|
-
{curveOffset}
|
|
1092
|
-
{defaultScaleCurves}
|
|
1093
|
-
{overrides}
|
|
1094
|
-
{editingKey}
|
|
1095
|
-
{snapPickerKey}
|
|
1096
|
-
{copiedKey}
|
|
1097
|
-
{copiedLabelKey}
|
|
1098
|
-
{paletteComputed}
|
|
1099
|
-
derivedHexFor={derivedHexForBase}
|
|
1100
|
-
effectiveHexFor={effectiveHexAny}
|
|
1101
|
-
stepKeyFor={stepKey}
|
|
1102
|
-
scaleCurveKeyFor={getScaleCurveKey}
|
|
1103
|
-
onToggleSnap={toggleSnapAll}
|
|
1104
|
-
onClearScaleOverrides={clearScaleOverrides}
|
|
1105
|
-
onToggleEditor={toggleScaleEditor}
|
|
1106
|
-
onResetOverride={resetOverride}
|
|
1107
|
-
onOverrideClick={handleOverrideClick}
|
|
1108
|
-
onSnappedClick={handleSnappedClick}
|
|
1109
|
-
onSelectSnapValue={selectSnapValue}
|
|
1110
|
-
onCopyHex={copyHex}
|
|
1111
|
-
onCopyVarName={copyVarName}
|
|
1112
|
-
onSetScaleCurve={setScaleCurve}
|
|
1113
|
-
onOffsetChange={handleOffset}
|
|
1114
|
-
/>
|
|
1115
|
-
{/each}
|
|
1116
|
-
</div>
|
|
1117
|
-
|
|
1118
|
-
<!-- Surfaces & Borders — per-scale editors -->
|
|
1119
|
-
<div class="scales-row">
|
|
1120
|
-
{#each scales.filter(s => !s.isText) as scale}
|
|
1121
|
-
<OverridesPanel
|
|
1122
|
-
{scale}
|
|
1123
|
-
editorOpen={scaleEditorOpen[scale.title] ?? false}
|
|
1124
|
-
snapped={snappedScales.has(scale.title)}
|
|
1125
|
-
supportsSnap={true}
|
|
1126
|
-
{cssNamespace}
|
|
1127
|
-
{scaleCurves}
|
|
1128
|
-
{curveOffset}
|
|
1129
|
-
{defaultScaleCurves}
|
|
1130
|
-
{overrides}
|
|
1131
|
-
{editingKey}
|
|
1132
|
-
{snapPickerKey}
|
|
1133
|
-
{copiedKey}
|
|
1134
|
-
{copiedLabelKey}
|
|
1135
|
-
{paletteComputed}
|
|
1136
|
-
derivedHexFor={derivedHexForBase}
|
|
1137
|
-
effectiveHexFor={effectiveHexAny}
|
|
1138
|
-
stepKeyFor={stepKey}
|
|
1139
|
-
scaleCurveKeyFor={getScaleCurveKey}
|
|
1140
|
-
onToggleSnap={toggleSnapAll}
|
|
1141
|
-
onClearScaleOverrides={clearScaleOverrides}
|
|
1142
|
-
onToggleEditor={toggleScaleEditor}
|
|
1143
|
-
onResetOverride={resetOverride}
|
|
1144
|
-
onOverrideClick={handleOverrideClick}
|
|
1145
|
-
onSnappedClick={handleSnappedClick}
|
|
1146
|
-
onSelectSnapValue={selectSnapValue}
|
|
1147
|
-
onCopyHex={copyHex}
|
|
1148
|
-
onCopyVarName={copyVarName}
|
|
1149
|
-
onSetScaleCurve={setScaleCurve}
|
|
1150
|
-
onOffsetChange={handleOffset}
|
|
1151
|
-
/>
|
|
1152
|
-
{/each}
|
|
1153
|
-
</div>
|
|
1154
|
-
{/if}
|
|
1155
|
-
|
|
1156
703
|
{:else}
|
|
1157
704
|
<!-- Gray mode: palette + text row -->
|
|
1158
705
|
<div class="scales-row">
|
|
1159
706
|
<div class="scale-section">
|
|
1160
707
|
<div class="scale-header">
|
|
1161
708
|
<h4 class="scale-title">{displayLabel ?? label}</h4>
|
|
1162
|
-
<
|
|
1163
|
-
<
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
onclick={() => grayEditorOpen = !grayEditorOpen}
|
|
1167
|
-
>{grayEditorOpen ? 'Close' : 'Edit'}</button>
|
|
709
|
+
<UIPillButton size="compact" variant="outline" onclick={clearPaletteOverrides}>Clear Overrides</UIPillButton>
|
|
710
|
+
<UIPillButton size="compact" variant="outline" onclick={() => grayEditorOpen = !grayEditorOpen}>
|
|
711
|
+
{grayEditorOpen ? 'Close' : 'Edit'}
|
|
712
|
+
</UIPillButton>
|
|
1168
713
|
</div>
|
|
1169
714
|
<div class="swatch-grid" style="--swatch-cols: {graySteps.length + 2}">
|
|
1170
715
|
<div class="step-column">
|
|
@@ -1234,6 +779,41 @@
|
|
|
1234
779
|
</div>
|
|
1235
780
|
</div>
|
|
1236
781
|
</div>
|
|
782
|
+
{/if}
|
|
783
|
+
|
|
784
|
+
{#snippet overridesPanel(scale: Scale, canSnap: boolean, derivedHexFor: (step: Step, scaleTitle: string) => string)}
|
|
785
|
+
<OverridesPanel
|
|
786
|
+
{scale}
|
|
787
|
+
editorOpen={scaleEditorOpen[scale.title] ?? false}
|
|
788
|
+
snapped={canSnap && snappedScales.has(scale.title)}
|
|
789
|
+
supportsSnap={canSnap}
|
|
790
|
+
{cssNamespace}
|
|
791
|
+
{scaleCurves}
|
|
792
|
+
{curveOffset}
|
|
793
|
+
{defaultScaleCurves}
|
|
794
|
+
{overrides}
|
|
795
|
+
{editingKey}
|
|
796
|
+
{snapPickerKey}
|
|
797
|
+
{copiedKey}
|
|
798
|
+
{copiedLabelKey}
|
|
799
|
+
{paletteComputed}
|
|
800
|
+
{derivedHexFor}
|
|
801
|
+
effectiveHexFor={effectiveHexAny}
|
|
802
|
+
stepKeyFor={stepKey}
|
|
803
|
+
scaleCurveKeyFor={getScaleCurveKey}
|
|
804
|
+
onToggleSnap={toggleSnapAll}
|
|
805
|
+
onClearScaleOverrides={clearScaleOverrides}
|
|
806
|
+
onToggleEditor={toggleScaleEditor}
|
|
807
|
+
onResetOverride={resetOverride}
|
|
808
|
+
onOverrideClick={handleOverrideClick}
|
|
809
|
+
onSnappedClick={handleSnappedClick}
|
|
810
|
+
onSelectSnapValue={selectSnapValue}
|
|
811
|
+
onCopyHex={copyHex}
|
|
812
|
+
onCopyVarName={copyVarName}
|
|
813
|
+
onSetScaleCurve={setScaleCurve}
|
|
814
|
+
onOffsetChange={handleOffset}
|
|
815
|
+
/>
|
|
816
|
+
{/snippet}
|
|
1237
817
|
|
|
1238
818
|
<button class="derived-toggle" type="button" onclick={() => showDerived = !showDerived}>
|
|
1239
819
|
<i class="fas" class:fa-chevron-right={!showDerived} class:fa-chevron-down={showDerived}></i>
|
|
@@ -1241,78 +821,18 @@
|
|
|
1241
821
|
</button>
|
|
1242
822
|
|
|
1243
823
|
{#if showDerived}
|
|
1244
|
-
|
|
1245
|
-
{
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
{
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
{editingKey}
|
|
1257
|
-
{snapPickerKey}
|
|
1258
|
-
{copiedKey}
|
|
1259
|
-
{copiedLabelKey}
|
|
1260
|
-
{paletteComputed}
|
|
1261
|
-
derivedHexFor={derivedHexForGray}
|
|
1262
|
-
effectiveHexFor={effectiveHexAny}
|
|
1263
|
-
stepKeyFor={stepKey}
|
|
1264
|
-
scaleCurveKeyFor={getScaleCurveKey}
|
|
1265
|
-
onToggleSnap={toggleSnapAll}
|
|
1266
|
-
onClearScaleOverrides={clearScaleOverrides}
|
|
1267
|
-
onToggleEditor={toggleScaleEditor}
|
|
1268
|
-
onResetOverride={resetOverride}
|
|
1269
|
-
onOverrideClick={handleOverrideClick}
|
|
1270
|
-
onSnappedClick={handleSnappedClick}
|
|
1271
|
-
onSelectSnapValue={selectSnapValue}
|
|
1272
|
-
onCopyHex={copyHex}
|
|
1273
|
-
onCopyVarName={copyVarName}
|
|
1274
|
-
onSetScaleCurve={setScaleCurve}
|
|
1275
|
-
onOffsetChange={handleOffset}
|
|
1276
|
-
/>
|
|
1277
|
-
{/each}
|
|
1278
|
-
</div>
|
|
1279
|
-
<!-- Surfaces & Borders for gray mode -->
|
|
1280
|
-
<div class="scales-row">
|
|
1281
|
-
{#each grayScales.filter(s => !s.isText) as scale}
|
|
1282
|
-
<OverridesPanel
|
|
1283
|
-
{scale}
|
|
1284
|
-
editorOpen={scaleEditorOpen[scale.title] ?? false}
|
|
1285
|
-
snapped={false}
|
|
1286
|
-
supportsSnap={false}
|
|
1287
|
-
{cssNamespace}
|
|
1288
|
-
{scaleCurves}
|
|
1289
|
-
{curveOffset}
|
|
1290
|
-
{defaultScaleCurves}
|
|
1291
|
-
{overrides}
|
|
1292
|
-
{editingKey}
|
|
1293
|
-
{snapPickerKey}
|
|
1294
|
-
{copiedKey}
|
|
1295
|
-
{copiedLabelKey}
|
|
1296
|
-
{paletteComputed}
|
|
1297
|
-
derivedHexFor={derivedHexForGray}
|
|
1298
|
-
effectiveHexFor={effectiveHexAny}
|
|
1299
|
-
stepKeyFor={stepKey}
|
|
1300
|
-
scaleCurveKeyFor={getScaleCurveKey}
|
|
1301
|
-
onToggleSnap={toggleSnapAll}
|
|
1302
|
-
onClearScaleOverrides={clearScaleOverrides}
|
|
1303
|
-
onToggleEditor={toggleScaleEditor}
|
|
1304
|
-
onResetOverride={resetOverride}
|
|
1305
|
-
onOverrideClick={handleOverrideClick}
|
|
1306
|
-
onSnappedClick={handleSnappedClick}
|
|
1307
|
-
onSelectSnapValue={selectSnapValue}
|
|
1308
|
-
onCopyHex={copyHex}
|
|
1309
|
-
onCopyVarName={copyVarName}
|
|
1310
|
-
onSetScaleCurve={setScaleCurve}
|
|
1311
|
-
onOffsetChange={handleOffset}
|
|
1312
|
-
/>
|
|
1313
|
-
{/each}
|
|
1314
|
-
</div>
|
|
1315
|
-
{/if}
|
|
824
|
+
{@const activeScales = mode === 'gray' ? grayScales : scales}
|
|
825
|
+
{@const derivedHexFor = mode === 'gray' ? derivedHexForGray : derivedHexForBase}
|
|
826
|
+
<div class="scales-row">
|
|
827
|
+
{#each activeScales.filter(s => s.isText) as scale}
|
|
828
|
+
{@render overridesPanel(scale, true, derivedHexFor)}
|
|
829
|
+
{/each}
|
|
830
|
+
</div>
|
|
831
|
+
<div class="scales-row">
|
|
832
|
+
{#each activeScales.filter(s => !s.isText) as scale}
|
|
833
|
+
{@render overridesPanel(scale, mode !== 'gray', derivedHexFor)}
|
|
834
|
+
{/each}
|
|
835
|
+
</div>
|
|
1316
836
|
{/if}
|
|
1317
837
|
|
|
1318
838
|
<!-- Color Edit Panel (non-base edits) -->
|
|
@@ -1342,7 +862,7 @@
|
|
|
1342
862
|
padding: var(--ui-space-16) var(--ui-space-16) var(--ui-space-24);
|
|
1343
863
|
background: none;
|
|
1344
864
|
border: none;
|
|
1345
|
-
border-bottom: 1px solid var(--ui-border-
|
|
865
|
+
border-bottom: 1px solid var(--ui-border-low);
|
|
1346
866
|
font-family: var(--ui-font-sans);
|
|
1347
867
|
min-width: 0;
|
|
1348
868
|
}
|
|
@@ -1357,21 +877,7 @@
|
|
|
1357
877
|
display: flex;
|
|
1358
878
|
align-items: center;
|
|
1359
879
|
gap: var(--ui-space-8);
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
.edit-toggle {
|
|
1363
|
-
font-size: var(--ui-font-size-md);
|
|
1364
|
-
color: var(--ui-text-tertiary);
|
|
1365
|
-
background: none;
|
|
1366
|
-
border: 1px solid var(--ui-border-subtle);
|
|
1367
|
-
border-radius: var(--ui-radius-sm);
|
|
1368
|
-
padding: var(--ui-space-2) var(--ui-space-6);
|
|
1369
|
-
cursor: pointer;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
.edit-toggle:hover {
|
|
1373
|
-
color: var(--ui-text-primary);
|
|
1374
|
-
border-color: var(--ui-border-medium);
|
|
880
|
+
padding-bottom: 0.5rem;
|
|
1375
881
|
}
|
|
1376
882
|
|
|
1377
883
|
.derived-toggle {
|
|
@@ -1381,17 +887,15 @@
|
|
|
1381
887
|
padding: var(--ui-space-6) var(--ui-space-4);
|
|
1382
888
|
background: none;
|
|
1383
889
|
border: none;
|
|
1384
|
-
color: var(--ui-text-
|
|
1385
|
-
font-size: var(--ui-font-size-
|
|
1386
|
-
font-weight: var(--ui-font-weight-
|
|
890
|
+
color: var(--ui-text-secondary);
|
|
891
|
+
font-size: var(--ui-font-size-lg);
|
|
892
|
+
font-weight: var(--ui-font-weight-light);
|
|
1387
893
|
cursor: pointer;
|
|
1388
894
|
transition: color var(--ui-transition-fast);
|
|
1389
|
-
text-transform: uppercase;
|
|
1390
|
-
letter-spacing: 0.04em;
|
|
1391
895
|
}
|
|
1392
896
|
|
|
1393
897
|
.derived-toggle:hover {
|
|
1394
|
-
color: var(--ui-text-
|
|
898
|
+
color: var(--ui-text-primary);
|
|
1395
899
|
}
|
|
1396
900
|
|
|
1397
901
|
.derived-toggle i {
|
|
@@ -1418,12 +922,11 @@
|
|
|
1418
922
|
}
|
|
1419
923
|
|
|
1420
924
|
.scale-title {
|
|
1421
|
-
font-size: var(--ui-font-size-
|
|
1422
|
-
font-weight: var(--ui-font-weight-
|
|
1423
|
-
color: var(--ui-text-
|
|
925
|
+
font-size: var(--ui-font-size-lg);
|
|
926
|
+
font-weight: var(--ui-font-weight-bold);
|
|
927
|
+
color: var(--ui-text-primary);
|
|
1424
928
|
margin: 0;
|
|
1425
|
-
|
|
1426
|
-
letter-spacing: 0.05em;
|
|
929
|
+
padding-right: 1rem;
|
|
1427
930
|
}
|
|
1428
931
|
|
|
1429
932
|
/* Step columns */
|
|
@@ -1474,7 +977,7 @@
|
|
|
1474
977
|
width: 100%;
|
|
1475
978
|
height: 2rem;
|
|
1476
979
|
border-radius: var(--ui-radius-sm);
|
|
1477
|
-
border: 1px solid var(--ui-border-
|
|
980
|
+
border: 1px solid var(--ui-border-low);
|
|
1478
981
|
}
|
|
1479
982
|
|
|
1480
983
|
/* Step hex values */
|
|
@@ -1490,7 +993,11 @@
|
|
|
1490
993
|
background: none;
|
|
1491
994
|
border: none;
|
|
1492
995
|
text-align: center;
|
|
996
|
+
display: block;
|
|
997
|
+
width: 100%;
|
|
998
|
+
box-sizing: border-box;
|
|
1493
999
|
min-width: 0;
|
|
1000
|
+
margin-top: var(--ui-space-2);
|
|
1494
1001
|
overflow: hidden;
|
|
1495
1002
|
text-overflow: ellipsis;
|
|
1496
1003
|
}
|
|
@@ -1530,12 +1037,12 @@
|
|
|
1530
1037
|
}
|
|
1531
1038
|
|
|
1532
1039
|
.swatch.gray-swatch:hover {
|
|
1533
|
-
border-color: var(--ui-border-
|
|
1040
|
+
border-color: var(--ui-border-high);
|
|
1534
1041
|
}
|
|
1535
1042
|
|
|
1536
1043
|
.swatch.gray-swatch.active {
|
|
1537
|
-
border-color: var(--ui-border-
|
|
1538
|
-
outline: 2px solid var(--ui-border-
|
|
1044
|
+
border-color: var(--ui-border-higher);
|
|
1045
|
+
outline: 2px solid var(--ui-border-high);
|
|
1539
1046
|
outline-offset: 1px;
|
|
1540
1047
|
}
|
|
1541
1048
|
|