@motion-proto/live-tokens 0.44.0 → 0.46.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist-plugin/index.cjs +1 -1
  3. package/dist-plugin/index.js +1 -1
  4. package/package.json +1 -1
  5. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +0 -1
  6. package/src/editor/component-editor/scaffolding/ComponentFileMenu.svelte +1 -1
  7. package/src/editor/core/palettes/colorHarmony.ts +35 -6
  8. package/src/editor/core/palettes/paletteDerivation.ts +2 -3
  9. package/src/editor/core/store/editorTypes.ts +2 -2
  10. package/src/editor/docs/CodeBlock.svelte +0 -2
  11. package/src/editor/docs/Docs.svelte +0 -3
  12. package/src/editor/pages/ComponentEditorPage.svelte +3 -5
  13. package/src/editor/pages/EditorShell.svelte +3 -5
  14. package/src/editor/ui/ColorEditPanel.svelte +19 -22
  15. package/src/editor/ui/EditorViewSwitcher.svelte +2 -3
  16. package/src/editor/ui/FileLoadList.svelte +7 -9
  17. package/src/editor/ui/ManifestFileManager.svelte +1 -3
  18. package/src/editor/ui/ProjectFontsSection.svelte +0 -4
  19. package/src/editor/ui/SurfacesTab.svelte +3 -3
  20. package/src/editor/ui/TextStylesSection.svelte +6 -8
  21. package/src/editor/ui/ThemeFileManager.svelte +1 -3
  22. package/src/editor/ui/UIEasingSelector.svelte +0 -2
  23. package/src/editor/ui/UIFontFamilySelector.svelte +9 -3
  24. package/src/editor/ui/UIMenuButton.svelte +230 -0
  25. package/src/editor/ui/UIPaletteSelector.svelte +0 -1
  26. package/src/editor/ui/VariablesTab.svelte +3 -4
  27. package/src/editor/ui/colors/AxisNumeral.svelte +70 -0
  28. package/src/editor/ui/colors/ColorReadouts.svelte +0 -2
  29. package/src/editor/ui/colors/ColorStory.svelte +24 -77
  30. package/src/editor/ui/colors/ColorWheel.svelte +48 -29
  31. package/src/editor/ui/colors/ColorsTab.svelte +361 -212
  32. package/src/editor/ui/colors/HarmonyAxesList.svelte +264 -141
  33. package/src/editor/ui/colors/LightnessBar.svelte +0 -2
  34. package/src/editor/ui/colors/harmonyDrag.ts +10 -0
  35. package/src/editor/ui/colors/harmonyModeIcons.ts +5 -0
  36. package/src/editor/ui/colors/paletteBaseColor.ts +25 -39
  37. package/src/live-tokens/data/tokens.generated.css +28 -28
  38. package/src/system/components/ImageLightbox.svelte +0 -2
  39. package/src/system/components/SectionDivider.svelte +4 -4
  40. package/src/editor/core/palettes/recommendText.ts +0 -216
  41. package/src/editor/core/palettes/solveTextContrast.ts +0 -254
