Skip to content

Adopt the DMC house style, and a light theme - #141

Merged
khagele merged 6 commits into
mainfrom
dmc-house-style
Sep 10, 2026
Merged

khagele merged 6 commits into
mainfrom
dmc-house-style

Conversation

@khagele

@khagele khagele commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

The triangulator was its own island: teal accent on a teal-dark ground, Avenir Next throughout, no light theme, and a tricolour stripe in colours that were not the brand's. DutchMeshCore-Branding is the source of truth for the DMC identity and the app had never been held against it.

Closes #139, #140.

1 · Tokens, type, background (65bd110)

Canonical token names from STYLE-GUIDE.md — --text-muted, --text-faint, --surface, --border — with the brand constants and semantic state on top. No bridge aliases: an alias keeps the divergence alive, which is the thing the issue is about.

  • Aldrich for headings and the brand, IBM Plex Sans for body, IBM Plex Mono for data, served as woff2 from assets/fonts/ (75KB) and packaged into the image.
  • The red/blue glow ground with the grid and scanline overlays the guide layers on it. Panels take the glass fill.
  • One --radius (8px) and --radius-lg (16px), replacing eight ad-hoc values between 8 and 24px.
  • The brand stripe becomes a full-width band above the navbar, as the guide calls for, instead of a 4px vertical bar beside it in near-flag hexes.

Every chrome literal moved into :root. The only hex left outside it is in a comment.

2 · Light theme (8168d19)

There was none — no data-theme, no prefers-color-scheme, nothing. Light is an override on the same token names, so no rule below :root changed. Pre-paint script in <head> so a light user never sees a flash of dark; color-scheme per root block so native selects and scrollbars follow.

dark | light | system, under the canonical meshcore-theme key so the choice carries across the DMC subdomains, following system as it changes.

This meant tokenising 64 more rgba() literals by role: a hardcoded rgba(5,15,22,.9) field stays dark whatever the theme says.

3 · Map colours (aec4615)

Thirty literals across the MapLibre layers — magenta coverage, lime observers, a blue-green-yellow-orange-red heat rainbow, teal links — several of them fighting for the same meaning. Now one MAP object with three roles:

green what is known — observers, proven links, support nodes
blue what is assumed — coverage ranges, the selected region
warm where the target is — heat ramp, estimate markers

The heat ramp runs --yellow to --red with three interpolations, so hotter reads as one direction instead of a rainbow colliding with the other two roles.

These stay literals rather than reading CSS tokens for two reasons: MapLibre paint takes colour strings, and the basemap is the light OSM raster in both themes, so a theme-flipping colour would be unreadable half the time. They are brand constants, which are theme-identical by definition.

4 · Three fixes found on the way

Result

before after
var(--token) uses 90 249
hex outside :root in CSS 46 unique 2 (one in a shadow, one in a comment)
map colours 30 loose literals one palette, three roles
themes 1 dark, light, system

Verified

Full run at 1200×780 and 375×812 in both themes: discovery, lock, estimate, the map layers painting green/blue/warm as designed, both dropdowns hit-tested as topmost, fields and menus following the theme, fresh loads confirming the gesture mode at each size.

Not verified end-to-end: a window crossing the 861px breakpoint mid-session. Neither resize nor matchMedia change fires under the test harness's viewport emulation — a probe listener confirmed zero events — so that path was verified by calling the sync directly.

I hit the upstream feeds' rate limits twice while testing, which AGENTS.md warns against.

🤖 Generated with Claude Code

khagele and others added 6 commits September 10, 2026 13:45
First of three parts on #139. The app was its own island: teal accent on a
teal-dark ground, Avenir Next throughout, 125 hex literals against 90 token
uses, and a tricolour stripe in colours that were not the brand's.

Tokens now come from DutchMeshCore-Branding/STYLE-GUIDE.md under their
canonical names — --text-muted, --text-faint, --surface, --border — with the
brand constants (--red/--white/--blue/--green/--yellow) and semantic state
(--success/--warning/--danger) on top. No bridge aliases: an alias would keep
the divergence alive, which is the thing this issue is about. Every chrome
literal moved into :root; the only hex left outside it is one in a comment.

Type: Aldrich for headings and the brand, IBM Plex Sans for body, IBM Plex Mono
for data, served as woff2 from assets/fonts/ (75KB total) and packaged into the
image. Headings are uppercase with the house drop shadow.

