Files
gw/frontend/THEME.md
T

315 lines
13 KiB
Markdown
Raw Normal View History

2026-04-18 07:23:55 +12:00
# Goodwalk — Design Language & Theme Reference
> Source of truth for visual language, spacing, tone, interaction style, and component feel across the Goodwalk frontend.
---
## Brand Palette
| Token | Value | Usage |
|---|---|---|
| `--gw-accent-color-1` | `#FFD100` | Highlight / CTA yellow |
| `--gw-accent-color-2` | `#213021` | Deep forest green (primary brand) |
| `--gw-accent-color-3` | `#59606D` | Muted slate |
| `--gw-accent-color-4` | `#E5D6C2` | Warm sand |
| `--gw-accent-color-5` | `#FFFFFF` | White |
| `--gw-accent-color-6` | `#000000` | Black |
Background texture: warm cream gradient (`#f4f1ea``#f1ede5`).
Typography: **DM Sans** (body), system heading scale.
---
## Desktop Design Language
**Tone:** Premium, intentional, warm. Not corporate. Feels like a trusted local service with high craft.
**Spacing:** 8-point grid. Components breathe. Generous white-space is never wasted.
**Surfaces:** Semi-transparent white (`rgba(255,255,255,0.94)`) on warm cream background. Cards carry a subtle multi-layer shadow.
**Borders:** Warm tan `#e7ddd1`, never cold grey.
**Shadows:** Multi-stop warm shadows — `0 4px 8px rgba(17,16,14,0.04), 0 16px 34px rgba(17,16,14,0.08)`.
**Navigation:** Sticky header with glassmorphism on scroll. Bottom tab bar on mobile (see below).
**Radius scale:** `8px` (inputs), `12px` (cards), `16px` (modals/sheets), `28px` (pills/nav).
**Interactions:** Subtle hover lifts (`translateY(-1px)`), scale-down press (`scale(0.960.98)`), smooth 150ms ease transitions.
---
## Admin Design System
Admin uses a theming layer on top of the base brand. The active theme is stored in `localStorage` as `gw_admin_theme`.
| Theme | Accent | Highlight |
|---|---|---|
| main | `#213021` | `#ffd100` |
| pink | `#c54b8c` | `#ffcadf` |
| purple | `#6f55d9` | `#d8cbff` |
| yellow | `#9a7700` | `#ffe08a` |
Admin CSS variables (set via inline `style` on the shell element):
```
--admin-accent, --admin-accent-dark, --admin-accent-rgb
--admin-topnav-start, --admin-topnav-end
--admin-highlight, --admin-highlight-rgb
--admin-button-text
--admin-font-scale
```
The admin header uses a gradient from `--admin-topnav-start``--admin-topnav-end`.
---
## Members Design System
Members uses the core brand palette directly (no theme switching).
```css
--members-accent: #213021
--members-accent-dark: #172217
--members-highlight: #ffd100
--members-border: #e7ddd1
--members-surface: rgba(255, 255, 255, 0.94)
--members-surface-soft: #f8f4ed
--members-surface-muted: #f2ece3
--members-text: #1d261d
--members-text-muted: #6b635c
```
---
## Mobile Design Language
> Mobile users receive a **dedicated, purpose-built experience** — not a squeezed desktop page. All mobile screens live under `/admin/m/` (admin) or `/members/m/` (members).
### Philosophy
- **Native-app inspired.** Feel fast, responsive, and deliberate. Every tap should feel satisfying.
- **One-handed first.** Primary actions sit at thumb-reach (bottom half of screen). Navigation is bottom-anchored.
- **Dense but not cramped.** Maximum information per screen without feeling overwhelming.
- **No desktop patterns forced onto phones.** Never squeeze a data table onto a 390px screen. Use lists, cards, and sheets instead.
### Mobile vs Desktop Rules
| Rule | Desktop | Mobile |
|---|---|---|
| Navigation | Top sticky header + subnav tabs | Fixed bottom tab bar |
| Primary actions | Anywhere sensible | Bottom-anchored action bar or prominent nav item |
| Modals | Standard centred modal | Slide-up bottom sheet |
| Tables / data grids | Full table layout | Vertical list with disclosure rows |
| Forms | Multi-column layouts OK | Single column, full-width fields |
| Page titles | In header nav | Sticky per-page header (MobilePageShell) |
| Content width | Max-width capped at 1320px | Full bleed (16px horizontal padding) |
### Mobile Token Layer
All mobile screens inherit tokens from the `MobileAppShell` component. These tokens provide a consistent design system shared between Admin and Member mobile experiences:
```css
/* Palette */
--m-accent: #213021 /* primary action colour */
--m-accent-dark: #172217 /* hover/active state */
--m-accent-rgb: 33, 48, 33 /* for rgba() usage */
--m-highlight: #ffd100
--m-bg: #f4f1ea
--m-surface: rgba(255,255,255,0.96)
--m-surface-soft: #f8f4ed
--m-surface-muted: #f2ece3
--m-border: #e7ddd1
--m-border-soft: rgba(231,221,209,0.6)
--m-text: #1d261d
--m-text-muted: #6b635c
--m-text-faint: #a09890
/* Shadows */
--m-shadow-card: 0 2px 8px rgba(17,16,14,0.06), 0 8px 20px rgba(17,16,14,0.08)
--m-shadow-sheet: 0 -8px 40px rgba(17,16,14,0.18)
--m-shadow-button: 0 4px 16px rgba(33,48,33,0.28)
/* Radii */
--m-radius-xs: 6px
--m-radius-sm: 10px
--m-radius: 16px /* default card/button radius */
--m-radius-lg: 24px
--m-radius-xl: 32px /* bottom sheet corners */
/* Spacing scale (4-point base) */
--m-space-1: 4px
--m-space-2: 8px
--m-space-3: 12px
--m-space-4: 16px
--m-space-5: 20px
--m-space-6: 24px
--m-space-8: 32px
--m-space-10: 40px
/* Type scale */
--m-text-xs: 11px
--m-text-sm: 13px
--m-text-md: 15px /* body text */
--m-text-lg: 17px /* page titles */
--m-text-xl: 20px
--m-text-2xl: 24px
--m-text-3xl: 30px /* stat card values */
/* Touch */
--m-touch-min: 44px /* minimum touch target (iOS HIG / WCAG) */
/* Layout */
--m-bottom-nav-h: 72px
--m-header-h: 56px
--m-page-px: 16px /* horizontal page padding */
/* Safe areas (iOS notch/home bar) */
--m-safe-top: env(safe-area-inset-top, 0px)
--m-safe-bottom: env(safe-area-inset-bottom, 0px)
```
For the **Admin mobile** variant, `--m-accent`, `--m-accent-dark`, `--m-accent-rgb`, and `--m-highlight` are overridden by the admin's active theme variables (`--admin-accent` etc.), so the admin theme switcher works on mobile too.
### Touch Target Minimums
- Every interactive element must be at least **44 × 44px** (iOS HIG) / **48 × 48dp** (Material).
- Use `min-height: var(--m-touch-min)` on buttons and list rows.
- Pad tappable areas generously — don't rely on text size alone for target size.
### Bottom Navigation Principles
The bottom tab bar is the **primary navigation** on mobile. It is always visible and never hides during scroll.
- Max **5 items** per bar. Fewer is better.
- Icons must be recognisable without labels — labels are shown but should not be the only differentiator.
- **One prominent item** allowed per bar (filled background, accent colour). Use for the most important CTA (e.g. Add Client, Complete Onboarding).
- Badge dots use red `#e03e3e` for unread counts (messages).
- The bar uses glassmorphism: `rgba(251,247,240,0.92)` background, `backdrop-filter: blur(18px)`, `border-radius: 28px`. Never make it a plain opaque bar.
- Accounts for safe area: `padding-bottom: calc(12px + env(safe-area-inset-bottom))`.
- Active state: light green tint `rgba(33,48,33,0.08)` + accent text colour.
- Inactive state: `--m-text-muted` colour.
- Press: `scale(0.94)` transform with `transition: 0.15s`.
### Spacing and Rhythm (Small Screens)
- Page content padding: `16px` horizontal.
- Section vertical spacing: `24px` top (tight variant: `16px`).
- Section labels: `11px`, `800` weight, `0.08em` letter-spacing, uppercase, muted colour.
- Card internal padding: `16px`.
- List row minimum height: `44px`. Default padding: `12px 0`.
- Group gaps: `8px` between sibling cards/list items, `12px` between elements within a card.
- Never use horizontal padding smaller than `8px` inside a card.
### Mobile Card / List Patterns
**Cards:**
- `border-radius: 16px`, warm border `#e7ddd1`, multi-layer warm shadow.
- Default: white surface with shadow.
- Subtle: `#f8f4ed` surface, no shadow (for secondary content blocks).
- Highlight: `3px` left border in accent colour (for time-sensitive or important content).
- Press: `scale(0.98)`.
**List items (MobileListItem):**
- Full-width rows with leading slot (icon/avatar), label, sublabel, trailing area (meta, badge, chevron).
- Divider is a `1px` line at `60%` opacity — not a full-width separator.
- Never nest more than 2 lines of text in a list item on mobile.
- Chevron (``) indicates navigable rows.
### Mobile Form Behaviour
- Single-column layouts only.
- Labels above inputs (`13px`, `700` weight).
- Full-width inputs with `min-height: 44px`, `border-radius: 10px`.
- Focus ring: `3px` at `0.12` opacity using accent colour.
- Error messages below the field in red (`#991b1b`), not in a toast.
- Use `MobileFormField` to wrap every input — it ensures consistent spacing and error handling.
- Avoid more than 4-5 fields visible at once without sectioning.
### Hierarchy of Headers and Page Titles
1. **App header** (`MobileHeader`) — optional sticky brand strip at the very top. Use only when brand context is needed (e.g. on first entry to a section).
2. **Page header** (`MobilePageShell`) — per-page sticky title bar. Always present. Contains: back button (44px touch), centred title (`17px, 700`), right-side action slot.
3. **Section headings** (`MobileSection title=`) — `11px, 800, uppercase, muted`. Acts as a visual grouping label, not a navigational heading.
4. **Card titles / list labels**`15px, 600` for primary labels, `13px` for sublabels.
Do not repeat the page title in the body content. The sticky page header is the single source of truth for where you are.
### Motion and Transitions
- Duration: `150ms` for micro-interactions (hover, press), `260ms` for sheet entry.
- Easing: `cubic-bezier(0.25, 0.46, 0.45, 0.94)` (ease-out feel).
- Sheet entry: `fly` transition upward 320px, 260ms.
- Press scales: `0.940.98` depending on element size (buttons larger → less scale, small icons → more scale).
- No bouncing or spring physics on primary navigation (too distracting).
- Reserve spring easing (`cubic-bezier(0.34, 1.56, 0.64, 1)`) for playful one-off moments only.
- Skeleton loading: sinusoidal opacity pulse `0.45 → 0.85`, `1.4s` period, staggered delays.
### Empty / Loading / Error States
Use `MobileEmptyState` for all three variants:
- **Loading:** animated skeleton cards (3 staggered shimmer bars).
- **Empty:** emoji icon, title, optional body text, optional CTA button.
- **Error:** same as empty but `role="alert"`, red title, warning emoji.
Rules:
- Every list/data section must handle all three states explicitly.
- Never show a spinner in place of a full-page experience — use skeleton screens.
- Empty state CTAs should always provide a path forward (e.g. "Book a Walk").
### Admin and Member Mobile Alignment
Admin and Member mobile experiences are visually indistinguishable in structure — they share:
- The same `MobileAppShell` token layer.
- The same component library.
- The same bottom nav design, touch targets, motion, and type scale.
- The same card/list/section patterns.
They differ in:
- Admin inherits the admin theme (accent colour changes with theme switcher).
- Members always uses the green brand palette.
- Different nav items and routes.
- Different auth patterns (admin: CSR localStorage; members: server cookie + localStorage).
- Different feature sets and business logic.
### What Must Never Happen in Mobile Views
- **No horizontal scrolling** in page content. Horizontal scroll only inside explicit scroll containers (e.g. a chip row).
- **No data tables.** Replace with vertical lists with disclosure rows or summary cards.
- **No fixed-position elements other than the bottom nav and action bar.** Avoid top banners that take up screen space.
- **No touch targets smaller than 44px.** Not even "less important" actions.
- **No modals that cover the full screen** (except full-screen sheets on purpose). Prefer slide-up panels.
- **No desktop responsive hacks in mobile components.** Do not import mobile tokens into desktop components or add `@media (max-width)` patches to the mobile components file.
- **No mixing desktop and mobile layouts in the same file** (e.g., showing/hiding via CSS in a shared component). Use separate route/layout files.
- **No navigation items that do not exist on mobile.** If a feature isn't in the mobile nav, provide a clear path to the desktop version or a dedicated in-app screen.
---
## Component Library Summary
### Shared Desktop Components
Located in `src/components/` and `src/lib/`.
### Shared Mobile Components
Located in `src/lib/mobile/components/`. All use the `--m-*` token layer.
| Component | Purpose |
|---|---|
| `MobileAppShell` | Top-level layout wrapper, token layer, bottom nav integration |
| `MobilePageShell` | Per-page container with sticky title header |
| `MobileHeader` | Optional branded app header strip |
| `MobileBottomNav` | Fixed bottom tab bar |
| `MobileSection` | Content group with optional title and action link |
| `MobileCard` | Touchable surface card |
| `MobileListItem` | Single list row (leading/label/sublabel/trailing) |
| `MobileStatCard` | Metric display card for dashboards |
| `MobileActionBar` | Sticky bottom CTA button area |
| `MobileSearchBar` | Touch-friendly search input with clear button |
| `MobileFormField` | Form field wrapper (label + input slot + error/hint) |
| `MobileEmptyState` | Empty / loading skeleton / error state |
| `MobileFilterSheet` | Slide-up bottom sheet for filters |
| `MobileTabs` | Segmented tab switcher |