--- phase: 01-design-tokens-rams-foundation plan: 03 type: execute wave: 3 depends_on: ["01-01", "01-02"] files_modified: - theme/basemap/liberty-rams.json - theme/basemap/build.mjs - theme/basemap/index.html - theme/package.json autonomous: true requirements: [DSGN-01] user_setup: [] must_haves: decisions: [D-08, D-09, D-10] truths: - "A Rams-styled MapLibre basemap renders in a browser: grayscale/muted land + water, desaturated roads, muted labels, zero signal-orange in the basemap" - "Basemap values are derived from the token package, not hand-tuned hex in the style JSON (D-09)" - "Map labels use the self-hosted Space Grotesk / Geist assets (D-10)" artifacts: - path: "theme/basemap/build.mjs" provides: "Generates liberty-rams.json by reading token values from theme/src/tokens.css" contains: "tokens.css" - path: "theme/basemap/liberty-rams.json" provides: "Forked OpenFreeMap liberty style with Rams cartography" contains: "glyphs" - path: "theme/basemap/index.html" provides: "MapLibre GL browser view proving the basemap renders (success criterion 3)" contains: "maplibre-gl" key_links: - from: "theme/basemap/build.mjs" to: "theme/src/tokens.css" via: "parse token custom properties" pattern: "tokens\\.css" - from: "theme/basemap/index.html" to: "theme/basemap/liberty-rams.json" via: "MapLibre style URL" pattern: "liberty-rams\\.json" --- Fork the OpenFreeMap `liberty` basemap style into a Rams-constant cartography output: grayscale/muted grounding, desaturated roads, muted labels in Rams type, `signal-orange` reserved exclusively for the future route line (not used in the basemap itself). Prove it renders in a browser with MapLibre GL. Purpose: D-08 (full Rams pass on basemap), D-09 (derived from tokens, not hand-tuned), D-10 (Rams font assets for labels), roadmap success criterion 3. Output: `theme/basemap/` — build script, generated style JSON, browser proof page. @planning/phases/01-design-tokens-rams-foundation/01-CONTEXT.md @planning/phases/01-design-tokens-rams-foundation/01-01-SUMMARY.md @stitch_minimalist_route_navigator/telemetry_rams/DESIGN.md Derivation targets (map token → cartography role per D-08 / telemetry_rams/DESIGN.md "Grayscale Map Grounding"): - Land/background ground tone: between `soft-beige` (#D9D2C6) and `chassis-subtle` (#CCC4B5) - Water: desaturated mid-gray derived from `matte-black` mixed toward low-saturation (rgba derived, NOT brand hues) - Roads: desaturated, hierarchy by weight only (major roads slightly darker than minor) - Buildings: light gray within the `#D9D2C6 → #1A1A1A` calibrated gray band - Labels: `matte-black` at reduced opacity; fonts Space Grotesk (major) / Geist (minor) — same assets as theme/fonts (D-10) - `signal-orange` (#EB8008), `functional-red` (#ED3F1C), `olive-green` (#736B1E) must NOT appear in the basemap — accent is reserved exclusively for the route line rendered in Phase 5 (D-08). Style input: `https://tiles.openfreemap.org/styles/liberty` style JSON (fetch at build time and fork; do not vendor the full original JSON — build.mjs fetches, transforms, writes). task 1: basemap build script deriving style from tokens theme/basemap/build.mjs, theme/basemap/liberty-rams.json 1. Create `theme/basemap/build.mjs` (Node ESM, no external deps beyond node builtins): fetches `https://tiles.openfreemap.org/styles/liberty` style JSON, transforms it, writes `theme/basemap/liberty-rams.json`. 2. Derivation step: parse `theme/src/tokens.css` extracting the `--color-*` custom property values (deterministic regex `-\-color-([a-z0-9-]+):\s*(#[0-9A-Fa-f]{6})`). Compute the Rams cartography palette IN SCRIPT (D-09: derived, not hand-tuned): desaturate all land/road/water/building colors to the calibrated gray band via a small `desaturate(hex, satMultiplier)` helper (pure function: convert hex→HSL, multiply saturation toward ~0.06–0.12, adjust lightness per role per Interfaces table). Map transformed values onto the liberty style's `layers[].paint` color properties (`background`, `water`, `road-*`, `building-*`, `landcover-*`, `bridge-*`). 3. FONT STEP: replace liberty's glyph/name font stacks. Point the style's `glyphs` to the OpenFreeMap hosted glyphs endpoint (keep `https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf` to avoid self-hosting pbf glyphs in Phase 1), and rewrite every `layout["text-font"]` value to `"Space Grotesk Medium"` for major label layers (place-city, place-subtown and above) and `"Noto Sans Regular"` fallback chain ending `"Space Grotesk Regular"`… — IMPORTANT constraint: OpenFreeMap glyphs server only serves font stacks it hosts. If `Space Grotesk` is not available on the glyphs endpoint, detect a 404 at build time and fall back to the closest hosted stack, recording the substitution in a `"metadata"` block (`"rams-font-substitutions": {...}`) — this keeps D-10 satisfied in spirit (Rams typography for labels) with honest fallback provenance. 4. Common concerns: signal-orange/functional-red/olive-green token names are excluded from the derivation set (guard: throw if any of those 3 hex values end up anywhere in the output layers' paint properties). 5. Add npm script to `theme/package.json`: `"basemap": "node basemap/build.mjs"`. 6. Run it; verify `theme/basemap/liberty-rams.json` exists, is valid JSON, has `version: 8`, `glyphs`, `sources`, `layers[0].paint` uses a transformed neutral. npm run basemap --workspace theme && node -e "const s=require('./theme/basemap/liberty-rams.json'); if(!s.layers||!s.glyphs) process.exit(1); const rgx=/EB8008|ED3F1C|736B1E/i; const bad=JSON.stringify(s.layers).match(rgx); if(bad){process.exit(2)} console.log('OK layers='+s.layers.length)" build.mjs derives all basemap colors from tokens.css (no hardcoded cartography hex in the script beyond token parsing), output JSON is a valid MapLibre style, and none of the 3 accent colors appear anywhere in the layer paint properties. task 2: browser proof page (dummy route + rendered basemap) theme/basemap/index.html, theme/package.json 1. Create `theme/basemap/index.html`: static MapLibre GL viewer (CDN unpkg `maplibre-gl@^4`), `