CARTO stamps an "API KEY REQUIRED" watermark onto every basemap tile fetched without a key since 26.08.2026, and both CartoDB presets plus the built-in fallback are CARTO. The key is free and needs no account, so add it as a setting rather than dropping the provider. carto_api_key sits next to mapbox_access_token: per user in Settings/Map, instance-wide under Admin/User Defaults, encrypted at rest, injected from CARTO_API_KEY on managed instances. Unlike Mapbox it never switches the map provider, since a raster key says nothing about the renderer. The key is appended as ?key= at render time instead of being stored in the template, so a saved URL stays portable and survives a rotation. That is also what reaches the four maps that pinned CARTO and ignored the user's tile setting: atlas, collections, the public trip share and the public journey. The two share pages read it from their token-gated payload, the same way baseCurrency already travels. Also lifts the release notice above page chrome. Its overlay sat at z-index 50, under the Vacay mode toolbar at 61, so the dialog was neither on top nor modal. Adds a six-step layer scale to index.css and moves the three notice shells onto it. Closes #2054 Closes #2052
4.4 KiB
Appearance & theming
TREK's look is driven by design tokens (CSS custom properties) so a single per-user config can re-skin the whole app — color scheme, accent, transparency, text size, density — without touching component code. New pages get this for free if they follow the contract below.
How it works
- The per-user config is one validated blob (
AppearanceConfigin@trek/shared), stored under theappearancesettings key. applyAppearance()(src/theme/applyAppearance.ts) is the only code that writes styling to the DOM: it toggles.dark, setsdata-scheme/data-no-transparency/data-density/data-reduce-motionon<html>, and writes the custom-accent + type-scale CSS variables.- A render-blocking
public/theme-boot.jsreplays a cached snapshot before first paint to avoid FOUC (external classic script — the prod CSP blocks inline scripts). - Schemes are data: swatch metadata in
src/theme/schemes.ts, token values in the[data-scheme="…"]blocks insrc/index.css.
Token taxonomy
| Purpose | Token / utility |
|---|---|
| Surfaces | --bg-primary/secondary/tertiary/elevated/card/input/hover/selected · bg-surface* |
| Text | --text-primary/secondary/muted/faint · text-content* |
| Borders | --border-primary/secondary/faint · border-edge* |
| Accent | --accent (fill) · --accent-text (on fill) · --accent-on (on surface) · --accent-hover · --accent-subtle · bg-accent / text-accent-on / bg-accent-subtle |
| Status | --success/-soft, --danger/-soft, --warning/-soft, --info/-soft · bg-danger-soft text-danger etc. |
| Elevation | --shadow-sm/md/lg/card/elevated/modal/dropdown/popover · shadow-modal etc. |
| Overlay / inverse | --overlay · --bg-inverse / --text-inverse · bg-inverse text-inverse-text |
| Type tiers | text-title / text-subtitle / text-body / text-caption (scale with the user's per-tier multipliers) |
Layering
Fixed and sticky surfaces read their stacking order from one scale in
src/index.css, so two of them cannot end up in the wrong order by accident.
| Token | Value | For |
|---|---|---|
--z-bar |
60 | BottomNav, page-bound sticky bars |
--z-nav |
200 | the desktop navbar |
--z-modal |
10000 | shared/Modal, ConfirmDialog |
--z-overlay |
99990 | page-owned full-screen overlays |
--z-notice |
99998 | app-wide system and release notices |
--z-toast |
100000 | toasts, tooltips, context menus |
A new fixed surface docks onto the step it belongs to (z-[var(--z-modal)] as a
class, z-index: var(--z-modal) in CSS) instead of inventing a number. Older
files still carry literal values; the steps are far enough apart that those keep
their current position until the file gets converted.
The contract (enforced by npm run theme:lint)
- Surfaces/text/borders use the semantic utilities (
bg-surface*,text-content*,border-edge*) orvar(--token)— never rawbg-slate-*/text-gray-*/bg-white. - The primary/"black" action look uses
bg-accent+text-accent-text(+ring-accent) — neverbg-slate-900/bg-black/bg-indigo-*. This is what lets a user's accent reach new surfaces automatically. - Semantic text size uses the tier utilities
text-title/subtitle/body/caption— never an inlinefontSize: <px>, never rawtext-sm/text-xsto mean a tier. - Translucent/blurred surfaces consume an alpha/glass token
(
--bg-elevated,--tooltip-bg,--glass-*) or abackdrop-blurutility, so transparency-off can neutralize them centrally — never inlinergba()backgrounds orbackdrop-filterin JSX. - Components never read
AppearanceConfigto compute styles. OnlyapplyAppearancereads the config and writes tokens; components read tokens. Dark state is read off the.darkclass, never duplicated into React state. - New appearance dimensions are added to
AppearanceConfig+applyAppearance+schemes.ts— never a second applier or a parallel token family. New feature palettes derive their accent fromvar(--accent).
Allowed to stay inline / literal
Genuinely dynamic values (data-driven colors like cat.color, computed
geometry/transforms/sizes), and the surfaces CSS variables can't reach: injected
map popup/marker HTML, Mapbox/MapLibre paint, and the standalone @react-pdf
documents. Mark intentional exceptions with a theme-lint-disable line comment.