---
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`), `