Skip to content
33 changes: 33 additions & 0 deletions configurator/src/components/CategoryHeader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script>
import Icon from './Icon.svelte';
let { domain, modifiedCount = 0, tokenCount = 0, onreset = null } = $props();
</script>

<header class="cat-hero">
<div class="cat-hero__icon"><Icon name={domain.icon} size={22} /></div>
<div class="cat-hero__copy">
<p class="cat-hero__eyebrow">Visual framework editor</p>
<h2>{domain.label}</h2>
<p>{domain.blurb}</p>
</div>
<div class="cat-hero__stats">
<span><strong>{modifiedCount}</strong> overrides</span>
<span><strong>{tokenCount}</strong> variables</span>
{#if modifiedCount > 0 && onreset}
<button class="cfg-btn cfg-btn--danger cfg-btn--sm" type="button" onclick={onreset}>Reset category</button>
{/if}
</div>
</header>

<style>
.cat-hero { display:grid; grid-template-columns:auto minmax(0,1fr) auto; gap:16px; align-items:center; padding:18px; border:1px solid var(--cfg-border); border-radius:var(--cfg-radius-l); background: radial-gradient(700px 180px at 10% 0%, var(--cfg-accent-soft), transparent 60%), var(--cfg-surface); box-shadow: var(--cfg-shadow-card); }
.cat-hero__icon { inline-size:48px; block-size:48px; display:grid; place-items:center; color:var(--cfg-accent); border:1px solid color-mix(in oklab, var(--cfg-accent) 35%, transparent); border-radius:16px; background: color-mix(in oklab, var(--cfg-accent) 10%, transparent); }
.cat-hero__copy { min-width:0; }
.cat-hero__eyebrow { margin:0 0 3px; color:var(--cfg-accent); font-size:11px; font-weight:800; letter-spacing:.09em; text-transform:uppercase; }
h2 { margin:0; font-size:clamp(22px,3vw,34px); line-height:1.05; }
.cat-hero__copy p:last-child { margin:6px 0 0; color:var(--cfg-text-muted); }
.cat-hero__stats { display:flex; align-items:center; justify-content:flex-end; gap:8px; flex-wrap:wrap; }
.cat-hero__stats span { display:inline-flex; gap:4px; align-items:center; padding:6px 10px; border-radius:999px; background:var(--cfg-bg-2); color:var(--cfg-text-muted); border:1px solid var(--cfg-border); font-size:12px; }
.cat-hero__stats strong { color:var(--cfg-text); }
@media(max-width:760px){ .cat-hero { grid-template-columns:1fr; } .cat-hero__stats { justify-content:flex-start; } }
</style>
63 changes: 63 additions & 0 deletions configurator/src/components/ControlPreview.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script>
import { overrides, ui } from '../lib/store.svelte.js';
import { buildPreviewDeclarations } from '../lib/preview.js';
let { token, value = '', type = 'text', label = '' } = $props();
const active = $derived(value || overrides[token?.name] || token?.value || '');
const stageStyle = $derived(buildPreviewDeclarations(overrides, ui.previewTheme));
</script>

<div class="ctrl-preview" style={stageStyle} title={token?.name || label}>
{#if type === 'color'}
<div class="ctrl-preview__color" style:--sample={active}>
<span></span><strong>Aa</strong><em>Button</em>
</div>
{:else if type === 'font'}
<p class="ctrl-preview__font" style:font-family={active}>Ag — The quick brown fox</p>
{:else if type === 'radius'}
<div class="ctrl-preview__radius" style:border-radius={active || 'var(--sf-radius-m)'}></div>
{:else if type === 'shadow'}
<div class="ctrl-preview__shadow" style:box-shadow={active || 'var(--sf-shadow-m)'}></div>
{:else if type === 'motion'}
<div class="ctrl-preview__motion"><span style:animation-duration={active || 'var(--sf-duration-normal)'}>motion</span></div>
{:else if type === 'line-height'}
<p class="ctrl-preview__lines" style:line-height={active || '1.5'}>Readable rhythm<br />across multiple<br />text lines.</p>
{:else if type === 'wrap'}
<p class="ctrl-preview__wrap" style:text-wrap={active || 'balance'}>A heading wraps cleanly in a narrow preview.</p>
{:else if type === 'opacity'}
<div class="ctrl-preview__opacity"><span style:opacity={active || '.5'}></span></div>
{:else if type === 'gradient'}
<div class="ctrl-preview__gradient" style:--scrim={active}></div>
{:else if type === 'border'}
<div class="ctrl-preview__border" style:border-style={active || 'solid'}></div>
{:else if type === 'media'}
<div class="ctrl-preview__media"><span style:object-fit={active || 'cover'}></span></div>
{:else if type === 'scale'}
<div class="ctrl-preview__scale"><span style:transform={`scale(${active || 1})`}></span></div>
{:else}
<div class="ctrl-preview__spacing"><span style:inline-size={active || '40%'}></span></div>
{/if}
</div>

<style>
.ctrl-preview { min-height: 54px; border: 1px solid var(--cfg-border); border-radius: var(--cfg-radius-s); background: var(--cfg-bg-2); padding: 10px; overflow: hidden; color: var(--sf-color-text, var(--cfg-text)); }
.ctrl-preview__color { display: grid; grid-template-columns: 38px 1fr auto; gap: 8px; align-items: center; }
.ctrl-preview__color span { inline-size: 38px; block-size: 34px; border-radius: 8px; background: var(--sample, var(--sf-color-primary)); border: 1px solid color-mix(in oklab, var(--sample, var(--sf-color-primary)), #000 20%); }
.ctrl-preview__color strong { color: var(--sample, var(--sf-color-primary)); }
.ctrl-preview__color em { font-style: normal; color: white; background: var(--sample, var(--sf-color-primary)); border-radius: 999px; padding: 4px 9px; font-size: 11px; }
.ctrl-preview__font { margin: 0; font-size: 17px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ctrl-preview__radius, .ctrl-preview__shadow { min-height: 36px; background: linear-gradient(135deg, var(--cfg-surface), var(--cfg-surface-3)); border: 1px solid var(--cfg-border-strong); }
.ctrl-preview__motion { min-height: 34px; position: relative; }
.ctrl-preview__motion span { display: inline-block; padding: 5px 10px; border-radius: 999px; background: var(--cfg-accent-strong); color: #fff; font-size: 11px; animation: cp-move var(--sf-duration-normal, 300ms) var(--sf-ease-in-out, ease-in-out) infinite alternate; }
.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)); } }
</style>
26 changes: 26 additions & 0 deletions configurator/src/components/ControlSection.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script>
let { title, hint = '', modifiedCount = 0, defaultOpen = true, children } = $props();
</script>

<details class="control-section" open={defaultOpen}>
<summary>
<span class="control-section__chev">›</span>
<strong>{title}</strong>
{#if hint}<em>{hint}</em>{/if}
{#if modifiedCount}<b>{modifiedCount} changed</b>{/if}
</summary>
<div class="control-section__body">{@render children?.()}</div>
</details>

<style>
.control-section { border:1px solid var(--cfg-border); border-radius:var(--cfg-radius); background:var(--cfg-surface); overflow:clip; }
summary { display:flex; align-items:center; gap:10px; padding:14px 16px; cursor:pointer; list-style:none; background:linear-gradient(to right, rgba(79,140,255,.06), transparent), var(--cfg-surface-2); border-bottom:1px solid var(--cfg-border); }
summary::-webkit-details-marker { display:none; }
.control-section:not([open]) summary { border-bottom:0; }
.control-section[open] .control-section__chev { transform:rotate(90deg); }
.control-section__chev { color:var(--cfg-text-faint); transition:transform .14s; font-size:20px; }
strong { font-size:13px; text-transform:uppercase; letter-spacing:.06em; }
em { color:var(--cfg-text-muted); font-style:normal; font-size:12px; flex:1; }
b { color:var(--cfg-accent); background:color-mix(in oklab, var(--cfg-accent) 12%, transparent); border:1px solid color-mix(in oklab, var(--cfg-accent) 35%, transparent); border-radius:999px; padding:2px 8px; font-size:10px; text-transform:uppercase; }
.control-section__body { display:block; }
</style>
Loading
Loading