Ground: the red/blue glow over a dark fade, plus the grid and scanline overlays
the guide layers on it, all behind a z-index:1 content layer. Panels take the
glass fill instead of a flat white wash.

Geometry: one --radius (8px) for controls and --radius-lg (16px) for large
surfaces, replacing eight ad-hoc values between 8 and 24px.

The brand stripe becomes what the guide calls for: a full-width band in normal
flow above the navbar, rather than a 4px vertical bar beside it — the same idea
in the wrong orientation, in near-flag hexes instead of the brand ones.

Refs #139

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Second of three on #139. The app had no light theme at all — no data-theme, no
prefers-color-scheme, nothing.

Light is an override on the same token names, so no rule below the :root blocks
changed. Dark stays the default: no attribute on <html> means dark. A pre-paint
script in <head> applies the saved choice before the stylesheet renders, so a
light user never gets a flash of dark, and color-scheme is set per root block so
native selects and scrollbars follow.

The picker is dark | light | system, persisted under the canonical
meshcore-theme key so the choice carries across the DMC subdomains, following
system as it changes. It reuses the topbar's existing <details> dropdown so the
bar has one pattern, with the guide's menuitemradio + checkmark semantics.

Getting there meant tokenising 64 more rgba() literals by role — fields, menus,
scrims, hover fills, borders — since a hardcoded rgba(5,15,22,.9) field stays
dark whatever the theme says. Their dark values also moved off the old teal
tints onto the neutral near-black ground.

Two literals stay: the text-shadow outline on map labels, which has to read
against a basemap that is light in both themes.

Refs #139

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Last of three on #139. Thirty colour literals were spread through the MapLibre
layer definitions — a magenta for coverage, lime for observers, a blue-green-
yellow-orange-red rainbow for heat, teal for links — none of them from the
palette, several of them fighting each other for the same meaning.

They now come from one MAP object with three roles, so six layers do not
compete over four hues:

  green  what is known       observers, proven links, support nodes
  blue   what is assumed     coverage ranges, the selected candidate region
  warm   where the target is heat ramp, estimate markers

The heat ramp runs --yellow to --red with three interpolations between them,
so hotter reads as one direction instead of a rainbow that collided with the
other two roles. Unselected candidates and the clue hull drop to neutral.

These stay literals rather than reading CSS tokens, for two reasons: MapLibre
paint takes colour strings, and the basemap is the light OpenStreetMap raster
in BOTH themes — a colour that flipped with the theme would be unreadable half
the time. They are the brand constants, which are theme-identical by
definition.

Refs #139

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The z-index:1 content layer added with the background overlays put .topbar and
.shell on the same level. At equal z-index the later element in the document
wins, so the map strip's buttons — Ranges, Links, Heat, Relief — cut straight
through the What's new panel and the theme menu hanging down from the topbar.
The panels' own z-index:20 could not help: it only ranks them inside the
topbar's stacking context, not against the shell's.

The topbar goes to z-index:2. Verified by hit-testing the centre of each open
panel: both now return themselves as the topmost element.

Refs #139

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"One or two sentences" was still too loose: two long sentences render as five
or six lines in a 250px panel, which is the wall of text the cap was meant to
prevent. All eight of today's entries come down to ~120 characters, and
AGENTS.md now says lines rather than sentences.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cooperativeGestures was on everywhere, so every zoom needed cmd/ctrl. It is
there for a real reason (#117): on the stacked layout the map is full-width and
sits in the page's scroll path, so without it a swipe over the map pans the map
and you can never scroll past it.

None of that holds from 861px up, where the map is a sticky column of its own
and the page scrolls beside it. So the gesture mode follows the layout instead
of being global, and syncMapGestures() keeps it in step when a window crosses
the breakpoint.

Verified by fresh load at both sizes: 375px keeps cooperative gestures on,
1200px has them off with scrollZoom enabled. The mid-session crossing is
verified by calling the sync directly — neither resize nor matchMedia change
fires under the test harness's viewport emulation, which a probe listener
confirmed, so both are wired and only the initial state was observable
end-to-end.

Closes #140

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khagele
khagele merged commit 0d1abaa into main Sep 10, 2026
2 checks passed
@khagele
khagele deleted the dmc-house-style branch September 10, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The triangulator does not follow the DMC house style

1 participant