/* ═══════════════════════════════════════════════════════════════════════════
   VIVERE COLORADO — shared stylesheet
   215 Ute Street, Delta CO · "to live Colorado"

   MOBILE-FIRST. Base styles are the phone. Larger screens arrive via
   min-width only. The previous build was the inverse — 7 max-width queries
   and 0 min-width — so every phone parsed and then overrode a desktop
   layout it never used.

   ONE stylesheet, not three. The rebuild plan specced base/components/motion
   as separate files; they are folded into one long-cached request here
   because three files is three round trips on rural mobile data to save
   nothing — @layer already gives the separation the split was for.

   Layer order is the cascade contract. Anything later wins regardless of
   specificity, so components never has to out-specify base, and the five
   pages can't start a specificity war with each other.
   ═══════════════════════════════════════════════════════════════════════════ */

@layer tokens, base, components, motion, utilities;

/* ───────────────────────────────────────────────────────────── TOKENS ─── */
@layer tokens {
  :root {
    --navy:       #183C6B;   /* Delta Roadhouse navy - the campus container */
    /* Palette derived from the emblem: ember flame, brass inner tongue,
       charcoal ground, bone type. Never write a raw hex below this block. */
    --ember:      #A34F22;   /* Delta Roadhouse 'Gray Brown' - art-verified #A34F21 */
    --ember-dk:   oklch(from var(--ember) calc(l - .12) c h);
    --ember-lt:   oklch(from var(--ember) calc(l + .14) calc(c - .04) h);
    /* --ember reaches only 4.43:1 against white, which fails WCAG AA for
       normal-size text. Any SOLID FILL carrying white text uses this
       darker step. Use --ember for accents, borders and glows only.
       (Re-measured 2026-08-20 after --ember was corrected to the art
       value #C9531F; it was 3.80:1 at the old #E4531F, so this step is
       still required, just less so.) */
    --ember-fill: #A34F22;   /* white on rust = 5.68:1, passes AA normal.
       The old darker step existed because #C9531F only reached 4.43:1;
       the Roadhouse rust is darker and no longer needs it. */
    --ember-fill-hover: oklch(from var(--ember-fill) calc(l - .07) c h);

    --brass:      #B57E28;   /* Delta Roadhouse 'Gray Orange' - art-verified #B57F29 */
    --brass-lt:   oklch(from var(--brass) calc(l + .16) c h);
    --brass-dk:   oklch(from var(--brass) calc(l - .18) c h);

    --char:       #14335A;   /* campus navy, mid - panels */
    --char-2:     oklch(from var(--char) calc(l + .05) c h);
    --ink:        #0C1F39;   /* campus navy, deep - the ground */
    --bone:       #E5D6BD;   /* Delta Roadhouse 'Light Orange' - art-verified #E6D6BD */
    --cream:      #EFE4D0;
    --paper:      #F7F1E6;
    --muted:      oklch(48% 0.02 60);
    --rule:       oklch(86% 0.02 78);
    --ok:         oklch(58% 0.13 150);

    --f-display: 'Anton', Impact, 'Haettenschweiler', sans-serif;
    --f-body:    'Barlow', system-ui, -apple-system, 'Segoe UI', sans-serif;

    /* Fluid scale. Lower bounds are the phone value — this is where
       mobile-first actually pays: no query needed to size type.

       RESCALED 2026-08-17. The first pass topped out at a 112px h1, a
       23px lede and 23px menu-item names — measured, not estimated. That
       reads as large-print: it fills the screen without conveying
       importance, because when everything is big, nothing is emphasised.
       The ratios between steps are what create hierarchy, not the
       absolute sizes, so the whole scale came down and the RATIOS were
       kept. Display steps fell hardest (a 112px headline is a poster,
       not a page); body text barely moved, since that was already in a
       comfortable range and is what people actually read. */
    /* Deliberately NOT reduced as hard as the display steps. The
       complaint was oversized headings, not undersized captions — and
       this step carries menu descriptions and card body copy, which are
       read, not scanned. Shrinking it in sympathy with the h1 would
       trade one readability problem for another. */
    --step--1: clamp(.85rem, .82rem + .14vw, .91rem);
    --step-0:  clamp(.97rem, .94rem + .17vw, 1.04rem);
    --step-1:  clamp(1.1rem, 1.03rem + .34vw, 1.28rem);
    --step-2:  clamp(1.5rem, 1.28rem + .95vw, 2rem);
    --step-3:  clamp(2rem, 1.55rem + 2.1vw, 3.1rem);
    --step-4:  clamp(2.35rem, 1.75rem + 3.6vw, 4.25rem);

    /* ── Spacing scale ───────────────────────────────────────────────
       Audited 2026-08-17 and rebuilt. The page had SIX different grid
       gaps in play — .7 / .9 / 1.1 / 1.3 / 1.4rem plus the button row —
       chosen ad hoc as each component was written. Nothing was
       individually wrong; collectively there was no system, which is
       what "the spacing needs fixed" actually looks like. These six
       steps now cover every gap and stack on the page, so two things
       that should feel related are spaced identically rather than
       coincidentally.  */
    --sp-2xs: .4rem;
    --sp-xs:  .65rem;
    --sp-sm:  .9rem;
    --sp-md:  1.25rem;
    --sp-lg:  1.9rem;
    --sp-xl:  2.75rem;

    /* Section rhythm. Was clamping to 7.5rem — 98px of padding top AND
       bottom, so ~196px of dead air between every section on desktop.
       That reads as a stretched page rather than a generous one, and it
       was a large part of why the site felt like it was scrolling
       forever. ~76px a side is the corrected value. */
    /* Retuned 2026-09-06. The old cap of 5.5rem put 88px above AND below
       every section — 176px of empty ground between one section's last
       line and the next section's first, measured at 1440. With sections
       painted on a single continuous field and nothing marking the joins,
       that read as the page having stopped rather than as breathing room.
       The phone end of the scale is untouched; only the desktop cap moves,
       and the section seams below now give the remaining space a job. */
    --sp-fluid: clamp(2.75rem, 2.05rem + 2.2vw, 4.25rem);
    --gutter:   1.25rem;
    --measure:  64ch;

    --r: 14px; --r-lg: 22px; --r-pill: 999px;
    --sh-1: 0 1px 2px oklch(17% .014 55/.07), 0 4px 12px oklch(17% .014 55/.07);
    --sh-2: 0 2px 6px oklch(17% .014 55/.09), 0 12px 30px oklch(17% .014 55/.11);
    --sh-3: 0 4px 14px oklch(17% .014 55/.11), 0 26px 60px oklch(17% .014 55/.16);
    /* Warm, ember-tinted shadow ramp — for anything floating on the dark
       canvas (.home). A neutral grey shadow under a light card on a warm
       dark ground reads as pasted-on; a shadow tinted toward the ground's
       own hue reads as the card actually sitting in the scene. */
    --sh-warm-1: 0 2px 4px oklch(21% .05 38/.28), 0 6px 16px oklch(21% .05 38/.22);
    --sh-warm-2: 0 6px 14px oklch(21% .05 38/.32), 0 18px 44px oklch(21% .05 38/.28);
    --sh-warm-3: 0 10px 24px oklch(21% .05 38/.36), 0 34px 80px oklch(21% .05 38/.32);

    /* Published by JS from the nav's measured height, for scroll offsets
       ONLY. Nothing that affects the nav's own box may read it — see the
       growth-loop note in components. */
    --nav-h: 60px;
  }
}

/* ─────────────────────────────────────────────────────────────── BASE ─── */
@layer base {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; padding: 0; }

  html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    scroll-padding-top: calc(var(--nav-h) + 12px);
  }

  /* clip, NOT hidden. `hidden` makes <body> a scroll container, which
     silently breaks position:sticky on the nav. `clip` contains horizontal
     overflow without creating one. */
  /* 1.55, not 1.62 — the old value was tuned for a smaller body size.
     Stacked on top of the (previously oversized) type it was what made
     the page feel double-spaced as well as large-print. */
  body {
    font-family: var(--f-body);
    font-size: var(--step-0);
    line-height: 1.55;
    color: var(--ink);
    background: var(--cream);
    overflow-x: clip;
  }

  h1, h2, h3, h4 {
    font-family: var(--f-display);
    font-weight: 400;
    line-height: .95;
    letter-spacing: .01em;
    text-transform: uppercase;
    text-wrap: balance;
  }
  p { text-wrap: pretty; }
  a { color: var(--ember-dk); }
  img, svg, video { display: block; max-width: 100%; }
  button, input, select, textarea { font: inherit; }
  button { cursor: pointer; }

  :focus-visible { outline: 3px solid var(--ember); outline-offset: 3px; border-radius: 4px; }

  /* The viewport is viewport-fit=cover, so the page paints under the display
     cutout. Backgrounds bleed there; content must not. Padding (not margin)
     keeps section backgrounds full-bleed while holding text clear in
     landscape. env() resolves to 0 on every device without a cutout. */
  .wrap {
    width: min(1120px, 100% - (var(--gutter) * 2));
    margin-inline: auto;
    padding-inline: env(safe-area-inset-left) env(safe-area-inset-right);
  }
  .wrap-narrow { width: min(760px, 100% - (var(--gutter) * 2)); }

  .sr {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .skip {
    position: absolute; left: -999px; top: 0; z-index: 999;
    background: var(--ember-fill); color: #fff; padding: .7em 1.2em;
  }
  .skip:focus { left: 0; }

  /* ── The dark canvas (homepage only) ────────────────────────────────
     The first build alternated opaque paper/cream/bone/char section
     backgrounds down the page — every scroll boundary was a flat ruled
     line where one solid colour stopped and another started. body.home
     replaces that with ONE continuous warm-dark ground for the whole
     page; sections stop carrying their own opaque fills (see .sched,
     .campus below) and instead let content float on this ground as
     paper/glass cards. book/index.html, privacy.html and 404.html don't
     carry this class and keep the light utility-page treatment — this
     is specifically the "first contact" page's atmosphere, not a
     site-wide repaint. */
  body.home {
    background: var(--char);
    color: oklch(92% .018 78 / .88);
  }

  /* ── Ambient layer ───────────────────────────────────────────────────
     Three large, heavily blurred colour fields drifting on independent,
     very slow timers (55-80s). This is what makes the dark canvas read
     as one continuous atmosphere rather than a flat fill — it is NOT
     the "nothing loops but the status pulse" rule being broken (see the
     motion layer): that rule is about foreground UI competing for
     attention. This sits behind every card, is never sharp, and moves
     too slowly to consciously perceive as animation — only as the
     scene feeling alive. Fixed, so it doesn't repaint or scroll-jank;
     one shared instance for the whole page rather than per-section. */
  .ambient {
    position: fixed; inset: 0; z-index: -1;
    overflow: hidden; pointer-events: none;
  }
  .ambient .orb {
    position: absolute; border-radius: 50%; filter: blur(90px);
    opacity: .55; will-change: transform;
  }
  .ambient .orb-a {
    width: 62vw; height: 62vw; top: -22vw; left: -14vw;
    background: radial-gradient(circle, oklch(from var(--ember) l c h / .5), transparent 68%);
    animation: drift-a 62s ease-in-out infinite alternate;
  }
  .ambient .orb-b {
    width: 54vw; height: 54vw; bottom: -20vw; right: -16vw;
    background: radial-gradient(circle, oklch(from var(--brass) l c h / .4), transparent 70%);
    animation: drift-b 78s ease-in-out infinite alternate;
  }
  .ambient .orb-c {
    width: 40vw; height: 40vw; top: 38%; left: 50%;
    background: radial-gradient(circle, oklch(from var(--ember-fill) l c h / .32), transparent 72%);
    animation: drift-c 70s ease-in-out infinite alternate;
  }
  @keyframes drift-a { from { transform: translate(0, 0); } to { transform: translate(6vw, 8vw); } }
  @keyframes drift-b { from { transform: translate(0, 0); } to { transform: translate(-7vw, -6vw); } }
  @keyframes drift-c { from { transform: translate(-50%, 0); } to { transform: translate(-46%, -10vh); } }

  /* ── Background layer 2: ember field ────────────────────────────────
     A slow upward drift of small warm points — embers off a griddle.
     It's the subject rendered as atmosphere rather than decoration,
     which is the difference between an effect that belongs and one
     that's just switched on.

     Built as two repeating radial-gradient sheets rather than DOM nodes:
     ~30 points cost two painted layers instead of 30 elements, and the
     whole thing animates on `transform` alone (compositor-only, no
     layout, no paint per frame). Each sheet is exactly one tile tall
     and translates by exactly its own tile height, so the loop is
     seamless with no visible reset. */
  .ambient .embers {
    position: absolute; inset: -10% -5% 0;
    opacity: .5; will-change: transform;
    background-repeat: repeat;
  }
  .ambient .embers-near {
    background-image:
      radial-gradient(1.6px 1.6px at 12% 18%, oklch(75% .17 45 / .85), transparent 60%),
      radial-gradient(1.3px 1.3px at 68% 42%, oklch(80% .14 70 / .7), transparent 60%),
      radial-gradient(1.9px 1.9px at 34% 72%, oklch(72% .19 40 / .8), transparent 60%),
      radial-gradient(1.2px 1.2px at 87% 88%, oklch(78% .12 78 / .6), transparent 60%),
      radial-gradient(1.5px 1.5px at 52% 6%,  oklch(74% .18 44 / .75), transparent 60%);
    background-size: 420px 420px;
    animation: ember-rise 34s linear infinite;
  }
  /* Farther layer: smaller, dimmer, slower — parallax depth, so the
     field reads as a volume of air rather than a flat texture. */
  .ambient .embers-far {
    background-image:
      radial-gradient(1.1px 1.1px at 26% 30%, oklch(70% .15 50 / .5), transparent 60%),
      radial-gradient(.9px .9px  at 78% 64%, oklch(74% .12 76 / .42), transparent 60%),
      radial-gradient(1.2px 1.2px at 44% 90%, oklch(68% .16 42 / .48), transparent 60%);
    background-size: 300px 300px;
    opacity: .34;
    animation: ember-rise-far 58s linear infinite;
  }
  @keyframes ember-rise     { to { transform: translate3d(14px, -420px, 0); } }
  @keyframes ember-rise-far { to { transform: translate3d(-10px, -300px, 0); } }

  /* ── Background layer 3: vignette ───────────────────────────────────
     Darkens the far edges so the eye settles toward the middle where the
     content is. Static — a moving vignette would fight the orbs.
     `screen`-free, pure multiply-ish darkening via a transparent-centre
     radial, so it never lightens anything. */
  .ambient .vignette {
    position: absolute; inset: 0;
    background: radial-gradient(ellipse 78% 64% at 50% 42%,
      transparent 0%, transparent 52%, oklch(12% .01 55 / .55) 100%);
  }

  /* ── Background layer 4: grain ──────────────────────────────────────
     The page already had .grain, but only on two sections — so the
     texture stopped and started mid-scroll. This is one fixed sheet over
     the whole canvas instead. Deliberately NOT animated: moving grain
     is film, and at this size it reads as noise on a broken screen. */
  .ambient .grain-layer {
    position: absolute; inset: 0; opacity: .055; mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='4'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  }

  /* ── Scroll reactivity ──────────────────────────────────────────────
     The whole field drifts and warms slightly as you move down the page,
     so the background is a participant in the scroll rather than a
     static wallpaper behind it. Scroll-driven: no listener, no JS, and
     absent entirely where unsupported. */
  @supports (animation-timeline: scroll()) {
    .ambient {
      animation: ambient-scroll linear;
      animation-timeline: scroll(root);
    }
    @keyframes ambient-scroll {
      from { transform: translateY(0)     scale(1);    filter: hue-rotate(0deg)  saturate(1); }
      to   { transform: translateY(-40px) scale(1.08); filter: hue-rotate(-12deg) saturate(1.25); }
    }
  }
}

