Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion configurator/src/components/panels/BordersPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,31 @@
onReset={() => onReset("--sf-focus-ring-color")}
/>
</div>
<div>
<div class="text-[10px] font-semibold text-slate-600 dark:text-slate-400 mb-1.5">Ring style</div>
<div class="flex gap-2">
{#each BORDER_STYLES as style (style)}
{@const current = overrides["--sf-focus-ring-style"] ?? "solid"}
<button
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-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}
</button>
{/each}
</div>
</div>
<!-- Focus ring preview -->
<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: 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
Focus ring · {overrides["--sf-focus-ring-style"] ?? "solid"}
</div>
</div>
</Section>
Expand Down
12 changes: 0 additions & 12 deletions configurator/src/components/panels/ColorsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,6 @@
const contrastKnobs = (KNOBS_BY_DOMAIN["colors"] ?? []).filter(
(k) => k.name === "--sf-contrast-bias" || k.name === "--sf-contrast-threshold"
);
const focusKnobs = (KNOBS_BY_DOMAIN["colors"] ?? []).filter(
(k) => k.name === "--sf-focus-ring-width"
);

let showBrandSources = $state(false);
let showTextContrast = $state(false);
Expand Down Expand Up @@ -817,15 +814,6 @@
>Open contrast tool →</button>
{/if}
</div>

