/* ============================================================
   JIGGER, DESIGN TOKENS
   Generated from the Figma variable collections. Names match
   one-for-one; contrast ratios are measured, not estimated.

   Three collections:
     1. Primitives. Raw values, never used directly
     2. Semantic. What a value MEANS, used by components
     3. Type Scale, Mobile / Desktop modes
   ============================================================ */

/* ── THE FACE ──────────────────────────────────────────────
   Served from the same directory rather than from Google Fonts: a CDN link
   fails on file:// and adds a render-blocking round trip on localhost.

   One file, four axes. wght carries every weight in the scale below; SHRP is
   the wordmark's only indulgence. CRSV and slnt are declared because the file
   has them, not because anything uses them yet. */
@font-face {
  font-family: "Geologica";
  src: url("Geologica-VariableFont_CRSV_SHRP_slnt_wght.ttf") format("truetype");
  font-weight: 100 900;      /* the whole axis, so 420 and 655 resolve */
  font-style: oblique 0deg 12deg;   /* slnt runs -12 to 0 */
  font-display: swap;
}

:root {

  /* ══ 1. PRIMITIVES ══════════════════════════════════════ */

  /* Colour, the Negroni palette. Alias these, never use them raw. */
  --colour-forest-900: #0E402D;
  --colour-forest-700: #1A5A41;
  --colour-forest-100: #D9E5DF;
  --colour-ink-900:    #14231C;
  --colour-ink-700:    #3B524A;
  --colour-ink-500:    #5A6F67;
  --colour-coral-600:  #B3452F;
  --colour-coral-500:  #EB5E55;
  --colour-garnet-700: #8C1D14;
  --colour-garnet-900: #6B0504;
  --colour-indigo-700: #2E294E;
  --colour-porcelain:  #FFFFFC;
  --colour-bone:       #EFF1EC;
  --colour-hairline:   #D8DED4;
  --colour-green-700:  #14532B;
  --colour-green-200:  #CFE4D4;
  --colour-amber-800:  #7A4A00;
  --colour-amber-200:  #F5D9A8;
  --colour-rose-200:   #F6D3CE;

  /* Spacing. 2px base, expressed in rem.
     Originally declared as a 4px scale, but counting every value in the
     design showed it was never followed: 2, 6, 10, 14, 20 and 40 were all
     in use. 2px alone accounts for the focus-ring offsets, which cannot be
     4 without changing the ring geometry. The scale now describes the
     design instead of contradicting it.

     rem, not px, because of text-only zoom. Someone who raises their
     browser's default font size gets larger text but, with px padding,
     containers that stay the same, so the text crowds and clips. WCAG
     1.4.4 asks for 200% text resize without loss of content, and fixed
     padding is the usual way that fails. */
  --space-2:  0.125rem;  /* 2 */
  --space-4:  0.25rem;   /* 4 */
  --space-6:  0.375rem;  /* 6 */
  --space-8:  0.5rem;    /* 8 */
  --space-10: 0.625rem;  /* 10 */
  --space-12: 0.75rem;   /* 12 */
  --space-14: 0.875rem;  /* 14 */
  --space-16: 1rem;      /* 16 */
  --space-20: 1.25rem;   /* 20 */
  --space-24: 1.5rem;    /* 24 */
  --space-32: 2rem;      /* 32 */
  --space-40: 2.5rem;    /* 40 */
  --space-48: 3rem;      /* 48 */
  --space-56: 3.5rem;    /* 56 */
  --space-64: 4rem;      /* 64 */

  /* Radius, strokes and touch targets stay in PIXELS, deliberately.
     A 1px hairline is meant to be a hairline at any text size. A 44px
     touch target is a physical measure, a finger does not grow when
     someone increases their font size. Radius is a visual property
     rather than a text-related one. */
  --radius-xs:    2px;   /* nested markers */
  --radius-sm:    4px;
  --radius-md:    6px;   /* segments, small controls */
  --radius-lg:    8px;   /* cards, buttons, fields */
  --radius-xl:    10px;  /* focus-ring wrapper = lg + offset */
  --radius-2xl:   12px;
  --radius-round: 28px;  /* the add button */
  --radius-pill:  999px;

  --stroke-hairline: 1px;    /* decorative edges */
  --stroke-medium:   1.5px;  /* bottle outlines */
  --stroke-thick:    2px;    /* icons, component boundaries */
  --stroke-focus:    3px;

  /* Motion. Every duration collapses under prefers-reduced-motion. */
  --motion-fast: 160ms;  /* focus, hover */
  --motion-base: 220ms;  /* swipe reveal, state change */
  --motion-slow: 320ms;  /* sheet rise and dismiss */
  --ease: cubic-bezier(0.2, 0, 0, 1);

  /* Opacity. These stopped being styling and became contrast decisions
     the moment they were measured. */
  --opacity-faded:    0.8;   /* out of stock. 0.6 gave 4.36:1 and 3.04:1, below AA */
  --opacity-disabled: 0.55;  /* 0.4 gave 3.20:1. WCAG exempts it; a label nobody can read still fails the user */
  --opacity-scrim:    0.45;  /* a token because binding a colour silently drops alpha */

  /* Layout. Physical and structural measures. Px on purpose, see above.
     Gutters and safe areas are in rem so they breathe with text zoom. */
  --touch-min:          44px;     /* physical: a finger does not scale with font size */
  --focus-offset:       2px;      /* + 3px stroke = ring extends 5px beyond a component */
  --gutter:             1rem;     /* 16 */
  --safe-top:           2.5rem;   /* 40 */
  --safe-bottom:        1.25rem;  /* 20 */
  --breakpoint-desktop: 900px;    /* the only breakpoint, one is enough when components are fluid */

  /* Content width. NOT a viewport width: the layout is fluid and nothing
     is ever pinned to a device size. The design was drawn at 402px (iPhone
     17) but that is a reference, not a constraint, so it is not a token.

     What does need a cap is line length. Left alone, body copy on a wide
     monitor runs to 120+ characters, which is hard to read regardless of
     font size. 68ch keeps prose in the comfortable 60–75 character range,
     and the list caps wider because rows are scanned, not read. */
  --measure-prose: 68ch;
  --measure-list:  920px;

  /* Type primitives */
  --font-family: "Geologica", system-ui, sans-serif;

  /* Intermediate axis values, not Figma's named instances. The difference
     is visible at display sizes and the variable axis makes it free. */
  --font-weight-420: 420;  /* captions, uppercase labels */
  --font-weight-460: 460;  /* body */
  --font-weight-524: 524;  /* headings, emphasis */
  --font-weight-620: 620;  /* subtitles */
  --font-weight-640: 640;  /* buttons */
  --font-weight-655: 655;  /* H1 only */

  /* SHRP has no Figma equivalent, a text style cannot hold an arbitrary
     axis, so this is one place CSS expresses more than the design file. */
  --axis-sharp: "SHRP" 100;  /* wordmark and H1 only */
  --axis-blunt: "SHRP" 0;    /* everything else */

  --tracking-tight:  -1.5%;  /* H1 */
  --tracking-snug:   -1%;    /* H2 */
  --tracking-slight: -0.5%;  /* H3 */
  --tracking-none:   0;
  --tracking-wide:   5%;     /* uppercase labels */

  /* ══ 2. SEMANTIC ════════════════════════════════════════ */

  --surface-header:   var(--colour-forest-900); /* white on this: 11.73:1 AAA */
  --surface-page:     var(--colour-bone);
  --surface-card:     var(--colour-porcelain);  /* 1.14:1 vs page. Needs border AND shadow */
  --surface-selected: var(--colour-forest-100); /* nav pill; action on this: 9.0:1 */

  --border-hairline:    var(--colour-hairline);   /* decorative only */
  --border-interactive: var(--colour-forest-900); /* 11.71:1, 1.4.11 needs 3:1 */

  --text-primary:   var(--colour-ink-900);   /* 16.29:1 on card, AAA */
  --text-secondary: var(--colour-ink-700);   /*  8.41:1 on card, AAA */
  --text-muted:     var(--colour-ink-500);   /*  5.37:1 on card, AA only, metadata */
  --text-on-brand:  var(--colour-porcelain); /* 11.73:1 on header */

  --action-primary:       var(--colour-forest-900);
  --action-primary-hover: var(--colour-forest-700);

  /* Indigo, not coral. Coral sits 1.66:1 from --state-out-ink and reads as a
     validation error on a form field. Indigo is the only hue in the palette
     carrying no status meaning: green is in stock AND action, amber is low,
     garnet is out. 13.58:1 card, 11.96:1 page, 10.51:1 selected. */
  --focus-ring:          var(--colour-indigo-700);
  /* No dark hue clears 3:1 on the forest header, so the ring inverts there.
     11.73:1, this is why it is a token and not a one-off. */
  --focus-ring-on-brand: var(--colour-porcelain);
  --accent-decorative:   var(--colour-coral-500); /* 3.35:1. DECORATIVE ONLY, never text */
  /* The filter count. It was borrowing --focus-ring, which was a token doing
     a job it was not named for; changing the ring to indigo would have
     silently recoloured it. Coral now has one honest use: 5.50:1 white on it,
     and it is the only thing on the strip that has to be noticed. */
  --marker-count:        var(--colour-coral-600);
  /* Shares indigo with the focus ring. A filled heart inside a row and an
     outline around a field are different shapes on different components; they
     have never been confusable. */
  --marker-favourite:    var(--colour-indigo-700);

  --state-good-fill: var(--colour-green-200);
  --state-good-ink:  var(--colour-green-700);  /* 6.82:1 on its fill, AAA */
  --state-low-fill:  var(--colour-amber-200);
  --state-low-ink:   var(--colour-amber-800);  /* 5.48:1 on its fill */
  --state-out-fill:  var(--colour-rose-200);
  --state-out-ink:   var(--colour-garnet-700); /* 6.57:1 on its fill, AAA */

  /* Elevation. Cards sit at 1.14:1 against the page, so shadow and border
     are both doing separation work, neither is decoration. */
  --elevation-card:
    0 1px 2px rgba(14, 35, 28, 0.08),
    0 4px 8px -2px rgba(14, 35, 28, 0.06);
  --elevation-raised:  0 6px 16px -4px rgba(14, 35, 28, 0.12);
  --elevation-overlay: 0 12px 32px -8px rgba(14, 35, 28, 0.18);

  /* ══ 3. TYPE SCALE, MOBILE ═════════════════════════════
     rem, so sizes respect the reader's own browser or OS text setting.
     Pixels ignore it, which is the difference between a design that
     scales and one that only survives page zoom. */

  --display-h1-size: 2rem;      --display-h1-line: 2.375rem;  /* 32 / 38 */
  --display-h2-size: 1.75rem;   --display-h2-line: 2.125rem;  /* 28 / 34 */
  --display-h3-size: 1.5rem;    --display-h3-line: 1.75rem;   /* 24 / 28 */
  --display-h4-size: 1.25rem;   --display-h4-line: 1.625rem;  /* 20 / 26 */
  --display-h5-size: 1.125rem;  --display-h5-line: 1.5rem;    /* 18 / 24 */

  --text-size-18: 1.125rem;  --text-line-28: 1.75rem;
  --text-size-16: 1rem;      --text-line-24: 1.5rem;
  --text-size-14: 0.875rem;  --text-line-20: 1.25rem;
  --text-size-12: 0.75rem;   --text-line-16: 1rem;
}

