From 6e0894a6b970bf933c020c9cd0301483ad59f3a4 Mon Sep 17 00:00:00 2001 From: Jack Granatowski Date: Wed, 24 Jun 2026 23:12:38 +0200 Subject: [PATCH 01/12] feat(configurator): redesign visual editor panels --- .../src/components/CategoryHeader.svelte | 33 ++ .../src/components/ControlPreview.svelte | 49 +++ .../src/components/ControlSection.svelte | 26 ++ .../src/components/DomainPanel.svelte | 111 +++---- .../src/components/FriendlyControl.svelte | 44 +++ configurator/src/components/Header.svelte | 290 ++++-------------- .../src/components/HeadingEditor.svelte | 2 +- .../src/components/RadiusEditor.svelte | 2 +- .../src/components/SmartSettings.svelte | 5 +- .../src/components/editors/ColorStudio.svelte | 16 + .../components/editors/EffectsStudio.svelte | 3 + .../components/editors/LayoutStudio.svelte | 3 + .../components/editors/MotionStudio.svelte | 3 + .../components/editors/ShadowStudio.svelte | 3 + .../src/components/editors/ShapeStudio.svelte | 3 + .../components/editors/SpacingStudio.svelte | 3 + .../src/components/editors/StudioFrame.svelte | 24 ++ .../editors/TypographyStudio.svelte | 24 ++ configurator/src/lib/controlSchema.js | 37 +++ 19 files changed, 370 insertions(+), 311 deletions(-) create mode 100644 configurator/src/components/CategoryHeader.svelte create mode 100644 configurator/src/components/ControlPreview.svelte create mode 100644 configurator/src/components/ControlSection.svelte create mode 100644 configurator/src/components/FriendlyControl.svelte create mode 100644 configurator/src/components/editors/ColorStudio.svelte create mode 100644 configurator/src/components/editors/EffectsStudio.svelte create mode 100644 configurator/src/components/editors/LayoutStudio.svelte create mode 100644 configurator/src/components/editors/MotionStudio.svelte create mode 100644 configurator/src/components/editors/ShadowStudio.svelte create mode 100644 configurator/src/components/editors/ShapeStudio.svelte create mode 100644 configurator/src/components/editors/SpacingStudio.svelte create mode 100644 configurator/src/components/editors/StudioFrame.svelte create mode 100644 configurator/src/components/editors/TypographyStudio.svelte create mode 100644 configurator/src/lib/controlSchema.js diff --git a/configurator/src/components/CategoryHeader.svelte b/configurator/src/components/CategoryHeader.svelte new file mode 100644 index 00000000..1020863b --- /dev/null +++ b/configurator/src/components/CategoryHeader.svelte @@ -0,0 +1,33 @@ + + +
+
+
+

Visual framework editor

+

{domain.label}

+

{domain.blurb}

