diff --git a/docs/design_handoff_pansy_ui/Pansy Editor.dc.html b/docs/design_handoff_pansy_ui/Pansy Editor.dc.html new file mode 100644 index 0000000..ff74e38 --- /dev/null +++ b/docs/design_handoff_pansy_ui/Pansy Editor.dc.html @@ -0,0 +1,901 @@ + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+ +
Toolkit
+ + + +
Drag a shape onto the plan. Double-click any bed to plant it.
+
+ +
+ + {{ focusName }} +
+ + + + +
+
+ +
+
+

Home Garden

+ 40′ × 28′ + + /{{ focusName }} + + + + + + + + + +
+ +
{{ banner }}
+
+
+ + + + + + + + + + + + + + + + + {{ labelsLayer }} + + + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + +
+
+ + + + +
+ {{ insp.kindLabel }} + {{ insp.sizeText }} +
+
{{ insp.rosterText }}
+
+ + + +
+
+ +
+ + {{ insp.plantName }} +
+
{{ insp.countText }} · {{ insp.plopSize }} patch
+ +
+
+ +
This garden
+
{{ summary.countsText }}
+
+ +
+ + {{ r.name }} + {{ r.where }} +
+
+
+
Select anything on the plan to edit it here. Double-click a bed to plant it.
+
+
+ + +
+
+ {{ e.obj }} + {{ e.d }} +
+
{{ e.txt }}
+
+
+
+ + +
+
+ +
Every change — yours or the assistant's — is one undoable step.
+ +
+ + {{ h.label }} + {{ h.when }} +
+
+
Nothing yet this session — go move something.
+ +
+ + +
{{ m.txt }}
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+ + + + + + + + + {{ mTitle }} + + + + + +
+ +
{{ banner }}
+
+
+ + + + + + + + + + + + + + + + + {{ labelsLayer }} + + + + + + + + + +
+ + +
+
+ {{ mPeekTitle }} + +
+
+ + + +
+ {{ insp.kindLabel }} + {{ insp.sizeText }} +
+
{{ insp.rosterText }}
+
+ + + +
+
+ +
+ + {{ insp.plantName }} + {{ insp.countText }} +
+ +
+
+ +
+ + +
+ +
+
+ {{ e.obj }} + {{ e.d }} +
+
{{ e.txt }}
+
+
+
+ + +
{{ m.txt }}
+
+
+ + +
+
+
+
+
+ + +
+ + + + + + + + + + + + + +
+
+ +
+ + + +
+
+
+
+
+ + + diff --git a/docs/design_handoff_pansy_ui/Pansy Gardens.dc.html b/docs/design_handoff_pansy_ui/Pansy Gardens.dc.html new file mode 100644 index 0000000..6cefde0 --- /dev/null +++ b/docs/design_handoff_pansy_ui/Pansy Gardens.dc.html @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + +
+ +
+
+

Gardens

+ Every plot you tend — and the ones you're scheming + +
+
+ +
+ + + + + + + + +
+
+ {{ g.name }} + {{ g.planTag }} + {{ g.sizeText }} +
+
{{ g.meta }}
+ +
{{ g.sharedText }}
+
+
+ Open + + + +
+
+
+
+
+
+ + +
+
+

A new garden

+
+
+
+
+
+
Stored in centimeters under the hood — feet are just how you talk.
+
+ + +
+
+
+
+ + +
+
+

Share {{ shareName }}

+
+ + +
+ +
+ {{ sh.email }} + +
+
+
+
+ Read-only link + +
+ +
pansy.example.com/p/gd_7Kx2mQ…
+
+
+ +
+
+
+
+
+
+ + + diff --git a/docs/design_handoff_pansy_ui/Pansy Login.dc.html b/docs/design_handoff_pansy_ui/Pansy Login.dc.html new file mode 100644 index 0000000..2dba426 --- /dev/null +++ b/docs/design_handoff_pansy_ui/Pansy Login.dc.html @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +

pansy

+

Plan the plot. Keep the notes. Grow the thing.

+
+
+
+
+ Into the garden +
+ + or + +
+ + + Sign in with Authentik + +
+

New here? Create an account — the first one becomes admin.

