From 6e03ad520f7ea7e8b9c448a165adeb5ecea0b5ac Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Jun 2026 19:30:31 +0000 Subject: [PATCH 1/4] feat(configurator): collapsible color panel sections + shade curve mini palettes + fade gradient fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Make Brand color sources, Text contrast, and Shade/tint curve sections collapsible (start expanded), matching the existing LumLocker/Status/Gradients pattern - Add Mini palettes dynamic preview in Shade/tint curve: shows all 5 brand colors × all 11 palette steps, with shared light/dark toggle linked to the existing curve preview toggle - Fade gradients (fade-right/left/up/down) no longer show direction-change buttons since their direction is their identity; brand gradients keep the angle/direction controls Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_016G1SLZmmZy4d71nYpotZbX --- .../src/components/panels/ColorsPanel.svelte | 69 +++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/configurator/src/components/panels/ColorsPanel.svelte b/configurator/src/components/panels/ColorsPanel.svelte index cd98f325..b7b40ba6 100644 --- a/configurator/src/components/panels/ColorsPanel.svelte +++ b/configurator/src/components/panels/ColorsPanel.svelte @@ -167,6 +167,9 @@ (k) => k.name === "--sf-focus-ring-width" ); + let showBrandSources = $state(true); + let showTextContrast = $state(true); + let showShadeCurve = $state(true); let showStatus = $state(false); let showSemanticOverrides = $state(false); let showGradients = $state(false); @@ -323,7 +326,14 @@
-
Brand color sources
+ + {#if showBrandSources}

OKLCH source values — all 200+ derived color steps are computed automatically.

@@ -409,6 +419,7 @@ {/each} + {/if}
@@ -531,7 +542,14 @@
-
Text contrast
+ + {#if showTextContrast}

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

@@ -577,13 +595,21 @@ onChange={(name, val) => val === null ? onReset(name) : onSet(name, val)} /> {/each} + {/if}
-
Shade / tint curve
+ + {#if showShadeCurve}

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

@@ -672,6 +698,39 @@

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

+ + +
+
+ Mini palettes — all brand colors +
+ {#each ["light", "dark"] as side (side)} + + {/each} +
+
+ {#each BRAND_COLOR_KEYS as colorKey (colorKey)} +
+ {colorKey} +
+ {#each SWATCH_STEPS as step (step)} + {@const swatch = paintTheme(`var(--sf-color-${colorKey}-${step})`, curvePreviewSide, `var(--sf-color-${colorKey}-${step})`)} +
+ {/each} +
+
+ {/each} +
+ {/if}
@@ -707,7 +766,7 @@ - {#if g.kind === "dir"} + {#if g.kind === "dir" && g.group !== "fade"}
Dir {#each ["top", "right", "bottom", "left"] as d (d)} @@ -719,7 +778,7 @@ >{d} {/each}
- {:else} + {:else if g.kind !== "dir"}
Angle Date: Sat, 27 Jun 2026 20:45:17 +0000 Subject: [PATCH 2/4] feat(configurator): make all setting groups collapsible across panels Every section header in Borders, Effects, Layout, Macros, Misc, Motion, Shadows, Spacing, Typography, and WCAG panels now toggles open/closed with a chevron button. Sections default to open; previously collapsed-by-default sections (e.g. Fine-tune, Bento, Prose) keep their existing defaults. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_016G1SLZmmZy4d71nYpotZbX --- .../src/components/panels/BordersPanel.svelte | 389 +++++++++------ .../src/components/panels/EffectsPanel.svelte | 379 ++++++++------ .../src/components/panels/LayoutPanel.svelte | 377 ++++++++------ .../src/components/panels/MacrosPanel.svelte | 251 ++++++---- .../src/components/panels/MiscPanel.svelte | 464 ++++++++++-------- .../src/components/panels/MotionPanel.svelte | 312 +++++++----- .../src/components/panels/ShadowsPanel.svelte | 207 ++++---- .../src/components/panels/SpacingPanel.svelte | 241 +++++---- .../components/panels/TypographyPanel.svelte | 68 ++- .../src/components/panels/WcagPanel.svelte | 22 +- 10 files changed, 1600 insertions(+), 1110 deletions(-) diff --git a/configurator/src/components/panels/BordersPanel.svelte b/configurator/src/components/panels/BordersPanel.svelte index 8dc8024f..3f437218 100644 --- a/configurator/src/components/panels/BordersPanel.svelte +++ b/configurator/src/components/panels/BordersPanel.svelte @@ -36,6 +36,13 @@ const knobs = KNOBS_BY_DOMAIN["borders"] ?? []; + let showBorderColor = $state(true); + let showBorderWidths = $state(true); + let showLineStyles = $state(true); + let showDividers = $state(true); + let showFocusRing = $state(true); + let showRadiusScale = $state(true); + let showRadiusPreview = $state(true); let showFineTune = $state(false); let showComponents = $state(false); @@ -85,191 +92,239 @@
- +
-
Border color
-
- onSet("--sf-color-border", (e.target as HTMLInputElement).value)} - class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" - /> - {borderColor || "auto-derived"} - {#if "--sf-color-border" in overrides} - - {/if} -
-

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

+ + {#if showBorderColor} +
+ onSet("--sf-color-border", (e.target as HTMLInputElement).value)} + class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {borderColor || "auto-derived"} + {#if "--sf-color-border" in overrides} + + {/if} +
+

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

+ {/if}
-
Border widths
- onSet("--sf-border-scale", String(v))} - onReset={() => onReset("--sf-border-scale")} - /> + + {#if showBorderWidths} + onSet("--sf-border-scale", String(v))} + onReset={() => onReset("--sf-border-scale")} + /> - -
- {#each [1, 2, 3, 4] as base (base)} -
- {base}px → -
- - {(base * borderScale).toFixed(1)}px - -
- {/each} -
+ +
+ {#each [1, 2, 3, 4] as base (base)} +
+ {base}px → +
+ + {(base * borderScale).toFixed(1)}px + +
+ {/each} +
+ {/if}
- +
-
Line styles
-
- {#each [ - { label: "Border", token: "--sf-border-style", default: "solid" }, - { label: "Divider", token: "--sf-divider-style", default: "solid" }, - ] as row (row.token)} -
-
{row.label}
-
- {#each BORDER_STYLES as s (s)} - {@const current = getStyleCurrent(row.token, row.default)} - - {/each} + + {#if showLineStyles} +
+ {#each [ + { label: "Border", token: "--sf-border-style", default: "solid" }, + { label: "Divider", token: "--sf-divider-style", default: "solid" }, + ] as row (row.token)} +
+
{row.label}
+
+ {#each BORDER_STYLES as s (s)} + {@const current = getStyleCurrent(row.token, row.default)} + + {/each} +
-
- {/each} -
+ {/each} +
- -
-
- Border sample + +
+
+ Border sample +
+ {(1 * borderScale).toFixed(1)}px · {borderStyle}
- {(1 * borderScale).toFixed(1)}px · {borderStyle} -
+ {/if}
-
Dividers
-
-
Color
- onSet("--sf-divider-color", (e.target as HTMLInputElement).value)} - class="w-7 h-7 rounded border border-white/10 bg-transparent cursor-pointer" + + {#if showDividers} +
+
Color
+ onSet("--sf-divider-color", (e.target as HTMLInputElement).value)} + class="w-7 h-7 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {dividerColor || "inherits border"} + {#if "--sf-divider-color" in overrides} + + {/if} +
+ onSet("--sf-divider-width", `${v}px`)} + onReset={() => onReset("--sf-divider-width")} /> - {dividerColor || "inherits border"} - {#if "--sf-divider-color" in overrides} - - {/if} -
- onSet("--sf-divider-width", `${v}px`)} - onReset={() => onReset("--sf-divider-width")} - /> - onSet("--sf-divider-gap", `${v}rem`)} - onReset={() => onReset("--sf-divider-gap")} - /> + onSet("--sf-divider-gap", `${v}rem`)} + onReset={() => onReset("--sf-divider-gap")} + /> + {/if}
-
Focus ring
- onSet("--sf-focus-ring-width", `${v}px`)} - onReset={() => onReset("--sf-focus-ring-width")} - /> - onSet("--sf-focus-ring-offset", `${v}px`)} - onReset={() => onReset("--sf-focus-ring-offset")} - /> -
-
Ring color
- onSet("--sf-focus-ring-color", (e.target as HTMLInputElement).value)} - class="w-7 h-7 rounded border border-white/10 bg-transparent cursor-pointer" + + {#if showFocusRing} + onSet("--sf-focus-ring-width", `${v}px`)} + onReset={() => onReset("--sf-focus-ring-width")} + /> + onSet("--sf-focus-ring-offset", `${v}px`)} + onReset={() => onReset("--sf-focus-ring-offset")} /> - {focusRingColor || "default (action)"} - {#if "--sf-focus-ring-color" in overrides} - - {/if} -
- -
-
- Focus preview +
+
Ring color
+ onSet("--sf-focus-ring-color", (e.target as HTMLInputElement).value)} + class="w-7 h-7 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {focusRingColor || "default (action)"} + {#if "--sf-focus-ring-color" in overrides} + + {/if} +
+ +
+
+ Focus preview +
-
+ {/if}
-
Radius scale
-
- {#each knobs as k (k.name)} - val === null ? onReset(name) : onSet(name, val)} - /> - {/each} -
+ + {#if showRadiusScale} +
+ {#each knobs as k (k.name)} + val === null ? onReset(name) : onSet(name, val)} + /> + {/each} +
+ {/if}
@@ -302,22 +357,30 @@
-
Radius preview
-
-
- {#each RADIUS_STEPS as step (step)} - {@const base = BASE_RADII[step] ?? 6} - {@const computed = step === "full" ? 9999 : base * radiusScale} -
-
- {step} -
- {/each} + + {#if showRadiusPreview} +
+
+ {#each RADIUS_STEPS as step (step)} + {@const base = BASE_RADII[step] ?? 6} + {@const computed = step === "full" ? 9999 : base * radiusScale} +
+
+ {step} +
+ {/each} +
-
+ {/if}
diff --git a/configurator/src/components/panels/EffectsPanel.svelte b/configurator/src/components/panels/EffectsPanel.svelte index 4a91cf5c..09bc09d7 100644 --- a/configurator/src/components/panels/EffectsPanel.svelte +++ b/configurator/src/components/panels/EffectsPanel.svelte @@ -35,208 +35,263 @@ let pendingOpacity = $derived(parseNum(overrides["--sf-state-pending-opacity"], 0.7)); let scrollbarThumb = $derived(overrides["--sf-scrollbar-thumb"] ?? ""); let scrollbarTrack = $derived(overrides["--sf-scrollbar-track"] ?? ""); + + let showBlur = $state(true); + let showOpacity = $state(true); + let showScrim = $state(true); + let showScrollbar = $state(true); + let showScrollShadow = $state(true); + let showTextShadow = $state(true);
-
Blur
- onSet("--sf-blur", `${v}px`)} - onReset={() => onReset("--sf-blur")} - /> - -
-
- {#each Array.from({ length: 12 }) as _, i (i)} -
- {/each} -
-
- {blur}px + + {#if showBlur} + onSet("--sf-blur", `${v}px`)} + onReset={() => onReset("--sf-blur")} + /> + +
+
+ {#each Array.from({ length: 12 }) as _, i (i)} +
+ {/each} +
+
+ {blur}px +
-
+ {/if}
-
Opacity
- onSet("--sf-opacity-muted", String(v))} - onReset={() => onReset("--sf-opacity-muted")} - /> - onSet("--sf-opacity-disabled", String(v))} - onReset={() => onReset("--sf-opacity-disabled")} - /> - onSet("--sf-state-pending-opacity", String(v))} - 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.val} -
- {/each} -
+ + {#if showOpacity} + onSet("--sf-opacity-muted", String(v))} + onReset={() => onReset("--sf-opacity-muted")} + /> + onSet("--sf-opacity-disabled", String(v))} + onReset={() => onReset("--sf-opacity-disabled")} + /> + onSet("--sf-state-pending-opacity", String(v))} + 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.val} +
+ {/each} +
+ {/if}
-
Scrim overlay
-

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

- - -
-
Color
- onSet("--sf-scrim-color", (e.target as HTMLInputElement).value)} - class="w-7 h-7 rounded border border-white/10 bg-transparent cursor-pointer" - /> - {scrimColor || "default (base color)"} - {#if "--sf-scrim-color" in overrides} - - {/if} -
- -
-
Direction
-
- {#each SCRIM_DIRECTIONS as d (d.value)} - - {/each} + + {#if showScrim} +

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

+ + +
+
Color
+ onSet("--sf-scrim-color", (e.target as HTMLInputElement).value)} + class="w-7 h-7 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {scrimColor || "default (base color)"} + {#if "--sf-scrim-color" in overrides} + + {/if} +
+ +
+
Direction
+
+ {#each SCRIM_DIRECTIONS as d (d.value)} + + {/each} +
-
- - -
-
+ + +
-
Scrim preview
+ class="w-full h-full relative" + style="background: linear-gradient(135deg, oklch(0.3 0.15 264), oklch(0.25 0.1 300))" + > +
+
Scrim preview
+
-
+ {/if}
-
Scrollbar
- {#each [ - { label: "Thumb color", token: "--sf-scrollbar-thumb", val: scrollbarThumb, default: "#6366f1" }, - { label: "Track color", token: "--sf-scrollbar-track", val: scrollbarTrack, default: "transparent" }, - ] as row (row.token)} -
-
{row.label}
- onSet(row.token, (e.target as HTMLInputElement).value)} - class="w-7 h-7 rounded border border-white/10 bg-transparent cursor-pointer" - /> - {row.val || "default"} - {#if row.token in overrides} - - {/if} -
- {/each} - -
-
-
+ + {#if showScrollbar} + {#each [ + { label: "Thumb color", token: "--sf-scrollbar-thumb", val: scrollbarThumb, default: "#6366f1" }, + { label: "Track color", token: "--sf-scrollbar-track", val: scrollbarTrack, default: "transparent" }, + ] as row (row.token)} +
+
{row.label}
+ onSet(row.token, (e.target as HTMLInputElement).value)} + class="w-7 h-7 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {row.val || "default"} + {#if row.token in overrides} + + {/if} +
+ {/each} + +
+
+
+
+ Scrollbar preview
- Scrollbar preview -
+ {/if}
-
Scroll shadow
- onSet("--sf-scroll-shadow-size", `${v}rem`)} - onReset={() => onReset("--sf-scroll-shadow-size")} - /> + + {#if showScrollShadow} + onSet("--sf-scroll-shadow-size", `${v}rem`)} + onReset={() => onReset("--sf-scroll-shadow-size")} + /> + {/if}
-
Text shadow
-

- Controls text legibility over images. Applied via text-shadow: var(--sf-text-shadow-m). -

-
- {#each TEXT_SHADOW_PRESETS as p (p.label)} - - {/each} -
+ + {#if showTextShadow} +

+ Controls text legibility over images. Applied via text-shadow: var(--sf-text-shadow-m). +

+
+ {#each TEXT_SHADOW_PRESETS as p (p.label)} + + {/each} +
+ {/if}
diff --git a/configurator/src/components/panels/LayoutPanel.svelte b/configurator/src/components/panels/LayoutPanel.svelte index 481352e5..3968bc96 100644 --- a/configurator/src/components/panels/LayoutPanel.svelte +++ b/configurator/src/components/panels/LayoutPanel.svelte @@ -35,6 +35,12 @@ { label: "Row tall", token: "--sf-bento-row-tall", min: 8, max: 32, step: 0.5, unit: "rem", default: 16 }, ]; + let showContainerWidths = $state(true); + let showCenterWrapper = $state(true); + let showAutoGrid = $state(true); + let showHeaderHeight = $state(true); + let showStickyOffset = $state(true); + let showSidebar = $state(true); let showBento = $state(false); let showMore = $state(false); @@ -70,206 +76,253 @@
-
Container widths
- - -
-
-
Narrow
- onSet("--sf-container-narrow", `${v}rem`)} - onReset={() => onReset("--sf-container-narrow")} - /> -
-
-
Prose
- onSet("--sf-container-prose", `${v}ch`)} - onReset={() => onReset("--sf-container-prose")} - /> -
-
-
Default
- onSet("--sf-container-default", `${v}rem`)} - onReset={() => onReset("--sf-container-default")} - /> -
-
-
Wide
- onSet("--sf-container-wide", `${v}rem`)} - onReset={() => onReset("--sf-container-wide")} - /> + + {#if showContainerWidths} + +
+
+
Narrow
+ onSet("--sf-container-narrow", `${v}rem`)} + onReset={() => onReset("--sf-container-narrow")} + /> +
+
+
Prose
+ onSet("--sf-container-prose", `${v}ch`)} + onReset={() => onReset("--sf-container-prose")} + /> +
+
+
Default
+ onSet("--sf-container-default", `${v}rem`)} + onReset={() => onReset("--sf-container-default")} + /> +
+
+
Wide
+ onSet("--sf-container-wide", `${v}rem`)} + onReset={() => onReset("--sf-container-wide")} + /> +
-
- - -
- {#each [ - { label: "narrow", value: containerNarrow, max: containerWide, unit: "rem" }, - { label: "prose", value: containerProse, max: containerWide * 1.5, unit: "ch" }, - { label: "default", value: containerDefault, max: containerWide, unit: "rem" }, - { label: "wide", value: containerWide, max: containerWide, unit: "rem" }, - ] as row (row.label)} -
- {row.label} -
-
+ + +
+ {#each [ + { label: "narrow", value: containerNarrow, max: containerWide, unit: "rem" }, + { label: "prose", value: containerProse, max: containerWide * 1.5, unit: "ch" }, + { label: "default", value: containerDefault, max: containerWide, unit: "rem" }, + { label: "wide", value: containerWide, max: containerWide, unit: "rem" }, + ] as row (row.label)} +
+ {row.label} +
+
+
+ {row.value}{row.unit}
- {row.value}{row.unit} -
- {/each} -
+ {/each} +
+ {/if}
-
Center wrapper
- onSet("--sf-center-max", `${v}rem`)} - onReset={() => onReset("--sf-center-max")} - /> - onSet("--sf-center-gutter", `${v}rem`)} - onReset={() => onReset("--sf-center-gutter")} - /> + + {#if showCenterWrapper} + onSet("--sf-center-max", `${v}rem`)} + onReset={() => onReset("--sf-center-max")} + /> + onSet("--sf-center-gutter", `${v}rem`)} + onReset={() => onReset("--sf-center-gutter")} + /> + {/if}
-
Auto grid
- onSet("--sf-grid-min", `${v}rem`)} - onReset={() => onReset("--sf-grid-min")} - /> -
-
Preview at 360px panel width
-
- {#each Array.from({ length: 8 }) as _, i (i)} -
col
- {/each} + + {#if showAutoGrid} + onSet("--sf-grid-min", `${v}rem`)} + onReset={() => onReset("--sf-grid-min")} + /> +
+
Preview at 360px panel width
+
+ {#each Array.from({ length: 8 }) as _, i (i)} +
col
+ {/each} +
-
+ {/if}
-
Header height
-
-
-
Mobile
- onSet("--sf-header-height-mobile", `${v}rem`)} - onReset={() => onReset("--sf-header-height-mobile")} - /> -
-
-
Desktop
- onSet("--sf-header-height-desktop", `${v}rem`)} - onReset={() => onReset("--sf-header-height-desktop")} - /> + + {#if showHeaderHeight} +
+
+
Mobile
+ onSet("--sf-header-height-mobile", `${v}rem`)} + onReset={() => onReset("--sf-header-height-mobile")} + /> +
+
+
Desktop
+ onSet("--sf-header-height-desktop", `${v}rem`)} + onReset={() => onReset("--sf-header-height-desktop")} + /> +
-
-
-
-
-
- {#each [1, 2, 3] as i (i)} -
- {/each} +
+
+
+
+ {#each [1, 2, 3] as i (i)} +
+ {/each} +
+
{headerMobile}rem mobile · {headerDesktop}rem desktop
-
{headerMobile}rem mobile · {headerDesktop}rem desktop
-
+ {/if}
-
Sticky offset
-

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

-
-
-
Mobile
- onSet("--sf-sticky-offset-mobile", `${v}rem`)} - onReset={() => onReset("--sf-sticky-offset-mobile")} - /> -
-
-
Desktop
- onSet("--sf-sticky-offset-desktop", `${v}rem`)} - onReset={() => onReset("--sf-sticky-offset-desktop")} - /> + + {#if showStickyOffset} +

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

+
+
+
Mobile
+ onSet("--sf-sticky-offset-mobile", `${v}rem`)} + onReset={() => onReset("--sf-sticky-offset-mobile")} + /> +
+
+
Desktop
+ onSet("--sf-sticky-offset-desktop", `${v}rem`)} + onReset={() => onReset("--sf-sticky-offset-desktop")} + /> +
-
+ {/if}
-
Sidebar
- onSet("--sf-sidebar-width", `${v}rem`)} - onReset={() => onReset("--sf-sidebar-width")} - /> -
-
- {sidebarWidth}rem + + {#if showSidebar} + onSet("--sf-sidebar-width", `${v}rem`)} + onReset={() => onReset("--sf-sidebar-width")} + /> +
+
+ {sidebarWidth}rem +
+
-
-
+ {/if}
diff --git a/configurator/src/components/panels/MacrosPanel.svelte b/configurator/src/components/panels/MacrosPanel.svelte index 9a18737e..ad709783 100644 --- a/configurator/src/components/panels/MacrosPanel.svelte +++ b/configurator/src/components/panels/MacrosPanel.svelte @@ -44,6 +44,11 @@ { label: "HR margin", token: "--sf-prose-hr-margin", def: 1.5, max: 4 }, ]; + let showFlow = $state(true); + let showLineClamp = $state(true); + let showAspect = $state(true); + let showScrollShadow = $state(true); + let showScrim = $state(true); let showProse = $state(false); let lineClamp = $derived(num("--sf-line-clamp", 3)); @@ -73,134 +78,174 @@
-
Flow rhythm
- onSet("--sf-flow-space", `${v}rem`)} - onReset={() => onReset("--sf-flow-space")} - /> -
- {#each [0, 1, 2] as i (i)} -
- {/each} -
+ + {#if showFlow} + onSet("--sf-flow-space", `${v}rem`)} + onReset={() => onReset("--sf-flow-space")} + /> +
+ {#each [0, 1, 2] as i (i)} +
+ {/each} +
+ {/if}
-
Line clamp
- onSet("--sf-line-clamp", String(v))} - onReset={() => onReset("--sf-line-clamp")} - /> -
-

- SLASHED is a token-first CSS framework. This sample paragraph repeats so you - can see exactly how many lines survive the clamp before the ellipsis kicks in. - Drag the slider above and watch the visible line count change in real time. - Extra filler text keeps the block taller than the clamp at every setting. -

-
+ + {#if showLineClamp} + onSet("--sf-line-clamp", String(v))} + onReset={() => onReset("--sf-line-clamp")} + /> +
+

+ SLASHED is a token-first CSS framework. This sample paragraph repeats so you + can see exactly how many lines survive the clamp before the ellipsis kicks in. + Drag the slider above and watch the visible line count change in real time. + Extra filler text keeps the block taller than the clamp at every setting. +

+
+ {/if}
-
Aspect ratio
-

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

-
- {#each ASPECT_PRESETS as p (p.value)} - - {/each} -
-
-
{aspectVal}
-
+ + {#if showAspect} +

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

+
+ {#each ASPECT_PRESETS as p (p.value)} + + {/each} +
+
+
{aspectVal}
+
+ {/if}
-
Scroll shadow
- onSet("--sf-scroll-shadow-size", `${v}rem`)} - onReset={() => onReset("--sf-scroll-shadow-size")} - /> -
-
-
+ + {#if showScrollShadow} + onSet("--sf-scroll-shadow-size", `${v}rem`)} + onReset={() => onReset("--sf-scroll-shadow-size")} + /> +
+
+
+ {/if}
-
Scrim overlay
-

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

-
-
Color
- { - const v = (e.target as HTMLInputElement).value.trim(); - v ? onSet("--sf-scrim-color", v) : onReset("--sf-scrim-color"); - }} - class="flex-1 min-w-0 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" - /> - {#if "--sf-scrim-color" in overrides} - - {/if} -
-
-
Direction
-
- {#each SCRIM_DIRECTIONS as d (d.value)} - - {/each} + + {#if showScrim} +

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

+
+
Color
+ { + const v = (e.target as HTMLInputElement).value.trim(); + v ? onSet("--sf-scrim-color", v) : onReset("--sf-scrim-color"); + }} + class="flex-1 min-w-0 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" + /> + {#if "--sf-scrim-color" in overrides} + + {/if} +
+
+
Direction
+
+ {#each SCRIM_DIRECTIONS as d (d.value)} + + {/each} +
+
+
+
+ Caption over scrim
-
-
-
- Caption over scrim -
+ {/if}
diff --git a/configurator/src/components/panels/MiscPanel.svelte b/configurator/src/components/panels/MiscPanel.svelte index 9c73a713..195ca651 100644 --- a/configurator/src/components/panels/MiscPanel.svelte +++ b/configurator/src/components/panels/MiscPanel.svelte @@ -45,6 +45,14 @@ let underlineThickness = $derived(overrides["--sf-link-underline-thickness"] ?? "auto"); let focusRingStyle = $derived(overrides["--sf-focus-ring-style"] ?? "solid"); + let showTouchTarget = $state(true); + let showScrollBehavior = $state(true); + let showZIndex = $state(true); + let showTextSelection = $state(true); + let showFocusRingStyle = $state(true); + let showComponentSizes = $state(true); + let showCaretLinks = $state(true); + function getSizeValue(t: typeof SIZE_TOKENS[0]): number { return parseNum(overrides[t.token]?.replace("rem",""), t.default); } @@ -54,251 +62,307 @@
-
Touch target
- onSet("--sf-touch-target", `${v}px`)} - onReset={() => onReset("--sf-touch-target")} - /> - -
-
- {touchTarget}px + + {#if showTouchTarget} + onSet("--sf-touch-target", `${v}px`)} + onReset={() => onReset("--sf-touch-target")} + /> + +
+
+ {touchTarget}px +
+

Minimum interactive area — ensures accessibility on touch devices.

-

Minimum interactive area — ensures accessibility on touch devices.

-
+ {/if}
-
Scroll behavior
-
- {#each SCROLL_BEHAVIORS as b (b.value)} - - {/each} -
+ + {#if showScrollBehavior} +
+ {#each SCROLL_BEHAVIORS as b (b.value)} + + {/each} +
+ {/if}
-
Z-index layers
- 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.token.replace("--sf-z-", "")} -
- {/each} -
-

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

+ + {#if showZIndex} + 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.token.replace("--sf-z-", "")} +
+ {/each} +
+

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

+ {/if}
-
Text selection
-
-
-
Selection background
-
- onSet("--sf-color-selection-bg", (e.target as HTMLInputElement).value)} - class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" - /> - {overrides["--sf-color-selection-bg"] ?? "default"} - {#if "--sf-color-selection-bg" in overrides} - - {/if} + + {#if showTextSelection} +
+
+
Selection background
+
+ onSet("--sf-color-selection-bg", (e.target as HTMLInputElement).value)} + class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {overrides["--sf-color-selection-bg"] ?? "default"} + {#if "--sf-color-selection-bg" in overrides} + + {/if} +
-
-
-
Selection text color
-
- onSet("--sf-color-selection-text", (e.target as HTMLInputElement).value)} - class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" - /> - {overrides["--sf-color-selection-text"] ?? "default"} - {#if "--sf-color-selection-text" in overrides} - - {/if} +
+
Selection text color
+
+ onSet("--sf-color-selection-text", (e.target as HTMLInputElement).value)} + class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {overrides["--sf-color-selection-text"] ?? "default"} + {#if "--sf-color-selection-text" in overrides} + + {/if} +
+
+ +
+

+ Select this text to preview the selection color. +

- -
-

- Select this text to preview the selection color. -

-
-
+ {/if}
-
Focus ring style
-
- {#each ["solid", "dashed", "dotted"] as style (style)} - {@const current = overrides["--sf-focus-ring-style"] ?? "solid"} - + {#if showFocusRingStyle} +
+ {#each ["solid", "dashed", "dotted"] as style (style)} + {@const current = overrides["--sf-focus-ring-style"] ?? "solid"} + + {/each} +
+ +
+
- {style} - - {/each} -
- -
-
- Focus ring · {focusRingStyle} + Focus ring · {focusRingStyle} +
-
+ {/if}
-
Component size scale
-

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

-
- {#each SIZE_TOKENS as s (s.token)} - {@const val = getSizeValue(s)} - onSet(s.token, `${v}rem`)} - onReset={() => onReset(s.token)} - /> - {/each} -
- -
- {#each SIZE_TOKENS as s (s.token)} - {@const val = getSizeValue(s)} -
-
- {s.label} -
- {/each} -
+ + {#if showComponentSizes} +

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

+
+ {#each SIZE_TOKENS as s (s.token)} + {@const val = getSizeValue(s)} + onSet(s.token, `${v}rem`)} + onReset={() => onReset(s.token)} + /> + {/each} +
+ +
+ {#each SIZE_TOKENS as s (s.token)} + {@const val = getSizeValue(s)} +
+
+ {s.label} +
+ {/each} +
+ {/if}
-
Caret & links
-
-
Caret color
-
- onSet("--sf-caret-color", (e.target as HTMLInputElement).value)} - class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" - /> - {caretColor || "default (action color)"} - {#if "--sf-caret-color" in overrides} - - {/if} + + {#if showCaretLinks} +
+
Caret color
+
+ onSet("--sf-caret-color", (e.target as HTMLInputElement).value)} + class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {caretColor || "default (action color)"} + {#if "--sf-caret-color" in overrides} + + {/if} +
-
- onSet("--sf-link-underline-offset", `${v}em`)} - onReset={() => onReset("--sf-link-underline-offset")} - /> -
-
- Underline thickness - {#if "--sf-link-underline-thickness" in overrides} - - {/if} + onSet("--sf-link-underline-offset", `${v}em`)} + onReset={() => onReset("--sf-link-underline-offset")} + /> +
+
+ Underline thickness + {#if "--sf-link-underline-thickness" in overrides} + + {/if} +
+
+ {#each [["auto", "auto"], ["1px", "1px"], ["2px", "2px"], ["3px", "3px"]] as [label, val] (val)} + + {/each} +
-
- {#each [["auto", "auto"], ["1px", "1px"], ["2px", "2px"], ["3px", "3px"]] as [label, val] (val)} - - {/each} + + -
- - + {/if}
diff --git a/configurator/src/components/panels/MotionPanel.svelte b/configurator/src/components/panels/MotionPanel.svelte index 19be4870..726d2784 100644 --- a/configurator/src/components/panels/MotionPanel.svelte +++ b/configurator/src/components/panels/MotionPanel.svelte @@ -47,6 +47,14 @@ let animating = $state(false); let animOffsetX = $state(0); + let showGlobalScale = $state(true); + let showThemeTransition = $state(true); + let showDurationOverrides = $state(true); + let showStaggerBase = $state(true); + let showEasingCurves = $state(true); + let showAnimationDemo = $state(true); + let showDurationPreview = $state(true); + function getDuration(token: string, base: number): number { const raw = overrides[token]; if (raw) return parseFloat(raw); @@ -111,170 +119,226 @@
-
Global scale
-
- {#each knobs as k (k.name)} - val === null ? onReset(name) : onSet(name, val)} - /> - {/each} -
+ + {#if showGlobalScale} +
+ {#each knobs as k (k.name)} + val === null ? onReset(name) : onSet(name, val)} + /> + {/each} +
+ {/if}
-
Theme transition
- onSet("--sf-theme-transition-duration", `${v}ms`)} - onReset={() => onReset("--sf-theme-transition-duration")} - /> + + {#if showThemeTransition} + onSet("--sf-theme-transition-duration", `${v}ms`)} + onReset={() => onReset("--sf-theme-transition-duration")} + /> + {/if}
-
Duration overrides
-

- Set absolute ms values to override the fluid scale calculation. - {#if motionDisabled} (No effect while motion is disabled.){/if} -

- {#each DURATIONS as d (d.token)} - {@const computed = getDuration(d.token, d.base)} - onSet(d.token, `${v}ms`)} - onReset={() => onReset(d.token)} - /> - {/each} + + {#if showDurationOverrides} +

+ Set absolute ms values to override the fluid scale calculation. + {#if motionDisabled} (No effect while motion is disabled.){/if} +

+ {#each DURATIONS as d (d.token)} + {@const computed = getDuration(d.token, d.base)} + onSet(d.token, `${v}ms`)} + onReset={() => onReset(d.token)} + /> + {/each} + {/if}
-
Stagger base
-

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

-
-
- Stagger base - {#if STAGGER_TOKENS.some(t => t in overrides)} - - {/if} + + {#if showStaggerBase} +

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

+
+
+ Stagger base + {#if STAGGER_TOKENS.some(t => t in overrides)} + + {/if} +
+ t in overrides)} + onChange={(v) => setStaggerBase(v)} + onReset={resetStagger} + />
- t in overrides)} - onChange={(v) => setStaggerBase(v)} - onReset={resetStagger} - /> -
- -
- {#each [1,2,3,4,5] as n (n)} - {@const delayMs = Math.round(staggerBase() * n)} -
- –{n} -
-
+ +
+ {#each [1,2,3,4,5] as n (n)} + {@const delayMs = Math.round(staggerBase() * n)} +
+ –{n} +
+
+
+ {delayMs}ms
- {delayMs}ms -
- {/each} -
+ {/each} +
+ {/if}
-
Easing curves
-

- Customize the named easing tokens used throughout the design system. -

-
- {#each EASINGS as e (e.token)} - {@const isOverridden = e.token in overrides} -
-
- {e.label} - {#if isOverridden} - - {/if} + + {#if showEasingCurves} +

+ Customize the named easing tokens used throughout the design system. +

+
+ {#each EASINGS as e (e.token)} + {@const isOverridden = e.token in overrides} +
+
+ {e.label} + {#if isOverridden} + + {/if} +
+ + + +
{e.token.replace("--sf-ease-", "")}
- - - -
{e.token.replace("--sf-ease-", "")}
-
- {/each} -
+ {/each} +
+ {/if}
-
Animation demo
-
-
-
+ + {#if showAnimationDemo} +
+
+
+
+
- -
+ {/if}
-
Duration preview
-
- {#each DURATIONS as d (d.label)} - {@const actual = getDuration(d.token, d.base)} -
- {d.label} -
-
+ + {#if showDurationPreview} +
+ {#each DURATIONS as d (d.label)} + {@const actual = getDuration(d.token, d.base)} +
+ {d.label} +
+
+
+ {actual}ms
- {actual}ms -
- {/each} -
+ {/each} +
+ {/if}
diff --git a/configurator/src/components/panels/ShadowsPanel.svelte b/configurator/src/components/panels/ShadowsPanel.svelte index 4e960c6c..a1a82ce8 100644 --- a/configurator/src/components/panels/ShadowsPanel.svelte +++ b/configurator/src/components/panels/ShadowsPanel.svelte @@ -20,6 +20,11 @@ let shadowColor = $derived(overrides["--sf-shadow-color"] ?? ""); let shadowGlowColor = $derived(overrides["--sf-shadow-glow-color"] ?? ""); let glowDisabled = $derived(overrides["--sf-shadow-glow"] === "none"); + + let showShadowAppearance = $state(true); + let showShadowColor = $state(true); + let showGlow = $state(true); + let showElevationPreview = $state(true);
@@ -36,112 +41,142 @@
-
Shadow appearance
- -
- {#each knobs as k (k.name)} - val === null ? onReset(name) : onSet(name, val)} - /> - {/each} -
- - onSet("--sf-shadow-lightness", String(v))} - onReset={() => onReset("--sf-shadow-lightness")} - /> + + {#if showShadowAppearance} +
+ {#each knobs as k (k.name)} + val === null ? onReset(name) : onSet(name, val)} + /> + {/each} +
+ + onSet("--sf-shadow-lightness", String(v))} + onReset={() => onReset("--sf-shadow-lightness")} + /> + {/if}
-
Shadow color
-
-
Shadow color
-
- onSet("--sf-shadow-color", (e.target as HTMLInputElement).value)} - class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" - /> - {shadowColor || "auto (neutral hue)"} - {#if "--sf-shadow-color" in overrides} - - {/if} -
-

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

-
-
- -
- - -
-
Glow
- - -
-
-
Shadow glow
-
Ambient glow on elevated surfaces
-
- -
- - {#if !glowDisabled} + + {#if showShadowColor}
-
Glow color
+
Shadow color
onSet("--sf-shadow-glow-color", (e.target as HTMLInputElement).value)} + value={shadowColor || "#1a1a2e"} + oninput={(e) => onSet("--sf-shadow-color", (e.target as HTMLInputElement).value)} class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" /> - {shadowGlowColor || "default (primary color)"} - {#if "--sf-shadow-glow-color" in overrides} - + {shadowColor || "auto (neutral hue)"} + {#if "--sf-shadow-color" in overrides} + {/if}
-

Which brand color radiates the glow. Defaults to primary.

+

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

{/if}
- -
-
Elevation preview
-
- {#each SHADOW_STEPS as step, i (step)} -
-
- {step} + +
+ + {#if showGlow} + +
+
+
Shadow glow
+
Ambient glow on elevated surfaces
- {/each} -
+ +
+ + {#if !glowDisabled} +
+
Glow color
+
+ onSet("--sf-shadow-glow-color", (e.target as HTMLInputElement).value)} + class="w-8 h-8 rounded border border-white/10 bg-transparent cursor-pointer" + /> + {shadowGlowColor || "default (primary color)"} + {#if "--sf-shadow-glow-color" in overrides} + + {/if} +
+

Which brand color radiates the glow. Defaults to primary.

+
+ {/if} + {/if} + + +
+ + +
+ + {#if showElevationPreview} +
+ {#each SHADOW_STEPS as step, i (step)} +
+
+ {step} +
+ {/each} +
+ {/if}
diff --git a/configurator/src/components/panels/SpacingPanel.svelte b/configurator/src/components/panels/SpacingPanel.svelte index 1abad417..45d0caaa 100644 --- a/configurator/src/components/panels/SpacingPanel.svelte +++ b/configurator/src/components/panels/SpacingPanel.svelte @@ -57,131 +57,168 @@ v === null ? !(k in overrides) : overrides[k] === v ) )); + + let showLayoutGap = $state(true); + let showDensityPresets = $state(true); + let showModularScale = $state(true); + let showSpacePreview = $state(true);
-
Layout gap
-

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

- onSet("--sf-gap", `${v}rem`)} - onReset={() => onReset("--sf-gap")} - /> - onSet("--sf-content-gap", `${v}rem`)} - onReset={() => onReset("--sf-content-gap")} - /> - onSet("--sf-gutter", `${v}rem`)} - onReset={() => onReset("--sf-gutter")} - /> + + {#if showLayoutGap} +

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

+ onSet("--sf-gap", `${v}rem`)} + onReset={() => onReset("--sf-gap")} + /> + onSet("--sf-content-gap", `${v}rem`)} + onReset={() => onReset("--sf-content-gap")} + /> + onSet("--sf-gutter", `${v}rem`)} + onReset={() => onReset("--sf-gutter")} + /> + {/if}
-
Density presets
-
- {#each DENSITY_PRESETS as d (d.label)} - - {/each} -
+ + {#if showDensityPresets} +
+ {#each DENSITY_PRESETS as d (d.label)} + + {/each} +
+ {/if}
-
Modular scale (Mobile → Desktop)
-

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

- - { onReset("--sf-fluid-min-vw"); onReset("--sf-fluid-max-vw"); }} - onMinChange={(v) => onSet("--sf-fluid-min-vw", String(v))} - onMaxChange={(v) => onSet("--sf-fluid-max-vw", String(v))} - /> - - { onReset("--sf-space-base-min"); onReset("--sf-space-base-max"); }} - onMinChange={(v) => onSet("--sf-space-base-min", String(v))} - onMaxChange={(v) => onSet("--sf-space-base-max", String(v))} - ratioPresets={RATIO_PRESETS} - activeRatioValue={activeRatio?.value} - ratioMin={ratioMin} ratioMax={ratioMax} - ratioMin_bound={1.1} ratioMax_bound={1.8} - onRatioPreset={(v) => onBulkChange({ "--sf-space-ratio-min": String(v), "--sf-space-ratio-max": String(v) })} - onRatioMinChange={(v) => onSet("--sf-space-ratio-min", String(v))} - onRatioMaxChange={(v) => onSet("--sf-space-ratio-max", String(v))} - /> - -
- {#each knobs as k (k.name)} - val === null ? onReset(name) : onSet(name, val)} - /> - {/each} -
+ + {#if showModularScale} +

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

+ + { onReset("--sf-fluid-min-vw"); onReset("--sf-fluid-max-vw"); }} + onMinChange={(v) => onSet("--sf-fluid-min-vw", String(v))} + onMaxChange={(v) => onSet("--sf-fluid-max-vw", String(v))} + /> + + { onReset("--sf-space-base-min"); onReset("--sf-space-base-max"); }} + onMinChange={(v) => onSet("--sf-space-base-min", String(v))} + onMaxChange={(v) => onSet("--sf-space-base-max", String(v))} + ratioPresets={RATIO_PRESETS} + activeRatioValue={activeRatio?.value} + ratioMin={ratioMin} ratioMax={ratioMax} + ratioMin_bound={1.1} ratioMax_bound={1.8} + onRatioPreset={(v) => onBulkChange({ "--sf-space-ratio-min": String(v), "--sf-space-ratio-max": String(v) })} + onRatioMinChange={(v) => onSet("--sf-space-ratio-min", String(v))} + onRatioMaxChange={(v) => onSet("--sf-space-ratio-max", String(v))} + /> + +
+ {#each knobs as k (k.name)} + val === null ? onReset(name) : onSet(name, val)} + /> + {/each} +
+ {/if}
-
Space scale preview
-
- {#each SPACE_STEPS as step, i (step)} - {@const midBase = (baseMin + baseMax) / 2} - {@const ratio = (ratioMin + ratioMax) / 2} - {@const offset = i - 4} - {@const rawRem = offset >= 0 ? midBase * Math.pow(ratio, offset) : midBase / Math.pow(ratio, -offset)} - {@const scaled = rawRem * spaceScale} - {@const barWidth = Math.min(scaled * 28, 240)} -
- {step} -
- {scaled.toFixed(2)}rem -
- {/each} -
+ + {#if showSpacePreview} +
+ {#each SPACE_STEPS as step, i (step)} + {@const midBase = (baseMin + baseMax) / 2} + {@const ratio = (ratioMin + ratioMax) / 2} + {@const offset = i - 4} + {@const rawRem = offset >= 0 ? midBase * Math.pow(ratio, offset) : midBase / Math.pow(ratio, -offset)} + {@const scaled = rawRem * spaceScale} + {@const barWidth = Math.min(scaled * 28, 240)} +
+ {step} +
+ {scaled.toFixed(2)}rem +
+ {/each} +
+ {/if}
diff --git a/configurator/src/components/panels/TypographyPanel.svelte b/configurator/src/components/panels/TypographyPanel.svelte index 908220ef..b2cb97f6 100644 --- a/configurator/src/components/panels/TypographyPanel.svelte +++ b/configurator/src/components/panels/TypographyPanel.svelte @@ -159,6 +159,13 @@ (p) => Math.abs(p.value - ratioMin) < 0.0015 && Math.abs(p.value - ratioMax) < 0.0015 )); + let showFontFamilies = $state(true); + let showPerType = $state(true); + let showBodyText = $state(true); + let showDisplayType = $state(true); + let showModularScale = $state(true); + let showScalePreview = $state(true); + let currentBodyFont = $derived(overrides["--sf-font-body"] ?? ""); let currentHeadingFont = $derived(overrides["--sf-font-heading"] ?? ""); let currentMonoFont = $derived(overrides["--sf-font-mono"] ?? "ui-monospace, monospace"); @@ -237,8 +244,14 @@
-
Font families
- + + {#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 }, @@ -307,13 +320,21 @@ Load & apply to {customFontTarget}
+ {/if}
-
Per-type styles
+ + {#if showPerType}
@@ -422,13 +443,21 @@ >reset {activeLevel.label} {/if}
+ {/if}
-
Body text
+ + {#if showBodyText} {#each [ @@ -510,13 +539,21 @@
{/each} + {/if}
-
Display type
+ + {#if showDisplayType}
{/each} + {/if}
-
Modular scale (Mobile → Desktop)
+ + {#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. @@ -661,13 +706,21 @@ {/each} + {/if}

-
Scale preview
+ + {#if showScalePreview}
{#each [...TEXT_STEPS].reverse() as { label, factor } (label)} {@const midBase = (baseMin + baseMax) / 2} @@ -684,5 +737,6 @@
{/each} + {/if}
diff --git a/configurator/src/components/panels/WcagPanel.svelte b/configurator/src/components/panels/WcagPanel.svelte index 6f146410..9c82214d 100644 --- a/configurator/src/components/panels/WcagPanel.svelte +++ b/configurator/src/components/panels/WcagPanel.svelte @@ -150,6 +150,9 @@ suggestion = null; } }); + + let showPairChecker = $state(true); + let showMatrix = $state(true);
@@ -162,6 +165,14 @@
+ + {#if showPairChecker}
Foreground
@@ -228,13 +239,21 @@

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

{/if}
+ {/if}
-
Contrast matrix
+ + {#if showMatrix}

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

@@ -270,5 +289,6 @@
+ {/if}
From 1c4cf5c8822b41e0321fe6146a66eca6efee3fb9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Jun 2026 21:16:44 +0000 Subject: [PATCH 3/4] fix(configurator): aria-expanded, session hook, and review bug fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add .claude/hooks/session-start.sh: runs npm install + npm run prepare on every remote session start, wiring .githooks so the commit-msg hook runs commitlint before each commit — prevents recurring lint failures - Update .gitignore: stop ignoring all of .claude/; ignore only lock/cache files so settings.json and hooks/ are tracked in the repo - Add aria-expanded to all 67 collapsible section toggle buttons across all panels so screen readers can announce the open/closed state - Fix MotionPanel theme transition slider: bind to themeTransition derived value instead of Math.round(300 * scale) (wrong when token is overridden) - Fix EffectsPanel scrollbar track color input: use #000000 as picker fallback instead of "transparent" (browsers reject transparent in color inputs) - Fix WcagPanel $effect: early-exit when pair checker is collapsed to skip the lightness-search loop; move showPairChecker declaration before the effect that references it Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_016G1SLZmmZy4d71nYpotZbX --- .claude/hooks/session-start.sh | 14 ++++++++++++++ .claude/settings.json | 14 ++++++++++++++ .gitignore | 4 +++- .../src/components/panels/AllTokensTab.svelte | 1 + .../src/components/panels/BordersPanel.svelte | 9 +++++++++ .../src/components/panels/ColorsPanel.svelte | 7 +++++++ .../src/components/panels/EffectsPanel.svelte | 8 +++++++- .../src/components/panels/LayoutPanel.svelte | 8 ++++++++ .../src/components/panels/MacrosPanel.svelte | 6 ++++++ .../src/components/panels/MiscPanel.svelte | 7 +++++++ .../src/components/panels/MotionPanel.svelte | 9 ++++++++- .../src/components/panels/ShadowsPanel.svelte | 4 ++++ .../src/components/panels/SpacingPanel.svelte | 4 ++++ .../src/components/panels/TypographyPanel.svelte | 6 ++++++ .../src/components/panels/WcagPanel.svelte | 8 ++++++-- 15 files changed, 104 insertions(+), 5 deletions(-) create mode 100755 .claude/hooks/session-start.sh create mode 100644 .claude/settings.json diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 00000000..50647121 --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -euo pipefail + +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + exit 0 +fi + +cd "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}" + +# Install root dependencies (includes commitlint, stylelint, etc.) +npm install + +# Wire up .githooks so the commit-msg hook runs commitlint pre-commit +npm run prepare diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..e06b0338 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh" + } + ] + } + ] + } +} diff --git a/.gitignore b/.gitignore index 59f12f01..bb3b0b75 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,9 @@ playwright-report/ *.tgz .DS_Store .agents/ -.claude/ +.claude/*.lock +.claude/todos/ +.claude/cache/ # Semantic review artifacts (sub-agent output, not source) semantic-review/ diff --git a/configurator/src/components/panels/AllTokensTab.svelte b/configurator/src/components/panels/AllTokensTab.svelte index 6ea99bac..f78b1c24 100644 --- a/configurator/src/components/panels/AllTokensTab.svelte +++ b/configurator/src/components/panels/AllTokensTab.svelte @@ -68,6 +68,7 @@ {#if showThemeTransition} onSet("--sf-theme-transition-duration", `${v}ms`)} @@ -167,6 +169,7 @@