Status, 2026-08-07: received from the design project (
Map Styles.htmllives there, with the live mockups and the copy-ready palette blocks this spec refers to) and built the same day - first at the spec's own v1 scope (field+night_hike+ the detail control, red light included, PR #345), then in full once the mockups' palette blocks arrived: all five styles, each with its reviewed day and night sheets, inclient/src/map/liveTopo.ts's SHEET_VARIANTS table, per-card tuning included (ridgeline's 0.55 relief and early contours, field's sunlight type). MAP_OPTIONS.md §6 records the choices made where this spec left room. The spec itself is kept verbatim below - it is what code comments citing MAP_STYLE_SPEC.md mean.
PR-shaped spec, written against main (2026-08-06). Companion to the live
mockups in this project's Map Styles.html, which render every palette below
on the real stack (OpenFreeMap tiles, 3DEP contours, liveTopoLayers() verbatim).
Three independent preferences, all display-only (never a map rebuild — the
same rule attachContourUnits already enforces):
| Preference | Values | Default |
|---|---|---|
mapStyle |
quiet_pine · field · night_hike · parchment · ridgeline |
field (day) |
mapMode |
day · night · auto (follows prefers-color-scheme) |
auto |
mapDetail |
full · standard · minimal |
standard |
night_hikealso carries ared_lightsub-mode (settings toggle, never default).- v1 could ship only
field+night_hike+ the detail control and still be most of the value; the other palettes are additive.
TOPO_PALETTE stops being one constant and becomes a lookup keyed by
(mapStyle, mapMode). Layer stack unchanged — every entry has the same 20 keys.
Field / day (the reviewed favorite — 1b in the mockups):
export const TOPO_PALETTE = {
wood: '#dcebd2', scrub: '#e8f0dd', wetland: '#cfe3d8', rock: '#eae6da',
park: '#d3e6c6', parkEdge: '#5f8f57',
water: '#8fc0dc', waterEdge: '#2e79a6', waterway: '#2e79a6',
contour: '#8a6c42', contourIndex: '#5f4527', contourLabel: '#4a3620',
// Roads/tracks NEUTRAL GRAY on purpose: nothing on the ground may share
// a hue with a blaze color (review finding, 2026-08-06).
roadMajor: '#dad6ca', roadMajorEdge: '#8e897a', roadMinor: '#ddd9cd',
track: '#7b776b', path: '#55503f',
boundary: '#6f6753', label: '#14130f', labelHalo: '#ffffff',
} as const
// style.ts: MAP_BACKGROUND_COLOR = '#ffffff'
// hillshade: shadow #4a4234 · highlight #ffffff · accent #6f6753
// water_name text-color #1c5c86 · trail casing #14130f
// HILLSHADE_EXAGGERATION (hiking-zoom end) = 0.30
// Field extras: peak text-size 12→14, contour label 10→11, halos 1.4→1.8All other palettes (field/night, quiet_pine day+night, night_hike dark+red,
parchment day+lantern, ridgeline day+night) are in Map Styles.html — each
card's "Drop-in TOPO_PALETTE" block, copy-ready. Red-light additionally
overrides line-color on trail-blaze to #e8804a (blaze identity moves to
the tapped trail card).
Pure layer visibility — setLayoutProperty(id, 'visibility', …) on a live
map, via a whenStyleReady attach helper exactly like attachContourUnits:
| Layer | full | standard | minimal |
|---|---|---|---|
topo-boundary (admin borders) |
✓ | — | — |
topo-track, topo-road-minor |
✓ | ✓ | — |
topo-contour (minor lines) + topo-contour-label |
✓ | ✓ | — |
topo-water-label, topo-scrub |
✓ | ✓ | — |
| index contours, paths, peaks, places, major roads, water, trails | ✓ | ✓ | ✓ |
Borders default OFF (standard): wanted sometimes, distracting mostly. Minimal keeps index contours so terrain still reads, and keeps paths because side trails are hiker signal, not clutter.
lib/userPreferences.ts: add the three keys (+red_light), persisted likeunits.MapView.tsx:mapStyle/mapModestay OUT of the map-building effect's deps; anattachMapAppearance(map, prefs)helper re-paints viasetPaintPropertyper layer (palettes share layer IDs, so one loop covers all 20+ paint targets).mapDetailis the visibility loop above. Neither tears down WebGL under a hiker.automode: listen tomatchMedia('(prefers-color-scheme: dark)'), apply the style's night palette; night_hike is the auto-dark forfield.- App chrome should follow
mapMode(pine-900 chrome in night modes). - Tests: palette tables are data — snapshot per (style, mode) that every key is a valid hex and layer IDs resolve; detail matrix asserted per level.
- Does
mapStylebelong in onboarding or only Settings? (Suggest: Settings only; default field/day + auto night_hike is right for most.) - Parchment as the forced style for print/export views?
- Should
minimalalso thin place labels to towns+ at hiking zooms?