system
400.wtf

400.wtf / system / docs · docs/design-system/plan.md

Design system plan — 400.wtf civic redesign

Status: draft, 2026-10-09. Forked from bad-request@4dc378b. Input: ../identity/brief.md + moodboards in ../identity/references/.

The design system (DS) is built first and lives in this repo next to the site. The site redesign starts only once the DS can compose a page without one-off CSS.


1. Reading of the proposal

The brief describes a spatial idea more than a visual style: the visitor should feel like they’re walking through a public building. It’s monumental, but signage keeps you oriented. Three forces must stay balanced: monumentality × clarity × organic life. What that means for the system:

Brief saysSystem consequence
Light and dark surfaces alternate deliberatelySurfaces, not themes. Each section declares its surface (paper, concrete, graphite, night, signal) and semantic tokens remap inside it. No global dark-mode toggle. Supersedes ADR-0009.
Wayfinding must serve navigation, not decorationEvery graphic element in the DS has a job. Arrows mean “go”. Numbers are real indices. Line colors are real categories. Route lines are real navigation.
Photography is centralThe DS owns an image pipeline (responsive formats, focal-point crops, captions/credits) and a photo treatment spec. The photos themselves are a content dependency (see §7).
Projects as editorial compositionsThe project entry is a composition with variants (lead / split / column), not a card.
Not a dev portfolio with a brutalist skinThe black hole stops being the site’s background and becomes a project the site exhibits (§6).

The moodboards are AI-generated art direction. Keep from them the density contrast (huge type vs. tiny mono annotations), the edge-to-edge photo blocks, and signage plates as content. Do not copy their literal layouts, their pictograms, or the São Paulo Metro line-color mapping.

2. Target repo shape

Convert to a pnpm workspace:

apps/
  site/                 Astro site (moved from repo root)
packages/
  ds/                   @400/ds: tokens, CSS, icons, Astro + Vue components
    src/tokens/         TS source of truth → generated CSS vars + JSON
    src/css/            base, layers, surfaces, utilities
    src/icons/          SVG sources (24-unit grid) → sprite + <Icon>
    src/components/     Astro (static, zero-JS) + Vue (interactive only)
    scripts/            build-tokens, check-contrast
  black-hole/           (later, §6) framework-agnostic engine + shaders + config
docs/
  identity/             brief + references
  design-system/        this plan, decisions, specs
  ADR/                  existing 0001–0009 + new ones (§8)

Stack (the “everything can change” pass). Keep Astro (static-first, islands, content collections, i18n routing), Vue, Cloudflare deploy, and Playwright. Tailwind v4 stays, but only as a consumer: the generated tokens feed @theme, so utilities resolve to semantic variables and follow surface scopes. Component styling is plain CSS in @layer components, so the DS doesn’t depend on Tailwind. Tokens are authored in TypeScript rather than DTCG/Style Dictionary. A ~100-line script emits CSS + JSON. It’s typed, testable, and can also feed shader uniforms.

