/**
 * Est Est Est Pizza — design tokens. ONE source of truth for all six apps.
 *
 * Why this file exists
 * -------------------
 * Before it, five apps each declared their own `:root` and had quietly
 * diverged into five different products:
 *
 *   --bg      #0c0a09 (site) · #0a0a0b (pos) · #14110e (admin) · #0b0b0c (driver)
 *   --accent  #e2542f (site) · #e4572e (admin) · #ff8a5c (driver, as --brand)
 *   --radius  14px (site/admin) · 12px (pos) · undefined (driver)
 *   naming    --bg-card vs --card · --ink-dim vs --muted · --good vs --ok vs --ready vs --paid
 *
 * Three different oranges is not a brand. A customer who orders on the site,
 * gets a text, and sees the driver's screen is looking at three shades of the
 * same intent — and nobody can say which one is "Est Est Est orange".
 *
 * How it is delivered
 * -------------------
 * Every app server serves THIS FILE at `/tokens.css`, read straight from
 * packages/ at request time. No copies, no build step, no sync script that can
 * drift. Edit here, reload any app, it is live everywhere.
 *
 * How to use it
 * -------------
 * Load it BEFORE the app's own stylesheet. An app may still override a token
 * locally, but doing so should be a deliberate, commented decision — not the
 * accident that produced the table above.
 */

