/*
 * Design tokens — the single source of custom properties for the shop
 * front. Raw brand values are PLAN.md section 8 (canonical); the semantic
 * layer built on top of them is DESIGN.md section 1 (canonical). Nothing
 * in this project should hardcode a hex value, spacing number, or font
 * size outside this file — see DESIGN.md before adding a new one.
 */

:root {
  /* Brand — raw values, PLAN.md section 8. Never reference these directly
     in component CSS; use the semantic --color-* tokens below instead. */
  --sand: #f3ede4;
  --oat: #ddd1c0;
  --taupe: #9c8a76;
  --cocoa: #4a3f35;

  --font-display: "Jost", system-ui, sans-serif;
  --font-body: "Jost", system-ui, sans-serif;

  --track-wide: 0.34em;
  /* The quieter echo of the wordmark's tracking, for UI text set in caps —
     nav, buttons, small labels. DESIGN.md section 3 already describes it
     as "a quieter echo of the wordmark's 0.34em, not a repeat of it"; it
     was written as a literal 0.08em in each place that used it, so this
     names it once. */
  --track-ui: 0.08em;
  /* Three tiers, grouped by what kind of thing they're applied to, not
     one shared value — a single flat value (tried at 2px, 10px, then
     16px) always looked wrong on one end of the size range: fine on a
     300px+ product photo, disproportionately "pill-like" on a ~48px
     button.
       - radius-sm: content/data-entry surfaces — form inputs, the skip
         link, and the filter bar's own dropdown *panels* (the
         rectangular popover boxes, not the pills inside them). A
         rectangle reads as "content," not an accident.
       - radius-lg: large surfaces — product images, the home hero band.
         16px confirmed unmistakably rounded, then eased to 12px — still
         clearly soft/premium at product-card width, just a touch more
         refined than 16px, and closer to radius-sm than 16px was, so the
         two read as one coherent system rather than two unrelated
         choices.
       - radius-pill: every actionable "press me / pick me" control site-
         wide — buttons, badges, the filter bar's search input/dropdown
         toggles/chips, and the product page's quantity steppers, colour
         swatch previews and gallery expand button. Originally scoped to
         just the filter bar
         (on request for a more current, boutique-e-commerce look), then
         extended to buttons and badges on direct follow-up feedback —
         "we must follow consistency, make everything reusable and
         consistent." A pill reads as "action/tag"; a rectangle (above)
         reads as "content/data-entry" — two deliberate categories, not
         one flat rule, which is *why* form inputs stay radius-sm on
         purpose rather than becoming pills too. 9999px, not a fixed
         pixel value, so it stays fully rounded regardless of height. */
  --radius-sm: 8px;
  --radius-lg: 12px;
  --radius-pill: 9999px;
  --measure: 68ch;

  /* Color — semantic roles, DESIGN.md section 1. Taupe is structural only
     (borders, dividers, icon fills) — never text. See DESIGN.md for why:
     it fails WCAG contrast against both Sand and Oat backgrounds. */
  --color-bg-primary: var(--sand);
  --color-bg-secondary: var(--oat);
  --color-text-primary: var(--cocoa);
  --color-text-muted: color-mix(in srgb, var(--cocoa) 65%, transparent);
  --color-border: var(--taupe);
  --color-accent: var(--cocoa);

  /* Soft surface + hairline, for grouping panels that should read as a
     considered block without becoming a heavy card. The wash is Oat at
     just over a third, so it separates from Sand without ever competing
     with product photography; the hairline is Taupe at 45%, quiet enough
     to repeat down a list of reviews without ruling the page into boxes.

     Both are tokens because each was being written inline: the hairline
     mix had already been hand-typed in three places (buy box rules,
     review dividers, and now the rating summary), which is exactly the
     "a second component needs the same thing" trigger in DESIGN.md
     section 8b. */
  --color-surface-soft: color-mix(in srgb, var(--oat) 38%, transparent);
  --color-border-soft: color-mix(in srgb, var(--taupe) 45%, transparent);


  /* The one functional colour, added after the original strict
     four-tone-only rule was relaxed on request ("we can add colours to
     indicate things for customer focus, but we will not go fully out").
     Rust, not a generic e-commerce red: it sits in the same warm, earthy
     family as Cocoa and Taupe, so it reads as attention without making
     the page look like a template. Measured against the real
     backgrounds — 6.27:1 on Sand, 4.85:1 on Oat — so it passes WCAG AA
     for text on both, unlike Taupe (2.9:1), which is why Taupe stays
     structural-only.

     Two rules keep it from spreading (DESIGN.md section 2):
       1. It signals MEANING, never decoration — sale, error, genuine
          urgency. Never headings, never a button that isn't already
          Cocoa.
       2. It is never the ONLY signal. A sale badge is colour + text +
          shape; out-of-stock stays text + fade and only then colour.
          That's what keeps the site readable for colourblind customers,
          a guarantee the original no-colour system had for free. */
  --color-attention: #8f3e1e;
  --color-attention-soft: color-mix(in srgb, #8f3e1e 10%, transparent);

  /* The second functional colour — BACK OFFICE ONLY. DESIGN.md section 9b.

     This is a formalisation, not an expansion: apps/reviews/admin.py had
     already hardcoded #3f5d45 as an inline style, so the hue was shipped and
     ungoverned. Naming it here is what makes it reviewable.

     It exists because the back office has states the shop front does not —
     "this is fine, nothing to do" is a real answer for a stock ledger and is
     not a thing a product page ever needs to say. Section 2's actual rule is
     unchanged and applies here too: never colour alone, every state also
     carries its word. Someone who cannot tell moss from rust reads exactly
     the same information.

     Not for storefront use. tests/test_template_hygiene.py fails if it
     appears in main.css or any non-admin template. */
  --color-positive: #3f5d45;
  --color-positive-soft: color-mix(in srgb, #3f5d45 10%, transparent);

  /* Spacing — 8px base scale. space-1/2/3 are micro, component-level gaps
     (button padding, form fields, badges) and stay fixed on purpose — a
     button that changes shape with viewport width reads as inconsistent,
     not premium. space-4/5/6 are macro, layout-level spacing (page
     padding, section gaps) and scale fluidly instead — see the fluid
     scale note below. */
  /* space-0 is a half-step below the scale, added for the back office. A
     dense data table needs a gap that reads as "these belong together"
     rather than "these are separate", and 8px is already too much at that
     size. Deliberately not used on the shop front, where generous
     whitespace is the whole point. */
  --space-0: 0.25rem;
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;

  /* Type scale. text-sm/base/lg stay fixed for the same reason as
     space-1/2/3 — they're body copy and functional UI text (prices,
     labels, subtotals), where a predictable, non-shifting size matters
     more than fluid scaling. text-xl/2xl are display/heading sizes only
     and get the fluid treatment below. */
  /* text-xs is back-office only, and restricted to uppercase labels carrying
     --track-ui. Below 0.875rem, Jost at weight 400 loses too much at
     sentence length to be body copy — it is a label size, not a reading
     size. */
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.25rem;

  /* Fluid scale — text-xl/2xl and space-4/5/6 scale continuously between
     a 20rem (320px) and 75rem (1200px) viewport, instead of jumping at
     the 48rem breakpoint.

     That 75rem ceiling used to be justified as "the container cap, so no
     reason to grow past it". The container is now 90rem and the ceiling
     deliberately stayed at 75rem: headings and spacing reach their
     intended maximum at 1200px, and the extra 240px goes to product
     photography rather than to ever-larger type. Body copy is capped by
     --measure (68ch) regardless, so nothing gets harder to read as the
     page widens.
     Every clamp() below is MIN, then a linear rem+vw interpolation
     between the two viewports, then MAX — the standard fluid-scale
     formula (see DESIGN.md section 4). Falls back to MIN in browsers
     without clamp()/vw support (effectively none left, but it degrades
     to the smallest size, never breaks). */
  --text-xl: clamp(1.375rem, 1.24rem + 0.68vw, 1.75rem);
  --text-2xl: clamp(1.75rem, 1.48rem + 1.36vw, 2.5rem); /* wordmark / hero only */

  --space-4: clamp(1.5rem, 1.32rem + 0.91vw, 2rem);
  --space-5: clamp(2rem, 1.64rem + 1.82vw, 3rem);
  --space-6: clamp(2.5rem, 1.95rem + 2.73vw, 4rem);

  /* Two weights only, per brand guide. 300 is wordmark/display-only —
     everything else (body, UI, buttons, labels) uses 400. */
  --font-weight-light: 300;
  --font-weight-regular: 400;

  /* Stacking order. There was no scale: 30 was hardcoded on .dropdown-panel
     and 100 on .skip-link, which was survivable with two of them. The back
     office adds sticky action bars, and a third hardcoded number is where an
     arms race starts. Named so the question becomes "which layer is this"
     rather than "what number beats the last one". */
  --z-sticky: 10;
  --z-dropdown: 30;
  --z-overlay: 100;

  /* The only shadow in the back office, and it is functional rather than
     decorative: it exists so content scrolling beneath a sticky bar reads as
     passing *under* it. The storefront's flat, shadowless product cards are
     a considered brand decision and are untouched by this. */
  --elevation-raised: 0 1px 3px color-mix(in srgb, var(--cocoa) 12%, transparent);

  /* Focus — one ring, used by every interactive element. Never color-only
     (see DESIGN.md section 2: state is never color-only either). */
  /* Motion — DESIGN.md section 5. Three durations, no others, and every
     transition decelerates into rest. These were written as literals in 22
     places across main.css, which is how two dropdowns ended up on
     different timings; naming them is what makes "one motion system"
     enforceable rather than aspirational. */
  --duration-fast: 180ms;    /* small physical feedback: colour, hover */
  --duration-panel: 200ms;   /* panels arriving/leaving */
  --duration-content: 300ms; /* content settling in: image fade-in */

  /* The panel curve. Still ease-out in the sense section 5 requires — it
     decelerates the whole way and never overshoots — but far more
     pronounced than the keyword, so a panel arrives with some life instead
     of drifting in. A true spring was considered and rejected: overshoot
     accelerates back out of rest, which section 5 rules out, and a
     bouncing panel argues with a brand whose interface is meant to
     recede. */
  --ease-panel: cubic-bezier(0.16, 1, 0.3, 1);

  --focus-ring: 2px solid var(--cocoa);
  --focus-ring-offset: 2px;
}
