From 49dd5a857e11ffbaae541a1bbd382bb03736e4e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Jul 2026 09:42:36 +0000 Subject: [PATCH 1/4] feat(configurator): light mode for input controls and panels (batch A) Second of a 3-part light-mode rollout, stacked on the shell/infra PR. Applies the theme.svelte.ts system (added in part 1) to every shared input control and the first batch of domain panels. Inputs: ClampField, ColorInput, OklchColorDesk, PowerKnobRow, RangeWithNumber, SliderRow, TokenRow. Panels: AllTokensTab, BordersPanel, CheatsheetPanel, ColorsPanel, EffectsPanel, ExportPanel, GenericTokenPanel, HomePanel. Includes fixes for two review-bot findings on the original combined PR: missing dark: placeholder pairing on the AllTokensTab and GenericTokenPanel search inputs, and missing dark: variants for the motion/form entries in CheatsheetPanel's KIND_COLOR map. --- .../src/components/inputs/ClampField.svelte | 26 +-- .../src/components/inputs/ColorInput.svelte | 10 +- .../components/inputs/OklchColorDesk.svelte | 26 +-- .../src/components/inputs/PowerKnobRow.svelte | 8 +- .../components/inputs/RangeWithNumber.svelte | 6 +- .../src/components/inputs/SliderRow.svelte | 14 +- .../src/components/inputs/TokenRow.svelte | 16 +- .../src/components/panels/AllTokensTab.svelte | 22 +-- .../src/components/panels/BordersPanel.svelte | 50 +++--- .../components/panels/CheatsheetPanel.svelte | 62 +++---- .../src/components/panels/ColorsPanel.svelte | 166 +++++++++--------- .../src/components/panels/EffectsPanel.svelte | 52 +++--- .../src/components/panels/ExportPanel.svelte | 28 +-- .../panels/GenericTokenPanel.svelte | 8 +- .../src/components/panels/HomePanel.svelte | 44 ++--- 15 files changed, 269 insertions(+), 269 deletions(-) diff --git a/configurator/src/components/inputs/ClampField.svelte b/configurator/src/components/inputs/ClampField.svelte index a1d88bd6..5a9835d5 100644 --- a/configurator/src/components/inputs/ClampField.svelte +++ b/configurator/src/components/inputs/ClampField.svelte @@ -77,17 +77,17 @@ ); -
+
- {title} + {title} - clamp({minValue}{unit}{maxValue}{unit}) + clamp({minValue}{unit}{maxValue}{unit})
-
+
{minLabel} onMinChange(clamp(parseFloat((e.target as HTMLInputElement).value) || min))} - class="w-full bg-white/5 border border-white/10 rounded text-[11px] font-mono text-slate-200 text-right px-1.5 py-0.5 focus:outline-none focus:border-indigo-500" /> + class="w-full bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded text-[11px] font-mono text-slate-800 dark:text-slate-200 text-right px-1.5 py-0.5 focus:outline-none focus:border-indigo-500" />
@@ -126,14 +126,14 @@ {#if previewKind === "type"}
Aa{minLabel.toLowerCase()} - Aa{maxLabel.toLowerCase()} + Aa{maxLabel.toLowerCase()}
{:else if previewKind === "space"}
- min + min
- max + max
{/if} @@ -141,7 +141,7 @@ Each breakpoint has its own preset dropdown plus an always-visible custom number input, so a preset can be picked and then fine-tuned per side. --> {#if ratioPresets} -
+
Modular scale ratio
{#each [ @@ -157,7 +157,7 @@ const v = parseFloat((e.target as HTMLSelectElement).value); if (Number.isFinite(v)) row.onChange?.(clampRatio(v)); }} - class="flex-1 min-w-0 bg-white/5 border border-white/10 rounded text-[10px] text-slate-200 px-1.5 py-1 focus:outline-none focus:border-indigo-500 cursor-pointer" + class="flex-1 min-w-0 bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded text-[10px] text-slate-800 dark:text-slate-200 px-1.5 py-1 focus:outline-none focus:border-indigo-500 cursor-pointer" > {#if row.active === undefined} @@ -170,7 +170,7 @@ aria-label={`${row.side} modular scale custom ratio`} type="number" min={ratioMin_bound} max={ratioMax_bound} step={0.001} value={row.value} onchange={(e) => { const n = parseFloat((e.target as HTMLInputElement).value); if (Number.isFinite(n)) row.onChange?.(clampRatio(n)); }} - class="w-16 shrink-0 bg-white/5 border border-white/10 rounded text-[11px] font-mono text-slate-200 text-right px-1.5 py-0.5 focus:outline-none focus:border-indigo-500" + class="w-16 shrink-0 bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded text-[11px] font-mono text-slate-800 dark:text-slate-200 text-right px-1.5 py-0.5 focus:outline-none focus:border-indigo-500" />
{/each} @@ -180,7 +180,7 @@ {#if overridden && onReset}
- +
{/if}
diff --git a/configurator/src/components/inputs/ColorInput.svelte b/configurator/src/components/inputs/ColorInput.svelte index 1a927f52..ffa66e25 100644 --- a/configurator/src/components/inputs/ColorInput.svelte +++ b/configurator/src/components/inputs/ColorInput.svelte @@ -61,7 +61,7 @@
-
+
{#if !isVar} {:else} + {/if}
diff --git a/configurator/src/components/inputs/OklchColorDesk.svelte b/configurator/src/components/inputs/OklchColorDesk.svelte index e63e7fec..efdf3ebe 100644 --- a/configurator/src/components/inputs/OklchColorDesk.svelte +++ b/configurator/src/components/inputs/OklchColorDesk.svelte @@ -50,19 +50,19 @@ } -
+
{#if expanded} -
+
- Lightness (L) - {parsed.l.toFixed(3)} + Lightness (L) + {parsed.l.toFixed(3)}
- Chroma (C) - {parsed.c.toFixed(3)} + Chroma (C) + {parsed.c.toFixed(3)}
- Hue (H) - {parsed.h.toFixed(1)}° + Hue (H) + {parsed.h.toFixed(1)}°
{#if overridden} diff --git a/configurator/src/components/inputs/PowerKnobRow.svelte b/configurator/src/components/inputs/PowerKnobRow.svelte index f6bdaa6b..6d156ca4 100644 --- a/configurator/src/components/inputs/PowerKnobRow.svelte +++ b/configurator/src/components/inputs/PowerKnobRow.svelte @@ -28,15 +28,15 @@
- {knob.label} + {knob.label} {#if knob.driving !== undefined} - → {knob.driving} tokens + → {knob.driving} tokens {/if}
{#if isOverridden} @@ -51,6 +51,6 @@ onChange={handleChange} /> {#if knob.help} -

{knob.help}

+

{knob.help}

{/if}
diff --git a/configurator/src/components/inputs/RangeWithNumber.svelte b/configurator/src/components/inputs/RangeWithNumber.svelte index d21404c4..f0fb6374 100644 --- a/configurator/src/components/inputs/RangeWithNumber.svelte +++ b/configurator/src/components/inputs/RangeWithNumber.svelte @@ -18,7 +18,7 @@
-
+
@@ -47,7 +47,7 @@ {value} {disabled} onchange={(e) => onChange(clamp(parseFloat((e.target as HTMLInputElement).value) || 0))} - class="w-14 bg-white/5 border border-white/10 rounded text-[11px] font-mono text-slate-200 text-right px-1.5 py-0.5 focus:outline-none focus:border-indigo-500 disabled:opacity-40" + class="w-14 bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded text-[11px] font-mono text-slate-800 dark:text-slate-200 text-right px-1.5 py-0.5 focus:outline-none focus:border-indigo-500 disabled:opacity-40" /> {#if unit} {unit} diff --git a/configurator/src/components/inputs/SliderRow.svelte b/configurator/src/components/inputs/SliderRow.svelte index 837dc944..a6c9c0ab 100644 --- a/configurator/src/components/inputs/SliderRow.svelte +++ b/configurator/src/components/inputs/SliderRow.svelte @@ -45,7 +45,7 @@
{#if label} - {label} + {label} {:else} {/if} @@ -56,15 +56,15 @@ title={showRaw ? "Switch to slider" : "Enter raw CSS value"} class={`text-[9px] font-mono cursor-pointer transition-all px-0.5 ${ showRaw - ? 'text-indigo-400' - : 'opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100 focus:opacity-100 text-slate-500 hover:text-indigo-400' + ? 'text-indigo-600 dark:text-indigo-400' + : 'opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100 focus:opacity-100 text-slate-500 hover:text-indigo-600 dark:hover:text-indigo-400' }`} ></> {/if} {#if overridden} {/if}
@@ -85,16 +85,16 @@ const v = rawDraft.trim(); if (v && onRawSet) onRawSet(v); }} - class="w-full bg-white/5 border border-white/10 rounded px-2 py-1.5 text-[11px] font-mono text-slate-300 placeholder:text-slate-500 focus:outline-none focus:border-indigo-500" + class="w-full bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded px-2 py-1.5 text-[11px] font-mono text-slate-700 dark:text-slate-300 placeholder:text-slate-500 focus:outline-none focus:border-indigo-500" /> {:else} {#if rawDefault && !overridden} -

default: {rawDefault}

+

default: {rawDefault}

{/if} {/if} {#if help} -

{help}

+

{help}

{/if}
diff --git a/configurator/src/components/inputs/TokenRow.svelte b/configurator/src/components/inputs/TokenRow.svelte index 464c9f5b..9699b8df 100644 --- a/configurator/src/components/inputs/TokenRow.svelte +++ b/configurator/src/components/inputs/TokenRow.svelte @@ -33,12 +33,12 @@ let swatchColor = $derived(type === "color" ? paintSwatch(displayValue) : ""); -
+
{#if type === "color"}
{:else} @@ -46,11 +46,11 @@ {/if}
-
+
{shortName}
{#if token.description} -
{token.description}
+
{token.description}
{/if}
@@ -67,16 +67,16 @@ if (e.key === "Enter") (e.currentTarget as HTMLInputElement).blur(); if (e.key === "Escape") { expanded = false; } }} - class="w-28 bg-white/8 border border-indigo-500/50 rounded px-1.5 py-0.5 text-[10px] font-mono text-slate-200 focus:outline-none text-right" + class="w-28 bg-black/8 dark:bg-white/8 border border-indigo-500/50 rounded px-1.5 py-0.5 text-[10px] font-mono text-slate-800 dark:text-slate-200 focus:outline-none text-right" /> {:else} diff --git a/configurator/src/components/panels/AllTokensTab.svelte b/configurator/src/components/panels/AllTokensTab.svelte index f78b1c24..a220d861 100644 --- a/configurator/src/components/panels/AllTokensTab.svelte +++ b/configurator/src/components/panels/AllTokensTab.svelte @@ -52,14 +52,14 @@ placeholder="Search tokens…" value={query} oninput={(e) => { query = (e.target as HTMLInputElement).value; }} - class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-[11px] text-slate-200 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" + class="w-full bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-lg px-3 py-1.5 text-[11px] text-slate-800 dark:text-slate-200 placeholder:text-slate-600 dark:placeholder:text-slate-400 focus:outline-none focus:border-indigo-500" />
- + {filtered().length}/{domainTokens.length}
@@ -87,27 +87,27 @@
- Public ({publicCount}) + Public ({publicCount})
- Advanced ({advancedCount}) + Advanced ({advancedCount})
{#if showInternal}
- Internal ({internalCount}) + Internal ({internalCount})
{/if}
-
+
{#if filtered().length === 0} -
+
{onlyModified ? "No modified tokens in this domain" : "No tokens found"}
{:else} diff --git a/configurator/src/components/panels/BordersPanel.svelte b/configurator/src/components/panels/BordersPanel.svelte index 67be49da..f480d283 100644 --- a/configurator/src/components/panels/BordersPanel.svelte +++ b/configurator/src/components/panels/BordersPanel.svelte @@ -103,13 +103,13 @@ onSet={(v) => onSet("--sf-color-border", v)} onReset={() => onReset("--sf-color-border")} /> -

+

Drives --sf-color-border--strong, --subtle, --translucent automatically.

{/if} -
+
@@ -145,10 +145,10 @@ /> -
+
{#each [1, 2, 3, 4] as base (base)}
- {base}px → + {base}px →
-
+
@@ -189,8 +189,8 @@ onclick={() => s === row.default ? onReset(row.token) : onSet(row.token, s)} class={`px-2 py-1.5 rounded-lg text-[10px] border transition-all cursor-pointer text-center ${ current === s - ? "bg-indigo-500/15 border-indigo-500/40 text-indigo-200" - : "border-white/8 text-slate-400 hover:bg-white/5 hover:text-slate-200" + ? "bg-indigo-500/15 border-indigo-500/40 text-indigo-800 dark:text-indigo-200" + : "border-black/8 dark:border-white/8 text-slate-600 dark:text-slate-400 hover:bg-black/5 dark:hover:bg-white/5 hover:text-slate-800 dark:hover:text-slate-200" }`} > @@ -203,19 +203,19 @@
-
+
Border sample
- {(1 * borderScale).toFixed(1)}px · {borderStyle} + {(1 * borderScale).toFixed(1)}px · {borderStyle}
{/if}
-
+
@@ -229,7 +229,7 @@ {#if showDividers}
-
Color
+
Color
-
+
@@ -290,7 +290,7 @@ onReset={() => onReset("--sf-focus-ring-offset")} />
-
Ring color
+
Ring color
-
+
Focus preview @@ -312,7 +312,7 @@ {/if}
-
+
Radius
@@ -322,13 +322,13 @@ {#if showFineTune} -
+
{#each RADIUS_FINE as r (r.name)} {showRadiusPreview ? "▲" : "▼"} {#if showRadiusPreview} -
+
{#each RADIUS_STEPS as step (step)} {@const base = BASE_RADII[step] ?? 6} @@ -370,7 +370,7 @@ class="w-10 h-10 bg-indigo-500/40 border border-indigo-500/30" style={`border-radius: ${Math.min(computed, 9999)}px`} >
- {step} + {step}
{/each}
@@ -378,14 +378,14 @@ {/if}
-
+
-
+
@@ -112,27 +112,27 @@
{#if tab === 'classes'} {#if filteredClasses().length === 0} -
No classes match "{query}"
+
No classes match "{query}"
{:else} {#each [...classGroups.entries()] as [cat, groups] (cat)}
-
{cat}
+
{cat}
{#each [...groups.entries()] as [grp, items] (grp)} {#if grp} -
{grp}
+
{grp}
{/if}
{#each items as cls (cls.name)} -
+
- {cls.selector} + {cls.selector} {#if cls.kind} {cls.kind} {/if} {#if cls.optional} - optional + optional {/if}
{#if cls.description} @@ -142,10 +142,10 @@ {#if showBrandSources} -

+

OKLCH source values — all 200+ derived color steps are computed automatically. Tints (50–400) mix toward Base (the "Surface" color) and shades (600–950) mix toward Text (driven by Neutral) — so editing Base or Neutral below will shift every family's tints/shades too. That's expected, not a bug. @@ -526,8 +526,8 @@ onclick={() => toggleDarkMode(light.colorKey, dark, light.name)} class={`text-[8px] px-1.5 py-0.5 rounded border transition-all cursor-pointer ${ isAutoMode - ? "border-indigo-500/40 bg-indigo-500/15 text-indigo-300" - : "border-white/10 text-slate-500 hover:text-slate-300" + ? "border-indigo-500/40 bg-indigo-500/15 text-indigo-700 dark:text-indigo-300" + : "border-black/10 dark:border-white/10 text-slate-500 hover:text-slate-700 dark:hover:text-slate-300" }`} >{isAutoMode ? "Auto dark" : "Manual dark"} {/if} @@ -552,12 +552,12 @@ {@const dText = getDarkText()}

- L + L
{#each SWATCH_STEPS as step (step)} {@const resolved = paletteSwatch(light.colorKey, lightSrcVal, step, lSurface, lText)}
{@render swatchTip(`${light.colorKey}-${step}`)}
@@ -565,12 +565,12 @@
- D + D
{#each SWATCH_STEPS as step (step)} {@const resolved = paletteSwatch(light.colorKey, darkSrcVal, step, dSurface, dText)}
{@render swatchTip(`${light.colorKey}-${step}`)}
@@ -579,7 +579,7 @@
{#if light.colorKey === "base"} -

+

Absolute lightness ramp — each step pins L and inherits chroma + hue from the source. Not the brand mix curve. Light and dark use the same L per step, so at the default near-zero chroma the two rows look almost identical — add chroma above to see them diverge by hue. @@ -597,9 +597,9 @@ /> {:else if dark && isAutoMode} {@const derivedDark = deriveDarkFromLight(sourceValue(light), light.colorKey)} -

+
@@ -612,7 +612,7 @@ {/if}
-
+
@@ -625,7 +625,7 @@ {showLumlocker ? "▲" : "▼"} {#if showLumlocker} -

+

Pins brand colors (primary, secondary, tertiary, action) to a fixed OKLCH lightness — useful for always-stable sections. Enable on canvas to preview.

@@ -638,8 +638,8 @@ /> -
-
+
+
Color Current Locked @@ -649,10 +649,10 @@ {@const cur = paintTheme(`var(--sf-color-${lk.key}-source-${side})`, side as "light" | "dark", "")} {@const locked = lockedColor(lk.key, side as "light" | "dark")}
- {lk.label} {side === "light" ? "L" : "D"} - - - + {lk.label} {side === "light" ? "L" : "D"} + + +
{/each} {/each} @@ -663,14 +663,14 @@ onclick={() => { lumlockerPreview.value = !lumlockerPreview.value; }} class={`w-full py-2 rounded-lg text-[10px] font-bold border transition-all cursor-pointer ${ lumlockerPreview.value - ? "bg-indigo-600/25 border-indigo-500/40 text-indigo-200" - : "border-white/10 text-slate-400 hover:bg-white/5 hover:text-slate-200" + ? "bg-indigo-600/25 border-indigo-500/40 text-indigo-800 dark:text-indigo-200" + : "border-black/10 dark:border-white/10 text-slate-600 dark:text-slate-400 hover:bg-black/5 dark:hover:bg-white/5 hover:text-slate-800 dark:hover:text-slate-200" }`} >{lumlockerPreview.value ? "✓ LumLocker active on canvas — click to disable" : "Preview LumLocker on live canvas"} {/if}
-
+
@@ -683,7 +683,7 @@ {showStatus ? "▲" : "▼"} {#if showStatus} -

+

Dark mode is auto-derived from each light source by default. Switch to Manual dark to set a bespoke dark value.

@@ -699,8 +699,8 @@ onclick={() => toggleDarkMode(light.colorKey, dark, light.name)} class={`text-[8px] px-1.5 py-0.5 rounded border transition-all cursor-pointer ${ isAutoMode - ? "border-indigo-500/40 bg-indigo-500/15 text-indigo-300" - : "border-white/10 text-slate-500 hover:text-slate-300" + ? "border-indigo-500/40 bg-indigo-500/15 text-indigo-700 dark:text-indigo-300" + : "border-black/10 dark:border-white/10 text-slate-500 hover:text-slate-700 dark:hover:text-slate-300" }`} >{isAutoMode ? "Auto dark" : "Manual dark"} {/if} @@ -724,9 +724,9 @@ /> {:else if dark && isAutoMode} {@const derivedDark = deriveDarkFromLight(sourceValue(light), light.colorKey)} -
+
@@ -740,18 +740,18 @@
{#each STATUS_VARIANTS as v (v.label)} - {v.label} + {v.label} {/each}
{#each [["L", "light"], ["D", "dark"]] as [tag, side] (tag)}
- {tag} + {tag}
{#each STATUS_VARIANTS as v (v.label)} {@const resolved = paintTheme(v.expr(light.colorKey), side as "light" | "dark", "")}
{@render swatchTip(`${light.colorKey}-${v.label.toLowerCase()}`)}
@@ -760,7 +760,7 @@
{/each}
-

+

Status colors don't ride the brand mix curve — only the resolved color plus subtle/muted alpha washes and a strong shade are derived. There's no -50…-950 ramp for success/warning/info/danger.

@@ -770,7 +770,7 @@ {/if}
-
+
@@ -783,7 +783,7 @@ {showTextContrast ? "▲" : "▼"} {#if showTextContrast} -

+

Controls whether text on brand-coloured surfaces is light or dark.

{#each contrastKnobs as k (k.name)} @@ -795,18 +795,18 @@ {/each} -
+
{#each KEY_CONTRAST_PAIRS as pair (pair.label)} {@const ratio = pairRatio(pair.fg, pair.bg)} {#if ratio !== null}
Aa {pair.label} - {ratio.toFixed(2)}:1 - = 4.5 ? "bg-emerald-500/20 text-emerald-300" : "bg-amber-500/20 text-amber-300"}`}> + {ratio.toFixed(2)}:1 + = 4.5 ? "bg-emerald-500/20 text-emerald-700 dark:text-emerald-300" : "bg-amber-500/20 text-amber-700 dark:text-amber-300"}`}> {ratio >= 4.5 ? "AA" : ratio >= 3 ? "AA-L" : "✗"}
@@ -815,7 +815,7 @@ {#if onSelectDomain} {/if}
@@ -831,7 +831,7 @@ {/if}
-
+
@@ -854,7 +854,7 @@ {@const _curveText = curvePreviewSide === "light" ? getLightText() : getDarkText()} {@const _miniSurface = curvePreviewSide === "light" ? getLightSurface() : getDarkSurface()} {@const _miniText = curvePreviewSide === "light" ? getLightText() : getDarkText()} -

+

Controls how much color is mixed into each palette step. Step 500 is always the source color.

@@ -864,8 +864,8 @@ onclick={() => onBulkChange(p.patch as Record)} class={`flex flex-col items-center gap-0.5 p-2.5 rounded-xl border transition-all cursor-pointer ${ activeCurvePreset?.label === p.label - ? "bg-indigo-500/15 border-indigo-500/40 text-indigo-200" - : "border-white/8 text-slate-400 hover:bg-white/5 hover:text-slate-200" + ? "bg-indigo-500/15 border-indigo-500/40 text-indigo-800 dark:text-indigo-200" + : "border-black/8 dark:border-white/8 text-slate-600 dark:text-slate-400 hover:bg-black/5 dark:hover:bg-white/5 hover:text-slate-800 dark:hover:text-slate-200" }`} > {p.label} @@ -876,7 +876,7 @@
-
Tints (50–400) — mixed toward surface
+
Tints (50–400) — mixed toward surface
{#each MIX_STEPS.filter((s) => s.step === "tint") as s (s.name)}
-
Shades (600–950) — mixed toward text
+
Shades (600–950) — mixed toward text
{#each MIX_STEPS.filter((s) => s.step === "shade") as s (s.name)} -
+
- Palette & curve preview -
+ Palette & curve preview +
{#each ["light", "dark"] as side (side)} {/each} @@ -930,29 +930,29 @@ {@const step = s.label} {@const swatch = computePaletteSwatch(_curvePrimSrc, step, _curveSurface, _curveText)}
- {Math.round(val)} + {Math.round(val)}
{@render swatchTip(`primary-${step}`)}
- {step} + {step}
{/each}
-

Bar height = mix amount · fill = the resolved primary palette swatch at each step

+

Bar height = mix amount · fill = the resolved primary palette swatch at each step

-
+
- Mini palettes — all brand colors -
+ Mini palettes — all brand colors +
{#each ["light", "dark"] as side (side)} {/each} @@ -966,12 +966,12 @@ : (BRAND_SOURCES.find(s => s.name === miniDOvKey)?.default ?? deriveDarkFromLight(miniLSrc, colorKey)))} {@const miniSrc = curvePreviewSide === "light" ? miniLSrc : miniDSrc}
- {colorKey} + {colorKey}
{#each SWATCH_STEPS as step (step)} {@const swatch = computePaletteSwatch(miniSrc, step, _miniSurface, _miniText)}
{@render swatchTip(`${colorKey}-${step}`)}
@@ -983,7 +983,7 @@ {/if}
-
+
@@ -996,7 +996,7 @@ {showGradients ? "▲" : "▼"} {#if showGradients} -

+

Auto-derived from your brand colors. Use the angle/direction and stop controls, or edit the raw value for full control.

@@ -1006,13 +1006,13 @@ {#each GRADIENT_TOKENS.filter((g) => g.group === grp) as g (g.name)} {@const expr = `var(${g.name})`} {@const e = gradEdit(g)} -
+
- {g.label} + {g.label} {#if g.name in overrides} - + {/if}
@@ -1024,7 +1024,7 @@ {/each} @@ -1037,7 +1037,7 @@ oninput={(ev) => setGradientPart(g, "angle", parseInt((ev.target as HTMLInputElement).value))} class="flex-1 accent-indigo-500" /> - {e.angle}° + {e.angle}°
{/if} @@ -1046,17 +1046,17 @@ setGradientPart(g, "stop1", (ev.target as HTMLInputElement).value.trim())} - class="flex-1 min-w-0 bg-white/5 border border-white/10 rounded px-1.5 py-0.5 text-[8px] font-mono text-slate-300 focus:outline-none focus:border-indigo-500" + class="flex-1 min-w-0 bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded px-1.5 py-0.5 text-[8px] font-mono text-slate-700 dark:text-slate-300 focus:outline-none focus:border-indigo-500" /> setGradientPart(g, "stop2", (ev.target as HTMLInputElement).value.trim())} - class="flex-1 min-w-0 bg-white/5 border border-white/10 rounded px-1.5 py-0.5 text-[8px] font-mono text-slate-300 focus:outline-none focus:border-indigo-500" + class="flex-1 min-w-0 bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded px-1.5 py-0.5 text-[8px] font-mono text-slate-700 dark:text-slate-300 focus:outline-none focus:border-indigo-500" />
-
+
default: {g.formula}
@@ -1070,7 +1070,7 @@ delete gradientEdits[g.name]; v ? onSet(g.name, v) : onReset(g.name); }} - class="w-full bg-white/5 border border-white/10 rounded px-1.5 py-0.5 text-[9px] font-mono text-slate-300 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" + class="w-full bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded px-1.5 py-0.5 text-[9px] font-mono text-slate-700 dark:text-slate-300 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" />
@@ -1080,7 +1080,7 @@ {/if}
-
+
@@ -1093,8 +1093,8 @@ {showColorScheme ? "▲" : "▼"} {#if showColorScheme} -

- --sf-color-scheme — sets the CSS color-scheme property on the root, controlling browser UI (scrollbars, inputs) and default background. +

+ --sf-color-scheme — sets the CSS color-scheme property on the root, controlling browser UI (scrollbars, inputs) and default background.

{#each [["light dark", "light dark (default)"], ["light", "Light only"], ["dark", "Dark only"]] as [val, label] (val)} @@ -1103,8 +1103,8 @@ onclick={() => val === "light dark" ? onReset("--sf-color-scheme") : onSet("--sf-color-scheme", val)} class={`flex-1 py-1.5 rounded-lg text-[10px] border transition-all cursor-pointer ${ current === val - ? "bg-indigo-500/15 border-indigo-500/40 text-indigo-200" - : "border-white/8 text-slate-400 hover:bg-white/5 hover:text-slate-200" + ? "bg-indigo-500/15 border-indigo-500/40 text-indigo-800 dark:text-indigo-200" + : "border-black/8 dark:border-white/8 text-slate-600 dark:text-slate-400 hover:bg-black/5 dark:hover:bg-white/5 hover:text-slate-800 dark:hover:text-slate-200" }`} >{label} {/each} @@ -1112,7 +1112,7 @@ {/if}
-
+
@@ -1125,14 +1125,14 @@ {showSemanticOverrides ? "▲" : "▼"} {#if showSemanticOverrides} -

+

Direct color overrides for tokens the framework auto-derives. Use for edge cases.

{#each SEMANTIC_OVERRIDES as s (s.name)} {@const resolved = paint(`var(${s.name})`, "")}
-
{s.label}
+
{s.label}
-
+

- All tokens tab — override individual color steps (50–950), semantic aliases, or surface tokens directly. + All tokens tab — override individual color steps (50–950), semantic aliases, or surface tokens directly.

diff --git a/configurator/src/components/panels/EffectsPanel.svelte b/configurator/src/components/panels/EffectsPanel.svelte index 9f565690..338fa6c2 100644 --- a/configurator/src/components/panels/EffectsPanel.svelte +++ b/configurator/src/components/panels/EffectsPanel.svelte @@ -66,23 +66,23 @@ onReset={() => onReset("--sf-blur")} /> -
+
{#each Array.from({ length: 12 }) as _, i (i)}
{/each}
- {blur}px + {blur}px
{/if}
-
+
@@ -117,14 +117,14 @@ onReset={() => onReset("--sf-state-pending-opacity")} /> -
+
{#each [ { label: "muted", val: opacityMuted }, { label: "disabled", val: opacityDisabled }, { label: "pending", val: pendingOpacity }, ] as row (row.label)}
- {row.label} + {row.label}
{row.val}
@@ -133,7 +133,7 @@ {/if}
-
+
@@ -146,13 +146,13 @@ {showScrim ? "▲" : "▼"} {#if showScrim} -

- Gradient overlay for hero images and media content. Used by the .sf-scrim macro. +

+ Gradient overlay for hero images and media content. Used by the .sf-scrim macro.

-
Color
+
Color
-
Direction
+
Direction
{#each SCRIM_DIRECTIONS as d (d.value)}
-
+
-
+
@@ -215,7 +215,7 @@ { label: "Track color", token: "--sf-scrollbar-track", val: scrollbarTrack }, ] as row (row.token)}
-
{row.label}
+
{row.label}
{/each} -
+
- Scrollbar preview + Scrollbar preview
{/if}
-
+
@@ -259,7 +259,7 @@ {/if}
-
+
@@ -272,14 +272,14 @@ {showTextShadow ? "▲" : "▼"} {#if showTextShadow} -

- Controls text legibility over images. Applied via text-shadow: var(--sf-text-shadow-m). - Edit each token directly — use none to disable. +

+ Controls text legibility over images. Applied via text-shadow: var(--sf-text-shadow-m). + Edit each token directly — use none to disable.

{#each TEXT_SHADOW_TOKENS as t (t.token)}
-
{t.label}
+
{t.label}
{#if t.token in overrides} - + {/if}
{/each} diff --git a/configurator/src/components/panels/ExportPanel.svelte b/configurator/src/components/panels/ExportPanel.svelte index c25ef76a..07cbdefc 100644 --- a/configurator/src/components/panels/ExportPanel.svelte +++ b/configurator/src/components/panels/ExportPanel.svelte @@ -72,7 +72,7 @@
Export
-

+

{count > 0 ? `${count} override${count !== 1 ? "s" : ""} ready to export.` : "No overrides yet. Customise tokens, then export."} @@ -83,11 +83,11 @@ @@ -115,22 +115,22 @@

-
{css || "/* No overrides */"}
+
{css || "/* No overrides */"}
-
Usage
-
+
Usage
+
Paste this CSS into your project after the SLASHED import.
{`@import 'slashed/slashed.full.css';
diff --git a/configurator/src/components/panels/GenericTokenPanel.svelte b/configurator/src/components/panels/GenericTokenPanel.svelte
index 40d72fbc..317c56ab 100644
--- a/configurator/src/components/panels/GenericTokenPanel.svelte
+++ b/configurator/src/components/panels/GenericTokenPanel.svelte
@@ -33,7 +33,7 @@
   
{domain} tokens
{#if modifiedCount > 0} - {modifiedCount} modified + {modifiedCount} modified {/if}
@@ -42,11 +42,11 @@ placeholder={`Search ${domain} tokens…`} value={query} oninput={(e) => { query = (e.target as HTMLInputElement).value; }} - class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-[11px] text-slate-200 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" + class="w-full bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-lg px-3 py-1.5 text-[11px] text-slate-800 dark:text-slate-200 placeholder:text-slate-600 dark:placeholder:text-slate-400 focus:outline-none focus:border-indigo-500" /> {#if filtered().length === 0} -

No tokens found

+

No tokens found

{:else}
{#each filtered().slice(0, 60) as t (t.name)} @@ -58,7 +58,7 @@ /> {/each} {#if filtered().length > 60} -

+

{filtered().length - 60} more — refine your search

{/if} diff --git a/configurator/src/components/panels/HomePanel.svelte b/configurator/src/components/panels/HomePanel.svelte index 3a695283..48218a06 100644 --- a/configurator/src/components/panels/HomePanel.svelte +++ b/configurator/src/components/panels/HomePanel.svelte @@ -68,13 +68,13 @@
- - SLASHED Studio + + SLASHED Studio
-

+

Configure every SLASHED design token visually. Changes appear live in the preview. {#if overridesCount > 0} - You have {overridesCount} active override{overridesCount !== 1 ? "s" : ""}. + You have {overridesCount} active override{overridesCount !== 1 ? "s" : ""}. {:else} Pick any domain below to get started. {/if} @@ -82,7 +82,7 @@ {#if overridesCount > 0} @@ -98,19 +98,19 @@ {@const DomainIcon = d.icon} {/each}

@@ -124,16 +124,16 @@ {@const ToolIcon = t.icon} {/each}
@@ -145,7 +145,7 @@
Saved themes
@@ -155,28 +155,28 @@ bind:value={newName} placeholder="Save current as…" onkeydown={(e) => { if (e.key === "Enter") handleQuickSave(); }} - class="flex-1 min-w-0 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-[11px] text-slate-200 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" + class="flex-1 min-w-0 bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-lg px-2 py-1.5 text-[11px] text-slate-800 dark:text-slate-200 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" />
{#if savedThemes.length === 0} -

No saved themes yet. Configure tokens, then save.

+

No saved themes yet. Configure tokens, then save.

{:else}
{#each savedThemes.slice(0, 6) as theme (theme.id)} From 3d5fedb444d95a0ca001056d87e56192e512ba1e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Jul 2026 09:43:04 +0000 Subject: [PATCH 2/4] feat(configurator): light mode for remaining panels (batch B) Third and final part of the light-mode rollout, stacked on batch A. Applies the theme.svelte.ts system to the remaining domain panels: LayoutPanel, MacrosPanel, MiscPanel, MotionPanel, ShadowsPanel, SpacingPanel, ThemesPanel, TypographyPanel, WcagPanel. Includes a fix for a review-bot finding on the original combined PR: LayoutPanel's two