Skip to content
2 changes: 1 addition & 1 deletion configurator/src/components/DomainPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
{#if domain === "colors"}
<ColorsPanel {tokens} {overrides} {onSet} {onReset} {onBulkChange} {onSelectDomain} />
{:else if domain === "typography"}
<TypographyPanel {tokens} {overrides} {onSet} {onReset} {onBulkChange} />
<TypographyPanel {overrides} {onSet} {onReset} {onBulkChange} />
{:else if domain === "spacing"}
<SpacingPanel {tokens} {overrides} {onSet} {onReset} />
{:else if domain === "layout"}
Expand Down
2 changes: 1 addition & 1 deletion configurator/src/components/inputs/PowerKnobRow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
{#if isOverridden}
<button
onclick={handleReset}
class="text-[9px] text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 transition-colors cursor-pointer opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100 focus:opacity-100"
class="text-[9px] text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 transition-colors cursor-pointer "
>
reset
</button>
Expand Down
4 changes: 2 additions & 2 deletions configurator/src/components/inputs/SliderRow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@
class={`text-[9px] font-mono cursor-pointer transition-all px-0.5 ${
showRawText
? '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'
: 'text-slate-500 hover:text-indigo-600 dark:hover:text-indigo-400'
}`}
>&lt;/&gt;</button>
{/if}
{#if overridden}
<button
onclick={() => { manualView = 'none'; onReset(); }}
class="text-[9px] text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100 focus:opacity-100 transition-colors"
class="text-[9px] text-slate-500 hover:text-rose-600 dark:hover:text-rose-400 cursor-pointer transition-colors"
>reset</button>
{/if}
</div>
Expand Down
142 changes: 72 additions & 70 deletions configurator/src/components/inputs/TokenRow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,81 +46,83 @@
let swatchColor = $derived(type === "color" ? paintSwatch(displayValue) : "");
</script>

<div class={`flex items-center gap-2 px-3 py-1.5 rounded-lg transition-colors group ${isOverridden ? "bg-indigo-500/8 border border-indigo-500/15" : "hover:bg-black/4 dark:hover:bg-white/4"}`}>
<div class={`w-1.5 h-1.5 rounded-full shrink-0 ${isOverridden ? "bg-indigo-500" : "bg-transparent"}`}></div>

{#if type === "color"}
<div
class="w-4 h-4 rounded-sm border border-black/10 dark:border-white/10 shrink-0"
style:background={swatchColor}
></div>
{:else}
<div class="w-4 h-4 shrink-0"></div>
{/if}

<div class="flex-1 min-w-0">
<div class="text-[10px] font-mono text-slate-600 dark:text-slate-400 truncate" title={token.name}>
<!-- Stacked layout — name, description, then a full-width value control, so
long values (clamp()/var() expressions) are never clipped by a narrow
right-aligned field. -->
<div class={`px-3 py-2 rounded-lg transition-colors group ${isOverridden ? "bg-indigo-500/8 border border-indigo-500/15" : "hover:bg-black/4 dark:hover:bg-white/4"}`}>
<div class="flex items-center gap-2">
<div class={`w-1.5 h-1.5 rounded-full shrink-0 ${isOverridden ? "bg-indigo-500" : "bg-black/15 dark:bg-white/15"}`}></div>
{#if type === "color"}
<div
class="w-4 h-4 rounded-sm border border-black/10 dark:border-white/10 shrink-0"
style:background={swatchColor}
></div>
{/if}
<div class="text-[10px] font-mono text-slate-700 dark:text-slate-300 truncate flex-1" title={token.name}>
{shortName}
</div>
{#if token.description}
<div class="text-[9px] text-slate-400 dark:text-slate-600 truncate">{token.description}</div>
{#if isOverridden}
<button
onclick={onReset}
aria-label={`Reset ${shortName}`}
class="text-[9px] text-slate-400 dark:text-slate-600 hover:text-rose-600 dark:hover:text-rose-400 transition-colors cursor-pointer shrink-0"
>
reset
</button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{/if}
</div>

{#if showScalePicker && scaleOpts}
<select
value={displayValue}
aria-label={`${shortName} value`}
onchange={(e) => {
const v = (e.target as HTMLSelectElement).value;
if (v === CUSTOM) { expanded = true; return; }
if (v === token.value) onReset(); else onSet(v);
}}
class="w-32 shrink-0 bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded px-1.5 py-0.5 text-[10px] font-mono text-slate-700 dark:text-slate-300 focus:outline-none focus:border-indigo-500 cursor-pointer"
>
{#if !scaleOpts.some((o) => o.value === token.value)}
<option value={token.value}>{token.value} (default)</option>
{/if}
{#each scaleOpts as o (o.value)}
<option value={o.value}>{o.label}</option>
{/each}
<option value={CUSTOM}>Custom…</option>
</select>
{:else if expanded}
<input
value={displayValue}
onblur={(e) => {
const v = (e.target as HTMLInputElement).value.trim();
if (v && v !== token.value) onSet(v);
else if (!v || v === token.value) onReset();
expanded = false;
}}
onkeydown={(e) => {
if (e.key === "Enter") (e.currentTarget as HTMLInputElement).blur();
if (e.key === "Escape") { expanded = false; }
}}
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}
<button
onclick={() => { expanded = true; }}
class="text-[10px] font-mono text-slate-500 hover:text-slate-800 dark:hover:text-slate-200 truncate max-w-28 text-right cursor-pointer transition-colors"
title={displayValue}
>
{#if isOverridden}
<span class="text-indigo-700 dark:text-indigo-300">{displayValue}</span>
{:else}
{displayValue}
{/if}
</button>
{#if token.description}
<div class="text-[9px] text-slate-400 dark:text-slate-600 leading-snug mt-0.5 pl-3.5">{token.description}</div>
{/if}

{#if isOverridden}
<button
onclick={onReset}
class="text-[9px] text-slate-400 dark:text-slate-600 hover:text-rose-600 dark:hover:text-rose-400 transition-colors cursor-pointer opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100 focus:opacity-100 shrink-0"
>
</button>
{/if}
<div class="mt-1 pl-3.5">
{#if showScalePicker && scaleOpts}
<select
value={displayValue}
aria-label={`${shortName} value`}
onchange={(e) => {
const v = (e.target as HTMLSelectElement).value;
if (v === CUSTOM) { expanded = true; return; }
if (v === token.value) onReset(); else onSet(v);
}}
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-[10px] font-mono text-slate-700 dark:text-slate-300 focus:outline-none focus:border-indigo-500 cursor-pointer"
>
{#if !scaleOpts.some((o) => o.value === token.value)}
<option value={token.value}>{token.value} (default)</option>
{/if}
{#each scaleOpts as o (o.value)}
<option value={o.value}>{o.label}</option>
{/each}
<option value={CUSTOM}>Custom…</option>
</select>
{:else if expanded}
<input
value={displayValue}
onblur={(e) => {
const v = (e.target as HTMLInputElement).value.trim();
if (v && v !== token.value) onSet(v);
else if (!v || v === token.value) onReset();
expanded = false;
}}
onkeydown={(e) => {
if (e.key === "Enter") (e.currentTarget as HTMLInputElement).blur();
if (e.key === "Escape") { expanded = false; }
}}
class="w-full bg-black/8 dark:bg-white/8 border border-indigo-500/50 rounded px-1.5 py-1 text-[10px] font-mono text-slate-800 dark:text-slate-200 focus:outline-none"
/>
{:else}
<button
onclick={() => { expanded = true; }}
class="w-full text-left text-[10px] font-mono text-slate-500 hover:text-slate-800 dark:hover:text-slate-200 truncate cursor-pointer transition-colors"
title={displayValue}
>
{#if isOverridden}
<span class="text-indigo-700 dark:text-indigo-300">{displayValue}</span>
{:else}
{displayValue}
{/if}
</button>
{/if}
</div>
</div>
43 changes: 43 additions & 0 deletions configurator/src/components/inputs/TypeSpecimenRow.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
/**
* TypeSpecimenRow — one row of the typographic scale preview. The "Aa"
* renders at the token's real, live font-size (`var(--sf-…)` resolved in
* this document, which loads the framework tokens plus live overrides), and
* the label reports the actually rendered size measured from the DOM — the
* preview can never disagree with reality.
*/
let { label, varName, family = "--sf-font-body" }: {
label: string;
varName: string;
family?: string;
} = $props();

let el = $state<HTMLElement | null>(null);
let px = $state(0);
let rootPx = $state(16);

$effect(() => {
if (!el) return;
const node = el;
const measure = () => {
px = parseFloat(getComputedStyle(node).fontSize) || 0;
rootPx = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
};
measure();
// A font-size change resizes the span, so ResizeObserver re-measures on
// every token/override change and on viewport-driven fluid changes alike.
const ro = new ResizeObserver(measure);
ro.observe(node);
return () => ro.disconnect();
});
</script>

<div class="flex items-baseline gap-2">
<span class="text-[9px] font-mono text-slate-400 dark:text-slate-600 w-6 shrink-0 text-right">{label}</span>
<span
bind:this={el}
class="text-slate-900/80 dark:text-white/80 font-medium leading-none"
style={`font-size: var(${varName}); font-family: var(${family})`}
>Aa</span>
<span class="text-[9px] font-mono text-slate-400 dark:text-slate-600 ml-auto shrink-0">{px ? `${(px / rootPx).toFixed(2)}rem` : ""}</span>
</div>
23 changes: 8 additions & 15 deletions configurator/src/components/panels/BordersPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

const BORDER_STYLES = ["solid", "dashed", "dotted"];
const RADIUS_STEPS = ["xs", "s", "m", "l", "xl", "2xl", "full"];
const BASE_RADII: Record<string, number> = { xs: 2, s: 4, m: 8, l: 12, xl: 16, "2xl": 24, full: 9999 };

const RADIUS_FINE: Array<{ step: string; name: string; default: number; max: number; step_size: number; rawDefault: string }> = [
{ step: "xs", name: "--sf-radius-xs", default: 2, max: 16, step_size: 0.5, rawDefault: "calc(2px * var(--sf-radius-scale))" },
Expand Down Expand Up @@ -50,7 +49,6 @@
return isNaN(v) ? fallback : v;
}

let radiusScale = $derived(parseNum(overrides["--sf-radius-scale"], 1));
let borderScale = $derived(parseNum(overrides["--sf-border-scale"], 1));
let focusWidth = $derived(parseNum(overrides["--sf-focus-ring-width"], 2, "px"));
let focusOffset = $derived(parseNum(overrides["--sf-focus-ring-offset"], 2, "px"));
Expand Down Expand Up @@ -130,14 +128,11 @@
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3 space-y-2">
{#each [1, 2, 3, 4] as base (base)}
<div class="flex items-center gap-3">
<span class="text-[9px] font-mono text-slate-400 dark:text-slate-600 w-10">{base}px →</span>
<span class="text-[9px] font-mono text-slate-400 dark:text-slate-600 w-14">width-{base}</span>
<div
class="flex-1 bg-indigo-400/50 rounded"
style={`height: ${Math.max(base * borderScale, 0.5)}px`}
style={`height: max(var(--sf-border-width-${base}, ${base}px), 0.5px)`}
></div>
<span class="text-[9px] font-mono text-slate-500 w-10 text-right">
{(base * borderScale).toFixed(1)}px
</span>
</div>
{/each}
</div>
Expand Down Expand Up @@ -178,11 +173,11 @@
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-4 flex items-center justify-center gap-3">
<div
class="px-4 py-2 rounded-lg text-[11px] text-slate-700 dark:text-slate-300 bg-black/4 dark:bg-white/4"
style={`border: ${Math.max(borderScale, 0.5)}px ${borderStyle} ${borderColor || "var(--sf-color-border, #64748b)"}`}
style={`border: var(--sf-border-width-1, 1px) ${borderStyle} ${borderColor || "var(--sf-color-border, #64748b)"}`}
>
Border sample
</div>
<span class="text-[9px] font-mono text-slate-400 dark:text-slate-600">{(1 * borderScale).toFixed(1)}px · {borderStyle}</span>
<span class="text-[9px] font-mono text-slate-400 dark:text-slate-600">width-1 · {borderStyle}</span>
</div>
</Section>

Expand Down Expand Up @@ -258,7 +253,7 @@
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-4 flex items-center justify-center">
<div
class="px-4 py-2 bg-indigo-600/30 rounded-lg text-[11px] text-indigo-800 dark:text-indigo-200"
style={`outline: ${focusWidth}px solid ${focusRingColor || "oklch(0.7 0.2 235)"}; outline-offset: ${focusOffset}px`}
style={`outline: var(--sf-focus-ring-width, 2px) var(--sf-focus-ring-style, solid) var(--sf-focus-ring-color, oklch(0.7 0.2 235)); outline-offset: var(--sf-focus-ring-offset, 2px)`}
>
Focus preview
</div>
Expand Down Expand Up @@ -316,12 +311,10 @@
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-4">
<div class="flex gap-3 flex-wrap">
{#each RADIUS_STEPS as step (step)}
{@const base = BASE_RADII[step] ?? 6}
{@const computed = step === "full" ? 9999 : base * radiusScale}
<div class="flex flex-col items-center gap-1">
<div
class="w-10 h-10 bg-indigo-500/40 border border-indigo-500/30"
style={`border-radius: ${Math.min(computed, 9999)}px`}
style={`border-radius: var(--sf-radius-${step})`}
></div>
<span class="text-[8px] font-mono text-slate-400 dark:text-slate-600">{step}</span>
</div>
Expand All @@ -345,15 +338,15 @@
overridden={"--sf-media-radius" in overrides}
onChange={(v) => onSet("--sf-media-radius", `${v}rem`)}
onReset={() => onReset("--sf-media-radius")}
rawDefault="var(--sf-radius-m)"
rawDefault="0"
variableOptions={RADIUS_SCALE}
currentRaw={overrides["--sf-media-radius"]}
onRawSet={(v) => onSet("--sf-media-radius", v)}
/>
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-4 flex items-center justify-center">
<div
class="w-16 h-16 bg-indigo-500/40 border border-indigo-500/30"
style={`border-radius: ${mediaRadius}rem`}
style={`border-radius: var(--sf-media-radius, 0)`}
></div>
</div>
</Section>
Expand Down
14 changes: 7 additions & 7 deletions configurator/src/components/panels/EffectsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
</div>
<div
class="relative z-10 w-20 h-12 rounded-lg border border-black/20 dark:border-white/20 bg-black/10 dark:bg-white/10 flex items-center justify-center"
style={`backdrop-filter: blur(${blur}px)`}
style={`backdrop-filter: blur(var(--sf-blur, 12px))`}
>
<span class="text-[9px] font-mono text-slate-900/70 dark:text-white/70">{blur}px</span>
</div>
Expand Down Expand Up @@ -94,13 +94,13 @@
<!-- Opacity preview -->
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3 space-y-2">
{#each [
{ label: "muted", val: opacityMuted },
{ label: "disabled", val: opacityDisabled },
{ label: "pending", val: pendingOpacity },
{ label: "muted", token: "--sf-opacity-muted", val: opacityMuted },
{ label: "disabled", token: "--sf-opacity-disabled", val: opacityDisabled },
{ label: "pending", token: "--sf-state-pending-opacity", val: pendingOpacity },
] as row (row.label)}
<div class="flex items-center gap-3">
<span class="text-[9px] text-slate-400 dark:text-slate-600 w-14">{row.label}</span>
<div class="flex-1 h-4 bg-indigo-400 rounded" style={`opacity: ${row.val}`}></div>
<div class="flex-1 h-4 bg-indigo-400 rounded" style={`opacity: var(${row.token}, ${row.val})`}></div>
<span class="text-[9px] font-mono text-slate-500 w-8">{row.val}</span>
</div>
{/each}
Expand Down Expand Up @@ -129,8 +129,8 @@
{/each}
<!-- Scrollbar strip preview -->
<div class="flex items-center gap-2 p-2 rounded-lg bg-black/4 dark:bg-white/4 border border-black/8 dark:border-white/8">
<div class="w-16 h-3 rounded-full" style:background={scrollbarTrack || "rgba(255,255,255,0.08)"}>
<div class="w-6 h-3 rounded-full" style:background={scrollbarThumb || "oklch(0.52 0.025 260)"}></div>
<div class="w-16 h-3 rounded-full" style:background={"var(--sf-scrollbar-track, rgba(255,255,255,0.08))"}>
<div class="w-6 h-3 rounded-full" style:background={"var(--sf-scrollbar-thumb, oklch(0.52 0.025 260))"}></div>
</div>
<span class="text-[9px] text-slate-400 dark:text-slate-600">Scrollbar preview</span>
</div>
Expand Down
6 changes: 3 additions & 3 deletions configurator/src/components/panels/MacrosPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
currentRaw={overrides["--sf-flow-space"]}
onRawSet={(v) => onSet("--sf-flow-space", v)}
/>
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3" style={`display:flex;flex-direction:column;gap:${flowSpace}rem`}>
<div class="bg-black/4 dark:bg-white/4 rounded-xl border border-black/8 dark:border-white/8 p-3" style={`display:flex;flex-direction:column;gap:var(--sf-flow-space, 0.5rem)`}>
{#each [0, 1, 2] as i (i)}
<div class="h-2.5 rounded bg-indigo-500/40" style={`width:${90 - i * 12}%`}></div>
{/each}
Expand Down Expand Up @@ -228,7 +228,7 @@
</div>
<div class="relative h-24 rounded-xl border border-black/8 dark:border-white/8 overflow-hidden flex items-end p-2"
style="background:repeating-linear-gradient(45deg,#475569,#475569 8px,#334155 8px,#334155 16px)">
<div class="absolute inset-0" style={`background:linear-gradient(${scrimDir}, ${scrimColor}, transparent)`}></div>
<div class="absolute inset-0" style={`background:linear-gradient(var(--sf-scrim-direction, to top), var(--sf-scrim-color, oklch(0 0 0 / 0.55)), transparent)`}></div>
<span class="relative text-[11px] font-bold text-white">Caption over scrim</span>
</div>
<RawTokenRow
Expand Down Expand Up @@ -366,7 +366,7 @@

<div
class="h-24 rounded-xl border border-black/8 dark:border-white/8 overflow-hidden flex items-end p-2"
style={`background-color:${surfaceBgColor};background-image:${surfaceBgOverlay}, ${surfaceBgImage};background-size:${surfaceBgSize};background-position:${surfaceBgPosition};background-repeat:${surfaceBgRepeat};background-attachment:${surfaceBgAttachment};animation:${surfaceBgAnimation}`}
style={`background-color:var(--sf-surface-bg-color, transparent);background-image:var(--sf-surface-bg-overlay, none), var(--sf-surface-bg-image, none);background-size:var(--sf-surface-bg-size, cover);background-position:var(--sf-surface-bg-position, center);background-repeat:var(--sf-surface-bg-repeat, no-repeat);background-attachment:var(--sf-surface-bg-attachment, scroll);animation:var(--sf-surface-bg-animation, none)`}
>
<span class="text-[11px] font-bold text-white mix-blend-difference">.sf-surface-bg preview</span>
</div>
Expand Down
Loading