/* ══ TYPE SCALE, DESKTOP ═════════════════════════════════
   Body text scales too. A larger screen sits further away, roughly 35cm
   for a phone, 60cm for a monitor, so identical pixel sizes are
   angularly smaller on desktop. 16px is the mobile floor. */

@media (min-width: 900px) {
  :root {
    --display-h1-size: 3rem;     --display-h1-line: 3.625rem;  /* 48 / 58 */
    --display-h2-size: 2.5rem;   --display-h2-line: 3rem;      /* 40 / 48 */
    --display-h3-size: 2rem;     --display-h3-line: 2.375rem;  /* 32 / 38 */
    --display-h4-size: 1.75rem;  --display-h4-line: 2.125rem;  /* 28 / 34 */
    --display-h5-size: 1.5rem;   --display-h5-line: 1.75rem;   /* 24 / 28 */

    --text-size-18: 1.25rem;    --text-line-28: 1.875rem;  /* 20 / 30 */
    --text-size-16: 1.125rem;   --text-line-24: 1.6875rem; /* 18 / 27 */
    --text-size-14: 0.9375rem;  --text-line-20: 1.375rem;  /* 15 / 22 */
    --text-size-12: 0.8125rem;  --text-line-16: 1.125rem;  /* 13 / 18 */
  }
}