/* ---------------------------------------------------------------------
   The display face, self-hosted.

   This used to come from fonts.googleapis.com, and only the customer-facing
   apps loaded it — the POS, kitchen and driver screens deliberately stayed on
   system fonts because a webfont that arrives late and reflows the screen
   mid-service is a real legibility risk on a cheap tablet during a rush.

   That reasoning was about the CDN, not about the typeface. Served from our
   own origin the font is a ~66 KB same-connection request that the browser
   caches immutably after the first load, so the failure mode it protected
   against no longer exists — and the whole estate can share one voice.

   `font-display: block` rather than `swap`: swap is what produces the flash
   of system text reflowing into the real face, which is precisely the thing
   the kitchen must never see. Block holds briefly for a font that is already
   local, so in practice the first paint is correct.

   Fraunces is a variable font (optical size 9–144, weight axis), so ONE file
   serves every weight we use; the full 100-900 axis is declared rather than
   shipping static cuts, because the admin already asks for 500 and clamping
   it to 600 would silently thicken that app's headings. Licensed SIL OFL 1.1 — see
   fonts/OFL.txt, which must travel with the files.
   --------------------------------------------------------------------- */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  src: url('/fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  src: url('/fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ---------------------------------------------------------- surfaces
     A warm near-black, not a neutral one. Brick, crust, wood and a 1973
     dining room are warm; the cool greys the POS and driver apps drifted
     into read as "generic SaaS dashboard" and fight the brand. */
  /* Six steps a half-tone apart (2026-09-19). The old page was #0c0a09,
     which is pitch black with a hint of brown: a section could not step
     forward without becoming a different colour, so every band was drawn
     with a border instead of light. The scale now has wine and charcoal
     in it -- page -> band -> chrome -> card -> hovered card -- and a
     seventh, deeper step for the hero's base and the footer. Every ink
     below is re-measured against the LIGHTEST of these (--bg-card-hi):
     see scripts/check-contrast.mjs. */
  --bg-deep:     #0e0a08;   /* under the hero film, the footer */
  --bg:          #130e0b;   /* page */
  --bg-band:     #18120e;   /* a section that steps forward from the page */
  --bg-raised:   #1e1712;   /* headers, footers, sticky chrome */
  --bg-card:     #241b16;   /* cards, sheets, panels */
  --bg-card-hi:  #2c221b;   /* hovered/selected card */

  --line:        #322721;   /* default border */
  --line-soft:   #261d18;   /* internal dividers */
  --line-strong: #52433a;   /* hover borders, emphasis */

  /* Warm gold: the second colour, for ORNAMENT only -- eyebrow rules, the
     hero pill, badges, chips, the quote bar, hairlines on cards. Never body
     text (it is 8.5:1 on the lightest card, so it could be; the rule is
     about restraint, not contrast). The two alphas are the card hairline
     and its hover. */
  --gold:        #e9b872;
  --gold-tint:   rgba(233,184,114,.10);
  --gold-edge:   rgba(233,184,114,.16);
  --gold-edge-hi:rgba(233,184,114,.30);
  --gold-glow:   rgba(233,184,114,.07);
  /* The oven red as TEXT (prices, the phone number): the accent itself is
     4.1:1 on a hovered card, one step too dark for 14px type; this is the
     same hue lifted to 4.86:1 there and 6.0:1 on the page. */
  --accent-text: #ee6540;

  /* ---------------------------------------------------------------- ink
     Four steps, warm-tinted to match the surfaces. --ink-faint is the
     lowest step that is still TEXT; anything dimmer is decoration.

     Measured, because the previous note here claimed --ink-faint "passes
     contrast on --bg-card" and it did not. At #7d7167 it was 3.82:1 there
     against the 4.5:1 WCAG AA needs for body text -- and worse on the
     lighter surfaces, not better, which is the trap: a value checked only
     against the darkest background looks safe and fails everywhere a card
     sits on top. On the ordering page that was 116 failing elements, 56 of
     them the item descriptions a customer reads to choose dinner.

     Every step below now clears 4.5:1 on the LIGHTEST surface it can land
     on (--bg-card-hi, #241f1a), which is the only version of this check
     that means anything:

       --ink        #f5efe7   14.9:1 on --bg-card-hi
       --ink-dim    #b3a79a    6.93
       --ink-faint  #91857b    4.55   <- the floor; do not darken it
       (anything below this is a border colour, not text) */
  --ink:       #f5efe7;
  --ink-dim:   #c4b6a7;
  --ink-faint: #9d9084;
  /* Near-black, not white. White on this orange is 3.79:1 -- a fail, and it
     fell on the one control that matters most, the "Order online" button.
     Darkening the orange until white passed would have moved the brand
     colour on every surface; keeping the orange exactly and flipping the
     text is 5.00:1, which is more headroom for less change. */
  --ink-onAccent: #14100d;

  /* -------------------------------------------------------------- brand
     ONE orange. Previously #e2542f / #e4572e / #ff8a5c across three apps.

     Held against the Google Stitch design (design/stitch-import/) and KEPT.
     Stitch's brand accent is terracotta #d2691e — the one colour it defined
     consistently, in 18 of 22 screens. It lost on three counts:

       1. #d2691e is plain CSS `chocolate`. A fifty-year-old New Haven
          institution should not have a named HTML keyword as its brand.
       2. It is browner and lower-chroma; against our warm near-black it
          reads muddy where #e2542f reads like the inside of the oven.
       3. Everything downstream is already tuned to #e2542f — --accent-hover,
          --focus-ring via --accent-2, the PWA manifest theme colour, the
          printed-ticket header. Shifting the hue to chase a generator
          default would mean re-tuning all of it for a change nobody asked
          for and no customer would read as an improvement.

     Rejected outright from Stitch: `primary #c8c6c5` (a grey — Material's
     auto-derived neutral, not a brand colour), `primary-container #f66018`
     and the #ea580c outlier (its second and third palettes), plus
     heritage-gold #d4af37 and heirloom-olive #4b5320, which are too dark to
     carry meaning on a dark surface. All five surface analyses agreed. */
  --accent:      #e2542f;
  --accent-hover:#ee6540;
  --accent-2:    #f0a04b;   /* warm secondary: prices, eyebrows, links */

  /* ------------------------------------------------------ status colours
     Semantic, shared, and named for MEANING not for the app that first
     needed them. The POS and driver apps had four different words for
     "this went well" (--good / --ok / --ready / --paid). */
  --ok:      #4ec97a;   /* success, ready, paid */
  --warn:    #f0a04b;   /* attention, cooking, ageing */
  --danger:  #ff5f52;   /* failure, late, destructive */
  --info:    #4a9eff;   /* new, incoming, neutral notice */
  --special: #b58cff;   /* out-for-delivery, "in flight" */
  --cash:    #ffc63d;   /* money owed to collect */

  /* ------------------------------------------------- status tints & edges
     A status colour at full strength is for TEXT, icons and bar fills. To
     tint the surface behind them you need the same hue at low alpha, and
     every app had been eyeballing that separately — grep found
     rgba(226,84,47,.18), rgba(226,84,47,.07), rgba(74,158,255,.06) and
     rgba(240,199,94,0.12) hardcoded across four stylesheets, i.e. four
     different opinions about how strong "a hint of the accent" is.

     Two steps per status: `-tint` fills, `-edge` outlines. Alpha rather
     than a baked hex so a tint sits correctly on --bg, --bg-card and
     --bg-card-hi alike instead of only matching the one it was mixed for.

     Deliberately NOT color-mix(): these run on cheap shop tablets and old
     Android webviews, and a token file is the wrong place to gamble on a
     colour function that silently drops the whole declaration if
     unsupported. Plain rgba() works everywhere. */
  --ok-tint:       rgba(78,201,122,.13);
  --ok-edge:       rgba(78,201,122,.34);
  --warn-tint:     rgba(240,160,75,.13);
  --warn-edge:     rgba(240,160,75,.34);
  --danger-tint:   rgba(255,95,82,.13);
  --danger-edge:   rgba(255,95,82,.34);
  --info-tint:     rgba(74,158,255,.13);
  --info-edge:     rgba(74,158,255,.34);
  --special-tint:  rgba(181,140,255,.13);
  --special-edge:  rgba(181,140,255,.34);
  --cash-tint:     rgba(255,198,61,.13);
  --cash-edge:     rgba(255,198,61,.34);
  --accent-tint:   rgba(226,84,47,.13);
  --accent-edge:   rgba(226,84,47,.34);

  /* The unfilled part of a progress bar, meter or capacity gauge. A neutral
     white wash rather than a surface colour, so one track token works on
     every surface instead of needing a variant per card shade. */
  --data-track: rgba(245,239,231,.08);

  /* Text sitting ON a solid status fill (a filled button, a solid badge) —
     different from -tint/-edge, which are for a status colour laid OVER a
     card. Not invented: --ok-ink and --danger-ink formalize what
     components.css's .ui-btn--go/--danger already hardcoded; --info-ink,
     --warn-ink and --special-ink formalize what apps/pos had independently
     landed on and repeated consistently 2-3x each (on-new badges, on-cooking
     buttons, the dispatch map's out-for-delivery pip) without ever naming it
     — exactly the kind of undocumented-but-real convention this file exists
     to catch. */
  /* Danger TEXT sitting on a danger-TINTED surface — a third job, distinct
     from both --danger (text on a plain card) and --danger-ink (text on a
     SOLID danger fill). #ff5f52 on a dark red tint is too close in hue to
     the surface behind it to stay comfortably legible, so both the POS and
     the driver app had independently arrived at this same soft pink: the
     driver named it --danger-text locally, the POS repeated the raw hex.
     Two apps converging on one value without ever agreeing to is the
     definition of a convention this file should own. */
  --danger-on-tint: #ffb0a4;

  --ok-ink:      #06180d;
  --warn-ink:    #241503;
  --danger-ink:  #2a0a08;
  --info-ink:    #041224;
  --special-ink: #150a24;

  /* --------------------------------------------------------- typography
     Two families. Fraunces is a variable serif with real character and is
     already loaded by the admin — it belongs on the CUSTOMER-facing site,
     which is the surface that has to feel like a fifty-year-old New Haven
     institution rather than a form.

     Operational screens (POS, kitchen, driver) deliberately stay on system
     fonts: they run on cheap tablets over shop wifi, and a web font that
     swaps mid-service is a legibility risk during a rush for no upside. */
  --font-body: ui-sans-serif, -apple-system, "Segoe UI", Inter, system-ui, sans-serif;
  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --font-mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", monospace;

  /* Type scale — 1.200 (minor third), rounded to whole/half pixels.
     Named by ROLE so a heading is not "the 28px one". */
  /* The low band steps by 1px, not by the 1.200 ratio. That is deliberate:
     below ~16px a minor third is far too coarse — the next step down from
     12px would be 10px, and the operational screens need 11px badge caps
     AND 12px captions AND 10px pill counters, three distinct jobs that a
     ratio-pure scale cannot express. A survey of every stylesheet found 45
     uses in the 9.5-11.5px range with no token to reach for, which is
     exactly why they were all hardcoded. Rounding those to a real step is
     the point; inventing a ratio they must obey is not. */
  --text-3xs:  10px;   /* pill counters, micro labels */
  --text-2xs:  11px;   /* badge caps, table meta */
  --text-xs:   12px;
  --text-sm:   13.5px;
  --text-ui:   14px;   /* dense UI body — the single most-used size in the
                          estate (28 hardcoded uses). Sits between sm and
                          base on purpose: it is what an operational row
                          wants when 15px wraps and 13.5px strains. */
  --text-base: 15px;
  --text-md:   16px;
  --text-lead: 17px;   /* card titles, lead paragraphs -- 12 hardcoded uses
                          and the only gap left in the ramp */
  --text-lg:   19px;
  --text-xl:   23px;
  --text-2xl:  28px;
  --text-3xl:  34px;
  --text-4xl:  clamp(30px, 5vw, 48px);
  --text-hero: clamp(38px, 7.4vw, 78px);

  /* Editorial display sizes — the marketing surface only.
     --text-3xl (34px, a fixed step) is the right size for a card title or an
     operational panel heading, and the wrong size for a section heading on a
     page whose whole job is to feel like a fifty-year-old institution rather
     than a dashboard. Every storefront section heading used --text-3xl, which
     is why the page read as competent-but-flat: nothing on it was ever allowed
     to be BIG. These two fluid steps sit above the fixed ramp and are reserved
     for section headings (--text-display) and the one or two moments a page is
     allowed to shout (--text-display-lg, used by pull quotes and the numerals
     in the commitment list).

     Fluid rather than fixed because a 56px heading that is right on a desktop
     is a four-line wrap on a phone. */
  --text-display:    clamp(28px, 4.2vw, 54px);
  --text-display-lg: clamp(38px, 6vw, 76px);

  --leading-tight: 1.15;
  --leading-snug:  1.35;
  --leading-body:  1.62;

  --tracking-tight: -.022em;
  --tracking-wide:  .08em;
  --tracking-caps:  .12em;

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-semi:   600;
  --weight-bold:   700;

  /* ------------------------------------------------------------- space
     Named by VALUE, not by index. `--space-3` required a lookup table to
     know it meant 12px, which is exactly the friction that kept 484 padding
     declarations hardcoded rather than reaching for a token.

     The band below 24px steps by 2px, not 4px. That is not sloppiness — it
     is what a survey of every stylesheet found: 2, 6, 10, 14, 18 and 22px
     together account for 267 declarations, more than every 4px step
     combined. Dense operational rows genuinely need 10px where 8 crowds and
     12 wastes a line, and a scale that cannot express that is a scale
     everyone quietly ignores. Above 24px the steps open out, because at
     section scale a 2px difference is not a decision anyone is making. */
  --space-2:  2px;
  --space-4:  4px;
  --space-6:  6px;
  --space-8:  8px;
  --space-10: 10px;
  --space-12: 12px;
  --space-14: 14px;
  --space-16: 16px;
  --space-18: 18px;
  --space-20: 20px;
  --space-22: 22px;
  --space-24: 24px;
  --space-28: 28px;
  --space-32: 32px;
  --space-40: 40px;
  --space-56: 56px;
  --space-80: 80px;

  /* Section rhythm — fluid, so a phone is not scrolling through desktop gaps.

     The note that used to live here said --section-y was referenced by
     NOTHING, that storefront sections carried their own hardcoded padding,
     and that "the fix is to make the storefront actually consume it, not to
     tune a token nobody reads." The storefront now consumes it, so the token
     has been tuned — that was the correct order of operations.

     Raised from 56/8vw/96 to 72/9vw/128. Cramped vertical rhythm was the
     single biggest reason the marketing pages read as ordinary: at 96px max,
     a 34px heading and a photo grid sat close enough together that the page
     never breathed. Space is the cheapest luxury signal there is, and the one
     the old layout spent least. --section-y-lg is for the few sections that
     carry a single idea (the story, the full-bleed break) and want room
     around it. */
  --section-y:    clamp(72px, 9vw, 128px);
  --section-y-lg: clamp(96px, 12vw, 176px);
  --gutter:       clamp(16px, 4vw, 40px);

  /* ------------------------------------------------------------ radius */
  --radius-sm:  9px;
  --radius:     14px;
  --radius-lg:  18px;
  --radius-pill: 999px;

  /* ------------------------------------------------------------ motion
     One easing curve everywhere. Durations short enough that the UI never
     feels like it is waiting for an animation to finish. */
  --ease:      cubic-bezier(.22,.61,.36,1);
  --dur-fast:  .16s;
  --dur:       .22s;
  --dur-slow:  .32s;

  /* ------------------------------------------------------------- depth */
  --shadow-card:  0 1px 2px rgba(0,0,0,.35);
  /* Something the page opens ON TOP of itself — a sheet, a dialog. Two apps
     were already hand-writing this token's exact value and three more had
     improvised their own; --shadow-pop existed the whole time and was used
     twice. */
  --shadow-pop:   0 30px 80px rgba(0,0,0,.6);
  /* Something that floats briefly over the page — a toast. A separate role,
     not a smaller --shadow-pop: a 30/80 blur under a 40px-tall toast reads
     as a smudge rather than as lift. The POS and driver apps had landed on
     0 12px 40px / 0 14px 44px independently, which is one intent and two
     spellings. */
  --shadow-float: 0 14px 44px rgba(0,0,0,.6);

  /* -------------------------------------------------------------- focus
     A REAL focus ring. Several surfaces had none at all, which means the
     site is unusable by keyboard and fails WCAG 2.4.7 — a genuine
     accessibility defect, not a polish item. Defined once, applied below. */
  --focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent-2);

  /* ------------------------------------------------------------ layout */
  --maxw: 1240px;
  /* Measure. Long-form paragraphs set to the full 1240px container run to
     ~150 characters on a desktop, roughly twice the readable maximum, and the
     /pages/* copy did exactly that. Cap the TEXT, not the container. */
  --maxw-text: 68ch;

  /* -------------------------------------------------- photographic overlay
     Sections that lay type over a photograph need a scrim, and every one of
     them had been mixing its own. Two roles: `-veil` is the flat wash that
     buys legibility over a busy image, `-fade` is the vertical gradient that
     lets a photo dissolve into the page background instead of ending on a
     hard horizontal edge. Warm-tinted to match --bg, because a neutral black
     over this (warm, wood-and-brick) photography reads as grey haze. */
  /* Expressed as a two-stop gradient rather than the plain rgba() colour it
     looks like it should be, and this is load-bearing: CSS only allows a
     bare COLOUR as the final value of the `background` shorthand, where it
     becomes background-color behind every layer. Written as
     `background: var(--photo-veil), var(--photo-fade)` a plain colour is an
     invalid image layer, which makes the WHOLE shorthand invalid — the
     browser silently drops the entire declaration and renders no scrim at
     all. That shipped once here: the full-bleed section rendered with no
     darkening whatever and its caption was unreadable over the photograph.
     A gradient between two identical stops is a valid image layer and
     paints the identical flat wash. */
  --photo-veil: linear-gradient(rgba(19,14,11,.58), rgba(19,14,11,.58));
  --photo-fade: linear-gradient(180deg,
                  rgba(12,10,9,.92) 0%,
                  rgba(12,10,9,.30) 26%,
                  rgba(12,10,9,.42) 66%,
                  rgba(12,10,9,.95) 100%);

  /* Kept so existing rules referencing the old names keep working while
     apps migrate one at a time. Remove once nothing references them. */
  --good: var(--ok);
}

