MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-1043

8.3.16 The public design showcase at motir.co (`/design`) — live Style × Palette × Type over the published `@motir/design-system` (motir-marketing)

To Do
Description

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-coremotir-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 · 10 palettes · 6 type pairings — 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 [data-palette], 109 [data-style] and 9 [data-type] blocks 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:

pairingreadsfacetoday
groteskvar(--font-grotesk-source)no in-var fallbackSpace Groteskdeclaration invalid ⇒ role falls back
editorialvar(--font-editorial-source)no in-var fallbackFrauncesdeclaration invalid ⇒ role falls back
mono-technicalvar(--font-mono-technical-source, ui-monospace)IBM Plex Monodegrades 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

  • /design is publicly reachable with no sign-in on motir.co and renders the page the design asset draws. (motir-marketing has 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-type attributes on <html> after each control is operated.
  • The light / dark / system control works, and system resolves through prefers-color-scheme.
  • All six type pairings render their real faces. app/layout.tsx loads Space Grotesk as --font-grotesk-source, Fraunces as --font-editorial-source and IBM Plex Mono as --font-mono-technical-source, each with preload: false; a test asserts each of the six TYPOGRAPHY ids maps to a -source variable the layout defines.
  • 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-system and declares no --el-*, no --color-*, and no raw rounded-* / p-* / h-* where a shape token exists.
  • The nav entry ships in app/_components/SiteHeader.tsx (desktop bar + md:hidden panel) with the current-page treatment the design specifies, as a next/link — it is the site's first internal second route. No accent-coloured text in the bar (MOTIR-3745).
  • app/sitemap.ts gains the /design line 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's tests/*.test.tsx pattern, 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.yml runs 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 build and pnpm test all 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-shippedapp/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 to motir-marketing in the same act, and the most recent pull request touching that repository's app/ is 1152's. The path is the disproof: 1152 shipped app/page.tsx, and this card's only page deliverable is app/design/page.tsx, which does not exist on origin/main (git ls-tree -r --name-only origin/main lists 52 files in motir-marketing and no app/design/ among them). What 1152 genuinely did ship — the chrome, both package imports, the two @source lines, the theme init script — is not duplicated here; it is this card's SUBSTRATE, which is exactly why 1152 is wired as a blocked_by rather 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 a blocked_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 all done; 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.x8.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 @source lines and the font loading this page extends. Now a SAME-PARENT sibling, since this card moved into 8.3. · MOTIR-1456@motir/brand
  • motir-marketing app/layout.tsx (the three loaded faces and the -source naming 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@0.1.0npm pack @motir/design-system@0.1.0, shasum ed10ed8db66eb09d34d062b0b4bad878c8509fed; read dist/index.js for the barrel and theme.css for the [data-type] blocks quoted above. Read the PUBLISHED package, not motir-core's packages/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 (its Sidebar, CommandPalette, chart and IssueTypeIcon sections are motir-core-only and are out of reach here)