+
+
+ {modifiedCount} overrides + {tokenCount} variables + {#if modifiedCount > 0 && onreset} + + {/if} +
+
+ + diff --git a/configurator/src/components/ControlPreview.svelte b/configurator/src/components/ControlPreview.svelte new file mode 100644 index 00000000..5b8b8f50 --- /dev/null +++ b/configurator/src/components/ControlPreview.svelte @@ -0,0 +1,49 @@ + + +
+ {#if type === 'color'} +
+ AaButton +
+ {:else if type === 'font'} +

Ag — The quick brown fox

+ {:else if type === 'radius'} +
+ {:else if type === 'shadow'} +
+ {:else if type === 'motion'} +
motion
+ {:else if type === 'line-height'} +

Readable rhythm
across multiple
text lines.

+ {:else if type === 'wrap'} +

A heading wraps cleanly in a narrow preview.

+ {:else if type === 'opacity'} +
+ {:else} +
+ {/if} +
+ + diff --git a/configurator/src/components/ControlSection.svelte b/configurator/src/components/ControlSection.svelte new file mode 100644 index 00000000..c3f18a19 --- /dev/null +++ b/configurator/src/components/ControlSection.svelte @@ -0,0 +1,26 @@ + + +
+ + + {title} + {#if hint}{hint}{/if} + {#if modifiedCount}{modifiedCount} changed{/if} + +
{@render children?.()}
+
+ + diff --git a/configurator/src/components/DomainPanel.svelte b/configurator/src/components/DomainPanel.svelte index a45dd94b..a142178a 100644 --- a/configurator/src/components/DomainPanel.svelte +++ b/configurator/src/components/DomainPanel.svelte @@ -41,7 +41,17 @@ import HeadingEditor from './HeadingEditor.svelte'; import RadiusEditor from './RadiusEditor.svelte'; import ContainerBars from './ContainerBars.svelte'; - import Icon from './Icon.svelte'; + import CategoryHeader from './CategoryHeader.svelte'; + import ControlSection from './ControlSection.svelte'; + import FriendlyControl from './FriendlyControl.svelte'; + import TypographyStudio from './editors/TypographyStudio.svelte'; + import ColorStudio from './editors/ColorStudio.svelte'; + import SpacingStudio from './editors/SpacingStudio.svelte'; + import LayoutStudio from './editors/LayoutStudio.svelte'; + import ShapeStudio from './editors/ShapeStudio.svelte'; + import ShadowStudio from './editors/ShadowStudio.svelte'; + import MotionStudio from './editors/MotionStudio.svelte'; + import EffectsStudio from './editors/EffectsStudio.svelte'; /** @type {{ domain: { id:string, label:string, icon:string, blurb:string, intro?:string, scaleIntro?:string, essentials?:string[], basicGenerators?:string[], brandColors?:boolean, docsPath?:string } }} */ let { domain } = $props(); @@ -200,33 +210,33 @@ {/snippet}
-
-
- -
-

{domain.label}

-

{domain.blurb}

-
-
- -
- {#if modifiedHere.length} - - {/if} -
-
-
+ {#if searching} {@render filters()} {@render catalogue()} {:else} + + {#if domain.id === 'typography'} + + {:else if domain.id === 'colors'} + + {:else if domain.id === 'spacing'} + + {:else if domain.id === 'layout'} + + {:else if domain.id === 'borders'} + + {:else if domain.id === 'shadows'} + + {:else if domain.id === 'motion'} + + {:else if domain.id === 'effects'} + + {/if} + {#each basicGroups as group (group.title)} -
- {@render expandSummary(group.title, group.controls.length)} + overrides[c.token] != null).length}>
{#each group.controls as c (c.token)} - + {/each}
-
+ {/each} {:else if essentials.length} -
- {@render expandSummary('Essentials', essentials.length, 'Curated for most projects.')} + overrides[t.name] != null).length}>
{#each essentials as token (token.name)} - + {/each}
-
+ {/if} @@ -405,44 +413,6 @@ height: 100%; min-height: 0; } - .panel__head { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - padding: 14px 20px 12px; - border-bottom: 1px solid var(--cfg-border); - background: var(--cfg-surface); - flex-wrap: wrap; - } - .panel__title-wrap { - display: flex; - align-items: center; - gap: 10px; - min-width: 0; - flex: 1 1 auto; - } - .panel__icon { - display: inline-grid; - place-items: center; - width: 32px; - height: 32px; - border-radius: var(--cfg-radius); - background: linear-gradient(135deg, rgba(79, 140, 255, 0.18), rgba(120, 80, 220, 0.18)); - border: 1px solid var(--cfg-border-strong); - font-size: 16px; - flex-shrink: 0; - } - .panel__title { margin: 0; font-size: 16px; font-weight: 600; } - .panel__blurb { margin: 2px 0 0; color: var(--cfg-text-muted); font-size: 12px; max-width: 70ch; } - - .panel__actions { - display: flex; - align-items: center; - gap: 10px; - flex-wrap: wrap; - flex-shrink: 0; - } .panel__usage-seg :global(.cfg-seg__btn) { padding: 4px 10px; font-size: 11.5px; } .panel__check { display: flex; @@ -645,13 +615,6 @@ @media (max-width: 600px) { .panel__body { padding: 12px 10px 60px; gap: 12px; } - .panel__head { padding: 10px 12px 8px; gap: 8px; } - .panel__title { font-size: 14px; } - .panel__blurb { display: none; } - .panel__actions { - flex-basis: 100%; - gap: 8px; - } .panel__usage-seg :global(.cfg-seg__btn) { padding: 4px 8px; font-size: 10.5px; diff --git a/configurator/src/components/FriendlyControl.svelte b/configurator/src/components/FriendlyControl.svelte new file mode 100644 index 00000000..41d026d4 --- /dev/null +++ b/configurator/src/components/FriendlyControl.svelte @@ -0,0 +1,44 @@ + + +
+
+
+ {title} + {#if modified}modified{/if} +
+ {#if desc}

{desc}

{/if} + {#if showToken || rawOpen}{token.name}{/if} +
+
+ +
+ + +
+
+ + diff --git a/configurator/src/components/Header.svelte b/configurator/src/components/Header.svelte index 4225a833..c6247452 100644 --- a/configurator/src/components/Header.svelte +++ b/configurator/src/components/Header.svelte @@ -1,26 +1,14 @@
-
+
{#each TABS as tab (tab.id)} {@const modified = tabHasOverride(tab.id)}
{:else}
{#each section.controls.filter((c) => exists(c.token)) as c (c.token)} - + {/each}
{/if} diff --git a/configurator/src/components/editors/ColorStudio.svelte b/configurator/src/components/editors/ColorStudio.svelte new file mode 100644 index 00000000..086b41b9 --- /dev/null +++ b/configurator/src/components/editors/ColorStudio.svelte @@ -0,0 +1,16 @@ + + +
+ {#each colors as c (c.key)}
{c.label}Aa
{/each} +
+
Surface card

Text, link, border and subtle background preview.

Status message
+
+ diff --git a/configurator/src/components/editors/EffectsStudio.svelte b/configurator/src/components/editors/EffectsStudio.svelte new file mode 100644 index 00000000..9087c225 --- /dev/null +++ b/configurator/src/components/editors/EffectsStudio.svelte @@ -0,0 +1,3 @@ + +

Readable media card

Scrim, blur and muted states preview.

+ diff --git a/configurator/src/components/editors/LayoutStudio.svelte b/configurator/src/components/editors/LayoutStudio.svelte new file mode 100644 index 00000000..4a0a10e4 --- /dev/null +++ b/configurator/src/components/editors/LayoutStudio.svelte @@ -0,0 +1,3 @@ + +
viewportcontainergrid columns
+ diff --git a/configurator/src/components/editors/MotionStudio.svelte b/configurator/src/components/editors/MotionStudio.svelte new file mode 100644 index 00000000..288d22e2 --- /dev/null +++ b/configurator/src/components/editors/MotionStudio.svelte @@ -0,0 +1,3 @@ + +
fastnormalslow
+ diff --git a/configurator/src/components/editors/ShadowStudio.svelte b/configurator/src/components/editors/ShadowStudio.svelte new file mode 100644 index 00000000..02e410d4 --- /dev/null +++ b/configurator/src/components/editors/ShadowStudio.svelte @@ -0,0 +1,3 @@ + +
{#each levels as l}
{l}
{/each}
+ diff --git a/configurator/src/components/editors/ShapeStudio.svelte b/configurator/src/components/editors/ShapeStudio.svelte new file mode 100644 index 00000000..82c8e331 --- /dev/null +++ b/configurator/src/components/editors/ShapeStudio.svelte @@ -0,0 +1,3 @@ + +
{#each names as n}
{n}
{/each}
+ diff --git a/configurator/src/components/editors/SpacingStudio.svelte b/configurator/src/components/editors/SpacingStudio.svelte new file mode 100644 index 00000000..5eb01aa7 --- /dev/null +++ b/configurator/src/components/editors/SpacingStudio.svelte @@ -0,0 +1,3 @@ + +
{#each steps as s,i}
{s}
{/each}
+ diff --git a/configurator/src/components/editors/StudioFrame.svelte b/configurator/src/components/editors/StudioFrame.svelte new file mode 100644 index 00000000..477d11a7 --- /dev/null +++ b/configurator/src/components/editors/StudioFrame.svelte @@ -0,0 +1,24 @@ + + +
+
+

{eyebrow}

+

{title}

+ {#if description}{description}{/if} +
+
{@render children?.()}
+
+ + diff --git a/configurator/src/components/editors/TypographyStudio.svelte b/configurator/src/components/editors/TypographyStudio.svelte new file mode 100644 index 00000000..fbb14889 --- /dev/null +++ b/configurator/src/components/editors/TypographyStudio.svelte @@ -0,0 +1,24 @@ + + +
{#each tabs as t}{/each}
+
+

The quick brown fox

+

Jumps over the lazy dog

+

Typography at every scale

+

Fluid, readable, precise

+ Fine-tune each heading level + Pixel-perfect control +

Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.

+
+
+ diff --git a/configurator/src/lib/controlSchema.js b/configurator/src/lib/controlSchema.js new file mode 100644 index 00000000..d02e972f --- /dev/null +++ b/configurator/src/lib/controlSchema.js @@ -0,0 +1,37 @@ +import { parseLength } from './length.js'; + +const EXPLICIT = { + '--sf-heading-text-wrap': { control: 'select', preview: 'wrap', options: ['balance', 'pretty', 'wrap', 'normal'], label: 'Heading wrap' }, + '--sf-scrim-direction': { control: 'select', preview: 'gradient', options: ['to top', 'to bottom', 'to right', 'to left', '135deg'], label: 'Scrim direction' }, +}; + +function titleFromName(name = '') { + return name.replace(/^--sf-/, '').replace(/-/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase()); +} + +export function controlForToken(token, override = {}) { + const name = token?.name ?? ''; + const explicit = EXPLICIT[name] ?? {}; + const value = token?.value ?? ''; + let schema = { label: titleFromName(name), description: token?.note || token?.description || '', control: 'text', preview: 'text' }; + + if (/color|palette|hsl|oklch|hex|focus|link|caret|border/.test(name) && /(#[0-9a-f]|oklch|oklab|hsl|rgb|var\(--sf-color)/i.test(value + name)) { + schema = { ...schema, control: 'color', preview: 'color' }; + } else if (/font/.test(name)) { + schema = { ...schema, control: 'font', preview: 'font' }; + } else if (/duration|motion-scale/.test(name)) { + schema = { ...schema, control: 'length', preview: 'motion' }; + } else if (/ease/.test(name)) { + schema = { ...schema, control: 'select', preview: 'motion', options: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'cubic-bezier(.2,.8,.2,1)'] }; + } else if (/radius/.test(name)) { + schema = { ...schema, control: 'length', preview: 'radius' }; + } else if (/shadow/.test(name)) { + schema = { ...schema, control: 'text', preview: 'shadow' }; + } else if (/space|gap|gutter|pad|width|height|size|container|leading|tracking|border-width|divider/.test(name) || parseLength(value)) { + schema = { ...schema, control: 'length', preview: /leading/.test(name) ? 'line-height' : 'spacing' }; + } else if (/scale|opacity|threshold|bias|weight|z-/.test(name) || /^-?\d*\.?\d+$/.test(value)) { + schema = { ...schema, control: 'number', preview: /opacity/.test(name) ? 'opacity' : 'scale' }; + } + + return { ...schema, ...explicit, ...override }; +} From 82f4a5e80c39cd4d03f302a025a5a52db6a28da8 Mon Sep 17 00:00:00 2001 From: Jack Granatowski Date: Wed, 24 Jun 2026 23:58:06 +0200 Subject: [PATCH 02/12] feat(configurator): deepen visual studios --- .../src/components/DomainPanel.svelte | 225 +----------------- configurator/src/components/Header.svelte | 2 +- .../src/components/editors/ColorStudio.svelte | 22 +- .../components/editors/EffectsStudio.svelte | 12 +- .../components/editors/LayoutStudio.svelte | 12 +- .../components/editors/MotionStudio.svelte | 12 +- .../components/editors/ShadowStudio.svelte | 13 +- .../src/components/editors/ShapeStudio.svelte | 13 +- .../components/editors/SpacingStudio.svelte | 13 +- .../editors/TypographyStudio.svelte | 49 +++- 10 files changed, 117 insertions(+), 256 deletions(-) diff --git a/configurator/src/components/DomainPanel.svelte b/configurator/src/components/DomainPanel.svelte index a142178a..fd65aea5 100644 --- a/configurator/src/components/DomainPanel.svelte +++ b/configurator/src/components/DomainPanel.svelte @@ -26,21 +26,14 @@ import { domainOf, KNOBS_BY_DOMAIN, DOCS_BASE_URL } from '../lib/domains.js'; import { smartSettingsFor } from '../lib/domainSettings.js'; import { BASIC_BY_DOMAIN } from '../lib/basics.js'; - import { BRAND_COLOR_KEYS } from '../lib/brandColors.js'; import { ui, overrides, patchOverrides } from '../lib/store.svelte.js'; import { STYLE_PRESETS_BY_DOMAIN } from '../lib/stylePresets.js'; import TokenGroup from './TokenGroup.svelte'; import TokenRow from './TokenRow.svelte'; - import BrandColorRow from './BrandColorRow.svelte'; import ScaleGenerator from './ScaleGenerator.svelte'; import QuickKnobs from './QuickKnobs.svelte'; import StylePresetRow from './StylePresetRow.svelte'; - import ColorAssignments from './ColorAssignments.svelte'; - import ShadeRamp from './ShadeRamp.svelte'; import SmartSettings from './SmartSettings.svelte'; - import HeadingEditor from './HeadingEditor.svelte'; - import RadiusEditor from './RadiusEditor.svelte'; - import ContainerBars from './ContainerBars.svelte'; import CategoryHeader from './CategoryHeader.svelte'; import ControlSection from './ControlSection.svelte'; import FriendlyControl from './FriendlyControl.svelte'; @@ -93,6 +86,8 @@ const generators = $derived(domain.basicGenerators ?? []); const knobs = $derived(KNOBS_BY_DOMAIN[domain.id] ?? []); const smartSections = $derived(smartSettingsFor(domain.id)); + const redesignedDomains = new Set(['colors', 'typography', 'spacing', 'layout', 'borders', 'shadows', 'motion', 'effects']); + const usesVisualStudio = $derived(redesignedDomains.has(domain.id)); // Domains with a scale generator (typography, spacing): preview goes BELOW // the generator so the specimen updates right next to the controls. @@ -110,19 +105,6 @@ let showAll = $state(false); - // Colors panel accordion states. - let showSecondary = $state(false); - let showStatus = $state(false); - let showColorRoles = $state(true); - let showShadeRamp = $state(false); - - const BRAND_PRIMARY = BRAND_COLOR_KEYS.filter((c) => ['base', 'neutral', 'primary'].includes(c.key)); - const BRAND_SECONDARY = BRAND_COLOR_KEYS.filter((c) => ['secondary', 'tertiary', 'action'].includes(c.key)); - const BRAND_STATUS = BRAND_COLOR_KEYS.filter((c) => c.group === 'status'); - - const hasBrandOverride = (key) => - overrides[`--sf-color-${key}-source-light`] != null || - overrides[`--sf-color-${key}-source-dark`] != null; const modifiedHere = $derived( domainTokens.filter((t) => overrides[t.name] != null) @@ -134,20 +116,6 @@ } -{#snippet expandSummary(title, count, hint = '')} - - - {title} - {#if typeof count === 'number'} - {count} - {:else if count} - {count} - {/if} - {#if hint} - {hint} - {/if} - -{/snippet} {#snippet filters()}
@@ -237,30 +205,14 @@ {/if} - + - {#if domain.brandColors} -
- {@render expandSummary('Semantic roles', 'How your brand colors surface')} - -
- {#if knobs.length} - - {/if} - - {:else if hasGenerators} + {#if !usesVisualStudio && hasGenerators} {#each generators as g (g)} {/each} - {#if knobs.length} - - {/if} - - {:else if knobs.length} + {/if} + {#if knobs.length} {/if} @@ -281,78 +233,8 @@ {/if} - - {#if domain.brandColors} - -
- {@render expandSummary('Core brand colors', BRAND_PRIMARY.length, 'Light values — dark mode is auto-derived. Click the dark swatch to pin.')} -
- {#each BRAND_PRIMARY as { key, label } (key)} - - {/each} -
-
- - -
- - - Extended brand colors - {BRAND_SECONDARY.length} colors - {#if BRAND_SECONDARY.some((c) => hasBrandOverride(c.key))} - modified - {/if} - -
- {#each BRAND_SECONDARY as { key, label } (key)} - - {/each} -
-
- - -
- - - Status colors - {BRAND_STATUS.length} colors - {#if BRAND_STATUS.some((c) => hasBrandOverride(c.key))} - modified - {/if} - -
- {#each BRAND_STATUS as { key, label } (key)} - - {/each} -
-
- - -
- - - Shade ramp - 7-step scale per brand color - - -
- - {:else if domain.id === 'typography'} - - - - {:else if domain.id === 'borders'} - - - - {:else if domain.id === 'layout'} - - - - {:else if basicGroups.length} - + + {#if !usesVisualStudio && basicGroups.length} {#each basicGroups as group (group.title)} overrides[c.token] != null).length}>
@@ -362,8 +244,7 @@
{/each} - - {:else if essentials.length} + {:else if !usesVisualStudio && essentials.length} overrides[t.name] != null).length}>
{#each essentials as token (token.name)} @@ -434,38 +315,6 @@ min-height: 0; } - .panel__card { overflow: clip; } - /* The live-preview card that leads each category gets a faint accent edge. */ - .panel__card--lead { border-color: var(--cfg-border-strong); } - .panel__card--lead > summary { border-left: 3px solid var(--cfg-accent-strong); } - .panel__card-head { - display: flex; - align-items: baseline; - gap: 12px; - padding: 12px 16px; - border-bottom: 1px solid var(--cfg-border); - background: var(--cfg-surface-2); - flex-wrap: wrap; - } - .panel__card-title { - font-size: 12.5px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--cfg-text); - } - .panel__card-count { - font-family: var(--cfg-mono); - font-size: 11px; - color: var(--cfg-text-faint); - } - .panel__card-hint { - flex: 1 1 auto; - text-align: right; - font-size: 11.5px; - color: var(--cfg-text-faint); - } - .panel__intro { margin: 0; padding: 10px 14px; @@ -557,62 +406,6 @@ } .panel__docs a:hover { color: var(--cfg-accent); text-decoration: underline; } - /* Progressive disclosure for collapsible section cards (brand colors, basic groups, essentials). */ - .panel__expand { - border: 1px solid var(--cfg-border); - border-radius: var(--cfg-radius); - background: var(--cfg-surface); - overflow: clip; - } - .panel__expand-summary { - display: flex; - align-items: center; - gap: 10px; - padding: 11px 16px; - cursor: pointer; - list-style: none; - user-select: none; - } - .panel__expand-summary::-webkit-details-marker { display: none; } - .panel__expand-summary:hover { background: var(--cfg-surface-2); } - .panel__expand-chev { - font-size: 15px; - line-height: 1; - color: var(--cfg-text-faint); - transition: transform 0.14s; - } - /* Chevron rotation when any collapsible card is open */ - .panel__expand[open] .panel__expand-chev, - details.cfg-card[open] .panel__expand-chev { transform: rotate(90deg); } - .panel__expand-title { - font-size: 12.5px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--cfg-text); - } - .panel__expand-count { - font-family: var(--cfg-mono); - font-size: 11px; - color: var(--cfg-text-faint); - } - .panel__expand-badge { - font-size: 9.5px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--cfg-accent-strong); - border: 1px solid var(--cfg-accent-strong); - border-radius: 999px; - padding: 1px 7px; - line-height: 1.6; - } - /* When the disclosure card is open its summary gets a border */ - details.cfg-card[open] > .panel__card-head.panel__expand-summary, - details.panel__expand[open] > .panel__card-head.panel__expand-summary { - border-bottom: 1px solid var(--cfg-border); - } - @media (max-width: 600px) { .panel__body { padding: 12px 10px 60px; gap: 12px; } .panel__usage-seg :global(.cfg-seg__btn) { diff --git a/configurator/src/components/Header.svelte b/configurator/src/components/Header.svelte index c6247452..3e057089 100644 --- a/configurator/src/components/Header.svelte +++ b/configurator/src/components/Header.svelte @@ -49,7 +49,7 @@
- +
diff --git a/configurator/src/components/editors/ColorStudio.svelte b/configurator/src/components/editors/ColorStudio.svelte index 086b41b9..882edddd 100644 --- a/configurator/src/components/editors/ColorStudio.svelte +++ b/configurator/src/components/editors/ColorStudio.svelte @@ -1,16 +1,22 @@ - -
- {#each colors as c (c.key)}
{c.label}Aa
{/each} + + +
+
Surface card

Text, link, border and subtle background preview.

Status message
+
Main colors
{#each main as {key,label} (key)}{/each}
+
Semantic colors
{#each status as {key,label} (key)}{/each}
+
Semantic role preview
+
Generated shade ramp
-
Surface card

Text, link, border and subtle background preview.

Status message
diff --git a/configurator/src/components/editors/EffectsStudio.svelte b/configurator/src/components/editors/EffectsStudio.svelte index 9087c225..05228733 100644 --- a/configurator/src/components/editors/EffectsStudio.svelte +++ b/configurator/src/components/editors/EffectsStudio.svelte @@ -1,3 +1,9 @@ - -

Readable media card

Scrim, blur and muted states preview.

- + +

Readable media card

Scrim, blur and muted states preview.

{#each tokens as token (token.name)}{/each}
+ diff --git a/configurator/src/components/editors/LayoutStudio.svelte b/configurator/src/components/editors/LayoutStudio.svelte index 4a0a10e4..46daa95a 100644 --- a/configurator/src/components/editors/LayoutStudio.svelte +++ b/configurator/src/components/editors/LayoutStudio.svelte @@ -1,3 +1,9 @@ - -
viewportcontainergrid columns
- + +
viewportcontainergrid columns
{#each tokens as token (token.name)}{/each}
+ diff --git a/configurator/src/components/editors/MotionStudio.svelte b/configurator/src/components/editors/MotionStudio.svelte index 288d22e2..a28470fe 100644 --- a/configurator/src/components/editors/MotionStudio.svelte +++ b/configurator/src/components/editors/MotionStudio.svelte @@ -1,3 +1,9 @@ - -
fastnormalslow
- + +
fastnormalslow
{#each tokens as token (token.name)}{/each}
+ diff --git a/configurator/src/components/editors/ShadowStudio.svelte b/configurator/src/components/editors/ShadowStudio.svelte index 02e410d4..ee228158 100644 --- a/configurator/src/components/editors/ShadowStudio.svelte +++ b/configurator/src/components/editors/ShadowStudio.svelte @@ -1,3 +1,10 @@ - -
{#each levels as l}
{l}
{/each}
- + +
{#each levels as l}
{l}
{/each}
{#each tokens as token (token.name)}{/each}
+ diff --git a/configurator/src/components/editors/ShapeStudio.svelte b/configurator/src/components/editors/ShapeStudio.svelte index 82c8e331..69a20d3f 100644 --- a/configurator/src/components/editors/ShapeStudio.svelte +++ b/configurator/src/components/editors/ShapeStudio.svelte @@ -1,3 +1,10 @@ - -
{#each names as n}
{n}
{/each}
- + +
{#each samples as n}
{n}
{/each}
{#each tokens as token (token.name)}{/each}
+ diff --git a/configurator/src/components/editors/SpacingStudio.svelte b/configurator/src/components/editors/SpacingStudio.svelte index 5eb01aa7..cbfa20c6 100644 --- a/configurator/src/components/editors/SpacingStudio.svelte +++ b/configurator/src/components/editors/SpacingStudio.svelte @@ -1,3 +1,10 @@ - -
{#each steps as s,i}
{s}
{/each}
- + +
{#each steps as s,i}
{s}
{/each}
{#each tokens as token (token.name)}{/each}
+ diff --git a/configurator/src/components/editors/TypographyStudio.svelte b/configurator/src/components/editors/TypographyStudio.svelte index fbb14889..905e5ced 100644 --- a/configurator/src/components/editors/TypographyStudio.svelte +++ b/configurator/src/components/editors/TypographyStudio.svelte @@ -1,24 +1,47 @@ - -
{#each tabs as t}{/each}
-
-

The quick brown fox

-

Jumps over the lazy dog

-

Typography at every scale

-

Fluid, readable, precise

- Fine-tune each heading level - Pixel-perfect control -

Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.

+ + +
+
+ {#each PANELS as p}{/each} +
+
+

The quick brown fox

+

Jumps over the lazy dog

+

Typography at every scale

+

Fluid, readable, precise

+ Fine-tune each heading level + Pixel-perfect control +

Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.

+
+
+ {#each activeTokens as token (token.name)} + + {/each} +
From 04072eb03fb57c30a5de08ee51eb46185f0bcfb5 Mon Sep 17 00:00:00 2001 From: Jack Granatowski Date: Thu, 25 Jun 2026 00:18:54 +0200 Subject: [PATCH 03/12] feat(configurator): expand studio redesign controls --- .../src/components/ControlPreview.svelte | 14 ++++ .../src/components/FriendlyControl.svelte | 20 ++++- .../src/components/editors/ColorStudio.svelte | 36 ++++++++- .../components/editors/EffectsStudio.svelte | 28 +++++-- .../components/editors/LayoutStudio.svelte | 31 ++++++-- .../components/editors/MotionStudio.svelte | 30 ++++++-- .../components/editors/ShadowStudio.svelte | 30 ++++++-- .../src/components/editors/ShapeStudio.svelte | 31 ++++++-- .../components/editors/SpacingStudio.svelte | 35 +++++++-- .../components/editors/StudioControls.svelte | 24 ++++++ .../src/components/editors/StudioFrame.svelte | 18 +++-- .../editors/TypographyStudio.svelte | 69 +++++++++++------ configurator/src/lib/controlSchema.js | 37 ++++++--- configurator/src/lib/studioSchema.js | 76 +++++++++++++++++++ 14 files changed, 394 insertions(+), 85 deletions(-) create mode 100644 configurator/src/components/editors/StudioControls.svelte create mode 100644 configurator/src/lib/studioSchema.js diff --git a/configurator/src/components/ControlPreview.svelte b/configurator/src/components/ControlPreview.svelte index 5b8b8f50..fdcdf56a 100644 --- a/configurator/src/components/ControlPreview.svelte +++ b/configurator/src/components/ControlPreview.svelte @@ -25,6 +25,14 @@

A heading wraps cleanly in a narrow preview.

{:else if type === 'opacity'}
+ {:else if type === 'gradient'} +
+ {:else if type === 'border'} +
+ {:else if type === 'media'} +
+ {:else if type === 'scale'} +
{:else}
{/if} @@ -43,6 +51,12 @@ .ctrl-preview__lines, .ctrl-preview__wrap { margin: 0; font-size: 12px; max-inline-size: 190px; } .ctrl-preview__wrap { font-size: 18px; font-weight: 800; } .ctrl-preview__opacity span { display:block; block-size: 34px; border-radius: 8px; background: var(--cfg-accent-strong); } + .ctrl-preview__gradient { min-height: 38px; border-radius: 9px; background: linear-gradient(var(--scrim, to top), oklch(0 0 0 / .62), transparent), linear-gradient(135deg, var(--cfg-accent), var(--cfg-accent-soft)); } + .ctrl-preview__border { min-height: 36px; border: 3px solid var(--cfg-accent-strong); border-radius: 10px; background: var(--cfg-surface); } + .ctrl-preview__media { min-height: 38px; border-radius: 10px; overflow: hidden; background: var(--cfg-surface-3); } + .ctrl-preview__media span { display:block; inline-size:100%; block-size:42px; background: radial-gradient(circle at 30% 25%, var(--cfg-accent), transparent 28%), linear-gradient(135deg, var(--cfg-accent-soft), var(--cfg-surface-3)); } + .ctrl-preview__scale { min-height: 38px; display:grid; place-items:center; } + .ctrl-preview__scale span { inline-size:30px; block-size:30px; border-radius:8px; background:var(--cfg-accent-strong); } .ctrl-preview__spacing { min-height: 34px; display:flex; align-items:center; } .ctrl-preview__spacing span { display:block; block-size: 14px; max-inline-size:100%; min-inline-size:12px; border-radius:999px; background: var(--cfg-accent-strong); } @keyframes cp-move { to { transform: translateX(min(120px, 35vw)); } } diff --git a/configurator/src/components/FriendlyControl.svelte b/configurator/src/components/FriendlyControl.svelte index 41d026d4..a984054d 100644 --- a/configurator/src/components/FriendlyControl.svelte +++ b/configurator/src/components/FriendlyControl.svelte @@ -1,5 +1,5 @@
@@ -20,7 +25,17 @@ {#if desc}

{desc}

{/if} {#if showToken || rawOpen}{token.name}{/if}
-
+
+ {#if meta.control === 'select' && meta.options?.length} + + {:else} + + {/if} +
@@ -39,6 +54,7 @@ .friendly__copy p { margin: 0; color: var(--cfg-text-muted); font-size: 12px; } .friendly__token { color: var(--cfg-text-faint); font-size: 11px; word-break: break-all; } .friendly__editor { min-width: 0; } + .friendly__select { width: 100%; } .friendly__actions { display:flex; gap:6px; justify-content:flex-end; flex-wrap:wrap; } @media (max-width: 980px) { .friendly { grid-template-columns: 1fr; align-items: stretch; } .friendly__actions { justify-content:flex-start; } } diff --git a/configurator/src/components/editors/ColorStudio.svelte b/configurator/src/components/editors/ColorStudio.svelte index 882edddd..808f1971 100644 --- a/configurator/src/components/editors/ColorStudio.svelte +++ b/configurator/src/components/editors/ColorStudio.svelte @@ -1,22 +1,50 @@ - +
-
Surface card

Text, link, border and subtle background preview.

Status message
+
+ +
Surface card

Text, link, border and subtle background preview.

Example link
+ Status message +
+ +
+ {#each main as { key, label } (key)}{/each} + {#each status as { key, label } (key)}{/each} +
+
Main colors
{#each main as {key,label} (key)}{/each}
Semantic colors
{#each status as {key,label} (key)}{/each}
+
Contrast & palette tuning
{#each tuning as token (token.name)}{/each}
Semantic role preview
Generated shade ramp
+ diff --git a/configurator/src/components/editors/EffectsStudio.svelte b/configurator/src/components/editors/EffectsStudio.svelte index 05228733..f9826847 100644 --- a/configurator/src/components/editors/EffectsStudio.svelte +++ b/configurator/src/components/editors/EffectsStudio.svelte @@ -1,9 +1,25 @@ -

Readable media card

Scrim, blur and muted states preview.

{#each tokens as token (token.name)}{/each}
- + + +
+

Readable media card

Scrim, blur and muted states preview.

+
+ +
+
+ + diff --git a/configurator/src/components/editors/LayoutStudio.svelte b/configurator/src/components/editors/LayoutStudio.svelte index 46daa95a..d0a0fc29 100644 --- a/configurator/src/components/editors/LayoutStudio.svelte +++ b/configurator/src/components/editors/LayoutStudio.svelte @@ -1,9 +1,28 @@ -
viewportcontainergrid columns
{#each tokens as token (token.name)}{/each}
- + + +
+
+
viewportcontainergrid columnsreading width
+ +
+
+ + diff --git a/configurator/src/components/editors/MotionStudio.svelte b/configurator/src/components/editors/MotionStudio.svelte index a28470fe..107bdef0 100644 --- a/configurator/src/components/editors/MotionStudio.svelte +++ b/configurator/src/components/editors/MotionStudio.svelte @@ -1,9 +1,27 @@ -
fastnormalslow
{#each tokens as token (token.name)}{/each}
- + + +
+
fastnormalslow
+
enter
hover
exit
+ +
+
+ + diff --git a/configurator/src/components/editors/ShadowStudio.svelte b/configurator/src/components/editors/ShadowStudio.svelte index ee228158..71603814 100644 --- a/configurator/src/components/editors/ShadowStudio.svelte +++ b/configurator/src/components/editors/ShadowStudio.svelte @@ -1,10 +1,26 @@ -
{#each levels as l}
{l}
{/each}
{#each tokens as token (token.name)}{/each}
- + + +
+
{#each levels as l}
{l}surface
{/each}
+

Media card glow

Text and image overlays use the same shadow language.

+ +
+
+ + diff --git a/configurator/src/components/editors/ShapeStudio.svelte b/configurator/src/components/editors/ShapeStudio.svelte index 69a20d3f..b1fef7b6 100644 --- a/configurator/src/components/editors/ShapeStudio.svelte +++ b/configurator/src/components/editors/ShapeStudio.svelte @@ -1,10 +1,27 @@ -
{#each samples as n}
{n}
{/each}
{#each tokens as token (token.name)}{/each}
- + + +
+
{#each samples as n}
{n}
{/each}
+

Divider and border rhythm preview

+ +
+
+ + diff --git a/configurator/src/components/editors/SpacingStudio.svelte b/configurator/src/components/editors/SpacingStudio.svelte index cbfa20c6..02f5091d 100644 --- a/configurator/src/components/editors/SpacingStudio.svelte +++ b/configurator/src/components/editors/SpacingStudio.svelte @@ -1,10 +1,31 @@ -
{#each steps as s,i}
{s}
{/each}
{#each tokens as token (token.name)}{/each}
- + + +
+
+
{#each steps as s, i}
{s}
{/each}
+
Card

Component padding

Stack

Content rhythm

Section

Large vertical space

+
+ +
+
+ + diff --git a/configurator/src/components/editors/StudioControls.svelte b/configurator/src/components/editors/StudioControls.svelte new file mode 100644 index 00000000..d6de97bb --- /dev/null +++ b/configurator/src/components/editors/StudioControls.svelte @@ -0,0 +1,24 @@ + + +
+ {#each groups as group (group.title)} + overrides[token.name] != null).length}> +
+ {#each group.tokens as token (token.name)} + + {/each} +
+
+ {/each} +
+ + diff --git a/configurator/src/components/editors/StudioFrame.svelte b/configurator/src/components/editors/StudioFrame.svelte index 477d11a7..0b58a989 100644 --- a/configurator/src/components/editors/StudioFrame.svelte +++ b/configurator/src/components/editors/StudioFrame.svelte @@ -10,15 +10,21 @@

{eyebrow}

{title}

{#if description}{description}{/if} +
+ 01 Preview02 Tune03 Verify +
{@render children?.()}
diff --git a/configurator/src/components/editors/TypographyStudio.svelte b/configurator/src/components/editors/TypographyStudio.svelte index 905e5ced..7aa7c032 100644 --- a/configurator/src/components/editors/TypographyStudio.svelte +++ b/configurator/src/components/editors/TypographyStudio.svelte @@ -1,47 +1,70 @@ - +
-
- {#each PANELS as p}{/each} +
+
+ {#each TYPOGRAPHY_PANELS as p (p.id)} + + {/each} +
+

{panel.description}

-
+ +
+
mobiletabletdesktop

The quick brown fox

Jumps over the lazy dog

Typography at every scale

Fluid, readable, precise

Fine-tune each heading level Pixel-perfect control -

Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height and spacing at a glance.

+

Body: The quick brown fox jumps over the lazy dog. A short paragraph shows body rhythm, line height, measure and spacing at a glance.

-
- {#each activeTokens as token (token.name)} - - {/each} + +
+
Heading wrapBalanced title blocks reduce awkward one-word lines.
+
Body rhythmLine height and content gap define reading comfort.
+
Code / monoconst token = true;
+ +
+ diff --git a/configurator/src/lib/controlSchema.js b/configurator/src/lib/controlSchema.js index d02e972f..d519ae83 100644 --- a/configurator/src/lib/controlSchema.js +++ b/configurator/src/lib/controlSchema.js @@ -1,8 +1,17 @@ import { parseLength } from './length.js'; const EXPLICIT = { - '--sf-heading-text-wrap': { control: 'select', preview: 'wrap', options: ['balance', 'pretty', 'wrap', 'normal'], label: 'Heading wrap' }, - '--sf-scrim-direction': { control: 'select', preview: 'gradient', options: ['to top', 'to bottom', 'to right', 'to left', '135deg'], label: 'Scrim direction' }, + '--sf-heading-text-wrap': { control: 'select', preview: 'wrap', options: ['balance', 'pretty', 'wrap', 'normal'], label: 'Heading wrap', description: 'How headings wrap when space gets tight.' }, + '--sf-body-text-wrap': { control: 'select', preview: 'wrap', options: ['pretty', 'balance', 'wrap', 'normal'], label: 'Body wrap', description: 'Readable paragraph wrapping behaviour.' }, + '--sf-scrim-direction': { control: 'select', preview: 'gradient', options: ['to top', 'to bottom', 'to right', 'to left', '135deg'], label: 'Scrim direction', description: 'Gradient direction used for media overlays.' }, + '--sf-border-style': { control: 'select', preview: 'border', options: ['solid', 'dashed', 'dotted', 'double'], label: 'Border style' }, + '--sf-divider-style': { control: 'select', preview: 'border', options: ['solid', 'dashed', 'dotted', 'double'], label: 'Divider style' }, + '--sf-object-fit': { control: 'select', preview: 'media', options: ['cover', 'contain', 'fill', 'none', 'scale-down'], label: 'Object fit' }, + '--sf-object-position': { control: 'select', preview: 'media', options: ['50% 50%', 'top', 'bottom', 'left', 'right', 'center'], label: 'Object position' }, + '--sf-motion-scale': { control: 'number', preview: 'motion', label: 'Motion scale', description: 'Global multiplier for animation and transition durations.' }, + '--sf-radius-scale': { control: 'number', preview: 'radius', label: 'Radius scale', description: 'Global multiplier for the corner radius ramp.' }, + '--sf-space-scale': { control: 'number', preview: 'spacing', label: 'Space scale', description: 'Global multiplier for the spacing ramp.' }, + '--sf-section-scale': { control: 'number', preview: 'spacing', label: 'Section scale', description: 'Multiplier for large section padding.' }, }; function titleFromName(name = '') { @@ -15,22 +24,28 @@ export function controlForToken(token, override = {}) { const value = token?.value ?? ''; let schema = { label: titleFromName(name), description: token?.note || token?.description || '', control: 'text', preview: 'text' }; - if (/color|palette|hsl|oklch|hex|focus|link|caret|border/.test(name) && /(#[0-9a-f]|oklch|oklab|hsl|rgb|var\(--sf-color)/i.test(value + name)) { + if (/ease/.test(name)) { + schema = { ...schema, control: 'select', preview: 'motion', options: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'cubic-bezier(.2,.8,.2,1)'] }; + } else if (/text-wrap/.test(name)) { + schema = { ...schema, control: 'select', preview: 'wrap', options: ['balance', 'pretty', 'wrap', 'normal'] }; + } else if (/scrim|gradient/.test(name)) { + schema = { ...schema, control: /direction/.test(name) ? 'select' : 'text', preview: 'gradient' }; + } else if (/color|palette|hsl|oklch|hex|focus|link|caret|border/.test(name) && /(#[0-9a-f]|oklch|oklab|hsl|rgb|var\(--sf-color)/i.test(value + name)) { schema = { ...schema, control: 'color', preview: 'color' }; } else if (/font/.test(name)) { schema = { ...schema, control: 'font', preview: 'font' }; - } else if (/duration|motion-scale/.test(name)) { - schema = { ...schema, control: 'length', preview: 'motion' }; - } else if (/ease/.test(name)) { - schema = { ...schema, control: 'select', preview: 'motion', options: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'cubic-bezier(.2,.8,.2,1)'] }; + } else if (/duration|motion-scale|animation|transition/.test(name)) { + schema = { ...schema, control: parseLength(value) ? 'length' : 'text', preview: 'motion' }; } else if (/radius/.test(name)) { - schema = { ...schema, control: 'length', preview: 'radius' }; + schema = { ...schema, control: parseLength(value) ? 'length' : 'number', preview: 'radius' }; } else if (/shadow/.test(name)) { schema = { ...schema, control: 'text', preview: 'shadow' }; - } else if (/space|gap|gutter|pad|width|height|size|container|leading|tracking|border-width|divider/.test(name) || parseLength(value)) { + } else if (/opacity/.test(name)) { + schema = { ...schema, control: 'number', preview: 'opacity' }; + } else if (/space|gap|gutter|pad|width|height|size|container|leading|tracking|border-width|divider|offset|inset/.test(name) || parseLength(value)) { schema = { ...schema, control: 'length', preview: /leading/.test(name) ? 'line-height' : 'spacing' }; - } else if (/scale|opacity|threshold|bias|weight|z-/.test(name) || /^-?\d*\.?\d+$/.test(value)) { - schema = { ...schema, control: 'number', preview: /opacity/.test(name) ? 'opacity' : 'scale' }; + } else if (/scale|threshold|bias|weight|z-/.test(name) || /^-?\d*\.?\d+$/.test(value)) { + schema = { ...schema, control: 'number', preview: /weight/.test(name) ? 'font' : 'scale' }; } return { ...schema, ...explicit, ...override }; diff --git a/configurator/src/lib/studioSchema.js b/configurator/src/lib/studioSchema.js new file mode 100644 index 00000000..08d07d7f --- /dev/null +++ b/configurator/src/lib/studioSchema.js @@ -0,0 +1,76 @@ +import { tokenByName } from './model.js'; + +export const TYPOGRAPHY_PANELS = [ + { + id: 'Overview', + label: 'Overview', + description: 'Font stacks, global rhythm and heading behavior for the whole type system.', + tokens: ['--sf-font-body', '--sf-font-heading', '--sf-font-mono', '--sf-leading-normal', '--sf-font-weight-heading', '--sf-heading-text-wrap'], + }, + { + id: 'Fluid scale', + label: 'Fluid scale', + description: 'Tune the source values that drive the generated text ramp.', + tokens: ['--sf-text-base-min', '--sf-text-base-max', '--sf-text-ratio-min', '--sf-text-ratio-max', '--sf-text-scale', '--sf-text-display-scale'], + }, + { + id: 'Headings', + label: 'Headings', + description: 'Heading aliases, line-height, tracking and max-width constraints.', + tokens: ['--sf-h1-size', '--sf-h1-line-height', '--sf-h1-letter-spacing', '--sf-h1-max-width', '--sf-h2-size', '--sf-h2-line-height', '--sf-h3-size', '--sf-h3-line-height', '--sf-heading-text-wrap'], + }, + { + id: 'Body', + label: 'Body', + description: 'Body copy, prose spacing and readable line length.', + tokens: ['--sf-body-font-size', '--sf-body-line-height', '--sf-body-text-wrap', '--sf-text-m-max-width', '--sf-prose-paragraph', '--sf-prose-heading-gap', '--sf-prose-list-gap'], + }, + { + id: 'Code', + label: 'Code', + description: 'Monospace family and inline-code sizing/padding.', + tokens: ['--sf-font-mono', '--sf-code-font-size', '--sf-code-padding-inline', '--sf-code-padding-block', '--sf-color-code-bg', '--sf-color-code-text'], + }, +]; + +export const STUDIO_GROUPS = { + spacing: [ + { title: 'Global scale', hint: 'One multiplier for the complete spacing ramp.', tokens: ['--sf-space-scale', '--sf-section-scale'] }, + { title: 'Page rhythm', hint: 'The defaults most layouts feel immediately.', tokens: ['--sf-section-pad', '--sf-content-gap', '--sf-gutter', '--sf-gap'] }, + { title: 'Component rhythm', hint: 'Interior padding and compact UI spacing.', tokens: ['--sf-component-pad', '--sf-space-2xs', '--sf-space-xs', '--sf-space-s', '--sf-space-m'] }, + ], + layout: [ + { title: 'Containers', hint: 'Main page widths and reading measure.', tokens: ['--sf-container-narrow', '--sf-container-prose', '--sf-container-default', '--sf-container-wide'] }, + { title: 'Grid & composition', hint: 'Breakpoint-free grid and sidebar defaults.', tokens: ['--sf-grid-min', '--sf-grid-min-s', '--sf-grid-min-l', '--sf-sidebar-width', '--sf-switcher-threshold'] }, + { title: 'Global anchors', hint: 'Sticky offsets, header height and accessible targets.', tokens: ['--sf-header-height-mobile', '--sf-header-height-desktop', '--sf-touch-target', '--sf-sticky-offset-mobile', '--sf-sticky-offset-desktop'] }, + ], + borders: [ + { title: 'Radius system', hint: 'Scale every corner from sharp to soft.', tokens: ['--sf-radius-scale', '--sf-radius-xs', '--sf-radius-s', '--sf-radius-m', '--sf-radius-l', '--sf-radius-xl', '--sf-radius-full'] }, + { title: 'Borders & dividers', hint: 'Strokes that frame cards, fields and rules.', tokens: ['--sf-border-scale', '--sf-border-style', '--sf-border-width-hairline', '--sf-border-width-1', '--sf-border-width-2', '--sf-divider-width', '--sf-divider-style'] }, + { title: 'Focus ring', hint: 'Keyboard focus affordance lives with shape controls.', tokens: ['--sf-focus-ring-width', '--sf-focus-ring-offset', '--sf-color-border--focus'] }, + ], + shadows: [ + { title: 'Elevation strength', hint: 'Global tone and lightness for all elevations.', tokens: ['--sf-shadow-strength', '--sf-shadow-lightness', '--sf-shadow-color', '--sf-shadow-glow-color'] }, + { title: 'Box shadows', hint: 'Individual elevation outputs for cards, popovers and dialogs.', tokens: ['--sf-shadow-xs', '--sf-shadow-s', '--sf-shadow-m', '--sf-shadow-l', '--sf-shadow-xl', '--sf-shadow-2xl'] }, + { title: 'Text & drop shadows', hint: 'Media and floating asset shadow presets.', tokens: ['--sf-text-shadow-s', '--sf-text-shadow-m', '--sf-text-shadow-l', '--sf-drop-shadow-s', '--sf-drop-shadow-m', '--sf-drop-shadow-l'] }, + ], + motion: [ + { title: 'Speed scale', hint: 'Set to 0 for near-reduced motion, raise for more expressive interfaces.', tokens: ['--sf-motion-scale', '--sf-duration-none', '--sf-duration-instant', '--sf-duration-fast', '--sf-duration-normal', '--sf-duration-slow', '--sf-duration-slower'] }, + { title: 'Easing curves', hint: 'The feel of entrance, exit and spring interactions.', tokens: ['--sf-ease-linear', '--sf-ease-in', '--sf-ease-out', '--sf-ease-in-out', '--sf-ease-spring', '--sf-ease-overshoot'] }, + { title: 'Animation presets', hint: 'Named shorthands consumed by utility classes and components.', tokens: ['--sf-animation-fade-in', '--sf-animation-slide-in-up', '--sf-animation-scale-up', '--sf-animation-float', '--sf-animation-shimmer'] }, + ], + effects: [ + { title: 'Glass & blur', hint: 'Backdrops and soft focus effects.', tokens: ['--sf-blur'] }, + { title: 'Opacity states', hint: 'Muted, disabled and pending UI states.', tokens: ['--sf-opacity-muted', '--sf-opacity-disabled', '--sf-state-pending-opacity'] }, + { title: 'Scrims & masks', hint: 'Readable text over media and scroll fades.', tokens: ['--sf-scrim-color', '--sf-scrim-direction', '--sf-scrim-text-shadow', '--sf-mask-scrim-start', '--sf-mask-scrim-end'] }, + ], +}; + +export function resolveStudioGroups(groups) { + return groups + .map((group) => ({ + ...group, + tokens: group.tokens.map((name) => tokenByName.get(name)).filter(Boolean), + })) + .filter((group) => group.tokens.length); +} From 43a83657f55c4e7f30ae584d048a2fe1ccc45bb5 Mon Sep 17 00:00:00 2001 From: Jack Granatowski Date: Thu, 25 Jun 2026 01:32:48 +0200 Subject: [PATCH 04/12] test(configurator): cover visual studios --- configurator/src/lib/studioSchema.js | 2 +- configurator/tests-components/studios.test.js | 65 +++++++++++++++++++ configurator/tests/studio-schema.test.js | 57 ++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 configurator/tests-components/studios.test.js create mode 100644 configurator/tests/studio-schema.test.js diff --git a/configurator/src/lib/studioSchema.js b/configurator/src/lib/studioSchema.js index 08d07d7f..17f62e83 100644 --- a/configurator/src/lib/studioSchema.js +++ b/configurator/src/lib/studioSchema.js @@ -29,7 +29,7 @@ export const TYPOGRAPHY_PANELS = [ id: 'Code', label: 'Code', description: 'Monospace family and inline-code sizing/padding.', - tokens: ['--sf-font-mono', '--sf-code-font-size', '--sf-code-padding-inline', '--sf-code-padding-block', '--sf-color-code-bg', '--sf-color-code-text'], + tokens: ['--sf-font-mono', '--sf-code-font-size', '--sf-font-numeric', '--sf-font-features', '--sf-font-variation'], }, ]; diff --git a/configurator/tests-components/studios.test.js b/configurator/tests-components/studios.test.js new file mode 100644 index 00000000..aa36f9a3 --- /dev/null +++ b/configurator/tests-components/studios.test.js @@ -0,0 +1,65 @@ +/** + * Component smoke tests for the redesigned Studio editors. + * + * These are intentionally structural: jsdom cannot evaluate the framework's + * relative-color CSS, but it can verify that each studio mounts, exposes its + * primary preview surface, and renders the curated control rows users need. + */ +import { describe, test, expect, beforeEach } from 'vitest'; +import { render, fireEvent } from '@testing-library/svelte'; +import { tick } from 'svelte'; +import TypographyStudio from '../src/components/editors/TypographyStudio.svelte'; +import ColorStudio from '../src/components/editors/ColorStudio.svelte'; +import SpacingStudio from '../src/components/editors/SpacingStudio.svelte'; +import LayoutStudio from '../src/components/editors/LayoutStudio.svelte'; +import ShapeStudio from '../src/components/editors/ShapeStudio.svelte'; +import ShadowStudio from '../src/components/editors/ShadowStudio.svelte'; +import MotionStudio from '../src/components/editors/MotionStudio.svelte'; +import EffectsStudio from '../src/components/editors/EffectsStudio.svelte'; +import FriendlyControl from '../src/components/FriendlyControl.svelte'; +import { tokenByName } from '../src/lib/model.js'; +import { clearAll, overrides } from '../src/lib/store.svelte.js'; + +beforeEach(() => clearAll()); + +const STUDIOS = [ + ['Typography Studio', TypographyStudio, ['The quick brown fox', 'Fluid scale']], + ['Color Studio', ColorStudio, ['Main colors', 'Semantic role preview', 'Contrast & palette tuning']], + ['Spacing Studio', SpacingStudio, ['Global scale', 'Page rhythm', 'Component rhythm']], + ['Layout Studio', LayoutStudio, ['Containers', 'Grid & composition', 'Global anchors']], + ['Shape Studio', ShapeStudio, ['Radius system', 'Focus ring', 'Focused action']], + ['Shadow Studio', ShadowStudio, ['Elevation strength', 'Box shadows', 'Media card glow']], + ['Motion Studio', MotionStudio, ['Speed scale', 'Easing curves', 'fast']], + ['Effects Studio', EffectsStudio, ['Glass & blur', 'Opacity states', 'Readable media card']], +]; + +describe('visual studios', () => { + for (const [name, Component, expectedText] of STUDIOS) { + test(`${name} mounts with preview and curated controls`, () => { + const { container, getByText } = render(Component); + expect(container.querySelector('.studio')).toBeTruthy(); + expect(getByText(name)).toBeInTheDocument(); + for (const text of expectedText) { + expect(getByText(text)).toBeInTheDocument(); + } + }); + } + + test('Typography Studio switches scopes and renders active panel controls', async () => { + const { getByRole, getByText, getAllByText } = render(TypographyStudio); + await fireEvent.click(getByRole('tab', { name: 'Headings' })); + expect(getAllByText('Heading aliases, line-height, tracking and max-width constraints.').length).toBeGreaterThan(0); + expect(getByText('--sf-h1-size')).toBeInTheDocument(); + }); + + test('FriendlyControl renders schema select controls and writes overrides', async () => { + const token = tokenByName.get('--sf-heading-text-wrap'); + const { getByLabelText } = render(FriendlyControl, { props: { token, showToken: true } }); + const select = getByLabelText('--sf-heading-text-wrap'); + + expect(select.tagName).toBe('SELECT'); + await fireEvent.change(select, { target: { value: 'pretty' } }); + await tick(); + expect(overrides['--sf-heading-text-wrap']).toBe('pretty'); + }); +}); diff --git a/configurator/tests/studio-schema.test.js b/configurator/tests/studio-schema.test.js new file mode 100644 index 00000000..fc66e7b0 --- /dev/null +++ b/configurator/tests/studio-schema.test.js @@ -0,0 +1,57 @@ +/** + * Tripwires for the visual Studio configuration layer. + * + * The studios are curated by hand, so every listed token must exist and every + * group needs enough metadata to render a useful visual editing section. + */ +import { test, describe } from 'node:test'; +import assert from 'node:assert/strict'; +import { TYPOGRAPHY_PANELS, STUDIO_GROUPS, resolveStudioGroups } from '../src/lib/studioSchema.js'; +import data from '../src/data/api-index.generated.json' with { type: 'json' }; + +const known = new Set(data.tokens.map((token) => token.name)); + +describe('studio schema', () => { + test('typography panels have labels, descriptions and real tokens', () => { + assert.ok(TYPOGRAPHY_PANELS.length >= 5, 'typography exposes multiple editing scopes'); + const ids = TYPOGRAPHY_PANELS.map((panel) => panel.id); + assert.equal(new Set(ids).size, ids.length, 'typography panel ids are unique'); + + for (const panel of TYPOGRAPHY_PANELS) { + assert.ok(panel.label?.trim(), `${panel.id} has a label`); + assert.ok(panel.description?.trim(), `${panel.id} has a description`); + assert.ok(panel.tokens.length > 0, `${panel.id} has tokens`); + for (const name of panel.tokens) { + assert.ok(known.has(name), `${panel.id}: ${name} is in api-index`); + } + } + }); + + test('every studio group has a title, hint and real tokens', () => { + for (const [domain, groups] of Object.entries(STUDIO_GROUPS)) { + assert.ok(groups.length > 0, `${domain} has studio groups`); + const titles = groups.map((group) => group.title); + assert.equal(new Set(titles).size, titles.length, `${domain} group titles are unique`); + + for (const group of groups) { + assert.ok(group.title?.trim(), `${domain}: group has a title`); + assert.ok(group.hint?.trim(), `${domain}/${group.title}: group has a hint`); + assert.ok(group.tokens.length > 0, `${domain}/${group.title}: group has tokens`); + for (const name of group.tokens) { + assert.ok(known.has(name), `${domain}/${group.title}: ${name} is in api-index`); + } + } + } + }); + + test('resolveStudioGroups removes dead token references without mutating labels', () => { + const resolved = resolveStudioGroups([ + { title: 'Mixed', hint: 'Contains one valid token and one stale token.', tokens: ['--sf-space-scale', '--sf-not-real'] }, + { title: 'Empty', hint: 'Should disappear.', tokens: ['--sf-not-real'] }, + ]); + + assert.equal(resolved.length, 1); + assert.equal(resolved[0].title, 'Mixed'); + assert.deepEqual(resolved[0].tokens.map((token) => token.name), ['--sf-space-scale']); + }); +}); From c4d4f517f3d21f1d229bc11a55d60b8a0ac00dbf Mon Sep 17 00:00:00 2001 From: Jack Granatowski Date: Thu, 25 Jun 2026 02:02:26 +0200 Subject: [PATCH 05/12] feat(configurator): deepen color studio workflow --- .../src/components/editors/ColorStudio.svelte | 63 +++++++++++++++++-- configurator/tests-components/studios.test.js | 6 +- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/configurator/src/components/editors/ColorStudio.svelte b/configurator/src/components/editors/ColorStudio.svelte index 808f1971..963b7942 100644 --- a/configurator/src/components/editors/ColorStudio.svelte +++ b/configurator/src/components/editors/ColorStudio.svelte @@ -1,5 +1,6 @@
-
+ + +
Surface card

Text, link, border and subtle background preview.

Example link
Status message
+
+
LightReadable surfaceBase, neutral and brand sources.
+
DarkAuto-derived pairOverride only when the automatic pair needs art direction.
+
+
{#each main as { key, label } (key)}{/each} {#each status as { key, label } (key)}{/each}
+
+
Role mapHow brand sources become real interface decisions.
+
+ {#each COLOR_ROLE_GROUPS as group (group.section)} +
+

{group.section}

+ {#each group.roles as role (role.token)} +

{role.label}{role.token}

+ {/each} +
+ {/each} +
+
+
Main colors
{#each main as {key,label} (key)}{/each}
Semantic colors
{#each status as {key,label} (key)}{/each}
Contrast & palette tuning
{#each tuning as token (token.name)}{/each}
@@ -36,15 +68,38 @@ diff --git a/configurator/tests-components/studios.test.js b/configurator/tests-components/studios.test.js index aa36f9a3..d8af3af3 100644 --- a/configurator/tests-components/studios.test.js +++ b/configurator/tests-components/studios.test.js @@ -24,7 +24,7 @@ beforeEach(() => clearAll()); const STUDIOS = [ ['Typography Studio', TypographyStudio, ['The quick brown fox', 'Fluid scale']], - ['Color Studio', ColorStudio, ['Main colors', 'Semantic role preview', 'Contrast & palette tuning']], + ['Color Studio', ColorStudio, ['Source pairs', 'Role map', 'Auto-derived pair', 'Contrast & palette tuning']], ['Spacing Studio', SpacingStudio, ['Global scale', 'Page rhythm', 'Component rhythm']], ['Layout Studio', LayoutStudio, ['Containers', 'Grid & composition', 'Global anchors']], ['Shape Studio', ShapeStudio, ['Radius system', 'Focus ring', 'Focused action']], @@ -36,11 +36,11 @@ const STUDIOS = [ describe('visual studios', () => { for (const [name, Component, expectedText] of STUDIOS) { test(`${name} mounts with preview and curated controls`, () => { - const { container, getByText } = render(Component); + const { container, getByText, getAllByText } = render(Component); expect(container.querySelector('.studio')).toBeTruthy(); expect(getByText(name)).toBeInTheDocument(); for (const text of expectedText) { - expect(getByText(text)).toBeInTheDocument(); + expect(getAllByText(text).length).toBeGreaterThan(0); } }); } From 7253eb7c2f0dfeaef73b3f96eabf531cef69d7a7 Mon Sep 17 00:00:00 2001 From: Jack Granatowski Date: Thu, 25 Jun 2026 06:13:32 +0200 Subject: [PATCH 06/12] feat(configurator): deepen spacing studio workflow --- .../components/editors/SpacingStudio.svelte | 75 ++++++++++++++++--- configurator/tests-components/studios.test.js | 2 +- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/configurator/src/components/editors/SpacingStudio.svelte b/configurator/src/components/editors/SpacingStudio.svelte index 02f5091d..66b06a80 100644 --- a/configurator/src/components/editors/SpacingStudio.svelte +++ b/configurator/src/components/editors/SpacingStudio.svelte @@ -2,30 +2,83 @@ import { STUDIO_GROUPS, resolveStudioGroups } from '../../lib/studioSchema.js'; import StudioFrame from './StudioFrame.svelte'; import StudioControls from './StudioControls.svelte'; + const steps = ['2xs', 'xs', 's', 'm', 'l', 'xl', '2xl', '3xl']; + const workflow = ['Scale', 'Rhythm', 'Components', 'Sections']; const groups = resolveStudioGroups(STUDIO_GROUPS.spacing); - +
-
-
{#each steps as s, i}
{s}
{/each}
-
Card

Component padding

Stack

Content rhythm

Section

Large vertical space

+ + +
+
+ Space map + Every step uses the same multiplier, so the whole framework keeps one rhythm. +
+
+ {#each steps as s, i} +
{s}
+ {/each} +
+
+ +
+
+ Section padding +
+

Marketing section

+

Large vertical space should feel intentional, not random.

+
+
+
+ Content gap +

Stack rhythm

+

Paragraph spacing

+

List spacing

+ +
+ +
+
Card

Component padding

+
Form
+
Cluster
ABC
+
+
diff --git a/configurator/tests-components/studios.test.js b/configurator/tests-components/studios.test.js index d8af3af3..3e5ecbc7 100644 --- a/configurator/tests-components/studios.test.js +++ b/configurator/tests-components/studios.test.js @@ -25,7 +25,7 @@ beforeEach(() => clearAll()); const STUDIOS = [ ['Typography Studio', TypographyStudio, ['The quick brown fox', 'Fluid scale']], ['Color Studio', ColorStudio, ['Source pairs', 'Role map', 'Auto-derived pair', 'Contrast & palette tuning']], - ['Spacing Studio', SpacingStudio, ['Global scale', 'Page rhythm', 'Component rhythm']], + ['Spacing Studio', SpacingStudio, ['Space map', 'Stack rhythm', 'Input gap', 'Global scale']], ['Layout Studio', LayoutStudio, ['Containers', 'Grid & composition', 'Global anchors']], ['Shape Studio', ShapeStudio, ['Radius system', 'Focus ring', 'Focused action']], ['Shadow Studio', ShadowStudio, ['Elevation strength', 'Box shadows', 'Media card glow']], From de6711d139b0fb5d690c447e96373d422f865a58 Mon Sep 17 00:00:00 2001 From: Jack Granatowski Date: Thu, 25 Jun 2026 06:38:56 +0200 Subject: [PATCH 07/12] feat(configurator): deepen layout studio workflow --- .../components/editors/LayoutStudio.svelte | 92 ++++++++++++++++--- configurator/tests-components/studios.test.js | 2 +- 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/configurator/src/components/editors/LayoutStudio.svelte b/configurator/src/components/editors/LayoutStudio.svelte index d0a0fc29..b7087b6a 100644 --- a/configurator/src/components/editors/LayoutStudio.svelte +++ b/configurator/src/components/editors/LayoutStudio.svelte @@ -2,27 +2,97 @@ import { STUDIO_GROUPS, resolveStudioGroups } from '../../lib/studioSchema.js'; import StudioFrame from './StudioFrame.svelte'; import StudioControls from './StudioControls.svelte'; + const groups = resolveStudioGroups(STUDIO_GROUPS.layout); + const workflow = ['Containers', 'Grid', 'Measure', 'Anchors']; + const devices = ['Mobile', 'Tablet', 'Desktop']; - +
-
-
viewportcontainergrid columnsreading width
+ + +
+
+ Container map + Compare narrow, prose, default and wide widths against the same viewport. +
+
+ {#each devices as device (device)}{device}{/each} +
+
+ wide + default + prose + narrow +
+
+ +
+ +
+
Card 1
+
Card 2
+
Card 3
+
Responsive gridCards collapse from multi-column to readable stacks.
+
+
+ +
+
+ Reading width +

Readable prose stays constrained

+

Text should not stretch across the full container. The prose rail keeps editorial content comfortable.

+
+
+ Global anchors +
Sticky offset
+ +
+
+ +
viewportcontainergrid columnsreading widthsticky offsets
diff --git a/configurator/tests-components/studios.test.js b/configurator/tests-components/studios.test.js index 3e5ecbc7..e5e10d49 100644 --- a/configurator/tests-components/studios.test.js +++ b/configurator/tests-components/studios.test.js @@ -26,7 +26,7 @@ const STUDIOS = [ ['Typography Studio', TypographyStudio, ['The quick brown fox', 'Fluid scale']], ['Color Studio', ColorStudio, ['Source pairs', 'Role map', 'Auto-derived pair', 'Contrast & palette tuning']], ['Spacing Studio', SpacingStudio, ['Space map', 'Stack rhythm', 'Input gap', 'Global scale']], - ['Layout Studio', LayoutStudio, ['Containers', 'Grid & composition', 'Global anchors']], + ['Layout Studio', LayoutStudio, ['Container map', 'Responsive grid', 'Reading width', 'Sticky offset']], ['Shape Studio', ShapeStudio, ['Radius system', 'Focus ring', 'Focused action']], ['Shadow Studio', ShadowStudio, ['Elevation strength', 'Box shadows', 'Media card glow']], ['Motion Studio', MotionStudio, ['Speed scale', 'Easing curves', 'fast']], From 3773e2633743a19ad0cd9a0db313fbdbdc5aeb0c Mon Sep 17 00:00:00 2001 From: Jack Granatowski Date: Thu, 25 Jun 2026 07:21:16 +0200 Subject: [PATCH 08/12] feat(configurator): deepen remaining studio workflows --- .../components/editors/EffectsStudio.svelte | 62 ++++++++++++-- .../components/editors/MotionStudio.svelte | 83 ++++++++++++++++--- .../components/editors/ShadowStudio.svelte | 55 ++++++++++-- .../src/components/editors/ShapeStudio.svelte | 70 +++++++++++++--- .../components/editors/StudioWorkflow.svelte | 16 ++++ 5 files changed, 244 insertions(+), 42 deletions(-) create mode 100644 configurator/src/components/editors/StudioWorkflow.svelte diff --git a/configurator/src/components/editors/EffectsStudio.svelte b/configurator/src/components/editors/EffectsStudio.svelte index f9826847..f248d42f 100644 --- a/configurator/src/components/editors/EffectsStudio.svelte +++ b/configurator/src/components/editors/EffectsStudio.svelte @@ -2,24 +2,68 @@ import { STUDIO_GROUPS, resolveStudioGroups } from '../../lib/studioSchema.js'; import StudioFrame from './StudioFrame.svelte'; import StudioControls from './StudioControls.svelte'; + import StudioWorkflow from './StudioWorkflow.svelte'; + const groups = resolveStudioGroups(STUDIO_GROUPS.effects); + const workflow = ['Glass', 'Opacity', 'Scrim', 'Masks']; + const states = ['normal', 'muted', 'disabled', 'pending']; - +
-

Readable media card

Scrim, blur and muted states preview.

-
+ + +
+
Glass & blur

Readable media card

Scrim, blur and muted states preview.

+
lowmediumhigh
+
+ +
+ {#each states as state} +
{state}{state === 'disabled' ? 'Disabled action' : state === 'pending' ? 'Pending feedback' : 'Readable state'}
+ {/each} +
+ +
+
Scrim overlay

Text over image

Direction and color keep content readable without manual gradients.

+
Without scrim

Harder to read

Preview makes the difference obvious.

+
+ +
+ Mask fade +
+
+
diff --git a/configurator/src/components/editors/MotionStudio.svelte b/configurator/src/components/editors/MotionStudio.svelte index 107bdef0..cc151a88 100644 --- a/configurator/src/components/editors/MotionStudio.svelte +++ b/configurator/src/components/editors/MotionStudio.svelte @@ -2,26 +2,85 @@ import { STUDIO_GROUPS, resolveStudioGroups } from '../../lib/studioSchema.js'; import StudioFrame from './StudioFrame.svelte'; import StudioControls from './StudioControls.svelte'; + import StudioWorkflow from './StudioWorkflow.svelte'; + const groups = resolveStudioGroups(STUDIO_GROUPS.motion); + const workflow = ['Duration', 'Easing', 'Presets', 'Reduced']; + const durations = ['instant', 'fast', 'normal', 'slow']; + const easings = ['linear', 'in', 'out', 'spring']; - +
-
fastnormalslow
-
enter
hover
exit
+ + +
+
Speed scaleMotion scale controls the feel of every duration without editing each preset by hand.
+
+ {#each durations as duration} +
{duration}--sf-duration-{duration}
+ {/each} +
+
+ +
+ {#each easings as easing} +
{easing}--sf-ease-{easing}
+ {/each} +
+ +
+
enter
+
hover
+
exit
+
shimmer
+
+ +
+ Reduced motion check +

When motion scale approaches zero, the interface should still communicate state changes with opacity and color.

+
+
+
diff --git a/configurator/src/components/editors/ShadowStudio.svelte b/configurator/src/components/editors/ShadowStudio.svelte index 71603814..64626173 100644 --- a/configurator/src/components/editors/ShadowStudio.svelte +++ b/configurator/src/components/editors/ShadowStudio.svelte @@ -2,25 +2,62 @@ import { STUDIO_GROUPS, resolveStudioGroups } from '../../lib/studioSchema.js'; import StudioFrame from './StudioFrame.svelte'; import StudioControls from './StudioControls.svelte'; + import StudioWorkflow from './StudioWorkflow.svelte'; + const levels = ['xs', 's', 'm', 'l', 'xl', '2xl']; + const workflow = ['Elevation', 'Surfaces', 'Overlays', 'Media']; const groups = resolveStudioGroups(STUDIO_GROUPS.shadows); - +
-
{#each levels as l}
{l}surface
{/each}
-

Media card glow

Text and image overlays use the same shadow language.

+ + +
+
Elevation stackCompare every shadow level on the same surface and tune global strength/lightness once.
+
+ {#each levels as level}
{level}surface
{/each} +
+
+ +
+
CardSurface shadow

Subtle depth for normal content.

+
PopoverFloating layer

Medium elevation above UI.

+
DialogModal overlay

Strong depth for interruption.

+
+ +
+

Media card glow

Text and image overlays use the same shadow language.

+
Text shadows

Readable over media

Drop and text shadows protect contrast on rich backgrounds.

+
+
diff --git a/configurator/src/components/editors/ShapeStudio.svelte b/configurator/src/components/editors/ShapeStudio.svelte index b1fef7b6..b25374cb 100644 --- a/configurator/src/components/editors/ShapeStudio.svelte +++ b/configurator/src/components/editors/ShapeStudio.svelte @@ -2,26 +2,72 @@ import { STUDIO_GROUPS, resolveStudioGroups } from '../../lib/studioSchema.js'; import StudioFrame from './StudioFrame.svelte'; import StudioControls from './StudioControls.svelte'; - const samples = ['Button', 'Input', 'Card', 'Badge']; + import StudioWorkflow from './StudioWorkflow.svelte'; + const groups = resolveStudioGroups(STUDIO_GROUPS.borders); + const workflow = ['Radius', 'Borders', 'Dividers', 'Focus']; + const radii = ['xs', 's', 'm', 'l', 'xl', 'full']; + const components = ['Button', 'Input', 'Card', 'Badge']; - +
-
{#each samples as n}
{n}
{/each}
-

Divider and border rhythm preview

+ + +
+
Radius systemOne scale controls components from sharp utility chips to fully pill-shaped actions.
+
+ {#each radii as radius (radius)} +
{radius}--sf-radius-{radius}
+ {/each} +
+
+ +
+ {#each components as item (item)} +
+ {item} + {item === 'Input' ? 'Field outline' : item === 'Card' ? 'Surface radius' : 'Interactive shape'} +
+ {/each} +
+ +
+
Border stack
+
Divider rhythm

Content before divider


Content after divider

+
Focus ringWidth + offset preview
+
+
diff --git a/configurator/src/components/editors/StudioWorkflow.svelte b/configurator/src/components/editors/StudioWorkflow.svelte new file mode 100644 index 00000000..c9fdd406 --- /dev/null +++ b/configurator/src/components/editors/StudioWorkflow.svelte @@ -0,0 +1,16 @@ + + + + + From 37827674bca67f89b74cf6eadb7e6667aad86e47 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Jun 2026 05:46:10 +0000 Subject: [PATCH 09/12] fix(configurator): resolve CI failures and address CodeRabbit review comments on PR #401 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI fixes: - CategoryHeader: add panel__title class to h2 so shell/undo-redo e2e tests that cycle domains via keyboard still find the heading (9 tests × 3 browsers) - DomainPanel: restore ScaleGenerator for visual-studio domains (typography, spacing) — removes the !usesVisualStudio guard so generator.spec.js and the undo-redo spec can locate .gen elements - ColorStudio: add panel__card + cfg-card classes and rename "Main colors" disclosure to "Core brand colors" to satisfy shell.spec.js panel__card check - LayoutStudio: replace custom .container-rails/.rail markup with ContainerBars component so domain-preview.spec.js .cbars__bar assertions pass; convert workflow nav to semantic
  1. ; fix Polish description - a11y.spec.js: expand allvars disclosure before querying .row__info-btn — Layout Settings now use FriendlyControl, raw TokenRows live in allvars CodeRabbit review fixes: - Header: import onDestroy and clearTimeout(_shareTimer) on component teardown - EffectsStudio / SpacingStudio / ColorStudio / TypographyStudio: replace mixed-Polish descriptions with full English copy - TypographyStudio: drive specimen content from the active tab (Headings shows heading stack, Body shows paragraph, Code shows mono sample, Overview all) - controlSchema: add options array when heuristic selects gradient-direction as a select control; check font-weight before the generic /font/ branch to give weight tokens a number control instead of a font-family picker - MotionStudio: add prefers-reduced-motion media query to disable infinite animations for users who have requested reduced motion Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01A2b6qg3hbi9QsBZmaFwjUd --- .../src/components/CategoryHeader.svelte | 2 +- .../src/components/DomainPanel.svelte | 2 +- configurator/src/components/Header.svelte | 2 ++ .../src/components/editors/ColorStudio.svelte | 4 ++-- .../components/editors/EffectsStudio.svelte | 2 +- .../components/editors/LayoutStudio.svelte | 23 +++++++------------ .../components/editors/MotionStudio.svelte | 4 ++++ .../components/editors/SpacingStudio.svelte | 2 +- .../editors/TypographyStudio.svelte | 21 ++++++++++------- configurator/src/lib/controlSchema.js | 6 ++++- configurator/tests-e2e/a11y.spec.js | 3 ++- 11 files changed, 40 insertions(+), 31 deletions(-) diff --git a/configurator/src/components/CategoryHeader.svelte b/configurator/src/components/CategoryHeader.svelte index 1020863b..9e31f619 100644 --- a/configurator/src/components/CategoryHeader.svelte +++ b/configurator/src/components/CategoryHeader.svelte @@ -7,7 +7,7 @@

    Visual framework editor

    -

    {domain.label}

    +

    {domain.label}

    {domain.blurb}

    diff --git a/configurator/src/components/DomainPanel.svelte b/configurator/src/components/DomainPanel.svelte index fd65aea5..636cde9f 100644 --- a/configurator/src/components/DomainPanel.svelte +++ b/configurator/src/components/DomainPanel.svelte @@ -207,7 +207,7 @@ - {#if !usesVisualStudio && hasGenerators} + {#if hasGenerators} {#each generators as g (g)} {/each} diff --git a/configurator/src/components/Header.svelte b/configurator/src/components/Header.svelte index 3e057089..04fb6b17 100644 --- a/configurator/src/components/Header.svelte +++ b/configurator/src/components/Header.svelte @@ -1,4 +1,5 @@ - +
-
Main colors
{#each main as {key,label} (key)}{/each}
+
Core brand colors
{#each main as {key,label} (key)}{/each}
Semantic colors
{#each status as {key,label} (key)}{/each}
Contrast & palette tuning
{#each tuning as token (token.name)}{/each}
Semantic role preview
diff --git a/configurator/src/components/editors/EffectsStudio.svelte b/configurator/src/components/editors/EffectsStudio.svelte index f248d42f..9f668caa 100644 --- a/configurator/src/components/editors/EffectsStudio.svelte +++ b/configurator/src/components/editors/EffectsStudio.svelte @@ -9,7 +9,7 @@ const states = ['normal', 'muted', 'disabled', 'pending']; - +
diff --git a/configurator/src/components/editors/LayoutStudio.svelte b/configurator/src/components/editors/LayoutStudio.svelte index b7087b6a..106b185f 100644 --- a/configurator/src/components/editors/LayoutStudio.svelte +++ b/configurator/src/components/editors/LayoutStudio.svelte @@ -2,16 +2,19 @@ import { STUDIO_GROUPS, resolveStudioGroups } from '../../lib/studioSchema.js'; import StudioFrame from './StudioFrame.svelte'; import StudioControls from './StudioControls.svelte'; + import ContainerBars from '../ContainerBars.svelte'; const groups = resolveStudioGroups(STUDIO_GROUPS.layout); const workflow = ['Containers', 'Grid', 'Measure', 'Anchors']; const devices = ['Mobile', 'Tablet', 'Desktop']; - +
@@ -22,12 +25,7 @@
{#each devices as device (device)}{device}{/each}
-
- wide - default - prose - narrow -
+
@@ -61,7 +59,8 @@ diff --git a/configurator/src/components/editors/SpacingStudio.svelte b/configurator/src/components/editors/SpacingStudio.svelte index 66b06a80..fa791ec9 100644 --- a/configurator/src/components/editors/SpacingStudio.svelte +++ b/configurator/src/components/editors/SpacingStudio.svelte @@ -8,7 +8,7 @@ const groups = resolveStudioGroups(STUDIO_GROUPS.spacing); - +