Add harness workflow and Material showcase design system

This commit is contained in:
2026-03-23 17:30:30 +08:00
parent c570431dba
commit 5d02bf9df4
46 changed files with 3343 additions and 1068 deletions
+36 -5
View File
@@ -146,11 +146,42 @@ export const Motion: Story = {
}
},
render: () => (
<div className="grid w-[720px] gap-3 sm:grid-cols-2">
<Button>Premium primary</Button>
<Button variant="subtle">Subtle surface</Button>
<Button variant="secondary">Secondary action</Button>
<Button loading>Saving changes</Button>
<div className="relative grid w-[840px] gap-5 overflow-hidden rounded-[2.2rem] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface)_84%,white_16%),color-mix(in_oklch,var(--color-background)_90%,white_10%))] p-6 shadow-[0_24px_72px_color-mix(in_oklch,var(--color-primary)_10%,transparent)] sm:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
<div className="pointer-events-none absolute inset-0">
<div className="motion-drift absolute left-[-2rem] top-[-2rem] h-28 w-28 rounded-full bg-[color-mix(in_oklch,var(--color-primary-container)_62%,transparent)] blur-3xl" />
<div className="motion-breathe absolute right-0 top-10 h-24 w-24 rounded-full bg-[color-mix(in_oklch,var(--color-tertiary-container)_58%,transparent)] blur-3xl" />
</div>
<div className="relative grid gap-4">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Material motion deck
</p>
<h3 className="max-w-md text-3xl font-semibold tracking-[var(--tracking-tight)] text-[var(--color-foreground)]">
Buttons should feel like touchable capsules floating over tinted light.
</h3>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<Button>Premium primary</Button>
<Button variant="subtle">Subtle surface</Button>
<Button variant="secondary">Secondary action</Button>
<Button loading>Saving changes</Button>
</div>
</div>
<div className="relative flex items-center justify-center">
<div className="motion-float absolute left-5 top-8 rounded-full border border-white/45 bg-[color-mix(in_oklch,var(--color-surface-container-low)_78%,white_22%)] px-4 py-2 text-xs font-medium tracking-[0.14em] text-[var(--color-muted-foreground)] shadow-[0_12px_30px_color-mix(in_oklch,var(--color-primary)_10%,transparent)]">
SOFT LIFT
</div>
<div className="motion-float-delayed absolute bottom-6 right-6 rounded-full bg-[var(--color-primary-container)] px-4 py-2 text-sm font-medium text-[var(--color-on-primary-container)] shadow-[0_14px_28px_color-mix(in_oklch,var(--color-primary)_12%,transparent)]">
PRESSED
</div>
<div className="grid w-full max-w-[16rem] gap-3 rounded-[2rem] border border-white/40 bg-[color-mix(in_oklch,var(--color-surface-container-low)_82%,white_18%)] p-4 shadow-[0_24px_60px_color-mix(in_oklch,var(--color-primary)_12%,transparent)]">
<div className="h-28 rounded-[1.5rem] bg-[linear-gradient(165deg,color-mix(in_oklch,var(--color-primary-container)_88%,white_12%),color-mix(in_oklch,var(--color-tertiary-container)_82%,white_18%))]" />
<Button>Shop set</Button>
<Button variant="ghost">Maybe later</Button>
</div>
</div>
</div>
)
};
+40 -15
View File
@@ -52,21 +52,46 @@ export const Playground: Story = {
export const Grid: Story = {
render: () => (
<div className="grid w-[760px] gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Default tone</CardTitle>
<CardDescription>Standard elevated panel for data and form sections.</CardDescription>
</CardHeader>
<CardContent>Reliable baseline for most admin surfaces.</CardContent>
</Card>
<Card interactive tone="accent">
<CardHeader>
<CardTitle>Interactive accent</CardTitle>
<CardDescription>Hover-capable treatment for navigable cards.</CardDescription>
</CardHeader>
<CardContent>Use sparingly for overview screens with clear primary actions.</CardContent>
</Card>
<div className="relative grid w-[940px] gap-6 overflow-hidden rounded-[2.3rem] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface)_82%,white_18%),color-mix(in_oklch,var(--color-background)_88%,white_12%))] p-6 shadow-[0_24px_72px_color-mix(in_oklch,var(--color-primary)_10%,transparent)] md:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]">
<div className="pointer-events-none absolute inset-0">
<div className="motion-drift absolute left-[-1.5rem] top-6 h-24 w-24 rounded-full bg-[color-mix(in_oklch,var(--color-primary-container)_58%,transparent)] blur-3xl" />
<div className="motion-breathe absolute right-10 top-0 h-20 w-20 rounded-full bg-[color-mix(in_oklch,var(--color-tertiary-container)_52%,transparent)] blur-3xl" />
</div>
<div className="relative grid gap-4 self-start">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Showcase slabs
</p>
<h3 className="max-w-sm text-3xl font-semibold tracking-[var(--tracking-tight)] text-[var(--color-foreground)]">
Cards should feel like lit objects on a display plinth, not admin rectangles.
</h3>
</div>
<div className="grid gap-3 rounded-[1.6rem] bg-[color-mix(in_oklch,var(--color-surface-container)_82%,white_18%)] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.4)]">
<div className="h-40 rounded-[1.4rem] bg-[linear-gradient(160deg,color-mix(in_oklch,var(--color-primary-container)_78%,white_22%),color-mix(in_oklch,var(--color-tertiary-container)_74%,white_26%))]" />
<div className="grid gap-2">
<span className="h-3 w-24 rounded-full bg-[var(--color-foreground)]/12" />
<span className="h-3 w-40 rounded-full bg-[var(--color-foreground)]/9" />
</div>
</div>
</div>
<div className="relative grid gap-4">
<Card className="motion-float">
<CardHeader>
<CardTitle>Default tone</CardTitle>
<CardDescription>Standard elevated panel for data and form sections.</CardDescription>
</CardHeader>
<CardContent>Reliable baseline for most admin surfaces.</CardContent>
</Card>
<Card className="motion-float-delayed justify-self-end md:w-[88%]" interactive tone="accent">
<CardHeader>
<CardTitle>Interactive accent</CardTitle>
<CardDescription>Hover-capable treatment for navigable cards.</CardDescription>
</CardHeader>
<CardContent>Use sparingly for overview screens with clear primary actions.</CardContent>
</Card>
</div>
</div>
)
};
@@ -1,6 +1,5 @@
import {
Badge,
Button,
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
@@ -9,7 +8,6 @@ import {
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
@@ -414,6 +414,7 @@ function DataTablePlayground() {
</SelectContent>
</Select>
<Button
className="border-[var(--color-border-strong)] bg-[var(--color-background)] text-[var(--color-foreground)] hover:bg-[var(--color-surface)]"
size="sm"
variant="secondary"
onClick={resetView}
@@ -8,7 +8,6 @@ import {
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
+6 -1
View File
@@ -173,7 +173,12 @@ function LaunchSettingsForm() {
>
Reset
</Button>
<Button type="submit">Save settings</Button>
<Button
className="bg-[var(--color-foreground)] text-[var(--color-background)] hover:bg-[color-mix(in_oklch,var(--color-foreground)_88%,white_12%)]"
type="submit"
>
Save settings
</Button>
</div>
<div className="rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-background)] p-4">
+6 -5
View File
@@ -8,14 +8,15 @@ function FoundationShowcase() {
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6">
<div className="max-w-3xl space-y-3">
<p className="text-sm uppercase tracking-[0.28em] text-[var(--color-muted-foreground)]">
AI UI / Phase 0
AI UI / Foundation
</p>
<h1 className="text-4xl font-semibold tracking-tight sm:text-5xl">
Monorepo scaffolding for a source-owned component system.
Source-owned infrastructure for a Material-first component system.
</h1>
<p className="max-w-2xl text-base leading-7 text-[var(--color-muted-foreground)] sm:text-lg">
The repo now has workspace packages for tokens, UI utilities, and docs.
The next phase can focus on component contracts instead of repo setup.
The workspace foundation now supports dynamic seed color, a shared UI package,
and a Storybook review surface. The next work can stay focused on component
quality instead of repo setup.
</p>
</div>
@@ -46,7 +47,7 @@ function FoundationShowcase() {
<aside className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-surface)] p-6 shadow-[var(--shadow-xs)]">
<div className="space-y-3">
<h2 className="text-xl font-semibold">Theme baseline</h2>
<h2 className="text-xl font-semibold">Seed presets</h2>
<ul className="space-y-2 text-sm text-[var(--color-muted-foreground)]">
{themeNames.map((themeName) => (
<li key={themeName} className="flex items-center justify-between gap-4">
+267 -162
View File
@@ -1,159 +1,196 @@
import type { CSSProperties } from "react";
import { useState } from "react";
import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Input,
Skeleton,
Switch,
defaultSkin,
skinDetails,
skinNames,
type SkinName
defaultSkin
} from "@ai-ui/ui";
import {
createDynamicColorVariables,
defaultMotionMode,
defaultTheme,
motionModeDetails,
themeDetails,
themeNames,
type MotionModeName,
type ThemeName
} from "@ai-ui/tokens";
import type { Meta, StoryObj } from "@storybook/react";
type StyleContractShowcaseProps = {
type MaterialRuntimeShowcaseProps = {
motionMode: MotionModeName;
skin: SkinName;
theme: ThemeName;
};
function RuntimeBadge({ label, value }: { label: string; value: string }) {
return (
<div className="rounded-[var(--radius-full)] border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
<div className="rounded-[var(--radius-full)] border border-[var(--color-outline-variant)] bg-[var(--color-surface-container)] px-3 py-2 text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
<span className="mr-2 text-[var(--color-foreground)]">{label}</span>
{value}
</div>
);
}
function SkinPanel({
description,
name
function FloatingNote({
className,
lines,
title
}: {
description: string;
name: SkinName;
className?: string;
lines: string[];
title: string;
}) {
const [enabled, setEnabled] = useState(name !== "minimal");
return (
<article
data-skin={name}
className="grid gap-4 border p-5"
style={{
background: "var(--ui-surface-bg)",
borderColor: "var(--ui-surface-border)",
borderRadius: "var(--ui-surface-radius)",
boxShadow: "var(--ui-surface-shadow)",
backdropFilter: "blur(var(--ui-surface-backdrop-blur))"
}}
className={`grid gap-3 rounded-[1.4rem] border border-white/45 bg-[color-mix(in_oklch,var(--color-surface-container-low)_78%,white_22%)] p-4 shadow-[0_18px_40px_color-mix(in_oklch,var(--color-primary)_12%,transparent)] backdrop-blur-sm ${className ?? ""}`}
>
<div className="flex items-start justify-between gap-4">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
data-skin=&quot;{name}&quot;
</p>
<div>
<h3 className="text-xl font-semibold text-[var(--color-foreground)]">
{skinDetails[name].label}
</h3>
<p className="mt-1 text-sm leading-6 text-[var(--color-muted-foreground)]">
{description}
</p>
</div>
</div>
<span
className="rounded-full border px-3 py-1 text-xs font-medium text-[var(--color-foreground)]"
style={{
background: "var(--ui-control-bg)",
borderColor: "var(--ui-control-border)",
borderRadius: "var(--ui-control-radius)",
boxShadow: "var(--ui-control-shadow)"
}}
>
phase 1
</span>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<div
className="relative overflow-hidden border p-4"
style={{
background: "var(--ui-control-bg)",
borderColor: "var(--ui-control-border)",
borderRadius: "var(--ui-control-radius)",
boxShadow: "var(--ui-control-shadow)"
}}
>
<p className="text-[0.72rem] font-medium uppercase tracking-[0.16em] text-[var(--color-muted-foreground)]">
{title}
</p>
<div className="grid gap-2">
{lines.map((line, index) => (
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-4 top-0 h-12"
style={{
background:
"linear-gradient(135deg, color-mix(in oklch, var(--color-primary) 24%, transparent), transparent)",
mixBlendMode:
name === "glass" ? "screen" : name === "pixel" ? "multiply" : "normal",
opacity: "var(--ui-ornament-opacity)"
}}
key={line}
className="h-2.5 rounded-full bg-[var(--color-foreground)]/10"
style={{ width: `${100 - index * 16}%` }}
/>
<p className="text-sm font-medium text-[var(--color-foreground)]">Surface hooks</p>
<p className="mt-2 text-sm leading-6 text-[var(--color-muted-foreground)]">
This panel reads the new Phase 1 skin variables directly. It is the proof that
root or nested `data-skin` scopes are now a stable runtime contract.
</p>
</div>
<div
className="grid gap-3 border p-4"
style={{
background: "var(--ui-control-bg)",
borderColor: "var(--ui-control-border)",
borderRadius: "var(--ui-control-radius)",
boxShadow: "var(--ui-control-shadow)"
}}
>
<div className="flex items-center justify-between gap-3">
<span className="text-sm font-medium text-[var(--color-foreground)]">
Preview controls
</span>
<Switch
aria-label={`${skinDetails[name].label} preview controls`}
checked={enabled}
onCheckedChange={setEnabled}
/>
</div>
<Input
aria-label={`${name} skin search`}
defaultValue={skinDetails[name].note}
readOnly
/>
<Button variant={name === "glass" ? "secondary" : "primary"}>
Same API, future skin target
</Button>
<Skeleton shape="line" />
</div>
))}
</div>
</article>
);
}
function StyleContractShowcase({
motionMode,
skin,
theme
}: StyleContractShowcaseProps) {
function ShowcasePhone({
accentClassName,
className,
eyebrow,
title
}: {
accentClassName: string;
className?: string;
eyebrow: string;
title: string;
}) {
return (
<article
className={`relative grid aspect-[0.53] w-[13.5rem] overflow-hidden rounded-[2.4rem] border border-white/50 bg-[color-mix(in_oklch,var(--color-surface-container-low)_82%,white_18%)] p-3 shadow-[0_28px_80px_color-mix(in_oklch,var(--color-primary)_14%,transparent)] ${className ?? ""}`}
>
<div className="flex items-center justify-between px-1 text-[0.6rem] font-medium text-[var(--color-muted-foreground)]">
<span>9:30</span>
<div className="flex items-center gap-1">
<span className="h-1.5 w-1.5 rounded-full bg-[var(--color-foreground)]/70" />
<span className="h-1.5 w-4 rounded-full bg-[var(--color-foreground)]/50" />
</div>
</div>
<div className="mt-3 grid gap-3">
<div
className={`rounded-[1.8rem] p-5 text-[var(--color-foreground)] shadow-[inset_0_1px_0_rgba(255,255,255,0.45)] ${accentClassName}`}
>
<p className="text-[0.7rem] uppercase tracking-[0.16em] text-[var(--color-foreground)]/62">
{eyebrow}
</p>
<h3 className="mt-3 text-[2rem] font-semibold leading-[0.95] tracking-[-0.04em]">
{title}
</h3>
</div>
<div className="grid gap-2 rounded-[1.4rem] bg-[color-mix(in_oklch,var(--color-surface-container)_88%,white_12%)] p-3">
<div className="flex items-center gap-2">
<span className="size-8 rounded-[1rem] bg-[var(--color-tertiary-container)] motion-breathe" />
<div className="grid gap-1">
<span className="h-2.5 w-24 rounded-full bg-[var(--color-foreground)]/16" />
<span className="h-2.5 w-16 rounded-full bg-[var(--color-foreground)]/10" />
</div>
</div>
<div className="flex gap-2">
<span className="h-9 flex-1 rounded-[1.1rem] bg-[var(--color-primary-container)]/78" />
<span className="h-9 w-14 rounded-[1.1rem] bg-[var(--color-surface-container-highest)]" />
</div>
</div>
</div>
<div className="mt-auto flex items-center justify-between rounded-[1.2rem] bg-[color-mix(in_oklch,var(--color-surface-container)_72%,white_28%)] px-4 py-3 text-[0.72rem] text-[var(--color-muted-foreground)]">
<span>Home</span>
<span className="rounded-full bg-[var(--color-primary-container)] px-2 py-1 text-[var(--color-on-primary-container)]">
Flow
</span>
</div>
</article>
);
}
function SeedPanel({ name }: { name: ThemeName }) {
const [enabled, setEnabled] = useState(name !== "sunset");
const theme = themeDetails[name];
return (
<article
style={createDynamicColorVariables(theme.seed) as CSSProperties}
className="grid gap-4 rounded-[var(--radius-lg)] border border-[var(--color-outline-variant)] bg-[var(--color-surface-container-low)] p-5 text-[var(--color-foreground)] shadow-[var(--shadow-sm)]"
>
<div className="flex items-start justify-between gap-4">
<div className="space-y-2">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
{name}
</p>
<div>
<h3 className="text-xl font-semibold">{theme.label}</h3>
<p className="mt-1 text-sm leading-6 text-[var(--color-muted-foreground)]">
{theme.note}
</p>
</div>
</div>
<span className="rounded-[var(--radius-full)] bg-[var(--color-secondary-container)] px-3 py-1 text-xs font-medium text-[var(--color-on-secondary-container)]">
{theme.seed}
</span>
</div>
<Card tone="accent">
<CardHeader>
<CardTitle>One style, many palettes</CardTitle>
<CardDescription>
The palette changes, but the component language stays recognizably Material.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-3 sm:grid-cols-2">
<Input aria-label={`${name} preset input`} defaultValue="team@cadence.dev" />
<div className="flex items-center justify-between rounded-[var(--radius-md)] bg-[var(--color-surface-container)] px-4 py-3">
<span className="text-sm font-medium text-[var(--color-foreground)]">
Tonal preference
</span>
<Switch checked={enabled} onCheckedChange={setEnabled} />
</div>
</div>
<div className="flex flex-wrap gap-3">
<Button>Filled action</Button>
<Button variant="secondary">Tonal action</Button>
<Button variant="subtle">Surface action</Button>
<Button variant="ghost">Text action</Button>
</div>
<Skeleton shape="line" />
</CardContent>
</Card>
</article>
);
}
function MaterialRuntimeShowcase({ motionMode, theme }: MaterialRuntimeShowcaseProps) {
return (
<div className="min-h-screen bg-[var(--color-background)] px-6 py-10 text-[var(--color-foreground)] sm:px-10">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-8">
<header className="max-w-4xl space-y-4">
<p className="text-sm uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
AI UI / Phase 1
AI UI / Material Runtime
</p>
<h1
className="font-semibold tracking-[var(--tracking-tight)]"
@@ -163,35 +200,107 @@ function StyleContractShowcase({
lineHeight: "var(--leading-tight)"
}}
>
Runtime skin switching is now a first-class docs contract, even before
component recipes are extracted.
Cadence UI now treats Material as the system language, with dynamic seed color
and one consistent motion baseline.
</h1>
<p className="max-w-3xl text-[var(--text-lg)] leading-[var(--leading-loose)] text-[var(--color-muted-foreground)]">
Phase 1 introduces `data-skin`, root helpers, Storybook toolbar wiring, and a
dedicated skin CSS entrypoint. Phase 2 will move component recipes onto this
contract.
The old multi-skin showcase has been collapsed into a single rounded, tonal
component system. Personalization now comes from seed color rather than
competing style packs.
</p>
</header>
<section className="relative overflow-hidden rounded-[2rem] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface)_82%,white_18%),color-mix(in_oklch,var(--color-background)_86%,white_14%))] px-6 py-8 shadow-[0_28px_80px_color-mix(in_oklch,var(--color-primary)_10%,transparent)] sm:px-8 lg:px-10">
<div className="pointer-events-none absolute inset-0">
<div className="motion-drift absolute left-[-4rem] top-[-3rem] h-40 w-40 rounded-full bg-[color-mix(in_oklch,var(--color-primary-container)_66%,transparent)] blur-3xl" />
<div className="motion-breathe absolute right-[-3rem] top-10 h-36 w-36 rounded-full bg-[color-mix(in_oklch,var(--color-tertiary-container)_72%,transparent)] blur-3xl" />
<div className="motion-drift absolute bottom-[-4rem] left-1/3 h-44 w-44 rounded-full bg-[color-mix(in_oklch,var(--color-secondary-container)_72%,transparent)] blur-3xl" />
<div className="absolute inset-x-10 bottom-0 h-24 rounded-[2rem_2rem_0_0] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface-container)_40%,transparent),color-mix(in_oklch,var(--color-surface-container-high)_88%,white_12%))] blur-xl" />
</div>
<div className="relative grid gap-8 lg:grid-cols-[minmax(0,1.15fr)_minmax(16rem,0.85fr)] lg:items-center">
<div className="space-y-5">
<div className="inline-flex items-center gap-2 rounded-full bg-[color-mix(in_oklch,var(--color-surface-container)_82%,white_18%)] px-4 py-2 text-sm font-medium text-[var(--color-muted-foreground)] shadow-[var(--shadow-xs)]">
<span className="motion-breathe size-2.5 rounded-full bg-[var(--color-primary)]" />
Material showcase mode
</div>
<div className="max-w-2xl space-y-4">
<h2 className="text-[clamp(2.2rem,5vw,4.6rem)] font-semibold leading-[0.94] tracking-[-0.05em]">
Softer slabs, tinted light, and motion that feels staged instead of flat.
</h2>
<p className="max-w-xl text-base leading-7 text-[var(--color-muted-foreground)] sm:text-lg">
This is the target mood for the system: pastel-tonal, editorial enough to
feel premium, but still obviously usable as a product surface.
</p>
</div>
<div className="flex flex-wrap gap-3">
<Button>Generate tonal palette</Button>
<Button variant="secondary">Preview motion</Button>
<Button variant="ghost">Inspect tokens</Button>
</div>
</div>
<div className="relative mx-auto flex h-[32rem] w-full max-w-[32rem] items-center justify-center">
<div className="absolute inset-x-3 bottom-2 h-12 rounded-[999px] bg-[color-mix(in_oklch,var(--color-primary)_16%,transparent)] blur-2xl" />
<div className="absolute inset-x-6 bottom-0 h-10 rounded-[1.6rem_1.6rem_0.9rem_0.9rem] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface-container)_70%,white_30%),color-mix(in_oklch,var(--color-surface-container-high)_82%,white_18%))] shadow-[inset_0_1px_0_rgba(255,255,255,0.6)]" />
<div className="absolute left-3 top-12 h-[20rem] w-[10.5rem] rounded-[2.6rem] border border-white/40 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface-container)_72%,white_28%),color-mix(in_oklch,var(--color-surface-bright)_88%,white_12%))] shadow-[0_24px_64px_color-mix(in_oklch,var(--color-primary)_10%,transparent)]" />
<div className="absolute right-4 top-8 h-[23rem] w-[11rem] rounded-[2.8rem] border border-white/40 bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface-container-high)_76%,white_24%),color-mix(in_oklch,var(--color-surface-bright)_90%,white_10%))] shadow-[0_26px_70px_color-mix(in_oklch,var(--color-tertiary)_10%,transparent)]" />
<ShowcasePhone
accentClassName="bg-[linear-gradient(160deg,color-mix(in_oklch,var(--color-primary-container)_88%,white_12%),color-mix(in_oklch,var(--color-secondary-container)_82%,white_18%))]"
className="motion-float absolute left-0 top-10 -rotate-[10deg]"
eyebrow="Expressive type"
title="Move with tonal depth"
/>
<ShowcasePhone
accentClassName="bg-[linear-gradient(165deg,color-mix(in_oklch,var(--color-tertiary-container)_84%,white_16%),color-mix(in_oklch,var(--color-primary-container)_54%,white_46%))]"
className="motion-float-delayed relative z-10"
eyebrow="Dynamic color"
title="Palette from one seed"
/>
<ShowcasePhone
accentClassName="bg-[linear-gradient(160deg,color-mix(in_oklch,var(--color-secondary-container)_86%,white_14%),color-mix(in_oklch,var(--color-surface-container-highest)_64%,white_36%))]"
className="motion-float absolute right-0 top-14 rotate-[11deg]"
eyebrow="Calm feedback"
title="Motion with restraint"
/>
<FloatingNote
className="motion-float-delayed absolute left-2 top-3 z-20 w-36"
lines={["", "", ""]}
title="Launch deck"
/>
<FloatingNote
className="motion-float absolute bottom-16 right-4 z-20 w-40"
lines={["", "", "", ""]}
title="Material pulse"
/>
<div className="motion-breathe absolute left-[42%] top-8 z-20 rounded-full border border-white/50 bg-[color-mix(in_oklch,var(--color-primary-container)_72%,white_28%)] px-3 py-2 text-xs font-medium text-[var(--color-on-primary-container)] shadow-[0_12px_26px_color-mix(in_oklch,var(--color-primary)_10%,transparent)]">
Dynamic color
</div>
</div>
</div>
</section>
<section className="flex flex-wrap gap-3">
<RuntimeBadge label="theme" value={theme} />
<RuntimeBadge label="skin" value={skin} />
<RuntimeBadge label="motion" value={motionMode} />
<RuntimeBadge label="theme" value={themeDetails[theme].label} />
<RuntimeBadge label="motion" value={motionModeDetails[motionMode].label} />
<RuntimeBadge label="skin" value={defaultSkin} />
</section>
<section className="grid gap-4 lg:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]">
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
<h2 className="text-2xl font-semibold">What Phase 1 includes</h2>
<section className="grid gap-4 lg:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)]">
<article className="rounded-[var(--radius-lg)] border border-[var(--color-outline-variant)] bg-[var(--color-surface-container-low)] p-6 shadow-[var(--shadow-sm)]">
<h2 className="text-2xl font-semibold">Runtime contract</h2>
<div className="mt-5 grid gap-3">
{[
"A new runtime attribute: `data-skin`",
"Public helpers from `@ai-ui/ui` for skin names, defaults, and root updates",
"A dedicated `@ai-ui/ui/skins.css` entrypoint imported by the docs app",
"Storybook globals that apply theme, skin, and interactive/static motion mode together"
"`setTheme(preset)` applies a named seed preset for docs and common app defaults.",
"`setDynamicColor(seed)` generates a full tonal palette from one color.",
"`setSkin(\"material\")` remains as the stable UI runtime marker, but no longer branches into multiple aesthetics.",
"`setMotionMode(mode)` keeps one default motion language plus a static accessibility override."
].map((item) => (
<div
key={item}
className="rounded-[var(--radius-sm)] border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-3"
className="rounded-[var(--radius-sm)] border border-[var(--color-outline-variant)] bg-[var(--color-surface-container)] px-4 py-3"
>
<p className="text-sm leading-6 text-[var(--color-foreground)]">{item}</p>
</div>
@@ -199,18 +308,18 @@ function StyleContractShowcase({
</div>
</article>
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
<h2 className="text-2xl font-semibold">What still waits for Phase 2</h2>
<article className="rounded-[var(--radius-lg)] border border-[var(--color-outline-variant)] bg-[var(--color-surface-container-low)] p-6 shadow-[var(--shadow-sm)]">
<h2 className="text-2xl font-semibold">Material priorities</h2>
<div className="mt-5 grid gap-3">
{[
"Button, card, input, dialog, switch, and skeleton recipe extraction",
"Skin-specific component semantic variables such as `--button-*` and `--panel-*`",
"A docs comparison page where existing components fully restyle under each skin",
"Consumer-facing polish after the runtime contract and docs surface are stable"
"Dynamic color replaces fixed stylistic theme packs.",
"Tonal surfaces replace decorative gradients, blur, and ornamental skins.",
"Large radii and softer outlines create warmth without losing system discipline.",
"Motion stays predictable: expressive enough to communicate, restrained enough to stay calm."
].map((item) => (
<div
key={item}
className="rounded-[var(--radius-sm)] border border-[var(--color-border)] bg-[var(--color-background)] px-4 py-3"
className="rounded-[var(--radius-sm)] border border-[var(--color-outline-variant)] bg-[var(--color-surface-container)] px-4 py-3"
>
<p className="text-sm leading-6 text-[var(--color-foreground)]">{item}</p>
</div>
@@ -219,13 +328,9 @@ function StyleContractShowcase({
</article>
</section>
<section className="grid gap-4">
{skinNames.map((name) => (
<SkinPanel
key={name}
description={skinDetails[name].note}
name={name}
/>
<section className="grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)_minmax(0,1fr)]">
{themeNames.map((name) => (
<SeedPanel key={name} name={name} />
))}
</section>
</div>
@@ -234,34 +339,34 @@ function StyleContractShowcase({
}
const meta = {
title: "Foundation/Style Contract",
component: StyleContractShowcase,
args: {
motionMode: defaultMotionMode,
skin: defaultSkin,
theme: defaultTheme
},
title: "Foundation/Material Runtime",
component: MaterialRuntimeShowcase,
parameters: {
docs: {
description: {
component:
"Phase 1 adds the runtime style contract. Use the Storybook toolbar to switch the active `theme`, `skin`, and `motion` mode globally, or inspect the side-by-side nested `data-skin` panels below."
"Use this page to review the new Material-centric runtime contract: one visual language, one motion baseline, and dynamic palette generation from a seed color."
}
}
},
render: (_args, context) => (
<StyleContractShowcase
motionMode={
(context.globals.motionMode as MotionModeName | undefined) ?? defaultMotionMode
}
skin={(context.globals.skin as SkinName | undefined) ?? defaultSkin}
theme={(context.globals.theme as ThemeName | undefined) ?? defaultTheme}
/>
)
} satisfies Meta<typeof StyleContractShowcase>;
},
layout: "fullscreen"
}
} satisfies Meta<typeof MaterialRuntimeShowcase>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Overview: Story = {};
export const Overview: Story = {
args: {
motionMode: defaultMotionMode,
theme: defaultTheme
},
render: (_args, context) => (
<MaterialRuntimeShowcase
motionMode={
(context.globals.motionMode as MotionModeName | undefined) ?? defaultMotionMode
}
theme={(context.globals.theme as ThemeName | undefined) ?? defaultTheme}
/>
)
};
+163 -170
View File
@@ -1,8 +1,5 @@
import {
motionModeDetails,
motionModeNames,
type MotionModeName
} from "@ai-ui/tokens";
import type { CSSProperties } from "react";
import {
Button,
Card,
@@ -10,162 +7,152 @@ import {
CardDescription,
CardHeader,
CardTitle,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
Input,
Skeleton,
Switch,
skinDetails,
skinNames,
type SkinName
EmptyState,
EmptyStateActions,
EmptyStateDescription,
EmptyStateHeader,
EmptyStateTitle,
Input
} from "@ai-ui/ui";
import {
createDynamicColorVariables,
motionModeDetails,
motionModeNames,
themeDetails,
themeNames
} from "@ai-ui/tokens";
import type { Meta, StoryObj } from "@storybook/react";
function ClosePreviewIcon() {
function MiniPhone({
className,
title
}: {
className?: string;
title: string;
}) {
return (
<svg aria-hidden="true" className="size-4" fill="none" viewBox="0 0 16 16">
<path
d="m4.5 4.5 7 7m0-7-7 7"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.75"
/>
</svg>
<article
className={`grid aspect-[0.56] w-[10.5rem] overflow-hidden rounded-[2rem] border border-white/40 bg-[color-mix(in_oklch,var(--color-surface-container-low)_82%,white_18%)] p-3 shadow-[0_22px_58px_color-mix(in_oklch,var(--color-primary)_10%,transparent)] ${className ?? ""}`}
>
<div className="flex items-center justify-between text-[0.58rem] font-medium text-[var(--color-muted-foreground)]">
<span>9:30</span>
<span className="h-1.5 w-5 rounded-full bg-[var(--color-foreground)]/45" />
</div>
<div className="mt-3 grid gap-3">
<div className="rounded-[1.4rem] bg-[linear-gradient(160deg,color-mix(in_oklch,var(--color-primary-container)_74%,white_26%),color-mix(in_oklch,var(--color-tertiary-container)_82%,white_18%))] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.42)]">
<p className="text-[0.66rem] uppercase tracking-[0.14em] text-[var(--color-foreground)]/60">
M3 panel
</p>
<h3 className="mt-3 text-[1.3rem] font-semibold leading-[0.96] tracking-[-0.04em] text-[var(--color-foreground)]">
{title}
</h3>
</div>
<div className="grid gap-2 rounded-[1.2rem] bg-[color-mix(in_oklch,var(--color-surface-container)_86%,white_14%)] p-3">
<span className="h-8 rounded-[1rem] bg-[var(--color-surface-container-highest)]" />
<span className="h-8 rounded-[1rem] bg-[var(--color-secondary-container)]" />
</div>
</div>
<div className="mt-auto flex items-center justify-between rounded-[1rem] bg-[color-mix(in_oklch,var(--color-surface-container)_70%,white_30%)] px-3 py-2 text-[0.64rem] text-[var(--color-muted-foreground)]">
<span>Home</span>
<span>Feed</span>
<span>Save</span>
</div>
</article>
);
}
function RuntimePill({ children }: { children: React.ReactNode }) {
return (
<span className="rounded-full border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1 text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
{children}
</span>
);
}
function SurfaceCluster({
motionMode,
themeName
}: {
motionMode: (typeof motionModeNames)[number];
themeName: (typeof themeNames)[number];
}) {
const theme = themeDetails[themeName];
function PanelPreview() {
return (
<div
className="grid gap-3 border p-4"
style={{
background: "var(--ui-panel-bg)",
borderColor: "var(--ui-panel-border)",
borderRadius: "var(--ui-panel-radius)",
borderWidth: "var(--ui-panel-border-width)",
boxShadow: "var(--ui-panel-shadow)",
backdropFilter: "blur(var(--ui-panel-backdrop-blur))"
}}
<article
data-motion={motionMode}
style={createDynamicColorVariables(theme.seed) as CSSProperties}
className={`grid gap-4 rounded-[var(--radius-lg)] border border-[var(--color-outline-variant)] bg-[var(--color-surface-container-low)] p-5 shadow-[var(--shadow-sm)] ${motionMode === "interactive" ? "motion-float" : ""}`}
>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-sm font-semibold text-[var(--color-foreground)]">
Dialog panel contract
</p>
<p className="mt-1 text-sm leading-6 text-[var(--color-muted-foreground)]">
Panel vars preview the dialog surface without opening an overlay in every
matrix cell.
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
{motionModeDetails[motionMode].label}
</p>
<h3 className="mt-1 text-xl font-semibold text-[var(--color-foreground)]">
{theme.label}
</h3>
</div>
<Button aria-hidden="true" size="icon" tabIndex={-1} variant="ghost">
<ClosePreviewIcon />
</Button>
</div>
<div className="grid gap-2">
<Skeleton shape="line" />
<Skeleton shape="block" />
</div>
</div>
);
}
function ComparisonCell({
motionMode,
skin
}: {
motionMode: MotionModeName;
skin: SkinName;
}) {
return (
<section
className="grid gap-4 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-4 shadow-[var(--shadow-sm)]"
data-motion={motionMode}
data-skin={skin}
>
<div className="flex flex-wrap gap-2">
<RuntimePill>{motionModeDetails[motionMode].label}</RuntimePill>
<RuntimePill>{skinDetails[skin].label}</RuntimePill>
<span className="rounded-[var(--radius-full)] bg-[var(--color-secondary-container)] px-3 py-1 text-xs font-medium text-[var(--color-on-secondary-container)]">
{theme.seed}
</span>
</div>
<Card interactive tone="default">
<div className="relative flex items-center justify-center gap-3 rounded-[1.6rem] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface)_78%,white_22%),color-mix(in_oklch,var(--color-surface-container)_82%,white_18%))] px-3 py-5 shadow-[inset_0_1px_0_rgba(255,255,255,0.4)]">
<div className="absolute left-6 top-4 h-12 w-12 rounded-full bg-[color-mix(in_oklch,var(--color-primary-container)_72%,transparent)] blur-2xl" />
<div className="absolute right-8 bottom-5 h-12 w-12 rounded-full bg-[color-mix(in_oklch,var(--color-tertiary-container)_66%,transparent)] blur-2xl" />
<MiniPhone className={motionMode === "interactive" ? "motion-float -rotate-[8deg]" : "-rotate-[8deg]"} title="Soft motion" />
<MiniPhone className={motionMode === "interactive" ? "motion-float-delayed rotate-[7deg]" : "rotate-[7deg]"} title="Tonal lift" />
</div>
<div className="grid gap-3 sm:grid-cols-3">
{[
["Surface", "var(--color-surface)"],
["Container", "var(--color-surface-container)"],
["Highest", "var(--color-surface-container-highest)"]
].map(([label, value]) => (
<div
key={label}
className={`rounded-[var(--radius-md)] border border-[var(--color-outline-variant)] p-4 ${motionMode === "interactive" ? "motion-breathe" : ""}`}
style={{ background: value }}
>
<p className="text-sm font-medium text-[var(--color-foreground)]">{label}</p>
</div>
))}
</div>
<Card tone="default">
<CardHeader>
<CardTitle>Release routing</CardTitle>
<CardTitle>Unified Material surface</CardTitle>
<CardDescription>
The same component tree should now pick up distinct skin treatments.
The palette changes, but density, radius, and tonal layering remain stable.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-3">
<Input
aria-label={`${motionMode} ${skin} release note status`}
defaultValue="Launch notes approved"
readOnly
/>
<div className="flex items-center justify-between gap-3">
<span className="text-sm text-[var(--color-muted-foreground)]">
Quiet notifications
</span>
<Switch
aria-label={`${motionMode} ${skin} quiet notifications`}
checked
/>
</div>
<CardContent className="grid gap-4">
<Input aria-label={`${themeName} input`} defaultValue="Release cadence" />
<div className="flex flex-wrap gap-3">
<Button>Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="subtle">Subtle</Button>
<Button variant="secondary">Tonal</Button>
<Button variant="subtle">Surface</Button>
</div>
</CardContent>
</Card>
<PanelPreview />
</section>
<EmptyState tone="subtle">
<EmptyStateHeader>
<EmptyStateTitle>No alternate skin to choose</EmptyStateTitle>
<EmptyStateDescription>
This matrix now validates one Material language across palettes and motion
modes instead of branching into separate aesthetics.
</EmptyStateDescription>
</EmptyStateHeader>
<EmptyStateActions>
<Button variant="ghost">Review tonal roles</Button>
</EmptyStateActions>
</EmptyState>
</article>
);
}
function MatrixDialogSandbox() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary">Open live dialog preview</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog validation sandbox</DialogTitle>
<DialogDescription>
Use the Storybook toolbar to validate the real overlay under the active theme,
skin, and motion settings.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="ghost">Back</Button>
<Button>Approve</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
function StyleMatrixShowcase() {
function MaterialToneMatrix() {
return (
<div className="min-h-screen bg-[var(--color-background)] px-6 py-10 text-[var(--color-foreground)] sm:px-10">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-8">
<header className="max-w-4xl space-y-4">
<p className="text-sm uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
AI UI / Phase 3
AI UI / Material Tone Matrix
</p>
<h1
className="font-semibold tracking-[var(--tracking-tight)]"
@@ -175,69 +162,75 @@ function StyleMatrixShowcase() {
lineHeight: "var(--leading-tight)"
}}
>
Style matrix compares the same product surface across skin and motion scopes.
Review the same Material component language across seed presets and the
standard versus static motion baselines.
</h1>
<p className="max-w-3xl text-[var(--text-lg)] leading-[var(--leading-loose)] text-[var(--color-muted-foreground)]">
This page is the screenshot-friendly regression target for the pilot skin work.
The grid uses nested `data-skin` and `data-motion` scopes so the same
building blocks can be
reviewed side by side.
This page is now the regression surface for tonal hierarchy. If a preset feels
off, the fix belongs in the token generator, not in a separate skin branch.
</p>
</header>
<section className="grid gap-4">
{motionModeNames.map((motionMode) => (
<div key={motionMode} className="grid gap-4">
<div>
<h2 className="text-2xl font-semibold">{motionModeDetails[motionMode].label}</h2>
<p className="mt-1 max-w-3xl text-sm leading-6 text-[var(--color-muted-foreground)]">
{motionModeDetails[motionMode].note}
</p>
</div>
<div className="grid gap-4 xl:grid-cols-3">
{skinNames.map((skin) => (
<ComparisonCell
key={`${motionMode}-${skin}`}
motionMode={motionMode}
skin={skin}
/>
))}
</div>
<section className="relative overflow-hidden rounded-[2rem] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface)_84%,white_16%),color-mix(in_oklch,var(--color-background)_88%,white_12%))] px-6 py-7 shadow-[0_24px_72px_color-mix(in_oklch,var(--color-primary)_10%,transparent)] sm:px-8">
<div className="pointer-events-none absolute inset-0">
<div className="motion-drift absolute left-10 top-0 h-28 w-28 rounded-full bg-[color-mix(in_oklch,var(--color-primary-container)_60%,transparent)] blur-3xl" />
<div className="motion-drift absolute right-12 top-8 h-24 w-24 rounded-full bg-[color-mix(in_oklch,var(--color-tertiary-container)_56%,transparent)] blur-3xl" />
</div>
<div className="relative flex flex-wrap items-center justify-between gap-4">
<div>
<p className="text-sm uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Tonal regression rig
</p>
<h2 className="mt-2 text-3xl font-semibold tracking-[var(--tracking-tight)]">
Interactive mode should feel alive. Static mode should still feel expensive.
</h2>
</div>
))}
<div className="flex flex-wrap gap-3">
<Button>Interactive baseline</Button>
<Button variant="secondary">Static fallback</Button>
</div>
</div>
</section>
<section className="grid gap-4 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)] lg:grid-cols-[minmax(0,1fr)_auto] lg:items-center">
<div>
<h2 className="text-2xl font-semibold">Live overlay validation</h2>
<p className="mt-2 max-w-3xl text-sm leading-6 text-[var(--color-muted-foreground)]">
Dialog still portals to the document root, so compare its real overlay and
panel treatment with the Storybook toolbar. The matrix above covers scoped
inline regression across interactive and static motion modes. The control below
covers the live overlay behavior.
</p>
</div>
<div className="flex justify-start lg:justify-end">
<MatrixDialogSandbox />
</div>
</section>
{motionModeNames.map((motionMode) => (
<section key={motionMode} className="grid gap-4">
<div>
<h2 className="text-2xl font-semibold">
{motionModeDetails[motionMode].label}
</h2>
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
{motionModeDetails[motionMode].note}
</p>
</div>
<div className="grid gap-4 xl:grid-cols-3">
{themeNames.map((themeName) => (
<SurfaceCluster
key={`${motionMode}-${themeName}`}
motionMode={motionMode}
themeName={themeName}
/>
))}
</div>
</section>
))}
</div>
</div>
);
}
const meta = {
title: "Foundation/Style Matrix",
component: StyleMatrixShowcase,
title: "Foundation/Material Tone Matrix",
component: MaterialToneMatrix,
parameters: {
docs: {
description: {
component:
"Phase 3 adds the regression-oriented comparison surface. Use this page for screenshots and visual review, then use the live dialog sandbox below to validate portal-driven overlays under the active toolbar settings."
"A regression surface for checking that seed presets and reduced/static motion still read as one coherent Material system."
}
}
},
layout: "fullscreen"
}
} satisfies Meta<typeof StyleMatrixShowcase>;
} satisfies Meta<typeof MaterialToneMatrix>;
export default meta;
+11 -9
View File
@@ -1,5 +1,6 @@
import {
colorTokens,
createDynamicColorVariables,
defaultTheme,
defaultMotionMode,
motionTokens,
@@ -13,6 +14,7 @@ import {
type ThemeName
} from "@ai-ui/tokens";
import type { Meta, StoryObj } from "@storybook/react";
import type { CSSProperties } from "react";
type TokensOverviewProps = {
motionMode: MotionModeName;
@@ -63,8 +65,8 @@ function ThemeCard({ themeName }: { themeName: ThemeName }) {
return (
<article
data-theme={themeName}
className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-background)] p-5 text-[var(--color-foreground)] shadow-[var(--shadow-sm)]"
style={createDynamicColorVariables(theme.seed) as CSSProperties}
>
<div className="flex items-start justify-between gap-4">
<div>
@@ -143,19 +145,19 @@ function TokensOverview({
}}
>
The first stable token layer defines color, type, surface depth, and
motion rhythm.
motion rhythm around a Material You style system.
</h1>
<p className="max-w-3xl text-[var(--text-lg)] leading-[var(--leading-loose)] text-[var(--color-muted-foreground)]">
Theme switching now happens at the token layer, not inside component
implementations. Motion is also represented as named tokens and starter
recipes rather than ad hoc transition values.
Seed color now drives the palette. Components inherit tonal surfaces and
emphasis roles from the token layer instead of shipping disconnected visual
skins.
</p>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<div className="rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-card)] px-4 py-3 shadow-[var(--shadow-xs)]">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Active Theme
Active Seed Preset
</p>
<p className="mt-2 text-sm font-medium text-[var(--color-foreground)]">
{themeDetails[theme].label}
@@ -175,10 +177,10 @@ function TokensOverview({
<section className="space-y-4">
<div className="flex items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-semibold">Theme scaffolds</h2>
<h2 className="text-2xl font-semibold">Seed presets</h2>
<p className="mt-1 text-sm text-[var(--color-muted-foreground)]">
These cards render their own nested theme roots, so tokens can be
validated side by side without touching component code.
These cards render their own seed-derived palettes, so the tonal system
can be reviewed side by side without changing component code.
</p>
</div>
</div>