/* ───────────────────────────────────────────────────────── COMPONENTS ─── */
@layer components {

  /* ── Nav ─────────────────────────────────────────────────────────── */
  .nav {
    position: sticky; top: 0; z-index: 60;
    background: oklch(from var(--char) l c h / .94);
    backdrop-filter: blur(14px) saturate(1.3);
    border-bottom: 3px solid var(--ember);
  }
  @supports not (backdrop-filter: blur(1px)) { .nav { background: var(--char); } }

  /* Height is a LITERAL, never var(--nav-h). --nav-h is measured FROM this
     element, so consuming it here creates a ResizeObserver feedback loop
     that grows the nav by its border width every tick. */
  .nav-in {
    display: flex; align-items: center; justify-content: space-between;
    gap: .5rem; height: 60px;
  }
  .brand { display: flex; align-items: center; text-decoration: none; }
  /* Height-driven so the 60px row stays the authority and the image can
     never push the nav taller. Intrinsic art is 1368x548. */
  .brand img { height: 21px; width: auto; }

  /* Type lockup - stands in for a mark that does not exist yet.
     Deliberate: a wordmark set well in the campus frame reads as intentional;
     a rushed logo reads as amateur and then has to be unpicked. */
  .brand .wm { display: flex; flex-direction: column; line-height: 1; }
  .brand .wm b {
    font-family: var(--f-display); font-weight: 400;
    font-size: 1.05rem; letter-spacing: .015em;
    color: var(--bone); text-transform: uppercase;
  }
  .brand .wm i {
    font-style: normal; font-size: .5rem; letter-spacing: .3em;
    text-transform: uppercase; color: var(--brass); margin-top: .22rem;
  }
  @media (min-width: 48rem) {
    .brand .wm b { font-size: 1.3rem; }
    .brand .wm i { font-size: .56rem; }
  }

  .nav-links { display: none; }
  .nav-links a {
    color: oklch(92% .018 78/.86); text-decoration: none;
    font-weight: 600; font-size: var(--step--1);
    padding: .75rem; border-radius: var(--r-pill); text-align: center;
    transition: background .2s, color .2s;
  }
  .nav-links a:hover, .nav-links a[aria-current="page"] {
    background: oklch(100% 0 0/.12); color: #fff;
  }
  /* Mobile drawer is the BASE state — no query needed to get it. */
  .nav-links.open {
    display: flex; flex-direction: column; align-items: stretch; gap: .2rem;
    position: absolute; inset: 100% 0 auto 0;
    background: oklch(from var(--ink) l c h / .98);
    border-bottom: 3px solid var(--ember);
    padding: .7rem calc(var(--gutter) + env(safe-area-inset-right)) 1.1rem
                   calc(var(--gutter) + env(safe-area-inset-left));
  }

  /* 44px minimum. This is the only route to the menu on a phone. */
  .burger {
    display: grid; place-items: center;
    min-width: 44px; min-height: 44px;
    background: none; border: 0; color: #fff;
    font-size: 1.5rem; line-height: 1; border-radius: var(--r-pill);
  }

  .order-btn {
    background: var(--ember-fill); color: #fff; border: 0;
    border-radius: var(--r-pill); padding: .55rem .9rem;
    font-weight: 700; font-size: var(--step--1);
    display: flex; align-items: center; gap: .5rem;
    transition: background .2s, transform .15s;
  }
  .order-btn:hover { background: var(--ember-fill-hover); }
  .cart-count {
    background: #fff; color: var(--ember-dk); border-radius: var(--r-pill);
    min-width: 1.35em; height: 1.35em; display: grid; place-items: center;
    font-size: .76rem; font-weight: 700; font-variant-numeric: tabular-nums;
  }
  /* Hide the badge entirely when the cart is empty. */
  .order-btn:has(.cart-count[data-n="0"]) .cart-count { display: none; }

  @media (min-width: 56rem) {
    .nav-in { gap: 1rem; }
    .brand img { height: 26px; }
    .burger { display: none; }
    .nav-links { display: flex; flex-direction: row; align-items: center; gap: .15rem; }
    .nav-links a { padding: .45rem .8rem; }
    .order-btn { padding: .55rem 1.15rem; }
  }

  /* ── Status rail — the first thing on the page ───────────────────── */
  .status {
    background: var(--ink); color: var(--bone);
    font-size: var(--step--1);
    border-bottom: 1px solid oklch(100% 0 0/.1);
  }
  .status-in {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: .5rem 1.4rem; padding: .6rem 0;
  }
  .pill { display: inline-flex; align-items: center; gap: .45rem; font-weight: 600; }
  .led {
    width: 8px; height: 8px; border-radius: 50%; background: var(--ok);
    box-shadow: 0 0 0 0 oklch(58% .13 150/.6);
    animation: ping 2.4s ease-out infinite;
  }
  .led.off { background: var(--muted); animation: none; box-shadow: none; }
  @keyframes ping {
    0%        { box-shadow: 0 0 0 0 oklch(58% .13 150/.55); }
    70%, 100% { box-shadow: 0 0 0 9px oklch(58% .13 150/0); }
  }
  .status .left { color: var(--brass-lt); font-variant-numeric: tabular-nums; }
  .status .left b { font-family: var(--f-display); font-size: 1.15rem; color: var(--ember-lt); }
  @media (min-width: 40rem) { .status .left { margin-left: auto; } }

  /* ── Hero ────────────────────────────────────────────────────────── */
  /* No base fill — body.home already provides it, and this section sits
     first, exactly where the ambient orbs are largest. An opaque rect
     here would blot out the one place the effect is most visible. */
  .hero {
    position: relative; overflow: hidden;
    color: var(--bone);
    padding: calc(var(--sp-fluid) * 1.25) 0 calc(var(--sp-fluid) * 1.1);
  }
  .hero::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background:
      radial-gradient(ellipse 70% 55% at 50% -10%, oklch(from var(--ember) l c h / .34), transparent 70%),
      repeating-linear-gradient(114deg, oklch(100% 0 0/.03) 0 2px, transparent 2px 17px);
  }
  /* Emblem watermark. Decorative only — the wordmark is already in the nav,
     so this carries no information and stays out of the accessibility tree.
     Brought inboard and up in opacity from .07: at 420px/-14% offset it
     was mostly off-canvas, so the right third of the hero read as empty
     space rather than as composition. Now it anchors that column. */
  .hero-mark {
    position: absolute; right: 2%; top: 50%; translate: 0 -50%;
    width: min(52vw, 340px); opacity: .1; pointer-events: none;
    animation: mark-breathe 26s ease-in-out infinite alternate;
  }
  @media (min-width: 60rem) { .hero-mark { right: 6%; width: 400px; opacity: .13; } }
  @keyframes mark-breathe {
    from { transform: scale(1) rotate(0deg); }
    to   { transform: scale(1.06) rotate(3deg); }
  }
  .hero-in { position: relative; z-index: 1; max-width: 46rem; }
  .eyebrow {
    display: inline-flex; align-items: center; gap: .55rem;
    font-size: .72rem; font-weight: 700; letter-spacing: .2em;
    text-transform: uppercase; color: var(--brass-lt); margin-bottom: 1.1rem;
  }
  .eyebrow::before { content: ""; width: 28px; height: 2px; background: var(--ember); }
  /* 18ch, not 14ch. At the old 112px size, 14ch forced this headline onto
     three lines with an orphaned "&". At the corrected size 18ch lets it
     break in two, which is both calmer and faster to read. */
  .hero h1 { font-size: var(--step-4); color: #fff; margin-bottom: .3em; max-width: 18ch; }
  .hero h1 em { font-style: normal; color: var(--ember); }
  .hero .lede {
    font-size: var(--step-1); max-width: 44ch;
    color: oklch(92% .018 78/.85); margin-bottom: 2em;
  }
  .hero-foot {
    margin-top: 2.8rem; padding-top: 1.5rem;
    border-top: 1px solid oklch(92% .018 78/.18);
    font-size: var(--step--1); color: oklch(92% .018 78/.72);
  }
  .hero-foot b { color: #fff; }

  /* ── Buttons ─────────────────────────────────────────────────────── */
  .btns { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
  .btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
    padding: .9em 1.7em; border-radius: var(--r-pill);
    text-decoration: none; font-weight: 700; font-size: var(--step--1);
    border: 0; min-height: 44px;
    transition: transform .15s, box-shadow .15s, background .2s;
  }
  .btn-p { background: var(--ember-fill); color: #fff; }
  .btn-p:hover { background: var(--ember-fill-hover); }
  .btn-s { background: oklch(100% 0 0/.07); color: #fff; border: 1.5px solid oklch(92% .018 78/.4); }
  .btn-s:hover { background: oklch(100% 0 0/.14); }
  .btn:hover { transform: translateY(-2px); box-shadow: var(--sh-3); }

  /* ── Sections ────────────────────────────────────────────────────── */
  section { scroll-margin-top: calc(var(--nav-h) + 8px); }
  .sec { padding: var(--sp-fluid) 0; }
  .sec-h { font-size: var(--step-2); margin-bottom: var(--sp-xs); }
  .sec-sub { color: var(--muted); max-width: 58ch; margin-bottom: var(--sp-lg); }
  /* No fill on .on-dark itself — on the homepage that fill IS body.home's
     --char, and painting the same colour again as an opaque section
     rectangle would block the ambient layer sitting behind it (that
     layer is what the fill used to BE, before the whole page went dark;
     see body.home above). .on-dark now only carries the text-colour
     contract; .method and .quote-section keep their own local tint
     because they still want a slightly distinct glow, not the section
     boundary that caused the original complaint. */
  .on-dark { color: var(--bone); }
  .on-dark .sec-h { color: #fff; }
  .on-dark .sec-sub { color: oklch(92% .018 78/.78); }

  /* ── Floating glass panel (Order Ahead CTA) ─────────────────────────
     True glassmorphism — translucent, blurred, bordered in light rather
     than colour, so the ambient orbs read as glowing through it rather
     than a flat card dropped on top of a flat background. */
  /* Centred, and centred on the page — the deliberate break in a layout
     that was otherwise flush-left in all six sections. Alignment change
     is doing real work here rather than decoration: this is the page's
     single primary call to action, and centring is what separates it
     from the reading columns above and below it. */
  .cta-panel {
    background: oklch(100% 0 0 / .05);
    border: 1px solid oklch(100% 0 0 / .12);
    backdrop-filter: blur(18px) saturate(1.4);
    border-radius: var(--r-lg);
    padding: clamp(1.8rem, 1.4rem + 2vw, 3rem);
    box-shadow: var(--sh-warm-2);
    max-width: 44rem; margin-inline: auto; text-align: center;
  }
  .cta-panel .sec-sub { margin-inline: auto; }

  /* ── How-to-order steps — floating numbered glass cards ─────────────
     Was a plain <ol>. The numerals reuse the Method section's oversized-
     ghost-digit treatment so the two "process" sections read as one
     family rather than two different UI patterns. */
  /* Second deliberate alignment break — a centred heading over a four-up
     row reads as a process/diagram, which is what this section is,
     versus the left-aligned reading columns around it. */
  .howto .sec-h, .howto .sec-sub { text-align: center; }
  .howto .sec-sub { margin-inline: auto; }
  .howto-steps {
    list-style: none; display: grid; grid-template-columns: 1fr;
    gap: var(--sp-md); margin-top: var(--sp-lg); counter-reset: howto;
  }
  @media (min-width: 30rem) {
    .howto-steps { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
  }
  .howto-steps li {
    position: relative; counter-increment: howto;
    background: oklch(100% 0 0 / .045); border: 1px solid oklch(100% 0 0 / .1);
    backdrop-filter: blur(14px);
    border-radius: var(--r-lg); padding: 1.5rem 1.5rem 1.4rem;
    display: flex; flex-direction: column; gap: .6rem;
    transition: transform .3s cubic-bezier(.2,.7,.3,1), background .3s, border-color .3s;
  }
  .howto-steps li:hover {
    transform: translateY(-5px);
    background: oklch(100% 0 0 / .08); border-color: oklch(from var(--ember) l c h / .4);
  }
  .howto-steps b {
    font-family: var(--f-display); font-size: 2.6rem; line-height: 1;
    color: var(--ember-lt); font-variant-numeric: tabular-nums;
  }
  .howto-steps span { font-size: var(--step--1); color: oklch(92% .018 78/.85); }
  .howto-steps em { font-style: normal; color: #fff; }

  /* ── Reserved slots — the swap-in contract ───────────────────────── */
  /* Every unknown ships as a box with a fixed aspect-ratio and a data-slot
     attribute, filled today by type or vector. When the real value lands it
     is an insertion, not a redesign, and layout shift is zero. */
  /* :where() zeroes this rule's specificity on purpose. It exists for the
     inline reserved chips, but it also matches any BLOCK-level element
     carrying a slot — and <ol class="howto-steps" data-slot="video:how-to-order">
     is one. At equal specificity the later rule won, so [data-slot]'s
     inline-block silently overrode .howto-steps' display:grid and the four
     order-ahead steps stacked in a 386px column instead of spanning the
     row. They had never once rendered as the grid they were written as.
     Zeroed, any element that declares its own display now keeps it. */
  :where([data-slot]) { display: inline-block; }
  .tbd {
    display: inline-block; font-size: .68rem; font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase;
    padding: .16em .6em; border-radius: var(--r-pill);
    background: repeating-linear-gradient(45deg,
      oklch(96% .03 85), oklch(96% .03 85) 6px,
      oklch(92% .05 85) 6px, oklch(92% .05 85) 12px);
    color: oklch(45% .08 82); border: 1px solid var(--brass);
    vertical-align: .1em;
  }

  /* ── Schedule ────────────────────────────────────────────────────── */
  /* No section fill any more — the day chips are their own floating
     glass cards on the dark canvas, spaced apart rather than packed
     edge to edge inside a bordered paper band. */
  .days { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-sm); margin-bottom: var(--sp-lg); }
  @media (min-width: 25rem) { .days { grid-template-columns: repeat(4, 1fr); } }
  @media (min-width: 40rem) { .days { grid-template-columns: repeat(7, 1fr); } }
  .day {
    border: 1.5px solid transparent; border-radius: var(--r-lg);
    padding: 1.1rem .5rem; text-align: center; background: var(--paper);
    box-shadow: var(--sh-warm-1);
    transition: transform .28s cubic-bezier(.2,.7,.3,1), box-shadow .28s;
  }
  .day:hover { transform: translateY(-4px); box-shadow: var(--sh-warm-2); }
  /* "Standing" days are a warm-lit glass card rather than a near-black
     fill, which nearly vanished once the page around it went dark too —
     the ring is what reads as "selected", not a flat block. */
  .day.on {
    background: oklch(from var(--ember-fill) l c h / .16);
    border-color: oklch(from var(--ember) l c h / .55);
    color: #fff;
    backdrop-filter: blur(10px);
  }
  .day.avail {
    border-style: dashed; border-color: oklch(from var(--brass) l c h / .55);
    background: oklch(100% 0 0 / .04); backdrop-filter: blur(10px);
  }
  .day dt {
    font-family: var(--f-display); font-size: .95rem;
    letter-spacing: .06em; margin-bottom: .3rem;
  }
  .day.on dt { color: #fff; }
  .day.avail dt { color: var(--bone); }
  .day dd { font-size: .71rem; line-height: 1.3; color: var(--muted); }
  .day.on dd { color: oklch(92% .018 78/.85); }
  .day.avail dd { color: var(--brass-lt); }
  .day .dot {
    display: block; width: 7px; height: 7px; border-radius: 50%;
    background: var(--ember); margin: 0 auto .5rem;
    box-shadow: 0 0 8px 1px oklch(from var(--ember) l c h / .6);
  }
  .day.avail .dot { background: var(--brass); box-shadow: 0 0 8px 1px oklch(from var(--brass) l c h / .55); }
  /* Set by schedule.js from the real America/Denver date — not a guess. */
  .day.today { outline: 2px solid var(--ember); outline-offset: 3px; }

  /* Explicit dark text, not inherited — this is a light card and its
     nearest ancestor with an opinion (.on-dark) sets a LIGHT text colour
     for the dark canvas around it. Without this override the card would
     render light text on its own light background. */
  .notice {
    display: flex; gap: .8rem; align-items: flex-start;
    background: oklch(96% .04 85); color: var(--ink); border: 1.5px solid var(--brass);
    border-left-width: 5px; border-radius: var(--r);
    padding: .9rem 1.1rem; font-size: var(--step--1);
  }
  /* This is the ONE place staff free-text reaches the public page. An
     unbroken string — a pasted URL, or a phone in a pocket — has no wrap
     opportunity and blows the layout out horizontally. Verified: a
     160-char unbroken note produced 1043px of content in a 314px box.
     min-width:0 is needed too, because a flex child's default
     min-width:auto refuses to shrink below its content. */
  .notice p { overflow-wrap: anywhere; min-width: 0; }
  .notice[hidden] { display: none; }

  /* ── Menu ────────────────────────────────────────────────────────── */
  .menu-cat {
    font-size: var(--step-1); letter-spacing: .06em;
    margin-top: 2.4rem; padding-bottom: .4rem;
    border-bottom: 2px solid var(--rule); color: var(--ink);
  }
  .menu-cat:first-of-type { margin-top: 1.4rem; }
  .on-dark .menu-cat { color: #fff; border-color: oklch(92% .018 78/.22); }
  /* One card per row below 34rem, deliberately. Two-up was tried and
     measured: a 390px phone gives the grid a 350px container, so two
     cards plus the gap land at ~164px each — and these cards carry a
     dish name, a description, a size picker and an Add button. That is
     a cramped card, not a denser menu. The scroll length is instead
     addressed by tightening card padding on narrow screens below. */
  .menu-grid {
    display: grid; grid-template-columns: 1fr; gap: var(--sp-md);
    margin-top: var(--sp-lg); align-items: stretch;
  }
  @media (min-width: 34rem) {
    .menu-grid { grid-template-columns: repeat(auto-fit, minmax(272px, 1fr)); }
  }
  /* Flex column + margin-top:auto on the footer aligns every card's price
     row without subgrid, which would need the parent to declare matching
     rows that auto-fit columns can't guarantee. */
  /* color:var(--ink) is explicit, not incidental. This card's background
     is always light paper, but its section's ambient text colour now
     varies (light-on-dark on the homepage, dark-on-light nowhere else
     that uses .item) — without its own colour the card would inherit
     whatever the section around it decided, and light text on a light
     card is invisible. Every light card component below repeats this
     same fix for the same reason (.item, .day, .camp). */
  .item {
    container-type: inline-size;
    background: var(--paper); color: var(--ink); border: 1px solid var(--rule);
    border-radius: var(--r-lg); box-shadow: var(--sh-warm-1);
    display: flex; flex-direction: column;
    /* Tighter on phones — with one card per row, padding is pure scroll
       length. Full padding returns once cards sit side by side.
       Held in custom properties because .item-photo bleeds to the card
       edge by cancelling this exact padding with negative margins; when
       the two were separate literals they silently drifted out of sync
       and left a hairline of card visible beside the photo. */
    --item-pad-y: 1.15rem;
    --item-pad-x: 1.25rem;
    padding: var(--item-pad-y) var(--item-pad-x);
    transition: transform .3s cubic-bezier(.2,.7,.3,1), box-shadow .3s, border-color .3s;
  }
  @media (min-width: 34rem) {
    .item { --item-pad-y: 1.5rem; --item-pad-x: 1.6rem; }
  }
  .item:hover {
    transform: translateY(-6px) scale(1.012); box-shadow: var(--sh-warm-3);
    border-color: oklch(from var(--ember) l c h / .5);
  }
  .item.feature {
    background: linear-gradient(168deg, var(--char), var(--char-2));
    border: 1px solid oklch(from var(--ember) l c h / .3); color: var(--bone);
    position: relative; overflow: hidden;
  }
  .item.feature::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: radial-gradient(circle at 88% 8%, oklch(from var(--ember) l c h / .3), transparent 58%);
  }
  @media (min-width: 42rem) { .item.feature { grid-column: span 2; } }
  @container (min-width: 420px) {
    .item.feature .feat-in {
      display: grid; grid-template-columns: 1fr auto; gap: 1.4rem; align-items: end;
    }
  }
  /* Derived from the card's own padding, never hardcoded — see .item.
     max-width:none is REQUIRED, not defensive: the base layer sets
     `img { max-width: 100% }` globally, which silently clamped this
     back to the content box and defeated the bleed entirely — the
     photo sat ~41px short of the card's right edge and had done since
     the rebuild. The width calc alone can't win that; it's a different
     property, so there's no specificity contest to lose, just a cap. */
  .item-photo {
    width: calc(100% + (var(--item-pad-x) * 2));
    max-width: none;
    margin: calc(var(--item-pad-y) * -1) calc(var(--item-pad-x) * -1) 1rem;
    aspect-ratio: 4/3; object-fit: cover;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
  }
  .feat-photo {
    width: 100%; aspect-ratio: 4/3; object-fit: cover;
    border-radius: var(--r); box-shadow: var(--sh-2); margin-top: 1rem;
  }
  @container (min-width: 420px) { .feat-photo { max-width: 220px; margin-top: 0; } }
  /* ── Reserved photo plates ────────────────────────────────────────
     Professional photography is coming. Until it lands these slots are
     filled by TYPE, not by stock and not by AI — IA §8.1: "Never fake a
     photograph. Replace the photograph." The sharp version of that
     argument: Mimi's publishes "No stock photos — every scoop below was
     made and served right here", and Mimi's is now an endorsed sibling
     brand on this same campus (209 Ute to our 215). Stock burgers here
     would retroactively falsify a claim the campus has already made.

     Each plate carries the dish's own METHOD, lifted verbatim from the
     copy already beside it — so the box informs instead of apologising.
     This is IA §8.2 device 2 ("the menu as artwork") in the slot where
     device 6/7 would otherwise go.

     Geometry is inherited, never redeclared: .slot-plate is applied
     alongside .item-photo / .feat-photo so the aspect-ratio, the
     edge-bleed negative margins and the corner radii all still come
     from those rules. Dropping the real <img> back in is a tag swap
     with zero layout shift — the box it lands in already exists at the
     right size. Do NOT set border-radius or margin here; that is what
     would silently break the bleed. See .item-photo for why. */
  .slot-plate {
    display: grid; place-content: center;
    padding: 1.1rem 1.15rem; text-align: center;
    position: relative; overflow: hidden;
    border: 1px solid oklch(from var(--brass) l c h / .28);
    background:
      radial-gradient(circle at 78% 12%, oklch(from var(--ember) l c h / .32), transparent 62%),
      linear-gradient(158deg, var(--char), var(--char-2));
    /* Its own containment context so the type below can size off THIS
       box rather than the card. The feature plate is capped at 220px
       inside a card that spans two columns — sizing off the card would
       overflow it. */
    container-type: inline-size;
  }
  /* The Pick, demoted to texture. It sits low and small deliberately:
     centred and large it collided with the method line and won, which
     is backwards — the words are the subject here, the mark is ground. */
  .slot-plate::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: url("../brand/pick-rust-200.png") no-repeat 50% 88% / auto 42%;
    opacity: .09;
  }
  /* Set as a menu board, not a caption — IA §8.2 device 2, "the menu as
     artwork": dish language at display scale earns the box the photo
     will eventually take, rather than apologising for being empty. */
  .slot-plate .pl-method {
    position: relative; /* lift above the watermark */
    font-family: var(--f-display); font-weight: 400;
    font-size: clamp(1rem, 11cqi, 1.85rem);
    line-height: 1.14; letter-spacing: .045em;
    text-transform: uppercase;
    color: var(--bone);
    text-wrap: balance;
    max-width: 15ch; margin-inline: auto;
  }
  /* The feature card is already dark, so its plate needs to read as a
     separate panel rather than a lighter patch of the same field.
     The colour re-declaration is NOT redundant: .pl-method is a <p>, so
     `.item.feature p` (0,2,1) out-specifies `.slot-plate .pl-method`
     (0,2,0) and was dimming this one plate's display type to 85% alpha
     while the other three sat at solid bone. */
  .item.feature .slot-plate {
    border-color: oklch(from var(--brass) l c h / .42);
  }
  .item.feature .slot-plate .pl-method { color: var(--bone); }
  /* The feature plate needs an EXPLICIT width, and this is load-bearing.
     Above 420px .feat-in becomes `grid-template-columns: 1fr auto`. The
     <img> this replaced carried intrinsic dimensions, so the auto track
     resolved to the image's own width (capped at 220px). A <div> has no
     intrinsic size, so the same track collapses to the type's min-content
     width — measured at 50x37px before this rule, i.e. the plate all but
     vanished on desktop while looking perfect on a phone.
     Mirrors the 220px cap directly above rather than inventing a number. */
  @container (min-width: 420px) {
    .feat-photo.slot-plate { width: 220px; }
  }


  .tag {
    align-self: start; font-size: .65rem; font-weight: 700;
    letter-spacing: .1em; text-transform: uppercase;
    padding: .32em .8em; border-radius: var(--r-pill);
    margin-bottom: .7rem; width: max-content;
  }
  .tag-limited { background: var(--ember-fill); color: #fff; }
  .tag-value   { background: var(--brass); color: oklch(22% .05 82); }
  .tag-std     { background: var(--bone); color: var(--muted); }

  .item h3 { font-size: var(--step-1); margin-bottom: .3rem; }
  .item.feature h3 { color: #fff; font-size: var(--step-2); }
  .item p { font-size: var(--step--1); color: var(--muted); flex: 1; }
  .item.feature p { color: oklch(92% .018 78/.85); }
  .item-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; margin-top: auto; padding-top: .9rem;
    border-top: 1px solid var(--rule);
  }
  .item.feature .item-foot { border-color: oklch(92% .018 78/.2); }
  .price {
    font-family: var(--f-display); font-size: var(--step-1);
    letter-spacing: .03em; font-variant-numeric: tabular-nums;
  }
  .item.feature .price { color: var(--brass-lt); }
  .add {
    background: var(--ink); color: #fff; border: 0;
    border-radius: var(--r-pill); padding: .55em 1.2em;
    font-weight: 700; font-size: var(--step--1); min-height: 44px;
    transition: background .2s, transform .15s;
  }
  .add:hover { background: var(--ember-fill); transform: translateY(-1px); }
  .item.feature .add { background: var(--ember-fill); }
  .item.feature .add:hover { background: var(--ember-lt); color: var(--ink); }
  .add[disabled] { background: var(--muted); opacity: .55; cursor: not-allowed; transform: none; }

  .countdown {
    display: flex; align-items: baseline; gap: .5rem; margin-top: .9rem;
    font-size: var(--step--1); color: var(--brass-lt); font-weight: 600;
  }
  .countdown b {
    font-family: var(--f-display); font-size: 2.1rem;
    color: var(--ember-lt); line-height: 1; font-variant-numeric: tabular-nums;
  }
  .allergen { margin-top: 2rem; font-size: var(--step--1); color: var(--muted); }
  .on-dark .allergen { color: oklch(92% .018 78/.7); }

  .sizes {
    border: 0; margin: .9rem 0 0; display: flex; gap: .5rem; flex-wrap: wrap;
  }
  .sizes label { position: relative; display: inline-flex; }
  .sizes input { position: absolute; opacity: 0; width: 0; height: 0; }
  .sizes span {
    display: inline-flex; align-items: center; min-height: 44px;
    border: 1.5px solid var(--rule); border-radius: var(--r-pill);
    padding: .4em 1em; font-size: var(--step--1); font-weight: 600;
    cursor: pointer; transition: background .18s, border-color .18s, color .18s;
  }
  .sizes label:hover span { border-color: var(--ember); }
  .sizes input:checked + span { background: var(--ink); color: #fff; border-color: var(--ink); }
  .sizes input:focus-visible + span { outline: 3px solid var(--ember); outline-offset: 3px; }

  /* ── The Method ──────────────────────────────────────────────────── */
  /* No base fill (see .on-dark above) — the radial glow in ::before is
     what makes this section still read as distinct from its neighbours,
     not a solid colour boundary. */
  .method { color: var(--bone); position: relative; overflow: hidden; }
  .method::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: radial-gradient(ellipse 60% 50% at 85% 0%, oklch(from var(--brass) l c h / .22), transparent 65%);
  }
  .method-in { position: relative; z-index: 1; }
  .method .sec-h { color: #fff; }
  .method-lede {
    max-width: 60ch; color: oklch(92% .018 78/.86);
    font-size: var(--step-1); margin-bottom: .9em;
  }
  .method-credit {
    max-width: 62ch; color: oklch(92% .018 78/.7); font-size: var(--step--1);
    margin-bottom: 2.2em; padding-left: 1rem; border-left: 2px solid var(--brass);
  }
  .method-credit strong { color: var(--brass-lt); }
  .steps {
    display: grid; grid-template-columns: 1fr; gap: var(--sp-md); counter-reset: step;
  }
  @media (min-width: 34rem) {
    .steps { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
  }
  .step {
    position: relative; background: oklch(100% 0 0/.05);
    border: 1px solid oklch(92% .018 78/.16); border-radius: var(--r-lg);
    padding: 1.5rem 1.5rem 1.4rem; counter-increment: step;
  }
  .step::before {
    content: counter(step); position: absolute; top: -.35em; right: .6rem;
    font-family: var(--f-display); font-size: 4.2rem; line-height: 1;
    color: oklch(from var(--brass) l c h / .22);
  }
  .step h3 { font-size: var(--step-1); color: #fff; margin-bottom: .35rem; position: relative; }
  .step .temp {
    display: inline-block; font-size: .68rem; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase;
    color: var(--ember-lt); margin-bottom: .5rem; font-variant-numeric: tabular-nums;
  }
  .step p { font-size: var(--step--1); color: oklch(92% .018 78/.8); position: relative; }
  .method-foot {
    margin-top: 2rem; padding-top: 1.3rem;
    border-top: 1px solid oklch(92% .018 78/.16);
    font-size: var(--step--1); color: oklch(92% .018 78/.75); max-width: 64ch;
  }
  .method-foot strong { color: #fff; }

  /* ── Cards / campus / booking ────────────────────────────────────── */
  .two { display: grid; grid-template-columns: 1fr; gap: var(--sp-md); margin-top: var(--sp-lg); }
  @media (min-width: 49rem) { .two { grid-template-columns: 1fr 1fr; } }
  .bcard {
    background: oklch(100% 0 0/.055); border: 1px solid oklch(92% .018 78/.17);
    border-radius: var(--r-lg); padding: 1.5rem 1.6rem;
  }
  .bcard h3 { font-size: var(--step-1); color: #fff; margin-bottom: .4rem; }
  .bcard p, .bcard ul { font-size: var(--step--1); color: oklch(92% .018 78/.8); }
  .bcard ul { list-style: none; margin-top: .9rem; }
  .bcard li { padding-left: 1.35em; position: relative; margin: .32em 0; }
  .bcard li::before { content: "→"; position: absolute; left: 0; color: var(--ember); }

  /* No fill — floats on the dark canvas like everything else on the
     homepage now. */
  .camp-grid {
    display: grid; grid-template-columns: 1fr; gap: var(--sp-md); margin-top: var(--sp-lg);
  }
  @media (min-width: 34rem) {
    .camp-grid { grid-template-columns: repeat(auto-fit, minmax(228px, 1fr)); }
  }
  /* color:var(--ink) explicit — same reasoning as .item above: a light
     card whose section text-colour now varies needs its own. */
  .camp {
    background: var(--paper); color: var(--ink); border: 1px solid var(--rule);
    border-radius: var(--r-lg); padding: 1.5rem 1.6rem; box-shadow: var(--sh-warm-1);
    transition: transform .3s cubic-bezier(.2,.7,.3,1), box-shadow .3s;
    display: flex; flex-direction: column;
  }
  .camp:hover { transform: translateY(-6px) scale(1.012); box-shadow: var(--sh-warm-3); }
  .camp .when {
    display: inline-block; font-size: .68rem; font-weight: 700;
    letter-spacing: .09em; text-transform: uppercase;
    color: var(--ember-dk); margin-bottom: .5rem;
  }
  .camp h3 { font-size: var(--step-1); margin-bottom: .35rem; }
  .camp p { font-size: var(--step--1); color: var(--muted); flex: 1; }
  .camp.self { border-color: var(--ember); border-width: 2px; }
  .camp-link {
    display: inline-flex; align-items: center; gap: .35rem;
    margin-top: .9rem; font-size: var(--step--1); font-weight: 700;
    color: var(--ember-dk); text-decoration: none; min-height: 44px;
  }
  .camp-link:hover { text-decoration: underline; }

  /* ── Book page: the advantage cards, service styles, quote form ──── */
  .edge { background: var(--paper); border-block: 1px solid var(--rule); }
  .edge-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-md); margin-top: var(--sp-lg); }
  @media (min-width: 32rem) { .edge-grid { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); } }
  .edge-card {
    background: var(--cream); border: 1px solid var(--rule); border-left: 4px solid var(--brass);
    border-radius: var(--r); padding: 1.3rem 1.4rem;
  }
  .edge-card h3 { font-size: var(--step-1); margin-bottom: .4rem; }
  .edge-card p { font-size: var(--step--1); color: var(--muted); }

  .svc-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-md); margin-top: var(--sp-lg); }
  @media (min-width: 34rem) { .svc-grid { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); } }
  .svc {
    container-type: inline-size; background: var(--paper); border: 1px solid var(--rule);
    border-radius: var(--r-lg); padding: 1.5rem 1.6rem; box-shadow: var(--sh-1);
    transition: transform .22s, box-shadow .22s, border-color .22s;
  }
  .svc:hover { transform: translateY(-3px); box-shadow: var(--sh-2); border-color: oklch(from var(--ember) l c h / .45); }
  .svc .tag {
    display: inline-block; font-size: .65rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
    padding: .32em .8em; border-radius: var(--r-pill); background: var(--bone); color: var(--muted); margin-bottom: .7rem;
  }
  .svc h3 { font-size: var(--step-1); margin-bottom: .4rem; }
  .svc p { font-size: var(--step--1); color: var(--muted); }
  .svc ul { list-style: none; margin-top: .8rem; font-size: var(--step--1); color: var(--muted); }
  .svc li { padding-left: 1.3em; position: relative; margin: .3em 0; }
  .svc li::before { content: "→"; position: absolute; left: 0; color: var(--ember); }

  .quote-section { background: var(--char); color: var(--bone); position: relative; overflow: hidden; }
  .quote-section .sec-h { color: #fff; }
  .quote-section .sec-sub { color: oklch(92% .018 78/.78); }
  .form {
    position: relative; z-index: 1; background: oklch(100% 0 0/.05);
    border: 1px solid oklch(92% .018 78/.17); border-radius: var(--r-lg);
    padding: 1.5rem; margin-top: 1.6rem; max-width: 760px;
  }
  @media (min-width: 30rem) { .form { padding: 1.7rem; } }
  .row { display: grid; grid-template-columns: 1fr; gap: 1rem; margin-bottom: 1rem; }
  @media (min-width: 40rem) { .row { grid-template-columns: 1fr 1fr; } }
  .form .fld { display: flex; flex-direction: column; gap: .35rem; }
  .form .fld label { font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--brass-lt); }
  .form .fld input, .form .fld select, .form .fld textarea {
    border: 1.5px solid oklch(92% .018 78/.22); background: oklch(100% 0 0/.06); color: #fff;
    border-radius: 10px; padding: .8em .9em; font: inherit; font-size: var(--step--1); min-height: 44px;
  }
  .form .fld textarea { min-height: 90px; resize: vertical; }
  .form .fld input::placeholder, .form .fld textarea::placeholder { color: oklch(92% .018 78/.4); }
  .form .fld select option { background: var(--char); color: #fff; }
  .form .fld input:focus-visible, .form .fld select:focus-visible, .form .fld textarea:focus-visible {
    border-color: var(--ember); outline: none; box-shadow: 0 0 0 3px oklch(from var(--ember) l c h/.25);
  }
  .form .fld input:user-invalid, .form .fld select:user-invalid { border-color: var(--ember); }
  .form-note { font-size: .8rem; color: oklch(92% .018 78/.6); margin-top: .9rem; }
  .submit { width: 100%; justify-content: center; background: var(--ember-fill); color: #fff; margin-top: .5rem; }
  .submit:hover { background: var(--ember-fill-hover); }
  .sent {
    margin-top: 1rem; padding: 1rem; border-radius: var(--r);
    background: oklch(from var(--ok) l c h/.16); border: 1.5px solid var(--ok);
    text-align: center; font-size: var(--step--1);
  }
  .sent[hidden] { display: none; }

  /* ── Privacy page ────────────────────────────────────────────────── */
  .legal { max-width: var(--measure); }
  .legal h2 { font-size: var(--step-1); margin-top: 2rem; margin-bottom: .5rem; }
  .legal h2:first-child { margin-top: 0; }
  .legal p { margin-bottom: 1rem; }
  .legal .updated { color: var(--muted); font-size: var(--step--1); margin-bottom: 2rem; }
  .legal .card {
    background: var(--paper); border: 1px solid var(--rule); border-radius: var(--r);
    padding: 1.2rem 1.4rem; margin-bottom: 1rem;
  }
  .legal .card ul { padding-left: 1.2rem; }
  .legal .card li { margin: .4rem 0; font-size: var(--step--1); }

  /* ── Footer ──────────────────────────────────────────────────────── */
  .foot {
    background: var(--ink); color: oklch(92% .018 78/.76);
    font-size: var(--step--1);
    padding: var(--sp-fluid) 0 calc(2.2rem + env(safe-area-inset-bottom));
  }
  .foot-emblem { width: 96px; height: auto; margin: 0 auto 1.8rem; opacity: .92; }
  .foot-grid {
    display: grid; grid-template-columns: 1fr; gap: 1.7rem; margin-bottom: 1.9rem;
  }
  @media (min-width: 34rem) {
    .foot-grid { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
  }
  .foot h4 {
    font-family: var(--f-display); font-size: 1.02rem; color: #fff;
    margin-bottom: .55rem; letter-spacing: .05em;
  }
  .foot a { color: var(--brass-lt); text-decoration: none; }
  .foot a:hover { text-decoration: underline; }
  .foot-btm {
    border-top: 1px solid oklch(92% .018 78/.15); padding-top: 1.3rem;
    font-size: .79rem; color: oklch(92% .018 78/.55);
    display: flex; flex-wrap: wrap; gap: .8rem; justify-content: space-between;
  }

  /* ── Sticky order bar (phones only) ─────────────────────────────────
     The site's primary job is taking an order, but on a phone the only
     order controls were the nav button (which scrolls away) and the
     Order Ahead section — so through the entire menu, the Method and
     the campus sections there was no way to act without scrolling back
     up. This is a persistent, thumb-reachable one.

     Hidden by default and revealed by JS once the hero has scrolled
     out, so it never competes with the hero's own CTA. Suppressed
     above 56rem, where the nav's Order Ahead button is always visible
     anyway. */
  .order-bar {
    position: fixed; z-index: 70;
    left: 0; right: 0; bottom: 0;
    display: flex; align-items: center; gap: .9rem;
    padding: .8rem calc(var(--gutter) + env(safe-area-inset-right))
             calc(.8rem + env(safe-area-inset-bottom))
             calc(var(--gutter) + env(safe-area-inset-left));
    background: oklch(from var(--ink) l c h / .9);
    backdrop-filter: blur(16px) saturate(1.3);
    border-top: 1px solid oklch(from var(--ember) l c h / .35);
    /* Off-screen rather than display:none so it can transition, and
       inert so it's unreachable by keyboard/AT while hidden. */
    translate: 0 110%;
    visibility: hidden;
    transition: translate .32s cubic-bezier(.2,.7,.3,1), visibility .32s;
  }
  .order-bar.show { translate: 0 0; visibility: visible; }
  /* The bar is fixed over the page, so at the very bottom of the scroll
     it would sit on top of the footer's last line. Reserve its height
     (and scroll-padding, so anchor jumps don't land underneath it)
     only while it's actually out. */
  body:has(.order-bar.show) { padding-bottom: 78px; }
  body:has(.order-bar.show) { scroll-padding-bottom: 90px; }
  .order-bar-txt {
    flex: 1; min-width: 0; font-size: var(--step--1);
    color: oklch(92% .018 78/.85); line-height: 1.25;
  }
  .order-bar-txt b {
    display: block; color: #fff; font-weight: 700;
    font-size: var(--step-0);
  }
  .order-bar .btn { flex: none; padding: .8em 1.4em; }
  @media (min-width: 56rem) { .order-bar { display: none; } }

  /* ── Toast ───────────────────────────────────────────────────────── */
  /* Rides above the sticky bar when it's out, so an "added to order"
     toast is never hidden behind the thing that triggered it. */
  body:has(.order-bar.show) #toast { bottom: calc(94px + env(safe-area-inset-bottom)); }
  #toast {
    position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom));
    translate: -50% 0; z-index: 90;
    background: var(--ink); color: #fff;
    padding: .85em 1.4em; border-radius: var(--r-pill);
    box-shadow: var(--sh-3); font-size: var(--step--1); font-weight: 600;
    opacity: 0; pointer-events: none;
    transition: opacity .25s, translate .25s;
  }
  #toast.show { opacity: 1; translate: -50% -6px; }
}

/* ───────────────────────────────────────────────────────────── MOTION ─── */
@layer motion {
  /* Scroll-driven first, JS fallback second. Only opacity and transform —
     never height/width/top/left. Compositor-only keeps 60fps on the cheap
     Android phones a rural Delta audience actually carries. */
  /* This layer loads after components, so `.rv`'s `transition` property
     always wins over e.g. `.item:hover`'s own transition list for as
     long as `.rv` still matches — which is forever, since JS adds `.in`
     alongside `.rv` rather than removing it. Left with only opacity/
     transform in the list, box-shadow and border-color hover changes
     would SNAP instead of easing. Carrying every hover-animatable
     property here keeps them smooth regardless of which layer's
     `transition` value actually wins. */
  .rv {
    opacity: 0; transform: translateY(20px) scale(.98);
    transition: opacity .6s cubic-bezier(.2,.7,.3,1), transform .6s cubic-bezier(.2,.7,.3,1),
      box-shadow .3s, border-color .3s, background .3s;
  }
  .rv.in { opacity: 1; transform: none; }
  @supports (animation-timeline: view()) {
    /* NOT `transition: none` — that was silently killing every hover
       transition (transform, box-shadow, border-color) on every card
       that carries .rv, in every browser that supports scroll-driven
       animation, which by 2026 is most of them. The entrance here runs
       once via `animation` and finishes with `fill:both`; by the time
       anyone hovers, that animation has already stopped, so `transition`
       is free to govern transform/box-shadow/border-color again without
       fighting it. Keep this list identical to the fallback `.rv` above. */
    .rv {
      opacity: 1; transform: none;
      transition: opacity .6s cubic-bezier(.2,.7,.3,1), transform .6s cubic-bezier(.2,.7,.3,1),
        box-shadow .3s, border-color .3s, background .3s;
      animation: rise both linear;
      animation-timeline: view();
      animation-range: entry 5% entry 62%;
    }
    @keyframes rise {
      from { opacity: 0; transform: translateY(22px) scale(.98); }
      to   { opacity: 1; transform: none; }
    }
  }
  /* Cascading entrance for card grids — each grid's own children stagger
     against EACH OTHER (nth-child), not the rest of the page, so a row
     of cards settles in as one gesture rather than one long file. Scoped
     to :not(.in) so the delay only ever applies to the reveal itself —
     once settled, hover transitions stay instant-on, never queued
     behind a leftover reveal delay. */
  .menu-grid .item.rv:not(.in):nth-child(1), .camp-grid .camp.rv:not(.in):nth-child(1), .howto-steps li.rv:not(.in):nth-child(1), .steps .step.rv:not(.in):nth-child(1) { transition-delay: 0s; }
  .menu-grid .item.rv:not(.in):nth-child(2), .camp-grid .camp.rv:not(.in):nth-child(2), .howto-steps li.rv:not(.in):nth-child(2), .steps .step.rv:not(.in):nth-child(2) { transition-delay: .08s; }
  .menu-grid .item.rv:not(.in):nth-child(3), .camp-grid .camp.rv:not(.in):nth-child(3), .howto-steps li.rv:not(.in):nth-child(3), .steps .step.rv:not(.in):nth-child(3) { transition-delay: .16s; }
  .menu-grid .item.rv:not(.in):nth-child(4), .howto-steps li.rv:not(.in):nth-child(4) { transition-delay: .24s; }
  .menu-grid .item.rv:not(.in):nth-child(5) { transition-delay: .32s; }
  .menu-grid .item.rv:not(.in):nth-child(6) { transition-delay: .4s; }
  /* The animation-timeline:view() path above doesn't use .in at all —
     stagger there via animation-delay on the same selectors, harmlessly
     inert wherever the @supports block didn't match. */
  @supports (animation-timeline: view()) {
    .menu-grid .item:nth-child(2), .camp-grid .camp:nth-child(2), .howto-steps li:nth-child(2), .steps .step:nth-child(2) { animation-delay: .08s; }
    .menu-grid .item:nth-child(3), .camp-grid .camp:nth-child(3), .howto-steps li:nth-child(3), .steps .step:nth-child(3) { animation-delay: .16s; }
    .menu-grid .item:nth-child(4), .howto-steps li:nth-child(4) { animation-delay: .24s; }
    .menu-grid .item:nth-child(5) { animation-delay: .32s; }
    .menu-grid .item:nth-child(6) { animation-delay: .4s; }
  }

  .progress {
    position: fixed; top: 0; left: 0; height: 3px; width: 100%;
    transform-origin: 0 50%; transform: scaleX(0);
    background: linear-gradient(90deg, var(--ember), var(--brass));
    z-index: 80;
  }
  @supports (animation-timeline: scroll()) {
    .progress { animation: grow linear; animation-timeline: scroll(root); }
    @keyframes grow { to { transform: scaleX(1); } }
  }

  /* ── Hero entrance ──────────────────────────────────────────────────
     The hero copy previously had NO entrance at all — .rv was only ever
     applied to section headings and cards further down, so the single
     most important screen on the site just appeared, fully formed, with
     nothing to lead the eye. This is a one-shot staggered rise, pure CSS
     and running immediately on load (never waiting on JS, per the
     above-the-fold rule). Each element enters ~110ms after the one
     before it, so the eye is walked eyebrow -> headline -> lede ->
     buttons -> footnote in the order they should be read. */
  .hero-in > * {
    animation: hero-rise .85s cubic-bezier(.16, 1, .3, 1) both;
  }
  .hero-in > .eyebrow    { animation-delay: .05s; }
  /* h1 no longer rises as one block — its words do, individually, via the
     per-word cascade below. The parent stays put (no animation) so it
     isn't moving while its own children are also moving. */
  .hero-in > h1           { animation: none; }
  .hero-in > .lede       { animation-delay: .30s; }
  .hero-in > .btns       { animation-delay: .42s; }
  .hero-in > .hero-foot  { animation-delay: .54s; }
  @keyframes hero-rise {
    from { opacity: 0; transform: translateY(26px); filter: blur(6px); }
    to   { opacity: 1; transform: none; filter: blur(0); }
  }

  /* ── Scroll-linked hero parallax ────────────────────────────────────
     The hero content drifts up and fades slightly as it leaves, so the
     transition into the page below is a continuous move rather than a
     hard scroll-off. Scroll-driven, so it costs no scroll listener and
     no JS; entirely absent where animation-timeline isn't supported. */
  @supports (animation-timeline: view()) {
    .hero-in {
      animation: hero-part linear both;
      animation-timeline: view();
      animation-range: exit 0% exit 90%;
    }
    @keyframes hero-part {
      from { opacity: 1; transform: translateY(0); }
      to   { opacity: .25; transform: translateY(-60px); }
    }
    /* The watermark moves further and slower than the copy — the depth
       cue that makes it read as sitting behind, not on, the text.
       BOTH animations are re-declared here, not just the new one: a bare
       `animation: hero-mark-part` would replace the shorthand set
       earlier and silently drop mark-breathe wherever scroll-driven
       animation is supported (i.e. almost everywhere). Two names, two
       timelines — `auto` keeps breathe on the document timeline while
       the parallax rides the scroll. */
    .hero-mark {
      animation: mark-breathe 26s ease-in-out infinite alternate,
                 hero-mark-part linear both;
      animation-timeline: auto, view();
      animation-range: normal, exit 0% exit 100%;
    }
    /* Both keyframes must carry the -50% — `translate` is the same
       property the base rule uses to vertically centre this element, so
       animating a bare offset here would drop the centring and snap the
       emblem down half its own height the moment the animation applies. */
    @keyframes hero-mark-part {
      from { translate: 0 -50%; }
      to   { translate: 0 calc(-50% - 110px); }
    }
  }

  /* Cross-document continuity. ~10 lines for the single biggest
     perceived-quality jump available on a multi-page static site. */
  @view-transition { navigation: auto; }
  .nav { view-transition-name: site-nav; }
  .foot { view-transition-name: site-foot; }

  /* Nothing loops except the status pulse. It means LIVE — anything else
     moving on a timer competes with the one thing that should read as
     alive. The ambient orbs are the one deliberate exception (see
     .ambient above): they're background atmosphere, not foreground UI,
     so they're addressed on their own terms below rather than folded
     into that rule. */
  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .rv {
      opacity: 1 !important; transform: none !important;
      transition: none !important; animation: none !important;
    }
    .progress { display: none; }
    .led { animation: none; }
    /* Every moving background layer stops. The layers themselves stay —
       vignette, grain and the colour fields are static design, not
       motion. Only the drift is suppressed. */
    .ambient .orb, .ambient .embers { animation: none; }
    .ambient { animation: none; transform: none; filter: none; }
    /* The hero entrance, its scroll parallax and the emblem drift all
       resolve to their finished state immediately rather than being
       skipped — `both` fill would otherwise leave hero copy stuck at
       opacity:0 with animations disabled, i.e. an invisible hero. */
    .hero-in > *, .hero-in, .hero-mark, .hero-in h1 .w {
      animation: none !important;
      opacity: 1 !important; transform: none !important; filter: none !important;
    }
    .hero-mark { translate: 0 -50% !important; }
    .flame-mark path { animation: none !important; stroke-dashoffset: 0 !important; }
    .tag-limited::after { animation: none !important; background-position: -50% 0 !important; }
    .item:hover, .camp:hover, .day:hover, .howto-steps li:hover,
    .btn:hover, .add:hover, .order-btn:hover { transform: none; }
    /* Appears/disappears instantly rather than sliding. It still needs
       to MOVE (it's translated off-screen when hidden) — so this kills
       the transition, not the positioning. */
    .order-bar { transition: none; }
    *, *::before, *::after { scroll-behavior: auto !important; }
  }
}

/* ────────────────────────────────────────────────────────── UTILITIES ─── */
@layer utilities {
  .center { text-align: center; }
  .stack-sm > * + * { margin-top: .5rem; }
  .mt-lg { margin-top: 1.7rem; }
  [hidden] { display: none !important; }
  /* Premium grain — one inline SVG, no asset request. */
  .grain::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    opacity: .05; mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
  }
}

/* ═══ CAMPUS LAYER — Delta Roadhouse container.
   Same block as the Mimi's build; container class swapped to this site's
   own .wrap, tenant lockup type-set rather than artwork. Navy holds
   everything and never changes colour for any tenant. Brass rules, bone
   text. A tenant's own accent NEVER appears inside this block.        ═══ */
.campus-rail {
  background: var(--navy); color: var(--bone);
  font-family: inherit; font-size: 0.7rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding-block: 0.55rem;
}
.campus-rail .campus-rail__in {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.campus-rail a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; padding-block: 0.7rem; }
@media (max-width: 640px) { .campus-rail__alt { display: none; } }

.campus-bar {
  background: var(--navy); color: var(--bone);
  padding-block: clamp(1.4rem, 3vw, 2rem);
  border-top: 1px solid var(--brass);
}
.campus-bar .campus-bar__in { display: grid; gap: 1.1rem; }
@media (min-width: 760px) { .campus-bar .campus-bar__in { grid-template-columns: auto 1fr; align-items: center; } }
.campus-bar p { color: var(--bone); font-size: 0.9rem; opacity: .88; margin: 0; }
.campus-bar a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }

/* The endorsement lockup, type-set. Ratio fixed by multiplier, not by two
   independent clamps - two clamps drift apart on a phone. */
.lock { display: inline-block; --lock-1: clamp(22px, 2.6vw, 26px); }
.lock .l1 { font-weight: 700; font-size: var(--lock-1); letter-spacing: 0.09em;
  text-transform: uppercase; color: var(--bone); line-height: 1.1; display: block; }
.lock hr { border: 0; height: 1px; background: var(--brass); margin: 0.4em 0 0.35em; }
.lock .l2 { font-weight: 600; font-size: calc(var(--lock-1) * 0.43);
  letter-spacing: 0.34em; text-transform: uppercase; color: var(--bone); display: block; }

/* ═══ Menu data layer — see assets/js/menu-data.js ═══════════════════════
   Appended to the existing `components` layer rather than left unlayered,
   so these keep losing to the unlayered rules below them by design and
   don't start a specificity war with the section styles above. */
@layer components {

  /* ── Tonight's special ─────────────────────────────────────────────
     The one line on the menu that changes by day, so it takes the ember
     accent the design direction reserves for things that are live or
     actionable. Hidden outright when there is no special — an empty
     flourish costs more than the space it fills. */
  .menu-today {
    display: flex; align-items: baseline; flex-wrap: wrap; gap: .55rem;
    margin: calc(var(--sp-md) * -1) 0 var(--sp-lg);
    padding: .75em 1.1em;
    border-left: 3px solid var(--ember);
    border-radius: 0 var(--r-lg) var(--r-lg) 0;
    background: oklch(from var(--ember) l c h / .12);
    color: var(--bone);
    font-size: var(--step-0);
    max-width: 58ch;
  }
  .menu-today::before {
    content: "Tonight";
    font-family: var(--f-display);
    font-size: .72em; letter-spacing: .1em; text-transform: uppercase;
    color: var(--ember-lt);
    flex: none;
  }

  /* ── Third-party ordering channels ─────────────────────────────────
     Rendered from ordering-config.js. An unconfigured channel arrives
     here as a disabled button carrying its own reason in `title`, never
     as a link that goes nowhere. */
  .order-channels {
    display: flex; flex-wrap: wrap; gap: var(--sp-sm);
    margin-top: var(--sp-md);
  }
  .order-channels .btn:disabled {
    opacity: .55;
    cursor: not-allowed;
    transform: none;
  }

  /* ── Event-sized order hint ────────────────────────────────────────
     Offers the catering route without competing with Place Order — this
     is a signpost, not a second call to action. */
  .cart-event {
    margin-top: var(--sp-xs);
    font-size: var(--step--1);
    color: var(--muted);
    text-align: center;
  }
  .cart-event a { color: var(--ember-fill); font-weight: 700; }
}

/* ═══ THE ORDER PANEL ════════════════════════════════════════════════════
   This is the site's primary job — "order ahead, skip the line" is the
   first CTA in the hero, the nav button, and the sticky bar on phones —
   and until now it had NO styles at all. Nineteen of its twenty-one
   classes matched nothing in this file, so the panel rendered as a raw
   browser form: a 10px close glyph, default grey steppers, an orphaned
   "Mobile" label, a run-together total, and a system submit button. Every
   marketing beat on the page was pushing traffic into that.

   Mobile-first: a bottom sheet, because the thumb is at the bottom of the
   phone and a sheet keeps the menu visible behind it. It becomes a centred
   dialog once there is room. */
@layer components {

  /* Reset the UA popover box before rebuilding it. */
  #cart {
    position: fixed;
    inset: auto 0 0 0;
    width: 100%;
    max-width: none;
    max-height: 88dvh;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    overflow: visible;
    color: var(--bone);
  }

  #cart::backdrop {
    background: oklch(from var(--ink) calc(l - .04) c h / .62);
    backdrop-filter: blur(3px);
  }

  .cart-panel {
    display: flex;
    flex-direction: column;
    max-height: 88dvh;
    container-type: inline-size;
    background:
      radial-gradient(120% 70% at 50% 0%, oklch(from var(--ember) l c h / .13), transparent 60%),
      linear-gradient(168deg, var(--char-2), var(--char) 42%, var(--ink));
    border: 1px solid oklch(from var(--brass) l c h / .34);
    border-bottom: 0;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    box-shadow: 0 1px 3px oklch(from var(--ink) l c h / .5),
                0 -18px 48px oklch(from var(--ink) l c h / .55);
    /* The home indicator sits under the footer on modern phones. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  /* ── header ── */
  .cart-hd {
    flex: none;
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-sm);
    padding: var(--sp-md) var(--sp-md) var(--sp-sm);
    border-bottom: 1px solid oklch(from var(--brass) l c h / .2);
  }
  .cart-hd h2 {
    margin: 0;
    font-family: var(--f-display);
    font-size: var(--step-2);
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--bone);
  }
  /* Was a ~10px glyph. A close control on a sheet that covers the page has
     to be findable and hittable, so it gets the full 44px. */
  .cart-hd .x {
    flex: none;
    width: 44px; height: 44px;
    display: grid; place-items: center;
    font-size: 1.6rem; line-height: 1;
    color: var(--bone);
    background: oklch(from var(--bone) l c h / .07);
    border: 1px solid oklch(from var(--bone) l c h / .16);
    border-radius: var(--r-pill);
    cursor: pointer;
    transition: background .18s, color .18s;
  }
  .cart-hd .x:hover { background: var(--ember-fill); border-color: transparent; color: #fff; }

  /* ── the lines ── */
  .cart-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: var(--sp-sm) var(--sp-md);
    display: flex; flex-direction: column; gap: .55rem;
  }
  .cart-body .empty {
    margin: 0; padding: var(--sp-md) 0;
    color: oklch(92% .018 78/.62);
    font-size: var(--step--1);
    text-wrap: pretty;
  }
  .line {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-sm);
    padding: .6rem .75rem;
    background: oklch(from var(--bone) l c h / .05);
    border: 1px solid oklch(from var(--bone) l c h / .1);
    border-radius: var(--r-lg);
  }
  .line strong { font-weight: 600; font-size: var(--step-0); text-wrap: pretty; }

  .qty { flex: none; display: flex; align-items: center; gap: .15rem; }
  .qty button {
    width: 44px; height: 44px;
    display: grid; place-items: center;
    font-size: 1.15rem; font-weight: 700; line-height: 1;
    color: var(--bone);
    background: oklch(from var(--bone) l c h / .08);
    border: 1px solid oklch(from var(--bone) l c h / .16);
    border-radius: var(--r-pill);
    cursor: pointer;
    transition: background .18s, transform .12s;
  }
  .qty button:hover { background: var(--ember-fill); border-color: transparent; color: #fff; }
  .qty button:active { transform: scale(.92); }
  .qty span {
    min-width: 2ch; text-align: center;
    font-weight: 700; font-variant-numeric: tabular-nums;
  }

  /* ── pickup ── */
  .pickup {
    flex: none;
    padding: var(--sp-sm) var(--sp-md) 0;
    border-top: 1px solid oklch(from var(--brass) l c h / .2);
  }
  .pickup h3 {
    margin: 0 0 .55rem;
    font-family: var(--f-display); font-weight: 400;
    font-size: var(--step--1); letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--brass-lt);
  }

  .slots {
    display: flex; flex-wrap: wrap; gap: .4rem;
    margin-bottom: var(--sp-sm);
  }
  /* Was a 28px default button — the most-tapped control in the order flow
     and the smallest thing on the page. */
  .slot {
    min-height: 44px;
    display: inline-flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: .35rem .85rem;
    font-family: inherit; font-size: var(--step--1); font-weight: 600;
    color: var(--bone);
    background: oklch(from var(--bone) l c h / .06);
    border: 1px solid oklch(from var(--bone) l c h / .18);
    border-radius: var(--r-pill);
    cursor: pointer;
    transition: background .18s, border-color .18s, color .18s;
  }
  .slot:hover { border-color: var(--ember); }
  .slot[aria-pressed="true"] {
    background: var(--ember-fill);
    border-color: transparent;
    color: #fff;
  }
  .slot .brk {
    font-size: .62rem; font-weight: 600; letter-spacing: .07em;
    text-transform: uppercase; opacity: .74;
  }

  .who { display: grid; gap: .6rem; }
  @container (min-width: 24rem) { .who { grid-template-columns: 1fr 1fr; } }

  .consent {
    display: flex; align-items: flex-start; gap: .6rem;
    margin: var(--sp-sm) 0 0;
    padding: .7rem .8rem;
    background: oklch(from var(--bone) l c h / .045);
    border: 1px solid oklch(from var(--bone) l c h / .12);
    border-radius: var(--r-lg);
    cursor: pointer;
  }
  /* The box gets a comfortable target without inflating the disclosure
     text beside it — carriers require that text stay legible. */
  .consent input {
    flex: none; width: 22px; height: 22px; margin-top: .1rem;
    accent-color: var(--ember-fill);
  }
  .consent b { display: block; font-size: var(--step--1); font-weight: 700; }
  .consent small {
    display: block; margin-top: .2rem; font-size: .72rem;
    color: oklch(92% .018 78/.6); text-wrap: pretty;
  }

  /* ── footer ── */
  .cart-ft { flex: none; padding: var(--sp-sm) var(--sp-md) var(--sp-md); }
  .total {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--sp-sm);
    margin-bottom: .6rem;
    padding-top: .7rem;
    border-top: 1px solid oklch(from var(--brass) l c h / .24);
    font-family: var(--f-display);
    font-size: var(--step-2);
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--bone);
    font-variant-numeric: tabular-nums;
  }
  .checkout {
    width: 100%;
    background: var(--ember-fill); color: #fff;
    font-size: var(--step-0);
  }
  .checkout:hover { background: var(--ember-fill-hover); }
  .checkout:disabled { opacity: .6; cursor: progress; }
  .cart-note {
    margin: .5rem 0 0; text-align: center;
    font-size: .74rem; color: oklch(92% .018 78/.58);
  }

  /* ── the pickup code ── */
  .confirm {
    margin-top: var(--sp-sm);
    padding: var(--sp-sm);
    text-align: center;
    background: oklch(from var(--ok) l c h / .12);
    border: 1px solid oklch(from var(--ok) l c h / .4);
    border-radius: var(--r-lg);
  }
  .confirm-lbl { margin: 0; font-size: var(--step--1); color: var(--bone); }
  .confirm-code {
    margin: .2rem 0; font-family: var(--f-display);
    font-size: var(--step-3); letter-spacing: .16em;
    color: var(--bone);
  }
  .confirm-sub { margin: 0; font-size: .76rem; color: oklch(92% .018 78/.7); }

  /* ── desktop: a centred dialog rather than a sheet ── */
  @media (min-width: 40rem) {
    #cart {
      inset: 0;
      margin: auto;
      width: min(30rem, calc(100vw - 2rem));
      height: max-content;
      max-height: min(84dvh, 46rem);
    }
    .cart-panel {
      border: 1px solid oklch(from var(--brass) l c h / .34);
      border-radius: var(--r-lg);
      max-height: min(84dvh, 46rem);
    }
  }
}

