Files
pansy/web/src/styles/index.css
T
steveandClaude Fable 5 52b2c09a9e
Build image / build-and-push (push) Successful in 31s
Gadfly review (reusable) / review (pull_request) Failing after 1s
Adversarial Review (Gadfly) / review (pull_request) Failing after 1s
Replace the UI with the Organic design handoff (docs/design_handoff_pansy_ui)
The frontend is rebuilt screen by screen from the handoff: warm cream ground,
terracotta + sage accents, Caprasimo over Figtree, every control a pill. Same
React/Vite/TanStack stack and the same lib/ data layer; the presentation is new.

- Tokens: web/src/styles/index.css declares the handoff's styles.css variables
  through Tailwind's @theme under the same names; dark mode is those variables
  overridden on <html> by the handoff's pansy-theme.js, inlined in index.html
  so it runs before first paint. Lucide glyphs at stroke 2.75; a small pill kit
  (Button, Dialog, Field, Seg, Toggle, Tag, toast).
- Login / Register: the centered column over soft accent circles; OIDC button
  and signup footer still follow /auth/providers.
- Gardens: cards with a real SVG plot thumbnail (objects + plant-colored dots
  from /full), a `plan` tag for "<name> — <year>" copies, shares line, Open +
  share/copy/edit/delete; New garden / Share / Plan-a-season dialogs.
- Plants: monogram markers derived from the name (collision-resolved across the
  catalog — replaces emoji icons), category chips, expandable lot cards, the
  scan-packet flow as a two-step dialog that never auto-creates.
- Settings: Appearance (theme seg), Who gets in (read-only sign-in config),
  Garden assistant (self-saving toggle + chat/vision model fields), You.
- Editor: a new canvas with the prototype's pointer model (wheel-to-cursor,
  pinch about the centroid, 3″ snap, one PATCH per drop, semantic-zoom
  monograms/labels), plus corner resize handles; desktop three-card workspace
  (toolkit | plan | rail with Plot/Journal/History/Assistant) and, below 760px
  of container width, the phone chrome (header, peek panel, tool strip, mode
  bar). Seasons as a segmented control over the years with data plus plan
  copies; Undo re-reads history before reverting the newest step.
- Public read-only view and the register page restyled to match.
- GET /settings gains a read-only `auth` view (registration mode, local auth,
  OIDC issuer) so the Settings page can show what's in force.
- README / DESIGN.md / CLAUDE.md updated; @use-gesture/react dropped.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 19:12:29 -04:00

464 lines
12 KiB
CSS

