315 lines
13 KiB
Markdown
315 lines
13 KiB
Markdown
# 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.96–0.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.94–0.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 |
|