/* ══ TYPE STYLES ══════════════════════════════════════════
   The 18 Figma text styles as classes. Each bundles family, size, line
   height, weight and tracking, the same composite Figma builds. */

.t-h1 { font: var(--font-weight-655) var(--display-h1-size)/var(--display-h1-line) var(--font-family);
        letter-spacing: var(--tracking-tight);
        font-variation-settings: var(--axis-sharp); }   /* brand layer only */
.t-h2 { font: var(--font-weight-524) var(--display-h2-size)/var(--display-h2-line) var(--font-family);
        letter-spacing: var(--tracking-snug); }
.t-h3 { font: var(--font-weight-524) var(--display-h3-size)/var(--display-h3-line) var(--font-family);
        letter-spacing: var(--tracking-slight); }
.t-h4 { font: var(--font-weight-524) var(--display-h4-size)/var(--display-h4-line) var(--font-family); }
.t-h5 { font: var(--font-weight-524) var(--display-h5-size)/var(--display-h5-line) var(--font-family); }

.t-s1 { font: var(--font-weight-620) var(--text-size-18)/var(--text-line-28) var(--font-family); }
.t-s2 { font: var(--font-weight-620) var(--text-size-16)/var(--text-line-24) var(--font-family); }

.t-b1 { font: var(--font-weight-460) var(--text-size-16)/var(--text-line-24) var(--font-family); }
.t-b2 { font: var(--font-weight-524) var(--text-size-16)/var(--text-line-24) var(--font-family); }
.t-b3 { font: var(--font-weight-460) var(--text-size-14)/var(--text-line-20) var(--font-family); }
.t-b4 { font: var(--font-weight-524) var(--text-size-14)/var(--text-line-20) var(--font-family); }