/* The entrance the design direction specified and nothing implemented:
   "Popover + @starting-style — cart animates instead of snapping."
   `allow-discrete` is what lets the exit run too; without it the panel
   pops out of existence on close however the entry is written. */
@layer motion {
  #cart, #cart::backdrop {
    transition:
      opacity .3s ease,
      transform .3s cubic-bezier(.22, 1, .36, 1),
      overlay .3s allow-discrete,
      display .3s allow-discrete;
  }
  #cart { opacity: 0; transform: translateY(100%); }
  #cart:popover-open { opacity: 1; transform: translateY(0); }
  @starting-style { #cart:popover-open { opacity: 0; transform: translateY(100%); } }

  #cart::backdrop { opacity: 0; }
  #cart:popover-open::backdrop { opacity: 1; }
  @starting-style { #cart:popover-open::backdrop { opacity: 0; } }

  /* A dialog rises a short distance; only the phone sheet travels a full
     screen height. */
  @media (min-width: 40rem) {
    #cart { transform: translateY(14px) scale(.985); }
    #cart:popover-open { transform: none; }
    @starting-style { #cart:popover-open { opacity: 0; transform: translateY(14px) scale(.985); } }
  }

  @media (prefers-reduced-motion: reduce) {
    #cart, #cart::backdrop { transition: none; }
    #cart, #cart:popover-open { transform: none; }
  }
}

/* ═══ Tap targets and the notch ══════════════════════════════════════════
   Measured on the live build at 320px, 375px and 1280px. Unlayered, to sit
   with the .campus-rail rules above rather than fight them from inside a
   layer they already outrank. */

/* Footer links measured 15–18px tall: the phone number at 83x16, Privacy at
   40x15, the three campus links at 18px. The phone number is the single
   most important control on a food-trailer site and it was the smallest
   thing in the footer.

   Vertical padding on an INLINE element grows the hit region without
   changing the line box, so the footer keeps its exact rhythm while the
   targets roughly double. */
.foot a { padding-block: .75rem; }
.foot-btm a { padding-block: .8rem; }

/* The wordmark was an 84x28 target. The 60px nav row already has the
   height; the link just was not claiming it. */
.brand { min-height: 44px; }

/* viewport-fit=cover is set and the inline/bottom insets are handled in
   five places, but nothing owned the TOP inset. This is the first painted
   element on the page, so it is the one that has to clear the notch. */
.campus-rail { padding-top: calc(0.55rem + env(safe-area-inset-top, 0px)); }

/* The cart's name/mobile fields carry .fld too — but every .fld rule in
   this file is scoped to `.form .fld`, which is the booking form. So they
   matched nothing and rendered as raw white browser inputs sitting on a
   dark sheet. Same treatment, same tokens, applied where it was missing.
   The cart uses <span> for its labels rather than a nested <label>, since
   the whole field IS the label element. */
@layer components {
  .cart-panel .fld { display: flex; flex-direction: column; gap: .35rem; }
  .cart-panel .fld span {
    font-size: .7rem; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--brass-lt);
  }
  .cart-panel .fld input {
    width: 100%;
    border: 1.5px solid oklch(92% .018 78/.22);
    background: oklch(100% 0 0/.06);
    color: #fff;
    border-radius: 10px;
    padding: .8em .9em;
    font: inherit; font-size: var(--step--1);
    min-height: 44px;
  }
  .cart-panel .fld input::placeholder { color: oklch(92% .018 78/.4); }
  .cart-panel .fld input:focus-visible {
    border-color: var(--ember); outline: none;
    box-shadow: 0 0 0 3px oklch(from var(--ember) l c h/.25);
  }
  .cart-panel .fld input:user-invalid { border-color: var(--ember); }
}

/* The nav CTA measured 39px — the one button present on every screen of
   the site, and a pixel under the target everything else here holds. */
.order-btn { min-height: 44px; }

/* ═══ SECTION SEAMS & PLANES ══════════════════════════════════════════════
   Sections deliberately carry no fill — an opaque rectangle would block
   the ambient layer sitting behind the whole page (see the note on
   .on-dark above). The side effect was that nothing at all marked where
   one section ended and the next began: eight sections stacked on one
   uninterrupted navy field, separated only by empty space. At desktop that
   space was 176px, and with no join to read it as a boundary it looked
   like the page had run out rather than moved on.

   These are background LAYERS, not fills. Every stop is transparent-to-
   tinted, so the ambient orbs and grain still show through exactly as
   before — nothing is painted over.

   Four layers, back to front:
     1. a hairline that fades to nothing at both ends, so the join never
        reads as a hard box edge
     2. an ember bloom sitting on that line, giving the seam depth instead
        of leaving a drawn line on a flat field
     3. a fine diagonal hatch, reusing the hero's own 114deg pinstripe so
        the texture is the same material, just quieter
     4. a short vertical lift at the top of the section, so each one reads
        as a plane stacked on the one before rather than more of the same
        continuous surface */
@layer components {
  .sec {
    position: relative;
    background-image:
      linear-gradient(90deg,
        transparent 0%,
        oklch(from var(--brass) l c h / .26) 16%,
        oklch(from var(--ember) l c h / .55) 50%,
        oklch(from var(--brass) l c h / .26) 84%,
        transparent 100%),
      radial-gradient(62% 150px at 50% 0%,
        oklch(from var(--ember) l c h / .14),
        transparent 72%),
      repeating-linear-gradient(114deg,
        oklch(100% 0 0 / .022) 0 2px,
        transparent 2px 17px),
      linear-gradient(180deg,
        oklch(from var(--bone) l c h / .05) 0,
        transparent 220px);
    background-size: 100% 1px, 100% 150px, 100% 100%, 100% 220px;
    background-position: top center, top center, top center, top center;
    background-repeat: no-repeat;
  }

  /* The Method already runs its own brass bloom from the top-right and the
     hero's hatch above it — a second ember bloom on the same edge muddies
     both. It keeps the hairline and the lift, drops the bloom. */
  .sec.method {
    background-image:
      linear-gradient(90deg,
        transparent 0%,
        oklch(from var(--brass) l c h / .26) 16%,
        oklch(from var(--ember) l c h / .55) 50%,
        oklch(from var(--brass) l c h / .26) 84%,
        transparent 100%),
      linear-gradient(180deg,
        oklch(from var(--bone) l c h / .05) 0,
        transparent 220px);
    background-size: 100% 1px, 100% 220px;
    background-position: top center, top center;
    background-repeat: no-repeat;
  }

  /* The footer is the end of the page, not another plane in the stack, so
     it takes the seam without the lift — the eye should settle here. */
  .foot {
    background-image:
      linear-gradient(90deg,
        transparent 0%,
        oklch(from var(--brass) l c h / .3) 16%,
        oklch(from var(--ember) l c h / .6) 50%,
        oklch(from var(--brass) l c h / .3) 84%,
        transparent 100%),
      radial-gradient(70% 180px at 50% 0%,
        oklch(from var(--ember) l c h / .12),
        transparent 74%);
    background-size: 100% 1px, 100% 180px;
    background-position: top center, top center;
    background-repeat: no-repeat;
  }

  /* Section headings sat directly on the ground with nothing behind them.
     A soft bloom behind the heading block gives the top of each section a
     focal point and stops the eye sliding straight past the join into the
     body copy. */
  .sec-h {
    position: relative;
    width: fit-content;
    text-wrap: balance;
  }
  .sec-h::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -.45rem;
    width: clamp(2.6rem, 14%, 4.5rem);
    height: 3px;
    border-radius: 2px;
    background: linear-gradient(90deg, var(--ember), oklch(from var(--brass) l c h / .35));
  }
  /* Centred headings centre their rule too. .sec-h is width:fit-content so
     the rule can sit under the text rather than the column — which means a
     centred heading needs margin-inline:auto to stay centred. The howto
     section deliberately centres its heading against the left-aligned
     reading columns around it; fit-content alone knocked it back left. */
  .cta-panel .sec-h,
  .howto .sec-h { margin-inline: auto; }
  .cta-panel .sec-h::after,
  .howto .sec-h::after { left: 50%; transform: translateX(-50%); }

  /* The rule needs room that the old --sp-xs did not leave. */
  .sec-h { margin-bottom: calc(var(--sp-xs) + .5rem); }
}

