What it does
Profile → Appearance (screen-Ap-Perfil-Aparencia) offers three themes (ADR-0016):
| Theme | testID | Looks |
|---|---|---|
| Monochrome | appearance-monochrome | the default: the tokens of handoff/design/DESIGN.md, the car photo the only colour |
| Car colour | appearance-car | the page tinted by the current car's Cover Colour, fading to black; cards and controls translucent over it |
| Warm | appearance-warm | coffee-brown surfaces and cream text |
The primary pill stays white and green, red and orange stay reserved for state in every theme. Under Car colour, "Transition between cars" (appearance-transition) cross-fades the tint over 400 ms when another car comes into focus; Reduce Motion turns the fade off whatever the switch says. "Colour on the showcase too" (appearance-showcase, off by default) lets the public showcase take the cars' colours.
Which colour
On a car's screens (M-Veiculo and the boards under it) the tint is that car's Cover Colour: each screen calls useAppearanceCar(vehicleId), which sets the car in the appearance store on focus, so the tab bar outside the screen's tree takes it too. Elsewhere it is the first current car whose colour tints (firstTint), so one silver car does not keep the app grey. No colour, or a seed with chroma under 0.03 (white, silver, grey, black cars), means Monochrome. The buyer's passport preview passes null: the passport never shows a car colour.
tintedTheme(seed) in @autofolio/tokens/themes derives the whole palette from one seed, clamped in OKLCH (lightness 0.28–0.34 at the top of the page, chroma at most 0.12). themes.test.ts checks WCAG AA for every text colour and the primary pill on every surface, for each theme and 24 seeds around the hue wheel; it is the gate for any change to the palettes.
Cover Colour
The cron tick's coverColors job (vehicle-photo.jobs.ts) reads each cover once on the server and stores vehicle_photo.dominant_color (hex) and color_checked_at, so the same job is the pipeline and the backfill. vehicle-photo.color.ts reads only each 8×8 JPEG block's DC coefficient (a 1/8-scale image; a Worker cannot hold a decoded 12-megapixel photo), weights the centre 60 % toward the middle, clusters with k-means and keeps the cluster with the most weight × saturation. The garage list returns it as cover.color; the app never decodes images for it.
shortcut: PNG, WebP and arithmetic-coded JPEG covers give no colour (Monochrome); decode them when a cover in those formats needs one.
Where the choice lives
The device's choice is in expo-secure-store and read synchronously at start-up through a useSyncExternalStore store (platform/theme/appearance.ts), so the first frame has the right colours. It is also saved on the account (account_preference.appearance, and showcaseColor for the showcase switch, through accountPreference.update), so a new phone starts with it; once a device chooses, its own choice wins.
Components read colours only through useAppTheme(). apps/mobile/src/design/theme-seam.test.ts fails on any hex or rgb() literal outside design/tokens.ts, since such a colour would stay Monochrome under the other themes.
The signed-in web app takes the same three themes from Settings → Appearance: applyAppearance sets data-theme and the --af-color-* variables on <html>. The marketing site and the passport at /p/:token stay as designed.
Dev boards
autofolio://dev/Ap-Perfil-Aparencia opens the screen; Ap-Garagem-Mono, Ap-Garagem-Cor, Ap-Garagem-Quente, Ap-Veiculo-Mono, Ap-Veiculo-Cor and Ap-Veiculo-Quente set the theme on the device and open M-Garagem or the seeded M3.
Deferred: share-card backgrounds in the car's colour (ADR-0016's implementation notes).