8.3.16 The public design showcase at motir.co (`/design`) — live Style × Palette × Type over the published `@motir/design-system` (motir-marketing)
Build motir.co/design — a PUBLIC page that demonstrates Motir's design ability by letting a visitor switch Style × Palette × Type and watch the whole site restyle live. It ships in motir-marketing, not motir-core.
▶ RE-PLANNED 2026-08-28 (Yue): "this should be implemented in motir.co."
This card was authored on 2026-06-17 as a motir-core surface, when Motir's public front door was motir-core's app/page.tsx. The 8.3 re-plan of 2026-06-30 moved the public marketing surface into the new motir-marketing repo (MOTIR-656) and this card was never swept. Its premise — a public marketing / credibility surface — now resolves to motir.co.
What the re-plan changed: the repo pin (motir-core → motir-marketing), the route (/tokens → /design), the substrate (motir-core's lib/theme/* + components/ui/* → the published @motir/design-system), the PARENT — this card was moved out of 8.8 (launch-readiness polish) into 8.3, the marketing-site story, where the rest of the motir.co work lives — and three deliverables the old card did not carry (a design asset, the nav entrance, the missing typefaces). It also GAINED a design blocker and a copy blocker, which is what the planning-time design gate should have produced in June.
What it did NOT change: motir-core's /tokens stays exactly as it is. It is the internal living spec (docs/DESIGN.md, docs/design-system.md; docs/a11y/shell-audit.md calls it "a dev-only specimen") and it still has real callers — tests/e2e/shell-a11y-tokens.spec.ts, plus app/tokens/ allowlists in tests/theme/shapeSwapLint.test.ts and tests/theme/swapLayerLint.test.ts. Nothing is being migrated and no path is being abandoned, so this card owes no deletion sibling: it adds a NEW public surface on a DIFFERENT host. (Measured: git grep "'/tokens'\|app/tokens" origin/main → 27 hits in motir-core, every one an internal spec, test, lint-allowlist or doc reference.)
Why /design and not /tokens
/tokens would name the same route as motir-core's dev specimen while meaning something else, and on a marketing host it reads as API tokens — a real collision, since app.motir.co/settings/account/tokens is exactly that. /design names what the page is about.
The substrate — already installed, already wired, verified by pulling it
Nothing has to be extracted, packaged or published. motir-marketing's package.json already depends on @motir/design-system@0.1.0 and @motir/brand@0.1.0; app/globals.css already @imports @motir/design-system/theme.css and @sources the package dist (without which Tailwind generates none of the package's classes and the page paints unstyled with no error); app/layout.tsx already runs the package's themeInitScript blocking in <head>.
The published tarball (npm pack @motir/design-system@0.1.0, shasum ed10ed8db66eb09d34d062b0b4bad878c8509fed, pulled anonymously — the consumer's own route) exports everything this page needs: ThemeProvider · useTheme · StylePicker · PalettePicker · TypePicker · ThemeSegmentedControl · AxisField / AxisNote / AxisRadioGroup · StyleVignette · TokensSpecimen · cn · the primitives · and the three registries at 9 styles 11 styles · 10 palettes · 6 type pairings (⚠️ AMENDED 2026-08-29 — STYLE_IDS.length === 11 on the installed package; it is a LAYOUT input, not a trivium) — the same membership as motir-core origin/main, so the showcase is not a stale copy of the system.
How the whole page restyles: ThemeProvider writes data-theme / data-style / data-palette / data-type onto document.documentElement and persists each to localStorage. The attributes therefore drive theme.css's 35 / 109 / 9 23 [data-palette], 112 [data-style] and 9 [data-type] blocks (⚠️ AMENDED 2026-08-29, measured on the pinned 0.1.1: grep -o "\[data-palette=[^]]*\]" theme.css | wc -l) for the entire document — header and footer included — which is exactly the whole-self styling this card is for. It also means the choice PERSISTS across motir.co; the design card decides and draws that (it is drawn as KEPT, with a Reset to default control).
⚠️ The three missing typefaces — a build deliverable, not a nicety
app/layout.tsx loads Inter + Source Serif 4 + JetBrains Mono only, and says so deliberately: "this site has no picker, so it loads the default pairing's three and nothing else." Three of the six pairings need faces that are not loaded, and two of them fail hard rather than degrading, read from the published theme.css:
| pairing | reads | face | today |
|---|---|---|---|
grotesk | var(--font-grotesk-source) — no in-var fallback | Space Grotesk | declaration invalid ⇒ role falls back |
editorial | var(--font-editorial-source) — no in-var fallback | Fraunces | declaration invalid ⇒ role falls back |
mono-technical | var(--font-mono-technical-source, ui-monospace) | IBM Plex Mono | degrades to ui-monospace |
⚠️ The loader variable MUST be the -source name. theme.css declares the ROLE tokens as --font-sans: var(--font-sans-source, …) and the [data-type] blocks re-point the roles; naming a next/font variable --font-sans directly leaves every var(--font-*-source) unresolved and silently disables the whole type axis. app/layout.tsx's own header says this — follow it.
Add the three with preload: false, so the landing's first paint is unchanged: only a visitor who actually picks one of those pairings pays for the face.
Scope BOUNDARY
One repository, one pull request, motir-marketing. It does not touch motir-core — not app/tokens/, not the package source, not theme.css. It ships no new design token and no new primitive: everything it renders comes from @motir/design-system, and where a primitive is missing the answer is to compose, never to hand-roll a container, a radius or a colour. It writes no copy — the copy card owns the strings and this card reads them from copy.designShowcase.* / copy.nav.*.
Acceptance criteria
/designis publicly reachable with no sign-in on motir.co and renders the page the design asset draws. (motir-marketinghas no auth of any kind, so this is a property of the route existing — not a gate to remove.)- Changing Style, Palette or Type restyles the whole document, header and footer included, with no reload — asserted by reading the
data-style/data-palette/data-typeattributes on<html>after each control is operated. - The light / dark / system control works, and
systemresolves throughprefers-color-scheme. - All six type pairings render their real faces.
app/layout.tsxloads Space Grotesk as--font-grotesk-source, Fraunces as--font-editorial-sourceand IBM Plex Mono as--font-mono-technical-source, each withpreload: false; a test asserts each of the sixTYPOGRAPHYTYPE_IDSids maps to a-sourcevariable the layout defines. (⚠️ AMENDED 2026-08-29: the registries areSTYLE_REGISTRY/PALETTE_REGISTRY/TYPE_REGISTRYplus the*_IDSarrays, and there is noToastexport — a build that writesimport { Toast }fails.) - AA holds for body text and controls on the page in light and dark, for every style × palette pair — checked programmatically over the registries rather than by eye, and reported as a matrix, not a sample.
- The page reuses the shipped runtime and registries and invents nothing: a test asserts the page module imports its primitives, pickers and registries from
@motir/design-systemand declares no--el-*, no--color-*, and no rawrounded-*/p-*/h-*where a shape token exists. - The nav entry ships in
app/_components/SiteHeader.tsx(desktop bar +md:hiddenpanel) with the current-page treatment the design specifies, as anext/link— it is the site's first internal second route.No accent-coloured text in the bar (MOTIR-3745).⚠️ AMENDED 2026-08-29 (MOTIR-1043, on the record) — THIS CLAUSE IS RETIRED AND THE BUILD DOES THE OPPOSITE. It was a MEASUREMENT of@motir/design-system@0.1.0—--el-accent-on-surfaceon--el-surface-softat 4.41:1 in dark, under AA — and it named its own expiry condition. MOTIR-3872 published 0.1.1 carrying MOTIR-3745's and MOTIR-3774's lifted ink and re-pinned this repository to it; MOTIR-3874 re-decided the treatment to the pairingExploreTopBarships. The current item IS--el-accent-on-surfaceatfont-weight: 600— re-measured here at 5.76:1 dark / 6.29:1 light on the bar and 5.54 / 6.03 in themd:hiddenpanel, over all ten palettes (tests/aaMatrix.test.ts). The--el-text+ 2px--el-accentrule the retired number forced is GONE rather than kept alongside. app/sitemap.tsgains the/designline in this same change — the file's own comment asks for exactly that: "When a second page lands it adds its line here, in the same change that adds the route."- a11y: the axis rail's regions are labelled, every picker is keyboard-operable and reports its selected option, and the nav's current item is exposed to assistive technology by the treatment the design chose.
- Its own tests ship with it (
vitest, the repo'stests/*.test.tsxpattern, per the code-and-tests-are-one-deliverable floor): the page renders, each control writes the expected<html>attribute, the type-pairing ↔ font-variable map is total, and the AA matrix passes. There is no separate test card and no E2E lane in this repository —ci.ymlruns lint · typecheck · build · test, and its own header says not to declare a gate before its job exists. pnpm lint,pnpm format:check,pnpm typecheck,pnpm buildandpnpm testall pass.
Advisory dispositions
validate_work_item, projected over this re-plan, raised two advisories against this card. Neither applies, and here is the check rather than the agreement.
subsumption·likely-already-shipped—app/page.tsx,moooon-B-V/motir-marketing#5(MOTIR-1152), merged 2026-08-28. DOES NOT APPLY — and it fired for a good reason worth recording. The detector matched because this card was re-pinned tomotir-marketingin the same act, and the most recent pull request touching that repository'sapp/is 1152's. The path is the disproof: 1152 shippedapp/page.tsx, and this card's only page deliverable isapp/design/page.tsx, which does not exist onorigin/main(git ls-tree -r --name-only origin/mainlists 52 files inmotir-marketingand noapp/design/among them). What 1152 genuinely did ship — the chrome, both package imports, the two@sourcelines, the theme init script — is not duplicated here; it is this card's SUBSTRATE, which is exactly why 1152 is wired as ablocked_byrather than ignored.reference·advisory— this card names MOTIR-656. DOES NOT APPLY, and it is now the card's own PARENT. It is cited in the re-plan banner to explain WHY the card moved repositories — a historical pointer, not a consumption — and a parent is never ablocked_by. 656 is a container story that produces no output this card reads, and its own deliverables here (the landing, the brand, the SEO root) are alldone; the two children of it that are still open, MOTIR-1156 (directory listings) and MOTIR-2267 (trademark clearance), are unrelated to this page and are named here only as the reason a story-level edge would have been wrong.
A note on the numbers. This card and its two new siblings were re-numbered 8.8.x → 8.3.x when the set moved from story 8.8 to story 8.3. Sibling cards are therefore cited by CHIP throughout, never by number — a number in prose is exactly what a move like this invalidates.
Context refs
- The design card — the asset this builds to · the copy card — the strings this renders
- MOTIR-1152 — the landing build that established the chrome, both package imports, the two
@sourcelines and the font loading this page extends. Now a SAME-PARENT sibling, since this card moved into 8.3. · MOTIR-1456 —@motir/brand motir-marketingapp/layout.tsx(the three loaded faces and the-sourcenaming rule) ·app/globals.css·app/_components/SiteHeader.tsx·app/sitemap.ts(its hand-off to this card) ·lib/copy.ts·.github/workflows/ci.yml- The published
@motir/design-system— ⚠️ AMENDED 2026-08-29: the pin is0.1.1, not thethis line quoted (MOTIR-3872 landed). 70 exports, not 68; read0.1.0/shasum ed10ed8db66eb09d34d062b0b4bad878c8509feddist/index.jsfor the barrel andtheme.cssfor the[data-type]blocks quoted above. Read the PUBLISHED package, not motir-core'spackages/design-system/source — this repository installs the registry copy. - MOTIR-1040 / MOTIR-1045 / MOTIR-1065 / MOTIR-1070 — the pickers and the three axes this page drives, kept as blockers because they remain genuine prerequisites even though all are
done. MOTIR-1061 (the onboarding-flow design) was DROPPED as a blocker by this re-plan: it drew motir-core's onboarding journey and is not a prerequisite of a motir.co marketing page. - motir-core
app/tokens/page.tsx— the internal specimen this does not replace, kept for reference on section composition only (itsSidebar,CommandPalette, chart andIssueTypeIconsections are motir-core-only and are out of reach here)