From 26e5efb627e55bd395b4478ff8f8ef68c918889c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Jul 2026 07:12:53 +0000 Subject: [PATCH] feat(configurator): add light mode to the Studio chrome The Studio UI was hardcoded dark-only. Adds a light/dark theme toggle (header) that defaults to and live-follows prefers-color-scheme until the user picks explicitly, persisted to localStorage. Every dark-only Tailwind utility across the shell and panels now pairs with a light-mode base value plus a `dark:` variant, using Tailwind v4's class-based custom variant keyed off a `.dark` class applied to the mount root before first paint (no flash of the wrong theme). Fixed backgrounds used purely as content previews (scrim/caption demos, tooltip chips) were deliberately left unmirrored since they represent color values, not app chrome. --- configurator/src/App.svelte | 14 +- configurator/src/app.css | 5 + .../src/components/CommandPalette.svelte | 32 ++-- .../src/components/DomainPanel.svelte | 12 +- .../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 | 58 +++--- .../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 ++--- .../src/components/panels/LayoutPanel.svelte | 110 +++++------ .../src/components/panels/MacrosPanel.svelte | 82 ++++----- .../src/components/panels/MiscPanel.svelte | 146 +++++++-------- .../src/components/panels/MotionPanel.svelte | 72 ++++---- .../src/components/panels/ShadowsPanel.svelte | 36 ++-- .../src/components/panels/SpacingPanel.svelte | 16 +- .../src/components/panels/ThemesPanel.svelte | 36 ++-- .../components/panels/TypographyPanel.svelte | 172 +++++++++--------- .../src/components/panels/WcagPanel.svelte | 60 +++--- .../src/components/shell/PreviewPanel.svelte | 40 ++-- .../src/components/shell/SidebarNav.svelte | 8 +- .../src/components/shell/StatusBar.svelte | 8 +- .../src/components/shell/StudioHeader.svelte | 53 ++++-- configurator/src/lib/theme.svelte.ts | 67 +++++++ configurator/src/main.ts | 4 + 34 files changed, 799 insertions(+), 708 deletions(-) create mode 100644 configurator/src/lib/theme.svelte.ts diff --git a/configurator/src/App.svelte b/configurator/src/App.svelte index 9a256f18..b7495586 100644 --- a/configurator/src/App.svelte +++ b/configurator/src/App.svelte @@ -261,7 +261,7 @@ onclick={() => { mobileView = view; }} aria-pressed={mobileView === view} class={`flex-1 flex items-center justify-center gap-1.5 py-2.5 text-[11px] font-bold transition-colors cursor-pointer ${ - mobileView === view ? "text-indigo-300 bg-indigo-500/10" : "text-slate-500 hover:text-slate-300" + mobileView === view ? "text-indigo-700 dark:text-indigo-300 bg-indigo-500/10" : "text-slate-500 hover:text-slate-700 dark:hover:text-slate-300" }`} > {label} @@ -271,7 +271,7 @@ {/snippet} -
+
-
+
{@render foldToggleButton("controls", SlidersHorizontal, "Controls")} {@render foldToggleButton("preview", Eye, "Preview")}
@@ -309,10 +309,10 @@ -
+
-
- +
+ {DOMAIN_LABELS[domain] ?? domain} {#if domainOverridesCount > 0} @@ -320,7 +320,7 @@ onclick={handleResetDomain} data-testid="reset-category" title={`Reset ${domainOverridesCount} override${domainOverridesCount !== 1 ? "s" : ""} in ${DOMAIN_LABELS[domain] ?? domain}`} - class="flex items-center gap-1 px-1.5 py-0.5 rounded-md text-[9px] font-bold text-slate-500 hover:text-rose-400 hover:bg-rose-500/10 transition-colors cursor-pointer shrink-0" + class="flex items-center gap-1 px-1.5 py-0.5 rounded-md text-[9px] font-bold text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 hover:bg-rose-500/10 transition-colors cursor-pointer shrink-0" > Reset {domainOverridesCount} diff --git a/configurator/src/app.css b/configurator/src/app.css index f1d8c73c..75b41109 100644 --- a/configurator/src/app.css +++ b/configurator/src/app.css @@ -1 +1,6 @@ @import "tailwindcss"; + +/* Studio chrome theme — toggled by lib/theme.svelte.ts via a `.dark` class + on the mount root, independent of the framework's own [data-theme] + (which only governs the dogfooded --sf-* tokens loaded below). */ +@custom-variant dark (&:where(.dark, .dark *)); diff --git a/configurator/src/components/CommandPalette.svelte b/configurator/src/components/CommandPalette.svelte index 60aa4caa..afbde580 100644 --- a/configurator/src/components/CommandPalette.svelte +++ b/configurator/src/components/CommandPalette.svelte @@ -76,9 +76,9 @@ onmousedown={(e) => { if (e.target === e.currentTarget) onClose(); }} > -
+
-
+
@@ -87,42 +87,42 @@ bind:value={query} onkeydown={handleKeydown} placeholder="Search tokens… (e.g. radius, primary, duration)" - class="flex-1 bg-transparent text-[13px] text-slate-100 placeholder-slate-600 outline-none" + class="flex-1 bg-transparent text-[13px] text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-600 outline-none" /> - Esc + Esc
{#if query.trim() && results.length === 0} -
No tokens matching "{query}"
+
No tokens matching "{query}"
{:else if !query.trim()} -
Type to search tokens by name or description
+
Type to search tokens by name or description
{:else} {#each results as r, i (r.token.name)}
-
+
-
+
+
{/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..45ace287 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 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..6310a705 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 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)} diff --git a/configurator/src/components/panels/LayoutPanel.svelte b/configurator/src/components/panels/LayoutPanel.svelte index 89b8f2c7..8eddc9f3 100644 --- a/configurator/src/components/panels/LayoutPanel.svelte +++ b/configurator/src/components/panels/LayoutPanel.svelte @@ -90,7 +90,7 @@
-
Narrow
+
Narrow
-
Prose
+
Prose
-
Default
+
Default
-
Wide
+
Wide
-
+
{#each [ { label: "narrow", value: containerNarrow, max: containerWide, unit: "rem" }, { label: "prose", value: containerProse, max: containerWide * 1.5, unit: "ch" }, @@ -136,8 +136,8 @@ { label: "wide", value: containerWide, max: containerWide, unit: "rem" }, ] as row (row.label)}
- {row.label} -
+ {row.label} +
-
+
@@ -186,7 +186,7 @@ {/if}
-
+
@@ -208,7 +208,7 @@ />
-
Named sizes (grid-min-xs → 2xl)
+
Named sizes (grid-min-xs → 2xl)
{#each [ { label: "xs", token: "--sf-grid-min-xs", def: 10 }, @@ -227,21 +227,21 @@ {/each}
-
-
Preview at 360px panel width
+
+
Preview at 360px panel width
{#each Array.from({ length: 8 }) as _, i (i)} -
col
+
col
{/each}
{/if}
-
+
@@ -256,7 +256,7 @@ {#if showHeaderHeight}
-
Mobile
+
Mobile
-
Desktop
+
Desktop
-
+
{#each [1, 2, 3] as i (i)} -
+
{/each}
-
{headerMobile}rem mobile · {headerDesktop}rem desktop
+
{headerMobile}rem mobile · {headerDesktop}rem desktop
{/if}
-
+
@@ -304,10 +304,10 @@ {showStickyOffset ? "▲" : "▼"} {#if showStickyOffset} -

Offsets position:sticky elements below a fixed header. Defaults to header heights.

+

Offsets position:sticky elements below a fixed header. Defaults to header heights.

-
Mobile
+
Mobile
-
Desktop
+
Desktop
-
+
@@ -354,26 +354,26 @@ onChange={(v) => onSet("--sf-sidebar-width", `${v}rem`)} onReset={() => onReset("--sf-sidebar-width")} /> -
+
- {sidebarWidth}rem + {sidebarWidth}rem
-
+
{/if}
-
+
-
+
@@ -401,7 +401,7 @@ onclick={() => { showMore = !showMore; }} aria-expanded={showMore} aria-controls="more-layout-primitives" - class="w-full flex items-center justify-between text-[10px] font-semibold text-slate-400 hover:text-slate-200 transition-colors cursor-pointer py-1" + class="w-full flex items-center justify-between text-[10px] font-semibold text-slate-600 dark:text-slate-400 hover:text-slate-800 dark:hover:text-slate-200 transition-colors cursor-pointer py-1" > More layout primitives {showMore ? "▲" : "▼"} @@ -441,21 +441,21 @@ { label: "Justify", token: "--sf-cluster-justify", def: "flex-start", opts: ["flex-start","center","flex-end","space-between","space-around"] }, ] as row (row.token)}
- {row.label} + {row.label} {#if row.token in overrides} - + {/if}
{/each} @@ -472,7 +472,7 @@ onReset={() => onReset("--sf-equal-min-col")} />
-
Named column counts (equal-min-col-2/3/4/6)
+
Named column counts (equal-min-col-2/3/4/6)
{#each [ { label: "2-col", token: "--sf-equal-min-col-2", def: 28 }, @@ -506,15 +506,15 @@
Frame ratio
-

--sf-frame-ratio — aspect ratio for .sf-frame

+

--sf-frame-ratio — aspect ratio for .sf-frame

{#each FRAME_PRESETS as r (r)} {/each} @@ -524,21 +524,21 @@
Reel
-

--sf-reel-item-width — width of each item in a horizontal .sf-reel

+

--sf-reel-item-width — width of each item in a horizontal .sf-reel

{#each REEL_PRESETS as p (p.value)} {/each}
- Height + Height {#if "--sf-reel-height" in overrides} - + {/if}
@@ -613,7 +613,7 @@ {/if}
-
+
@@ -627,7 +627,7 @@ {#if showNamedRatios}
-

Override the built-in named aspect ratios used by .sf-ratio-* utilities.

+

Override the built-in named aspect ratios used by .sf-ratio-* utilities.

{#each [ { label: "Square", token: "--sf-ratio-square", def: "1" }, { label: "Portrait", token: "--sf-ratio-portrait", def: "3 / 4" }, @@ -638,7 +638,7 @@ { label: "Golden", token: "--sf-ratio-golden", def: "1.618 / 1" }, ] as r (r.token)}
- {r.label} + {r.label} {#if r.token in overrides} - + {/if}
{/each} @@ -658,7 +658,7 @@ {/if}
-
+
@@ -672,27 +672,27 @@ {#if showBgLayer}
-

Default values for the .sf-bg full-bleed background utility.

+

Default values for the .sf-bg full-bleed background utility.

{#each [ { label: "Fit", token: "--sf-bg-fit", placeholder: "cover", opts: ["cover","contain","fill","none","scale-down"] }, { label: "Position", token: "--sf-bg-position", placeholder: "50% 50%", opts: ["50% 50%","top","bottom","left","right","center"] }, ] as row (row.token)}
- {row.label} + {row.label} {#if row.token in overrides} - + {/if}
{/each} diff --git a/configurator/src/components/panels/MacrosPanel.svelte b/configurator/src/components/panels/MacrosPanel.svelte index 62203918..577a59a0 100644 --- a/configurator/src/components/panels/MacrosPanel.svelte +++ b/configurator/src/components/panels/MacrosPanel.svelte @@ -70,11 +70,11 @@
-

- Tokens for the framework's utility macros — .sf-prose, - .sf-flow, .sf-line-clamp, - .sf-aspect, .sf-scroll-shadow - and .sf-scrim. +

+ Tokens for the framework's utility macros — .sf-prose, + .sf-flow, .sf-line-clamp, + .sf-aspect, .sf-scroll-shadow + and .sf-scrim.

@@ -98,7 +98,7 @@ currentRaw={overrides["--sf-flow-space"]} onRawSet={(v) => onSet("--sf-flow-space", v)} /> -
+
{#each [0, 1, 2] as i (i)}
{/each} @@ -106,7 +106,7 @@ {/if}
-
+
@@ -126,9 +126,9 @@ onChange={(v) => onSet("--sf-line-clamp", String(v))} onReset={() => onReset("--sf-line-clamp")} /> -
+

SLASHED is a token-first CSS framework. This sample paragraph repeats so you @@ -140,7 +140,7 @@ {/if}

-
+
@@ -153,29 +153,29 @@ {showAspect ? "▲" : "▼"} {#if showAspect} -

--sf-aspect — default ratio for .sf-aspect / .sf-frame

+

--sf-aspect — default ratio for .sf-aspect / .sf-frame

{#each ASPECT_PRESETS as p (p.value)} {/each}
-
+
{aspectVal}
{/if}
-
+
@@ -198,7 +198,7 @@ currentRaw={overrides["--sf-scroll-shadow-size"]} onRawSet={(v) => onSet("--sf-scroll-shadow-size", v)} /> -
+
-
+
@@ -220,9 +220,9 @@ {showScrim ? "▲" : "▼"} {#if showScrim} -

--sf-scrim-color / --sf-scrim-direction — darkening gradient for .sf-scrim

+

--sf-scrim-color / --sf-scrim-direction — darkening gradient for .sf-scrim

-
Color
+
Color
{#if "--sf-scrim-color" in overrides} - + {/if}
-
Direction
+
Direction
{#each SCRIM_DIRECTIONS as d (d.value)} {/each}
-
Caption over scrim
-
Text shadow
+
Text shadow
{#if "--sf-scrim-text-shadow" in overrides} - + {/if}
{/if}
-
+
@@ -289,7 +289,7 @@ {showProse ? "▲" : "▼"} {#if showProse} -

Spacing tokens for .sf-prose. Defaults track the space scale; sliders write rem overrides.

+

Spacing tokens for .sf-prose. Defaults track the space scale; sliders write rem overrides.

{#each PROSE_SPACE as t (t.token)} onSet("--sf-prose-media-radius", v)} />
-
Marker color
+
Marker color
{#if "--sf-prose-marker-color" in overrides} - + {/if}
@@ -344,11 +344,11 @@ {showContentIntrinsic ? "▲" : "▼"} {#if showContentIntrinsic} -

- --sf-content-intrinsic-size — placeholder size used with content-visibility: auto to improve scroll performance on tall pages. +

+ --sf-content-intrinsic-size — placeholder size used with content-visibility: auto to improve scroll performance on tall pages.

-
Intrinsic size
+
Intrinsic size
{#if "--sf-content-intrinsic-size" in overrides} - + {/if}
{/if}
-
+

- All tokens tab — edit every prose, scrim and surface token directly. + All tokens tab — edit every prose, scrim and surface token directly.

diff --git a/configurator/src/components/panels/MiscPanel.svelte b/configurator/src/components/panels/MiscPanel.svelte index e8fc861a..faa73823 100644 --- a/configurator/src/components/panels/MiscPanel.svelte +++ b/configurator/src/components/panels/MiscPanel.svelte @@ -88,19 +88,19 @@ onReset={() => onReset("--sf-touch-target")} /> -
+
{touchTarget}px
-

Minimum interactive area — ensures accessibility on touch devices.

+

Minimum interactive area — ensures accessibility on touch devices.

{/if}
-
+
@@ -119,8 +119,8 @@ onclick={() => b.value === "smooth" ? onReset("--sf-scroll-behavior") : onSet("--sf-scroll-behavior", b.value)} class={`flex-1 py-2 rounded-lg text-[10px] border transition-all cursor-pointer ${ scrollBehavior === b.value - ? "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" }`} > {b.label} @@ -130,7 +130,7 @@ {/if}
-
+
@@ -150,22 +150,22 @@ onChange={(v) => onSet("--sf-z-base", String(v))} onReset={() => onReset("--sf-z-base")} /> -
+
{#each Z_INDEX_STEPS as z (z.token)} {@const isOverridden = z.token in overrides}
-
- {z.label} - {z.note} +
+ {z.label} + {z.note} {z.token.replace("--sf-z-", "")}
{/each}
-

Override individual z-index tokens in the "All tokens" tab.

+

Override individual z-index tokens in the "All tokens" tab.

{/if}
-
+
@@ -180,7 +180,7 @@ {#if showTextSelection}
-
Selection background
+
Selection background
-
Selection text color
+
Selection text color
-
+

Select this text to preview the selection color. @@ -214,7 +214,7 @@ {/if}

-
+
@@ -234,8 +234,8 @@ onclick={() => style === "solid" ? onReset("--sf-focus-ring-style") : onSet("--sf-focus-ring-style", style)} class={`flex-1 py-2 rounded-lg text-[10px] border transition-all cursor-pointer capitalize ${ current === style - ? "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" }`} > {style} @@ -243,9 +243,9 @@ {/each}
-
+
Focus ring · {focusRingStyle} @@ -254,7 +254,7 @@ {/if}
-
+
@@ -267,7 +267,7 @@ {showComponentSizes ? "▲" : "▼"} {#if showComponentSizes} -

+

Controls the height / tap-target of all sized components (buttons, inputs, badges).

@@ -283,7 +283,7 @@ {/each}
-
+
{#each SIZE_TOKENS as s (s.token)} {@const val = getSizeValue(s)}
@@ -291,14 +291,14 @@ class="w-full bg-indigo-500/30 border border-indigo-500/30 rounded flex items-center justify-center" style={`height: ${Math.min(val * 14, 64)}px`} >
- {s.label} + {s.label}
{/each}
{/if}
-
+
@@ -312,7 +312,7 @@ {#if showCaretLinks}
-
Caret color
+
Caret color
- Underline thickness + Underline thickness {#if "--sf-link-underline-thickness" in overrides} - + {/if}
@@ -342,26 +342,26 @@ onclick={() => val === "auto" ? onReset("--sf-link-underline-thickness") : onSet("--sf-link-underline-thickness", val)} class={`flex-1 py-1.5 rounded-lg text-[10px] border transition-all cursor-pointer ${ underlineThickness === 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}
-
-
+
@@ -382,8 +382,8 @@ {showIconSizes ? "▲" : "▼"} {#if showIconSizes} -

- Icon scale tokens used by .sf-icon-* utilities. Values are in em units relative to surrounding text. +

+ Icon scale tokens used by .sf-icon-* utilities. Values are in em units relative to surrounding text.

{#each [ @@ -412,7 +412,7 @@ {/if}
-
+
@@ -425,24 +425,24 @@ {showObjectFit ? "▲" : "▼"} {#if showObjectFit} -

Default values for .sf-media images and replaced elements.

+

Default values for .sf-media images and replaced elements.

- Fit + Fit
{#each ["cover","contain","fill","none"] as v (v)} {@const cur = overrides["--sf-object-fit"] ?? "cover"} {/each}
{#if "--sf-object-fit" in overrides} - + {/if}
- Position + Position {#if "--sf-object-position" in overrides} - + {/if}
{/if}
-
+
@@ -473,8 +473,8 @@ {showSafeArea ? "▲" : "▼"} {#if showSafeArea} -

- Default to the device's env(safe-area-inset-*). Override only if you need fixed padding regardless of notch/home-indicator. +

+ Default to the device's env(safe-area-inset-*). Override only if you need fixed padding regardless of notch/home-indicator.

{#each [ @@ -484,7 +484,7 @@ { label: "Left", token: "--sf-safe-left", placeholder: "env(safe-area-inset-left, 0px)" }, ] as row (row.token)}
- {row.label} + {row.label} {#if row.token in overrides} - + {/if}
{/each} @@ -504,7 +504,7 @@ {/if}
-
+
@@ -517,15 +517,15 @@ {showPrint ? "▲" : "▼"} {#if showPrint} -

Applied inside @media print via optional/print.css.

+

Applied inside @media print via optional/print.css.

- Page size + Page size
{#each ["a4","letter","legal","a3"] as v (v)} {@const cur = overrides["--sf-print-page-size"] ?? "a4"} {/each}
@@ -535,7 +535,7 @@ { label: "Page margin", token: "--sf-print-page-margin", placeholder: "2cm" }, ] as row (row.token)}
- {row.label} + {row.label} {#if row.token in overrides} - + {/if}
{/each} {/if}
-
+
@@ -567,7 +567,7 @@ {showStateFlags ? "▲" : "▼"} {#if showStateFlags} -

+

Boolean CSS custom property flags (0 = off, 1 = on). Components toggle these to activate state-specific styles. Overriding globally forces all matching elements into that state — useful for testing and demos.

@@ -585,20 +585,20 @@ aria-label={f.label} aria-pressed={cur === "1"} onclick={() => cur === "1" ? onReset(f.token) : onSet(f.token, "1")} - class={`w-8 h-4 rounded-full transition-colors relative cursor-pointer shrink-0 ${cur === "1" ? "bg-indigo-600" : "bg-white/10"}`} + class={`w-8 h-4 rounded-full transition-colors relative cursor-pointer shrink-0 ${cur === "1" ? "bg-indigo-600" : "bg-black/10 dark:bg-white/10"}`} >
- {f.label} - {f.note} + {f.label} + {f.note} {#if f.token in overrides} - + {/if}
{/each}
- Required marker + Required marker {#if "--sf-field-required-marker" in overrides} - + {/if}
{/if}
-
+
-
+

Selection colors, caret, links, focus ring, borders and sizes all render in the - live preview (try the Components template). - Scroll behavior applies to the page + live preview (try the Components template). + Scroll behavior applies to the page itself and can't be shown in the static canvas.

diff --git a/configurator/src/components/panels/MotionPanel.svelte b/configurator/src/components/panels/MotionPanel.svelte index b25dff18..172b6820 100644 --- a/configurator/src/components/panels/MotionPanel.svelte +++ b/configurator/src/components/panels/MotionPanel.svelte @@ -94,9 +94,9 @@
-
+
-
Disable motion
+
Disable motion
Respects prefers-reduced-motion
-
+
@@ -175,9 +175,9 @@ {showDurations ? "▲" : "▼"} {#if showDurations} -

+

Named duration tokens. Drag to set an absolute ms value, overriding the global scale. - {#if motionDisabled} (No effect while motion is disabled.){/if} + {#if motionDisabled} (No effect while motion is disabled.){/if}

{#each DURATIONS as d (d.token)} {@const computed = getDuration(d.token, d.base)} @@ -194,12 +194,12 @@ /> {/each} -
+
{#each DURATIONS as d (d.label)} {@const actual = getDuration(d.token, d.base)}
{d.label} -
+
{actual}ms @@ -209,7 +209,7 @@ {/if}
-
+
@@ -222,16 +222,16 @@ {showEasing ? "▲" : "▼"} {#if showEasing} -

+

Edit the named easing tokens. Accepts any CSS timing function — - cubic-bezier(…) or - linear(…). Use “Live preview” above to feel a curve. + cubic-bezier(…) or + linear(…). Use “Live preview” above to feel a curve.

{#each EASINGS as e (e.token)} {@const isOverridden = e.token in overrides}
@@ -239,14 +239,14 @@
- {e.label} + {e.label}
{#if isOverridden} - + {/if}
@@ -258,7 +258,7 @@ const v = (ev.target as HTMLInputElement).value.trim(); v ? onSet(e.token, v) : onReset(e.token); }} - class="w-full bg-white/5 border border-white/10 rounded px-1.5 py-1 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-1 text-[9px] font-mono text-slate-700 dark:text-slate-300 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" />
@@ -268,7 +268,7 @@ {/if}
-
+
@@ -281,14 +281,14 @@ {showStagger ? "▲" : "▼"} {#if showStagger} -

+

One slider sets all five stagger delays (delay-1 through delay-5 are multiples of this base).

- Stagger base + Stagger base {#if STAGGER_TOKENS.some(t => t in overrides)} - + {/if}
-
+
{#each [1,2,3,4,5] as n (n)} {@const delayMs = Math.round(staggerBase * n)}
- –{n} -
+ –{n} +
{delayMs}ms @@ -314,14 +314,14 @@ {/if}
-
+
+ {/if}
{/each} diff --git a/configurator/src/components/panels/ShadowsPanel.svelte b/configurator/src/components/panels/ShadowsPanel.svelte index 145d62bf..f6110ad1 100644 --- a/configurator/src/components/panels/ShadowsPanel.svelte +++ b/configurator/src/components/panels/ShadowsPanel.svelte @@ -27,19 +27,19 @@
-
+
{#each SHADOW_STEPS as step, i (step)}
- {step} + {step}
{/each}
-
+
onReset("--sf-shadow-lightness")} /> -
+
@@ -64,7 +64,7 @@ {#if showShadowColor}
-
Shadow color
+
Shadow color
onSet("--sf-shadow-color", v)} onReset={() => onReset("--sf-shadow-color")} /> -

Warm, cool or brand-tinted shadows. Default uses neutral hue.

+

Warm, cool or brand-tinted shadows. Default uses neutral hue.

{/if}
-
+
@@ -92,9 +92,9 @@ {#if showGlow} -
+
-
Shadow glow
+
Shadow glow
Ambient glow on elevated surfaces
-
+
{#if showLayoutGap} -

+

The two most-referenced spacing tokens. Referenced by every layout primitive (cluster, grid, stack, bento…).

-
+
@@ -130,7 +130,7 @@ {showModularScale ? "▲" : "▼"} {#if showModularScale} -

+

Utopia-style fluid scale — viewport width, base unit and ratio per endpoint. The viewport range is shared with the type scale.

@@ -165,14 +165,14 @@ {/if}
-
+
-

+

{#if overridesCount === 0} No active overrides yet — adjust some tokens first. {:else} @@ -73,9 +73,9 @@

{#if themes.length === 0} -
+

No saved themes yet.

-

Configure your tokens, then save them above.

+

Configure your tokens, then save them above.

{:else} {#each themes as theme (theme.id)} @@ -83,15 +83,15 @@
{theme.icon}
-
+
{theme.name} {#if activeId === theme.id} - + {/if}
{tokenCount} override{tokenCount === 1 ? "" : "s"}
@@ -99,12 +99,12 @@
@@ -112,15 +112,15 @@ {#if tokenCount > 0}
{#each Object.entries(theme.overrides).slice(0, 5) as [k, v] (k)} -
+
{#if k.includes("color") || k.includes("source")} -
+
{/if} - {k.replace("--sf-", "")} + {k.replace("--sf-", "")}
{/each} {#if tokenCount > 5} - +{tokenCount - 5} more + +{tokenCount - 5} more {/if}
{/if} @@ -133,7 +133,7 @@ {#if overridesCount > 0} {/if}
diff --git a/configurator/src/components/panels/TypographyPanel.svelte b/configurator/src/components/panels/TypographyPanel.svelte index 4ac839e6..bd4f5097 100644 --- a/configurator/src/components/panels/TypographyPanel.svelte +++ b/configurator/src/components/panels/TypographyPanel.svelte @@ -265,14 +265,14 @@ { label: "Mono", token: "--sf-font-mono", current: currentMonoFont, opts: MONO_STACKS }, ] as f (f.token)}
- {f.label} + {f.label} {#if f.token in overrides} - + {/if}
{/each} -
-
Google Font
+
+
Google Font
setLevel(activeLevel.size, (e.target as HTMLSelectElement).value, activeLevel.dSize)} - class="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-[11px] text-slate-200 focus:outline-none focus:border-indigo-500 cursor-pointer" + class="flex-1 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 focus:outline-none focus:border-indigo-500 cursor-pointer" > {#each SIZE_OPTIONS as o (o.value)} @@ -388,11 +388,11 @@
- Line height + Line height setLevel(activeLevel.ls, (e.target as HTMLSelectElement).value, activeLevel.dLs)} - class="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-[11px] text-slate-200 focus:outline-none focus:border-indigo-500 cursor-pointer" + class="flex-1 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 focus:outline-none focus:border-indigo-500 cursor-pointer" > {#each TRACKING_OPTIONS as o (o.value)} @@ -443,7 +443,7 @@ {#if activeLevel.id !== "body"} {@const maxWidthToken = `--sf-${activeLevel.id}-max-width`}
- Max width + Max width {#if maxWidthToken in overrides} - + {/if}
{/if} @@ -468,14 +468,14 @@ if (activeLevel.ls) patch[activeLevel.ls] = null; onBulkChange(patch); }} - class="text-[9px] text-slate-500 hover:text-rose-400 cursor-pointer" + class="text-[9px] text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer" >reset {activeLevel.label} {/if}
{/if} -
+
@@ -499,9 +499,9 @@ {@const isOverridden = row.tokenName in overrides}
- {row.label} weight + {row.label} weight {#if isOverridden} - + {/if}
@@ -511,8 +511,8 @@ style={`font-weight: ${parseInt(w)}`} class={`flex-1 py-1.5 rounded-lg text-[10px] border transition-all cursor-pointer font-mono ${ current === w - ? "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" }`} >{w} {/each} @@ -522,7 +522,7 @@
-
Emphasis style
+
Emphasis style
{#each [["Italic", "italic"], ["Normal", "normal"]] as [label, val] (val)} {@const current = overrides["--sf-body-em-style"] ?? "italic"} @@ -530,8 +530,8 @@ onclick={() => val === "italic" ? onReset("--sf-body-em-style") : onSet("--sf-body-em-style", val)} class={`flex-1 py-2 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} @@ -554,15 +554,15 @@ ] as row (row.token)} {@const current = overrides[row.token] ?? row.defaultVal}
-
{row.label}
+
{row.label}
{#each WRAP_OPTIONS as o (o.value)} {/each} @@ -572,7 +572,7 @@
- External marker + External marker {#if "--sf-link-external-marker" in overrides} - + {/if}
{/if}
-
+
@@ -603,7 +603,7 @@ {showFontWeights ? "▲" : "▼"} {#if showFontWeights} -

Named weight tokens. Override if your font uses non-standard axis values.

+

Named weight tokens. Override if your font uses non-standard axis values.

{#each [ { label: "Light", token: "--sf-font-weight-light", def: 300 }, { label: "Normal", token: "--sf-font-weight-normal", def: 400 }, @@ -613,9 +613,9 @@ ] as row (row.token)}
- {row.label} + {row.label} {#if row.token in overrides} - + {/if}
@@ -626,8 +626,8 @@ style={`font-weight:${parseInt(w)}`} class={`flex-1 py-1.5 rounded-lg text-[10px] border transition-all cursor-pointer font-mono ${ cur === w - ? "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" }`} >{w} {/each} @@ -637,7 +637,7 @@ {/if}
-
+
@@ -669,7 +669,7 @@ { label: "Display M line-height", token: "--sf-display-m-line-height", def: 1.05 }, ] as row (row.token)}
-
{row.label}
+
{row.label}
- {row.label} weight + {row.label} weight {#if isOverridden} - + {/if}
@@ -702,8 +702,8 @@ style={`font-weight: ${parseInt(w)}`} class={`flex-1 py-1.5 rounded-lg text-[10px] border transition-all cursor-pointer font-mono ${ current === w - ? "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" }`} >{w} {/each} @@ -713,7 +713,7 @@ {/if}
-
+
@@ -726,7 +726,7 @@ {showModularScale ? "▲" : "▼"} {#if showModularScale} -

+

Utopia-style fluid scale. Each endpoint has three values — viewport width, base size, and modular ratio. Sizes interpolate fluidly between mobile and desktop.

@@ -762,7 +762,7 @@
-
Line height scale
+
Line height scale
{#each LEADING_TOKENS as t (t.token)}
-
Letter-spacing scale
-

+

Letter-spacing scale
+

Overriding these propagates to heading sizes that reference them.

{#each TRACKING_TOKENS as t (t.token)}
- {t.label} + {t.label} {#if t.token in overrides} - + {/if}
{ showScaleAdvanced = !showScaleAdvanced; }} aria-expanded={showScaleAdvanced} - class="w-full flex items-center justify-between text-slate-600 hover:text-slate-400 transition-colors cursor-pointer" + class="w-full flex items-center justify-between text-slate-400 dark:text-slate-600 hover:text-slate-600 dark:hover:text-slate-400 transition-colors cursor-pointer" >
Advanced
{showScaleAdvanced ? "▲" : "▼"} @@ -834,7 +834,7 @@ {/if}
-
+
@@ -847,26 +847,26 @@ {showScalePreview ? "▲" : "▼"} {#if showScalePreview} -
+
{#each [...TEXT_STEPS].reverse() as { label, factor } (label)} {@const midBase = (baseMin + baseMax) / 2} {@const size = midBase * factor * textScale}
- {label} + {label} Aa - {size.toFixed(2)}rem + {size.toFixed(2)}rem
{/each}
{/if}
-
+
@@ -879,8 +879,8 @@ {showLineLengths ? "▲" : "▼"} {#if showLineLengths} -

- Per-text-size max-width caps on .sf-text-* elements. Set to none to remove the constraint; use ch units for character-based measures. +

+ Per-text-size max-width caps on .sf-text-* elements. Set to none to remove the constraint; use ch units for character-based measures.

{#each [ @@ -895,7 +895,7 @@ { label: "4xl", token: "--sf-text-4xl-max-width", def: "none" }, ] as row (row.token)}
- {row.label} + {row.label} {#if row.token in overrides} - + {/if}
{/each} @@ -915,7 +915,7 @@ {/if}
-
+
@@ -928,11 +928,11 @@ {showAdvancedType ? "▲" : "▼"} {#if showAdvancedType} -

Advanced OpenType and variable-font axes. Leave as normal unless your font supports these features.

+

Advanced OpenType and variable-font axes. Leave as normal unless your font supports these features.

-
Numeric figures
+
Numeric figures
{#each [["tabular-nums", "tabular-nums (default)"], ["proportional-nums", "proportional-nums"], ["normal", "normal"]] as [val, label] (val)} {@const cur = overrides["--sf-font-numeric"] ?? "tabular-nums"} @@ -940,8 +940,8 @@ onclick={() => val === "tabular-nums" ? onReset("--sf-font-numeric") : onSet("--sf-font-numeric", val)} class={`flex-1 py-1.5 rounded-lg text-[9px] border transition-all cursor-pointer ${ cur === 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} @@ -950,7 +950,7 @@
-
Optical sizing
+
Optical sizing
{#each [["auto", "auto (default)"], ["none", "none"]] as [val, label] (val)} {@const cur = overrides["--sf-optical-sizing"] ?? "auto"} @@ -958,8 +958,8 @@ onclick={() => val === "auto" ? onReset("--sf-optical-sizing") : onSet("--sf-optical-sizing", val)} class={`flex-1 py-2 rounded-lg text-[10px] border transition-all cursor-pointer ${ cur === 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} @@ -968,7 +968,7 @@
- Font features + Font features {#if "--sf-font-features" in overrides} - + {/if}
- Variation + Variation {#if "--sf-font-variation" in overrides} - + {/if}
{/if} diff --git a/configurator/src/components/panels/WcagPanel.svelte b/configurator/src/components/panels/WcagPanel.svelte index 4ec93076..c9108ee5 100644 --- a/configurator/src/components/panels/WcagPanel.svelte +++ b/configurator/src/components/panels/WcagPanel.svelte @@ -84,10 +84,10 @@ } function levelOf(ratio: number): { tag: string; cls: string } { - if (ratio >= 7) return { tag: "AAA", cls: "bg-emerald-500/25 text-emerald-200" }; - if (ratio >= 4.5) return { tag: "AA", cls: "bg-emerald-500/15 text-emerald-300" }; - if (ratio >= 3) return { tag: "AA-L", cls: "bg-amber-500/20 text-amber-300" }; - return { tag: "Fail", cls: "bg-rose-500/20 text-rose-300" }; + if (ratio >= 7) return { tag: "AAA", cls: "bg-emerald-500/25 text-emerald-800 dark:text-emerald-200" }; + if (ratio >= 4.5) return { tag: "AA", cls: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300" }; + if (ratio >= 3) return { tag: "AA-L", cls: "bg-amber-500/20 text-amber-700 dark:text-amber-300" }; + return { tag: "Fail", cls: "bg-rose-500/20 text-rose-700 dark:text-rose-300" }; } let pairRatio = $derived(ratioOf(fg.expr, bg.expr)); @@ -160,7 +160,7 @@
Contrast checker
-

+

Live WCAG ratios computed from the actual rendered preview colors — including derived steps and your overrides.

@@ -179,73 +179,73 @@
Foreground
- {#each ALL_OPTS as o (o.label)}{/each}
- +
Background
- {#each ALL_OPTS as o (o.label)}{/each}
-
+
Aa Bb Cc
{#if pairRatio !== null} {@const lvl = levelOf(pairRatio)} -
- {pairRatio.toFixed(2)}:1 +
+ {pairRatio.toFixed(2)}:1 {lvl.tag}
- = 4.5 ? "text-emerald-300" : "text-rose-300"}>{pairRatio >= 4.5 ? "✓" : "✗"} AA normal - = 3 ? "text-emerald-300" : "text-rose-300"}>{pairRatio >= 3 ? "✓" : "✗"} AA large - = 7 ? "text-emerald-300" : "text-rose-300"}>{pairRatio >= 7 ? "✓" : "✗"} AAA normal - = 4.5 ? "text-emerald-300" : "text-rose-300"}>{pairRatio >= 4.5 ? "✓" : "✗"} AAA large + = 4.5 ? "text-emerald-700 dark:text-emerald-300" : "text-rose-700 dark:text-rose-300"}>{pairRatio >= 4.5 ? "✓" : "✗"} AA normal + = 3 ? "text-emerald-700 dark:text-emerald-300" : "text-rose-700 dark:text-rose-300"}>{pairRatio >= 3 ? "✓" : "✗"} AA large + = 7 ? "text-emerald-700 dark:text-emerald-300" : "text-rose-700 dark:text-rose-300"}>{pairRatio >= 7 ? "✓" : "✗"} AAA normal + = 4.5 ? "text-emerald-700 dark:text-emerald-300" : "text-rose-700 dark:text-rose-300"}>{pairRatio >= 4.5 ? "✓" : "✗"} AAA large
{:else} -
Waiting for live preview…
+
Waiting for live preview…
{/if}
-
+
- Auto-fix to pass (for {getActiveTheme()} mode) -
+ Auto-fix to pass (for {getActiveTheme()} mode) +
{#each ["AA", "AAA"] as t (t)} - + {/each}
{#if !fg.editKey} -

Pick an editable brand/status (base) color as the foreground to enable auto-fix.

+

Pick an editable brand/status (base) color as the foreground to enable auto-fix.

{:else if pairRatio !== null && pairRatio >= (targetLevel === "AAA" ? 7 : 4.5)} -

Already passes {targetLevel}. 🎉

+

Already passes {targetLevel}. 🎉

{:else if suggestion}
- +
-
{suggestion.value}
-
→ {suggestion.ratio.toFixed(2)}:1 ({targetLevel} ✓)
+
{suggestion.value}
+
→ {suggestion.ratio.toFixed(2)}:1 ({targetLevel} ✓)
- +
{:else} -

No lightness of this hue reaches {targetLevel} on this background — try a different background or hue.

+

No lightness of this hue reaches {targetLevel} on this background — try a different background or hue.

{/if}
{/if}
-
+
@@ -258,7 +258,7 @@ {showMatrix ? "▲" : "▼"} {#if showMatrix} -

Foreground (rows) × background (cols). Click a cell to load it above.

+

Foreground (rows) × background (cols). Click a cell to load it above.

@@ -285,7 +285,7 @@ >{r.toFixed(1)} {:else} - + {/if} {/each} diff --git a/configurator/src/components/shell/PreviewPanel.svelte b/configurator/src/components/shell/PreviewPanel.svelte index 52645254..fea3142a 100644 --- a/configurator/src/components/shell/PreviewPanel.svelte +++ b/configurator/src/components/shell/PreviewPanel.svelte @@ -595,17 +595,17 @@ ${BODIES[template]} } -
+
-
+
-
+
{#each TEMPLATES as t (t.id)}
-
+
-
+
{#each (["fluid", "desktop", "tablet", "mobile"] as const) as w (w)}
-
+
@@ -665,7 +665,7 @@ ${BODIES[template]}
··