Files
gw/frontend/THEME.md
T
ponzischeme89 6d44e05de4 v1
2026-04-18 07:23:55 +12:00

315 lines
13 KiB
Markdown
Raw 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.
# 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 |