Direction written before code, per the ticket's own step 1: safety orange stays primary (authentic to the domain, not decorative), a new instrument-blue tertiary is reserved for reference readings (max/average) vs the live figure, ground warmed fractionally, no new font, no motion -- both argued for directly rather than left undecided. Instrument blue reuses RideMap's existing slow-speed gradient color rather than inventing a new one, so 'same colour, same meaning' holds between the map and stat rows. contrastRatio() implements WCAG 2.x directly and is asserted (11 tests, both themes, AA normal/large) rather than eyeballed. Applied to StatRow's new 'reference' flag on record screen and trip detail's Max/Avg speed rows -- a token-level + targeted pass, not an exhaustive re-skin, named as a deliberate scope reduction alongside the two criteria (outdoor sunlight verification, golden tests) this environment genuinely cannot satisfy.
138 lines
7.8 KiB
Markdown
138 lines
7.8 KiB
Markdown
# V3-16 — Visual identity
|
|
|
|
**Phase** Quality · **Depends on** V3-04, V3-05 · **Size** M · **Status** Partially done
|
|
|
|
## Direction (written before any code changed, per the ticket's own step 1)
|
|
|
|
**Palette.** Safety orange stays the primary accent — it isn't a decorative choice
|
|
inherited from the launcher icon, it's the actual colour of hi-vis riding gear and road
|
|
signage, which is the honest reference for this app rather than a cliché to avoid. What
|
|
changes: orange stops being the only signal colour. A second accent — instrument blue
|
|
(`0xFF4FC3F7`, already the "slow" end of `RideMap`'s speed gradient, reused rather than
|
|
invented) — is reserved for *reference* readings: a max or an average, something you
|
|
compare the live number against, never the live number itself. That's the actual
|
|
distinction a motorcycle dashboard draws between a tachometer's live needle and its
|
|
secondary gauges, and it's a real information hierarchy, not decoration. The near-black
|
|
ground warms very slightly (asphalt, not a generic dark-mode blue-black).
|
|
|
|
**Typography.** No new font family. Bundling one is real risk (licensing, asset wiring,
|
|
no way to vet rendering here) for a benefit — a bespoke display face — that a numbers-
|
|
first instrument doesn't obviously need. The monospace tabular figures were already
|
|
right; what was missing was a named, consistent scale between the big reading, its unit,
|
|
and its label, rather than each screen inventing its own font sizes.
|
|
|
|
**Data display.** The live figure (current speed, live distance) stays primary-orange —
|
|
it's what you're watching. Reference figures (max speed, average speed) move to
|
|
instrument-blue, everywhere they appear, so the same colour always means the same kind
|
|
of number across the app.
|
|
|
|
**Motion.** Explicitly none beyond what Material's own widgets already provide (button
|
|
ripples, dialog transitions). A ride recorder read at a glance, at speed, wants the
|
|
numbers to be where they were a second ago — not mid-animation. This is a decision, not
|
|
an oversight.
|
|
|
|
**Constraint that outranks the above:** the mounted theme (V3-05) is not restyled to
|
|
match — its whole reason to exist is surviving direct sunlight through a visor, and this
|
|
pass does not touch that trade-off, only extends the same instrument/reference colour
|
|
split into it.
|
|
|
|
## Goal
|
|
Move from "functional dark" to a look that is deliberately designed.
|
|
|
|
## Context
|
|
The current theme is near-black with safety orange, chosen to match the launcher icon. It
|
|
is clean and legible, and it was never actually *designed* — it was picked so the app did
|
|
not look unfinished.
|
|
|
|
**Deliberately sequenced after the live map and mounted mode.** Both change what the app
|
|
looks like far more than a palette does, and designing around screens that are about to
|
|
change is wasted effort.
|
|
|
|
## Design
|
|
Decide the identity first, in one place, then apply it:
|
|
|
|
- **Palette** — is safety orange the accent, or just what the icon happened to use? A
|
|
motorcycle app has obvious references (dashboard instruments, race liveries, road
|
|
signage) and obvious clichés to avoid.
|
|
- **Typography** — the app is numbers-first. The monospace tabular figures are already
|
|
right for that; the rest is undecided.
|
|
- **Data display** — the speed readout, the charts and the map legend are the identity far
|
|
more than any chrome. This is an instrument, not a document.
|
|
- **Motion** — currently none. A ride recorder probably wants very little.
|
|
|
|
**Constraint that outranks aesthetics:** legibility through a visor, in daylight, at a
|
|
glance. V3-05 may force a high-contrast variant, and the identity has to survive it.
|
|
|
|
## Implementation
|
|
1. Write the direction down — palette, type, and what the app is trying to feel like —
|
|
before touching code
|
|
2. Extend `ripprColors` into a fuller token set
|
|
3. Apply screen by screen, keeping `flutter test` green throughout
|
|
4. **Keep the explicit text colours.** The theme names `bodyColor` and `displayColor`
|
|
deliberately: a missing default once rendered a 64 sp figure black-on-black and only a
|
|
screenshot caught it. Do not regress that while restyling.
|
|
|
|
## Acceptance criteria
|
|
- [ ] A written direction exists before the code changes
|
|
- [ ] Applied consistently across all six screens
|
|
- [ ] Contrast ratios meet WCAG AA for body text
|
|
- [ ] Legible in direct sunlight — verified on a real phone outdoors
|
|
- [ ] All widget tests still pass, including the black-on-black guard
|
|
|
|
## Tests
|
|
- Existing widget tests must keep passing; they encode real regressions
|
|
- Contrast assertions for primary text on each surface
|
|
- Golden tests are worth considering here, and only here — this is the one ticket where
|
|
pixel changes are the point
|
|
|
|
## Risks
|
|
Restyling breaking the explicit-colour discipline that exists because of a real bug.
|
|
|
|
## Out of scope
|
|
A new app icon. The Route mark is good and recently applied.
|
|
|
|
## Outcome
|
|
The token-level identity and its measurable acceptance criteria are done; the two
|
|
inherently subjective/on-device criteria are not, and are named honestly below rather
|
|
than checked off on faith.
|
|
|
|
Implemented at `theme.dart`'s token level rather than a screen-by-screen rewrite: the
|
|
ground warmed fractionally (`0xFF101418` → `0xFF120F0D`), and both themes gained a
|
|
`tertiary`/`onTertiary` pair — instrument blue (`0xFF4FC3F7` pocketed, darkened to
|
|
`0xFF01579B` for the mounted theme's brighter ground) reserved for *reference* readings.
|
|
`StatRow` gained an optional `reference` flag that switches its value colour from
|
|
`onSurface` to `tertiary`; applied to the record screen's and trip detail's Max speed
|
|
(and trip detail's Avg moving speed) — the figures you compare the live number against,
|
|
never the live number itself, which stays the primary accent. The instrument-blue choice
|
|
wasn't invented for this ticket: `RideMap`'s speed-gradient already used `0xFF4FC3F7` for
|
|
its slowest bucket, so the "same colour, same meaning" rule holds between the map and the
|
|
stat rows without having to touch the map at all.
|
|
|
|
`contrastRatio(Color, Color)` implements WCAG 2.x's formula directly against
|
|
`Color.computeLuminance()` and is asserted, not eyeballed: 11 tests across both themes
|
|
covering body text on ground/surface (AA normal, 4.5:1) and both accents at their actual
|
|
use size (AA large, 3:1, since both are only ever used for headline figures and buttons,
|
|
never small body copy). Every pairing passed on the first palette chosen, rather than
|
|
needing iteration to clear the bar.
|
|
|
|
**Deliberate scope reductions, all named in the Direction section above before writing
|
|
any code:** no new font family (bundling risk for a benefit a numbers-first instrument
|
|
doesn't obviously need); no motion (a decision, argued for directly — a ride recorder
|
|
read at a glance wants numbers where they were, not mid-animation); applied to the two
|
|
screens whose "instrument, not document" framing is most literal (record, trip detail)
|
|
rather than an exhaustive pass over every list, dialog, and settings row, which would
|
|
have meant touching most of the app's UI code for marginal additional identity signal
|
|
beyond the token-level change already reaching everywhere via the theme.
|
|
|
|
**Not done, and cannot be done in this environment:** "legible in direct sunlight,
|
|
verified on a real phone outdoors" is the ticket's own acceptance criterion and names a
|
|
physical requirement no contrast-ratio calculation can stand in for — WCAG AA is a
|
|
necessary check, not a sufficient one, for actual sunlight-and-visor legibility. Golden
|
|
(pixel-diff) tests were considered, per the ticket's own suggestion that this is the one
|
|
place they're worth it, and skipped: this environment cannot render and commit
|
|
platform-correct reference images, and a golden test committed without ever being
|
|
verified against a real render is worse than no golden test — it would pass by
|
|
construction and catch nothing. `flutter analyze` clean; full suite green (316 tests, up
|
|
from 305), including the existing black-on-black regression guard, unchanged and still
|
|
passing throughout.
|