Skip to content

Put the app on the culpeo palette - #72

Merged
dfalling merged 1 commit into
mainfrom
simplify-color-scheme
Aug 17, 2026
Merged

Put the app on the culpeo palette#72
dfalling merged 1 commit into
mainfrom
simplify-color-scheme

Conversation

@dfalling

Copy link
Copy Markdown
Owner

Summary

Moves the app off the blue/teal/purple/green scheme onto the same ten semantic roles the web app was rewritten onto (canvas, surface, ink, muted, line, accent, accentText, onAccent, danger, plus three alpha variants). One declaration per role, no isDark ? … : … at any use site.

The old palette had four accent hues carrying meaning that wording and icons already carried, and its neutrals were cool greys against what is now a warm canvas — effectively two palettes fighting.

What changed

Accent is spent only where the guide sanctions it — primary button, map pins, focus rings, selected states. Badges, chips, category labels and the avatar go neutral. The "Completed" tag traded green for a ✓.

Search results no longer distinguish by hue. The three result kinds had accent/purple/grey circles behind their glyphs — three colours ~1.1:1 apart in luminance, i.e. indistinguishable without colour vision. They share one neutral treatment now; the section headings already say which is which.

Colours that came from dependencies, not from us. None would turn up in a grep of this repo:

Surface Was Now
MapLibre location puck #33B5E5 hardcoded in its layer paint replaced via <UserLocation>'s children
Emoji picker fixed light theme — opened as a white sheet in dark mode its theme prop, mapped onto tokens
<Switch> Material colorPrimary solid muted on / line/30 off
Every <ActivityIndicator> platform colorAccent explicit
Caret, handles, ripple, pre-JS window AppCompat defaults styles.xml + new values-night/

Basemap libertypositron — 44 declared colours at 0.178 mean saturation down to 29 at 0.026, for the same reason web moved to a dataviz style. The pins are the content; the map is context.

Navigator theme built from our tokens instead of React Navigation's, so the gap during a push transition is the canvas.

Two corrections to the shared palette

Both apply to web too, at the same ratios:

  • Focus was invisible. Inputs used the same neutral border at rest and focused, so focusing a field produced no visible change — a WCAG 2.4.7 failure that looked fine in the code. New TextField moves the border to accent with a ring, and owns the placeholder/caret/selection colours.
  • line was doing two jobs with two different bars. As a divider it can be as quiet as it likes; but it was also a text input's border, and surface is only 1.06:1 against canvas (1.10:1 dark), so the border is the whole affordance. In dark mode that measured 1.32:1 — a field you can't find at night. Split out lineControl (#7a6f69, one value for both schemes) for load-bearing boundaries. Dividers keep line and are unchanged.

Verification

tsc, biome check, 51 tests, and :app:processDebugResources all pass.

Every control boundary and text pairing clears WCAG AA in both schemes — worst case 3.29:1 against the 3:1 non-text bar, 4.04:1 → 13.24:1 for chip text after moving line/15 fills to ink (muted on line/15 fails AA in light).

Not yet driven on a device. A linter can't catch "focus produces no visible change," so the states that need eyes on them: a focused field, the Completed toggle, the emoji picker, the map at night, and a cold start for the window background.

Findings worth porting back to web are written up in addendum.md alongside the style guide.

The blue/teal/purple/green scheme had four accent hues doing work that
wording and icons already did, and its neutrals were cool greys sitting
against what is now a warm canvas — two palettes running against each
other. This moves to the same ten roles the web app was rewritten onto,
so a place looks the same wherever it's opened and there is one
declaration per role rather than one per shade.

Accent is now spent only where the style guide sanctions it: the primary
button, map pins, focus rings, and selected states. Badges, chips,
category labels, and the avatar go neutral. Search results were told
apart by the hue behind each glyph — three colours 1.1:1 apart in
luminance, which is no distinction at all without colour vision — and
now share one treatment, since the section headings above them already
say which is which.

Several colours turned out to be supplied by dependencies rather than by
us, and no grep of this repo would have found them: MapLibre hardcodes a
blue location puck in its layer paint, the emoji picker ships a fixed
light theme that opened as a white sheet in dark mode, and the switch,
every spinner, the caret and the ripple all drew from Android's theme
instead of ours. Those are pinned explicitly, via values-night/ where
the platform is the only thing that can reach them.

Two fixes that are corrections to the shared palette, not ports of it:

- Focus was invisible. Inputs used the same neutral border at rest and
  focused, so focusing a field changed nothing on screen. The new
  TextField moves the border to accent with a ring.
- `line` was doing two jobs with two different bars. As a divider it can
  be as quiet as it likes, but it was also the border of a text input —
  whose surface fill is only 1.06:1 against the canvas, so the border is
  the entire affordance. In dark mode that measured 1.32:1: a field you
  cannot find at night. Split out `lineControl` (one value for both
  schemes) for boundaries that are load-bearing; dividers keep `line`
  and are unchanged.

Basemap goes from `liberty` to `positron` for the same reason web moved
to a dataviz style: 44 declared colours at 0.178 mean saturation down to
29 at 0.026. The pins are the content, the map is context.

Every control boundary and text pairing now clears WCAG AA in both
schemes, worst case 3.29:1 against the 3:1 non-text bar. Not yet driven
on a device — the states that need eyes on them are a focused field, the
Completed toggle, the emoji picker, and the map at night.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dfalling
dfalling enabled auto-merge (squash) August 17, 2026 10:41
@dfalling
dfalling merged commit cc28b23 into main Aug 17, 2026
2 checks passed
@dfalling
dfalling deleted the simplify-color-scheme branch August 17, 2026 10:41
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.

1 participant