diff --git a/configurator/src/components/inputs/AspectRatioInput.svelte b/configurator/src/components/inputs/AspectRatioInput.svelte new file mode 100644 index 000000000..6ab5d4748 --- /dev/null +++ b/configurator/src/components/inputs/AspectRatioInput.svelte @@ -0,0 +1,49 @@ + + +
+ {#each items as p (p.value)} + + {/each} +
+ +{#if showPreview} +
+
{value}
+
+{/if} diff --git a/configurator/src/components/inputs/RawTokenRow.svelte b/configurator/src/components/inputs/RawTokenRow.svelte new file mode 100644 index 000000000..7550f5d6c --- /dev/null +++ b/configurator/src/components/inputs/RawTokenRow.svelte @@ -0,0 +1,34 @@ + + +
+ {#if label} + {label} + {/if} + { const v = (e.target as HTMLInputElement).value; v.trim() ? onSet(v) : onReset(); }} + 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-1 text-[9px] font-mono text-slate-700 dark:text-slate-300 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" + /> + {#if overridden} + + {/if} +
diff --git a/configurator/src/components/inputs/Section.svelte b/configurator/src/components/inputs/Section.svelte new file mode 100644 index 000000000..6d018d4a8 --- /dev/null +++ b/configurator/src/components/inputs/Section.svelte @@ -0,0 +1,36 @@ + + +
+ + {#if open} + {@render children()} + {/if} +
diff --git a/configurator/src/components/inputs/Toggle.svelte b/configurator/src/components/inputs/Toggle.svelte new file mode 100644 index 000000000..82a6a4588 --- /dev/null +++ b/configurator/src/components/inputs/Toggle.svelte @@ -0,0 +1,29 @@ + + + diff --git a/configurator/src/components/inputs/TokenRow.svelte b/configurator/src/components/inputs/TokenRow.svelte index 9699b8dfd..70691ee52 100644 --- a/configurator/src/components/inputs/TokenRow.svelte +++ b/configurator/src/components/inputs/TokenRow.svelte @@ -1,6 +1,7 @@ @@ -53,16 +42,7 @@
-
- - {#if showBlur} +
{blur}px
- {/if} - +
-
- - {#if showOpacity} +
{/each} - {/if} -
- -
- - -
- - {#if showScrim} -

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

- - -
-
Color
- onSet("--sf-scrim-color", v)} - onReset={() => onReset("--sf-scrim-color")} - /> -
- -
-
Direction
-
- {#each SCRIM_DIRECTIONS as d (d.value)} - - {/each} -
-
- - -
-
-
-
Scrim preview
-
-
- {/if} -
+
-
- - {#if showScrollbar} +
{#each [ { label: "Thumb color", token: "--sf-scrollbar-thumb", val: scrollbarThumb }, { label: "Track color", token: "--sf-scrollbar-track", val: scrollbarTrack }, @@ -238,45 +134,12 @@ Scrollbar preview - {/if} -
- -
- - -
- - {#if showScrollShadow} - onSet("--sf-scroll-shadow-size", `${v}rem`)} - onReset={() => onReset("--sf-scroll-shadow-size")} - /> - {/if} -
+
-
- - {#if showTextShadow} +

Controls text legibility over images. Applied via text-shadow: var(--sf-text-shadow-m). Edit each token directly — use none to disable. @@ -301,22 +164,12 @@ {/each} - {/if} -

+
-
- - {#if showDropShadow} +

filter: drop-shadow(…) — unlike box-shadow, follows the element's actual alpha shape (SVG icons, PNG cutouts, transparent logos). Edit each token directly — @@ -360,6 +213,5 @@ {/each} - {/if} -

+
diff --git a/configurator/src/components/panels/LayoutPanel.svelte b/configurator/src/components/panels/LayoutPanel.svelte index 94cfd1a04..c533c31e7 100644 --- a/configurator/src/components/panels/LayoutPanel.svelte +++ b/configurator/src/components/panels/LayoutPanel.svelte @@ -2,6 +2,9 @@ import type { SlashedToken } from '../../types'; import SliderRow from '../inputs/SliderRow.svelte'; import ColorInput from '../inputs/ColorInput.svelte'; + import AspectRatioInput from '../inputs/AspectRatioInput.svelte'; + import RawTokenRow from '../inputs/RawTokenRow.svelte'; + import Section from '../inputs/Section.svelte'; import { themeState } from '../../lib/theme.svelte'; import { SPACE_SCALE, CONTAINER_SCALE } from '../../lib/variableScales'; @@ -89,16 +92,7 @@
-
- - {#if showContainerWidths} +
@@ -159,22 +153,12 @@
{/each}
- {/if} -
+
-
- - {#if showCenterWrapper} +
onSet("--sf-center-gutter", v)} /> - {/if} -
+
-
- - {#if showAutoGrid} +
-
- - {#if showHeaderHeight} +
Mobile
@@ -318,22 +282,12 @@
{headerMobile}rem mobile · {headerDesktop}rem desktop
- {/if} -
+
-
- - {#if showStickyOffset} +

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

@@ -361,22 +315,12 @@ />
- {/if} -
+
-
- - {#if showSidebar} +
- {/if} - +
@@ -511,21 +454,19 @@ />
Rule style - - {#if "--sf-equal-rule-style" in overrides} - - {/if} +
Rule color @@ -556,18 +497,11 @@
Frame ratio

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

-
- {#each FRAME_PRESETS as r (r)} - - {/each} -
+
@@ -690,22 +624,13 @@ { label: "Cinema", token: "--sf-ratio-cinema", def: "21 / 9" }, { label: "Golden", token: "--sf-ratio-golden", def: "1.618 / 1" }, ] as r (r.token)} -
- {r.label} - { - const v = (e.target as HTMLInputElement).value.trim(); - v ? onSet(r.token, v) : onReset(r.token); - }} - 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-1 text-[9px] font-mono text-slate-700 dark:text-slate-300 placeholder:text-slate-600 focus:outline-none focus:border-indigo-500" - /> - {#if r.token in overrides} - - {/if} -
+ onSet(r.token, v)} + onReset={() => onReset(r.token)} + /> {/each}
{/if} diff --git a/configurator/src/components/panels/MacrosPanel.svelte b/configurator/src/components/panels/MacrosPanel.svelte index 18d3f6416..aa7134fa3 100644 --- a/configurator/src/components/panels/MacrosPanel.svelte +++ b/configurator/src/components/panels/MacrosPanel.svelte @@ -1,5 +1,9 @@ + +{#snippet weightGrid(label: string, tokenName: string, defaultVal: string)} + {@const current = overrides[tokenName] ?? defaultVal} + {@const isOverridden = tokenName in overrides} +
+
+ {label} weight + {#if isOverridden} + + {/if} +
+
+ {#each WEIGHT_OPTIONS as w (w)} + + {/each} +
+
+{/snippet} +
-
- - {#if showFontFamilies} +
{#each [ { label: "Body", token: "--sf-font-body", current: currentBodyFont, opts: BODY_STACKS }, { label: "Heading", token: "--sf-font-heading", current: currentHeadingFont, opts: HEADING_STACKS }, @@ -332,22 +353,12 @@ Load & apply to {customFontTarget}
- {/if} - +
-
- - {#if showPerType} +
@@ -477,53 +488,16 @@ >reset {activeLevel.label} {/if}
- {/if} -
+
-
- - {#if showBodyText} +
- {#each [ - { label: "Body strong", tokenName: "--sf-font-weight-strong" }, - { label: "Interactive", tokenName: "--sf-font-weight-interactive" }, - ] as row (row.tokenName)} - {@const defaultVal = WEIGHT_DEFAULTS[row.tokenName] ?? "600"} - {@const current = overrides[row.tokenName] ?? defaultVal} - {@const isOverridden = row.tokenName in overrides} -
-
- {row.label} weight - {#if isOverridden} - - {/if} -
-
- {#each WEIGHT_OPTIONS as w (w)} - - {/each} -
-
- {/each} + {@render weightGrid("Body strong", "--sf-font-weight-strong", WEIGHT_DEFAULTS["--sf-font-weight-strong"] ?? "600")} + {@render weightGrid("Interactive", "--sf-font-weight-interactive", WEIGHT_DEFAULTS["--sf-font-weight-interactive"] ?? "600")}
@@ -610,22 +584,12 @@ {/if}
- {/if} -
+
-
- - {#if showFontWeights} +

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

{#each [ { label: "Light", token: "--sf-font-weight-light", def: 300 }, @@ -657,22 +621,12 @@ {/each} - {/if} -
+
-
- - {#if showDisplayType} +
onSet("--sf-text-display-base-max", String(v))} /> - -
+ + onSet("--sf-text-display-scale", String(v))} + onReset={() => onReset("--sf-text-display-scale")} + /> + +

+ Modular ratio is shared with the text scale (Mobile {ratioMin} → Desktop {ratioMax}) — edit it under “Modular scale”. +

+ + +
{#each [ - { label: "Display L line-height", token: "--sf-display-l-line-height", def: 1 }, - { label: "Display M line-height", token: "--sf-display-m-line-height", def: 1.05 }, + { label: "Display S", token: "--sf-display-s-line-height", def: 1.1 }, + { label: "Display M", token: "--sf-display-m-line-height", def: 1.05 }, + { label: "Display L", token: "--sf-display-l-line-height", def: 1 }, ] as row (row.token)}
-
{row.label}
+
{row.label} line-height
- {#each [ - { label: "Display", tokenName: "--sf-font-weight-display" }, - { label: "Heading", tokenName: "--sf-font-weight-heading" }, - ] as row (row.tokenName)} - {@const defaultVal = WEIGHT_DEFAULTS[row.tokenName] ?? "700"} - {@const current = overrides[row.tokenName] ?? defaultVal} - {@const isOverridden = row.tokenName in overrides} -
-
- {row.label} weight - {#if isOverridden} - - {/if} -
-
- {#each WEIGHT_OPTIONS as w (w)} - - {/each} -
-
- {/each} - {/if} -
+ {@render weightGrid("Display", "--sf-font-weight-display", WEIGHT_DEFAULTS["--sf-font-weight-display"] ?? "700")} + {@render weightGrid("Heading", "--sf-font-weight-heading", WEIGHT_DEFAULTS["--sf-font-weight-heading"] ?? "700")} +
-
- - {#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. @@ -783,6 +715,15 @@ onRatioMaxChange={(v) => onSet("--sf-text-ratio-max", String(v))} /> + + onSet("--sf-text-scale", String(v))} + onReset={() => onReset("--sf-text-scale")} + /> +

Line height scale
@@ -854,8 +795,7 @@
{/if} - {/if} -
+
@@ -870,18 +810,38 @@ {showScalePreview ? "▲" : "▼"} {#if showScalePreview} -
+ {@const midBase = (baseMin + baseMax) / 2} + {@const midDisp = (dispMin + dispMax) / 2} + {@const midRatio = (ratioMin + ratioMax) / 2} + +
+
Text
{#each [...TEXT_STEPS].reverse() as { label, factor } (label)} - {@const midBase = (baseMin + baseMax) / 2} {@const size = midBase * factor * textScale}
{label} - Aa - + class="text-slate-900/80 dark:text-white/80 font-medium leading-none" + style={`font-size: ${size.toFixed(3)}rem; font-family: ${currentBodyFont || "inherit"}`} + >Aa + {size.toFixed(2)}rem +
+ {/each} + +
Display
+ {#each [ + { label: "l", factor: midRatio * midRatio }, + { label: "m", factor: midRatio }, + { label: "s", factor: 1 }, + ] as { label, factor } (label)} + {@const size = midDisp * factor * displayScale} +
+ {label} + Aa {size.toFixed(2)}rem
{/each} @@ -892,16 +852,7 @@
-
- - {#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.

@@ -935,22 +886,12 @@
{/each}
- {/if} - +
-
- - {#if showAdvancedType} +

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

@@ -1024,6 +965,5 @@ {/if} - {/if} -
+
diff --git a/configurator/src/lib/variableScales.ts b/configurator/src/lib/variableScales.ts index 4fd07abb3..64f04ab70 100644 --- a/configurator/src/lib/variableScales.ts +++ b/configurator/src/lib/variableScales.ts @@ -38,3 +38,26 @@ export const CONTAINER_SCALE: VarOption[] = scale("container", [ export const SIZE_SCALE: VarOption[] = scale("size", ["xs", "s", "m", "l", "xl"]); export const SHADOW_SCALE: VarOption[] = scale("shadow", ["xs", "s", "m", "l", "xl"]); + +/** + * Map a token's default value (e.g. `var(--sf-space-m)`) to the sibling scale + * it draws from, so generic editors can offer that scale as a dropdown by + * default. Returns null when the value doesn't reference a known scale. + */ +export function scaleForValue(value: string | null | undefined): VarOption[] | null { + if (!value) return null; + // Only a *pure* single-token reference qualifies — not a compound value like + // a border shorthand (`var(--sf-border-width-2) solid …`), which the dropdown + // would clobber down to just the width. + const m = value.trim().match(/^var\(\s*--sf-(space|radius|border-width|container|size|shadow)-[a-z0-9]+\s*\)$/); + if (!m) return null; + switch (m[1]) { + case "space": return SPACE_SCALE; + case "radius": return RADIUS_SCALE; + case "border-width": return BORDER_WIDTH_SCALE; + case "container": return CONTAINER_SCALE; + case "size": return SIZE_SCALE; + case "shadow": return SHADOW_SCALE; + default: return null; + } +}