+
+
+
+ + + diff --git a/docs/design_handoff_pansy_ui/Pansy Phone Preview.dc.html b/docs/design_handoff_pansy_ui/Pansy Phone Preview.dc.html new file mode 100644 index 0000000..6334bfb --- /dev/null +++ b/docs/design_handoff_pansy_ui/Pansy Phone Preview.dc.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + +
+
+

pansy in your pocket

+ same editor, one column — pinch to zoom, tap a bed, plant from the strip +
+ +
+ +
+
+
+
+ + diff --git a/docs/design_handoff_pansy_ui/Pansy Plants.dc.html b/docs/design_handoff_pansy_ui/Pansy Plants.dc.html new file mode 100644 index 0000000..da13c79 --- /dev/null +++ b/docs/design_handoff_pansy_ui/Pansy Plants.dc.html @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + +
+ +
+
+

Plants

+ The built-ins plus everything you've added + + + + +
+
+ + + + +
+
+ +
+
+ + {{ p.letter }} + + {{ p.name }} + {{ p.sub }} + + built-in +
+
{{ p.lotText }}
+ +
+ +
+
+ {{ l.vendor }} + packed for {{ l.year }} +
+
{{ l.detail }}
+
+
+
No seed lots yet — scan a packet to add one.
+
+
+
+
+
+
+ + +
+
+

Scan a seed packet

+ +
+ +
Photograph the packet — front is enough
+
The vision model reads it into fields. It only reads; nothing is saved until you confirm.
+ +
+
+ +
+
+
+
+
+
+
Match it to your catalog — nothing is auto-created:
+ + + +
+ + +
+
+
+
+
+ + +
+
+

A new plant

+
+
+
+ +
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+
+
+ + + diff --git a/docs/design_handoff_pansy_ui/Pansy Settings.dc.html b/docs/design_handoff_pansy_ui/Pansy Settings.dc.html new file mode 100644 index 0000000..3f45f3c --- /dev/null +++ b/docs/design_handoff_pansy_ui/Pansy Settings.dc.html @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + +
+ +
+
+

Settings