/* A .btn label that contains inline markup is wrapped in a span so it
   stays a single flex item and wraps as ordinary text inside the button. */
.btn > span { display: inline; text-wrap: balance; }

/* ═══ DEPTH: PLANES THAT ACTUALLY STACK ═══════════════════════════════════
   Diagnosis, plainly: every grid on this site is
   `repeat(auto-fit, minmax(Xpx, 1fr))` — nine of them, the same pattern
   each time — every section is heading / sub / a row of equal-weight
   cards, all inside the same 1120px centred wrap, all on one continuous
   navy field. The seams added earlier marked the joins, but the page was
   still one flat surface from top to bottom. Nothing ever sat in front of
   anything else, and nothing broke the navy.

   Three devices, applied sparingly so they stay devices rather than
   becoming the new uniform. */
@layer components {

  /* ── 1. A recessed plate ──
     "How order-ahead works" drops into the page instead of sitting on it.
     Two inset shadows read as the panel being sunk below the surrounding
     surface, and a darker wash gives the eye somewhere to rest between the
     menu above and the Method below. This is the only recessed section on
     the page — a second one would flatten the effect back out. */
  .sec.howto {
    background-color: oklch(from var(--ink) calc(l - .015) c h / .55);
    box-shadow:
      inset 0 22px 44px -24px oklch(from var(--ink) calc(l - .06) c h / .95),
      inset 0 -22px 44px -24px oklch(from var(--ink) calc(l - .06) c h / .95);
  }

  /* ── 2. A card that breaks a boundary ──
     The order panel now lifts across the hero/order seam instead of
     sitting politely below it. One element crossing a join does more for
     perceived depth than any amount of shadow on elements that all sit on
     the same plane, because it is the only unambiguous cue that there ARE
     two planes.

     The lift is capped in rem rather than scaled off --sp-fluid, so it
     stays a deliberate overlap at every width instead of becoming a
     collision on a phone. */
  .cta-panel {
    position: relative;
    z-index: 2;
    /* Pull up by the section's OWN top padding plus a fixed overlap, so the
       panel crosses the seam by the same 1.75rem at every width. A flat
       negative margin does not work here: --sp-fluid changes with the
       viewport, so at desktop the padding simply absorbed the lift and the
       panel landed 12px BELOW the join it was meant to break. */
    margin-top: calc((var(--sp-fluid) + 1.75rem) * -1);
    box-shadow: var(--sh-warm-3);
  }

  /* ── 3. Asymmetry, where the content already earns it ──
     The Method's two-column block was 1fr 1fr — two equal columns of
     unequal content. The prose column carries three paragraphs and the
     aside carries a short callout, so equal columns left the aside
     stranded in space. This is the only asymmetric grid on the page and
     it is asymmetric because the content is. */
  @media (min-width: 49rem) {
    .two { grid-template-columns: 1.35fr .9fr; gap: var(--sp-lg); align-items: start; }
  }

  /* The limited item is the one card on the menu carrying scarcity, and it
     already spans two columns. Give it the elevation to match — it should
     read as the thing on the board, not as a wider sibling. */
  .item.feature { box-shadow: var(--sh-warm-3); }
}

