Files
docs/android/theme-plan.md

99 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Android theme plan — mirroring the website frontend
This is a summary of the **website frontend theme** (source of truth:
`website/client/src/styles/theme.css`, applied at runtime by
`website/client/src/contexts/SiteContext.jsx`) so the native Android client can
present a visually consistent brand. Where the web uses CSS custom properties,
the Android equivalent is a Compose `MaterialTheme` `ColorScheme` + `Typography`.
## Overall character
A **dark, moody, "arcane fantasy" theme** — deep blue-black backgrounds, muted
slate-blue accent, parchment-white text, and an engraved serif display face. It
reads like a leather-and-moonlight fantasy ledger, not a bright consumer app.
There is **no light mode** on the web; the app should ship dark-only to match.
## Color tokens
The web theme is a flat set of CSS variables under `:root`. Map them to Compose
as follows (hex is authoritative):
| Web token | Hex | Role | Compose slot (suggested) |
|-------------------|------------|----------------------------------------|-------------------------------|
| `--bg` | `#0e1318` | App background | `background` |
| `--bg-deep` | `#0b0f14` | Deepest surface / on-accent text | `surfaceDim` / `onPrimary` |
| `--panel-a` | `#192231` | Card gradient top | `surface` |
| `--panel-b` | `#141a21` | Card gradient bottom | `surfaceContainer` |
| `--panel-flat` | `#11161d` | Flat panels, toolbars | `surfaceContainerLow` |
| `--line` | `#2a3544` | Borders / dividers | `outline` |
| `--line-soft` | `#1d2733` | Subtle row dividers | `outlineVariant` |
| `--accent` | `#7f99bd` | **Primary accent** (brand-overridable) | `primary` |
| `--accent-bright` | `#cdd9e8` | Primary button fill, active states | `primaryContainer` / bright |
| `--ink` | `#eef3f8` | Highest-contrast text | `onBackground` |
| `--head` | `#e6edf6` | Headings | heading color |
| `--text` | `#c4cdd8` | Body prose | `onSurface` |
| `--muted` | `#aeb8c4` | Secondary text | `onSurfaceVariant` |
| `--dim` | `#6f7d8e` | Meta / captions / placeholders | dim / disabled text |
| `--blue` | `#13243c` | Accent hover/active background | `secondaryContainer` |
| `--mode-live` | `#5fb98a` | "Shard live" status (green) | success |
| `--mode-maint` | `#e6c26a` | "Maintenance" status (amber) | warning |
### Semantic / status colors (used in badges, diffs, moderation)
- **Success / published / live:** green `#5fb98a` (fills at ~1622% alpha, text `#7fd0a4`).
- **Warning / maintenance / moderation (kick/mute/warn):** amber `#e0b070` / `#e6c26a`.
- **Danger / ban / red-link / errors:** desaturated red `#d98b84` (borders `#6e3b38`).
- **Admin badge:** near-white `#d8e2ef` on `#3a4a5e`.
## Branding is data, not code
The `--accent` value is **overridden at runtime** per shard instance. On the web,
`SiteContext` reads `brand.accent` from the site settings API and sets the CSS
variable, so one build reskins for any shard. **The Android app should do the
same:** fetch the brand payload (name, `accent`, colors, logo/hero/favicon) from
the website API and derive the `primary` color at runtime rather than hardcoding
`#7f99bd`. Default to `#7f99bd` when the brand payload is absent/offline.
## Typography
Three font families, by role:
- **Display** (`--display`): **Cinzel**, falling back to Georgia serif — an
engraved Roman capitals face used for the logo, `h1`/`.h1`, and prose
`h2`/`h3`. Bundle Cinzel as an app font; this face carries the brand.
- **Serif body** (`--serif`): **Georgia / Times New Roman** — default body and
prose text; `line-height ≈ 1.6`.
- **Sans** (`--sans`): **Helvetica Neue / Arial** — UI chrome: buttons, pills,
form labels, table headers, badges, meta. Labels/eyebrows/kickers are
UPPERCASE with wide letter-spacing (`0.10.18em`) and small (0.680.86rem).
Heading scale is fluid on web (`h1` clamps ~2.43.6rem); pick fixed Material type
scale equivalents (e.g. display for `h1`, headline for `h2`, title for `h3`).
## Shape, elevation & motion
- **Corners:** cards/panels `1012px` radius; inputs/small elements `8px`;
pills and buttons are **fully rounded** (`999px` / capsule).
- **Cards:** vertical gradient `--panel-a → --panel-b`, 1px `--line` border, soft
drop shadow (`0 14px 34px rgba(0,0,0,0.3)`). On hover the web lifts `-3px` and
brightens the border to `--accent` — translate to a pressed/focused accent
border on Android.
- **Buttons:** primary = bright fill (`--accent-bright`) with dark text;
ghost/secondary = translucent dark fill with accent-on-hover border.
- **Motion:** short, subtle transitions (0.120.18s). Keep animations understated.
## Signature accents (nice-to-have)
- The **"moon"** motif: a radial-gradient sphere (`#eef3f8 → #9fb0c6 → #5d6e88`) —
a small brand flourish worth reproducing.
- Accent-tinted focus rings and left-border "note" callouts
(`border-left: 3px solid --accent` over a translucent `--blue` background).
## Implementation note for Compose
Define one `darkColorScheme(...)` from the table above, a `Typography` binding the
three families, and a `Shapes` set (`small = 8.dp`, `medium = 10.dp`, capsule for
buttons). Load `accent` from the brand API into a state holder and rebuild the
`primary` (and derived `primaryContainer`) at runtime so a shard's custom accent
flows through the whole UI — exactly as `SiteContext` does on the web.