Skip to content

Color

Every color in Tomoda comes from frontend/constants/Themes.ts. The system is token-based: components reference semantic names (primary, surface, text) and the active theme decides what hex to render.

Two themes ship today: dark (default — the brand's primary mode) and light. Both are first-class — every component is built to work in either.

Surface ladder

The "tonal layering" depth model. Each step up the ladder adds about 5-8% in luminance. To create depth, place a higher-tier surface on top of a lower-tier one — no shadow required.

Dark

background #0e0e0f App root, scroll background
surface #18181a Cards, headers
surfaceContainerLow #1c1c1f Grouping containers
surfaceContainerHigh #252528 Interactive layers, inputs
surfaceContainerHighest #2e2e32 Popovers, focused inputs

Light

background / surface #FBFAF6 App root, cards — warm paper
surfaceContainerLow #f4f4f5 Grouping containers
surfaceContainerHigh #e4e4e7 Interactive layers, inputs
surfaceContainerHighest #d4d4d8 Popovers, focused inputs

Brand accents

Primary — duotone

The primary token is duotone across themes. Dark mode is gold-forward: primary is ember gold. Light mode is ink-forward: primary is the brand ink, so filled CTAs read as a confident dark chip on warm paper. Gold does not disappear in light mode, it moves to the Cozy Campfire accent family and the gold gradient.

Text and icons on a primary surface come from onPrimary (never a hardcoded literal): dark ink on the gold dark-mode primary, light paper on the ink light-mode primary. The gold gradient stays gold in both themes, so labels on it use onPrimaryGradient (dark ink in both).

primary (dark) #D4955A Ember gold
primaryContainer (dark) #673d00 Gradient end, deep bronze
primary (light) #0F0F10 Ink-forward light primary
primaryContainer (light) #fce3c5 Warm cream tint
onPrimary (dark) #0F0F10 Dark ink on the gold primary
onPrimary (light) #F7F5F0 Light paper on the ink primary
onPrimaryGradient #0F0F10 Dark ink on the gold gradient, both themes

Primary CTAs use a 135° linear gradient from primary to primaryContainer, giving the warm gold a metallic, brushed quality. Don't stop at flat fills. The gradient is gold in both themes; its labels use onPrimaryGradient.

Secondary — indigo

Used for non-primary interactive accents (links in certain contexts, secondary highlights). Picked deliberately as the cool complement to the warm gold.

secondary (dark) #818CF8
secondary (light) #4F46E5

Status colors

For success, warning, error states. Sized and saturated for clear differentiation in both modes.

success (dark) #34D399
warning (dark) #FBBF24
error (dark) #F87171
success (light) #059669
warning (light) #D97706
error (light) #DC2626

Online / active-now

online is the presence green used for active-now and online-now indicators (chat header status, friend dots, cluster popup dots, location "operational" state). Same value in both modes; the saturated green reads clearly on dark chrome and warm paper alike.

online (dark + light) #4CAF50 Active-now / online-now

Dock

dock is the frosted background of the bottom app-nav bar (components/layout/AppNav.tsx). Dark keeps a near-black glass; light uses the taupe surfaceContainerHigh tone at 0.82 alpha so the bar reads as a distinct elevated surface over the warm-paper background instead of blending into it.

dock (dark) rgba(10,10,11,0.75)
dock (light) rgba(229,225,216,0.82) Taupe elevated bar

Text

Three tiers per mode. Use the lowest-emphasis tier that still reads — every step up the contrast ladder is an attention cost.

text (dark) #FAFAFA Primary content
textSecondary (dark) #A1A1AA Labels, helper text
textMuted (dark) #52525B Disabled, metadata
text (light) #18181B Primary content
textSecondary (light) #71717A Labels, helper text
textMuted (light) #A1A1AA Disabled, metadata

Cozy Campfire — accent palette

A warm, autumn-toned set used for tag pills, earned-state highlights, decorative accents, and any moment where we want to gently introduce variety without leaving the brand temperature. Never use for chrome (backgrounds, app shell). Both dark- and light-mode variants are tuned for legibility against their respective base surfaces.

Dark mode

oxblood#660F09Deep ember scar — rare, high-emphasis
ember#C44A2CGlowing coal — earned / live / "now"
rust#B0623AIron oxide — secondary warm tag
amber#E89545Autumn flame — progress, attention
cedar#895129Cinnamon wood — warm filled surfaces
bronze#AD7A32Aged metal — info-warm
ochre#C19A4AAutumn leaf — landmarks, attractions
mustard#8A6E29Dried grass — quieter ochre
chestnut#5C3B22Dark warm wood — borders, depth
pinecone#3D2B1EDeepest warm brown — edges, ground

Light mode

Same palette, darkened for contrast against white surfaces. The semantic meaning of each token stays the same across modes.

oxblood#4A0A05
ember#9A3820
rust#864628
amber#B3691A
cedar#6E3E1E
bronze#7C551E
ochre#8F6E22
mustard#614B16
chestnut#3F2614
pinecone#2A1B0F

Borders

Borders are intentionally subtle. The border and outlineVariant tokens are both rendered at 15% opacity — solid lines are forbidden by the no-line rule.

  • Dark: rgba(255, 255, 255, 0.15)
  • Light: rgba(0, 0, 0, 0.10)

Map markers

Marker colors are mode-independent and live in MARKER_COLORS (frontend/components/ui/categoryIcons.ts), not in the theme. The marker layer (web components/map/engine/markers, native components/map/native/MarkerOverlay) renders the same on both light and dark maps, so its palette must not flip with the app theme. Web and native import the same map to stay in lock-step.

The palette covers event/location/moment tiers plus the ink and status accents painted on them:

  • eventDefault / eventSponsored / calendarEvent — event pin tiers.
  • locationBase / locationActive / locationHot — location activity tiers.
  • momentColor / momentMuted — moment pins.
  • markerInk (#0e0e0f) — dark ink for text and borders on the gold/orange marker tiles and badges.
  • globallyActive (#7c3aed) — the sharing-worldwide globe badge.
  • ringEvents / ringLocations / ringMoments — geo-cluster ring segments (one source for both renderers).

The MapLegendModal legend swatches import from MARKER_COLORS too, so the legend always matches the live markers.

Map tiles

constants/MapStyles.ts ships a per-mode JSON array for Google Maps (mobile). On web, Leaflet styles are driven by the same theme tokens (custom CSS overrides on top of OpenStreetMap base tiles).

Adding a color

  1. Add the token to frontend/constants/Themes.ts in both the dark and light blocks. Every token must have both-mode parity.
  2. If it's a brand-level addition (a new accent), add a swatch entry to the appropriate section of this page in the same PR.
  3. If it's a semantic state (e.g. info), update Components to show which components use it.
  4. Run task docs:build to verify swatches render correctly.