package/CHANGELOG.md CHANGED
@@ -1,5 +1,108 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.46.0 — Harmony axes are numbered, and you assign them with a picker
4
+
5
+ ### Changed
6
+
7
+ - **The four harmony axes are numbered, not named.** `AXIS_ROLES`
8
+ (`Anchor`/`Secondary`/`Tertiary`/`Quaternary`) is deleted; every call site
9
+ routes through the new `axisLabel(index)`, and only axis 1 keeps a word.
10
+ Rows read `1 Anchor`, `2`, `3`, `4`. History entries read `colors: axis 3
11
+ hue`, `colors: anchor rotate`, `colors: assign Brand`, `colors: unassign
12
+ Brand`. Aria and titles drop the doubled "axis": `Rotate axis 3 hue`,
13
+ `Brand, on axis 2 of 4. ... Delete to unassign.` `bindFamilyToAxis` and
14
+ `unbindFamily` keep their names, so the model verb is unchanged.
15
+
16
+ - **One source of truth for axis status.** `axisStatuses(mode, axes)` replaces
17
+ the three per-surface derivations off `modeActiveAxes`, so the swatch row,
18
+ the wheel and the axes list can no longer disagree about a family bound to an
19
+ axis the current mode gives no position. That family now reads as
20
+ bound-and-off-wheel everywhere: the list keeps its chip at full strength and
21
+ states `Off the wheel · <Mode> uses N`, the free dot carries its axis numeral and
22
+ says why it left the ring, and the swatch row's mark tracks the same status.
23
+ Unused rows replace the bare "Empty" with their reason. New helpers
24
+ `activeAxisCount` and `axisLabel` keep those strings out of per-call-site
25
+ composition.
26
+
27
+ - **Assigning a family to an axis is a picker.** Each row opens an `Assign to
28
+ ...` menu listing the eligible families, with meta reading `moves from
29
+ anchor` / `moves from axis 2`. Dragging a chip onto an axis still works and
30
+ is now the accelerator, not the only path. The axes description leads with
31
+ the click path and mentions drag second.
32
+
33
+ - **The Colors view is laid out around the wheel.** Color Harmony and Harmony
34
+ axes sit side by side, the family swatches tuck under the harmony presets,
35
+ the axes list moves under the mode row, and the selected color's edit panel
36
+ moves under the wheel with the lightness bar inside it and Absolute Chroma in
37
+ its header. The three slider rows are grid-aligned on one column set.
38
+
39
+ - **`Special` moves ahead of `Canvas` in `PALETTE_SPECS`.** It reorders the
40
+ family list in the editor and the block order in
41
+ `src/live-tokens/data/tokens.generated.css`. No token name is added, renamed
42
+ or removed, so no migration applies.
43
+
44
+ - **SectionDivider's eyebrow letter-spacing default is
45
+ `--letter-spacing-normal`.** All three sizes previously defaulted to
46
+ `--letter-spacing-wide`. Consumers on a saved component config keep their own
47
+ value; consumers on `default` pick this up.
48
+
49
+ ### Added
50
+
51
+ - **`UIMenuButton`**, the shared editor menu-button behind the axis picker.
52
+ `aria-haspopup="menu"`, arrow navigation that wraps, Home/End, and dismissal
53
+ on Escape, Tab or outside mousedown, each pinned by a test. Internal to the
54
+ editor; it is not exported from `@motion-proto/live-tokens/ui`.
55
+
56
+ - **`AxisNumeral`**, the one numeral treatment shared by the swatch row, the
57
+ wheel and the axes list.
58
+
59
+ ### Removed
60
+
61
+ - **The tint-neutrals button.** Absolute Chroma sits beside the harmony presets
62
+ and covers the case.
63
+
64
+ - **Chrome the surfaces did not need.** The Theme eyebrow and the panel's
65
+ preview square in the Colors view, the Proportional preview eyebrow, and the
66
+ inline axes description, which is now an info icon. Selected rows lose the
67
+ third emphasis signal on `.role`: the settled treatment is inverted numeral
68
+ plus one row border.
69
+
70
+ ## 0.45.0 — The Color Story shows the tokens you actually have
71
+
72
+ ### Fixed
73
+
74
+ - **The Color Story's text steps read the live tokens.** The primary, secondary
75
+ and tertiary row rendered the values the *Even neutral steps* pill would have
76
+ written, not the current ones, so the same step could report two different
77
+ ratios in the same frame (the Canvas header said 17.9:1 while the row said
78
+ 14.7:1). Each step now resolves `--text-primary` / `--text-secondary` /
79
+ `--text-tertiary` through the same path the functional chips and tone pills
80
+ already used, renders in its own token, and reports its real contrast against
81
+ the canvas.
82
+
83
+ ### Removed
84
+
85
+ - **The Color Story's action pills.** *Raise all text to AA*, *Even neutral
86
+ steps*, and the *Anchor at pure white/black* toggle, added in 0.44.0, are
87
+ gone. The story is a proportional preview, and authoring text steps from it
88
+ put a second palette editor next to the real one. Text steps are edited in
89
+ the Tokens view.
90
+
91
+ - **The text-contrast solvers.** `solveTextContrast.ts` and `recommendText.ts`
92
+ (plus `applySolvedTextCurves` and `applySuggestedNeutralText`) backed only
93
+ those pills and are deleted. Both were internal to `src/editor/core/palettes/`
94
+ and never reachable through the package exports, so no consumer import
95
+ changes. `BW_GUARD_MIN_L` / `BW_GUARD_MAX_L` are unaffected: they live in
96
+ `paletteDerivation.ts` and still guard derived inverted text.
97
+
98
+ ### Changed
99
+
100
+ - **The story's prose lines and its unlabeled header ratio are gone.** The three
101
+ sentences under Canvas ("The dominant surface. Body text sits here." and its
102
+ two siblings) said nothing the labelled steps don't, and the ratio beside the
103
+ Canvas heading was primary's, measured but never named. The stacked steps
104
+ carry all three ratios now.
105
+
3
106
  ## 0.44.0 — Every text step derives to body contrast
4
107
 
5
108
  ### Added