@import 'tailwindcss';
/* ─────────────────────────────────────────────────────────────────────────────
"Organic" — the design tokens from docs/design_handoff_pansy_ui (its
_ds/…/styles.css is the source of truth; these are the same values under the
same names). Tailwind utilities reference these variables by name, and dark
mode is done by overriding the SAME variables on <html> from the theme script
in index.html — so there is exactly one set of names, no second stylesheet,
and no class swapping. Every namespace is reset first so the only colors,
fonts and radii that exist in this app are the design's.
───────────────────────────────────────────────────────────────────────────── */
@theme {
--color-*: initial;
--font-*: initial;
--shadow-*: initial;
--radius-*: initial;
--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);
/* The monogram ink on plant markers; does not change between modes. */
--color-paper: #fffaf1;
--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;
/* Ink + canvas roles alias the --p-* properties declared on :root below (the
names the theme script overrides), so text-ink-mute etc. flip with the
theme as well. */
--color-ink-strong: var(--p-ink-strong);
--color-ink-soft: var(--p-ink-soft);
--color-ink-mute: var(--p-ink-mute);
--color-field: var(--p-field);
--font-heading: 'Caprasimo', system-ui, sans-serif;
--font-body: 'Figtree', system-ui, sans-serif;
--radius-sm: 8px;
--radius-md: 16px;
--radius-xl: 22px;
--radius-lg: 28px;
}
@layer base {
:root {
/* Canvas + ink tokens (light values). Dark values live in index.html. */
--p-field: #efe3c9;
--p-grid-ink: #201e1d;
--p-ink-strong: #474238;
--p-ink-soft: #645c50;
--p-ink-mute: #82796a;
--p-bed-fill: #e3d0ac;
--p-bed-stroke: #b5a37f;
--p-ing-fill: #d6bf98;
--p-ing-stroke: #b1a07c;
--p-path-fill: #ece1cb;
--p-path-stroke: #cabfa6;
--p-bag-fill: #d8c5a2;
--p-bag-stroke: #a99677;
--p-bkt-fill: #cfc3ad;
--p-bkt-stroke: #9a8d76;
--p-tree-fill: #dce9c6;
--p-tree-stroke: #8fa073;
--p-str-fill: #d9cfbc;
--p-str-stroke: #a3947c;
/* Elevation — ink-tinted on the light ground (the theme script swaps in
ambient darkness). Plain custom properties rather than Tailwind's shadow
namespace, which inlines its values and so could not follow the override:
use the .elev-* classes below. */
--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);
}
html,
body,
#root {
height: 100%;
}
body {
margin: 0;
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-body);
font-size: 15px;
line-height: 1.55;
font-weight: 400;
-webkit-font-smoothing: antialiased;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-heading);
font-weight: 400;
line-height: 1.12;
letter-spacing: -0.015em;
margin: 0;
}
h1 {
font-size: 42px;
}
h2 {
font-size: 32px;
}
h3 {
font-size: 25px;
}
h4 {
font-size: 20px;
}
h5 {
font-size: 16px;
}
h6 {
font-size: 13px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
a {
color: var(--color-accent);
text-underline-offset: 3px;
}
a:hover {
color: var(--color-accent-600);
}
button {
cursor: pointer;
font: inherit;
color: inherit;
}
button:disabled {
cursor: not-allowed;
}
: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);
}
::-webkit-scrollbar {
height: 6px;
width: 6px;
}
::-webkit-scrollbar-thumb {
background: var(--color-neutral-400);
border-radius: 99px;
}
::-webkit-scrollbar-track {
background: transparent;
}
}
/* ── Components — the handful of classes the prototypes lean on ─────────── */
@layer components {
.elev-sm {
box-shadow: var(--shadow-sm);
}
.elev-md {
box-shadow: var(--shadow-md);
}
.elev-lg {
box-shadow: var(--shadow-lg);
}
/* Buttons: every control is a pill; labels are set in the display face. */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
cursor: pointer;
text-decoration: none;
font-family: var(--font-heading);
font-weight: 400;
font-size: 14px;
line-height: 1.2;
color: var(--color-text);
background: transparent;
border: 1px solid transparent;
padding: 8.8px 15.84px;
border-radius: 999px;
white-space: nowrap;
}
.btn svg {
display: block;
flex: none;
}
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.btn-primary {
background: var(--color-accent);
color: var(--color-bg);
}
.btn-primary:hover:not(:disabled) {
background: var(--color-accent-600);
}
.btn-primary:active:not(:disabled) {
background: var(--color-accent-700);
}
.btn-secondary {
border-color: var(--color-divider);
}
.btn-secondary:hover:not(:disabled) {
background: color-mix(in srgb, var(--color-text) 7%, transparent);
}
.btn-secondary:active:not(:disabled) {
background: color-mix(in srgb, var(--color-text) 14%, transparent);
}
.btn-ghost {
color: var(--color-accent);
padding-inline: 4.4px;
}
.btn-ghost:hover:not(:disabled) {
background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}
.btn-ghost:active:not(:disabled) {
background: color-mix(in srgb, var(--color-accent) 18%, transparent);
}
.btn-icon {
width: 36px;
height: 36px;
padding: 0;
flex: none;
}
.btn-block {
width: 100%;
}
/* A bare icon button on a surface (zoom pill, peek close): no border, a soft
accent wash on hover. */
.btn-soft:hover:not(:disabled) {
background: var(--color-accent-100);
}
/* 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 14px;
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: 999px;
}
.input::placeholder {
color: var(--p-ink-mute);
opacity: 1;
}
.input:hover {
border-color: color-mix(in srgb, var(--color-text) 45%, transparent);
}
.input:focus-visible {
border-color: var(--color-accent);
outline-offset: 0;
}
.input:disabled,
.input[readonly] {
cursor: default;
}
.input:disabled {
opacity: 0.7;
}
textarea.input {
min-height: 90px;
resize: vertical;
border-radius: var(--radius-md);
}
/* Phone: 16px stops iOS Safari zooming the page on focus. */
.input-lg {
font-size: 16px;
min-height: 44px;
}
/* Tags */
.tag {
display: inline-flex;
align-items: center;
font-size: 11px;
letter-spacing: 0.02em;
padding: 3px 10px;
border-radius: 999px;
white-space: nowrap;
font-weight: 400;
}
.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);
}
/* The raised panel every page builds from: cards, the editor's three regions. */
.panel {
background: var(--color-neutral-100);
border: 1px solid var(--color-divider);
border-radius: var(--radius-lg);
}
/* Segmented pill control: a neutral-200 track, the active option lifts. */
.seg {
display: inline-flex;
background: var(--color-neutral-200);
border-radius: 999px;
padding: 3px;
}
.seg-opt {
padding: 5px 16px;
cursor: pointer;
border: none;
border-radius: 999px;
white-space: nowrap;
font-family: var(--font-body);
font-size: 12.5px;
font-weight: 700;
line-height: 1.55;
background: transparent;
color: var(--p-ink-soft);
}
.seg-opt[aria-pressed='true'] {
background: var(--color-neutral-100);
color: var(--color-text);
box-shadow: var(--shadow-sm);
}
.seg-opt:disabled {
cursor: default;
}
/* On/Off pill */
.toggle {
cursor: pointer;
border: none;
border-radius: 999px;
padding: 6px 18px;
font-family: var(--font-body);
font-size: 12.5px;
font-weight: 700;
line-height: 1.55;
background: var(--color-neutral-300);
color: var(--p-ink-soft);
}
.toggle[aria-pressed='true'] {
background: var(--color-accent-2-300);
color: var(--color-accent-2-800);
}
.toggle:disabled {
cursor: default;
opacity: 0.8;
}
/* Chips: category filters, the phone tool strip, rail tabs share this shape. */
.chip {
cursor: pointer;
border: 1px solid var(--color-divider);
border-radius: 999px;
padding: 7px 16px;
font-family: var(--font-body);
font-size: 13px;
font-weight: 700;
line-height: 1.55;
background: transparent;
color: var(--p-ink-soft);
white-space: nowrap;
}
.chip[aria-pressed='true'],
.chip-active {
background: var(--color-accent-200);
border-color: var(--color-accent-400);
color: var(--color-accent-800);
}
/* Dialogs */
.dialog-backdrop {
position: fixed;
inset: 0;
background: color-mix(in srgb, #201e1d 40%, transparent);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
z-index: 50;
}
.dialog {
background: var(--color-neutral-100);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: 26px;
width: min(420px, 100%);
max-height: calc(100dvh - 40px);
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 14px;
}
.hr {
height: 1px;
border: 0;
background: var(--color-divider);
}
/* The editor's camera move: fit/focus animate, drags and zooms don't. */
.camera-anim {
transition: transform 0.48s cubic-bezier(0.22, 0.85, 0.3, 1);
}
}