Every DS component is a Vue SFC (decided 2026-10-09):

  • Astro renders Vue components to HTML at build time. With no client:* directive they ship zero JS. So “static” vs “interactive” is decided where a component is used, not by its file type.
  • One authoring model. The same Button/Icon/Field works in static pages and inside hydrated islands. This matters because Astro components cannot be rendered inside Vue islands, and the artifact config panel is one.
  • The DS stays portable outside Astro (e.g. a standalone black-hole demo on plain Vite).
  • Accessible behavior for the few complex widgets (select, dialog/mobile nav, slider, switch, tooltip) comes from Reka UI. It’s headless, Vue, and the same base shadcn-vue uses, but we style it with our own tokens. React + shadcn was rejected: shadcn’s head start is a rounded-card SaaS look the brief explicitly rejects, so we’d restyle everything anyway, and the artifacts are Vue.
  • Known costs:
    1. Vue can’t render Astro’s <Image>. Figure takes an already-optimized image (src/srcset/sizes/dimensions) that the site resolves with Astro’s getImage() through a small toFigure() helper.
    2. Astro slot content arrives in Vue as static HTML. That’s fine for static use.
    3. Accidental hydration bloat. Guard: a Playwright run of /system/* with JavaScript disabled must render every static component correctly.

Astro files are limited to site concerns: layouts, <head>, routes, i18n plumbing.

Docs surface: a living /system route in the site, not Storybook. It costs no extra tooling, uses the DS on itself, and the DS becomes a showcased project in its own right.

3. Foundations (tokens)

3.1 Color: three tiers

  1. Primitives: the brief’s palette as-is (concrete, paper = warm off-white, graphite, ink = near-black, gray, botanical, moss, signal-yellow, transit-blue, signal-red), plus derived accessible variants.
  2. Semantic: surface, surface-raised, fg, fg-muted, rule, rule-strong, action, action-fg, focus, status-{info,warn,error,ok}, organic.
  3. Surface scopes: [data-surface=paper|concrete|graphite|night|botanical|signal-yellow|signal-blue|signal-red] rebind the semantic tier. Components only ever read semantic tokens.

Measured contrast (WCAG 2.x) shows the brief’s palette cannot be used as-is:

PairRatioVerdict → token
gray #777973 on paper3.86✗ body text → fg-muted@paper = #6B6C67 (4.64)
gray on graphite3.42✗ → fg-muted@graphite = #8D8F8A (4.61)
moss #777E60 on paper3.73✗ as text → #696F54 (4.60); raw moss is decorative only
signal yellow on paper1.51✗ never text/focus on light. Yellow is a fill with ink text (10.46)
transit blue on paper6.12✓ action@paper
transit blue on graphite2.16✗ → action@graphite = #6692C5 (4.65)
signal red on paper4.54✓ (barely). Error text only at ≥16px
red on graphite2.91✗ → #D86F6C (4.60)
ink on concrete8.96✓ concrete works as a surface

These values come from a straight lighten/darken. Hand-tune the hue/chroma later. scripts/check-contrast asserts every semantic fg/bg pair per surface in CI (text ≥ 4.5, large display ≥ 3, UI/focus ≥ 3).

Line colors (categories, not decoration): a small set of line-* tokens, each mapped to a project category. Each must hit ≥ 3:1 against the surfaces it sits on, and must never be the only signal (always paired with a label or number).

3.2 Typography

  • Spike first (§5, DS-1): a specimen page rendering the same content in three pairings, then pick one:
    • A: Inter / Inter Display + IBM Plex Mono
    • B: Geist + Geist Mono
    • C: IBM Plex Sans + Plex Sans Condensed (display) + Plex Mono Criteria: uppercase display at 15–20vw, Portuguese diacritics in caps (Ã Ç Õ É Ê, which clash with tight leading), mono legibility at 11px, total font weight ≤ ~120 KB.
  • Self-hosted variable woff2 (Fontsource), subset latin + latin-ext, max 2 preloads.
  • Scale: fluid clamp() steps.
    • display-{xl,l,m}: uppercase, leading 0.85–0.95, tracking −0.02 to −0.04em. xl may reach ~20vw.
    • heading-{1,2,3}, body-{l,m,s}: body leading 1.5–1.6, measure 60–72ch.
    • label: compact uppercase sans, tracking +0.06em.
    • mono-{m,s}: indices, dates, coordinates, metadata.
  • The display/annotation size ratio is a design feature: keep it ≥ 10:1 in heroes.

3.3 Space, grid, structure

  • Spacing: 4px base. 4 8 12 16 24 32 48 64 96 128 192 256, plus fluid section-{s,m,l} gaps for “architectural void”.
  • Grid: 4 / 8 / 12 columns (mobile / tablet / desktop). Outer margin clamp(16px, 5vw, 96px). Gutter fluid 16–32px. Named lines for bleed (edge-to-edge) vs. content.
  • ?grid overlay (same pattern as the existing ?debug). Plans are drawn on grids, and so is this site.
  • Rules: hairline 1px, structural 2px, route 6–8px (the route line).
  • Radius: 0 everywhere. 2px only where the browser needs a hint (inputs). No shadows, no elevation scale. Depth comes from surface tone and overlap.
  • Z-index: named layers only (base, overlap, nav, overlay).

3.4 Motion

  • Durations 120 / 240 / 480 / 800ms. A single “transit” ease (cubic-bezier(0.2, 0, 0, 1)), no overshoot or springs.
  • Allowed: directional slides along the route axis, scroll reveals with scale and rhythm, arrow nudges on hover, sparse parallax on bleed photos.
  • prefers-reduced-motion: all of it becomes instant. Pages must work fully with no animation.

3.5 Focus and states

A visible focus ring on every surface, with ≥ 3:1 contrast against it: ink outline on light and yellow surfaces, yellow outline on dark ones. Hover never carries meaning on its own.

4. Graphic language and components

4.1 Wayfinding system (the original part)

  • Route line. The site is a line, and projects are stations. A vertical route rule in the left margin carries station markers (/01, /02…) and shows “you are here” on scroll. It is the primary in-page navigation, and it collapses to a top progress rule on mobile.
  • Station marker. Circle plus mono index. States: past / current / ahead.
  • Sign plate. High-contrast panel with arrow + label (+ optional pictogram). Used for primary CTAs and “next station →” between projects.
  • Notice. Wayfinding-style message (info / attention / error).
  • Annotation. Small mono labels attached to big things (coordinates, dates, indices). Each one must be real data.
  • Mythology hook. HTTP 400 ↔ wayfinding: the 404 becomes a “wrong platform” sign. The joke survives in the new language.

4.2 Icons

Original set on a 24-unit grid, 2px monoline with solid variants where weight is needed, optically balanced. v1, about 24 glyphs: 8 arrows (+ up-right “external”), plus/minus/close/menu, info/attention/error/ok, pin, link, mail, github, rss, play/pause, settings (artifact config panel), grid, and 3–4 category pictograms. SVG sources → generated sprite + <Icon name> component, aria-hidden by default.

4.3 Component inventory

LayerComponents
LayoutSection (owns surface), Container, Grid/Col, Split, Bleed, Stack
MediaFigure (astro:assets, AVIF/WebP, focal point, caption + credit), Texture (concrete / tile / ripado / vegetation plates)
TypeDisplay, Heading, Text, Label, Mono, Index (/01)
WayfindingRouteLine, StationMarker, Sign, Notice, Annotation, Arrow
ActionsLink (inline / nav / arrow), Button (primary-blue, primary-yellow, neutral, ghost)
DataTag, MetaList (dl), Table
FormsField, Input, Select, Toggle, Slider, ColorInput. Ports today’s components/shared/* so the artifact config panel joins the DS.
NavigationSiteNav, MobileNav (Vue), Pager (prev/next station, keeps ←/→ keys)
EditorialProjectEntry (variants: lead full-bleed / split / column), ArtifactFrame (concrete frame around a live artifact)

All are Vue SFCs (§2). Most are rendered without hydration and ship no JS. Only MobileNav, the form controls, LocaleSwitch (if it remembers the choice), and the config panel are hydrated.

Bilingual from day one. Components never hardcode copy. Labels, aria text and units come in as props/slots, and a tiny t() dictionary covers the DS’s own strings (e.g. “você está aqui” / “you are here”). Layouts must survive Portuguese running ~15–30% longer than English, especially in uppercase display type and sign plates. Specimens on /system render in both languages side by side.

5. Phases

Each phase ends with a /system/* page you can open and judge with your eyes.

PhaseScopeExit criteria
DS-0 Setup ✅ 2026-10-10Workspace conversion (apps/site, packages/ds). Remove the black hole from the site shell (code parked for §6). Old index replaced with a holding page. Weaver + Puzzle routes keep working. Vue + Reka UI wired into packages/ds. Astro i18n configured (PT /, EN /en/) with the holding page in both. Photo manifest + first stock photos. CI updated.pnpm build green. Artifacts still run. Both locales resolve.
DS-1 Foundations ✅ 2026-10-10Token TS source + generator, surfaces, contrast checker in CI, type spike → decision, type scale, spacing, grid + ?grid, rules, motion, focus/system/foundations shows every token on every surface. Contrast check passes. Type ADR written.
DS-2 Wayfinding + icons ✅ 2026-10-10Icon grid spec + v1 set + sprite, route line, station marker, sign, notice, annotation/system/wayfinding and /system/icons
DS-3 Layout + media ✅ 2026-10-10Section/Grid/Split/Bleed/Stack, Figure + image pipeline, photo treatment spec, textures/system/layout composes a page from primitives only, with no ad-hoc CSS
DS-4 Components ✅ 2026-10-10Type, actions, data, forms (config panel ported), nav, ProjectEntry, ArtifactFrame/system/components. axe clean. Keyboard pass.
DS-5 Patterns ✅ 2026-10-10Hero, project index, project detail, about, footer, 404, assembled from DS parts with real contentPattern pages pass the “final design test” review (brief §final)

Then the site track: S-1 IA + copy + photography → S-2 build pages from DS-5 patterns → S-3 black hole as an exhibited project → S-4 launch (OG images regenerated, deploy switch).

Quality gates from DS-1 on: contrast check (CI), Playwright screenshots of /system/* (visual regression), axe on /system/*, DS CSS ≤ ~25 KB gzip, zero JS for static components, ≤ 2 font preloads, LCP image budget on the hero.

6. The black hole as its own project

The black hole fits a cosmic, dark-only site. It doesn’t fit a daylight-concrete one, and running a 300k-particle background behind editorial pages hurts the new identity’s performance budget. Plan:

  • Extract engine.ts, the shaders, config/blackHole.ts and the persistent-mount composable into packages/black-hole, with a framework-agnostic API (mount, config, quality tier, reduced-motion). ADR-0004 / 0008 move with it. ADR-0002 is superseded for the site.
  • On the site it becomes an exhibited project (“station”) with its own full-bleed stage, and possibly a standalone deploy/demo page.
  • Before extracting: the uncommitted black-hole work still in bad-request (shader + engine + config changes) needs committing there and pulling into this fork (git pull upstream main or a local remote).

DS-1 notes (2026-10-10)

  • Roles are derived, not hand-picked: primitive + OKLCH lightness shift until the target contrast is reached against surface-alt. See ADR-0011. Eight surfaces ship: paper, concrete, graphite, night, botanical, signal-yellow, signal-blue, and signal-red (added after DS-1: the brief’s red darkened to #B21B1E so paper text reaches 6:1. Use rarely, like the single red canopy in the reference photo).
  • Photography direction lives in docs/identity/photography.md, distilled from the owner’s own photo, which outranks the moodboards.
  • The spike changed the type plan. Display leading is 1.0 (not 0.85–0.95), because Portuguese caps collide at anything tighter. The display face became Bricolage Grotesque at 87.5% width (ADR-0014), with Plex Sans for text and signage.
  • Display type inside narrow columns needs container-relative sizing (min(var(--ds-type-display-m), 16cqi)); DS-3 should make this a layout primitive.
  • /system/* pages: English chrome, bilingual specimens, noindex, out of the sitemap.
  • Tailwind’s default palette stays enabled: after DS-4 the only users are the artifacts themselves (Puzzle’s glow colors, the old artifact stage). Artifacts are exhibits with their own art direction and are exempt from DS color rules.

DS-2 notes (2026-10-10)

  • Icons are TypeScript data rendered inline by Icon.vue (no sprite): 26 glyphs, 24-unit grid, 2-unit strokes, butt caps and miter joins. Status icons are solid discs told apart by shape. No third-party brand marks: links say “GitHub ↗”. Category pictograms wait for the real category list (S-1).
  • Wayfinding components: StationMarker, RouteLine (static current, scroll-spy when hydrated, vertical or horizontal), Sign (plate, arrow on the side it points, hover nudge), Notice (attention/error as yellow/red plates, info/ok as panels), Annotation.
  • Line colors: blue, yellow, red, green. A casing in the surface’s fg is generated where a line is below 3:1 on a surface. The category → line mapping lives in apps/site/src/lines.ts (D6 defaults).
  • Grid gotcha: numbered lines on .ds-page include the margin tracks. Place by column number inside .ds-subgrid.
  • Components take locale as a prop: Vue provide/inject doesn’t cross Astro’s separately rendered components.

DS-3 notes (2026-10-10)

  • Primitives: Section (surface, void, rule; is the page grid), SectionHead, Grid (subgrid) + Col (span/start per breakpoint, counted from the first content column, never the margin), Stack, Bleed, FitText. Split was dropped: Grid + Col covers it without a second API.
  • Bleed is a subgrid of the page that inherits its named lines. side="start|end" with a beside slot gives the asymmetric layout (image off one edge, text on the grid, same row). Line numbers are computed as integers in the component, because calc() in grid placement isn’t reliable everywhere.
  • Figure takes a host-resolved FigureImage (AVIF/WebP + JPEG fallback); apps/site/src/photos/figure.ts builds it with getImage(). A narrow image art-directs phones (the owner’s portrait photo is the holding-page hero). Captions: subject, then credit on its own line; inside a bleed they stay on the margin.
  • Texture: tile, ripado, tactile, hazard as token-drawn patterns. Concrete comes from photography only.
  • Route lines were remodelled as a transit network (ADR-0015): RouteMap with single-color lines, interchange capsules, branches and lane reuse; RouteLine is its one-line case. Prompted by the owner’s São Paulo network reference.
  • /system/layout is composed from primitives only. Its source has no <style>.
  • Page CSS must never style bare elements (a { … }). Astro’s scoped styles are unlayered and beat DS components’ @layer components rules. This turned the index’s sign text ink-on-graphite until scoped.
  • /system is now the hub: specimens, a component inventory driven by apps/site/src/system/registry.ts, and the repo docs (identity, plan, ADRs) rendered at /system/docs/… with cross-links rewritten.

DS-4 notes (2026-10-10)

  • Actions: Link (inline / nav / arrow, external shows ↗ and never opens tabs), Button (primary = surface action fill, signal = yellow plate, neutral, ghost; renders a link with href).
  • Data: Tag (optional line chip), MetaList (rows / inline), Table (hairlines, mono headers, scrolls in its own frame).
  • Forms: Field (label, hint, explicit error; ids/aria wiring via scoped slot in Vue or the ${id}-hint/-error/-label convention from Astro), Input, native Select, Toggle and Slider (Reka UI), ColorInput (swatch shows any CSS color incl. rgba; the native picker is hex-only).
  • Navigation: SiteNav works without JS at every width (destinations drop to a scrolling row on phones). MobileNav was dropped: no dialog until the nav outgrows a row. Pager renders prev/next as signs with data-nav-prev/next.
  • Editorial: ProjectEntry (lead / split / column; the title is the one link), ArtifactFrame (station annotations, hint, framed / frameless / full-bleed, aspect to keep an artifact’s proportions).
  • The artifact config panel is rebuilt on DS forms; components/shared/ is gone. Testing it found a dropped-value bug (an inline set(id) handler returning a closure) that would have broken every control.
  • Guard: pnpm check:nojs loads every built page at 360px with JavaScript disabled (visible h1, no sideways scroll, no empty islands). It runs in CI and caught the artifact pages having no <h1>.
  • Gotcha: display-xl at its 60px minimum overflows phones for 11-letter words; the /system masthead uses FitText. Display headings in narrow places should too.
  • Project previews (public/previews/) are still black-hole-era captures; recapture with pnpm og once artifact pages are redesigned (S-2).

DS-5 notes (2026-10-10)

  • Copy: projects are estações / stations (not “artifacts”); the owner suggested “platforms”, but plataforma/platform already means software platform, and the DS already speaks in stations. URLs: /estacoes/<slug>/, /en/stations/<slug>/; /artifact/* and /colophon/ redirect.
  • Content: src/stations/<slug>/{pt,en}.mdx hold the write-ups only; the live island is _stage.astro. PT translations are drafted and flagged reviewed: false, which shows a notice linking the English original.
  • The stations themselves were redesigned in the identity. Weaver is ink on a plan grid (joints as stops, red body, blue station target, dashed route), sized to its stage instead of the window; config schema v2 drops old stored palettes. Puzzle is drawn as transit lines (8px tracks in line colors, interchange-capsule buttons, colored terminal edges).
  • Patterns, built as the real pages: home (owner’s photo hero, headline, station map + editorial entries, about room), station page (title and facts, ArtifactFrame stage on the station’s surface, write-up in Prose, line map + pager), about (former colophon), credits, a red bilingual 404, site header and night footer. The holding page is gone.
  • pnpm og now captures the station stage (centered) for share images and index previews; previews show the redesigned stations.
  • Found while building: a one-station line rendered nothing in RouteMap (now a terminal stub); a paper sign on a paper page has no plate (use another surface).

DS-5 refinements (2026-10-10)

  • Text over photos is signage mounted on the image: Figure’s overlay slot takes a Sign or a Plate (a surface panel), so contrast never depends on the photo. Overlapping planes use Overlap (a plate breaking over the edge above). Home: the headline plate breaks over the hero; a sign is mounted on it; About mounts its heading plate on a bleeding photo. On phones the hero plate stacks (no overlap) so the caption stays visible.
  • Sticky site header (SiteNav sticky); the host sets --ds-sticky-top to its height so anchors and sticky rails clear it.
  • RouteLine in use: station pages carry a sticky reading rail whose stations are the write-up’s own h2 sections. Tracking is now “the last target past a reading line one third down the viewport”, recomputed per frame; the earlier IntersectionObserver band missed anchor jumps.
  • Config panel: header sticky inside its own scroll; closes on outside click and on Escape (focus returns to the toggle).
  • Scroll is left to Astro’s client router: top on new pages, restored on back/forward (the old “keep scroll across navigation” from the black-hole site was removed).
  • Bleed renders beside after the media when the media bleeds from the start side, so stacked reading order matches the visual order.
  • Mobile pass in a real browser (Chrome, 390px frame): ArtifactFrame aspect stages are width-driven (min(100%, height × ratio)), so square stations stay square on phones; the config panel caps at 60% of the stage on phones; its header has a solid background; the pager shows one sign when previous and next are the same station; the home hero keeps a void below its plate (Section flush).
  • Figure frames take width: 100%: with aspect-ratio and an auto width, max-height shrinks the frame horizontally (the hero stopped being full-bleed on short, wide viewports). Overlap and right-aligned captions now start at 1024px; below that plates stack, so they never cover a credit.
  • Weaver: the redesign made the target visible (station ring + route), exposing the steering’s slow quadratic arrival creep and idle wandering toward random targets. Arrival is now linear with a 2px/frame floor (radius 120), steering never softer than 40%, a short dwell at placed stations, and only placed stations are drawn. A/B against the DS-4 build: same ~1000px/s cruise, no creep.
  • Line casings only where a line would vanish (< 1.4:1, e.g. yellow on concrete, blue on signal-blue). The earlier < 3:1 rule cased only yellow on paper, which read as accidental; real metro maps don’t case yellow either. Puzzle’s board is a paper plate on its concrete stage so all four lines read uncased.
  • graphite is a neutral line for routes that aren’t a category. Home and station pages use one RouteLine through every station (stops carry their category as a chip + note) instead of one-station-per-category stubs.
  • Home entries are divided: a structural station bar (marker, index, category) plus a section void, so it’s obvious where one station ends.
  • Smooth scrolling for in-page jumps (scroll-behavior: smooth, off under reduced motion); verified that client-router navigation still lands at the top instantly.

Brand (2026-10-10)

  • Name: Terminal 400; 400.wtf is its address (kept in titles, the name board and the footer annotation).
  • Station mark (packages/ds/src/brand.ts): a current station on a horizontal track. Favicon per line at /favicon/<line>.svg: station pages use their own line, other pages a random line per visit; the center follows the tab’s color scheme. pnpm --filter @400/site icons renders favicon.ico + apple-touch-icon.
  • Logo (everyday mark): the station mark + “Terminal 400”, following --ds-line-current (set from <html data-line>), so it always matches the favicon. NameBoard (alternative mark): platform name board, used for the default share image (/system/share-card/ → public/og/default.jpg).
  • pnpm og binds its server to a free port on 127.0.0.1: the fixed :4321 on “localhost” silently hit a running dev server and produced a blank Weaver card.

Estação Fim da Linha (2026-10-10)

  • The black hole returns as station 03 on the red line (ADR-0016), on the original time-driven engine: a tall stage pins the view while scrolling drives the full dive; drag to turn, click to spin.
  • Stations renamed to eerie, believable place names (URLs too, with 301s from the old slugs): Weaver → Vigília, Puzzle → Quatro Cantos, Horizonte → Fim da Linha. Weaver’s config keeps its storage key (weaver) so saved settings and ?c= permalinks survive; the panel shows the new title.
  • ProjectEntry split titles are capped at 18cqi of their column (inline style; the type utility class would beat a components-layer rule), and the title arrow is glued with a no-break space.

7. Open decisions

#DecisionBlocksRecommended default
D1Content language Decided: bilingual, PT-BR primary, EN first-class—Astro i18n: PT at /, EN at /en/ (prefixDefaultLocale: false), hreflang pairs, a locale switch that keeps you on the equivalent page, and per-locale content entries (artifact write-ups translated, never machine-only). EN pages are not allowed to lag: a CI check fails if an entry exists in one locale only.
D2Photography Decided: free-license stock now (Unsplash, Pexels, Wikimedia Commons), own photos later—Download and self-host through astro:assets; don’t hotlink. Every photo is registered in a photos.ts manifest: source URL, author, license, focal point, alt text (PT + EN). Unsplash/Pexels need no attribution, but credit anyway. CC-BY(-SA) from Wikimedia requires it. A /creditos page renders from the manifest. Unsplash+ (paid) is excluded. Replacing a photo later = swapping one manifest entry.
D7UI framework Decided: Vue for everything, Reka UI for headless behavior—See §2
D3Type pairing Decided (ADR-0014): Bricolage Grotesque (display) + IBM Plex Sans (text, signage) + IBM Plex Mono (metadata)——
D4Fork remote: new GitHub repo name, domain cutover planS-4New private repo now. Keep 400.wtf on the old site until S-4.
D5Logo/wordmark for 400.wtfDS-2Typographic wordmark from the chosen family + a station-marker monogram. No mascot.
D6Line-color ↔ category mappingS-1 (defaults in place)Defaults: interaction study → blue, playable system → yellow, rendering → red, system → green. Revisit when the category list is final.

8. ADRs to write (in order)

  • 0010 pnpm workspace with an in-repo design system package ✅
  • 0011 Surface scopes instead of themes (supersedes 0009) ✅
  • 0012 DS components are Vue SFCs, statically rendered by default (Reka UI for behavior) ✅
  • 0013 Bilingual routing and content model (PT-BR default, EN first-class) ✅
  • 0014 Typefaces (Bricolage + Plex Sans + Plex Mono) ✅
  • 0015 Route lines as a transit network ✅
  • 0016 Black hole as a station (Estação Horizonte) ✅ extracted as a standalone package/project (supersedes 0002 for the site)

The old docs/roadmap/* and docs/analysis/* describe the dark black-hole site. They are useful history, but they no longer set direction for this fork. Some principles still hold: one-person maintenance, artifacts over platform, never-blink navigation. They should carry over into a new roadmap after DS-5.