+ admin + Instance-wide · everyone on this pansy +
+ +
+
Appearance
+
+ + Theme + System follows your device + + + + +
+
+ +
+
Who gets in
+
+ Self-service signup + + + +
+
+ + Local passwords + Turn off for pure-Authentik sign-in + + +
+
+ +
+
Client ID and secret live in the environment, not here. The first account ever registered became admin — that's you.
+
+ +
+
+
Garden assistant
+ {{ agentStatus }} +
+
+ + Assistant + Swaps live — no restart + + +
+
+ +
+
+ +
+
The API key stays in the environment on purpose — a secret in the database would ride along in every backup. Precedence: this page → env → default.
+
+ +
+
You
+
+
+
+
+
+ Sign out +
+
+
+
+
+ + + diff --git a/docs/design_handoff_pansy_ui/README.md b/docs/design_handoff_pansy_ui/README.md new file mode 100644 index 0000000..5cc9c1b --- /dev/null +++ b/docs/design_handoff_pansy_ui/README.md @@ -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 `` (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. diff --git a/docs/design_handoff_pansy_ui/_ds/organic-9b68eb3f-20a3-40f4-b132-a5a6542b5b19/_ds_bundle.js b/docs/design_handoff_pansy_ui/_ds/organic-9b68eb3f-20a3-40f4-b132-a5a6542b5b19/_ds_bundle.js new file mode 100644 index 0000000..3b11c8a --- /dev/null +++ b/docs/design_handoff_pansy_ui/_ds/organic-9b68eb3f-20a3-40f4-b132-a5a6542b5b19/_ds_bundle.js @@ -0,0 +1,11 @@ +/* @ds-bundle: {"format":4,"namespace":"Organic_organi","components":[],"sourceHashes":{},"inlinedExternals":[],"unexposedExports":[]} */ + +(() => { + +const __ds_ns = (window.Organic_organi = window.Organic_organi || {}); + +const __ds_scope = {}; + +(__ds_ns.__errors = __ds_ns.__errors || []); + +})(); diff --git a/docs/design_handoff_pansy_ui/_ds/organic-9b68eb3f-20a3-40f4-b132-a5a6542b5b19/styles.css b/docs/design_handoff_pansy_ui/_ds/organic-9b68eb3f-20a3-40f4-b132-a5a6542b5b19/styles.css new file mode 100644 index 0000000..0f306b0 --- /dev/null +++ b/docs/design_handoff_pansy_ui/_ds/organic-9b68eb3f-20a3-40f4-b132-a5a6542b5b19/styles.css @@ -0,0 +1,257 @@ +/* Organic — design-system tokens and component classes. This file is the source of truth for the system's look; retune it here and see readme.md. */ +@import url('https://fonts.googleapis.com/css2?family=Caprasimo:wght@400&family=Figtree:wght@400;600;700&display=swap'); + +:root { + --color-bg: #f5ead8; + --color-surface: #ebddc5; + --color-text: #201e1d; + --color-accent: #c67139; + --color-accent-2: #7a8a5e; + --color-divider: color-mix(in srgb, #201e1d 16%, transparent); + + /* Tonal ramps — generated in OKLCH on one shared lightness scale, so the + same step of any role matches the others in visual value. */ + --color-neutral-100: #f9f4ed; + --color-neutral-200: #eee7db; + --color-neutral-300: #dcd3c4; + --color-neutral-400: #c0b6a5; + --color-neutral-500: #a19786; + --color-neutral-600: #82796a; + --color-neutral-700: #645c50; + --color-neutral-800: #474238; + --color-neutral-900: #2e2b25; + + --color-accent-100: #fff2eb; + --color-accent-200: #ffe1d0; + --color-accent-300: #ffc6a5; + --color-accent-400: #f6a06b; + --color-accent-500: #d67f48; + --color-accent-600: #b2622d; + --color-accent-700: #8c491a; + --color-accent-800: #643312; + --color-accent-900: #402310; + + --color-accent-2-100: #f0fae1; + --color-accent-2-200: #e1eecc; + --color-accent-2-300: #ccdbb2; + --color-accent-2-400: #aebf92; + --color-accent-2-500: #8fa073; + --color-accent-2-600: #728157; + --color-accent-2-700: #56633f; + --color-accent-2-800: #3d472b; + --color-accent-2-900: #272e1b; + + --font-heading: "Caprasimo", system-ui, sans-serif; + --font-heading-weight: 400; + --font-body: "Figtree", system-ui, sans-serif; + + --space-1: 4.4px; + --space-2: 8.8px; + --space-3: 13.2px; + --space-4: 17.6px; + --space-6: 26.4px; + --space-8: 35.2px; + + --radius-sm: 8px; + --radius-md: 16px; + --radius-lg: 28px; + + /* Elevation — derived from the ground: soft ink-tinted shadows on a + light theme, a hairline edge + ambient darkness on a dark one. */ + --shadow-sm: 0 1px 2px color-mix(in srgb, #2e2b25 14%, transparent); + --shadow-md: 0 3px 10px color-mix(in srgb, #2e2b25 16%, transparent); + --shadow-lg: 0 12px 32px color-mix(in srgb, #2e2b25 22%, transparent); +} + +body { + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-body); +} +h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: var(--font-heading-weight); } + +.washed{filter:saturate(0.6) contrast(0.85) brightness(1.1) opacity(0.94)} + +/* ══════════════════════════════════════════════════════════════════════════ + Components — built with the tokens above. Plain CSS + on plain HTML: no JavaScript, no build step. Each class is documented in + readme.md and demonstrated in foundations/ and components/. + ══════════════════════════════════════════════════════════════════════ */ + +*, *::before, *::after { box-sizing: border-box; } +body { margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400; } +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-heading); font-weight: var(--font-heading-weight); + line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2); +} +h1 { font-size: 42px; } +h2 { font-size: 32px; } +h3 { font-size: 25px; } +h4 { font-size: 20px; } +h5 { font-size: 16px; } +h6 { font-size: 13px; } +h6 { letter-spacing: 0.08em; text-transform: uppercase; } +p { margin: 0 0 var(--space-3); } +a { color: var(--color-accent); text-underline-offset: 3px; } +img { display: block; max-width: 100%; } +figure { margin: 0; } +figcaption { + font-size: 11px; margin-top: var(--space-1); + color: color-mix(in srgb, var(--color-text) 55%, transparent); +} +.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); } +:focus { outline: none; } +:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; } +::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); } + +/* — rules — */ +.hr { + height: 1px; border: 0; margin: var(--space-4) 0; + background: var(--color-divider); +} + +/* — buttons — */ +.btn { + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + cursor: pointer; text-decoration: none; + font-family: var(--font-heading); font-weight: var(--font-heading-weight); + font-size: 14px; line-height: 1.2; color: var(--color-text); /* matches the .input's 14px — + the pair sits side by side in sign-up rows */ + background: transparent; border: 1px solid transparent; + padding: var(--space-2) calc(var(--space-3) * 1.2); + border-radius: var(--radius-md); +} +.btn svg { display: block; } +.btn:disabled { opacity: 0.45; cursor: not-allowed; } +.btn-primary { background: var(--color-accent); color: var(--color-bg); } +.btn-primary:hover { background: var(--color-accent-600); } +.btn-primary:active { background: var(--color-accent-700); } +.btn-secondary { border-color: var(--color-divider); } +.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); } +.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); } +.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); } +.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); } +.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); } +.btn-icon { width: 36px; height: 36px; padding: 0; } +.btn-block { width: 100%; margin-top: var(--space-2); } + +/* — forms — */ +.field > label { + display: block; font-size: 12px; margin-bottom: 5px; + color: color-mix(in srgb, var(--color-text) 70%, transparent); +} +.input { + width: 100%; min-height: 36px; padding: 6px 10px; font: inherit; + font-size: 14px; color: var(--color-text); caret-color: var(--color-accent); + background: var(--color-surface); + border: 1px solid var(--color-divider); border-radius: var(--radius-md); +} +.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); } +.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; } +textarea.input { min-height: 90px; resize: vertical; } +.radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; } +.radio input, .seg-opt input { + position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; +} +.radio .dot { + width: 16px; height: 16px; flex: none; border-radius: 50%; + border: 1.5px solid var(--color-divider); +} +.radio:hover .dot { border-color: var(--color-accent); } +.radio input:checked + .dot { + border-color: var(--color-accent); background: var(--color-accent); + box-shadow: inset 0 0 0 4px var(--color-bg); +} +.radio input:focus-visible + .dot { outline: 2px solid var(--color-accent); outline-offset: 2px; } +.seg { + display: inline-flex; overflow: hidden; + border: 1px solid var(--color-divider); border-radius: var(--radius-md); +} +.seg-opt { + display: inline-flex; align-items: center; gap: 6px; + padding: 7px 12px; font-size: 13px; cursor: pointer; +} +.seg-opt + .seg-opt { border-left: 1px solid var(--color-divider); } +.seg-opt:has(input:checked) { background: var(--color-accent); color: var(--color-bg); } +.seg-opt:not(:has(input:checked)):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); } +.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; } + +/* — cards — */ +.card { + display: flex; flex-direction: column; gap: var(--space-2); + padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface); +} +.card-kicker { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); } +.card-title { + font-family: var(--font-heading); font-weight: var(--font-heading-weight); + font-size: 17px; line-height: 1.2; +} +.card-body { margin: 0; font-size: 13px; opacity: 0.8; flex: 1; } +.card-meta { + display: flex; align-items: center; gap: 6px; font-size: 11px; + color: color-mix(in srgb, var(--color-text) 50%, transparent); +} +.elev-sm { box-shadow: var(--shadow-sm); } +.elev-md { box-shadow: var(--shadow-md); } +.elev-lg { box-shadow: var(--shadow-lg); } + +/* — tags — */ +.tag { + display: inline-flex; align-items: center; font-size: 11px; + letter-spacing: 0.02em; padding: 3px 10px; + border-radius: calc(var(--radius-md) * 0.75); +} +.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); } +.tag-accent-2 { background: var(--color-accent-2-100); color: var(--color-accent-2-800); } +.tag-neutral { background: var(--color-neutral-100); color: var(--color-neutral-800); } +.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); } + +/* — navigation — */ +.nav { + display: flex; align-items: center; gap: var(--space-4); + padding: var(--space-3) var(--space-4); + border-bottom: none; +} +.nav-brand { + font-family: var(--font-heading); font-weight: var(--font-heading-weight); + font-size: 18px; margin-right: auto; +} +.nav a { color: inherit; text-decoration: none; font-size: 14px; } +.nav a:hover, .nav a[aria-current='page'] { color: var(--color-accent); } + +/* — tables — */ +.table { width: 100%; border-collapse: collapse; font-size: 14px; } +.table th { + text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; + color: color-mix(in srgb, var(--color-text) 60%, transparent); + padding: var(--space-2); border-bottom: 1px solid var(--color-divider); +} +.table td { + padding: var(--space-2); + border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent); +} +.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); } + +/* — dialog — */ +.dialog-backdrop { + position: fixed; inset: 0; display: grid; place-items: center; + padding: var(--space-4); + background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent); +} +.dialog { + width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3); + padding: var(--space-4); border-radius: var(--radius-lg); + background: var(--color-surface); box-shadow: var(--shadow-lg); +} +.dialog-title { + font-family: var(--font-heading); font-weight: var(--font-heading-weight); + font-size: 20px; +} +.dialog-body { font-size: 14px; opacity: 0.85; } +.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); } + +/* — rounded frame: everything softens, small controls go pill — */ +.card, .dialog { border-radius: calc(var(--radius-lg) * 1.15); } +.btn, .tag, .seg, .input { border-radius: 999px; } +.input { padding-inline: 14px; } + diff --git a/docs/design_handoff_pansy_ui/ios-frame.jsx b/docs/design_handoff_pansy_ui/ios-frame.jsx new file mode 100644 index 0000000..d6f5686 --- /dev/null +++ b/docs/design_handoff_pansy_ui/ios-frame.jsx @@ -0,0 +1,352 @@ +// @ds-adherence-ignore -- omelette starter scaffold (raw elements/hex/px by design) +// Copied omelette starter. Re-running copy_starter_component with this kind overwrites this file with the latest version (page content is unaffected). + +/* BEGIN USAGE */ +// iOS.jsx — Simplified iOS 26 (Liquid Glass) device frame +// Based on the iOS 26 UI Kit + Figma status bar spec. No assets, no deps. +// Exports (to window): IOSDevice, IOSStatusBar, IOSNavBar, IOSGlassPill, IOSList, IOSListRow, IOSKeyboard +// +// Usage — wrap your screen content in to get the bezel, status bar +// and home indicator (props: title, dark, keyboard): +// +// +// ...your screen content... +// +// +/* END USAGE */ + +// ───────────────────────────────────────────────────────────── +// Status bar +// ───────────────────────────────────────────────────────────── +function IOSStatusBar({ dark = false, time = '9:41' }) { + const c = dark ? '#fff' : '#000'; + return ( +
+
+ {time} +
+
+ + + + + + + + + + + + + + + + +
+
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Liquid glass pill — blur + tint + shine +// ───────────────────────────────────────────────────────────── +function IOSGlassPill({ children, dark = false, style = {} }) { + return ( +
+ {/* blur + tint */} +
+ {/* shine */} +
+
+ {children} +
+
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Navigation bar — glass pills + large title +// ───────────────────────────────────────────────────────────── +function IOSNavBar({ title = 'Title', dark = false, trailingIcon = true }) { + const muted = dark ? 'rgba(255,255,255,0.6)' : '#404040'; + const text = dark ? '#fff' : '#000'; + const pillIcon = (content) => ( + +
+ {content} +
+
+ ); + return ( +
+
+ {/* back chevron */} + {pillIcon( + + + + )} + {/* trailing ellipsis */} + {trailingIcon && pillIcon( + + + + + + )} +
+ {/* large title */} +
{title}
+
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Grouped list (inset card, r:26) + row (52px) +// ───────────────────────────────────────────────────────────── +function IOSListRow({ title, detail, icon, chevron = true, isLast = false, dark = false }) { + const text = dark ? '#fff' : '#000'; + const sec = dark ? 'rgba(235,235,245,0.6)' : 'rgba(60,60,67,0.6)'; + const ter = dark ? 'rgba(235,235,245,0.3)' : 'rgba(60,60,67,0.3)'; + const sep = dark ? 'rgba(84,84,88,0.65)' : 'rgba(60,60,67,0.12)'; + return ( +
+ {icon && ( +
+ )} +
{title}
+ {detail && {detail}} + {chevron && ( + + + + )} + {!isLast && ( +
+ )} +
+ ); +} + +function IOSList({ header, children, dark = false }) { + const hc = dark ? 'rgba(235,235,245,0.6)' : 'rgba(60,60,67,0.6)'; + const bg = dark ? '#1C1C1E' : '#fff'; + return ( +
+ {header && ( +
{header}
+ )} +
{children}
+
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Device frame +// ───────────────────────────────────────────────────────────── +function IOSDevice({ + children, width = 402, height = 874, dark = false, + title, keyboard = false, +}) { + return ( + // data-om-starter: inert presence marker — Claude Design's starter-usage + // probe reads it; it renders nothing. Keep it on this root element. +
+ {/* dynamic island */} +
+ {/* status bar (absolute) */} +
+ +
+ {/* nav + content */} +
+ {title !== undefined && } +
{children}
+ {keyboard && } +
+ {/* home indicator — always on top */} +
+
+
+
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Keyboard — iOS 26 liquid glass +// ───────────────────────────────────────────────────────────── +function IOSKeyboard({ dark = false }) { + const glyph = dark ? 'rgba(255,255,255,0.7)' : '#595959'; + const sugg = dark ? 'rgba(255,255,255,0.6)' : '#333'; + const keyBg = dark ? 'rgba(255,255,255,0.22)' : 'rgba(255,255,255,0.85)'; + + // special-key icons + const icons = { + shift: , + del: , + ret: , + }; + + const key = (content, { w, flex, ret, fs = 25, k } = {}) => ( +
{content}
+ ); + + const row = (keys, pad = 0) => ( +
+ {keys.map(l => key(l, { flex: true, k: l }))} +
+ ); + + return ( +
+ {/* liquid glass bg — same recipe as nav pills */} +
+
+ + {/* autocorrect bar */} +
+ {['"The"', 'the', 'to'].map((w, i) => ( + + {i > 0 &&
} +
{w}
+ + ))} +
+ + {/* key layout */} +
+ {row(['q','w','e','r','t','y','u','i','o','p'])} + {row(['a','s','d','f','g','h','j','k','l'], 20)} +
+ {key(icons.shift, { w: 45, k: 'shift' })} +
+ {['z','x','c','v','b','n','m'].map(l => key(l, { flex: true, k: l }))} +
+ {key(icons.del, { w: 45, k: 'del' })} +
+
+ {key('ABC', { w: 92.25, fs: 18, k: 'abc' })} + {key('', { flex: true, k: 'space' })} + {key(icons.ret, { w: 92.25, ret: true, k: 'ret' })} +
+
+ + {/* bottom spacer (emoji+mic area, icons omitted) */} +
+
+ ); +} + +Object.assign(window, { + IOSDevice, IOSStatusBar, IOSNavBar, IOSGlassPill, IOSList, IOSListRow, IOSKeyboard, +}); diff --git a/docs/design_handoff_pansy_ui/pansy-theme.js b/docs/design_handoff_pansy_ui/pansy-theme.js new file mode 100644 index 0000000..dcf13f5 --- /dev/null +++ b/docs/design_handoff_pansy_ui/pansy-theme.js @@ -0,0 +1,50 @@ +// Pansy theme — light is the stylesheet default; dark overrides the tokens on . +// Pref ('system'|'light'|'dark') persists in localStorage and is shared by every page. +(function () { + const KEY = 'pansy-theme'; + const DARK = { + '--color-bg': '#252220', '--color-surface': '#33302a', '--color-text': '#f1e9da', + '--color-divider': 'color-mix(in srgb, #f5ead8 16%, transparent)', + '--color-accent': '#d67f48', + '--color-neutral-100': '#2e2b25', '--color-neutral-200': '#3a362f', '--color-neutral-300': '#474238', + '--color-neutral-400': '#645c50', '--color-neutral-500': '#82796a', '--color-neutral-800': '#dcd3c4', + '--color-accent-100': '#3d2c1d', '--color-accent-200': '#59331a', '--color-accent-300': '#8c491a', + '--color-accent-400': '#d67f48', '--color-accent-700': '#f6a06b', '--color-accent-800': '#ffd9bd', '--color-accent-900': '#ffe9da', + '--color-accent-2-200': '#333d24', '--color-accent-2-100': '#2d3520', '--color-accent-2-300': '#3d472b', '--color-accent-2-500': '#728157', + '--color-accent-2-600': '#aebf92', '--color-accent-2-700': '#ccdbb2', '--color-accent-2-800': '#e1eecc', + '--shadow-sm': '0 1px 2px rgba(0,0,0,0.4)', '--shadow-md': '0 3px 10px rgba(0,0,0,0.45)', '--shadow-lg': '0 12px 32px rgba(0,0,0,0.55)', + '--p-field': '#2b2823', '--p-grid-ink': '#f5ead8', + '--p-ink-strong': '#d9d0bf', '--p-ink-soft': '#b3a992', '--p-ink-mute': '#8f8674', + '--p-bed-fill': '#4a4131', '--p-bed-stroke': '#6d5f47', '--p-ing-fill': '#3f382c', '--p-ing-stroke': '#5c5343', + '--p-path-fill': '#312e28', '--p-path-stroke': '#4d473c', '--p-bag-fill': '#463d2f', '--p-bag-stroke': '#6a5d49', + '--p-bkt-fill': '#3e382e', '--p-bkt-stroke': '#5f574a', '--p-tree-fill': '#333a28', '--p-tree-stroke': '#56633f', + '--p-str-fill': '#3b362e', '--p-str-stroke': '#5f574a', + }; + const P = { + get() { try { return localStorage.getItem(KEY) || 'system'; } catch (e) { return 'system'; } }, + set(v) { try { localStorage.setItem(KEY, v); } catch (e) {} }, + sysDark() { return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches); }, + isDark(pref) { return pref === 'dark' || (pref === 'system' && P.sysDark()); }, + apply(dark) { + const r = document.documentElement.style; + Object.keys(DARK).forEach(k => { dark ? r.setProperty(k, DARK[k]) : r.removeProperty(k); }); + r.colorScheme = dark ? 'dark' : 'light'; + }, + watch(cb) { + if (!window.matchMedia) return () => {}; + const m = window.matchMedia('(prefers-color-scheme: dark)'); + m.addEventListener('change', cb); + return () => m.removeEventListener('change', cb); + }, + next(p) { return p === 'system' ? 'light' : p === 'light' ? 'dark' : 'system'; }, + icon(p) { + return { + system: ['M4 3h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z', 'M8 21h8', 'M12 17v4'], + light: ['M12 8a4 4 0 1 0 0 8 4 4 0 1 0 0-8', 'M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4', ''], + dark: ['M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z', '', ''], + }[p] || ['', '', '']; + }, + }; + window.PansyTheme = P; + P.apply(P.isDark(P.get())); +})(); diff --git a/docs/design_handoff_pansy_ui/screenshots/editor-desktop-dark.png b/docs/design_handoff_pansy_ui/screenshots/editor-desktop-dark.png new file mode 100644 index 0000000..eb86c65 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/editor-desktop-dark.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/editor-desktop-light.png b/docs/design_handoff_pansy_ui/screenshots/editor-desktop-light.png new file mode 100644 index 0000000..16e1273 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/editor-desktop-light.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/editor-phone-dark.png b/docs/design_handoff_pansy_ui/screenshots/editor-phone-dark.png new file mode 100644 index 0000000..3c94940 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/editor-phone-dark.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/editor-phone-light.png b/docs/design_handoff_pansy_ui/screenshots/editor-phone-light.png new file mode 100644 index 0000000..3424012 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/editor-phone-light.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/gardens-dark.png b/docs/design_handoff_pansy_ui/screenshots/gardens-dark.png new file mode 100644 index 0000000..4b5ac88 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/gardens-dark.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/gardens-light.png b/docs/design_handoff_pansy_ui/screenshots/gardens-light.png new file mode 100644 index 0000000..54bebf7 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/gardens-light.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/login-dark.png b/docs/design_handoff_pansy_ui/screenshots/login-dark.png new file mode 100644 index 0000000..95f09c3 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/login-dark.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/login-light.png b/docs/design_handoff_pansy_ui/screenshots/login-light.png new file mode 100644 index 0000000..52355d2 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/login-light.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/plants-dark.png b/docs/design_handoff_pansy_ui/screenshots/plants-dark.png new file mode 100644 index 0000000..4432e51 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/plants-dark.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/plants-light.png b/docs/design_handoff_pansy_ui/screenshots/plants-light.png new file mode 100644 index 0000000..39d41f2 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/plants-light.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/settings-dark.png b/docs/design_handoff_pansy_ui/screenshots/settings-dark.png new file mode 100644 index 0000000..680f431 Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/settings-dark.png differ diff --git a/docs/design_handoff_pansy_ui/screenshots/settings-light.png b/docs/design_handoff_pansy_ui/screenshots/settings-light.png new file mode 100644 index 0000000..ff50cdc Binary files /dev/null and b/docs/design_handoff_pansy_ui/screenshots/settings-light.png differ