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 says | System consequence |
|---|---|
| Light and dark surfaces alternate deliberately | Surfaces, 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 decoration | Every 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 central | The 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 compositions | The project entry is a composition with variants (lead / split / column), not a card. |
| Not a dev portfolio with a brutalist skin | The 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/Fieldworks 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:
- Vue can’t render Astro’s
<Image>.Figuretakes an already-optimized image (src/srcset/sizes/dimensions) that the site resolves with Astro’sgetImage()through a smalltoFigure()helper. - Astro slot content arrives in Vue as static HTML. That’s fine for static use.
- Accidental hydration bloat. Guard: a Playwright run of
/system/*with JavaScript disabled must render every static component correctly.
- Vue can’t render Astro’s
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
- 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. - Semantic:
surface,surface-raised,fg,fg-muted,rule,rule-strong,action,action-fg,focus,status-{info,warn,error,ok},organic. - 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:
| Pair | Ratio | Verdict → token |
|---|---|---|
gray #777973 on paper | 3.86 | ✗ body text → fg-muted@paper = #6B6C67 (4.64) |
| gray on graphite | 3.42 | ✗ → fg-muted@graphite = #8D8F8A (4.61) |
moss #777E60 on paper | 3.73 | ✗ as text → #696F54 (4.60); raw moss is decorative only |
| signal yellow on paper | 1.51 | ✗ never text/focus on light. Yellow is a fill with ink text (10.46) |
| transit blue on paper | 6.12 | ✓ action@paper |
| transit blue on graphite | 2.16 | ✗ → action@graphite = #6692C5 (4.65) |
| signal red on paper | 4.54 | ✓ (barely). Error text only at ≥16px |
| red on graphite | 2.91 | ✗ → #D86F6C (4.60) |
| ink on concrete | 8.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.xlmay 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 fluidsection-{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. ?gridoverlay (same pattern as the existing?debug). Plans are drawn on grids, and so is this site.- Rules:
hairline1px,structural2px,route6–8px (the route line). - Radius:
0everywhere.2pxonly 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
| Layer | Components |
|---|---|
| Layout | Section (owns surface), Container, Grid/Col, Split, Bleed, Stack |
| Media | Figure (astro:assets, AVIF/WebP, focal point, caption + credit), Texture (concrete / tile / ripado / vegetation plates) |
| Type | Display, Heading, Text, Label, Mono, Index (/01) |
| Wayfinding | RouteLine, StationMarker, Sign, Notice, Annotation, Arrow |
| Actions | Link (inline / nav / arrow), Button (primary-blue, primary-yellow, neutral, ghost) |
| Data | Tag, MetaList (dl), Table |
| Forms | Field, Input, Select, Toggle, Slider, ColorInput. Ports today’s components/shared/* so the artifact config panel joins the DS. |
| Navigation | SiteNav, MobileNav (Vue), Pager (prev/next station, keeps ←/→ keys) |
| Editorial | ProjectEntry (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.
| Phase | Scope | Exit criteria |
|---|---|---|
| DS-0 Setup ✅ 2026-10-10 | Workspace 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-10 | Token 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-10 | Icon grid spec + v1 set + sprite, route line, station marker, sign, notice, annotation | /system/wayfinding and /system/icons |
| DS-3 Layout + media ✅ 2026-10-10 | Section/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-10 | Type, actions, data, forms (config panel ported), nav, ProjectEntry, ArtifactFrame | /system/components. axe clean. Keyboard pass. |
| DS-5 Patterns ✅ 2026-10-10 | Hero, project index, project detail, about, footer, 404, assembled from DS parts with real content | Pattern 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.tsand the persistent-mount composable intopackages/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 mainor 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(staticcurrent, 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-pageinclude the margin tracks. Place by column number inside.ds-subgrid. - Components take
localeas 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. Bleedis a subgrid of the page that inherits its named lines.side="start|end"with abesideslot 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.Figuretakes a host-resolvedFigureImage(AVIF/WebP + JPEG fallback);apps/site/src/photos/figure.tsbuilds it withgetImage(). Anarrowimage 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):
RouteMapwith single-color lines, interchange capsules, branches and lane reuse;RouteLineis its one-line case. Prompted by the owner’s São Paulo network reference. /system/layoutis 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 componentsrules. This turned the index’s sign text ink-on-graphite until scoped. /systemis now the hub: specimens, a component inventory driven byapps/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,externalshows ↗ and never opens tabs),Button(primary = surface action fill, signal = yellow plate, neutral, ghost; renders a link withhref). - 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/-labelconvention from Astro),Input, nativeSelect,ToggleandSlider(Reka UI),ColorInput(swatch shows any CSS color incl. rgba; the native picker is hex-only). - Navigation:
SiteNavworks without JS at every width (destinations drop to a scrolling row on phones). MobileNav was dropped: no dialog until the nav outgrows a row.Pagerrenders prev/next as signs withdata-nav-prev/next. - Editorial:
ProjectEntry(lead / split / column; the title is the one link),ArtifactFrame(station annotations, hint, framed / frameless / full-bleed,aspectto 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 inlineset(id)handler returning a closure) that would have broken every control. - Guard:
pnpm check:nojsloads 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 withpnpm ogonce 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}.mdxhold the write-ups only; the live island is_stage.astro. PT translations are drafted and flaggedreviewed: 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 ognow 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
overlayslot takes a Sign or aPlate(a surface panel), so contrast never depends on the photo. Overlapping planes useOverlap(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 (
SiteNavsticky); the host sets--ds-sticky-topto 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
besideafter 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.
graphiteis 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 iconsrenders 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 ogbinds 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
| # | Decision | Blocks | Recommended default |
|---|---|---|---|
| D1 | — | 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. | |
| D2 | — | 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. | |
| D7 | — | See §2 | |
| D3 | — | — | |
| D4 | Fork remote: new GitHub repo name, domain cutover plan | S-4 | New private repo now. Keep 400.wtf on the old site until S-4. |
| D5 | Logo/wordmark for 400.wtf | DS-2 | Typographic wordmark from the chosen family + a station-marker monogram. No mascot. |
| D6 | Line-color ↔ category mapping | S-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)
0010pnpm workspace with an in-repo design system package ✅0011Surface scopes instead of themes (supersedes 0009) ✅0012DS components are Vue SFCs, statically rendered by default (Reka UI for behavior) ✅0013Bilingual routing and content model (PT-BR default, EN first-class) ✅0014Typefaces (Bricolage + Plex Sans + Plex Mono) ✅0015Route lines as a transit network ✅0016Black 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.