added claude design's ui framework
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# Handoff: Pansy UI redesign
|
||||
|
||||
## Overview
|
||||
A full UI/UX replacement for **pansy**, the self-hosted garden planner (Go backend + JSON API, see repo `DESIGN.md`). This package covers every screen: the garden editor (desktop workspace + phone layout), gardens list, plants catalog with seed lots and packet scanning, instance settings, and login — plus a light/dark theme system defaulting to the OS preference.
|
||||
|
||||
## About the design files
|
||||
The `.dc.html` files in this bundle are **design references created in HTML** — working prototypes showing intended look and behavior, not production code to copy. The task is to **recreate these designs in the pansy codebase**. The existing frontend (React 19 + TS + Vite + Tailwind 4) is explicitly up for replacement: keep it, or choose anything that renders SVG and talks JSON. Everything here was deliberately built with framework-agnostic primitives (plain SVG canvas, pointer events, CSS custom properties) so it ports anywhere. The Go backend and its API are the fixed contract — this design maps 1:1 onto the existing endpoints.
|
||||
|
||||
The prototypes carry in-memory demo data (the real 9-bed garden: five 3'×6' beds, four 2'×8' beds, bags, buckets). Wherever demo state exists, the matching API call is named below.
|
||||
|
||||
## Fidelity
|
||||
**High-fidelity.** Colors, typography, spacing, radii, and interactions are final. Recreate pixel-perfectly. The visual system is "Organic": warm cream ground, terracotta + sage accents, Caprasimo display over Figtree body, everything over-rounded (pills, 16–28px radii). Fonts are Google Fonts: `Caprasimo:wght@400` and `Figtree:wght@400;600;700`.
|
||||
|
||||
## Files
|
||||
| File | What it is |
|
||||
| --- | --- |
|
||||
| `Pansy Editor.dc.html` | The editor — BOTH desktop and phone layouts in one file (breakpoint 760px on container width) |
|
||||
| `Pansy Gardens.dc.html` | Gardens list + new-garden and share dialogs |
|
||||
| `Pansy Plants.dc.html` | Plant catalog + seed lots + scan-packet and add-plant dialogs |
|
||||
| `Pansy Settings.dc.html` | Instance settings (admin) + appearance |
|
||||
| `Pansy Login.dc.html` | Login |
|
||||
| `Pansy Phone Preview.dc.html` | The editor mounted in an iPhone frame (`ios-frame.jsx`) — preview aid only, do not implement |
|
||||
| `pansy-theme.js` | The theme mechanism: dark-mode token overrides + system-pref logic. Port this pattern directly |
|
||||
| `_ds/organic-…/styles.css` | The design-token stylesheet (CSS custom properties + component classes). The single source of visual truth |
|
||||
|
||||
Each `.dc.html` is template + logic in one file; read the markup for exact inline styles and the `class Component` script for behavior math.
|
||||
|
||||
## Screenshots
|
||||
`screenshots/` holds visual ground truth for every screen in both modes: `editor-desktop`, `editor-phone` (in the device frame), `gardens`, `plants`, `settings`, `login` — each as `-light.png` and `-dark.png`. When a spec and a screenshot disagree, flag it rather than guessing; the live HTML file is the tiebreaker.
|
||||
|
||||
## Design tokens
|
||||
|
||||
### Core (light) — from `styles.css`
|
||||
- Ground `--color-bg: #f5ead8` · surface `#ebddc5` · text `#201e1d`
|
||||
- Accent (terracotta) `#c67139` with 100–900 ramp (`#fff2eb → #402310`); accent-2 (sage) `#7a8a5e` with ramp (`#f0fae1 → #272e1b`)
|
||||
- Neutral ramp `#f9f4ed → #2e2b25`
|
||||
- Divider: `color-mix(in srgb, #201e1d 16%, transparent)`
|
||||
- Radii: sm 8 / md 16 / lg 28 / pills `999px` (all controls are pills)
|
||||
- Shadows: `--shadow-sm/md/lg` (ink-tinted; see styles.css)
|
||||
- Type: h-font Caprasimo 400; body Figtree; base 15px/1.55. Page titles h2 32px, card titles ~16–19px Caprasimo, UI labels 12.5–13.5px Figtree 600–700, metadata 11.5–12.5px at `--p-ink-mute`
|
||||
|
||||
### Pansy canvas + ink tokens (light values; declared per-page in `:root`)
|
||||
```
|
||||
--p-field:#efe3c9 garden field fill --p-grid-ink:#201e1d (grid lines @ .06/.12 opacity)
|
||||
--p-ink-strong:#474238 primary secondary text --p-ink-soft:#645c50 --p-ink-mute:#82796a
|
||||
--p-bed-fill:#e3d0ac / --p-bed-stroke:#b5a37f raised bed
|
||||
--p-ing-fill:#d6bf98 / #b1a07c (dash 10 7) in-ground plot
|
||||
--p-path-fill:#ece1cb / #cabfa6 (dash 3 8) path
|
||||
--p-bag-fill:#d8c5a2 / #a99677 grow bag
|
||||
--p-bkt-fill:#cfc3ad / #9a8d76 container/bucket
|
||||
--p-tree-fill:#dce9c6 / #8fa073 (dash 12 8) tree canopy
|
||||
--p-str-fill:#d9cfbc / #a3947c structure
|
||||
```
|
||||
|
||||
### Dark mode
|
||||
`pansy-theme.js` holds the full override map — dark is implemented **only** by overriding these custom properties on `<html>` (plus `color-scheme`). Key values: bg `#252220`, surface `#33302a`, text `#f1e9da`, cards `#2e2b25`, field `#2b2823`, grid ink flips to `#f5ead8`, accent lifts to `#d67f48`, tag/tint pairs flip (e.g. accent-2-100 → `#2d3520` with accent-2-800 → `#e1eecc`). Plant marker colors do NOT change between modes.
|
||||
- Preference: `'system' | 'light' | 'dark'`, persisted (`localStorage['pansy-theme']` in the prototype; per-user setting in production). Default **system**, live-updates on `prefers-color-scheme` change.
|
||||
- UI: icon button in every nav cycling system→light→dark (monitor/sun/moon, Lucide, stroke 2.75) + a System/Light/Dark segmented control in Settings → Appearance.
|
||||
|
||||
### Plant markers (demo palette)
|
||||
Solid circle in the plant's color + 1–2 letter monogram in Caprasimo `#fffaf1`: Garlic `#97a97c` G · Tomato `#c8553d` T · Cucumber `#6f8f4f` C · Watermelon `#46683c` W · Basil `#5f8f45` B · Pepper `#b2622d` P · Marigold `#d9912f` Ma · Melon `#c2913a` Me. In production the color comes from `plants.color`; derive the monogram from the name. This replaces the old emoji icons.
|
||||
|
||||
## Screens
|
||||
|
||||
### 1. Editor — desktop (≥760px container)
|
||||
Three-region workspace under a nav bar; every region is a `--color-neutral-100` card, `--radius-lg`, 1px divider border, in a 14px-gap grid: `216px | minmax(0,1fr) | 336px`.
|
||||
|
||||
- **Nav**: brand (sprout icon, sage, stroke 2.75 + "pansy" Caprasimo 18), links Gardens/Plants (active = accent), right cluster: theme toggle, settings gear, avatar circle (32px, sage-300 bg).
|
||||
- **Left card — Toolkit**: 7 draggable object kinds (Bed 3'×6', Grow bag 1'4", Container 2', In-ground 6'7", Tree 9'10", Path 3'3"×9'10", Structure 6'7"), each a pill row: mini shape swatch (SVG, true proportions, kind's fill/stroke) + bold 13px label + 11px size in ink-mute. Click arms (accent-200 bg + accent-400 border, crosshair cursor, click canvas to place); drag-drop onto canvas also places. When a bed is **focused** the card swaps to: back chevron + bed name (Caprasimo 16), search input, plant list rows (14px color dot + name + spacing), same arm/drag behavior.
|
||||
- **Center card — canvas**: header row (18px padding): "Home Garden" Caprasimo 19 + size `40′ × 28′` ink-mute + focus crumb (`/ Bed 2` accent-700) + right cluster (wraps at narrow widths): season segmented pill control [2025 | 2026 | 2027 plan] on neutral-200 track (active = neutral-100 pill + shadow-sm) and an Undo pill button (disabled at 45% opacity when stack empty). A status banner strip (accent-2-200 bg / accent-2-800 text, 13px) appears under the header when season ≠ 2026: 2025 = read-only; 2027 plan = "separate copy" note.
|
||||
- **Canvas (SVG)**: field = rounded rect (rx 18) `--p-field`, stroke accent-2-500 3px/scale; 1ft grid minor / 5ft major lines. Objects positioned by center + rotation (`translate(x y) rotate(deg)`), stroke width 2.5/scale (3.5 accent when selected). Bed names in 13px/scale above plantable objects; centered inside non-plantable ones. Plops (planting patches) = circles in plant color, opacity .94, monogram shown when `r·scale ≥ 9px`, name below when `≥ 34px` (semantic zoom). Selection = dashed accent outline offset 7/scale + size label (`3′ × 6′`) under the object. Ghost preview follows cursor when armed, 55% opacity dashed.
|
||||
- **Zoom pill** floating bottom-right of canvas: − / fit / +.
|
||||
- **Right card — rail** with 4 pill tabs: **Plot** (garden summary: counts line + full plant roster with dots and "64 in Long bed A"-style locations; or, with a selection, the inspector: name input, kind tag + size, "Growing:" roster, actions [Plant this · rotate 90° · delete]; plop selection: plant swatch + count + patch size + "Pull it out"), **Journal** (entry cards: object tag accent-2-700 + date, 13px body; input + add pill, Enter submits, attaches to selection/focus/garden), **History** ("Every change — yours or the assistant's — is one undoable step." + newest-first op pills with timestamps + "Undo the last step"), **Assistant** (chat bubbles: user right-aligned accent-200/accent-900 radius 18/18/4/18, assistant left bg/divider 18/18/18/4; input + send).
|
||||
|
||||
### 2. Editor — phone (<760px container)
|
||||
Same file, same state, different chrome (matches repo DESIGN.md #99/#101):
|
||||
- **Header** 10px padding: back circle button (38px; exits focus first, else → Gardens), garden/bed name Caprasimo 17 (ellipsis), right: theme button, season chip (tap cycles seasons, shows "26"/"27 plan"), undo circle button.
|
||||
- **Canvas** fills the middle; floating fit button bottom-right. **Pinch to zoom** (two-pointer), one-finger pan/drag, tap = select, touch drag threshold 7px (mouse 3px).
|
||||
- **Peek panel** (inspector on selection, or Journal/Assistant modes): docked between canvas and mode bar, max-height 45%, radius 22px top corners, shadow-lg, title + close X; canvas stays visible above it. Inputs inside are 16px font (prevents iOS zoom). All hit targets ≥ 44px.
|
||||
- **Tool strip** (Build or Plants mode, hidden while peek is open): horizontal scroll row of pill chips (min-height 44) above the mode bar. In focus + Plants mode the first chip is a primary "Done".
|
||||
- **Mode bar**, always visible, surface bg, 4 items (icon 19px + 11px label): Build, Plants, Journal, Assistant; active = accent-200 pill. Bottom padding `calc(6px + env(safe-area-inset-bottom))`.
|
||||
- Focusing a bed on phone auto-switches to Plants mode; plants can also be tapped straight into any bed without focusing.
|
||||
|
||||
### 3. Gardens list
|
||||
Nav + `max-width 1080px` page. Title row: "Gardens" h2 + tagline ink-mute + "New garden" primary pill. Card grid `repeat(auto-fill, minmax(300px,1fr))`, 18px gap. Each card: SVG plot thumbnail (150px band, `--p-field` bg, real object layout + plant-colored dots/pills), name Caprasimo 18 (+ optional `plan` accent tag), size right-aligned, 13px meta line, optional "Shared with lauren@ · editor" in accent-2-700, footer: Open (primary pill, flex 1) + share / copy / delete icon buttons. Dialogs: **New garden** (name, width/height in ft — note: API stores cm; "Break ground" primary) and **Share** (invite by email + role chips toggling viewer/editor on tap, read-only-link toggle + dashed link pill). Deep-copy card behavior mirrors `POST /gardens/:id/copy`.
|
||||
|
||||
### 4. Plants catalog
|
||||
Title row + two actions: "Scan a packet" (secondary, camera icon) and "Add a plant" (primary). Filter row: search input (260px) + category pill chips (All/Vegetables/Herbs/Flowers/Fruit), live filtering. Card grid `minmax(240px,1fr)`: 40px color swatch with Caprasimo monogram, name Caprasimo 16.5 (ellipsis), sub line `Category · spacing · days`, `built-in` neutral tag for seeded plants, seed-lot summary line; clicking expands the card (accent border) to show lot cards (vendor bold + "packed for YYYY" + detail line with derived remaining) — `remaining` is **derived** server-side, never stored.
|
||||
**Scan-packet dialog** (the #81 flow): step 1 = dashed drop zone + camera icon + copy "The vision model reads it into fields. It only reads; nothing is saved until you confirm." Step 2 = extracted fields grid (Variety/Vendor/Packed for/Quantity) + ranked match list as radio pills ("Garlic (built-in)" / "Garlic — Music (yours) · best match" / "Create a new plant") + "Add the lot" primary. **Never auto-create — the user confirms the match.**
|
||||
**Add-plant dialog**: name, category select, spacing (in), marker color swatch row (6 curated swatches, accent ring on selection).
|
||||
|
||||
### 5. Settings (admin)
|
||||
`max-width 720px` column of cards: **Appearance** (theme seg), **Who gets in** (signup Open/Closed seg, Local passwords toggle pill with "pure-Authentik" note, read-only OIDC issuer input, first-user-is-admin note), **Garden assistant** (live status tag e.g. "key present · glm-5.2 live", on/off toggle, chat model + vision model inputs with inherit-from-env placeholders, key-stays-in-env note — precedence Settings → env → default), **You** (display name, email read-only, Sign out ghost). Maps to `GET/PATCH /settings` + `/capabilities`.
|
||||
|
||||
### 6. Login
|
||||
Centered 400px column over two soft blurred accent circles (decoration, 50–55% opacity): sprout 44px + "pansy" Caprasimo 40 + tagline "Plan the plot. Keep the notes. Grow the thing." Card: email + password fields (16px font), "Into the garden" primary block pill, "or" divider, "Sign in with Authentik" secondary block (lock icon; label from `PANSY_OIDC_BUTTON_LABEL`; hide per `GET /auth/providers`), footer "New here? Create an account — the first one becomes admin."
|
||||
|
||||
## Interactions & behavior (editor core)
|
||||
|
||||
**Viewport**: world = garden cm, screen = `translate(tx,ty) scale(s)` on one SVG group (s = px/cm, clamp 0.12–8). Wheel zoom to cursor (`s · e^(−deltaY·0.0016)`, non-passive listener). Pinch: scale by finger-distance ratio, keep world point under the centroid fixed. Fit: `s = min((w−2p)/GW, (h−2p)/GH)`, pad 40 desktop / 20 phone, centered. Camera animates on fit/focus with `transform .48s cubic-bezier(.22,.85,.3,1)`; drags/zooms are transition-free. Refit when container size changes >60px (ResizeObserver) or mobile/desktop flips.
|
||||
|
||||
**Placement & drag**: object drags snap center to a 3in (7.62cm) grid; drag commits ONE change (PATCH on drop, not per frame). Plops live in the parent object's local frame (rotate/move the bed moves its plants); drag clamps so a patch may overhang the bed edge by up to r/2 (spacing rule from DESIGN.md). Placing a plop: radius = spacing/2, count derived `max(1, round(πr²/spacing²))`. Click vs drag disambiguated by movement threshold. Escape ladder: disarm → deselect → unfocus. Delete/Backspace deletes selection (never while typing in inputs).
|
||||
|
||||
**Focus (plant a bed)**: double-click (desktop) or inspector "Plant this" — camera zooms to the bed (≈2.1× margin desktop, 1.35× phone, cap s=6), siblings dim to 30% opacity (their plops 22%), palette/strip swaps to plants. Same canvas, no separate view; mirror to `?focus=objectId`.
|
||||
|
||||
**Undo/History**: every operation (add, move, rotate, delete, plant, pull, clear) is one undoable step with a human label ("Planted garlic in Long bed A"). Maps directly to the change-set API (`GET /gardens/:id/history`, `POST /change-sets/:id/revert`); the prototype's snapshot stack is a stand-in.
|
||||
|
||||
**Seasons**: the seg switches `?year=` on `GET /gardens/:id/full`. Past seasons render read-only (edit handlers guard + banner). "2027 plan" opens the copied plan garden (from `/copy`) — visually identical editing with a persistent banner.
|
||||
|
||||
**Read-only viewers** (share role viewer / public token): same guard path as the 2025 season — canvas renders, all mutation entry points disabled.
|
||||
|
||||
## State management (suggested shape)
|
||||
- Server state: `GET /gardens/:id/full` (garden + objects + plantings + plants) keyed per garden+year; optimistic mutations with `version` guard — on 409 roll back and refetch.
|
||||
- Ephemeral UI state: `{tx, ty, s}`, `selection {type: 'object'|'plop', id} | null`, `focusId | null`, `armedKind | null`, `armedPlant | null`, season/year, rail tab, phone mode ('build'|'plants'|'journal'|'chat'), theme pref.
|
||||
- Journal (`/gardens/:id/journal`) and assistant thread (`/gardens/:id/agent/history`, `POST /agent/chat` SSE) load per tab/peek.
|
||||
|
||||
## Assets
|
||||
No binary assets. Icons are Lucide (https://lucide.dev) inlined at **stroke-width 2.75**, round caps/joins: sprout (brand + Plants mode), shovel (Build), notebook (Journal), message-circle (Assistant), settings, undo-2, rotate-cw, trash-2, plus, minus, maximize (fit), chevron-left/right, x, camera, share-2, copy, lock, monitor/sun/moon (theme), search, send. Object-kind "icons" are not glyphs — they're mini SVG swatches of the kind's actual shape, fill, and stroke.
|
||||
|
||||
## Implementation notes for pansy specifically
|
||||
- Keep rendering as **plain SVG** — the prototypes prove tens of objects + low-hundreds of plops need nothing heavier; native hit-testing does all picking.
|
||||
- The editor's desktop/phone split is **container-width-driven (760px)**, one component tree, two chromes — don't build two apps.
|
||||
- Semantic zoom thresholds that felt right: monogram at `r·s ≥ 9px`, plop name at `≥ 34px`, object labels when `max(w,h)·s > 54px`.
|
||||
- Imperial display is presentation-only: cm → nearest inch, shown as `3′` / `1′6″` / `8″`; API stays metric.
|
||||
- Theme: implement exactly as `pansy-theme.js` does — token overrides on the root element, `color-scheme` set, system watcher. No second stylesheet, no class swapping on every node.
|
||||
Reference in New Issue
Block a user