/* Reduced motion does not disable layered depth — none of the above moves.
   But the overlap must not create a focus trap on a small screen, so the
   lift collapses if the panel would ever cross the nav. */
@media (max-width: 22rem) {
  .cta-panel { margin-top: 0; }
}

/* ═══ THREE ACCENTS ═══════════════════════════════════════════════════════
   Ported from CSS/SVG techniques in the HyperFrames catalog
   (hyperframes.dev/?view=catalog). That catalog builds VIDEO —
   compositions that render to MP4 — so its blocks cannot be installed
   here directly; these are hand-built equivalents of three techniques
   whose underlying mechanism (stroke-dasharray tracing, per-word cascade,
   a masked gradient sweep) is plain CSS and ports cleanly to a page.
   Used once each, on purpose — see motion-doctrine's ban on idle motion:
   these perform a specific moment, they do not run to fill time. */
@layer motion {

  /* ── 1. Per-word hero rise ("Per Word Rise" in the catalog) ──────────
     The h1 used to rise as one block. Now each word rises individually —
     blur-to-sharp, a short lift, staggered ~60ms apart — so the headline
     assembles itself instead of arriving all at once. The <em> ampersand
     carries class "w" too, so it's timed into the same sequence rather
     than popping in with the rest of the block. */
  .hero h1 .w {
    display: inline-block;
    opacity: 0;
    filter: blur(8px);
    transform: translateY(16px);
    animation: word-rise .7s cubic-bezier(.16, 1, .3, 1) both;
  }
  .hero h1 .w:nth-child(1) { animation-delay: .16s; }
  .hero h1 .w:nth-child(2) { animation-delay: .225s; }
  .hero h1 .w:nth-child(3) { animation-delay: .29s; }
  .hero h1 .w:nth-child(4) { animation-delay: .355s; }
  .hero h1 .w:nth-child(5) { animation-delay: .42s; }
  @keyframes word-rise {
    from { opacity: 0; filter: blur(8px); transform: translateY(16px); }
    to   { opacity: 1; filter: blur(0);   transform: none; }
  }
  /* The rest of the hero sequence shifts to land after the last word
     settles, rather than arriving underneath it. Same increments as
     before (~.12s apart), just re-based off the longer opening beat. */
  .hero-in > .lede       { animation-delay: .52s; }
  .hero-in > .btns       { animation-delay: .64s; }
  .hero-in > .hero-foot  { animation-delay: .76s; }

  /* ── 2. Shimmer sweep on the Smoke Burger's scarcity pill
     ("Shimmer Sweep" in the catalog — "ideal for AI accents and premium
     reveals") ──
     One pass, not a loop: _docs/DESIGN-DIRECTION.md's own motion law is
     "nothing loops except the status pulse", so this fires once when the
     card enters view, and again on hover/focus — a user action re-firing
     it is causal, not idle, which is the distinction the law is drawing. */
  .tag-limited { position: relative; overflow: hidden; }
  .tag-limited::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(115deg,
      transparent 35%,
      oklch(100% 0 0 / .6) 50%,
      transparent 65%);
    background-size: 250% 100%;
    background-position: 150% 0;
    pointer-events: none;
  }
  @keyframes shimmer-sweep {
    from { background-position: 150% 0; }
    to   { background-position: -50% 0; }
  }
  @supports (animation-timeline: view()) {
    .tag-limited::after {
      animation: shimmer-sweep 1.1s ease-in-out both;
      animation-timeline: view();
      animation-range: entry 40% entry 90%;
    }
  }
  .item.feature:hover .tag-limited::after,
  .item.feature:focus-within .tag-limited::after {
    animation: shimmer-sweep .9s ease-in-out;
  }

  /* ── 3. Flame-trace eyebrow mark ("SVG Stroke Trace" in the catalog) ──
     A hand-authored flame-lick path replaces the flat 2px dash that used
     to sit before "Delta, Colorado" and "The Method". Purely a
     typographic flourish, NOT the brand mark — the trailer has no logo
     yet (see _docs/LOGO-BRIEF.md); this never appears anywhere a real
     mark would, and is not a candidate to become one.

     Base state is fully drawn (dashoffset:0) — a safe, correct look on
     any browser with no animation support at all. Hero draws it in on
     load, gated to nothing (a plain CSS animation always runs). The
     Method's copy is scroll-gated behind @supports, so a browser without
     scroll-timeline support just sees it pre-drawn rather than a
     permanently-undrawn squiggle. */
  .eyebrow--flame::before { content: none; }
  .flame-mark { flex: none; overflow: visible; }
  .flame-mark path {
    fill: none;
    stroke: var(--ember);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 42;
    stroke-dashoffset: 0;
  }
  @keyframes flame-trace {
    from { stroke-dashoffset: 42; }
    to   { stroke-dashoffset: 0; }
  }
  .hero .flame-mark path {
    animation: flame-trace .6s ease-out .1s both;
  }
  @supports (animation-timeline: view()) {
    .method .flame-mark path {
      stroke-dashoffset: 42;
      animation: flame-trace .6s ease-out both;
      animation-timeline: view();
      animation-range: entry 10% entry 60%;
    }
  }
}