/* ---------------------------------------------------------------------
   Base ground.

   Everything above only DEFINES the palette — something still has to paint
   with it. Every app stylesheet does declare `html, body { background:
   var(--bg); color: var(--ink) }` for itself, so these three lines change
   nothing for any screen that loads one. They exist for the page that
   doesn't.

   Two already didn't. apps/customer/public/index.html and
   apps/admin/public/style-guide.html both load tokens.css + components.css
   but not their app's styles.css, so they fell through to UA defaults and
   rendered as WHITE pages with an 8px body margin and Times body text —
   inside a product whose entire identity is a warm near-black. A dark theme
   that only exists in a file a page might forget to load is not a theme, it
   is a convention, and conventions are exactly what this file was created to
   stop relying on.

   Plain `html, body` rather than a `:where()` wrapper on purpose: an app
   sheet loading later must be able to override it with equal specificity
   (they all do), while this still beats the user-agent default.
   --------------------------------------------------------------------- */
html, body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
}
body {
  font-family: var(--font-body);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------------
   Focus visibility — applies to every app that loads this file.

   `:focus-visible` rather than `:focus` so a mouse click does not leave a
   ring behind, but every keyboard user gets one. This single block closes
   the largest accessibility gap in the current build.
   --------------------------------------------------------------------- */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}

/* Respect the OS setting. Someone who gets motion sickness from a
   transition has told their computer so; honour it rather than animating
   anyway. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
