How the site consumes the shared @yesid/* design system, the app's styling responsibilities, and the rules that keep styles consistent.
| Reference | Value |
|---|---|
| Subject | Design packages, CSS composition, theme boot, motion policy, reading scale, design gates; for anyone changing visuals |
| Verified source | apps/web/package.json, app.css, tools/ on main at f40e8704; checked 2026-11-07. package.json re-checked at 0461e045 on 2026-11-10; app.css, tools/, hooks.server.ts, article-body.css and morphGate.ts unchanged since 7a71e081; first-visit sections behavior from tests/remembered-sections.spec.ts at 0461e045 |
| Support state | Implemented from main f40e8704. Main 7a71e081 (#671) pinned v0.39.1; main 0461e045 (#692) pins all @yesid/* packages to v0.40.0 from yesid-dev/yesid.design (installed from the organization since #683). Public requests on November 7, 2026 returned the f40e8704 revision's project redirect and robots tag |
flowchart LR
R["yesid.design<br/>GitHub release"] --> P["package.json URLs<br/>bun.lock integrity"]
P --> T["@yesid/tokens<br/>tokens.css, fonts"]
P --> U["@yesid/ui<br/>base.css, components, head"]
P --> M["@yesid/motion"]
P --> G["@yesid/gates"]
T & U --> A["src/app.css"]
M --> MO["src/lib/motion"]
G --> LD["lint:design"]
| Package | Used for |
|---|---|
@yesid/tokens |
tokens.css (also injected into PostCSS as global custom media), self-hosted Inter and JetBrains Mono, font preload helper |
@yesid/ui |
base.css, shared components and blueprint drawings, head theme boot injection, boot.json CSP hash |
@yesid/motion |
Motion primitives and the reduced-motion policy |
@yesid/seo-kit |
Sitemap and robots builders, JSON-LD helpers, code highlighting, OG palette and fonts; the app owns its share-card layout in src/lib/og/card.ts |
@yesid/analytics, @yesid/i18n-core |
Plausible client sending through the same-origin relay route src/routes/relay/e/+server.ts (endpoint: '/relay/e' in src/lib/analytics/client.ts), locale helpers |
@yesid/gates, @yesid/config |
Style regression gates; shared Svelte, Playwright and tooling config (root package.json) |
All packages install by URL from release v0.40.0 (https://github.com/yesid-dev/yesid.design/releases/download/v0.40.0/...) and are pinned by integrity in bun.lock. Never patch them in node_modules. Change yesid.design, publish a release, then bump every @yesid/* URL together in one consumer PR.
| Concern | Contract |
|---|---|
| Composition | app.css imports tokens, Tailwind, @yesid/ui/base.css and @yesid/ui/station-tabs.css, then app rules. Sheet loads its own animation stylesheet. Keyframes used by two or more components live here |
| Values | Every color and spacing value references a token; no arbitrary Tailwind values such as text-[11px]. A value that recurs becomes a token upstream |
| Breakpoints | Canonical Tailwind breakpoints and token custom media only |
| Viewport units | dvh, svh, lvh; never vh. Safe areas through env(safe-area-inset-*) |
| Sticky layouts | Use overflow-x: clip, not hidden, on ancestors of sticky elements |
| Cascade | No !important, z-index: 9999 or transition: all; use z-index and duration tokens |
| Theme before paint | hooks.server.ts calls injectThemeBoot; the script hash comes from @yesid/ui/boot.json for the hash-only CSP policy |
Motion responds to interaction: tap, hover and scroll. The policy lives in @yesid/motion (shouldAnimate(tier) and the reducedMotion store) and has two tiers: safe-always effects (opacity, color, moves of 4px or less, brief feedback) run under prefers-reduced-motion; motion-gated effects (pins, scrubs, parallax, ambient loops, smooth scrolling) do nothing. The reduced-motion Playwright project checks this. In the app, every SVG entrance and path morph runs through src/lib/motion/utils/morphGate.ts: a running animation always finishes, and input during it only changes the end state, which plays afterwards (tests/svg-morph.spec.ts).
src/lib/styles/article-body.css maps the installed @yesid/tokens reading roles (--text-reading*) onto a .reading-detail scale for Blog, Project and Service detail pages, legal copy and the home biography:
| Element | Phone / desktop |
|---|---|
| Running text | 1.1875rem / 1.25rem, line-height 1.7, measure at most 70ch |
| Code blocks | 1rem / 1.0625rem, line-height 1.6, horizontal overflow inside the block |
| Supporting prose | 1.125rem |
| Collapsible section headings | 1.25rem / 1.375rem |
Change these sizes upstream in the tokens, not in the app. Verify resized text and spacing in the rendered page rather than trusting pixel values.
| Gate | Command | Checks |
|---|---|---|
| Design lint | bun run --cwd apps/web lint:design |
@yesid/gates style regressions, raw brand-hex checks with three named illustration/Mermaid exceptions, plus the site's forbidden token-utility patterns in tools/design-gates.ts. Contrast-pair and color-mix floor engines ship in @yesid/gates but are not invoked by this repository's gates |
| Breakpoints | Vitest and Playwright | responsive-breakpoint-contract.test.ts, responsive-breakpoint-boundaries.spec.ts |
| Visual themes | Playwright | light, dark, phone, tablet, desktop projects |
With JavaScript disabled, server-rendered section bodies are open and readable. If storage access throws, interactive pages use declared defaults and bulk actions are usable.
| Subject | Current contract |
|---|---|
| Pointer effects | @yesid/motion shares an any-hover/any-pointer fine policy, including hybrid devices; Lenis keeps its own capability test |
| Station tabs | Shared stylesheet owns inactive weight 400, active weight 700, inactive primary-foreground ink, active signage colors and a 3px hover rule. Native links use aria-current="page"; site distance opacity has a 0.9 floor |
| Disclosure state | On eligible pages with at least two collapsible content sections, a first visit (no stored choice, or a malformed one) starts with every section expanded and the bulk control in its neutral state, labelled Collapse all (since design v0.37.0; tests/remembered-sections.spec.ts). A stored Collapse all collapses content across pages and visits. Bulk actions store collapsed or expanded in origin-wide localStorage['sections-choice']; that choice applies across pages and visits. Individual toggles, TOC/fragment jumps and restoration do not write it. Pages below the threshold and independent panels use their declared defaults. |
| Consumer copy | Header, language switch, dialog Close and relevant TOC labels belong to the app's EN/FR/ES locale files |
| Contract-changing release | When API reports or migration notes change, each of the three consumers (Transit, yesid.dev, yesid.login) rehearses the rc.N candidate inside a draft adoption PR. After stable promotion, the same PR switches to stable URLs, reruns CI and merges |