diff --git a/frontend/index.html b/frontend/index.html
index 1a13c4f..567778d 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -3,6 +3,9 @@
+
+
+
ecomm
diff --git a/frontend/public/brand/mark-mono-gold.svg b/frontend/public/brand/mark-mono-gold.svg
new file mode 100644
index 0000000..cc7fde2
--- /dev/null
+++ b/frontend/public/brand/mark-mono-gold.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/frontend/public/brand/mark-tile.svg b/frontend/public/brand/mark-tile.svg
new file mode 100644
index 0000000..51d6c69
--- /dev/null
+++ b/frontend/public/brand/mark-tile.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/frontend/public/fonts/inter-v20-latin-500.woff2 b/frontend/public/fonts/inter-v20-latin-500.woff2
new file mode 100644
index 0000000..54f0a59
Binary files /dev/null and b/frontend/public/fonts/inter-v20-latin-500.woff2 differ
diff --git a/frontend/public/fonts/inter-v20-latin-600.woff2 b/frontend/public/fonts/inter-v20-latin-600.woff2
new file mode 100644
index 0000000..d189794
Binary files /dev/null and b/frontend/public/fonts/inter-v20-latin-600.woff2 differ
diff --git a/frontend/public/fonts/inter-v20-latin-regular.woff2 b/frontend/public/fonts/inter-v20-latin-regular.woff2
new file mode 100644
index 0000000..f15b025
Binary files /dev/null and b/frontend/public/fonts/inter-v20-latin-regular.woff2 differ
diff --git a/frontend/public/fonts/space-grotesk-v22-latin-500.woff2 b/frontend/public/fonts/space-grotesk-v22-latin-500.woff2
new file mode 100644
index 0000000..0db251f
Binary files /dev/null and b/frontend/public/fonts/space-grotesk-v22-latin-500.woff2 differ
diff --git a/frontend/public/fonts/space-grotesk-v22-latin-700.woff2 b/frontend/public/fonts/space-grotesk-v22-latin-700.woff2
new file mode 100644
index 0000000..44604a0
Binary files /dev/null and b/frontend/public/fonts/space-grotesk-v22-latin-700.woff2 differ
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index f8fc6f5..0b18e9d 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,3 +1,4 @@
+import "./styles/index.css";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css
new file mode 100644
index 0000000..683dd53
--- /dev/null
+++ b/frontend/src/styles/app.css
@@ -0,0 +1,394 @@
+/* ecomm app chrome — the hf-kit primitives (Direction A · Quiet centered) as CSS.
+ Depth = layered surfaces + hairlines; hover = small lift, no new shadows. */
+
+* { box-sizing: border-box; }
+
+html, body, #root { height: 100%; }
+
+body {
+ margin: 0;
+ background: var(--wv-midnight);
+ color: var(--wv-starlight);
+ font-family: var(--wv-font-body);
+ -webkit-font-smoothing: antialiased;
+}
+
+/* ── screen ground: barely-there starfield + optional gold horizon ─────────── */
+.screen {
+ min-height: 100%;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ background:
+ radial-gradient(1.3px 1.3px at 16% 22%, rgba(237, 234, 255, .34), transparent),
+ radial-gradient(1.2px 1.2px at 78% 14%, rgba(237, 234, 255, .22), transparent),
+ radial-gradient(1.1px 1.1px at 88% 46%, rgba(155, 140, 255, .30), transparent),
+ radial-gradient(1.3px 1.3px at 30% 72%, rgba(237, 234, 255, .24), transparent),
+ radial-gradient(1.1px 1.1px at 62% 86%, rgba(237, 234, 255, .18), transparent),
+ radial-gradient(1.2px 1.2px at 7% 56%, rgba(155, 140, 255, .26), transparent),
+ var(--wv-midnight);
+}
+.screen--horizon {
+ background:
+ radial-gradient(120% 70% at 50% 142%, rgba(244, 199, 107, .16) 0%, rgba(244, 199, 107, .05) 38%, transparent 64%),
+ radial-gradient(1.3px 1.3px at 16% 22%, rgba(237, 234, 255, .34), transparent),
+ radial-gradient(1.2px 1.2px at 78% 14%, rgba(237, 234, 255, .22), transparent),
+ radial-gradient(1.1px 1.1px at 88% 46%, rgba(155, 140, 255, .30), transparent),
+ radial-gradient(1.3px 1.3px at 30% 72%, rgba(237, 234, 255, .24), transparent),
+ radial-gradient(1.1px 1.1px at 62% 86%, rgba(237, 234, 255, .18), transparent),
+ radial-gradient(1.2px 1.2px at 7% 56%, rgba(155, 140, 255, .26), transparent),
+ var(--wv-midnight);
+}
+.screen--plain { background: var(--wv-midnight); }
+
+.screen__main {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 32px 24px;
+}
+
+/* ── top bar: glass chrome ──────────────────────────────────────────────────── */
+.topbar {
+ flex: 0 0 auto;
+ height: 68px;
+ padding: 0 36px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ border-bottom: 1px solid var(--border-soft);
+ background: var(--glass-sky);
+ backdrop-filter: blur(var(--glass-blur));
+ position: relative;
+ z-index: 5;
+}
+.topbar__side { display: flex; align-items: center; gap: 16px; min-width: 0; }
+
+/* ── wordmark ───────────────────────────────────────────────────────────────── */
+.wordmark { display: inline-flex; align-items: center; gap: 10px; }
+.wordmark img { display: block; border-radius: 6px; }
+.wordmark__name {
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-bold);
+ font-size: 22px;
+ letter-spacing: var(--tracking-display);
+ color: var(--wv-starlight);
+}
+
+/* ── links & text buttons ───────────────────────────────────────────────────── */
+.lk {
+ color: var(--wv-lilac);
+ text-decoration: none;
+ background: none;
+ border: none;
+ padding: 0;
+ font: inherit;
+ cursor: pointer;
+ transition: color var(--dur-fast) var(--ease);
+}
+.lk:hover { color: var(--wv-gold); }
+.lk--mute { color: var(--text-on-dark-mute); }
+.lk--nav {
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-medium);
+ font-size: 14px;
+ color: var(--wv-starlight);
+}
+
+.signout {
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-medium);
+ font-size: 14px;
+ color: var(--wv-starlight);
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ transition: color var(--dur-fast) var(--ease);
+}
+.signout:hover { color: var(--wv-gold); }
+
+/* ── account chip (top-bar right, signed in) ────────────────────────────────── */
+.chip { display: flex; align-items: center; gap: 14px; min-width: 0; }
+.chip__avatar {
+ width: 30px;
+ height: 30px;
+ border-radius: 15px;
+ background: var(--wv-lilac-16);
+ border: 1px solid var(--border-card);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-bold);
+ font-size: 13px;
+ flex: 0 0 auto;
+}
+.chip__email {
+ font-size: 14px;
+ color: var(--text-on-dark-soft);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.chip__divider { width: 1px; height: 18px; background: var(--border-soft); flex: 0 0 auto; }
+
+/* ── eyebrow ────────────────────────────────────────────────────────────────── */
+.eyebrow {
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-medium);
+ font-size: var(--text-eyebrow);
+ letter-spacing: var(--tracking-eyebrow);
+ text-transform: uppercase;
+ color: var(--wv-lilac);
+ margin: 0;
+}
+
+/* ── auth card — the one surface with a real shadow ─────────────────────────── */
+.card {
+ width: 440px;
+ max-width: 100%;
+ background: var(--surface-raised);
+ border: 1px solid var(--border-card);
+ border-radius: var(--radius-card);
+ padding: 40px 40px 34px;
+ display: flex;
+ flex-direction: column;
+ gap: 22px;
+ box-shadow: var(--shadow-soft);
+ position: relative;
+ z-index: 2;
+}
+.card h1 {
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-bold);
+ letter-spacing: var(--tracking-display);
+ font-size: 27px;
+ line-height: 1.08;
+ margin: 0 0 9px;
+}
+.card__sub { font-size: 14.5px; line-height: 1.55; color: var(--text-on-dark-soft); margin: 0; }
+
+/* ── fields ─────────────────────────────────────────────────────────────────── */
+.field { display: flex; flex-direction: column; gap: 8px; }
+.field__label {
+ font-weight: var(--weight-medium);
+ font-size: 13.5px;
+ color: var(--text-on-dark-soft);
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+}
+.field__optional { font-size: 12.5px; color: var(--text-on-dark-mute); font-weight: var(--weight-regular); }
+.field__input {
+ height: 52px;
+ border-radius: 10px;
+ padding: 0 16px;
+ background: rgba(237, 234, 255, .035);
+ border: 1.5px solid var(--border-soft);
+ font-family: var(--wv-font-body);
+ font-size: 16px;
+ color: var(--wv-starlight);
+ width: 100%;
+ transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
+}
+.field__input::placeholder { color: var(--text-on-dark-mute); }
+.field__input:hover { border-color: var(--border-strong); }
+.field__input:focus {
+ outline: none;
+ border-color: var(--wv-gold);
+ box-shadow: 0 0 0 3px var(--wv-gold-28);
+ background: rgba(237, 234, 255, .06);
+}
+.field__input--error { border-color: var(--wv-gold); }
+
+.note { font-size: 13px; line-height: 1.45; color: var(--text-on-dark-mute); margin: 0; }
+.note--attn { color: var(--wv-gold); display: flex; gap: 7px; align-items: baseline; }
+.note--attn::before { content: "◆"; font-size: 11px; }
+
+/* ── primary button: gold pill ──────────────────────────────────────────────── */
+.btn-primary {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: .4em;
+ width: 100%;
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-medium);
+ font-size: 15.5px;
+ line-height: 1;
+ padding: .85rem 1.4rem;
+ border-radius: var(--radius-pill);
+ border: var(--btn-border-w) solid transparent;
+ background: var(--cta);
+ color: var(--cta-text);
+ cursor: pointer;
+ transition: transform var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
+}
+.btn-primary:hover:not(:disabled) { background: var(--cta-hover); transform: var(--lift-1); }
+.btn-primary:disabled { opacity: .5; cursor: not-allowed; }
+.btn-primary--auto { width: auto; }
+.btn-primary:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
+
+/* ── banner: gold attention / lilac info (no red in the palette) ────────────── */
+.banner {
+ display: flex;
+ gap: 12px;
+ padding: 14px 16px;
+ border-radius: 12px;
+ align-items: flex-start;
+ font-size: 14px;
+ line-height: 1.5;
+ color: var(--text-on-dark-soft);
+ text-align: left;
+}
+.banner--attn { background: rgba(244, 199, 107, .10); border: 1px solid var(--wv-gold-40); }
+.banner--info { background: var(--wv-lilac-08); border: 1px solid var(--wv-lilac-18); }
+.banner__icon { font-size: 13px; line-height: 22px; flex: 0 0 auto; }
+.banner--attn .banner__icon { color: var(--wv-gold); }
+.banner--info .banner__icon { color: var(--wv-lilac); }
+.banner__title { color: var(--wv-starlight); font-weight: var(--weight-semibold); margin-bottom: 2px; }
+
+/* ── code input: 6 cells over one invisible input ───────────────────────────── */
+.code { position: relative; display: flex; gap: 11px; justify-content: center; }
+.code__hidden {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+ border: none;
+ font-size: 16px;
+ cursor: pointer;
+}
+.code__cell {
+ width: 52px;
+ height: 64px;
+ border-radius: 11px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(237, 234, 255, .035);
+ border: 1.5px solid var(--border-soft);
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-medium);
+ font-size: 26px;
+ transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
+}
+.code--focus .code__cell--active {
+ border-color: var(--wv-gold);
+ box-shadow: 0 0 0 3px var(--wv-gold-28);
+}
+.code--error .code__cell { border-color: var(--wv-gold); }
+
+/* ── footer ─────────────────────────────────────────────────────────────────── */
+.footer {
+ flex: 0 0 auto;
+ padding: 20px 36px;
+ border-top: 1px solid var(--border-soft);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+ font-size: 12.5px;
+ color: var(--text-on-dark-mute);
+ background: rgba(9, 12, 34, .4);
+}
+.footer__id { display: inline-flex; align-items: center; gap: 9px; }
+.footer__id img { opacity: .85; }
+
+/* ── landing ────────────────────────────────────────────────────────────────── */
+.hero { max-width: 680px; display: flex; flex-direction: column; align-items: center; text-align: center; }
+.hero h1 {
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-bold);
+ letter-spacing: -0.02em;
+ line-height: 1.02;
+ font-size: clamp(36px, 6.5vw, 62px);
+ margin: 20px 0 22px;
+}
+.hero h1 em { font-style: normal; color: var(--wv-gold); }
+.hero__lead {
+ font-size: clamp(15.5px, 1.8vw, 18.5px);
+ line-height: 1.6;
+ color: var(--text-on-dark-soft);
+ max-width: 520px;
+ margin: 0 0 32px;
+ text-wrap: pretty;
+}
+.hero__actions { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; justify-content: center; }
+.hero__actions .btn-primary { width: auto; }
+
+.promises {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ width: 100%;
+ margin-top: 44px;
+ border-top: 1px solid var(--border-soft);
+ padding-top: 24px;
+ text-align: left;
+}
+.promises > div { padding: 0 22px; }
+.promises > div + div { border-left: 1px solid var(--border-soft); }
+.promises__title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 5px;
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-medium);
+ font-size: 14.5px;
+}
+.promises__title::before { content: "◆"; color: var(--wv-gold); font-size: 10px; }
+.promises p { font-size: 13px; line-height: 1.5; color: var(--text-on-dark-mute); margin: 0; }
+
+/* ── admin shell ────────────────────────────────────────────────────────────── */
+.storeid { display: inline-flex; align-items: center; gap: 11px; min-width: 0; }
+.storeid img { display: block; border-radius: 7px; flex: 0 0 auto; }
+.storeid__col { display: inline-flex; flex-direction: column; line-height: 1.12; min-width: 0; }
+.storeid__name {
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-bold);
+ font-size: 17px;
+ letter-spacing: -0.01em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.storeid__sub { font-size: 11px; color: var(--text-on-dark-mute); }
+
+.empty { display: flex; flex-direction: column; align-items: center; text-align: center; max-width: 560px; }
+.empty__seal {
+ width: 76px;
+ height: 76px;
+ border-radius: 50%;
+ border: 1px solid var(--border-card);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 26px;
+ background: var(--wv-lilac-08);
+}
+.empty h1 {
+ font-family: var(--wv-font-display);
+ font-weight: var(--weight-bold);
+ letter-spacing: var(--tracking-display);
+ line-height: 1.04;
+ font-size: clamp(30px, 4.5vw, 42px);
+ margin: 12px 0 16px;
+}
+.empty__copy { font-size: 17px; line-height: 1.6; color: var(--text-on-dark-soft); max-width: 460px; margin: 0; text-wrap: pretty; }
+
+/* ── small screens ──────────────────────────────────────────────────────────── */
+@media (max-width: 720px) {
+ .topbar { padding: 0 20px; }
+ .footer { padding: 16px 20px; flex-direction: column; }
+ .card { padding: 28px 22px 24px; }
+ .promises { grid-template-columns: 1fr; gap: 14px; }
+ .promises > div { padding: 0; }
+ .promises > div + div { border-left: none; }
+ .code__cell { width: 44px; height: 56px; font-size: 22px; }
+}
diff --git a/frontend/src/styles/fonts.css b/frontend/src/styles/fonts.css
new file mode 100644
index 0000000..56abbf6
--- /dev/null
+++ b/frontend/src/styles/fonts.css
@@ -0,0 +1,37 @@
+/* Wiggleverse webfonts (from the design-system export) — Space Grotesk (display) +
+ Inter (body/UI). Fraunces (human register) deliberately omitted: no screen uses it yet. */
+@font-face {
+ font-family: "Space Grotesk";
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url("/fonts/space-grotesk-v22-latin-500.woff2") format("woff2");
+}
+@font-face {
+ font-family: "Space Grotesk";
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url("/fonts/space-grotesk-v22-latin-700.woff2") format("woff2");
+}
+@font-face {
+ font-family: "Inter";
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url("/fonts/inter-v20-latin-regular.woff2") format("woff2");
+}
+@font-face {
+ font-family: "Inter";
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+ src: url("/fonts/inter-v20-latin-500.woff2") format("woff2");
+}
+@font-face {
+ font-family: "Inter";
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url("/fonts/inter-v20-latin-600.woff2") format("woff2");
+}
diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css
new file mode 100644
index 0000000..d924144
--- /dev/null
+++ b/frontend/src/styles/index.css
@@ -0,0 +1,7 @@
+/* Global style entry point — Wiggleverse design-system tokens (copied from the
+ ui/designs export) + the ecomm app chrome built on them. */
+@import "./fonts.css";
+@import "./tokens-colors.css";
+@import "./tokens-typography.css";
+@import "./tokens-spacing.css";
+@import "./app.css";
diff --git a/frontend/src/styles/tokens-colors.css b/frontend/src/styles/tokens-colors.css
new file mode 100644
index 0000000..57edf8e
--- /dev/null
+++ b/frontend/src/styles/tokens-colors.css
@@ -0,0 +1,60 @@
+/* Wiggleverse — Color tokens
+ Source of truth: wiggleverse-www/assets/tokens.css (brand BRAND.md §8–9).
+ Dark "sky" is the primary ground; Paper is for long reading. No-center motif. */
+
+:root {
+ /* ---- Brand palette (base values) ---- */
+ --wv-midnight: #0E1230; /* sky / ground — primary dark background */
+ --wv-indigo: #1C2150; /* raised surfaces on dark (cards, mobile nav) */
+ --wv-indigo-2: #232A63; /* hover state for raised surfaces */
+ --wv-lilac: #9B8CFF; /* accent — "the bonds between us"; links, nodes */
+ --wv-violet: #7C6FE0; /* secondary links / strokes (on light) */
+ --wv-gold: #F4C76B; /* warmth / horizon / primary CTAs */
+ --wv-gold-hi: #F7D488; /* gold hover */
+ --wv-starlight:#EDEAFF; /* nodes / text on dark */
+ --wv-paper: #F6F4FB; /* light-mode background */
+ --wv-ink: #3B2F7A; /* text on light */
+ --wv-night: #090C22; /* footer / deepest ground */
+
+ /* CTA text-on-gold (very dark gold-brown, not pure black) */
+ --wv-gold-ink: #2A2003;
+ --wv-gold-ink-soft: #6B4E10; /* "soon" tag text on gold tint */
+
+ /* ---- Alpha derivations (lilac / gold / starlight washes) ---- */
+ --wv-lilac-08: rgba(155, 140, 255, .08);
+ --wv-lilac-12: rgba(155, 140, 255, .12);
+ --wv-lilac-16: rgba(155, 140, 255, .16);
+ --wv-lilac-18: rgba(155, 140, 255, .18);
+ --wv-lilac-32: rgba(155, 140, 255, .32);
+ --wv-gold-28: rgba(244, 199, 107, .28);
+ --wv-gold-40: rgba(244, 199, 107, .40);
+ --wv-starlight-85: rgba(237, 234, 255, .85);
+ --wv-starlight-78: rgba(237, 234, 255, .78);
+ --wv-starlight-60: rgba(237, 234, 255, .60);
+ --wv-starlight-55: rgba(237, 234, 255, .55);
+
+ /* ---- Semantic aliases ---- */
+ --surface-sky: var(--wv-midnight); /* page ground (dark) */
+ --surface-raised: var(--wv-indigo); /* cards / panels on dark */
+ --surface-raised-hi: var(--wv-indigo-2); /* raised hover */
+ --surface-paper: var(--wv-paper); /* long-reading light sections */
+ --surface-card-light:#FFFFFF; /* build-cards on paper */
+ --surface-footer: var(--wv-night);
+
+ --text-on-dark: var(--wv-starlight);
+ --text-on-dark-soft: var(--wv-starlight-78);
+ --text-on-dark-mute: var(--wv-starlight-60);
+ --text-on-light: var(--wv-ink);
+ --text-on-light-soft:#4B4170;
+
+ --accent: var(--wv-lilac); /* links + nodes on dark */
+ --accent-on-light: var(--wv-violet); /* links + strokes on light */
+ --cta: var(--wv-gold); /* primary action / horizon */
+ --cta-hover: var(--wv-gold-hi);
+ --cta-text: var(--wv-gold-ink);
+
+ --border-soft: var(--wv-lilac-16); /* hairlines on dark */
+ --border-card: var(--wv-lilac-18);
+ --border-strong: var(--wv-lilac-32);
+ --focus-ring: var(--wv-gold);
+}
diff --git a/frontend/src/styles/tokens-spacing.css b/frontend/src/styles/tokens-spacing.css
new file mode 100644
index 0000000..04a084a
--- /dev/null
+++ b/frontend/src/styles/tokens-spacing.css
@@ -0,0 +1,55 @@
+/* Wiggleverse — Spacing, radius, shadow, layout & motion tokens
+ Derived from the marketing-site CSS. The brand has almost no shadow system —
+ depth is carried by surface color + hairline borders, not drop shadows. */
+
+:root {
+ /* ---- Spacing scale (rem) ---- */
+ --space-0: 0;
+ --space-1: .25rem;
+ --space-2: .5rem;
+ --space-3: .7rem;
+ --space-4: 1rem;
+ --space-5: 1.2rem;
+ --space-6: 1.6rem;
+ --space-8: 2rem;
+ --space-10: 2.5rem;
+ --space-12: 3rem;
+
+ /* Section rhythm — fluid vertical padding for page bands */
+ --section-pad: clamp(3rem, 7vw, 5.5rem);
+ --page-hero-pad: clamp(2.8rem, 6vw, 4.5rem);
+
+ /* ---- Layout ---- */
+ --wrap-max: 1080px; /* content column */
+ --wrap-gutter: 92vw; /* width: min(--wrap-max, --wrap-gutter) */
+ --measure-prose: 68ch; /* reading measure for prose blocks */
+
+ /* ---- Radii ---- */
+ --radius-card: 14px; /* path-cards, build-cards */
+ --radius-panel: 12px; /* principle tiles */
+ --radius-sm: 4px; /* focus ring rounding */
+ --radius-pill: 999px; /* buttons, tags, notices */
+ --radius-mark: 26px; /* favicon tile rounding */
+
+ /* ---- Borders ---- */
+ --border-hair: 1px; /* default hairline */
+ --border-card-w: 1px;
+ --btn-border-w: 1.5px; /* ghost button / focus weight */
+
+ /* ---- Elevation ---- *
+ * The system avoids drop shadows. "Lift" on hover is a -1 to -3px translateY,
+ * not a shadow. These tokens exist for the rare card that needs real elevation. */
+ --shadow-none: none;
+ --shadow-soft: 0 8px 24px rgba(9, 12, 34, .28);
+ --lift-1: translateY(-1px); /* @kind other */ /* buttons */
+ --lift-3: translateY(-3px); /* @kind other */ /* cards */
+
+ /* ---- Glass (sticky header) ---- */
+ --glass-sky: rgba(14, 18, 48, .82);
+ --glass-blur: 10px;
+
+ /* ---- Motion ---- */
+ --ease: ease; /* @kind other */
+ --dur-fast: .15s; /* @kind other */ /* hover / press transitions */
+ --dur-mid: .25s; /* @kind other */ /* mobile nav reveal */
+}
diff --git a/frontend/src/styles/tokens-typography.css b/frontend/src/styles/tokens-typography.css
new file mode 100644
index 0000000..900a7fe
--- /dev/null
+++ b/frontend/src/styles/tokens-typography.css
@@ -0,0 +1,53 @@
+/* Wiggleverse — Typography tokens
+ Two registers, one meaning (BRAND.md):
+ • Machine register — Space Grotesk (display) + Inter (body/UI): precise, structural.
+ • Human register — Fraunces, ITALIC ONLY: warm, literary, used sparingly
+ for the lines that carry conscience ("the soul").
+ @font-face rules live in assets/fonts/fonts.css. */
+
+:root {
+ /* ---- Families ---- */
+ --wv-font-display: 'Space Grotesk', system-ui, sans-serif; /* headings, wordmark */
+ --wv-font-body: 'Inter', system-ui, sans-serif; /* body / UI */
+ --wv-font-human: 'Fraunces', Georgia, serif; /* pull-quotes — italic only */
+
+ /* semantic aliases */
+ --font-display: var(--wv-font-display);
+ --font-body: var(--wv-font-body);
+ --font-soul: var(--wv-font-human);
+
+ /* ---- Weights ---- */
+ --weight-regular: 400; /* Inter body */
+ --weight-medium: 500; /* nav, eyebrows, buttons, Space Grotesk text */
+ --weight-semibold:600; /* Inter emphasis, ledger totals */
+ --weight-bold: 700; /* Space Grotesk headings, wordmark */
+ --weight-soul: 500; /* Fraunces italic pull-quotes */
+
+ /* ---- Fluid display sizes (clamp: min, vw, max) ---- */
+ --text-h1: clamp(2.1rem, 5.2vw, 3.6rem);
+ --text-h2: clamp(1.6rem, 3.4vw, 2.4rem);
+ --text-h3: 1.2rem;
+ --text-lead: clamp(1.05rem, 1.8vw, 1.3rem); /* intro paragraph */
+ --text-soul: clamp(1.2rem, 2.4vw, 1.7rem); /* hero pull-quote */
+
+ /* ---- Body / UI scale ---- */
+ --text-body: 1rem; /* 16px base */
+ --text-small: .95rem;
+ --text-fine: .92rem;
+ --text-eyebrow: .8rem; /* uppercase label */
+ --text-tag: .72rem; /* pill tags */
+
+ /* ---- Line heights ---- */
+ --leading-tight: 1.12; /* headings */
+ --leading-body: 1.6; /* paragraphs */
+
+ /* ---- Letter spacing ---- */
+ --tracking-display: -0.015em; /* headings + wordmark draw in slightly */
+ --tracking-eyebrow: 0.12em; /* uppercase eyebrows open up */
+ --tracking-tag: 0.08em;
+ --tracking-notice: 0.06em;
+
+ /* ---- Measure ---- */
+ --measure-lead: 60ch;
+ --measure-prose:68ch;
+}
diff --git a/frontend/src/ui/CodeInput.tsx b/frontend/src/ui/CodeInput.tsx
new file mode 100644
index 0000000..32934e0
--- /dev/null
+++ b/frontend/src/ui/CodeInput.tsx
@@ -0,0 +1,46 @@
+// Six-cell one-time-code input: one real (invisible, full-bleed) carries the value
+// and the OTC autofill semantics; the cells are presentation. Click anywhere to focus;
+// paste works because the input is real.
+import { useState } from "react";
+
+const LENGTH = 6;
+
+export default function CodeInput({
+ value,
+ onChange,
+ error = false,
+}: {
+ value: string;
+ onChange: (code: string) => void;
+ error?: boolean;
+}) {
+ const [focused, setFocused] = useState(false);
+ const digits = value.slice(0, LENGTH).split("");
+ const active = Math.min(digits.length, LENGTH - 1);
+
+ return (
+
+ setFocused(true)}
+ onBlur={() => setFocused(false)}
+ onChange={(ev) => onChange(ev.target.value.replace(/\D/g, "").slice(0, LENGTH))}
+ />
+ {Array.from({ length: LENGTH }, (_, i) => (
+
+ {digits[i] ?? ""}
+
+ ))}
+
+ );
+}
diff --git a/frontend/src/ui/kit.tsx b/frontend/src/ui/kit.tsx
new file mode 100644
index 0000000..d666764
--- /dev/null
+++ b/frontend/src/ui/kit.tsx
@@ -0,0 +1,147 @@
+// The ecomm UI kit — React faces of the app.css primitives, matching the Claude Design
+// export (ui/designs/ecomm-login-and-create-storefront-designs, hf-kit). Presentation
+// only: no business rules live here (§6.2 — the SPA renders what the BFF returns).
+import type { ReactNode } from "react";
+
+export function Screen({
+ children,
+ horizon = false,
+ plain = false,
+}: {
+ children: ReactNode;
+ horizon?: boolean;
+ plain?: boolean;
+}) {
+ const cls = plain ? "screen screen--plain" : horizon ? "screen screen--horizon" : "screen";
+ return {children}
;
+}
+
+export function TopBar({ left, right }: { left: ReactNode; right?: ReactNode }) {
+ return (
+
+ );
+}
+
+export function Wordmark({ size = 26 }: { size?: number }) {
+ return (
+
+
+
+ ecomm
+
+
+ );
+}
+
+export function Footer() {
+ return (
+
+ );
+}
+
+export function AuthCard({ children }: { children: ReactNode }) {
+ return {children}
;
+}
+
+export function Eyebrow({ children }: { children: ReactNode }) {
+ return {children}
;
+}
+
+export function Banner({
+ tone = "attn",
+ title,
+ children,
+}: {
+ tone?: "attn" | "info";
+ title?: string;
+ children: ReactNode;
+}) {
+ return (
+
+
+ {tone === "attn" ? "◆" : "◇"}
+
+
+ {title &&
{title}
}
+ {children}
+
+
+ );
+}
+
+export function PrimaryButton({
+ children,
+ busy = false,
+ disabled = false,
+ type = "submit",
+ onClick,
+}: {
+ children: ReactNode;
+ busy?: boolean;
+ disabled?: boolean;
+ type?: "submit" | "button";
+ onClick?: () => void;
+}) {
+ return (
+
+ );
+}
+
+export function Field({
+ label,
+ optional = false,
+ error,
+ helper,
+ inputProps,
+}: {
+ label: string;
+ optional?: boolean;
+ error?: string | null;
+ helper?: string;
+ inputProps: React.InputHTMLAttributes;
+}) {
+ return (
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+ {helper && !error &&
{helper}
}
+
+ );
+}
+
+export function AccountChip({ email, onSignOut }: { email: string; onSignOut: () => void }) {
+ return (
+
+
+ {email[0]?.toUpperCase()}
+
+ {email}
+
+
+
+ );
+}