<!-- Focus ring width — logically belongs with contrast/accessibility -->
{#each focusKnobs as k (k.name)}
<PowerKnobRow
knob={k}
{overrides}
onChange={(name, val) => val === null ? onReset(name) : onSet(name, val)}
/>
{/each}
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down
16 changes: 0 additions & 16 deletions configurator/src/components/panels/ComponentsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -568,22 +568,6 @@
</div>
</div>

<div>
<div class="text-[9px] text-slate-500 mb-1">Media object-fit — --sf-object-fit (global, affects all img/video)</div>
<div class="flex flex-wrap gap-1">
{#each ["cover", "contain"] as fit (fit)}
{@const cur = overrides["--sf-object-fit"] ?? "cover"}
<button
onclick={() => fit === "cover" ? onReset("--sf-object-fit") : onSet("--sf-object-fit", fit)}
class={`px-2 py-1 rounded-lg text-[10px] border transition-all cursor-pointer ${
cur === fit
? "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"
}`}
>{fit}</button>
{/each}
</div>
</div>
</div>
</Section>
</div>
32 changes: 0 additions & 32 deletions configurator/src/components/panels/MiscPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,11 @@
let caretColor = $derived(overrides["--sf-caret-color"] ?? "");
let underlineOffset = $derived(parseNum(overrides["--sf-link-underline-offset"]?.replace("em",""), 0.15));
let underlineThickness = $derived(overrides["--sf-link-underline-thickness"] ?? "auto");
let focusRingStyle = $derived(overrides["--sf-focus-ring-style"] ?? "solid");

let showTouchTarget = $state(false);
let showScrollBehavior = $state(false);
let showZIndex = $state(false);
let showTextSelection = $state(false);
let showFocusRingStyle = $state(false);
let showComponentSizes = $state(false);
let showCaretLinks = $state(false);
let showIconSizes = $state(false);
Expand Down Expand Up @@ -217,36 +215,6 @@

<div class="h-px bg-black/6 dark:bg-white/6"></div>

<!-- FOCUS RING STYLE -->
<Section title="Focus ring style" bind:open={showFocusRingStyle}>
<div class="flex gap-2">
{#each ["solid", "dashed", "dotted"] as style (style)}
{@const current = overrides["--sf-focus-ring-style"] ?? "solid"}
<button
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-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}
</button>
{/each}
</div>
<!-- Focus ring style preview -->
<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: 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 ring · {focusRingStyle}
</div>
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>

<!-- COMPONENT SIZES -->
<Section title="Component size scale" bind:open={showComponentSizes}>
<p class="text-[10px] text-slate-400 dark:text-slate-600 leading-relaxed">
Expand Down
105 changes: 39 additions & 66 deletions configurator/src/components/panels/TypographyPanel.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script lang="ts">
import { KNOBS_BY_DOMAIN } from '../../lib/powerKnobs';
import PowerKnobRow from '../inputs/PowerKnobRow.svelte';
import SliderRow from '../inputs/SliderRow.svelte';
import RangeWithNumber from '../inputs/RangeWithNumber.svelte';
import ClampField from '../inputs/ClampField.svelte';
import Section from '../inputs/Section.svelte';
import TypeSpecimenRow from '../inputs/TypeSpecimenRow.svelte';
import { themeState } from '../../lib/theme.svelte';
import GOOGLE_FONTS_ALL from '../../data/google-fonts.generated.json';

// <option> only reliably accepts a background via inline style (no dark:
// variant support), so it's derived from the chrome theme directly.
Expand Down Expand Up @@ -56,14 +55,11 @@
{ label: "Source Code Pro", value: "'Source Code Pro', 'Courier New', monospace" },
];

// Curated Google Fonts for the dropdown loader.
const GOOGLE_FONTS = [
"Inter", "Roboto", "Open Sans", "Lato", "Montserrat", "Poppins",
"Source Sans 3", "Raleway", "Nunito", "Work Sans", "Manrope", "DM Sans",
"Outfit", "Plus Jakarta Sans", "Geist", "Space Grotesk", "Figtree",
"Merriweather", "Playfair Display", "Lora", "Source Serif 4", "Roboto Slab",
"IBM Plex Sans", "IBM Plex Mono", "JetBrains Mono", "Fira Code", "Space Mono",
];
// Full Google Fonts catalogue (family + category), generated from
// fonts.google.com/metadata/fonts and sorted by popularity — powers the
// autocomplete datalist below. Any of the ~1900 families can be loaded.
type GoogleFont = { f: string; c: string };
const GOOGLE_FONTS: GoogleFont[] = GOOGLE_FONTS_ALL as GoogleFont[];

const WRAP_OPTIONS = [
{ label: "balance", value: "balance" },
Expand Down Expand Up @@ -148,8 +144,6 @@
let activeLevel = $derived(TYPE_LEVELS.find((l) => l.id === activeLevelId)!);
let activeMw = $derived(levelMaxWidthToken(activeLevelId));

const knobs = KNOBS_BY_DOMAIN["typography"] ?? [];

function num(name: string, fallback: number) {
const v = parseFloat(overrides[name] ?? "");
return isNaN(v) ? fallback : v;
Expand Down Expand Up @@ -177,14 +171,13 @@
let showWeights = $state(false);
let showElements = $state(false);
let showLineLengths = $state(false);
let showScaleAdvanced = $state(false);

let currentBodyFont = $derived(overrides["--sf-font-body"] ?? "");
let currentHeadingFont = $derived(overrides["--sf-font-heading"] ?? "");
let currentMonoFont = $derived(overrides["--sf-font-mono"] ?? "ui-monospace, monospace");

let googleFontChoice = $state("");
let customFontTarget = $state<"body" | "heading">("body");
let customFontTarget = $state<"body" | "heading" | "mono">("body");

function getTrackingVal(t: typeof TRACKING_TOKENS[0]): number {
const raw = overrides[t.token];
Expand All @@ -208,11 +201,17 @@
}
}

function applyCustomFont(fontName: string, target: "body" | "heading") {
const FONT_TARGETS = [
{ id: "body", label: "Body", token: "--sf-font-body", fallback: "sans-serif" },
{ id: "heading", label: "Heading", token: "--sf-font-heading", fallback: "sans-serif" },
{ id: "mono", label: "Mono", token: "--sf-font-mono", fallback: "monospace" },
] as const;

function applyCustomFont(fontName: string, target: "body" | "heading" | "mono") {
if (!fontName.trim()) return;
injectGoogleFont(fontName);
const token = target === "body" ? "--sf-font-body" : "--sf-font-heading";
onSet(token, `'${fontName}', sans-serif`);
const t = FONT_TARGETS.find((x) => x.id === target)!;
onSet(t.token, `'${fontName}', ${t.fallback}`);
}

// Per-element helpers
Expand Down Expand Up @@ -292,39 +291,36 @@
</div>
{/each}

<!-- Google font loader (dropdown + custom) -->
<!-- Google font loader — one autocomplete over the full catalogue
(~1900 families, native datalist filtering), applied to body,
heading or mono. -->
<div class="rounded-xl bg-black/4 dark:bg-white/4 border border-black/8 dark:border-white/8 p-3 space-y-2">
<div class="text-[10px] font-semibold text-slate-600 dark:text-slate-400">Google Font</div>
<div class="flex gap-2">
<select
value={googleFontChoice}
onchange={(e) => { googleFontChoice = (e.target as HTMLSelectElement).value; }}
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"
>
<option value="" style={`background:${optionBg};`}>Pick a font…</option>
{#each GOOGLE_FONTS as g (g)}
<option value={g} style={`background:${optionBg};`}>{g}</option>
{/each}
</select>
<div class="flex gap-1 shrink-0">
<button
onclick={() => { customFontTarget = "body"; }}
class={`px-2 rounded text-[10px] border transition-all cursor-pointer ${customFontTarget === "body" ? "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"}`}
>Body</button>
<button
onclick={() => { customFontTarget = "heading"; }}
class={`px-2 rounded text-[10px] border transition-all cursor-pointer ${customFontTarget === "heading" ? "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"}`}
>Heading</button>
</div>
<div class="flex items-baseline justify-between">
<div class="text-[10px] font-semibold text-slate-600 dark:text-slate-400">Google Font</div>
<div class="text-[9px] text-slate-400 dark:text-slate-600">{GOOGLE_FONTS.length} fonts</div>
</div>
<input
type="text"
list="sf-google-fonts"
aria-label="Google font name"
placeholder="…or type any Google font name"
placeholder="Type to search all Google Fonts…"
oninput={(e) => { googleFontChoice = (e.target as HTMLInputElement).value; }}
value={googleFontChoice}
class="w-full 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"
/>
<datalist id="sf-google-fonts">
{#each GOOGLE_FONTS as g (g.f)}
<option value={g.f}>{g.c}</option>
{/each}
</datalist>
<div class="flex gap-1">
{#each FONT_TARGETS as t (t.id)}
<button
onclick={() => { customFontTarget = t.id; }}
class={`flex-1 py-1.5 rounded text-[10px] border transition-all cursor-pointer ${customFontTarget === t.id ? "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"}`}
>{t.label}</button>
{/each}
</div>
<button
onclick={() => applyCustomFont(googleFontChoice, customFontTarget)}
class="w-full py-1.5 rounded-lg bg-indigo-600/20 border border-indigo-500/30 text-indigo-700 dark:text-indigo-300 text-[11px] font-bold cursor-pointer hover:bg-indigo-600/30 transition-all"
Expand Down Expand Up @@ -444,7 +440,7 @@
onRatioMaxChange={(v) => onSet("--sf-text-ratio-max", String(v))}
/>
<SliderRow
label="Text scale multiplier" value={textScale} min={0.75} max={1.5} step={0.01}
label="Text scale multiplier" value={textScale} min={0.5} max={2} step={0.05}
help="--sf-text-scale — multiplies every text size at once."
overridden={"--sf-text-scale" in overrides}
onChange={(v) => onSet("--sf-text-scale", String(v))}
Expand All @@ -471,7 +467,7 @@
onRatioMaxChange={(v) => onSet("--sf-text-ratio-max", String(v))}
/>
<SliderRow
label="Display scale multiplier" value={displayScale} min={0.75} max={1.5} step={0.01}
label="Display scale multiplier" value={displayScale} min={0.5} max={2} step={0.05}
help="--sf-text-display-scale — multiplies every display size at once."
overridden={"--sf-text-display-scale" in overrides}
onChange={(v) => onSet("--sf-text-display-scale", String(v))}
Expand All @@ -490,29 +486,6 @@
<TypeSpecimenRow {label} varName={`--sf-text-display-${label}`} family="--sf-font-heading" />
{/each}
</div>

<!-- Advanced power knobs — de-emphasised, at end of the scale group -->
<div class="pt-1">
<button
onclick={() => { showScaleAdvanced = !showScaleAdvanced; }}
aria-expanded={showScaleAdvanced}
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"
>
<div class="text-[10px] font-semibold uppercase tracking-widest">Advanced</div>
<span class="text-[10px]">{showScaleAdvanced ? "▲" : "▼"}</span>
</button>
{#if showScaleAdvanced}
<div class="mt-3 space-y-4">
{#each knobs as k (k.name)}
<PowerKnobRow
knob={k}
{overrides}
onChange={(name, val) => val === null ? onReset(name) : onSet(name, val)}
/>
{/each}
</div>
{/if}
</div>
</Section>

<div class="h-px bg-black/6 dark:bg-white/6"></div>
Expand Down
1 change: 1 addition & 0 deletions configurator/src/data/google-fonts.generated.json

Large diffs are not rendered by default.

20 changes: 0 additions & 20 deletions configurator/src/lib/powerKnobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,6 @@ export const KNOBS_BY_DOMAIN: Record<string, PowerKnob[]> = {
help: "OKLCH lightness above which a swatch is considered 'light' (gets dark text). Below = light text.",
default: 0.6, min: 0, max: 1, step: 0.01, driving: 11,
},
{
name: "--sf-focus-ring-width",
label: "Focus ring width",
help: "Thickness of the focus ring on every interactive element.",
default: 2, min: 0, max: 6, step: 0.5, unit: "px", driving: 1,
},
],
typography: [
{
name: "--sf-text-scale",
label: "Text scale",
help: "Multiplier applied to the entire fluid text ramp (2xs … 2xl).",
default: 1, min: 0.5, max: 2, step: 0.05, driving: 9,
},
{
name: "--sf-text-display-scale",
label: "Display scale",
help: "Multiplier applied to the display ramp (display-s, display-m, display-l).",
default: 1, min: 0.5, max: 2, step: 0.05, driving: 3,
},
],
spacing: [
{
Expand Down