@@ -198,10 +198,10 @@ function sampleCurve(anchors, xPos) {
198
198
  var PALETTE_SPECS = [
199
199
  { label: "Brand", cssNamespace: "brand", initialColor: { l: 0.5573, c: 0.1841, h: 25.49 } },
200
200
  { label: "Accent", cssNamespace: "accent", initialColor: { l: 0.7674, c: 0.164, h: 70.44 } },
201
+ { label: "Special", cssNamespace: "special", initialColor: { l: 0.6056, c: 0.2189, h: 292.72 } },
201
202
  { label: "Canvas", cssNamespace: "canvas", emptySelector: true, initialColor: { l: 0.2284, c: 0.0384, h: 282.93 } },
202
203
  { label: "Neutral", cssNamespace: "neutral", neutral: true, initialColor: { l: 0.5679, c: 0.0134, h: 240.07 } },
203
204
  { label: "Alternate", cssNamespace: "alternate", neutral: true, displayLabel: "Alternate (neutral)", initialColor: { l: 0.5873, c: 87e-4, h: 48.57 } },
204
- { label: "Special", cssNamespace: "special", initialColor: { l: 0.6056, c: 0.2189, h: 292.72 } },
205
205
  { label: "Info", cssNamespace: "info", initialColor: { l: 0.5871, c: 0.1855, h: 259.56 } },
206
206
  { label: "Success", cssNamespace: "success", initialColor: { l: 0.7198, c: 0.1918, h: 149.52 } },
207
207
  { label: "Warning", cssNamespace: "warning", initialColor: { l: 0.6704, c: 0.1716, h: 48.75 } },
@@ -157,10 +157,10 @@ function sampleCurve(anchors, xPos) {
157
157
  var PALETTE_SPECS = [
158
158
  { label: "Brand", cssNamespace: "brand", initialColor: { l: 0.5573, c: 0.1841, h: 25.49 } },
159
159
  { label: "Accent", cssNamespace: "accent", initialColor: { l: 0.7674, c: 0.164, h: 70.44 } },
160
+ { label: "Special", cssNamespace: "special", initialColor: { l: 0.6056, c: 0.2189, h: 292.72 } },
160
161
  { label: "Canvas", cssNamespace: "canvas", emptySelector: true, initialColor: { l: 0.2284, c: 0.0384, h: 282.93 } },
161
162
  { label: "Neutral", cssNamespace: "neutral", neutral: true, initialColor: { l: 0.5679, c: 0.0134, h: 240.07 } },
162
163
  { label: "Alternate", cssNamespace: "alternate", neutral: true, displayLabel: "Alternate (neutral)", initialColor: { l: 0.5873, c: 87e-4, h: 48.57 } },
163
- { label: "Special", cssNamespace: "special", initialColor: { l: 0.6056, c: 0.2189, h: 292.72 } },
164
164
  { label: "Info", cssNamespace: "info", initialColor: { l: 0.5871, c: 0.1855, h: 259.56 } },
165
165
  { label: "Success", cssNamespace: "success", initialColor: { l: 0.7198, c: 0.1918, h: 149.52 } },
166
166
  { label: "Warning", cssNamespace: "warning", initialColor: { l: 0.6704, c: 0.1716, h: 48.75 } },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.44.0",
3
+ "version": "0.46.0",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
6
6
  "keywords": [
@@ -531,7 +531,6 @@
531
531
  .cfm-row-label {
532
532
  font-size: var(--ui-font-size-xs);
533
533
  font-weight: var(--ui-font-weight-semibold);
534
- text-transform: uppercase;
535
534
  color: var(--ui-text-secondary);
536
535
  line-height: 1.1;
537
536
  }
@@ -108,7 +108,7 @@
108
108
  title="Load {component} Config"
109
109
  {files}
110
110
  {activeFileName}
111
- systemBadge={{ label: 'system', title: 'Protected system config' }}
111
+ systemBadge={{ label: 'System', title: 'Protected system config' }}
112
112
  emptyMessage="No saved files"
113
113
  onload={handleLoad}
114
114
  ondelete={handleDelete}
@@ -1,9 +1,9 @@
1
1
  // Harmony rotates HUE ONLY (Global invariant 6). Each palette keeps its own
2
2
  // OKLCH chroma and lightness; only a family's hue moves onto a geometric
3
3
  // relationship with the anchor. The anchor is axis 0's stored hue: harmony lives
4
- // on four fixed axes (Anchor/Secondary/Tertiary/Quaternary), each owning a hue
5
- // whether or not a color family is bound to it. No chroma clamp or cap lives
6
- // here; a saturated background is a legitimate choice, not something to correct.
4
+ // on four fixed axes, numbered, each owning a hue whether or not a color family
5
+ // is bound to it. No chroma clamp or cap lives here; a saturated background is a
6
+ // legitimate choice, not something to correct.
7
7
 
8
8
  import type { Oklch } from './oklch';
9
9
  import type { PaletteConfig } from '../themes/themeTypes';
@@ -24,7 +24,6 @@ export type HarmonyMode =
24
24
  export const HARMONY_ELIGIBLE: readonly string[] = ['Brand', 'Accent', 'Canvas', 'Special', 'Neutral', 'Alternate'];
25
25
 
26
26
  export const AXIS_COUNT = 4;
27
- export const AXIS_ROLES = ['Anchor', 'Secondary', 'Tertiary', 'Quaternary'] as const;
28
27
 
29
28
  export interface HarmonyAxis {
30
29
  /** Hue in [0, 360). Always present, bound or not. */
@@ -85,7 +84,7 @@ const specInitialHue = (label: string): number =>
85
84
 
86
85
  /**
87
86
  * Seed the four axes for a fresh editor: the default trio bound (hues read from
88
- * `PALETTE_SPECS`, never hardcoded) and Quaternary unbound at anchor + 270 (the
87
+ * `PALETTE_SPECS`, never hardcoded) and axis 4 unbound at anchor + 270 (the
89
88
  * square slot-3 offset). Fresh array each call — it seeds mutable state.
90
89
  */
91
90
  export function defaultHarmonyAxes(): HarmonyAxis[] {
@@ -110,6 +109,36 @@ export function modeActiveAxes(mode: HarmonyMode): boolean[] {
110
109
  return slots.map((h, i) => slots.slice(0, i).every((prev) => prev !== h));
111
110
  }
112
111
 
112
+ export type AxisStatus = 'on-wheel' | 'off-wheel' | 'unused';
113
+
114
+ /**
115
+ * Per-axis presentation status. The single source every Colors surface reads,
116
+ * so the wheel, the swatch row and the axes list cannot disagree about the
117
+ * same axis.
118
+ *
119
+ * on-wheel — the mode deals this slot a distinct hue: rail, handle, numeral.
120
+ * off-wheel — a family is bound, but the mode deals the slot no position.
121
+ * The binding is real and returns when the mode changes; the
122
+ * family edits as the free dot meanwhile.
123
+ * unused — no family bound and no position dealt.
124
+ */
125
+ export function axisStatuses(mode: HarmonyMode, axes: HarmonyAxis[]): AxisStatus[] {
126
+ return modeActiveAxes(mode).map((active, i) => {
127
+ if (active) return 'on-wheel';
128
+ return axes[i].family !== null ? 'off-wheel' : 'unused';
129
+ });
130
+ }
131
+
132
+ /** How many axes `mode` deals a distinct position to. Reason strings quote it. */
133
+ export function activeAxisCount(mode: HarmonyMode): number {
134
+ return modeActiveAxes(mode).filter(Boolean).length;
135
+ }
136
+
137
+ /** Axis 1 is the slot the other three are measured from, so it keeps a name. */
138
+ export function axisLabel(index: number): string {
139
+ return index === 0 ? 'anchor' : `axis ${index + 1}`;
140
+ }
141
+
113
142
  /**
114
143
  * Re-deal every axis's hue from `mode`'s geometry, anchored on axis 0's hue.
115
144
  * Bindings are preserved; `'custom'` imposes no constraint and returns a copy.
@@ -173,7 +202,7 @@ export function sanitizeHarmonyAxes(
173
202
  axis.hue = norm(palettes[axis.family].baseColor.h);
174
203
  }
175
204
  }
176
- // With no stored axes (a theme predating them), the default Quaternary hue is
205
+ // With no stored axes (a theme predating them), the default axis 4 hue is
177
206
  // an offset from the anchor, so it follows the anchor's palette-seeded hue
178
207
  // rather than the spec hue the defaults were built from.
179
208
  const last = axes[AXIS_COUNT - 1];
@@ -40,10 +40,10 @@ export interface PaletteSpec {
40
40
  export const PALETTE_SPECS: readonly PaletteSpec[] = [
41
41
  { label: 'Brand', cssNamespace: 'brand', initialColor: { l: 0.5573, c: 0.1841, h: 25.49 } },
42
42
  { label: 'Accent', cssNamespace: 'accent', initialColor: { l: 0.7674, c: 0.164, h: 70.44 } },
43
+ { label: 'Special', cssNamespace: 'special', initialColor: { l: 0.6056, c: 0.2189, h: 292.72 } },
43
44
  { label: 'Canvas', cssNamespace: 'canvas', emptySelector: true, initialColor: { l: 0.2284, c: 0.0384, h: 282.93 } },
44
45
  { label: 'Neutral', cssNamespace: 'neutral', neutral: true, initialColor: { l: 0.5679, c: 0.0134, h: 240.07 } },
45
46
  { label: 'Alternate', cssNamespace: 'alternate', neutral: true, displayLabel: 'Alternate (neutral)', initialColor: { l: 0.5873, c: 0.0087, h: 48.57 } },
46
- { label: 'Special', cssNamespace: 'special', initialColor: { l: 0.6056, c: 0.2189, h: 292.72 } },
47
47
  { label: 'Info', cssNamespace: 'info', initialColor: { l: 0.5871, c: 0.1855, h: 259.56 } },
48
48
  { label: 'Success', cssNamespace: 'success', initialColor: { l: 0.7198, c: 0.1918, h: 149.52 } },
49
49
  { label: 'Warning', cssNamespace: 'warning', initialColor: { l: 0.6704, c: 0.1716, h: 48.75 } },
@@ -104,8 +104,7 @@ export const SCALES: readonly Scale[] = [
104
104
  ];
105
105
 
106
106
  /** Guard band for derived near-black/near-white text: values stay between
107
- * ≈ #111 and ≈ #e8e8e8 unless pure black/white is opted into. The AA floor
108
- * beats the guard (see `recommendText.ts`). */
107
+ * ≈ #111 and ≈ #e8e8e8, the range most designers prefer over pure extremes. */
109
108
  export const BW_GUARD_MIN_L = 0.17;
110
109
  export const BW_GUARD_MAX_L = 0.93;
111
110
 
@@ -137,8 +137,8 @@ export interface EditorState {
137
137
  columns: ColumnsState;
138
138
  components: Record<string, ComponentSlice>;
139
139
  gradients: { tokens: GradientToken[] };
140
- /** Four fixed axes (index = role: Anchor/Secondary/Tertiary/Quaternary); each
141
- * owns a hue; family is the live-bound color or null. */
140
+ /** Four fixed numbered axes (index 0 is the anchor); each owns a hue;
141
+ * family is the live-bound color or null. */
142
142
  harmonyAxes: HarmonyAxis[];
143
143
  cssVars: Record<string, string>;
144
144
  }
@@ -67,8 +67,6 @@
67
67
  font-family: var(--font-mono, ui-monospace, monospace);
68
68
  font-size: var(--font-size-xs, 0.75rem);
69
69
  color: var(--text-tertiary, #7e8285);
70
- text-transform: uppercase;
71
- letter-spacing: var(--letter-spacing-wider, 0.06em);
72
70
  background: color-mix(in srgb, var(--surface-neutral-lowest, #040c13) 60%, transparent);
73
71
  border-left: var(--border-width-1, 1px) solid var(--border-neutral-faint, #1c2327);
74
72
  border-bottom: var(--border-width-1, 1px) solid var(--border-neutral-faint, #1c2327);
@@ -449,7 +449,6 @@
449
449
  font-size: var(--font-size-lg, 1.125rem);
450
450
  line-height: 1.75rem;
451
451
  font-weight: var(--font-weight-bold, 700);
452
- letter-spacing: 0.025em;
453
452
  color: var(--text-primary);
454
453
  text-decoration: none;
455
454
  cursor: pointer;
@@ -638,8 +637,6 @@
638
637
  .footer-dir {
639
638
  font-size: var(--font-size-xs, 0.75rem);
640
639
  color: var(--text-tertiary);
641
- text-transform: uppercase;
642
- letter-spacing: var(--letter-spacing-wider, 0.08em);
643
640
  }
644
641
  .chapter-link { text-decoration: none; }
645
642
 
@@ -343,9 +343,9 @@
343
343
  background: black;
344
344
  }
345
345
 
346
- /* Divider between SYSTEM and CUSTOM groups. The horizontal line uses the
347
- dimmer border token (sub-element separator), with an uppercase eyebrow
348
- label that fades out when the rail is collapsed so the line still reads. */
346
+ /* Divider between the System and Custom groups. The horizontal line uses the
347
+ dimmer border token (sub-element separator), with an eyebrow label that
348
+ fades out when the rail is collapsed so the line still reads. */
349
349
  .nav-divider {
350
350
  display: grid;
351
351
  grid-template-columns: 48px 1fr;
@@ -360,8 +360,6 @@
360
360
  font-size: var(--ui-font-size-xs);
361
361
  font-weight: var(--ui-font-weight-semibold);
362
362
  color: var(--ui-text-tertiary);
363
- text-transform: uppercase;
364
- letter-spacing: 0.04em;
365
363
  opacity: 0;
366
364
  transition: opacity 180ms ease;
367
365
  }
@@ -299,9 +299,9 @@
299
299
  flex-shrink: 0;
300
300
  }
301
301
 
302
- /* Divider between SYSTEM and CUSTOM groups. The horizontal line uses the
303
- dimmer border token (sub-element separator), with an uppercase eyebrow
304
- label that fades out when the rail is condensed so the line still reads. */
302
+ /* Divider between the System and Custom groups. The horizontal line uses the
303
+ dimmer border token (sub-element separator), with an eyebrow label that
304
+ fades out when the rail is condensed so the line still reads. */
305
305
  .nav-divider {
306
306
  display: grid;
307
307
  grid-template-columns: 48px 1fr;
@@ -316,8 +316,6 @@
316
316
  font-size: var(--ui-font-size-xs);
317
317
  font-weight: var(--ui-font-weight-semibold);
318
318
  color: var(--ui-text-tertiary);
319
- text-transform: uppercase;
320
- letter-spacing: 0.04em;
321
319
  opacity: 1;
322
320
  transition: opacity 180ms ease;
323
321
  }
@@ -12,6 +12,9 @@
12
12
  /** Hide the confirm/cancel actions for live-apply consumers (Colors view
13
13
  * readout) where edits commit through the store as they happen. */
14
14
  hideActions?: boolean;
15
+ /** Hide the preview square where the surrounding view already shows the
16
+ * color (Colors view: wheel, swatches and derived scale all do). */
17
+ hidePreview?: boolean;
15
18
  onConfirm?: () => void;
16
19
  onCancel?: () => void;
17
20
  onRemoveOverride?: () => void;
@@ -36,6 +39,7 @@
36
39
  title = null,
37
40
  showRemoveOverride = false,
38
41
  hideActions = false,
42
+ hidePreview = false,
39
43
  onConfirm = () => {},
40
44
  onCancel = () => {},
41
45
  onRemoveOverride = () => {},
@@ -131,7 +135,9 @@
131
135
 
132
136
  <div class="hsl-panel">
133
137
  <div class="hsl-panel-header">
134
- <div class="hsl-preview" style="background: {previewHex}"></div>
138
+ {#if !hidePreview}
139
+ <div class="hsl-preview" style="background: {previewHex}"></div>
140
+ {/if}
135
141
  {#if hasEyeDropper}
136
142
  <button
137
143
  class="eyedropper-btn"
@@ -190,7 +196,7 @@
190
196
  type="number"
191
197
  min="0"
192
198
  max="360"
193
- value={hue}
199
+ value={Math.round(hue)}
194
200
  onchange={(e) => onHueChromaChange(Math.min(360, Math.max(0, +e.currentTarget.value)), chroma, lPct)}
195
201
  /><span class="hsl-slider-unit">&deg;</span>
196
202
  </div>
@@ -205,14 +211,14 @@
205
211
  oninput={(e) => onHueChromaChange(hue, +e.currentTarget.value, lPct)} />
206
212
  </div>
207
213
  <input
208
- class="hsl-slider-input chroma-input"
214
+ class="hsl-slider-input"
209
215
  type="number"
210
216
  min="0"
211
217
  max={chromaMax}
212
218
  step="0.001"
213
219
  value={chroma.toFixed(3)}
214
220
  onchange={(e) => onHueChromaChange(hue, Math.min(chromaMax, Math.max(0, +e.currentTarget.value)), lPct)}
215
- />
221
+ /><span class="hsl-slider-unit"></span>
216
222
  </div>
217
223
  <div class="hsl-slider-row">
218
224
  <span class="hsl-slider-label">L</span>
@@ -330,25 +336,24 @@
330
336
  flex-wrap: wrap;
331
337
  }
332
338
 
339
+ /* Shared column tracks keep every bar and every input the same width
340
+ regardless of row content; the row wrappers dissolve into the grid. */
333
341
  .hsl-sliders {
334
- display: flex;
335
- flex-direction: column;
336
- gap: var(--ui-space-6);
342
+ display: grid;
343
+ grid-template-columns: 2.5rem minmax(6rem, 1fr) 3.5rem 0.75rem;
344
+ gap: var(--ui-space-6) var(--ui-space-8);
345
+ align-items: center;
337
346
  }
338
347
 
339
348
  .hsl-slider-row {
340
- display: flex;
341
- align-items: center;
342
- gap: var(--ui-space-8);
349
+ display: contents;
343
350
  }
344
351
 
345
352
  .hsl-slider-label {
346
353
  font-size: var(--ui-font-size-md);
347
354
  font-weight: var(--ui-font-weight-semibold);
348
355
  color: var(--ui-text-tertiary);
349
- width: 2.5rem;
350
356
  text-align: right;
351
- flex-shrink: 0;
352
357
  }
353
358
 
354
359
  .slider-track {
@@ -356,8 +361,6 @@
356
361
  height: 1.25rem;
357
362
  border-radius: var(--ui-radius-md);
358
363
  border: 1px solid var(--ui-border-low);
359
- flex: 1;
360
- min-width: 6rem;
361
364
  }
362
365
 
363
366
  .chroma-hint {
@@ -422,9 +425,9 @@
422
425
  font-size: var(--ui-font-size-md);
423
426
  color: var(--ui-text-primary);
424
427
  font-family: var(--ui-font-mono);
425
- width: 2.5rem;
428
+ width: 100%;
429
+ box-sizing: border-box;
426
430
  text-align: right;
427
- flex-shrink: 0;
428
431
  background: var(--ui-surface-lowest);
429
432
  border: 1px solid var(--ui-border-low);
430
433
  border-radius: var(--ui-radius-sm);
@@ -433,10 +436,6 @@
433
436
  appearance: textfield;
434
437
  }
435
438
 
436
- .hsl-slider-input.chroma-input {
437
- width: 3.5rem;
438
- }
439
-
440
439
  .hsl-slider-input::-webkit-inner-spin-button,
441
440
  .hsl-slider-input::-webkit-outer-spin-button {
442
441
  -webkit-appearance: none;
@@ -452,7 +451,5 @@
452
451
  font-size: var(--ui-font-size-md);
453
452
  color: var(--ui-text-muted);
454
453
  font-family: var(--ui-font-mono);
455
- width: 0.75rem;
456
- flex-shrink: 0;
457
454
  }
458
455
  </style>
@@ -116,10 +116,9 @@
116
116
  }
117
117
 
118
118
  .seg-label {
119
- font-size: 10px;
119
+ font-size: var(--ui-font-size-xs);
120
120
  font-weight: var(--ui-font-weight-semibold);
121
- text-transform: uppercase;
122
- color: #fff;
121
+ color: var(--ui-text-primary);
123
122
  padding-left: var(--ui-space-2);
124
123
  margin-bottom: 2px;
125
124
  }
@@ -152,7 +152,7 @@
152
152
  <span class="updated-at" title={file.updatedAt}>{formatUpdatedAt(file.updatedAt)}</span>
153
153
  {/if}
154
154
  {#if file.fileName === activeFileName}
155
- <span class="active-badge">active</span>
155
+ <span class="active-badge">Active</span>
156
156
  {/if}
157
157
  {#if onexport}
158
158
  <button
@@ -207,10 +207,9 @@
207
207
  padding: 4px 0;
208
208
  background: none;
209
209
  border: none;
210
- color: #888;
211
- font-size: 11px;
212
- font-weight: 600;
213
- text-transform: uppercase;
210
+ color: var(--ui-text-tertiary);
211
+ font-size: var(--ui-font-size-xs);
212
+ font-weight: var(--ui-font-weight-semibold);
214
213
  cursor: pointer;
215
214
  text-align: left;
216
215
  }
@@ -312,13 +311,12 @@
312
311
 
313
312
  .system-badge {
314
313
  flex-shrink: 0;
315
- font-size: 11px;
314
+ font-size: var(--ui-font-size-xs);
316
315
  padding: 1px 6px;
317
316
  border-radius: 3px;
318
317
  background: transparent;
319
318
  border: 1px solid #3a3a3a;
320
- color: #888;
321
- text-transform: uppercase;
319
+ color: var(--ui-text-tertiary);
322
320
  }
323
321
 
324
322
  .updated-at {
@@ -331,7 +329,7 @@
331
329
 
332
330
  .active-badge {
333
331
  flex-shrink: 0;
334
- font-size: 12px;
332
+ font-size: var(--ui-font-size-xs);
335
333
  padding: 1px 6px;
336
334
  border-radius: 3px;
337
335
  background: #333;
@@ -343,7 +343,6 @@
343
343
  .mfm-header-label {
344
344
  font-size: var(--ui-font-size-xs);
345
345
  color: var(--ui-text-secondary);
346
- text-transform: uppercase;
347
346
  }
348
347
 
349
348
  .mfm-card {
@@ -380,9 +379,8 @@
380
379
  }
381
380
 
382
381
  .mfm-card-label {
383
- font-size: 10px;
382
+ font-size: var(--ui-font-size-xs);
384
383
  font-weight: var(--ui-font-weight-semibold);
385
- text-transform: uppercase;
386
384
  color: var(--ui-text-tertiary);
387
385
  }
388
386
 
@@ -539,8 +539,6 @@
539
539
  font-size: var(--ui-font-size-xs);
540
540
  font-weight: var(--ui-font-weight-semibold);
541
541
  color: var(--ui-text-tertiary);
542
- text-transform: uppercase;
543
- letter-spacing: 0.04em;
544
542
  }
545
543
  .pf-browse-row {
546
544
  display: flex;
@@ -569,8 +567,6 @@
569
567
  gap: var(--ui-space-8);
570
568
  color: var(--ui-text-tertiary);
571
569
  font-size: var(--ui-font-size-xs);
572
- text-transform: uppercase;
573
- letter-spacing: 0.04em;
574
570
  }
575
571
  .pf-add-divider::before,
576
572
  .pf-add-divider::after {
@@ -290,9 +290,9 @@
290
290
  }
291
291
 
292
292
  .group-title {
293
- font-size: var(--ui-font-size-lg);
294
- font-weight: var(--ui-font-weight-semibold);
295
- color: var(--ui-text-secondary);
293
+ font-size: var(--ui-font-size-xl);
294
+ font-weight: var(--ui-font-weight-bold);
295
+ color: var(--ui-text-primary);
296
296
  margin: 0;
297
297
  }
298
298
 
@@ -23,28 +23,28 @@
23
23
 
24
24
  <div class="ts-controls">
25
25
  <div class="ts-field">
26
- <span class="ts-label">family</span>
26
+ <span class="ts-label">Family</span>
27
27
  <UIFontFamilySelector variable={`${style.prefix}-font-family`} />
28
28
  </div>
29
29
  <div class="ts-field">
30
- <span class="ts-label">size</span>
30
+ <span class="ts-label">Size</span>
31
31
  <UIFontSizeSelector variable={`${style.prefix}-font-size`} />
32
32
  </div>
33
33
  <div class="ts-field">
34
- <span class="ts-label">weight</span>
34
+ <span class="ts-label">Weight</span>
35
35
  <UIFontWeightSelector variable={`${style.prefix}-font-weight`} />
36
36
  </div>
37
37
  <div class="ts-field">
38
- <span class="ts-label">letter spacing</span>
38
+ <span class="ts-label">Letter spacing</span>
39
39
  <UILetterSpacingSelector variable={`${style.prefix}-letter-spacing`} />
40
40
  </div>
41
41
  <div class="ts-field">
42
- <span class="ts-label">line height</span>
42
+ <span class="ts-label">Line height</span>
43
43
  <UILineHeightSelector variable={`${style.prefix}-line-height`} />
44
44
  </div>
45
45
  {#if style.hasTextTransform}
46
46
  <div class="ts-field">
47
- <span class="ts-label">transform</span>
47
+ <span class="ts-label">Transform</span>
48
48
  <UITextTransformSelector variable={`${style.prefix}-text-transform`} />
49
49
  </div>
50
50
  {/if}
@@ -165,8 +165,6 @@
165
165
  .ts-label {
166
166
  font-size: var(--ui-font-size-xs);
167
167
  font-weight: var(--ui-font-weight-medium);
168
- letter-spacing: 0.04em;
169
- text-transform: uppercase;
170
168
  color: var(--ui-text-muted);
171
169
  white-space: nowrap;
172
170
  }
@@ -417,7 +417,7 @@
417
417
  activeFileName={$activeFileName}
418
418
  sortable
419
419
  showUpdatedAt
420
- systemBadge={{ label: 'system', title: 'Protected system theme' }}
420
+ systemBadge={{ label: 'System', title: 'Protected system theme' }}
421
421
  emptyMessage="No saved files"
422
422
  canDelete={(file) => file.fileName !== 'default' && file.fileName !== $themeProductionInfo?.fileName}
423
423
  onload={handleLoad}
@@ -460,7 +460,6 @@
460
460
  .tfm-header-label {
461
461
  font-size: var(--ui-font-size-xs);
462
462
  color: var(--ui-text-secondary);
463
- text-transform: uppercase;
464
463
  }
465
464
 
466
465
  /* Two-card pipeline (Editor → Production) — theme card + production card
@@ -508,7 +507,6 @@
508
507
  .tfm-card-label {
509
508
  font-size: var(--ui-font-size-xs);
510
509
  font-weight: var(--ui-font-weight-semibold);
511
- text-transform: uppercase;
512
510
  color: var(--ui-text-secondary);
513
511
  line-height: 1.1;
514
512
  }