.t-c1 { font: var(--font-weight-420) var(--text-size-12)/var(--text-line-16) var(--font-family); }
.t-c2 { font: var(--font-weight-524) var(--text-size-12)/var(--text-line-16) var(--font-family); }

.t-label { font: var(--font-weight-420) var(--text-size-12)/var(--text-line-16) var(--font-family);
           letter-spacing: var(--tracking-wide);
           text-transform: uppercase; }

.t-btn-lg { font: var(--font-weight-640) var(--text-size-16)/var(--text-line-20) var(--font-family); }
.t-btn-md { font: var(--font-weight-640) var(--text-size-14)/var(--text-line-16) var(--font-family); }
.t-btn-sm { font: var(--font-weight-640) var(--text-size-12)/var(--text-line-16) var(--font-family); }

/* Quantities and counts. Tabular figures stop a row reflowing between 9
   and 10, which matters in a list of 74 items. */
.t-num { font: var(--font-weight-460) var(--text-size-14)/var(--text-line-20) var(--font-family);
         font-variant-numeric: tabular-nums; }

/* ══ SHARED BEHAVIOUR ═════════════════════════════════════ */

/* Focus is persistent and belongs to keyboard and switch users. Pressed is
   momentary touch feedback. :focus-visible keeps them apart, so a touch
   user never gets a ring stuck on the last thing they tapped. */
:focus-visible {
  outline: var(--stroke-focus) solid var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* Safe areas: the token is a floor, env() takes over where the device
   reports a real inset. */
.safe-top    { padding-top:    max(var(--safe-top),    env(safe-area-inset-top)); }
.safe-bottom { padding-bottom: max(var(--safe-bottom), env(safe-area-inset-bottom)); }

@media (prefers-reduced-motion: reduce) {
  :root { --motion-fast: 1ms; --motion-base: 1ms; --motion-slow: 1ms; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    transition-duration: 1ms !important;
  }
}
