/* Web Templates/Smoke Shops/styles.css
   FlowerBomb DC — smoke shop / curbside pickup storefront.
   Rebrand from the :root token block; nothing below it hardcodes a colour.
   Contrast: every text/background pair is verified >= 4.5:1 (ratios noted inline).

   Derived from the weedzdc.com teardown in reference.md — same skeleton
   (offer bar → hero+zip → trust → tiers → collections → how-to-order → proof),
   deliberately different bones: dark-first, condensed display, 4px surfaces. */

/* ============================================================
   1. TOKENS  — the only block you edit to rebrand
   ============================================================ */
:root {
  /* Colour — "black & FlowerBomb yellow".
     Dark-first because this category is browsed at night, on a phone, in bed.
     The accent is the yellow off the mascot's fuse spark, sampled from the
     logo artwork (media/brand/flowerbomb-wordmark.png) rather than picked by
     eye: #F8D840 is the dominant tone in that flare in both logo lockups.

     ── The one thing to understand before editing these ──
     The accent flipped from a mid purple to a bright yellow, which inverts
     how it can be used. Purple took WHITE text on top (#6941c6 + white was
     6.62:1). Yellow cannot: white on #EFC41E measures 1.67:1, which is
     effectively invisible. Every solid accent fill therefore takes
     --on-ember (near-black) instead, at 11.78:1.

     If you ever re-theme to a dark accent again, --on-ember goes back to
     #fff and nothing else has to change — that is the whole reason it is a
     token rather than a literal repeated across seven rules.

     Measured against the real surfaces (see the ratios inline):            */
  --void:     #0b0b0d;   /* page base */
  --slab:     #141418;   /* raised surface — cards, drawer, header */
  --slab-2:   #1b1b21;   /* hover / inset */
  --edge:     #262630;   /* hairlines */
  --chalk:    #f4f2ee;   /* primary text          16.1:1 on void */
  --ash:      #9b9aa4;   /* muted text             7.1:1 on void */
  --ember:    #F8D840;   /* PRIMARY ACCENT — fuse-spark yellow  13.9:1 on void */
  --ember-lo: #EFC41E;   /* solid fill, takes --on-ember text   11.8:1 on void */
  --on-ember: #0b0b0d;   /* text/icons ON an accent fill        11.8:1 on ember-lo */
  /* Deepened from #e8b44a to a bronze. Against a yellow accent the old amber
     was 1.16:1 apart from --ember — near enough to read as the same colour,
     which collapsed the distinction between "this is the CTA" and "this is a
     rating". At #C8912A the two sit 1.97:1 apart and the hierarchy holds. */
  --gold:     #C8912A;   /* tier marks, star ratings, badges     7.1:1 on void */
  --live:     #45d67f;   /* status: open / in stock             10.5:1 on void */
  /* Pushed orange for the same reason: #ffb020 was a yellow-amber sitting
     1.1:1 from the new accent, so warnings stopped looking like warnings. */
  --warn:     #FF8A3D;   /* age gate denial, compliance notes    8.4:1 on void */
  /* Product-photo plate. Warm off-white rather than pure #fff so a packshot's
     white background can blend into it via mix-blend-mode without a visible
     seam, and so the tiles don't glare against the near-black page. */
  --plate:    #ece9e2;
  --hero-bg:  hsl(260 91% 58%);
  --hero-ink: #0b0b0d;

  /* Accent discipline: --ember is spent on the primary CTA, the cart badge,
     and price figures — and nothing else. Prices were added deliberately (see
     the price rule in §9); the point of the rule survives, which is that the
     accent never lands on decoration. Put it on a border, a rule, or a section
     heading and it stops pulling the eye to the things that make money. */

  /* Type — Anton (display) / Inter (body and labels).
     Anton rather than the source's Zilla Slab: a heavy condensed face sets
     enormous on a 375px screen and still fits, which is most of what produces
     the "designed" feel.

     Inter carries all labels, prices, chips and buttons for a clean, readable
     system. Its compact forms stay legible at the small UI sizes used here. */
  --f-display: 'Anton', 'Oswald', Impact, sans-serif;
  --f-body:    'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --f-label:   'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --f-h1:      var(--f-display);
  --f-h2:      var(--f-display);
  --f-h3:      var(--f-display);

  --t-hero:  clamp(2.9rem, 8.5vw, 7rem);
  --t-h2:    clamp(2rem, 4.6vw, 3.6rem);
  --t-h3:    clamp(1.15rem, 2.1vw, 1.5rem);
  --t-body:  1.0625rem;   /* 17px */
  --t-small: 0.9375rem;
  --t-label: 0.72rem;

  /* Anton sits ~.02em right of its box at display sizes — pull it back so
     hero text optically aligns with the gutter. */
  --bearing:     .02em;
  --track-label: .055em;

  /* Space */
  --content:   1320px;
  --narrow:    64ch;
  --gutter:    clamp(1.1rem, 4vw, 3rem);
  --section-y: clamp(4.5rem, 8.5vw, 8rem);

  /* Geometry — one 6px radius on surfaces AND controls.
     Previously 4px surfaces against 999px pill controls. Unifying on 6px
     trades a little of the "printed packaging" hardness for a calmer,
     more contemporary grid: with pills gone, every edge in a card — media,
     card, add button, badge — shares one corner, so the eye stops counting
     shapes. Still well short of the source site's 12px-on-everything, which
     is the value that makes a store read as a stock theme.
     --radius-pill survives for the two things that are genuinely round. */
  --radius:      6px;   /* surfaces — cards, panels, drawer, media */
  --radius-ctl:  6px;   /* controls — buttons, chips, inputs, tags, qty */
  --radius-pill: 999px; /* only the cart count badge and the progress meter */
  --shadow:      0 .4rem 1.6rem rgba(0,0,0,.45);

  /* --header-h is the header's INNER min-height. --header-stack adds the
     1px bottom border, and is what anything sticking or scroll-margining
     below the header must use — pinning to --header-h alone leaves a
     hairline gap that page content scrolls through.
     The inner height drops on small phones (see the 520px block), so both
     tokens are re-declared there rather than hard-coded at the use site,
     which is how .shopbar came to sit 5px below the header on a phone. */
  --header-h: 64px;
  --header-stack: calc(var(--header-h) + 1px);
  --bar-h:    40px;   /* announcement bar */

  /* z-index scale — declared, not improvised */
  --z-sticky: 20; --z-header: 30; --z-actionbar: 40;
  --z-drawer: 60; --z-gate: 90;
}

/* ============================================================
   2. RESET / BASE
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  font-family: var(--f-body);
  font-size: var(--t-body);
  line-height: 1.6;
  color: var(--chalk);
  background: var(--void);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  isolation: isolate;
}
body.is-locked, body.is-product-open, body.is-savings-open { overflow: hidden; }

/* Small viewports get a little less peripheral motion and no accidental
   horizontal canvas. The product rails keep their deliberate, local swipe.
   `overflow-x: clip` avoids creating a second scrolling surface. */
@media (max-width: 699px) {
  html, body { overflow-x: clip; }
  :root { --gutter: 1rem; }
}
/* Matches .header__inner's own drop at this width — see the token block. */
@media (max-width: 520px) { :root { --header-h: 58px; } }


/* A light palette for the live style-preview toggle. All component colours
   inherit these tokens, so the entire interface switches as one system.

   ⚠️ UNREACHABLE, AND NOT SAFE TO RE-ENABLE AS-IS.
   The only thing that adds `.theme-light` is applyMode() inside the
   styleControls IIFE in app.js, which returns before it ever runs — the
   authoring controls were deliberately taken off the storefront. That
   matters here because this block deliberately does NOT override the accent:
   under a dark accent it did not need to, but --ember is now a bright yellow
   and #F8D840 on #f6f4ef measures about 1.2:1 — every accent mark, price and
   eyebrow would vanish.
   If the toggle is ever restored, this block has to redefine --ember,
   --ember-lo and --on-ember for a light ground (a much deeper amber with
   white or near-black text, re-measured) before it is switched on.          */
body.theme-light {
  --void: #f6f4ef;
  --slab: #ffffff;
  --slab-2: #eeece7;
  --edge: #d8d4cc;
  --chalk: #171719;
  --ash: #65636a;
  --plate: #f1eee8;
  --live: #16824c;
}
body.theme-light .header { background: rgba(246,244,239,.92); }
body.theme-light .actionbar { background: rgba(246,244,239,.94); }

img, svg, video { display: block; max-width: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; }

/* Low-contrast falling assets sit behind the site content; no interaction,
   no semantic content, and no impact when motion is reduced. */
.ambient-products { position: fixed; z-index: -1; inset: 0; overflow: hidden; pointer-events: none; transform: translate3d(0, var(--ambient-parallax, 0px), 0); }
.ambient-products__item { position: absolute; width: clamp(9rem, 18vw, 20rem); opacity: .1; mix-blend-mode: soft-light; filter: saturate(1.1); will-change: transform; animation: ambient-fall 32s linear infinite; }
.ambient-products__rope { left: -5%; top: -30%; width: clamp(14rem, 28vw, 31rem); animation-duration: 46s; animation-delay: -12s; }
.ambient-products__nug { right: 5%; top: -18%; animation-duration: 35s; animation-delay: -6s; }
.ambient-products__joint { left: 17%; top: -42%; width: clamp(8rem, 14vw, 15rem); animation-duration: 42s; animation-delay: -23s; }
.ambient-products__vape { right: -3%; top: -38%; width: clamp(8rem, 15vw, 16rem); animation-duration: 39s; animation-delay: -17s; }
@keyframes ambient-fall { from { transform: translate3d(-1rem, -4vh, 0) rotate(-14deg); } to { transform: translate3d(2rem, 128vh, 0) rotate(21deg); } }
@media (max-width: 699px) { .ambient-products__item { opacity: .07; } .ambient-products__rope { left: -18%; } .ambient-products__joint { left: -4%; } }

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--ember-lo); color: var(--on-ember); padding: .8rem 1.2rem;
  font-family: var(--f-label); font-size: var(--t-label);
  text-transform: uppercase; letter-spacing: var(--track-label);
}
.skip:focus { left: 0; }

.sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ============================================================
   3. PRIMITIVES
   ============================================================ */
.wrap { width: min(100% - (var(--gutter) * 2), var(--content)); margin-inline: auto; }

/* Desktop uses the full available canvas. The shared edge is padding—not a
   centered max-width—so the header, hero, and every following section align. */
@media (min-width: 1200px) {
  .wrap { width: 100%; max-width: none; margin-inline: 0; padding-inline: var(--gutter); }
}

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(3rem, 5.5vw, 4.75rem); }
.section--dark { background: var(--slab); }
.section--flush { padding: 0; }

/* FAQ hero — a restrained dark wash keeps the header copy legible. */
.faq-hero {
  background-image:
    linear-gradient(90deg, rgba(11,11,13,.78) 0%, rgba(11,11,13,.58) 52%, rgba(11,11,13,.48) 100%),
    url("media/brand/flowerbomb-lounge-1400.webp");
  background-size: cover;
  background-position: center 15%;
  border-bottom: 1px solid var(--edge);
  /* A centered 40% slice of the 16:9 source: 30% is cropped from both its
     top and bottom while the image keeps its native proportions. */
  aspect-ratio: 40 / 9;
}
.faq-hero .section { min-height: inherit; display: flex; flex-direction: column; justify-content: center; }
.faq-hero .lede { max-width: 48rem; color: var(--chalk); }
@media (max-width: 700px) {
  .faq-hero { aspect-ratio: auto; min-height: 36rem; background-position: 60% center; background-image: linear-gradient(rgba(11,11,13,.7), rgba(11,11,13,.76)), url("media/brand/flowerbomb-lounge-1400.webp"); }
}

/* Events and referral pages. */
.events-hero, .referral-hero {
  background-size: cover; background-position: center;
  border-bottom: 1px solid var(--edge);
}
.events-hero {
  background-image: linear-gradient(90deg, rgba(11,11,13,.88), rgba(11,11,13,.42)), url("media/brand/flowerbomb-lounge-1400.webp");
}
.referral-hero {
  background-image: linear-gradient(90deg, rgba(11,11,13,.9), rgba(11,11,13,.4)), url("media/ylohero.webp");
}
.events-hero .lede, .referral-hero .lede { max-width: 48rem; color: var(--chalk); margin-top: 1rem; }
.referral-hero .btn { margin-top: 1.5rem; }
.event-grid, .referral-grid { display: grid; gap: 1rem; grid-template-columns: repeat(3, 1fr); }
.event-card, .referral-grid article { padding: clamp(1.2rem, 3vw, 1.8rem); background: var(--slab); border: 1px solid var(--edge); border-radius: var(--radius); }
.event-card { display: flex; flex-direction: column; align-items: flex-start; gap: .85rem; }
.event-card h3, .referral-grid h3 { font-size: 1.3rem; }
.event-card p, .referral-grid p { color: var(--ash); font-size: var(--t-small); }
.event-card__date, .event-card__place, .referral-grid article > span { font-family: var(--f-label); font-size: .65rem; font-weight: 700; letter-spacing: var(--track-label); text-transform: uppercase; }
.event-card__date, .referral-grid article > span { color: var(--ember); }
.event-card__place { color: var(--chalk) !important; margin-top: auto; }
.referral-grid article > span { display: block; margin-bottom: 1rem; }
@media (max-width: 760px) { .event-grid, .referral-grid { grid-template-columns: 1fr; } }

/* One continuous image field spans the Shop heading and its filter controls. */
.shop-header {
  background-image:
    linear-gradient(90deg, rgba(11,11,13,.78) 0%, rgba(11,11,13,.58) 52%, rgba(11,11,13,.48) 100%),
    url("media/ylohero.webp");
  background-size: cover;
  background-position: center 65%;
  border-bottom: 1px solid var(--edge);
}
.shop-hero .lede { max-width: 48rem; color: var(--chalk); }
@media (max-width: 700px) {
  .shop-header { background-position: 60% center; background-image: linear-gradient(rgba(11,11,13,.7), rgba(11,11,13,.76)), url("media/ylohero.webp"); }
}

/* The drop-list band closes the page. Asset swapped twice now: a grey-bowl
   ashtray, then an orange one, and now `img/bg/yloash.webp` — the branded
   FlowerBomb ashtray shot flat on the yellow field, which is the same
   backdrop the category heroes use.

   The scrim value survived the swap but the binding constraint moved, so
   the numbers below are not the old ones re-labelled. On the dark photos
   the tightest element was the eyebrow (small ember text). On a bright,
   near-uniform yellow field the eyebrow has enormous headroom (10.6:1) and
   the limit is now the --ash body copy, whose luminance sits much closer to
   a lightly-scrimmed yellow. Same method as `.steps`: sweep the mid-stop
   against the real rendered pixels behind each real element.

     mid-stop   body ratio (--ash, the binding constraint now)
     .78        4.27   FAILS
     .82        4.80   passes, but only 0.30 over the floor
     .85        5.27   shipped — 0.77 margin, eyebrow 10.6, h2 12.8
     .91        6.11   darker than it needs to be; buries the artwork

   Note the direction of the trade here: lightening the scrim to show off
   the branded ashtray is the obvious instinct and it is not available.
   .78 is the first stop where the artwork really reads, and it fails. */
.section--photo {
  position: relative; isolation: isolate;
  background-image:
    linear-gradient(180deg, rgba(11,11,13,.89) 0%, rgba(11,11,13,.85) 50%,
                            rgba(11,11,13,.89) 100%),
    image-set(url("img/bg/yloash.webp") 1x);
  background-size: cover;
  background-position: 50% 45%;
  background-repeat: no-repeat;
  border-block: 1px solid var(--edge);
}
@media (max-width: 700px) {
  /* More headroom again at the -900 crop; same margin discipline. */
  .section--photo { background-image:
    linear-gradient(180deg, rgba(11,11,13,.87) 0%, rgba(11,11,13,.84) 50%,
                            rgba(11,11,13,.87) 100%),
    image-set(url("img/bg/yloash-900.webp") 1x); }
}

/* Eyebrow → heading. Every section opens with a two-or-three-word mono eyebrow
   so the headline never has to explain itself. */
.eyebrow {
  font-family: var(--f-label); font-size: var(--t-label); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label);
  color: var(--ash); display: block; margin-bottom: .7rem;
}
.eyebrow--accent { color: var(--ember); }

h1, h2, h3 { font-weight: 400; line-height: .95;
             text-transform: uppercase; letter-spacing: .005em; }
/* Headings share the same left edge as their eyebrow and body copy. This is
   more important than compensating for a tiny font-side bearing: on desktop
   the old negative margin made the hero and every section heading drift left
   of the shared content grid. */
h1 { font-family: var(--f-h1); font-size: var(--t-hero); margin-left: 0; }
h2 { font-family: var(--f-h2); font-size: var(--t-h2);   margin-left: 0; }
h3 { font-family: var(--f-h3); font-size: var(--t-h3); line-height: 1.1; }

.lede { color: var(--ash); max-width: var(--narrow); font-size: 1.09rem; }

.head { margin-bottom: clamp(2.1rem, 3.8vw, 3.2rem); }
.head--row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1.5rem; flex-wrap: wrap;
}

/* --- Buttons --------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font-family: var(--f-label); font-size: var(--t-label); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label);
  padding: 1rem 1.85rem; border-radius: var(--radius-ctl);
  border: 1px solid transparent; text-decoration: none; cursor: pointer;
  transition: background .18s ease, border-color .18s ease, color .18s ease,
              transform .18s ease;
}
/* --ember-lo is the solid Drop DC purple used beneath white button text. */
.btn--primary { background: var(--ember-lo); color: var(--on-ember); }
.btn--primary:hover { background: var(--ember); color: var(--on-ember); transform: translateY(-1px); }
.btn--ghost { background: transparent; color: var(--chalk); border-color: #fff; }
.btn--ghost:hover,
.btn--ghost:focus-visible { border-color: var(--ember); color: var(--ember); }
.btn--outline-white { border-color: #fff; }
.btn--block { width: 100%; }
.btn--sm { padding: .65rem 1.2rem; font-size: .66rem; }
/* The roomier desktop padding tips the hero's two CTAs onto separate rows at
   375px, which shoves the ZIP check — the highest-value thing in the hero —
   below the fold. Pull the horizontal padding back on small screens so the
   pair sits on one line again. */
@media (max-width: 480px) { .btn { padding: .9rem 1.25rem; } }
@media (prefers-reduced-motion: reduce) { .btn:hover { transform: none; } }

/* --- 3a. FIGURES ------------------------------------------------
   A monospace aligned every numeral for free. A proportional sans does not:
   in a product grid, "$88.00" and "$125.00" stop sharing a decimal column,
   and a per-second countdown visibly jitters as 1s and 0s swap width.

   Syne is unusually bad about this: its `1` is barely half the width of its
   `0`. Measured at 40px/700, "111111" sets 93.6px proportional against
   177.1px for "000000" — an 84px swing inside six glyphs. Left alone, a price
   column would look broken.

   Syne does ship `tnum`, and it fully equalises them (both 169.9px). Test any
   replacement face the same way before trusting it — a display face that
   silently lacks the feature will fail this quietly, at which point the
   #timer-val reservation below is the only thing still holding. */
.card__price, .card__was, .tier__from, .price-from,
.line__price, .qty span, .drawer__total, .timer, .progress__txt,
.trust__val, .result-count, .card__meta {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
/* The countdown is the only figure that rewrites itself every second, so it is
   the only one where width instability is actually visible. Reserve the box. */
#timer-val {
  display: inline-block; min-width: 4.4ch; text-align: left;
}

/* --- Tags / pills ---------------------------------------------- */
.tag {
  display: inline-flex; align-items: center; gap: .35rem;
  font-family: var(--f-label); font-size: .66rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label);
  padding: .3rem .7rem; border-radius: var(--radius-ctl);
  background: var(--slab-2); color: var(--ash); border: 1px solid var(--edge);
}
.tag--gold { color: var(--gold); border-color: rgba(232,180,74,.35); }
.tag--live { color: var(--live); border-color: rgba(69,214,127,.35); }
.tag--sale { background: var(--ember-lo); color: var(--on-ember); border-color: transparent; }
.tag--sold-out { background: #a32121; color: #fff; border-color: transparent; }
.account-icon,
[data-open="drawer"],
a[href="referrals.html"],
.drawer,
#checkout { display: none !important; }
.telegram-header svg { width: 1.15rem; height: 1.15rem; fill: currentColor; stroke: none; }
.menu-action { border-color: var(--ember); color: var(--ember); }
.telegram-action { background: #229ed9; border-color: #229ed9; color: #fff; }
.telegram-action:hover { background: #1d8bc2; border-color: #1d8bc2; }
#telegram-chat-button {
  position: fixed; right: clamp(1rem, 3vw, 2rem); bottom: clamp(1rem, 3vw, 2rem); z-index: 80;
  display: inline-flex; align-items: center; gap: .55rem; min-height: 3rem; padding: .65rem 1rem;
  border-radius: var(--radius-ctl); background: #229ed9; color: #fff;
  box-shadow: 0 8px 24px rgba(0,0,0,.3); font-family: var(--f-label); font-size: .68rem;
  font-weight: 700; letter-spacing: .04em; text-decoration: none; text-transform: uppercase;
}
#telegram-chat-button:hover { background: #1d8bc2; transform: translateY(-1px); }
#telegram-chat-button:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
#telegram-chat-button svg { width: 1.15rem; height: 1.15rem; fill: currentColor; }
@media (max-width: 499px) { #telegram-chat-button span { display: none; } #telegram-chat-button { padding: .85rem; } }
@media (max-width: 999px) { #telegram-chat-button { display: none; } }
@media (min-width: 1000px) {
  #telegram-chat-button {
    right: calc(clamp(1rem, 2.2vw, 2rem) + 5rem);
    bottom: 1.25rem;
  }
}

/* --- Reveal on scroll ------------------------------------------
   Position check, not a bare IntersectionObserver: on an anchor-nav page a
   jump to #shop moves earlier sections from below the viewport to above it
   with no threshold crossing, and they stay invisible forever. Guarded here
   plus a 4s timeout failsafe in app.js. Content loss > missed animation. */
.reveal { opacity: 0; transform: translateY(14px);
          transition: opacity .5s ease, transform .5s ease; }
.reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}

/* ============================================================
   4. AGE GATE  — blocking, focus-trapped, remembered for the browser session
   The source site ships age-gate styles but renders no gate. In this
   category that is the most serious defect available: a compliance
   exposure and an ad-platform disqualifier, fixed in ~40 lines.
   ============================================================ */
.gate {
  position: fixed; inset: 0; z-index: var(--z-gate);
  background: var(--void);
  display: grid; place-items: center; padding: var(--gutter);
}
.gate[hidden] { display: none; }
/* Legacy markup is present in every static page as a no-JS fallback. Keep it
   out of sight while the entry-pass session check runs, so navigating with a
   valid pass never flashes the retired yes/no age panel. */
.gate:not(.is-ready) .gate__panel { display: none; }
.gate__panel {
  max-width: 30rem; text-align: center;
  border: 1px solid var(--edge); background: var(--slab);
  border-radius: var(--radius); padding: clamp(1.8rem, 5vw, 3rem);
}
.gate__panel h2 { font-size: 1.6rem; margin-bottom: .8rem; }
.gate__panel p { color: var(--ash); font-size: var(--t-small); margin-bottom: 1.4rem; }
/* After .gate__panel p — same specificity class, so source order decides. */
.gate__panel .gate__mark {
  font-family: var(--f-display); font-size: 3.4rem; line-height: 1;
  color: var(--ember); margin-bottom: .4rem;
}
.gate__actions { display: grid; gap: .7rem; }
.gate__form { display: grid; gap: .85rem; text-align: left; }
.gate__form .field__label { margin-bottom: -.35rem; }
.gate__form input[type="email"],
.gate__form input[type="text"] {
  width: 100%; background: var(--void); border: 1px solid var(--edge);
  border-radius: var(--radius-ctl); padding: .85rem 1rem; color: var(--chalk);
}
.gate__form input:focus-visible { outline: 2px solid var(--ember); outline-offset: 2px; }
.gate__form .check { font-size: .8rem; }
.gate__error { min-height: 1.25em; margin: 0 !important; color: var(--warn) !important; text-align: center; }
.gate__back { appearance: none; border: 0; background: transparent; color: var(--ash); text-decoration: underline; cursor: pointer; font: inherit; font-size: .82rem; }
.gate__back:hover { color: var(--chalk); }
.gate__fine {
  margin-top: 1.4rem; font-size: .78rem; color: var(--ash);
  border-top: 1px solid var(--edge); padding-top: 1rem;
}
.gate__deny {
  display: none; margin-top: 1rem; color: var(--warn);
  font-family: var(--f-label); font-size: var(--t-label);
  text-transform: uppercase; letter-spacing: var(--track-label);
}
.gate.is-denied .gate__deny { display: block; }
.gate.is-denied .gate__actions { display: none; }

/* ============================================================
   5. ANNOUNCEMENT BAR + HEADER
   ============================================================ */
/* PROMO MARQUEE.
   Note this reverses the "no marquee" rule in reference.md §5 — deliberately,
   and only here. That rule is about the source site's TRUST ticker: one static
   claim ("650+ reviews") set moving purely for decoration, which buys nothing
   and costs reduced-motion compliance. This bar is a different job: several
   live promos, each its own link, more than fit on one line. Motion is what
   makes the second and third offer reachable at all.

   Speed is constant in px/sec, not a fixed duration — a fixed duration makes
   the bar accelerate every time the merchant adds a message, which is exactly
   the setting they'll touch most. app.js measures the track and derives the
   duration from --bar-speed. */
.bar {
  background: var(--ember-lo); color: var(--on-ember);
  min-height: var(--bar-h);
  display: flex; align-items: center;
  overflow: hidden;
  --bar-speed: 55;          /* px per second */
  --bar-dur: 40s;           /* replaced by app.js once measured */
}
.bar__viewport { display: flex; width: 100%; overflow: hidden; }

/* Two identical tracks sit inside ONE animated wrapper which translates
   -50% of itself. Because the wrapper holds exactly two identical copies,
   -50% is exactly one copy's width, so when copy 1 has fully exited, copy 2
   sits precisely where copy 1 began and the restart is invisible.

   The animation must stay on the wrapper. It was previously on each track,
   with each translating -100% of its own width — which is geometrically
   equivalent but only while both animations share a phase, and they did
   not: the original starts at first paint, the clone starts when app.js
   appends it. The two drifted apart and the promo text overlapped itself.
   One element cannot fall out of step with itself.

   The clone is aria-hidden so the promo list isn't announced twice. */
.bar__run {
  display: flex; flex: 0 0 auto;
  animation: bar-scroll var(--bar-dur) linear infinite;
  will-change: transform;
}
.bar__track {
  display: flex; align-items: center; flex: 0 0 auto;
  margin: 0; padding: 0; list-style: none;
}
@keyframes bar-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
/* Pause on hover so a reader can finish a line, and on focus-within so a
   keyboard user can actually reach and read the links inside. */
.bar:hover .bar__run,
.bar:focus-within .bar__run { animation-play-state: paused; }

.bar__item {
  flex: none; display: inline-flex; align-items: center;
  padding: .55rem 0; margin-right: 2.6rem;
  font-family: var(--f-label); font-size: var(--t-label); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label);
  white-space: nowrap;
}
/* Separator drawn after the text rather than between items, so it survives
   the wrap-around join without doubling up or dropping out. */
.bar__item::after {
  content: ""; width: 5px; height: 5px; margin-left: 2.6rem;
  background: currentColor; opacity: .5; transform: rotate(45deg);
}
/* The bar's fill is --ember-lo, set on .bar above, but this rule is detached
   from it — so it survived the accent flip as white-on-yellow (1.67:1) and
   only showed up when the rendered pixels were measured rather than the
   tokens. Inherit the bar's own text colour instead of restating one, and it
   cannot drift out of sync again. */
.bar a { color: inherit; text-underline-offset: 3px; }
.bar a:hover { text-decoration-thickness: 2px; }

/* Reduced motion: stop entirely and hand the user a scrollable strip instead.
   Freezing the animation alone would strand every message after the first
   off-screen — the promos still have to be reachable. */
@media (prefers-reduced-motion: reduce) {
  .bar__viewport { overflow-x: auto; scrollbar-width: none; }
  .bar__viewport::-webkit-scrollbar { display: none; }
  /* Both selectors on purpose. The animation moved from .bar__track to the
     .bar__run wrapper when the two-element phase bug was fixed; app.js also
     skips building the wrapper entirely under reduced motion, so in practice
     only one of these ever matches. Naming both means neither a stale run
     left over from a motion-preference toggle nor a future move of the
     animation can quietly start it running for someone who asked it not to. */
  .bar__run, .bar__track { animation: none; }
  .bar__run { transform: none; }
  .bar__track[aria-hidden="true"] { display: none; }
  .bar__item:last-child::after { display: none; }
}

.header {
  position: sticky; top: 0; z-index: var(--z-header);
  background: rgba(11,11,13,.92); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--edge);
}
.header__inner {
  min-height: var(--header-h);
  display: flex; align-items: center; gap: 1.2rem;
}

/* Top-of-page visual controls — intentionally compact so they are useful for
   exploring a direction without competing with the shopping UI. */
.style-controls { background: var(--slab); border-bottom: 1px solid var(--edge); }
.style-controls__inner { min-height: 2.75rem; display: flex; align-items: center; justify-content: flex-end; gap: .65rem; flex-wrap: wrap; padding-block: .35rem; }
.style-controls label { display: flex; align-items: center; gap: .55rem; color: var(--ash); font-family: var(--f-label); font-size: .62rem; font-weight: 700; letter-spacing: var(--track-label); text-transform: uppercase; }
.style-controls input[type="range"] { width: min(18vw, 9rem); accent-color: var(--ember); cursor: ew-resize; }
.style-controls__swatch { width: .8rem; height: .8rem; border-radius: 50%; background: var(--ember); border: 1px solid var(--edge); }
.style-controls__hex { min-width: 4.7rem; color: var(--chalk); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .7rem; letter-spacing: .03em; }
.style-controls button { min-width: 5.3rem; min-height: 1.8rem; padding: .25rem .5rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); color: var(--chalk); font-family: var(--f-label); font-size: .62rem; font-weight: 700; letter-spacing: .04em; cursor: pointer; }
.style-controls__type { display: flex; align-items: center; gap: .45rem; }
.style-controls select { min-height: 1.8rem; max-width: 8.7rem; padding: .2rem .4rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); color: var(--chalk); font-family: var(--f-label); font-size: .62rem; }
.style-controls__type button { min-width: 4.9rem; }
.style-controls__export { min-width: 7.9rem !important; }
.style-controls button[aria-pressed="true"] { border-color: var(--ember); background: color-mix(in srgb, var(--ember) 18%, var(--void)); color: var(--ember); }
.style-controls button:hover { border-color: var(--ember); color: var(--ember); }
@media (max-width: 520px) {
  .style-controls__inner { min-height: 2.35rem; justify-content: center; flex-wrap: wrap; gap: .35rem; }
  .style-controls label { font-size: 0; gap: 0; }
  .style-controls input[type="range"] { width: 4.35rem; }
  .style-controls__swatch { width: .65rem; height: .65rem; flex: 0 0 auto; }
  .style-controls__hex { min-width: 3.9rem; font-size: .6rem; }
  .style-controls button { min-width: 0; padding: .24rem .34rem; font-size: .54rem; }
  .style-controls__type { width: 100%; justify-content: center; }
  .style-controls select { max-width: 6.8rem; font-size: .54rem; }
}

/* Local page editor — available from the visual-controls bar. */
body.is-page-editing { padding-right: min(25rem, 34vw); }
body.is-page-editing main section, body.is-page-editing > footer { cursor: crosshair; }
body.is-page-editing main section:hover, body.is-page-editing > footer:hover { outline: 2px dashed color-mix(in srgb, var(--ember) 76%, transparent); outline-offset: -4px; }
.is-editor-selected { outline: 3px solid var(--ember) !important; outline-offset: -4px; }
.section-editor { position: fixed; z-index: calc(var(--z-gate) - 1); inset: 0 0 0 auto; width: min(25rem, 34vw); display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto; border-left: 1px solid var(--edge); background: var(--slab); box-shadow: -1rem 0 3rem rgba(0,0,0,.35); }
.section-editor[hidden] { display: none; }
.section-editor header { display: flex; align-items: start; justify-content: space-between; gap: .8rem; padding: 1.1rem 1rem .8rem; border-bottom: 1px solid var(--edge); }
.section-editor header .eyebrow { margin-bottom: .35rem; font-size: .6rem; }
.section-editor h2 { font-size: 1.35rem; }
.section-editor header button { width: 2rem; height: 2rem; border: 0; background: transparent; color: var(--ash); font-size: 1.5rem; cursor: pointer; }
.section-editor__hint { padding: .85rem 1rem; border-bottom: 1px solid var(--edge); color: var(--ash); font-size: .78rem; line-height: 1.45; }
.section-editor__fields { min-height: 0; overflow: auto; display: grid; align-content: start; gap: .8rem; padding: 1rem; }
.section-editor__field { display: grid; gap: .35rem; }
.section-editor__field > span { color: var(--ash); font-family: var(--f-label); font-size: .59rem; font-weight: 700; letter-spacing: var(--track-label); text-transform: uppercase; }
.section-editor__field input, .section-editor__field textarea { width: 100%; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); color: var(--chalk); font: inherit; font-size: .84rem; line-height: 1.45; padding: .55rem .65rem; resize: vertical; }
.section-editor__field input:focus, .section-editor__field textarea:focus { outline: 2px solid var(--ember); outline-offset: 1px; }
.section-editor footer { display: flex; gap: .6rem; justify-content: flex-end; padding: .85rem 1rem; border-top: 1px solid var(--edge); }
@media (max-width: 820px) {
  body.is-page-editing { padding-right: 0; padding-bottom: min(70svh, 34rem); }
  .section-editor { inset: auto 0 0; width: 100%; height: min(70svh, 34rem); border-top: 1px solid var(--edge); border-left: 0; }
}
/* BRAND LOCKUP.
   Both supplied logos are STACKED (mascot over wordmark). Scaled to fit a
   ~58px header bar a stacked lockup lands around 52px wide and the words stop
   being readable, so the header composes its own horizontal lockup instead:
   the mascot as an image mark, "FLOWER BOMB" as real text. That stays sharp at
   any size, keeps the accessible name in the DOM rather than hidden behind a
   background image, and mirrors the supplied wordmark's weight split — solid
   "FLOWER", grey "BOMB".
   The full stacked artwork (media/brand/flowerbomb-stacked-*.webp) is the
   right choice for large placements: share image, 404, print. */
.brand {
  display: inline-flex; align-items: center; gap: .5rem;
  flex: 0 0 auto; margin-right: auto;
  text-decoration: none; line-height: 1;
}
.brand::before {
  content: "";
  flex: 0 0 auto;
  height: clamp(1.85rem, 3.2vw, 2.4rem);
  /* 512x424 source crop — keep the ratio so the mascot never distorts. */
  aspect-ratio: 512 / 424;
  background: url("media/brand/flowerbomb-mark-96.webp") center / contain no-repeat;
}
@media (min-resolution: 1.5dppx) {
  .brand::before { background-image: url("media/brand/flowerbomb-mark-192.webp"); }
}
.brand__word {
  font-family: var(--f-display);
  font-size: clamp(1rem, 2vw, 1.4rem);
  letter-spacing: .015em;
  text-transform: uppercase;
  color: var(--chalk);
  white-space: nowrap;
}
.brand__word em { font-style: normal; color: var(--ash); }
body.theme-light .brand__word { color: var(--void); }
@media (max-width: 520px) {
  .header__inner { gap: .45rem; }   /* height now comes from --header-h */
  .brand { gap: .4rem; }
  .brand::before { height: 1.75rem; }
  .brand__word { font-size: .95rem; }
  .header__tools { gap: .05rem; }
  .icon-btn { width: 39px; height: 40px; }
}

.nav { display: none; }
@media (min-width: 1000px) {
  .nav { display: flex; align-items: center; gap: .3rem; }
}
.nav__item { position: relative; }
.nav__link {
  display: inline-flex; align-items: center; gap: .35rem;
  font-family: var(--f-label); font-size: var(--t-label); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label);
  padding: .7rem .85rem; text-decoration: none; color: var(--chalk);
  background: none; border: 0; cursor: pointer; border-radius: var(--radius);
}
.nav__link:hover, .nav__link[aria-expanded="true"] { color: var(--ember); }
.nav__link[aria-current="page"] { color: var(--ember); }
.account-icon.is-authed { color: var(--ember); border-color: color-mix(in srgb, var(--ember) 55%, var(--edge)); }

/* Tier mega-menu. The best idea recovered from the source: flower sorted by
   QUALITY TIER, not strain. Tiers map to budget, which is how people actually
   shop; the glyph set is a price ladder rendered as an icon; and every tier
   becomes its own indexable collection page. */
.mega {
  position: absolute; top: 100%; left: 0; min-width: 17rem;
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: .5rem; list-style: none;
}
.mega[hidden] { display: none; }
.mega a {
  display: flex; align-items: center; gap: .6rem;
  padding: .55rem .7rem; border-radius: var(--radius);
  text-decoration: none; font-size: var(--t-small);
}
.mega a:hover { background: var(--slab-2); color: var(--ember); }
/* Product categories read faster as clean text in the dropdown; the old emoji
   markers were decorative noise beside already-specific labels. */
.mega .tier-mark { display: none; }
.mega .price-from {
  margin-left: auto; font-family: var(--f-label); font-size: .66rem;
  letter-spacing: .04em;   /* colour set by the price rule */
}

.header__tools { display: flex; align-items: center; gap: .35rem; }
.icon-btn {
  display: grid; place-items: center; width: 42px; height: 42px;
  background: none; border: 0; cursor: pointer; border-radius: var(--radius-ctl);
  position: relative;
}
.icon-btn:hover { background: var(--slab-2); }
.icon-btn svg { width: 21px; height: 21px; stroke: currentColor; fill: none;
                stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.icon-btn--social { color: var(--ash); }
.icon-btn--social:hover, .icon-btn--social:focus-visible { color: var(--ember); }
@media (max-width: 480px) { .icon-btn--social { width: 36px; height: 42px; } }
.cart-count {
  position: absolute; top: 4px; right: 3px; min-width: 18px; height: 18px;
  border-radius: var(--radius-pill); background: var(--ember-lo); color: var(--on-ember);
  font-family: var(--f-label); font-size: .62rem; font-weight: 700;
  display: grid; place-items: center; padding: 0 4px;
}
.cart-count[data-count="0"] { display: none; }
@media (min-width: 1000px) { .icon-btn--menu { display: none; } }

/* Mobile drawer nav */
.mnav {
  position: fixed; inset: 0; z-index: var(--z-drawer);
  background: rgba(0,0,0,.6);
}
.mnav[hidden] { display: none; }
.mnav__panel {
  position: absolute; inset-block: 0; left: 0; width: min(88vw, 21rem);
  background: var(--slab); border-right: 1px solid var(--edge);
  padding: 1.2rem; overflow-y: auto;
}
.mnav__panel a {
  display: block; padding: .75rem .2rem; text-decoration: none;
  border-bottom: 1px solid var(--edge);
  font-family: var(--f-label); font-size: var(--t-label); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label);
}
.mnav__panel a.is-sub { padding-left: 1.2rem; text-transform: none;
                        letter-spacing: 0; font-family: var(--f-body);
                        font-size: var(--t-small); color: var(--ash); }

/* ============================================================
   6. HERO  — photo (or video) bed, zip gate INSIDE it
   The source puts its pickup details three sections down. Moving it
   into the hero answers "can I even get this?" before the visitor spends
   any attention on inventory they may not be able to buy.

   Two coats of paint share this markup:
     · The rules directly below are the DARK-HERO BASELINE — chalk text on a
       scrim over dark subject matter. This is what a video hero or a
       dark/desaturated photo hero should use, unmodified.
     · `.hero--poster`, lower in this file, OVERRIDES this baseline for the
       shipped art, which is a bright orange field where chalk-on-dark fails
       outright (3.32:1). That's the block actually in effect on the current
       build — see README "Re-tuning the hero scrim" before touching either.
   Swap the art back to something dark and `.hero--poster` becomes unnecessary
   (drop the class from the markup); the baseline here still works as-is.
   ============================================================ */
/* grid-template-columns is NOT decorative here — it fixes a real bug.
   With `display:grid` and no explicit column, the single implicit column
   track is auto-sized (its width comes FROM its content), which makes it an
   INDEFINITE containing block for that content. .hero__inner's own width
   formula, `width: min(100% - (var(--gutter) * 2), var(--content))`,
   includes a percentage — and a percentage width resolved against an
   indefinite containing block doesn't reliably produce "100% of the visible
   375px hero," so the gutter subtraction landed short: measured 357.5px
   wide (~one gutter's worth too wide) instead of the correct 339.8px, with
   the excess entirely on the right — the ZIP form (and everything else in
   .hero__inner) sat flush against the viewport edge on mobile.
   `minmax(0, 1fr)` makes the track explicit and definite (100% of `.hero`,
   full stop), which resolves the percentage correctly — verified against the
   live layout: 339.8px / 17.6px gutter both sides, matching every other
   `.wrap` on the page. The `minmax(0, ...)` half matters too: a bare `1fr`
   track still carries grid's automatic min-width:auto, which sizes the
   track to its content's minimum and can overflow — same family of bug as
   `min-width:0` on the flex children elsewhere in this file (.chips). */
.hero { position: relative; isolation: isolate; overflow: hidden;
        min-height: clamp(30rem, 78vh, 46rem); display: grid;
        grid-template-columns: minmax(0, 1fr); align-items: end; }
.hero__bed { position: absolute; inset: 0; z-index: -2; background: var(--void); }
.hero__bed picture { display: contents; }
.hero__bed img, .hero__bed video { width: 100%; height: 100%; object-fit: cover; }

/* Baseline object-position, tuned for a subject sitting hard right against a
   dark field (true of the original video hero; would suit e.g. a dark product
   shot the same way). Whether this shifts anything depends on which axis
   `cover` actually overflows — check before reusing these numbers on new
   art; see README "Re-tuning the hero scrim" step 3 for the measurement. */
.hero__video { object-position: 68% center; }
.hero__img { object-position: center; }
/* The hero sits on photography, so --edge (a near-black hairline tuned for
   the dark page ground) all but disappears against it — the ghost button
   read as floating text with no affordance. White is the only border that
   holds against every frame of the art.

   Declared BEFORE the .hero-text-* pair below on purpose: those are the
   dev style-panel's manual overrides and carry the same specificity, so
   they must come later to keep winning. In production neither class is
   applied, which is exactly why this default was needed. */
.hero--image .hero__cta .btn--ghost { border-color: #fff; }
.hero--image .hero__cta .btn--ghost:hover,
.hero--image .hero__cta .btn--ghost:focus-visible { border-color: var(--ember); color: var(--ember); }

.hero-text-white h1, .hero-text-white .hero__sub { color: #fff; }
.hero-text-white .hero__sub { opacity: .92; }
.hero-text-white .eyebrow { color: #fff; }
.hero-text-white .hero__cta .btn--ghost { color: #fff; border-color: rgba(255,255,255,.68); }
.hero-text-black h1, .hero-text-black .hero__sub { color: #0b0b0d; }
.hero-text-black .hero__sub { opacity: .92; }
.hero-text-black .eyebrow { color: #0b0b0d; }
.hero-text-black .hero__cta .btn--ghost { color: #0b0b0d; border-color: rgba(11,11,13,.55); }

/* Scrim. This is a VERBATIM COPY of the .shop-header gradient, which is also
   what .cat-hero-overlay__scrim uses — all three heroes on the site now carry
   the same wash, so moving between them does not change how the photography
   reads. Change one, change all three; there is no shared token because each
   applies it differently (background-image stack here and on .shop-header, a
   real element on the category pages).

   It replaced a two-layer scrim: a horizontal wash from .92 that cleared
   completely by 82%, plus a vertical wash that ran .62 at the top down to
   .88 at the bottom. Stacked, those put roughly .95 alpha over the left half
   and nothing over the right, which is what made the fade read as a hard
   edge with the photograph only surviving in one corner. One gentle
   horizontal pass keeps the whole frame.

   Losing the vertical layer means the hero no longer self-darkens under the
   header or at the fold. That is deliberate and matches the other two — but
   it is the thing to re-check first if header text ever stops reading. */
.hero__bed::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(90deg,
      rgba(11, 11, 13, .78) 0%,
      rgba(11, 11, 13, .58) 52%,
      rgba(11, 11, 13, .48) 100%);
}

/* Below the split the copy spans full width, so the horizontal wash has
   nowhere to clear to — and on a sufficiently tall-relative-to-wide box there
   may be no vertical escape either (a 16:9 source in a narrow phone hero can
   have zero vertical overflow, making the Y half of object-position inert;
   measure before assuming it helps). Baseline mobile response: drop the
   horizontal wash, go near-uniform dark, and let the subject read as texture
   rather than a focal point. */
@media (max-width: 899px) {
  .hero__video { object-position: 60% center; }
  /* At 375x633 the hero box is 0.59:1 against a 1.78:1 frame, so `cover`
     shows barely a third of the image width — centred, that window is
     33%-67% of the source and the ashtray, which lives at 65%-94%, is
     almost entirely off-screen. Pushing the crop to 93% puts the window at
     62%-95% and the whole subject back in the frame. Re-derive this number
     if the art changes: window width = (box ratio / image ratio), and
     object-position p places its left edge at p x (1 - window width). */
  .hero__img { object-position: 93% center; }
  .hero__bed::after {
    background: linear-gradient(180deg, rgba(11,11,13,.55) 0%,  rgba(11,11,13,.64) 30%,
                                        rgba(11,11,13,.88) 62%, rgba(11,11,13,.97) 100%);
  }
}

/* ---- .hero--poster : bright artwork, inverted copy ---------------
   The shipped hero art is a saturated orange field (#f93b0c) with the product
   cluster on the right. Everything above assumes a DARK frame, and none of it
   survives contact with this one:

     chalk on raw orange      3.32:1   FAILS
     near-black on raw orange 5.30:1   passes
     ember CTA vs raw orange  1.35:1   the button effectively vanishes

   That last number is the decisive one. A scrim heavy enough to rescue white
   text (~.75 alpha) turns #f93b0c into #46170d — at which point the art is a
   dark red rectangle and there was no reason to shoot it orange.

   So this hero inverts instead: near-black copy, near-black primary button,
   and only a light scrim to settle the field and blend the bottom edge into
   the page. The result reads as a printed poster panel above a dark site,
   which is a stronger use of the asset than hiding it. */
.hero--poster .hero__bed::after {
  background:
    /* barely-there horizontal lift so the flat field has some depth */
    linear-gradient(90deg, rgba(120,26,4,.20) 0%, rgba(120,26,4,.06) 45%,
                           rgba(120,26,4,0) 70%),
    /* bottom fade into --void so the panel doesn't end on a hard seam */
    linear-gradient(180deg, rgba(11,11,13,.10) 0%, rgba(11,11,13,0) 22%,
                            rgba(11,11,13,0) 62%, rgba(11,11,13,.55) 92%,
                            rgba(11,11,13,.92) 100%);
}
.hero--poster .hero__img { object-position: 62% center; }

/* Sizing here is doing accessibility work, not just styling. Measured against
   the real pixels, near-black on this field lands at 4.00:1 (h1's old colour)
   and 4.33:1 (sub). WCAG wants 4.5:1 for body copy but only 3:1 for large
   text — ≥24px, or ≥18.66px bold — so both clear their floor comfortably *at
   these sizes* and would fail if the sub were left at its usual 17px. Don't
   shrink either.

   h1 is chalk, by request — every other hero element (eyebrow, sub, buttons)
   stays near-black. That split creates a real constraint, not just a colour
   choice: h1 and .hero__sub both sit directly on the shared field scrim
   below, and they need it to move in OPPOSITE directions — white text wants
   the field darker, near-black text wants it lighter. One shared gradient
   cannot satisfy both, so the scrim itself was left alone (still tuned for
   the dark-text elements) and h1 gets its own, independent fix instead. */
.hero--poster h1 {
  color: var(--chalk);
  /* Measured (canvas-sampled against the real composited pixels, same method
     as the rest of this file) worst case on the flat scrim alone: 3.36:1 —
     clears the 3:1 large-text floor (112px desktop / 46px mobile) but with a
     thin margin, at the trailing edge of the wrapped line ("10."), where both
     the horizontal and vertical washes have faded to nearly nothing. That's
     thinner than every other margin in this file, which is deliberately
     avoided elsewhere (see "Photo band contrast" in the README) — and here
     the scrim can't just get heavier, per the constraint above.
     A soft shadow is the standard fix for light text over photography, adds
     real legibility a flat-color contrast formula doesn't capture, and —
     critically — is scoped to h1 alone: it cannot touch the sub-headline's
     or eyebrow's numbers, unlike any shared-gradient change would. */
  text-shadow: 0 2px 20px rgba(0,0,0,.55), 0 1px 4px rgba(0,0,0,.7);
}

.hero--poster .hero__sub  { color: var(--void); opacity: .88; }

/* 24px is a threshold, not a preference. WCAG's large-text exemption starts at
   24px (or 18.66px bold); at 21px this paragraph was still body copy needing
   4.5:1 and measured 4.21. `min()` rather than `clamp()` so the floor cannot
   drift back under 24px on a narrow desktop. Scoped to the light-field branch
   — below 900px the hero inverts to chalk on a darkened field and the normal
   body size is fine. */
@media (min-width: 900px) {
  .hero--poster .hero__sub { font-size: min(1.7rem, max(1.5rem, 1.55vw)); }
}

/* The eyebrow is 11.5px and cannot be sized into the large-text exemption, so
   it stops relying on the field entirely and gets its own black chip. Text on
   the chip is 16.8:1; the chip against the orange is 5.30:1, well past the 3:1
   a UI component needs. Reads as a printed label, which suits the poster. */
.hero--poster .eyebrow {
  display: inline-block; margin-bottom: 1rem;
  background: var(--void); color: var(--chalk);
  padding: .4rem .7rem;
  border-radius: var(--radius-ctl);
}

/* The primary CTA goes near-black. On orange that is 5.30:1 against the field
   — far past the 3:1 floor for a UI component — where the ember fill sat at
   1.35:1 and disappeared. White on near-black inside the button is 16.8:1. */
.hero--poster .btn--primary {
  background: var(--void); border-color: var(--void); color: #fff;
}
.hero--poster .btn--primary:hover { background: #000; border-color: #000; }
.hero--poster .btn--ghost {
  color: #fff; border-color: #fff;
}
.hero--poster .btn--ghost:hover {
  background: transparent; border-color: var(--ember); color: var(--ember);
}
.hero--poster :focus-visible { outline-color: var(--void); }

/* MOBILE — reframe, don't recolour.
   The first attempt flipped the copy to chalk here and measured 1.28:1 on the
   h1: white on near-raw orange is hopeless, and a scrim heavy enough to save
   it (~.75) would have erased the artwork on the one device that matters most
   in this category.

   The real problem is framing. At 375px a 16:9 source scales to 1125x633 in a
   375x633 box — zero vertical overflow, and only 33% of the width visible. At
   58% that window lands on the product cluster, so full-width copy was sitting
   over a grinder and a lighter. Panning to the flat left field instead gives
   the text a uniform ground, and the same near-black treatment as desktop
   works untouched. The cluster is lost on small screens; a flat orange panel
   with black type is the better trade. */
@media (max-width: 899px) {
  .hero--poster .hero__img { object-position: 6% center; }
  .hero--poster .hero__bed::after {
    background: linear-gradient(180deg, rgba(120,26,4,.06) 0%, rgba(120,26,4,.04) 55%,
                                        rgba(11,11,13,.45) 90%, rgba(11,11,13,.94) 100%);
  }
}

/* The hero is a pure accent field, not an image treatment. Its colour is
   controlled by the hue slider, and --hero-ink is calculated in app.js for
   the strongest available black/white contrast. */
.hero--poster .hero__bed { background: var(--hero-bg); }
.hero--poster .hero__bed::after { display: none; }
.hero--poster h1 { color: var(--hero-ink); text-shadow: none; }
.hero--poster .hero__sub { color: var(--hero-ink); opacity: .86; }
.hero--poster .eyebrow { background: var(--hero-ink); color: var(--hero-bg); }
.hero--poster .btn--primary { background: var(--hero-ink); border-color: var(--hero-ink); color: var(--hero-bg); }
.hero--poster .btn--primary:hover { background: var(--hero-ink); border-color: var(--hero-ink); filter: brightness(.88); }
.hero--poster .btn--ghost { color: #fff; border-color: #fff; }
.hero--poster .btn--ghost:hover,
.hero--poster .btn--ghost:focus-visible { background: transparent; border-color: var(--ember); color: var(--ember); }

.hero__inner { padding-block: clamp(3rem, 9vw, 6rem); }
.hero h1 { max-width: 16ch; }

/* The homepage is designed mobile-first. With the three-line hero lockup,
   let the H1 occupy the phone viewport instead of inheriting a desktop-sized
   minimum; it scales smoothly from compact phones through tablet widths. */
@media (max-width: 899px) {
  .hero h1 {
    font-size: clamp(3.6rem, 17vw, 6.25rem);
    line-height: .95;
    letter-spacing: .01em;
  }
  .hero__inner { padding-block: clamp(3.5rem, 12vw, 5.5rem); }
}

/* Split composition: keep every line of copy inside the opaque half of the
   scrim. Without this the h1 runs to ~66% and its last line sits over the lit
   subject at ~.38 scrim — the one place on the page where contrast fails.
   Capped as a percentage rather than in `ch` so it survives a font swap. */
@media (min-width: 900px) {
  .hero h1      { max-width: min(16ch, 52%); }
  .hero__sub    { max-width: min(46ch, 44%); }
  .hero__cta,
  .zipcheck     { max-width: min(34rem, 44%); }
}
.hero__telegram {
  display: inline-flex; align-items: center; gap: .55rem; width: fit-content; margin-top: 1rem;
  color: #229ed9; font-family: var(--f-label); font-size: .9rem;
  font-weight: 700; letter-spacing: var(--track-label); text-transform: uppercase;
  text-underline-offset: .28em; text-decoration-color: currentColor;
}
.hero__telegram svg { width: 1.25rem; height: 1.25rem; fill: currentColor; flex: none; }
.hero__telegram:hover { color: #1d8bc2; }
.hero__sub { margin-top: 1rem; max-width: 46ch; color: var(--chalk);
             font-size: 1.09rem; }
.hero__cta { margin-top: 1.8rem; display: flex; gap: .7rem; flex-wrap: wrap; }

/* Pickup information panel */
.zipcheck {
  margin-top: 2rem; max-width: 34rem;
  background: rgba(20,20,24,.86); backdrop-filter: blur(8px);
  border: 1px solid var(--edge); border-radius: var(--radius);
  padding: 1.1rem 1.2rem;
}
.zipcheck__label {
  font-family: var(--f-label); font-size: var(--t-label); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label);
  color: var(--ash); display: block; margin-bottom: .6rem;
}
.zipcheck__row { display: flex; gap: .5rem; }
.zipcheck input {
  flex: 1; min-width: 0; background: var(--void); border: 1px solid var(--edge);
  border-radius: var(--radius-ctl); padding: .8rem 1.1rem;
  font-family: var(--f-label); letter-spacing: .1em;
}
.zipcheck input::placeholder { color: var(--ash); letter-spacing: .1em; }
.zipcheck__out {
  margin-top: .8rem; font-size: var(--t-small); display: none;
}
.zipcheck__out.is-shown { display: block; }
.zipcheck__out.is-ok  { color: var(--live); }
.zipcheck__out.is-no  { color: var(--warn); }

/* ============================================================
   7. TRUST ROW
   The source scrolls this as an infinite marquee. Dropped: motion adds
   nothing to a static claim and costs prefers-reduced-motion compliance.
   A still, high-contrast badge row converts the same and loads free.
   ============================================================ */
.trust {
  border-block: 1px solid var(--edge); background: var(--slab);
}
.trust__grid {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: 1px; background: var(--edge);
}
@media (min-width: 760px) { .trust__grid { grid-template-columns: repeat(4, 1fr); } }
/* Three-cell variant. The base 2-column rule would leave the third cell
   alone on a half-empty second row, so stack cleanly on small screens and
   go three-across once there is room for the labels to sit on one line. */
.trust__grid--three { grid-template-columns: 1fr; }
@media (min-width: 560px) { .trust__grid--three { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 760px) { .trust__grid--three { grid-template-columns: repeat(3, 1fr); } }
.trust__cell {
  background: var(--slab); padding: 1.7rem var(--gutter);
  display: flex; flex-direction: column; gap: .35rem; align-items: center;
  text-align: center;
}
.trust__val {
  font-family: var(--f-display); font-size: 1.6rem; line-height: 1;
  text-transform: uppercase;
}
.trust__val .stars { color: var(--gold); font-size: 1.1rem; letter-spacing: .08em; }
.trust__key {
  font-family: var(--f-label); font-size: .66rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label); color: var(--ash);
}

/* ============================================================
   8. TIER LADDER
   ============================================================ */
.tiers { display: grid; gap: 1.25rem; grid-template-columns: repeat(2, 1fr); }
@media (min-width: 760px)  { .tiers { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1080px) { .tiers { grid-template-columns: repeat(5, 1fr); } }
.tier {
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: 1.6rem 1.35rem;
  text-decoration: none; display: flex; flex-direction: column; gap: .6rem;
  transition: border-color .18s ease, background .18s ease;
}
.tier:hover { border-color: var(--ember); background: var(--slab-2); }
.tier__mark { font-size: 1.6rem; line-height: 1; }
.tier__name { font-family: var(--f-display); font-size: 1.15rem;
              text-transform: uppercase; line-height: 1.05; }
.tier__from { font-family: var(--f-label); font-size: .68rem;
              letter-spacing: .06em; }   /* colour set by the price rule */
.tier__note { font-size: .84rem; color: var(--ash); line-height: 1.45; }

/* ============================================================
   9. PRODUCT GRID + CARD
   ============================================================ */
.grid {
  display: grid; gap: clamp(1rem, 2vw, 1.75rem);
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 700px)  { .grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1080px) { .grid { grid-template-columns: repeat(4, 1fr); } }

.card {
  position: relative; display: flex; flex-direction: column;
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); overflow: hidden;
  transition: border-color .18s ease, transform .18s ease;
}
.card:hover { border-color: color-mix(in srgb, var(--ember) 85%, transparent); transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) { .card:hover { transform: none; } }
.card.is-out { opacity: .55; }

/* Product media sits on a LIGHT PLATE, inset from the card edge.
   The placeholder — and most real product photography a smoke shop can get
   from a distributor — is a packshot on a pure-white background. Dropped
   straight onto the dark card with `cover`, that renders as a hard white
   rectangle bleeding to the card's corners: it reads as a broken image, and
   the crop lops the top and bottom off a standing package.
   So: `contain` the product, put a deliberate light plate behind it, inset
   the plate so the dark card frames it, and round it to match everything else.
   Same treatment a printed catalogue uses.

   `mix-blend-mode: multiply` is what makes the join seamless — white × plate
   = plate, so the photo's background disappears into the tile with no seam.
   It works for white and light-grey backgrounds and for transparent PNGs.
   It does NOT work for photography shot on a dark or coloured ground: multiply
   will crush those to mud. If the client's photos are dark-ground, drop the
   blend and switch --plate to a dark value instead. */
.card__media {
  position: relative; aspect-ratio: 1 / 1;
  margin: .55rem .55rem 0;
  border-radius: var(--radius);
  background: var(--plate);
  overflow: hidden;
  isolation: isolate;   /* confine the child's blend to this tile */
}
.card__media--white,
.product-modal__media.product-modal__media--white { background: #fff; }
.card__media img {
  width: 100%; height: 100%;
  object-fit: contain;
  padding: 8%;                 /* keeps the product off the plate edges */
  mix-blend-mode: multiply;    /* melts a white photo bg into the plate */
}

.exotic-lock {
  position: absolute; z-index: 4; inset: 0; display: grid; place-content: center;
  justify-items: center; gap: .35rem; padding: 1rem; text-align: center;
  background: rgba(10,10,12,.96); backdrop-filter: blur(8px); color: #fff;
}
.exotic-lock__mark { color: var(--ember); font-size: 1.45rem; line-height: 1; }
.exotic-lock strong { font-family: var(--f-label); font-size: .78rem; letter-spacing: var(--track-label); text-transform: uppercase; }
.exotic-lock > span:not(.exotic-lock__mark) { color: rgba(255,255,255,.68); font-size: .72rem; }
.exotic-lock__cta { margin-top: .35rem; padding: .4rem .6rem; border: 1px solid var(--ember); border-radius: var(--radius-ctl); color: var(--ember); font-family: var(--f-label); font-size: .62rem; font-weight: 700; letter-spacing: .04em; text-decoration: none; text-transform: uppercase; }
.exotic-lock__cta:hover { background: var(--ember); color: var(--on-ember); }
.has-verified-account .exotic-lock { display: none; }
.card__badges {
  position: absolute; top: .5rem; left: .5rem;
  display: flex; flex-direction: column; gap: .35rem; align-items: flex-start;
  z-index: 2;
}
.card__tier {
  position: absolute; top: .5rem; right: .5rem; z-index: 2;
  font-size: 1rem; line-height: 1;
  background: rgba(11,11,13,.82); border-radius: var(--radius-ctl);
  width: 1.9rem; height: 1.9rem; display: grid; place-items: center;
}
.card__tier--exotic { color: #b65cff; }
.card__tier--indoor { color: var(--gold); }
.card__tier--greenhouse { color: #fff; text-shadow: 0 1px 2px #000; }
.card__body { padding: .85rem .9rem .9rem; display: flex; flex-direction: column;
              gap: .4rem; }
.card__brand {
  font-family: var(--f-label); font-size: .62rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label); color: var(--ash);
}
.card__name {
  font-family: var(--f-body); font-weight: 600; font-size: var(--t-small);
  line-height: 1.3; text-transform: none; letter-spacing: 0;
}
.card__open { appearance: none; border: 0; padding: 0; color: inherit; background: transparent; font: inherit; text-align: left; cursor: pointer; }
.card__open::after { content: ""; position: absolute; inset: 0; z-index: 0; } /* whole-card hit area */
.card__meta {
  display: flex; align-items: center; gap: .45rem; flex-wrap: wrap;
  font-family: var(--f-label); font-size: .66rem; color: var(--ash);
  letter-spacing: .04em;
}
.card__rating { color: var(--gold); display: inline-flex; gap: .25rem;
                align-items: center; }
.card__foot { margin-top: .1rem; padding-top: .25rem;
              display: flex; align-items: center; gap: .5rem; }
.card__price {
  font-family: var(--f-label); font-weight: 700; font-size: .95rem;
  letter-spacing: .02em;
}

/* --- PRICES CARRY THE ACCENT ------------------------------------
   Every live price figure is --ember. Note this widens the accent-discipline
   rule in the token block, which reserved --ember for the primary CTA and the
   cart badge alone — the trade is real and worth knowing: the "Add" button no
   longer owns the only warm pixel on a card, so it pulls slightly less on its
   own. It earns that back by making the price scannable down a column, which
   is how people actually shop a menu, and price is itself a conversion
   element here rather than decoration.
   --ember on --slab measures 5.56:1 — passes AA for body text.
   .card__was is excluded on purpose: a struck-through former price should
   recede, and accenting it would advertise the wrong number. */
.card__price,
.tier__from,
.price-from,
strong.line__price,
.drawer__total span:last-child { color: var(--ember); }
.card__price--sold-out { color: #ff5a5a; text-transform: uppercase; }
.card__was { font-family: var(--f-label); font-size: .72rem; color: var(--ash);
             text-decoration: line-through; }
/* Product record — intentionally translucent so it feels like a closer look
   at the existing menu rather than a separate page. */
.product-modal { position: fixed; inset: 0; z-index: var(--z-gate); padding: clamp(1rem, 4vw, 3rem); display: grid; place-items: center; }
.product-modal__scrim { position: absolute; inset: 0; background: rgba(4, 4, 6, .72); backdrop-filter: blur(8px); }
.product-modal__panel { position: relative; display: grid; grid-template-columns: minmax(14rem, .85fr) minmax(0, 1.15fr); width: min(100%, 58rem); max-height: min(90vh, 48rem); overflow: auto; background: rgba(27, 27, 33, .9); border: 1px solid rgba(255, 255, 255, .16); border-radius: var(--radius); box-shadow: var(--shadow); backdrop-filter: blur(18px); }
.product-modal__close { position: absolute; z-index: 2; top: .75rem; right: .75rem; width: 2.25rem; height: 2.25rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: rgba(11, 11, 13, .82); color: var(--chalk); font-size: 1.7rem; line-height: 1; cursor: pointer; }
.product-modal__media { position: relative; min-height: 100%; background: rgba(236, 233, 226, .92); }
.product-modal__media img { width: 100%; height: 100%; min-height: 27rem; object-fit: contain; padding: 10%; mix-blend-mode: multiply; }
.card__video, .product-modal__video { width: 100%; height: 100%; display: block; object-fit: contain; background: #000; }
/* Video tiles ride on a black plate; the cream multiply-blend background is
   for cutout still photos, not videos. Applies to card tiles AND the modal
   media panel, catching both mounted and unmounted (lazy) video elements. */
.card__media:has(video),
.product-modal__media:has(video) { background: #000; }
.product-modal__media:has(video) img { mix-blend-mode: normal; }

/* ---------- SECOND-SHOT REVEAL ---------------------------------------
   Opt-in per record via `imgHover` (MEDIA_OVERRIDES in app.js). The media
   container gets .has-hover-media and a second, stacked layer; records
   without one render exactly as before and are untouched by any of this.

   The second shot must COVER the first, not sit on top of it. Two reasons
   that is not automatic here: both images are `object-fit: contain`, so a
   portrait shot letterboxes and leaves the landscape one showing down both
   sides; and `mix-blend-mode: multiply` means an overlaid layer multiplies
   into whatever is beneath rather than hiding it. So the primary is faded
   out on the same curve the secondary fades in — coverage is then total by
   construction, whatever the two aspect ratios are.

   .media-hover carries the plate ground and `isolation: isolate` so its
   child's multiply resolves against that plate and not against the layer
   below, which is what keeps the two shots looking identically treated.

   focus-within is not decoration: the card's name is a real button, so a
   keyboard visitor tabbing the grid gets the same reveal a mouse does.
   Under (hover: none) there is no pointer to trigger it, so the layer is
   dropped rather than left sitting invisibly over the product.
   --------------------------------------------------------------------- */
.has-hover-media { position: relative; }
.media-hover {
  position: absolute; inset: 0; z-index: 1;   /* under badges/tier at 2 */
  overflow: hidden; isolation: isolate;
  background: var(--plate);
  opacity: 0; transition: opacity .25s ease;
}
.media-hover > img {
  width: 100%; height: 100%;
  min-height: 0; max-height: none;            /* the modal's 27rem floor */
  object-fit: contain;
}
/* Opt-in per record via `hoverZoom` (MEDIA_OVERRIDES in app.js), for a
   second shot whose subject sits in the middle third of a much taller
   frame. `contain` fits the whole bitmap, empty margin included, so the
   product lands small in the centre of the plate. Scaling the layer past
   the tile and letting the overflow crop the margin is the fix; the tile
   already clips, so nothing escapes. Keep this well under the point where
   the subject itself starts to crop — check any new record before adding
   it here rather than assuming this value travels. */
.media-hover--zoom > img { transform: scale(1.35); }

.has-hover-media > img,
.has-hover-media > video { transition: opacity .25s ease; }
/* The modal's plate is translucent over a blurred panel, so an opaque
   --plate layer here would brighten the whole media column on hover. Match
   the surrounding tone exactly instead; coverage does not depend on it,
   because the primary is at opacity 0 by the time this is at 1. */
.product-modal__media .media-hover { background: rgba(236, 233, 226, .92); }

/* ---------- DRIFT LAYER ----------------------------------------------
   A loose knock-out of the product tumbling behind the packshot, for
   records where the primary shot is a tall narrow box surrounded by empty
   plate. Opt-in per record via `imgBack`.

   Stacking is the fiddly part. The primary is an <img> — inline-level
   replaced content, painted in step 7 of the painting order — while this
   layer is positioned. A positioned sibling at z-index 0 paints in step 8,
   i.e. ON TOP of the primary, which is the opposite of what is wanted. So
   .has-back-media lifts the primary into the positioned layer at z-index 1
   and the drift sits at 0 beneath it. (z-index:-1 also works, but then the
   layer paints under anything that later gains a background here, which is
   a trap for whoever edits this next.)

   Deliberately quiet: 16% opacity, half saturation, tilted, and sized past
   the tile so the tile crops it. It must read as depth, never as a second
   product the visitor might think they are buying. It also sits below the
   opaque hover layer, so a hover still covers everything.
   --------------------------------------------------------------------- */
.media-back {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden; pointer-events: none;
}
.media-back > img {
  position: absolute; top: 50%; left: 50%;
  width: auto; height: 132%;
  min-height: 0; max-height: none; padding: 0;
  object-fit: contain; mix-blend-mode: normal;
  opacity: .16; filter: saturate(.5);
  transform: translate(-72%, -44%) rotate(26deg);
}
.has-back-media > img,
.has-back-media > video { position: relative; z-index: 1; }

/* ---------- FLAVOUR GALLERY ------------------------------------------
   For records that are one listing covering a dozen printed bags. The
   media column splits into a packshot stage and a rail of buttons; see
   FLAVOR_SETS in app.js for which records opt in.

   The stage keeps the plate + multiply treatment the single-shot modal
   uses, so a flavour bag sits on the page exactly like every other
   packshot. Only min-height and padding are relaxed, to leave room for the
   rail without pushing the panel past its max-height.

   The rail is a real <button> group, not a strip of images: each button
   carries the flavour name and the strain type printed on that bag, so it
   works with the image un-loaded, reads correctly to a screen reader, and
   is tabbable in order.
   --------------------------------------------------------------------- */
.product-modal__media--gallery { display: flex; flex-direction: column; min-height: 0; }
.flavor-stage { position: relative; flex: 1 1 auto; min-height: 0; }
.product-modal__media--gallery img { min-height: 18rem; padding: 7%; }
.product-gallery { position: relative; flex: 1 1 auto; min-height: 0; }
.product-gallery__nav {
  position: absolute; z-index: 2; top: 50%; transform: translateY(-50%);
  width: 2.5rem; height: 2.5rem; border: 1px solid var(--edge);
  border-radius: 50%; background: rgba(11, 11, 13, .82); color: var(--chalk);
  font-size: 2rem; line-height: 1; cursor: pointer;
}
.product-gallery__nav:hover { border-color: var(--ember); color: var(--ember); }
.product-gallery__nav:focus-visible { outline: 2px solid var(--ember); outline-offset: 2px; }
.product-gallery__nav--prev { left: .75rem; }
.product-gallery__nav--next { right: .75rem; }
.product-gallery__count {
  position: absolute; right: .75rem; bottom: .75rem; padding: .28rem .5rem;
  border-radius: var(--radius-ctl); background: rgba(11, 11, 13, .82); color: var(--chalk);
  font-family: var(--f-label); font-size: .62rem; letter-spacing: var(--track-label);
}
.flavor-stage__clip { position: absolute; inset: 0; }
.flavor-stage__clip[hidden] { display: none; }
.flavor-stage__clip video { min-height: 0; height: 100%; background: var(--plate); }

/* No max-height, deliberately. Capping it at 13rem hid the last two rows
   of an 11-button rail behind a scroll nobody would guess was there — the
   flavours a visitor cannot see are the ones they assume are out of stock.
   The panel already scrolls as a whole (.product-modal__panel), so letting
   the rail size to its content costs nothing and loses nothing. */
.flavor-rail {
  flex: none;
  display: block; padding: .5rem;
  background: var(--void);
  border-top: 1px solid var(--edge);
}
.flavor-select-field { display: block; }
.flavor-select,
.product-modal__sku-select {
  width: 100%; min-height: 2.7rem; padding: .65rem .75rem;
  border: 1px solid var(--edge); border-radius: var(--radius-ctl);
  background: var(--slab); color: var(--chalk); font: inherit; cursor: pointer;
}
.flavor-select:hover,
.product-modal__sku-select:hover { border-color: color-mix(in srgb, var(--ember) 55%, var(--edge)); }
.flavor-select:focus-visible,
.product-modal__sku-select:focus-visible { outline: 2px solid var(--ember); outline-offset: 2px; }
.flavor-chip {
  display: grid; gap: .1rem; justify-items: start; text-align: left;
  min-height: 2.5rem; padding: .38rem .55rem;
  border: 1px solid var(--edge); border-radius: var(--radius-ctl);
  background: var(--slab); color: var(--chalk); cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.flavor-chip:hover { background: var(--slab-2); border-color: color-mix(in srgb, var(--ember) 55%, var(--edge)); }
.flavor-chip__name {
  font-family: var(--f-label); font-size: .68rem; font-weight: 700; line-height: 1.15;
}
.flavor-chip__type {
  font-family: var(--f-label); font-size: .55rem; font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash);
}
/* Solid accent fill takes --on-ember, never white — see the token block. */
.flavor-chip[aria-pressed="true"] {
  background: var(--ember-lo); border-color: var(--ember-lo); color: var(--on-ember);
}
.flavor-chip[aria-pressed="true"] .flavor-chip__type { color: color-mix(in srgb, var(--on-ember) 74%, transparent); }
.flavor-chip--clip { grid-column: 1 / -1; }

@media (max-width: 640px) {
  .product-modal__media--gallery img { min-height: 11rem; max-height: 14rem; }
  .product-gallery__nav { width: 2.2rem; height: 2.2rem; }
}

.card:hover .has-hover-media > img,
.card:focus-within .has-hover-media > img,
.has-hover-media:hover > img,
.has-hover-media:focus-within > img { opacity: 0; }

.card:hover .media-hover,
.card:focus-within .media-hover,
.has-hover-media:hover .media-hover,
.has-hover-media:focus-within .media-hover { opacity: 1; }

@media (hover: none) { .media-hover { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .media-hover, .has-hover-media > img, .has-hover-media > video { transition: none; }
}

.product-modal__video { min-height: 27rem; }
.card__no-media { width: 100%; height: 100%; min-height: 12rem; display: grid; place-items: center; padding: 2rem; background: linear-gradient(135deg, #28272d, #17171c); color: var(--ash); font-family: var(--f-label); font-size: .7rem; letter-spacing: var(--track-label); text-align: center; text-transform: uppercase; }
.product-modal__content { padding: clamp(1.5rem, 4vw, 2.5rem); display: flex; flex-direction: column; align-items: stretch; gap: 1rem; }
.product-modal__content h2 { font-size: clamp(1.8rem, 4vw, 2.8rem); line-height: 1.08; }
.product-modal__price { font-family: var(--f-label); font-size: 1.15rem; font-weight: 700; color: var(--ember); }
.product-modal__availability-note { margin: -.25rem 0 0; padding: .7rem .8rem; border: 1px solid color-mix(in srgb, var(--ember) 48%, var(--edge)); border-radius: var(--radius-ctl); background: color-mix(in srgb, var(--ember) 10%, transparent); color: var(--ember); font-family: var(--f-label); font-size: .78rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; }
.product-modal__note { color: var(--chalk); font-size: var(--t-small); line-height: 1.6; }
.product-modal__ladder { width: 100%; border-collapse: collapse; margin: .35rem 0 .25rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); overflow: hidden; font-family: var(--f-label); }
.product-modal__ladder th, .product-modal__ladder td { padding: .55rem .8rem; text-align: left; font-size: .82rem; }
.product-modal__ladder thead th { font-size: .58rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash); background: rgba(255, 255, 255, .04); border-bottom: 1px solid var(--edge); }
.product-modal__ladder tbody tr + tr td { border-top: 1px solid var(--edge); }
.product-modal__ladder tbody td:last-child { text-align: right; font-weight: 700; color: var(--ember); font-variant-numeric: tabular-nums; }
.product-modal__ladder tbody td:first-child { color: var(--chalk); }
.product-modal__discounts { list-style: none; margin: .25rem 0 0; padding: .6rem .8rem; display: grid; gap: .3rem; border: 1px dashed var(--edge); border-radius: var(--radius-ctl); font-family: var(--f-label); font-size: .74rem; color: var(--chalk); background: color-mix(in srgb, var(--ember) 6%, transparent); }
.product-modal__discounts li::before { content: '★  '; color: var(--ember); }
.product-modal__ladder-add-col { width: 5.5rem; }
.product-modal__ladder td.product-modal__ladder-add { text-align: right; padding: .35rem .6rem; }
.product-modal__ladder-add .btn { padding: .3rem .7rem; font-size: .68rem; line-height: 1; }
.product-modal__ladder-total { color: var(--ash); font-weight: 400; font-size: .72rem; margin-left: .25rem; }
.product-modal__order-options { display: grid; gap: .45rem; margin-top: .8rem; }
.product-modal__order-option {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  width: 100%; padding: .75rem .8rem; border: 1px solid var(--edge);
  border-radius: var(--radius-ctl); background: var(--slab-2); color: var(--chalk);
  text-align: left; cursor: pointer;
}
.product-modal__order-option:hover, .product-modal__order-option:focus-visible { border-color: var(--ember); background: var(--ember-lo); }
.product-modal__order-option[aria-pressed="true"] { border-color: var(--ember); background: var(--ember); color: var(--on-ember); }
.product-modal__order-option span { display: grid; gap: .2rem; }
.product-modal__order-option small { color: var(--ash); font-size: .7rem; }
.product-modal__order-option strong { color: var(--ember); font-family: var(--f-label); font-size: .85rem; }
.product-modal__order-option[aria-pressed="true"] small,
.product-modal__order-option[aria-pressed="true"] strong { color: var(--on-ember); }
.product-modal__pack { display: flex; flex-wrap: wrap; gap: .35rem; margin: 0 0 .2rem; }
.product-modal__pack-pill { padding: .25rem .55rem; border: 1px solid var(--edge); border-radius: 999px; font-family: var(--f-label); font-size: .62rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--chalk); background: rgba(255, 255, 255, .04); }
.product-modal__strains { display: flex; flex-wrap: wrap; gap: .4rem; margin: .2rem 0; }
.product-modal__strain { display: inline-flex; align-items: baseline; gap: .35rem; padding: .3rem .55rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: rgba(255, 255, 255, .03); font-size: .74rem; color: var(--chalk); }
.product-modal__strain em { font-family: var(--f-label); font-size: .58rem; letter-spacing: var(--track-label); text-transform: uppercase; font-style: normal; color: var(--ash); }
.product-modal__strain--indica { border-color: color-mix(in srgb, #a68bff 55%, var(--edge)); }
.product-modal__strain--indica em { color: color-mix(in srgb, #a68bff 82%, #fff); }
.product-modal__strain--sativa { border-color: color-mix(in srgb, var(--gold) 55%, var(--edge)); }
.product-modal__strain--sativa em { color: color-mix(in srgb, var(--gold) 82%, #fff); }
.product-modal__strain--hybrid { border-color: color-mix(in srgb, var(--ember) 55%, var(--edge)); }
.product-modal__strain--hybrid em { color: color-mix(in srgb, var(--ember) 78%, #fff); }
.product-modal__skus { display: grid; gap: .45rem; }
.product-modal__sku-field { display: grid; gap: .3rem; }
.product-modal__sku-rail { display: flex; flex-wrap: wrap; gap: .35rem; }
.product-modal__sku-group { display: grid; gap: .3rem; margin-top: .2rem; }
.product-modal__sku-group-label { font-family: var(--f-label); font-size: .58rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash); }
.product-modal__sku { display: inline-flex; align-items: baseline; gap: .35rem; padding: .35rem .7rem; border: 1px solid var(--edge); border-radius: 999px; background: rgba(255, 255, 255, .04); color: var(--chalk); font-family: var(--f-label); font-size: .72rem; cursor: pointer; transition: border-color .15s ease, background .15s ease, color .15s ease; }
.product-modal__sku b { font-weight: 500; }
.product-modal__sku em { font-style: normal; font-size: .56rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash); }
.product-modal__sku:hover { border-color: color-mix(in srgb, var(--ember) 45%, var(--edge)); }
.product-modal__sku[aria-pressed="true"] { border-color: var(--ember); background: color-mix(in srgb, var(--ember) 18%, transparent); color: color-mix(in srgb, var(--ember) 78%, #fff); }
.product-modal__sku[aria-pressed="true"] em { color: color-mix(in srgb, var(--ember) 62%, #fff); }
.line__tier { font-size: .72rem; color: var(--ember); font-family: var(--f-label); letter-spacing: .02em; text-transform: uppercase; }
.checkout__tier { font-style: normal; color: var(--ember); font-size: .78rem; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.product-modal__stats { display: grid; grid-template-columns: repeat(3, 1fr); border-block: 1px solid var(--edge); }
.product-modal__stats div { padding: .7rem .8rem; display: grid; gap: .15rem; }
.product-modal__stats div + div { border-left: 1px solid var(--edge); }
.product-modal__stats span, .product-modal__label, .product-modal__results dt { font-family: var(--f-label); font-size: .6rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash); }
.product-modal__stats strong { font-family: var(--f-label); font-size: .9rem; }
.product-modal__section { display: grid; gap: .5rem; }
.product-modal__terps { display: flex; flex-wrap: wrap; gap: .4rem; }
.product-modal__terp { padding: .3rem .5rem; border: 1px solid color-mix(in srgb, var(--ember) 42%, transparent); background: color-mix(in srgb, var(--ember) 10%, transparent); border-radius: var(--radius-ctl); font-family: var(--f-label); font-size: .65rem; color: color-mix(in srgb, var(--ember) 72%, #fff); }
.product-modal__muted, .product-modal__fine { color: var(--ash); font-size: .76rem; }
.product-modal__results { display: grid; grid-template-columns: repeat(3, 1fr); gap: .75rem; }
.product-modal__results div { display: grid; gap: .15rem; }
.product-modal__results dd { font-size: .78rem; margin: 0; }
.product-modal__fine { line-height: 1.45; }
@media (max-width: 640px) {
  .product-modal { padding: .75rem; align-items: end; }
  .product-modal__panel { grid-template-columns: 1fr; max-height: 92vh; }
  .product-modal__media { order: -1; }
  .product-modal__media--white { min-height: 15rem; background: #fff; }
  .product-modal__media img { min-height: 13rem; max-height: 15rem; }
  .product-modal__media--white img { background: #fff; }
  .product-modal__content { padding: 1.4rem; }
}

/* Welcome savings wheel — a self-contained, one-session promotional moment. */
.savings-popup { position: fixed; inset: 0; z-index: var(--z-gate); display: grid; place-items: center; padding: 1rem; }
.savings-popup__scrim { position: absolute; inset: 0; background: rgba(3, 3, 5, .78); backdrop-filter: blur(8px); }
.savings-popup__panel { position: relative; width: min(100%, 32rem); max-height: 94vh; overflow: auto; display: flex; flex-direction: column; align-items: center; padding: clamp(1.4rem, 4vw, 2.4rem); background: rgba(20,20,24,.95); border: 1px solid rgba(255,255,255,.14); border-radius: var(--radius); text-align: center; box-shadow: 0 1rem 4rem rgba(0,0,0,.65); }
.savings-popup__close { position: absolute; top: .65rem; right: .65rem; width: 2rem; height: 2rem; border: 0; background: transparent; color: var(--ash); font-size: 1.6rem; cursor: pointer; }
.savings-popup__kicker, .savings-popup__fine { font-family: var(--f-label); font-size: .62rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash); }
.savings-popup__timer { display: grid; place-items: center; width: 5.5rem; height: 3.8rem; margin: .9rem 0 1.1rem; border-radius: var(--radius); background: var(--ember-lo); color: var(--on-ember); font-family: var(--f-label); font-weight: 700; font-size: 1.3rem; }
.savings-popup h2 { font-size: clamp(1.75rem, 5vw, 2.55rem); line-height: 1.05; }
.savings-wheel-wrap { position: relative; width: min(58vw, 14rem); aspect-ratio: 1; margin: 1.25rem 0; }
.savings-wheel { --wheel-spin: 0deg; width: 100%; height: 100%; border: .45rem solid var(--gold); border-radius: 50%; background: conic-gradient(var(--ember) 0deg 60deg, #17171c 60deg 120deg, var(--gold) 120deg 180deg, #101014 180deg 240deg, var(--ember-lo) 240deg 300deg, #1d1d23 300deg 360deg); transition: transform 3.8s cubic-bezier(.13,.75,.1,1); box-shadow: inset 0 0 0 .3rem rgba(11,11,13,.2), 0 .7rem 1.8rem rgba(0,0,0,.35); display: grid; place-items: center; }
.savings-wheel.is-spinning { transform: rotate(var(--wheel-spin)); }
.savings-wheel__label { position: absolute; top: 50%; left: 50%; width: 43%; transform-origin: 0 50%; transform: rotate(var(--segment)) translateX(14%) rotate(calc(var(--segment) * -1)); color: #fff; font-family: var(--f-label); font-size: .52rem; font-weight: 700; line-height: 1.15; letter-spacing: .025em; text-align: center; text-transform: uppercase; text-shadow: 0 1px 2px rgba(0,0,0,.7); }
.savings-wheel__label:nth-of-type(3) { color: var(--void); text-shadow: none; }
.savings-wheel__hub { display: grid; place-items: center; width: 3.7rem; height: 3.7rem; border-radius: 50%; background: var(--void); border: 2px solid var(--chalk); color: var(--chalk); font-family: var(--f-label); font-size: .65rem; font-weight: 700; letter-spacing: .08em; }
.savings-wheel__pointer { position: absolute; z-index: 1; left: 50%; top: -.15rem; transform: translateX(-50%); width: 0; height: 0; border-inline: .75rem solid transparent; border-top: 1.4rem solid var(--chalk); filter: drop-shadow(0 2px 2px rgba(0,0,0,.45)); }
.savings-popup__result { display: grid; gap: .2rem; margin: -.3rem 0 .85rem; color: var(--ash); font-size: .8rem; }
.savings-popup__result strong { color: var(--gold); font-family: var(--f-label); font-size: 1.25rem; }
.savings-popup__result code { margin-inline: auto; padding: .25rem .55rem; border: 1px dashed var(--ember); border-radius: var(--radius-ctl); color: var(--chalk); font-family: var(--f-label); font-size: .75rem; letter-spacing: .06em; }
.savings-popup__spin { min-width: 14rem; }
.savings-popup__spin:disabled { opacity: .72; cursor: wait; }
.savings-popup__dismiss { margin-top: 1rem; border: 0; background: transparent; color: var(--ash); font-size: .82rem; text-decoration: underline; text-underline-offset: .2rem; cursor: pointer; }
.savings-popup__fine { margin-top: 1.2rem; }
.welcome-email { width: min(100%, 23rem); display: grid; gap: .7rem; margin-top: 1.2rem; text-align: left; }
.welcome-email label { font-family: var(--f-label); font-size: .65rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash); }
.welcome-email input { min-height: 2.9rem; padding: .65rem .8rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); color: var(--chalk); font: inherit; }
.welcome-email input:focus { outline: 2px solid var(--ember); outline-offset: 2px; }
.welcome-email__status { min-height: 1.2em; margin: 0; color: var(--gold); text-align: center; font-size: .85rem; }
@media (max-height: 740px) { .savings-popup__timer { margin-block: .55rem; } .savings-wheel-wrap { width: min(44vw, 10.5rem); margin-block: .7rem; } .savings-popup h2 { font-size: 1.65rem; } }

/* Collections remain centred grids on phones as well as desktop. */
@media (max-width: 699px) {
  .grid--rail {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .75rem; margin-inline: 0; padding: 0;
  }
}

/* Four popular products form a compact two-up grid on phones rather than
   inheriting the larger, swipe-first collection rail. */
@media (max-width: 699px) {
  .grid--popular { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .7rem; }
  .grid--popular .card__media { margin: .4rem .4rem 0; }
  .grid--popular .card__body { padding: .72rem; gap: .4rem; }
  .grid--popular .card__brand { font-size: .55rem; }
  .grid--popular .card__name { font-size: .78rem; line-height: 1.2; }
  .grid--popular .card__meta { font-size: .57rem; gap: .25rem; }
  .grid--popular .card__foot { margin-top: 0; padding-top: .2rem; }
  .grid--popular .card__price { font-size: .82rem; }
}

/* The homepage arrival shelf is intentionally sparse: three large tiles make
   a new drop feel curated rather than like another dense menu grid. */
.new-arrivals { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.new-arrivals .card__media { aspect-ratio: 4 / 4.6; }
.new-arrivals .card__body { padding: 1.25rem 1.25rem 1.35rem; }
.new-arrivals .card__name { font-size: 1.05rem; }
.new-arrivals .card__price { font-size: 1.05rem; }
@media (max-width: 699px) { .new-arrivals.grid--rail { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ============================================================
   10. HOW TO ORDER  — the one inverted band
   Placed between collections, not at the bottom. It intercepts the visitor
   at the moment they know WHAT and now need to know HOW — and the how is
   unusual here (scheduled window, off-rails payment, ID check). At the
   bottom of the page it would only be read by people who already converted.
   ============================================================ */
/* The ordering explainer stays visually quiet so the verification, Telegram
   coordination, and curbside-pickup instructions remain easy to scan. */
.trust + .google-trust, .google-trust + .steps { margin-top: -1px; }
.steps {
  position: relative; isolation: isolate;
  height: 300vh;
  background-color: var(--slab);
  border-block: 1px solid var(--edge);
}
.steps__backdrop {
  position: sticky;
  z-index: 0;
  top: 0;
  height: 100svh;
  margin-bottom: -100svh;
  overflow: hidden;
  pointer-events: none;
}
.steps__backdrop::after { content: ''; position: absolute; inset: 0; background: linear-gradient(rgba(11,11,13,.78), rgba(11,11,13,.85)); }
.steps__backdrop img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center; }
.steps__pin { position: sticky; z-index: 1; top: 0; height: 100svh; overflow: hidden; }
.steps__stage { height: 100%; display: grid; grid-template-rows: auto auto auto; align-content: center; justify-items: center; gap: clamp(.65rem, 1.5vh, 1.15rem); padding-block: .75rem; }
.google-trust { width: 100%; margin: 0; overflow: hidden; border-block: 1px solid color-mix(in srgb, var(--ember) 65%, transparent); background: color-mix(in srgb, var(--ember) 8%, transparent); color: var(--ember); }
.google-trust[role="button"] { cursor: pointer; }
.google-trust[role="button"]:focus-visible { outline: 2px solid var(--ember); outline-offset: -3px; }
.google-trust__track { display: flex; align-items: center; gap: 1.35rem; width: max-content; min-height: 4.2rem; padding-inline: 1rem; font-family: var(--f-label); font-size: .73rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap; animation: google-trust-marquee 52s linear infinite; }
.google-trust__lead { font-size: .88rem; letter-spacing: var(--track-label); }
.google-trust__lead b { margin-left: .45rem; color: var(--chalk); }
.google-trust__stars { letter-spacing: .1em; font-size: 1rem; }
.google-trust q { color: var(--chalk); font-family: var(--f-body); font-size: .93rem; font-weight: 500; letter-spacing: 0; text-transform: none; }
.google-trust__who { color: color-mix(in srgb, var(--ember) 72%, var(--chalk)); font-size: .61rem; }
.google-trust i { font-style: normal; font-size: .85rem; }
@keyframes google-trust-marquee { to { transform: translateX(-50%); } }

.review-dialog { position: fixed; z-index: var(--z-gate); inset: 0; display: grid; place-items: center; padding: 1rem; }
.review-dialog__scrim { position: absolute; inset: 0; background: rgba(0,0,0,.72); backdrop-filter: blur(5px); }
.review-dialog__panel { position: relative; width: min(42rem, 100%); max-height: min(46rem, calc(100svh - 2rem)); display: grid; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; border: 1px solid var(--edge); border-radius: var(--radius); background: var(--slab); box-shadow: var(--shadow); }
.review-dialog__panel header { display: flex; justify-content: space-between; gap: 1rem; align-items: start; padding: 1.4rem 1.4rem 1rem; border-bottom: 1px solid var(--edge); }
.review-dialog__panel h2 { font-size: clamp(2rem, 6vw, 3.4rem); }
.review-dialog__panel header button { width: 2.25rem; height: 2.25rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: transparent; color: var(--chalk); font-size: 1.35rem; cursor: pointer; }
.review-dialog__list { overflow-y: auto; padding: 1rem 1.4rem 1.4rem; display: grid; gap: .75rem; }
.review-dialog__item { padding: 1rem; border: 1px solid var(--edge); border-radius: var(--radius); }
.review-dialog__item > div { display: flex; justify-content: space-between; gap: 1rem; align-items: center; }
.review-dialog__stars { color: var(--gold); letter-spacing: .08em; }
.review-dialog__item p { margin-top: .5rem; color: var(--chalk); font-size: .92rem; }
.review-dialog__item small { display: block; margin-top: .35rem; color: var(--ash); font-size: .72rem; }
/* Sticky-feeling footer: it is the third grid row, so it never scrolls away
   with the list above it. The panel's rows are auto / 1fr / auto — header,
   scrolling list, this. Adding a row here without updating that template is
   what collapses the list to nothing. */
.review-dialog__foot { display: flex; justify-content: center; padding: 1rem 1.4rem 1.25rem; border-top: 1px solid var(--edge); background: var(--slab); }
.review-dialog__foot .btn { width: min(20rem, 100%); }
.steps__head { text-align: center; margin: 0; }
/* Keep the centered story copy anchored to the same responsive grid at every
   desktop width. The previous negative translation made this section appear
   offset even though the rest of the page was correctly centered. */
.steps__head, .steps__account { transform: none; }
.steps__head .eyebrow { font-size: .72rem; }
.steps__head h2 { font-size: clamp(2.9rem, 5.8vw, 5.7rem); margin-inline: 0; }
/* Break out of the centered content wrapper before creating the carousel
   viewport. A raw 100vw width inside that wrapper was shifted by the wrapper's
   left gutter on desktop, so the whole How to Order sequence sat to the right. */
.steps__viewport { width: 100vw; margin-inline: calc(50% - 50vw); display: grid; align-items: center; overflow: hidden; }
.steps__grid { display: flex; gap: clamp(.5rem, 1.4vw, 1.25rem); width: max-content; will-change: transform; }
.step { --step-accent: var(--ember); position: relative; isolation: isolate; overflow: visible; flex: 0 0 min(64vw, 40rem); min-height: clamp(14rem, 30vh, 20rem); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .8rem; padding: clamp(.5rem, 2vw, 1.2rem); text-align: center; opacity: .18; transform: scale(.82); transition: opacity .35s ease, transform .35s ease; }
.step.is-active { opacity: 1; transform: scale(1); }
.step__art { position: absolute; z-index: -1; width: clamp(15rem, 32vw, 30rem); height: auto; right: clamp(-5rem, -6vw, -1rem); top: 50%; transform: translateY(-50%) rotate(-8deg); object-fit: contain; opacity: .34; pointer-events: none; filter: drop-shadow(0 1.25rem 1.6rem rgba(0,0,0,.25)); }
.step:nth-child(2) .step__art { left: clamp(-5rem, -6vw, -1rem); right: auto; transform: translateY(-50%) rotate(9deg); }
.step:nth-child(3) .step__art { right: 50%; transform: translate(50%, -50%) rotate(-4deg); }
.step > :not(.step__art) { position: relative; z-index: 1; }
.step:nth-child(2) { --step-accent: var(--gold); }
.step:nth-child(3) { --step-accent: #A2C931; }
.step__n {
  font-family: var(--f-display); font-weight: 400; font-size: clamp(5.3rem, 10.5vw, 10rem);
  line-height: .72; letter-spacing: .035em; color: color-mix(in srgb, var(--step-accent) 72%, #fff);
  background: none; -webkit-text-fill-color: color-mix(in srgb, var(--step-accent) 72%, #fff);
  -webkit-text-stroke: 3px var(--chalk);
  text-shadow: 0 0 1.2rem color-mix(in srgb, var(--step-accent) 24%, transparent);
}
.step h3 { font-size: clamp(2.15rem, 3.95vw, 3.4rem); color: var(--chalk); }
.step p { color: var(--ash); font-size: clamp(.97rem, 1.42vw, 1.12rem); max-width: 29ch; }
.step__note {
  font-family: var(--f-label); font-size: .71rem; letter-spacing: .06em;
  color: var(--step-accent); text-transform: uppercase;
}
.steps__account { display: grid; justify-items: center; gap: .7rem; margin: 0; text-align: center; }
.steps__account p { color: var(--chalk); font-family: var(--f-label); font-size: .84rem; font-weight: 700; letter-spacing: var(--track-label); text-transform: uppercase; }
.steps__account-actions { display: flex; justify-content: center; gap: .7rem; flex-wrap: wrap; }
.steps__account .btn { font-size: .8rem; }
/* The home page uses a pinned, scrolling story. Supporting pages need the
   same information without the animation or three-viewport section height. */
.steps--simple { height: auto; padding-block: clamp(3rem, 7vw, 5rem); }
.steps--simple .head { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }
.steps--simple .steps__grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  width: auto; transform: none !important; will-change: auto;
  gap: clamp(.75rem, 2vw, 1.25rem);
}
.steps--simple .step {
  min-height: 0; padding: clamp(1.35rem, 3vw, 2rem);
  align-items: flex-start; justify-content: flex-start; text-align: left;
  opacity: 1; transform: none; border: 1px solid var(--edge);
  border-radius: var(--radius); background: var(--slab-2);
}
.steps--simple .step__n { font-size: clamp(3.6rem, 6vw, 5.5rem); }
.steps--simple .step h3 { font-size: clamp(1.75rem, 2.5vw, 2.45rem); }
.steps--simple .step p { font-size: .95rem; max-width: none; }
.steps--home {
  min-height: min(100svh, 44rem); display: grid; align-items: center;
  overflow: hidden; padding-block: clamp(2.5rem, 5vh, 4rem);
}
.steps--home .steps__backdrop { position: absolute; inset: 0; height: auto; margin: 0; }
.steps--home .steps__stage { position: relative; z-index: 1; height: auto; padding-block: 0; }
.steps--home .steps__head h2 { font-size: clamp(2.4rem, 4vw, 3.8rem); }
.steps--home .steps__grid { margin-top: clamp(1rem, 2.5vh, 1.75rem); }
.steps--home .step {
  min-height: clamp(16rem, 34vh, 21rem); overflow: hidden;
  display: grid; grid-template-rows: clamp(4rem, 5vw, 4.75rem) minmax(2.6rem, auto) minmax(4.5rem, 1fr) auto;
  gap: .45rem; padding: clamp(1rem, 2vw, 1.5rem); align-items: start; text-align: left;
}
.steps--home .step__art { width: clamp(6rem, 10vw, 8rem); right: .75rem; top: .75rem; transform: none; opacity: .5; }
.steps--home .step:nth-child(2) .step__art,
.steps--home .step:nth-child(3) .step__art { left: auto; right: .75rem; top: .75rem; transform: none; }
.steps--home .step__n { font-size: clamp(3.8rem, 5vw, 5rem); }
.steps--home .step h3 { margin: 0; font-size: clamp(1.55rem, 2.2vw, 2.15rem); line-height: 1; }
.steps--home .step p { margin: 0; font-size: clamp(.82rem, 1.15vw, .95rem); }
.steps--home .step__note { align-self: end; }
@media (max-width: 699px) {
  .steps--simple .steps__grid { grid-template-columns: 1fr; }
  .steps--home { min-height: 0; padding-block: 2.5rem; }
  .steps--home .steps__grid { gap: .75rem; margin-top: 1rem; }
  .steps--home .step {
    min-height: 13rem;
    grid-template-rows: 3.7rem minmax(2.6rem, auto) minmax(4rem, 1fr) auto;
    padding: 1rem;
  }
  .steps--home .step__art { width: 5.25rem; top: .65rem; right: .65rem; }
  .steps--home .step:nth-child(2) .step__art,
  .steps--home .step:nth-child(3) .step__art { right: .65rem; top: .65rem; }
  .steps--home .step__n { font-size: 3.55rem; }
  .steps--home .step h3 { font-size: 1.65rem; }
  .steps--home .step p { max-width: 34ch; font-size: .88rem; }
}
@media (max-height: 700px) { .step { min-height: 16rem; } .google-trust__track { min-height: 3.25rem; } }
@media (max-width: 699px) { .step { flex-basis: 78vw; } }
@media (max-width: 999px) {
  .steps__pin { height: calc(100svh - 2rem); }
  .steps__head, .steps__account { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .steps { height: auto; padding-block: 3rem; }
  .steps__backdrop { position: absolute; inset: 0; height: auto; margin: 0; }
  .steps__pin { position: static; height: auto; overflow: visible; }
  .steps__stage { display: grid; gap: 1.5rem; padding-block: 3rem; }
  .steps__head, .steps__account { transform: none; }
  .steps__viewport { width: 100%; overflow: visible; }
  .steps__grid { display: grid; width: auto; transform: none !important; }
  .step { opacity: 1; transform: none; }
  /* Stopping the animation alone would strand every quote after the first
     one or two outside a 12000px track that the container clips. Hand the
     reader a scrollable strip instead, and drop the duplicated run — it
     exists only to make the loop seamless and is pure repetition once
     nothing is moving. Same fix, same reason, as the announcement bar. */
  .google-trust__track { animation: none; }
  .google-trust { overflow-x: auto; scrollbar-width: none; }
  .google-trust::-webkit-scrollbar { display: none; }
  .google-trust__track > [aria-hidden="true"]:not(.google-trust__stars):not(i) { display: none; }
}

/* ============================================================
   11. PROMO BANNER / REVIEWS / NEWSLETTER
   ============================================================ */
.promo {
  border: 1px solid var(--edge); border-radius: var(--radius);
  background: linear-gradient(120deg, var(--slab) 0%, #241318 62%, #33161a 100%);
  padding: clamp(1.8rem, 5vw, 3.2rem);
  display: grid; gap: 1.4rem; align-items: center;
}
@media (min-width: 880px) { .promo { grid-template-columns: 1.4fr auto; } }
.promo h2 { font-size: clamp(1.8rem, 4vw, 3rem); }
.promo p { color: var(--ash); margin-top: .7rem; max-width: 48ch; }

.category-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .7rem; }
/* Kept outside the animated section so this primary page heading is always
   visible, even if JavaScript is unavailable. Do not clip this wrapper: the
   display font deliberately has a small negative optical bearing. */
.category-heading {
  position: relative; z-index: 2; overflow: visible;
  padding-top: var(--section-y); padding-bottom: clamp(1.5rem, 3vw, 2.5rem);
}
.category-heading h2 {
  position: relative; z-index: 1; display: block; margin-bottom: 0;
  color: var(--chalk); -webkit-text-fill-color: var(--chalk);
  text-shadow: 0 2px 16px rgba(0,0,0,.42);
}
.category-heading + .section { position: relative; z-index: 1; padding-top: 0; }
.hours {
  display: grid; grid-template-columns: minmax(13rem, .55fr) minmax(0, 1.45fr);
  gap: clamp(1.25rem, 3vw, 3rem); align-items: center; margin-top: 1.25rem;
  margin-bottom: 2.5rem; padding: clamp(1.2rem, 3vw, 2rem);
  border: 1px solid var(--edge); border-radius: var(--radius); background: var(--slab);
}
.hours__heading h2 { margin: .28rem 0 .35rem; }
.hours__heading p { margin: 0; color: var(--ash); font-size: .88rem; }
.hours__schedule { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); margin: 0; }
.hours__schedule > div { min-width: 0; padding: .15rem .55rem; text-align: center; border-left: 1px solid var(--edge); }
.hours__schedule > div:first-child { border-left: 0; }
.hours__schedule dt { font-family: var(--f-label); font-size: .62rem; font-weight: 700; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--chalk); }
.hours__schedule dd { margin: .45rem 0 0; font-family: var(--f-label); font-size: .78rem; font-weight: 700; text-transform: uppercase; color: var(--live); }
.hours__schedule dd small { display: block; margin-top: .18rem; font-size: .7rem; color: var(--chalk); }
.hours__schedule .hours__closed { color: #ff5a5a; }
.category-tile { --tile-accent: var(--ember); --tile-h: clamp(9rem, 18vw, 13rem); position: relative; min-height: var(--tile-h); display: flex; flex-direction: column; justify-content: flex-end; gap: .25rem; overflow: hidden; padding: 1rem; border: 1px solid var(--edge); border-radius: var(--radius); background: linear-gradient(145deg, color-mix(in srgb, var(--tile-accent) 28%, var(--slab)), var(--slab)); color: var(--chalk); text-decoration: none; transition: transform .2s ease, border-color .2s ease; }
.category-tile::after { content: ''; position: absolute; width: 72%; aspect-ratio: 1; right: -22%; top: -36%; border: 1px solid color-mix(in srgb, var(--tile-accent) 62%, transparent); border-radius: 50%; box-shadow: inset 0 0 0 1.3rem color-mix(in srgb, var(--tile-accent) 11%, transparent); }
.category-tile:hover { transform: translateY(-3px); border-color: var(--tile-accent); }
.category-tile span { position: absolute; top: .85rem; left: .95rem; color: var(--tile-accent); font-family: var(--f-label); font-size: .65rem; font-weight: 700; letter-spacing: var(--track-label); }
.category-tile strong { position: relative; font-family: var(--f-display); font-size: clamp(1.45rem, 3vw, 2.4rem); line-height: .9; text-transform: uppercase; }
.category-tile small { position: relative; color: var(--ash); font-family: var(--f-label); font-size: .61rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.category-tile__asset { position: absolute; z-index: 0; object-fit: contain; pointer-events: none; filter: drop-shadow(0 .8rem .9rem rgba(0,0,0,.2)); }

/* ---------- TILE ART GEOMETRY ----------------------------------------
   Every number below is a multiple of --tile-h, the tile's own height.

   It used to be a mix: `width` and `right` were percentages (of the tile's
   WIDTH) while `top` was a percentage of its HEIGHT. That only holds while
   the tile keeps one aspect ratio. It does not — the tile runs 299x180 on a
   1000px desktop and 166x144 on a phone, so the ratio swings from 1.66 to
   1.15 and the compositions tore themselves apart: art sized off a width
   that had shrunk 45% while its offsets tracked a height that had shrunk
   only 20%, leaving every product cropped in half against the top edge.

   Driving size AND both offsets from a single dimension makes each tile a
   rigid composition that simply scales. Height is the right dimension to
   pick because it is the one under direct control (`--tile-h` is also the
   min-height) and because these tiles are always wider than they are tall,
   so height is the smaller axis at every breakpoint.

   Consequence worth knowing: .cat-next re-declares --tile-h for its smaller
   footer tiles, and the art now scales with them for free. Anything that
   changes a tile's height must change --tile-h, never min-height directly.
   -------------------------------------------------------------------- */
.category-tile__asset--nug-a  { width: calc(var(--tile-h) *  .897); right: calc(var(--tile-h) * -.083); top: calc(var(--tile-h) * -.12); transform: rotate(15deg); }
.category-tile__asset--nug-b  { width: calc(var(--tile-h) *  .698); right: calc(var(--tile-h) *  .382); top: calc(var(--tile-h) *  .13); transform: rotate(-14deg); }
.category-tile__asset--vape   { width: calc(var(--tile-h) *  .947); right: calc(var(--tile-h) * -.05);  top: calc(var(--tile-h) * -.10); transform: rotate(17deg); }
/* Carts: the battery sits BEHIND the cartridge and has to overlap it to read
   that way. Both packshots are narrow vertical strips floating in a much
   wider transparent canvas — the cartridge's ink occupies only x 42–58% of
   its own box, the battery's x 40–60% — so two boxes that overlap generously
   can still leave the two products with clear air between them. That is what
   the first attempt did: the boxes overlapped, the ink did not, and it read
   as two objects side by side. These offsets are set from the INK, not the
   box: the battery's strip lands just left of the cartridge's and slips
   about half its width behind it. Measure the ink before moving either. */
.category-tile__asset--battery{ width: calc(var(--tile-h) *  .46);  right: calc(var(--tile-h) *  .276); top: calc(var(--tile-h) *  .02); transform: rotate(-17deg); }
/* Vapes and Pre-Rolls are both sets of two: one slants back and high, the
   other forward and lower, so they read as depth rather than as two
   products. DOM order does the stacking — every asset here is z-index 0, so
   the back one must be emitted first. */
.category-tile__asset--pack   { width: calc(var(--tile-h) *  .631); right: calc(var(--tile-h) *  .166); top: calc(var(--tile-h) * -.27); transform: rotate(-15deg); }
.category-tile__asset--device { width: calc(var(--tile-h) *  .831); right: calc(var(--tile-h) * -.066); top: calc(var(--tile-h) * -.06); transform: rotate(14deg); }
.category-tile__asset--box    { width: calc(var(--tile-h) *  .698); right: calc(var(--tile-h) *  .266); top: calc(var(--tile-h) * -.13); transform: rotate(15deg); }
.category-tile__asset--joint  { width: calc(var(--tile-h) * 1.080); right: calc(var(--tile-h) * -.133); top: calc(var(--tile-h) * -.11); transform: rotate(-26deg); }
.category-tile__asset--jar    { width: calc(var(--tile-h) *  .831); right: calc(var(--tile-h) * -.116); top: calc(var(--tile-h) * -.13); transform: rotate(-9deg); }
/* Edibles: the gummy bag is emitted before --rope and peeks out below it.
   The bag art was supplied as a JPEG on white and is knocked out in
   media/ambient/puff-gummy-bag.webp — a white rectangle on the tile's
   coloured gradient would read as a broken image. */
.category-tile__asset--gummy  { width: calc(var(--tile-h) *  .565); right: calc(var(--tile-h) * -.05);  top: calc(var(--tile-h) *  .44); transform: rotate(15deg); }
.category-tile__asset--rope   { width: calc(var(--tile-h) * 1.428); right: calc(var(--tile-h) * -.316); top: calc(var(--tile-h) * -.28); transform: rotate(-9deg); }
/* Mushrooms has no product cut-out in the library, so its tile and its hero
   both carry the brand lockup rather than leaving one tile bare next to six
   decorated ones. */
.category-tile__asset--mark   { width: calc(var(--tile-h) *  .664); right: calc(var(--tile-h) * -.033); top: calc(var(--tile-h) * -.08); transform: rotate(-6deg); }
.category-tile--carts { --tile-accent: #E0765F; }.category-tile--vapes { --tile-accent: #4DB8A4; }.category-tile--edibles { --tile-accent: #D562A4; }.category-tile--concentrates { --tile-accent: var(--gold); }.category-tile--prerolls { --tile-accent: #61A9CF; }.category-tile--mushrooms { --tile-accent: #9BB067; }
@media (min-width: 700px) { .category-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 699px) { .category-heading { padding-top: clamp(3rem, 12vw, 4.5rem); } }
@media (max-width: 899px) {
  .hours { grid-template-columns: 1fr; }
  .hours__schedule { grid-template-columns: repeat(4, minmax(0, 1fr)); row-gap: 1rem; }
  .hours__schedule > div:nth-child(5) { border-left: 0; }
}
@media (max-width: 499px) {
  .hours__schedule { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hours__schedule > div { border-left: 0; border-top: 1px solid var(--edge); padding: .65rem .3rem; }
  .hours__schedule > div:nth-child(-n+2) { border-top: 0; }
}
.promo--referral {
  position: relative; isolation: isolate; min-height: clamp(23rem, 38vw, 31rem); overflow: hidden;
  display: flex; align-items: center; border-color: color-mix(in srgb, var(--ember) 50%, var(--edge));
  background: linear-gradient(118deg, var(--slab) 0%, color-mix(in srgb, var(--ember) 15%, var(--slab)) 58%, var(--slab) 100%);
}
.promo__content { position: relative; z-index: 1; max-width: 43rem; }
.promo--referral .eyebrow { color: var(--ember); }
.promo--referral h2 { margin-top: .4rem; font-size: clamp(2.8rem, 6.4vw, 5.8rem); }
.promo--referral h2 em { color: var(--ember); font-style: normal; }
.promo--referral p { font-size: clamp(1rem, 1.7vw, 1.2rem); color: var(--ash); }
.promo__actions { display: flex; flex-wrap: wrap; gap: .7rem; margin-top: 1.65rem; }
.promo--referral .promo__actions .btn--ghost { border-color: #fff; color: #fff; }
.promo__nug { position: absolute; z-index: -1; right: clamp(-8rem, -3vw, -1rem); top: 50%; width: clamp(18rem, 43vw, 41rem); transform: translateY(-50%) rotate(-13deg); opacity: .4; filter: saturate(1.25) drop-shadow(0 2rem 2rem rgba(0,0,0,.25)); }
@media (max-width: 699px) {
  .promo--referral { min-height: 22rem; align-items: flex-start; }
  .promo__content { max-width: 30rem; }
  .promo__nug { right: -9rem; top: 66%; width: 29rem; opacity: .25; }
}

.featured-perm {
  --featured-accent: #75B103;
  position: relative; isolation: isolate; overflow: hidden; min-height: clamp(28rem, 48vw, 38rem);
  border-block: 1px solid var(--edge); background: var(--void);
  display: flex; align-items: center; padding: clamp(1.6rem, 5vw, 4rem);
}
.featured-perm__video { position: absolute; z-index: -3; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center; }
.featured-perm__scrim { position: absolute; z-index: -2; inset: 0; background: linear-gradient(90deg, rgba(5,10,9,.94) 0%, rgba(5,10,9,.76) 43%, rgba(5,10,9,.20) 72%, rgba(5,10,9,.12) 100%); }
.featured-perm__content { max-width: 34rem; }
.featured-perm__content .eyebrow { color: var(--featured-accent); }
.featured-perm__content h2 { margin-top: .55rem; font-size: clamp(3rem, 7vw, 6.5rem); color: var(--featured-accent); }
.featured-perm__content p { margin-top: .8rem; max-width: 38ch; color: rgba(255,255,255,.82); }
.featured-perm__offer { display: grid; gap: .2rem; margin-block: 1.4rem !important; }
.featured-perm__offer strong { color: var(--featured-accent); font-family: var(--f-label); font-size: 1.15rem; letter-spacing: .04em; }
.featured-perm__offer span { color: #fff; font-family: var(--f-label); font-size: .67rem; font-weight: 700; letter-spacing: var(--track-label); text-transform: uppercase; }
.featured-perm .btn--primary { background: var(--featured-accent); border-color: var(--featured-accent); color: #071000; }
.featured-perm .btn--primary:hover { background: #8DCB12; border-color: #8DCB12; color: #071000; }
.featured-perm__jar { position: absolute; z-index: -1; right: clamp(-4rem, -2vw, 0rem); top: 50%; width: clamp(21rem, 43vw, 44rem); transform: translateY(-50%); filter: drop-shadow(0 2rem 1.8rem rgba(0,0,0,.45)); }
@media (max-width: 699px) {
  .featured-perm { align-items: flex-start; min-height: 36rem; padding-top: 2rem; }
  .featured-perm__scrim { background: linear-gradient(180deg, rgba(5,10,9,.9) 0%, rgba(5,10,9,.66) 50%, rgba(5,10,9,.8) 100%); }
  .featured-perm__jar { right: -8rem; top: 58%; width: 28rem; opacity: .72; }
}

.reviews { display: grid; gap: 1.25rem; grid-template-columns: 1fr; }
@media (min-width: 760px)  { .reviews { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .reviews { grid-template-columns: repeat(3, 1fr); } }
.review {
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: 1.6rem;
  display: flex; flex-direction: column; gap: .85rem;
}
.review__stars { color: var(--gold); letter-spacing: .12em; }
.review p { font-size: var(--t-small); }
.review__who {
  margin-top: auto; font-family: var(--f-label); font-size: .66rem;
  text-transform: uppercase; letter-spacing: var(--track-label); color: var(--ash);
}
.reviews__cta { display: flex; justify-content: center; margin-top: clamp(1.5rem, 3vw, 2.5rem); }
/* White outline, for the same reason the hero's ghost button carries one:
   --edge is a near-black hairline tuned to sit quietly against the page
   ground, which leaves this reading as floating text with no affordance —
   and this is the one control on the page that asks for something back. */
.reviews__cta .btn--ghost { border-color: #fff; }
.reviews__cta .btn--ghost:hover,
.reviews__cta .btn--ghost:focus-visible { border-color: var(--ember); color: var(--ember); }

.review-form-dialog { position: fixed; z-index: var(--z-gate); inset: 0; display: grid; place-items: center; padding: 1rem; }
.review-form-dialog__scrim { position: absolute; inset: 0; background: rgba(0,0,0,.72); backdrop-filter: blur(6px); }
.review-form-dialog__panel { position: relative; width: min(34rem, 100%); max-height: calc(100svh - 2rem); overflow-y: auto; padding: clamp(1.4rem, 5vw, 2.25rem); border: 1px solid var(--edge); border-radius: var(--radius); background: var(--slab); box-shadow: var(--shadow); }
.review-form-dialog__close { position: absolute; top: .8rem; right: .8rem; width: 2.25rem; height: 2.25rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); color: var(--chalk); font-size: 1.45rem; line-height: 1; cursor: pointer; }
.review-form-dialog__panel > h2 { margin-top: .3rem; font-size: clamp(2.2rem, 7vw, 3.7rem); }
.review-form-dialog__panel > p { margin-top: .75rem; color: var(--ash); }
.review-form-dialog__form { display: grid; gap: 1rem; margin-top: 1.35rem; }
.review-form-dialog__form textarea, .review-form-dialog__form select { width: 100%; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); color: var(--chalk); padding: .75rem .85rem; resize: vertical; }
.review-form-dialog__anonymous { display: flex; align-items: start; gap: .6rem; color: var(--chalk); font-size: var(--t-small); cursor: pointer; }
.review-form-dialog__anonymous input { margin-top: .28rem; accent-color: var(--ember); }
.review-form-dialog__privacy, .review-form-dialog__status { color: var(--ash); font-size: .76rem; line-height: 1.5; }
.review-form-dialog__status:not(:empty) { padding: .7rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); }

/* The .telegram-connect modal was removed with showTelegramConnect() in
   app.js — connecting Telegram is an opt-in card on the account page now
   (.acctcard--wide below), not an interstitial after registration. */

.signup { display: grid; gap: 1.5rem; align-items: center; }
@media (min-width: 880px) { .signup { grid-template-columns: 1fr 1fr; } }
.signup form { display: flex; gap: .5rem; flex-wrap: wrap; }
.signup input {
  flex: 1; min-width: 12rem; background: var(--slab);
  border: 1px solid var(--edge); border-radius: var(--radius-ctl);
  padding: .9rem 1.2rem;
}
.signup .fine { margin-top: .8rem; font-size: .78rem; color: var(--ash); }

/* ============================================================
   12. SHOP PAGE — filter rail
   ============================================================ */
.shopbar {
  position: sticky; top: var(--header-stack); z-index: var(--z-sticky);
  background: transparent;
  border-bottom: 1px solid var(--edge);
  padding-block: .95rem;
}
/* Category pages opt out. Their rail sits directly under a full-bleed hero,
   and pinning it there parks a second bar under the header for the whole
   scroll — on a phone that is two fixed strips eating the top of a viewport
   that is mostly product. The chips are one tap from the top of the page,
   and the page is a single shelf, so there is nothing to keep in reach. */
.shopbar--static { position: static; z-index: auto; }
.category-filter { position: relative; width: min(100%, 24rem); }
.category-filter summary {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  min-height: 2.8rem; padding: .55rem 1rem; cursor: pointer; list-style: none;
  border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--slab);
  color: var(--chalk); font-family: var(--f-label); font-size: .7rem; font-weight: 700;
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.category-filter summary::-webkit-details-marker { display: none; }
.category-filter summary::after { content: '+'; margin-left: auto; color: var(--ember); font-size: 1.1rem; }
.category-filter[open] summary { border-color: var(--ember); border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.category-filter[open] summary::after { content: '–'; }
#category-filter-status { color: var(--ash); font-size: .6rem; letter-spacing: .04em; text-transform: none; }
.category-filter__panel {
  position: absolute; z-index: var(--z-sticky); left: 0; top: 100%; width: min(24rem, calc(100vw - 2rem));
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .35rem; padding: .8rem;
  border: 1px solid var(--ember); border-top: 0; border-radius: 0 0 var(--radius-ctl) var(--radius-ctl);
  background: var(--slab); box-shadow: var(--shadow);
}
.category-filter__panel label { display: flex; align-items: center; gap: .5rem; padding: .45rem; color: var(--ash); font-size: .75rem; line-height: 1.25; cursor: pointer; }
.category-filter__panel input { accent-color: var(--ember); }
.category-filter__clear { grid-column: 1 / -1; justify-self: start; border: 0; padding: .35rem .45rem; background: transparent; color: var(--ember); cursor: pointer; font: 700 .65rem var(--f-label); letter-spacing: .05em; text-transform: uppercase; }
.shopbar__row { display: flex; gap: .9rem; align-items: center; min-width: 0; }
.shopbar select {
  flex: none; background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius-ctl); padding: .5rem .9rem;
  font-family: var(--f-label); font-size: .66rem; text-transform: uppercase;
  letter-spacing: var(--track-label);
}
@media (max-width: 520px) {
  .shopbar { padding-block: .7rem; }
  .shopbar__row { align-items: stretch; flex-direction: column; gap: .55rem; }
  .category-filter, .category-filter__panel { width: 100%; }
  .category-filter__panel { grid-template-columns: 1fr; max-height: min(60svh, 27rem); overflow-y: auto; }
  .shopbar select { width: 100%; min-height: 2.8rem; }
}
.result-count {
  font-family: var(--f-label); font-size: .68rem; color: var(--ash);
  letter-spacing: .06em; margin-bottom: 1.2rem;
}
.catalog-sections { display: grid; gap: clamp(3.5rem, 7vw, 6rem); }
.catalog-section { min-width: 0; scroll-margin-top: calc(var(--header-stack) + 1rem); }
.catalog-section__head {
  display: flex; justify-content: space-between; align-items: end; gap: 1rem;
  margin-bottom: 1.1rem; padding-bottom: .9rem; border-bottom: 1px solid var(--edge);
}
.catalog-section__head h2 { font-size: clamp(1.65rem, 2.7vw, 2.7rem); }
.catalog-section__count {
  flex: none; padding: .45rem .65rem; border: 1px solid var(--ash); border-radius: var(--radius-ctl);
  color: var(--ash); font-family: var(--f-label); font-size: .65rem; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
}
/* Desktop: product listings wrap into the base .grid's responsive
   columns (2/3/4 up per viewport — see section 8), tightened further here
   because a shelf of 40+ tiles reads better dense than a homepage
   showcase does — 4-across at 1080px still left each tile pushing
   300px, oversized for what is basically a name, a price and an add
   button. This scope (.catalog-section .catalog-section__grid) only
   touches product-listing shelves — it does not reach the homepage
   "popular" strip or the product modal, which keep the roomier base
   .grid sizing on purpose. */
.catalog-section .catalog-section__grid { gap: clamp(.7rem, 1.3vw, 1.15rem); }
@media (min-width: 1080px) { .catalog-section .catalog-section__grid { grid-template-columns: repeat(5, 1fr); } }
@media (min-width: 1400px) { .catalog-section .catalog-section__grid { grid-template-columns: repeat(6, 1fr); } }
/* Product listings stay in a compact centred grid at every viewport. */
@media (max-width: 699px) {
  .catalog-section .catalog-section__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem;
    justify-content: center;
  }
  .catalog-section .catalog-section__grid > * { min-width: 0; }
}
@media (max-width: 520px) {
  .catalog-section__head { align-items: start; flex-direction: column; gap: .65rem; }
}
/* All-products head. Declared AFTER the 520px block on purpose: that block
   sets align-items/flex-direction on the base class at equal specificity,
   so anything written before it would be silently reverted on a phone —
   the exact viewport where a centred title matters most.

   The rule under the head is a short centred segment rather than the full
   underline the multi-shelf pages use. Eleven full-width rules stacked down
   a long page start to read as dividers between unrelated pages; a short
   one reads as punctuation belonging to the title above it. */
.catalog-section__head--center {
  flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: .45rem;
  margin-bottom: 1.4rem; padding-bottom: 0; border-bottom: 0;
}
.catalog-section__head--center h2 { margin: 0; }
.catalog-section__tagline {
  max-width: 46ch; margin: 0;
  color: var(--ash); font-size: clamp(.9rem, 1.4vw, 1rem); line-height: 1.5;
  text-wrap: balance;
}
.catalog-section__head--center .catalog-section__count { margin-top: .25rem; }
/* Divider under the centred all-products head was removed at operator
   request; the count pill now reads as its own punctuation. */
.empty { text-align: center; padding: 4rem 0; color: var(--ash); }

/* ============================================================
   13. CART DRAWER
   ============================================================ */
.drawer { position: fixed; inset: 0; z-index: var(--z-drawer); }
.drawer[hidden] { display: none; }
.drawer__scrim { position: absolute; inset: 0; background: rgba(0,0,0,.65); }
.drawer__panel {
  position: absolute; inset-block: 0; right: 0; width: min(94vw, 25rem);
  background: var(--slab); border-left: 1px solid var(--edge);
  display: flex; flex-direction: column;
}
.drawer__head {
  display: flex; align-items: center; gap: .8rem;
  padding: 1rem 1.1rem; border-bottom: 1px solid var(--edge);
}
.drawer__head h2 { font-size: 1.2rem; margin-right: auto; }

/* Reservation timer — 20 minutes, because that is roughly how long a real
   hold lasts. A 5-minute fake countdown that resets on reload teaches the
   customer the site lies to them. */
.timer {
  padding: .6rem 1.1rem; background: var(--slab-2);
  border-bottom: 1px solid var(--edge);
  font-family: var(--f-label); font-size: .68rem;
  text-transform: uppercase; letter-spacing: var(--track-label); color: var(--ash);
}
.timer strong { color: var(--gold); }
.timer.is-expired strong { color: var(--warn); }

/* Threshold progress — raises AOV, and the travelling badge makes progress
   legible without reading the sentence. */
.progress { padding: .9rem 1.1rem; border-bottom: 1px solid var(--edge); }
.progress__txt { font-size: .8rem; color: var(--ash); margin-bottom: .55rem; }
.progress__txt b { color: var(--chalk); }
.progress__track {
  position: relative; height: 6px; border-radius: var(--radius-pill);
  background: var(--slab-2); border: 1px solid var(--edge);
}
.progress__fill {
  position: absolute; inset-block: 0; left: 0; width: 0;
  border-radius: var(--radius-pill); background: var(--ember-lo);
  transition: width .35s ease;
}
.progress.is-hit .progress__fill { background: var(--live); }
@media (prefers-reduced-motion: reduce) { .progress__fill { transition: none; } }

.drawer__body { flex: 1; overflow-y: auto; padding: .4rem 1.1rem; }
.line { display: flex; gap: .95rem; padding: 1.1rem 0;
        border-bottom: 1px solid var(--edge); }
/* Same plate treatment in the cart, at thumbnail size — and note the wrapper.
   The plate has to live on a PARENT, never on the blended element itself:
   mix-blend-mode blends an element (its background included) with whatever is
   painted behind it, so an img carrying its own plate multiplies against the
   dark drawer and comes out muddy. Plate on the wrapper, blend on the image,
   `isolation: isolate` to stop the blend reaching past it. */
.line__thumb {
  width: 62px; height: 62px; flex: none;
  border-radius: var(--radius); background: var(--plate);
  overflow: hidden; isolation: isolate;
}
.line__img { width: 100%; height: 100%; object-fit: contain; padding: 5px;
             mix-blend-mode: multiply; }
.line__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .3rem; }
.line__name { font-size: .86rem; font-weight: 600; line-height: 1.3; }
.line__price { font-family: var(--f-label); font-size: .78rem; color: var(--ash); }
.qty { display: inline-flex; align-items: center; border: 1px solid var(--edge);
       border-radius: var(--radius-ctl); overflow: hidden; }
.qty button { width: 28px; height: 28px; background: none; border: 0;
              cursor: pointer; color: var(--ash); }
.qty button:hover { color: var(--ember); }
.qty span { font-family: var(--f-label); font-size: .74rem; min-width: 1.6rem;
            text-align: center; }
/* Editable qty input styled to sit where the read-only <span> used to —
   same font, same size, same width — so the drawer reads unchanged and
   only clicking into the number reveals it is now typable. */
.qty__input { width: 1.8rem; min-width: 1.6rem; height: auto; padding: 0;
              border: 0; background: transparent; color: inherit;
              font-family: var(--f-label); font-size: .74rem; text-align: center;
              -moz-appearance: textfield; }
.qty__input::-webkit-outer-spin-button,
.qty__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qty__input:focus { outline: none; color: var(--ember); }
.line__remove {
  align-self: flex-start; padding: 0; border: 0; background: none;
  color: var(--ash); font-family: var(--f-label); font-size: .62rem;
  letter-spacing: .04em; text-transform: uppercase; cursor: pointer;
}
.line__remove:hover, .line__remove:focus-visible { color: var(--ember); text-decoration: underline; }
.drawer__empty { text-align: center; color: var(--ash); padding: 3rem 1rem;
                 font-size: var(--t-small); }
.drawer__foot { border-top: 1px solid var(--edge); padding: 1rem 1.1rem;
                display: grid; gap: .7rem; }
.drawer__total { display: flex; justify-content: space-between; align-items: baseline;
                 font-family: var(--f-label); font-size: .9rem; font-weight: 700; }
.drawer__fine { font-size: .74rem; color: var(--ash); }

/* ============================================================
   PICKUP CHECKOUT + TOP SOCIAL LINKS
   ============================================================ */
.social-strip { background: var(--void); border-bottom: 1px solid var(--edge); }
.social-strip__inner { min-height: 2rem; display: flex; justify-content: flex-end; align-items: center; gap: 1rem; font-family: var(--f-label); font-size: .62rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash); }
.social-strip a { color: var(--chalk); text-decoration: none; }
.social-strip a:hover, .social-strip a:focus-visible { color: var(--ember); }
.checkout { position: fixed; inset: 0; z-index: calc(var(--z-drawer) + 2); display: grid; place-items: center; padding: 1rem; }
.checkout__scrim { position: absolute; inset: 0; background: rgba(0,0,0,.78); }
.checkout__panel { position: relative; width: min(100%, 48rem); max-height: calc(100vh - 2rem); overflow: auto; border: 1px solid var(--edge); border-radius: var(--radius); background: var(--slab); padding: clamp(1.35rem, 4vw, 2.4rem); box-shadow: 0 1.5rem 4rem rgba(0,0,0,.5); }
.checkout__close { position: absolute; top: .7rem; right: .85rem; width: 2.5rem; height: 2.5rem; border: 0; background: transparent; color: var(--chalk); font-size: 2rem; cursor: pointer; }
.checkout__intro { max-width: 38rem; padding-right: 2rem; }
.checkout__intro h2 { margin: .4rem 0 .55rem; }
.checkout__intro p, .checkout__privacy { color: var(--ash); font-size: .86rem; }
.checkout__form { display: grid; gap: 1.15rem; margin-top: 1.35rem; }
.checkout fieldset { margin: 0; padding: 1rem; border: 1px solid var(--edge); border-radius: var(--radius); }
.checkout legend { padding: 0 .35rem; font-family: var(--f-label); font-size: .7rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ember); }
.checkout__grid { display: grid; gap: .9rem; }
.checkout .field { font-size: .82rem; color: var(--chalk); }
.checkout .field input, .checkout .field select { width: 100%; min-height: 2.8rem; padding: .65rem .75rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); color: var(--chalk); font: inherit; }
.checkout .field input:focus, .checkout .field select:focus { outline: 2px solid var(--ember); outline-offset: 2px; }
.checkout__check { display: flex; gap: .65rem; align-items: flex-start; color: var(--ash); font-size: .82rem; line-height: 1.45; }
.checkout__check input { margin-top: .18rem; accent-color: var(--ember); }
.checkout__privacy { margin: .9rem 0 0; }
.checkout__wholesale { margin: 0; padding: .6rem .85rem; border-radius: var(--radius-ctl); border: 1px solid color-mix(in srgb, var(--gold) 55%, var(--edge)); background: color-mix(in srgb, var(--gold) 8%, transparent); color: var(--chalk); font-size: .82rem; line-height: 1.45; }
.checkout__idv { display: grid; gap: .6rem; padding: .9rem 1rem; border: 1px solid var(--edge); border-radius: var(--radius); background: color-mix(in srgb, var(--ember) 6%, transparent); }
.checkout__idv--ok { border-color: color-mix(in srgb, var(--gold) 55%, var(--edge)); background: color-mix(in srgb, var(--gold) 8%, transparent); grid-template-columns: 1fr auto; align-items: center; }
.checkout__idv p { margin: .2rem 0 0; color: var(--ash); font-size: .82rem; line-height: 1.45; }
.checkout__idv-badge { font-family: var(--f-label); font-size: .62rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--gold); }
.checkout__idv-badge--warn { color: var(--ember); }
.checkout__idv-upload { justify-self: start; cursor: pointer; }
.checkout__idv-err { margin: 0; color: var(--ember); font-size: .8rem; }
.checkout__idv-err[hidden] { display: none; }
.idv-field { margin: 0; padding: .9rem 1rem; border: 1px solid var(--edge); border-radius: var(--radius); background: color-mix(in srgb, var(--ember) 5%, transparent); display: grid; gap: .55rem; }
.idv-field legend { padding: 0 .4rem; font-family: var(--f-label); font-size: .68rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--chalk); }
.idv-field legend .field__hint { color: var(--ash); text-transform: none; letter-spacing: 0; margin-left: .3rem; font-size: .68rem; }
.idv-field__note { margin: 0; color: var(--ash); font-size: .8rem; line-height: 1.5; }
.idv-field__upload { justify-self: start; cursor: pointer; }
.idv-field__err { margin: 0; color: var(--ember); font-size: .8rem; }
.idv-field__err[hidden] { display: none; }

/* Email confirmation landing page (verify.html). One card, centred, no
   competing calls to action — the visitor arrived from their inbox to
   close a single loop. */
.verify { display: grid; place-items: center; min-height: 60vh; }
.verify__card { width: min(100%, 34rem); text-align: center; display: grid; gap: .9rem; padding: clamp(1.6rem, 4vw, 2.6rem); border: 1px solid var(--edge); border-radius: var(--radius); background: rgba(255, 255, 255, .02); }
.verify__card h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); line-height: 1.05; }
.verify__card .lede { margin: 0; }
.verify__code { display: grid; gap: .4rem; padding: 1rem; border: 1px dashed var(--edge); border-radius: var(--radius-ctl); background: color-mix(in srgb, var(--ember) 7%, transparent); }
.verify__code[hidden] { display: none; }
.verify__code span { font-family: var(--f-label); font-size: .6rem; letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ash); }
.verify__code strong { font-family: var(--f-label); font-size: 1.5rem; letter-spacing: .12em; color: var(--ember); }
.verify__actions { display: flex; flex-wrap: wrap; gap: .6rem; justify-content: center; margin-top: .4rem; }
.verify__actions[hidden] { display: none; }
/* State accents. The card border is the only thing that changes colour —
   the copy already says what happened, so this is reinforcement, not the
   sole signal (which would fail for anyone who cannot distinguish them). */
[data-verify-state="ok"] .verify__card { border-color: color-mix(in srgb, var(--gold) 55%, var(--edge)); }
[data-verify-state="expired"] .verify__card,
[data-verify-state="invalid"] .verify__card { border-color: color-mix(in srgb, var(--ember) 55%, var(--edge)); }
.checkout__summary { border: 1px solid var(--edge); border-radius: var(--radius); padding: 1rem; background: var(--void); }
.checkout__summary h3 { font-size: .95rem; margin-bottom: .8rem; }
.checkout__summary ul { display: grid; gap: .55rem; margin: 0; padding: 0; list-style: none; }
.checkout__summary li, .checkout__summary p { display: flex; justify-content: space-between; gap: 1rem; font-size: .82rem; }
.checkout__summary p { margin: .9rem 0 0; padding-top: .8rem; border-top: 1px solid var(--edge); }
.checkout__summary strong { color: var(--ember); white-space: nowrap; }
.checkout__status { min-height: 1.3em; margin: 0; color: var(--gold); font-size: .85rem; }
@media (min-width: 640px) { .checkout__grid { grid-template-columns: 1fr 1fr; } .checkout__wide { grid-column: 1 / -1; } }
@media (max-width: 520px) { .social-strip__inner { justify-content: center; gap: .65rem; } .social-strip__inner span { display: none; } .checkout { padding: 0; } .checkout__panel { max-height: 100vh; border-radius: 0; } }

/* ============================================================
   14. MOBILE ACTION BAR
   Most traffic is mobile and in-the-moment. A persistent action affordance
   converts better than anything else on the page.
   ============================================================ */
.actionbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-actionbar);
  display: grid; grid-template-columns: 1fr 1fr; gap: .5rem;
  padding: .55rem var(--gutter) calc(.55rem + env(safe-area-inset-bottom));
  background: rgba(11,11,13,.94); backdrop-filter: blur(10px);
  border-top: 1px solid var(--edge);
  opacity: 0; pointer-events: none; transform: translateY(100%);
  transition: opacity .2s ease, transform .2s ease;
}
.actionbar.is-shown { opacity: 1; pointer-events: auto; transform: translateY(0); }
@media (min-width: 1000px) { .actionbar { display: none; } }
@media (max-width: 999px)  { body { padding-bottom: 4.6rem; } }

/* Support placeholder: presentation and local-only interaction live here
   until a chosen chat provider supplies the real API/widget. */
/* ---------- SUPPORT / HELP CLUSTER ----------------------------------
   Was .support-chat, back when the panel was a mock chat with a text input.
   It is a pre-loaded FAQ now (see supportFaq() in app.js) so the name went
   with it — but the CLUSTER GEOMETRY below is load-bearing and must not
   drift: bottom 1.25rem on desktop, 5.35rem on mobile, 3rem controls. Two
   other components were positioned against those exact numbers after
   measuring real overlaps — .totop in §23 and the hero CTA in §25. Move the
   cluster and you silently put it back on top of them. */
/* .support-chat is the OLD class name, kept only as a safety alias. app.js
   and styles.css are separate cache entries, so a browser can briefly hold a
   stale app.js emitting .support-chat markup against a fresh stylesheet that
   only knows .support-help. Without this the cluster loses `position: fixed`
   and lands at the end of the document as a full-size unstyled SVG under the
   footer. Costs a few bytes; delete it once you are confident no cached copy
   of the old script is in the wild. */
.support-help, .support-chat { position: fixed; z-index: var(--z-actionbar); right: clamp(1rem, 2.2vw, 2rem); bottom: 1.25rem; }
.support-chat svg { width: 1.1rem; height: 1.1rem; }
.support-chat__panel { display: none; }
.support-help__actions { display: flex; align-items: center; justify-content: flex-end; gap: .55rem; }
.support-help__launcher { display: flex; align-items: center; gap: .55rem; min-height: 3rem; padding: .65rem .9rem; border: 1px solid var(--ember); border-radius: var(--radius-ctl); background: var(--ember); color: var(--on-ember); box-shadow: var(--shadow); font-family: var(--f-label); font-size: .66rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; cursor: pointer; }
.support-help__launcher span { font-size: 1rem; font-weight: 700; line-height: 1; }

.support-help__panel { position: absolute; right: 0; bottom: calc(100% + .7rem); width: min(23rem, calc(100vw - 2rem)); overflow: hidden; border: 1px solid var(--edge); border-radius: var(--radius); background: var(--slab); box-shadow: var(--shadow); }
.support-help__panel[hidden] { display: none; }
.support-help__panel header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .9rem 1rem; background: var(--void); color: var(--chalk); }
.support-help__panel header span, .support-help__panel header small { display: block; font-family: var(--f-label); font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.support-help__panel header span { font-size: .68rem; }
.support-help__panel header small { margin-top: .2rem; color: var(--ash); font-size: .55rem; }
.support-help__panel header button { flex: none; width: 2rem; height: 2rem; border: 0; background: transparent; color: var(--chalk); font-size: 1.5rem; line-height: 1; cursor: pointer; }

/* Capped and scrollable: eight questions collapse short, but an open answer
   on a small phone would otherwise push the panel past the top of the
   viewport, where it cannot be scrolled back into view. */
.support-help__list { max-height: min(24rem, 52svh); overflow-y: auto; overscroll-behavior: contain; }
.support-help__qa { border-bottom: 1px solid var(--edge); }
.support-help__qa summary {
  display: flex; align-items: center; justify-content: space-between; gap: .7rem;
  min-height: 2.9rem; padding: .7rem 1rem; cursor: pointer; list-style: none;
  color: var(--chalk); font-family: var(--f-label); font-size: .74rem; font-weight: 700; line-height: 1.3;
}
.support-help__qa summary::-webkit-details-marker { display: none; }
.support-help__qa summary::after { content: '+'; flex: none; color: var(--ember); font-size: 1.1rem; line-height: 1; }
.support-help__qa[open] summary { color: var(--ember); }
.support-help__qa[open] summary::after { content: '\2013'; }
.support-help__qa p { margin: 0; padding: 0 1rem .9rem; color: var(--ash); font-size: .78rem; line-height: 1.55; }
.support-help__qa:hover summary { background: var(--slab-2); }

.support-help__foot { display: flex; flex-wrap: wrap; gap: .25rem 1rem; padding: .8rem 1rem; background: var(--void); }
.support-help__foot a { color: var(--ember); font-family: var(--f-label); font-size: .64rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; text-decoration: none; }
.support-help__foot a:hover { text-decoration: underline; }
@media (max-width: 999px) { .support-help { right: 1rem; bottom: 5.35rem; } .support-help__launcher b { display: none; } .support-help__launcher { width: 3rem; justify-content: center; padding: 0; border-radius: 50%; } }

/* ============================================================
   15. FOOTER
   ============================================================ */
.footer { border-top: 1px solid var(--edge); background: var(--slab);
          padding-block: clamp(2.5rem, 5vw, 4rem) 2rem; }
.footer__grid { display: grid; gap: 2rem; grid-template-columns: 1fr; }
@media (max-width: 759px) {
  .footer__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem .8rem; }
  .footer__grid > :first-child { grid-column: 1 / -1; }
  .footer h3 { font-size: .8rem; margin-bottom: .65rem; }
  .footer ul { gap: .42rem; }
  .footer a, .footer li { font-size: .76rem !important; overflow-wrap: anywhere; }
}
@media (min-width: 760px)  { .footer__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .footer__grid { grid-template-columns: 1.4fr 1fr 1fr 1fr; } }
.footer h3 { font-size: .95rem; margin-bottom: .9rem; }
.footer ul { list-style: none; padding: 0; display: grid; gap: .5rem; }
.footer a { text-decoration: none; color: var(--ash); font-size: var(--t-small); }
.footer a:hover { color: var(--ember); }
.footer .brand {
  width: clamp(11rem, 18vw, 14rem); height: clamp(3.5rem, 5.6vw, 4.5rem);
  margin: 0 0 1rem; background-position: left center;
}
.footer__legal {
  margin-top: 2.5rem; padding-top: 1.4rem; border-top: 1px solid var(--edge);
  display: flex; flex-wrap: wrap; gap: .8rem 1.4rem; align-items: center;
  font-size: .78rem; color: var(--ash);
}
.footer__legal a { font-size: .78rem; }
.compliance {
  margin-top: 1.2rem; font-size: .74rem; color: var(--ash); line-height: 1.6;
  max-width: 80ch;
}
.compliance strong { color: var(--warn); }

/* Compact, standalone pages used for unmatched URLs and the edge geo fence. */
.utility-page { min-height: 100svh; display: grid; place-items: center; padding: max(1.25rem, env(safe-area-inset-top)) var(--gutter) max(1.25rem, env(safe-area-inset-bottom)); }
.utility-page__main { width: min(100%, 38rem); padding: clamp(1.5rem, 7vw, 4rem); border: 1px solid var(--edge); border-radius: var(--radius); background: linear-gradient(145deg, var(--slab), var(--void)); box-shadow: var(--shadow); }
.utility-page__brand { width: clamp(10rem, 40vw, 13rem); height: clamp(3.25rem, 13vw, 4rem); margin: 0 0 clamp(2.5rem, 8vw, 4rem); }
.utility-page h1 { font-size: clamp(3.2rem, 12vw, 6rem); }
.utility-page .lede { margin-top: 1rem; }
.utility-page__actions { display: flex; flex-wrap: wrap; gap: .7rem; margin-top: 2rem; }
.utility-page__note { margin-top: 1.25rem; color: var(--ash); font-size: var(--t-small); }
@media (max-width: 420px) { .utility-page__main { padding: 1.5rem; } .utility-page__actions .btn { width: 100%; } }

/* ============================================================
   16. FAQ PAGE
   ============================================================ */
.faq { display: grid; gap: .6rem; max-width: 60rem; }
.faq details {
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: 0 1.1rem;
}
.faq summary {
  cursor: pointer; list-style: none; padding: 1.05rem 0;
  font-weight: 600; font-size: var(--t-small);
  display: flex; align-items: center; gap: .8rem;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; margin-left: auto; color: var(--ember);
  font-family: var(--f-label); font-size: 1.1rem; line-height: 1;
}
.faq details[open] summary::after { content: "\2013"; }
.faq details > p, .faq details > ul {
  padding-bottom: 1.1rem; color: var(--ash); font-size: var(--t-small);
}
.faq details > ul { padding-left: 1.2rem; display: grid; gap: .35rem; }

.zonelist { display: grid; gap: .6rem; grid-template-columns: 1fr; }
@media (min-width: 700px) { .zonelist { grid-template-columns: repeat(3, 1fr); } }
.zone {
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: 1.1rem;
}
.zone h3 { font-size: 1.05rem; margin-bottom: .4rem; }
.zone p { font-size: .84rem; color: var(--ash); }

/* ============================================================
   17. TOASTS + SCROLL TOP
   ============================================================ */
.toast {
  position: fixed; left: 50%; bottom: 5.6rem; transform: translateX(-50%);
  z-index: var(--z-actionbar);
  background: var(--slab); border: 1px solid var(--live); color: var(--chalk);
  border-radius: var(--radius-ctl); padding: .6rem 1.2rem;
  font-family: var(--f-label); font-size: .68rem; text-transform: uppercase;
  letter-spacing: var(--track-label);
  opacity: 0; pointer-events: none; transition: opacity .25s ease;
}
.toast.is-shown { opacity: 1; }
@media (min-width: 1000px) { .toast { bottom: 1.6rem; } }

.totop {
  position: fixed; right: 1rem; bottom: 5.4rem; z-index: var(--z-sticky);
  width: 42px; height: 42px; border-radius: var(--radius-ctl);
  background: var(--slab); border: 1px solid var(--edge); color: var(--chalk);
  display: grid; place-items: center; cursor: pointer;
  opacity: 0; pointer-events: none; transition: opacity .25s ease;
}
.totop.is-shown { opacity: 1; pointer-events: auto; }
.totop:hover { border-color: var(--ember); color: var(--ember); }
@media (min-width: 1000px) { .totop { bottom: 1.5rem; right: 1.5rem; } }

/* ============================================================
   18. PRINT
   ============================================================ */
@media print {
  .header, .bar, .actionbar, .drawer, .gate, .totop, .zipcheck { display: none !important; }
  body { background: #fff; color: #000; }
  .card, .tier, .review { border: 1px solid #ccc; background: #fff; color: #000; }
}

/* ============================================================
   19. ACCOUNTS  (account.html)
   ============================================================ */
.codeblock {
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: 1rem 1.1rem; margin-top: 1.2rem;
  overflow-x: auto; font-family: var(--f-label); font-size: .8rem;
  color: var(--chalk); line-height: 1.7;
}

.authgrid { display: grid; gap: 1.25rem; grid-template-columns: 1fr; }
@media (min-width: 820px) { .authgrid { grid-template-columns: repeat(2, 1fr); } }

.authcard {
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: clamp(1.5rem, 3vw, 2.2rem);
  display: flex; flex-direction: column; gap: 1rem;
}
.authcard h2 { font-size: 1.5rem; }

.field { display: grid; gap: .4rem; }
.field__label {
  font-family: var(--f-label); font-size: var(--t-label); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--track-label); color: var(--ash);
}
.field__hint { color: var(--ash); opacity: .75; margin-left: .5rem; }
.field input {
  background: var(--void); border: 1px solid var(--edge);
  border-radius: var(--radius-ctl); padding: .85rem 1rem; color: var(--chalk);
}
.field input:focus-visible { border-color: var(--ember); }
/* Chrome paints its own near-white autofill background, which is unreadable
   against chalk text on a dark field. The inset shadow is the only reliable
   way to repaint it; -webkit-text-fill-color is the only way to keep the text. */
.field input:-webkit-autofill,
.field input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--chalk);
  -webkit-box-shadow: 0 0 0 100px var(--void) inset;
  caret-color: var(--chalk);
}

.check { display: flex; gap: .65rem; align-items: flex-start;
         font-size: .84rem; color: var(--ash); line-height: 1.5; }
.check input { margin-top: .2rem; accent-color: var(--ember); flex: none;
               width: 1rem; height: 1rem; }

.authcard__err {
  color: var(--warn); font-size: .84rem; border: 1px solid rgba(255,176,32,.35);
  border-radius: var(--radius-ctl); padding: .65rem .8rem; background: rgba(255,176,32,.07);
}
.authcard__fine { font-size: .78rem; color: var(--ash); }
.authcard__fine a { color: var(--ember); }

/* Account access stays in context: the shopper can sign in or create an
   account without losing the menu, cart, or the page they were viewing. */
.account-modal { position: fixed; z-index: var(--z-gate); inset: 0; display: grid; place-items: center; padding: 1rem; }
.account-modal__scrim { position: absolute; inset: 0; background: rgba(3,3,5,.8); backdrop-filter: blur(8px); }
.account-modal__panel { position: relative; width: min(100%, 29rem); max-height: calc(100svh - 2rem); overflow: auto; padding: clamp(1.5rem, 4vw, 2.35rem); border: 1px solid rgba(255,255,255,.15); border-radius: var(--radius); background: var(--slab); box-shadow: 0 1.2rem 4rem rgba(0,0,0,.6); }
.account-modal__close { position: absolute; top: .65rem; right: .7rem; width: 2.25rem; height: 2.25rem; border: 0; background: transparent; color: var(--ash); font-size: 1.7rem; line-height: 1; cursor: pointer; }
.account-modal__intro { margin-top: .7rem; color: var(--ash); font-size: .92rem; }
.account-modal__form { display: grid; gap: .9rem; margin-top: 1.35rem; }
.account-modal__status { padding: .65rem .75rem; border: 1px solid rgba(255,176,32,.38); border-radius: var(--radius-ctl); background: rgba(255,176,32,.07); color: var(--warn); font-size: .82rem; }
.account-modal__switch { margin-top: 1rem; color: var(--ash); font-size: .82rem; text-align: center; }
.account-modal__switch button { padding: 0; border: 0; background: transparent; color: var(--ember); font: inherit; cursor: pointer; text-decoration: underline; text-underline-offset: .18em; }

.acctgrid { display: grid; gap: 1.25rem; grid-template-columns: 1fr; }
@media (min-width: 700px)  { .acctgrid { grid-template-columns: repeat(3, 1fr); } }
.acctcard {
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: 1.5rem 1.35rem;
  display: flex; flex-direction: column; gap: .5rem;
}

/* Telegram card — full width under the three stat cards. It is a settings
   row, not a stat: state on the left, actions on the right, and it wraps to
   stacked on a phone rather than squeezing a 44px button next to text. */
.acctcard--wide { grid-column: 1 / -1; }
.acctcard__row {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1rem;
}
.acctcard__row > div:first-child { min-width: min(100%, 22rem); flex: 1 1 22rem; }
.acctcard__actions { display: flex; flex-wrap: wrap; gap: .5rem; flex: 0 0 auto; }
.profile-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .9rem; margin-top: .65rem; }
.profile-password { grid-column: 1 / -1; }
.acctcard__err {
  margin-top: .9rem; padding: .6rem .75rem;
  border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--edge));
  border-radius: var(--radius-ctl);
  color: var(--warn); font-size: .8rem; line-height: 1.45;
}
.acctcard__err[hidden] { display: none; }
@media (max-width: 560px) {
  .acctcard__actions { width: 100%; }
  .acctcard__actions .btn { flex: 1 1 auto; justify-content: center; }
  .profile-fields { grid-template-columns: 1fr; }
  .profile-password { grid-column: auto; }
}

/* ============================================================
   20. CATALOG ADMINISTRATION
   ============================================================ */
.admin-page { min-height: 100vh; }
.admin-shell { padding-block: clamp(1rem, 4vw, 3rem); }
.admin-head, .admin-toolbar, .admin-toolbar__actions, .product-dialog__actions { display: flex; align-items: center; gap: 1rem; }
.admin-head, .admin-toolbar { justify-content: space-between; }
.admin-head .eyebrow { margin: .7rem 0 0; }
.admin-login { width: min(100%, 32rem); margin: clamp(3rem, 10vw, 8rem) auto; padding: clamp(1.5rem, 4vw, 2.5rem); border: 1px solid var(--edge); border-radius: var(--radius); background: var(--slab); }
.admin-login h1, .admin-toolbar h1 { margin-top: .45rem; }
.admin-login > p, .admin-toolbar p { color: var(--ash); }
.admin-login__form { display: grid; gap: 1rem; margin-top: 1.5rem; }
.admin-toolbar { margin-block: 2rem 1rem; }
.admin-toolbar__actions { flex-wrap: wrap; justify-content: end; }
.admin-status { margin-block: 1rem; padding: .7rem .9rem; border: 1px solid color-mix(in srgb, var(--live) 45%, var(--edge)); border-radius: var(--radius-ctl); color: var(--live); background: color-mix(in srgb, var(--live) 8%, transparent); }
.admin-status.is-error { border-color: color-mix(in srgb, var(--warn) 45%, var(--edge)); color: var(--warn); background: color-mix(in srgb, var(--warn) 8%, transparent); }
.admin-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .75rem; margin-bottom: 1.25rem; }
.admin-stats article, .admin-list-panel, .admin-accounts, .admin-sessions { border: 1px solid var(--edge); border-radius: var(--radius); background: var(--slab); }
.admin-stats article { display: grid; gap: .25rem; padding: 1rem; }
.admin-stats span { color: var(--ash); font: 700 .63rem/1.3 var(--f-label); letter-spacing: var(--track-label); text-transform: uppercase; }
.admin-stats strong { font: 400 2rem/1 var(--f-display); }
.admin-layout { display: grid; grid-template-columns: minmax(16rem, .8fr) minmax(0, 2fr); gap: 1.25rem; align-items: start; }
.admin-list-panel, .admin-accounts, .admin-sessions { padding: 1rem; }
.admin-product-list { display: grid; gap: .45rem; margin-top: .8rem; max-height: 65vh; overflow: auto; }
.admin-product { display: grid; gap: .2rem; width: 100%; padding: .75rem; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); color: var(--chalk); text-align: left; cursor: pointer; }
.admin-product:hover { border-color: var(--ember); }
.admin-product span { font-weight: 700; }
.admin-product small, .admin-account-row small { color: var(--ash); }
.admin-accounts { min-width: 0; }
.admin-accounts > .eyebrow { display: block; margin-bottom: .8rem; }
.admin-sessions { grid-column: 1 / -1; min-width: 0; }
.admin-sessions > .eyebrow { display: block; margin-bottom: .3rem; }
.admin-sessions__note { margin-bottom: .8rem; color: var(--ash); font-size: .78rem; }
.admin-account-table { display: grid; overflow-x: auto; }
.admin-account-row { display: grid; grid-template-columns: minmax(14rem, 1.3fr) minmax(11rem, 1fr) minmax(10rem, 1fr); gap: 1rem; align-items: center; padding: .85rem 0; border-top: 1px solid var(--edge); font-size: .83rem; }
.admin-account-row > span:first-child { display: grid; gap: .15rem; }
.admin-account-row--head { border-top: 0; color: var(--ash); font: 700 .62rem/1.2 var(--f-label); letter-spacing: var(--track-label); text-transform: uppercase; }
.product-dialog { width: min(100% - 1.5rem, 52rem); max-height: calc(100dvh - 1.5rem); margin: auto; padding: 0; border: 1px solid var(--edge); border-radius: var(--radius); color: var(--chalk); background: var(--slab); }
.product-dialog::backdrop { background: rgba(0, 0, 0, .76); backdrop-filter: blur(5px); }
.product-dialog__panel { display: grid; gap: 1rem; max-height: calc(100dvh - 1.5rem); padding: clamp(1rem, 4vw, 2rem); overflow: auto; }
.admin-editor__head { display: flex; justify-content: space-between; align-items: start; gap: 1rem; }
.admin-editor__head h2 { margin-top: .3rem; }
.admin-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .85rem; }
.admin-fields__full { grid-column: 1 / -1; }
.admin-fields textarea, .admin-fields input { width: 100%; background: var(--void); border: 1px solid var(--edge); border-radius: var(--radius-ctl); padding: .75rem .9rem; color: var(--chalk); font: inherit; }
.admin-fields textarea:focus, .admin-fields input:focus { outline: 2px solid var(--ember); outline-offset: 1px; }
.admin-photo-list { display: flex; flex-wrap: wrap; gap: .6rem; }
.admin-photo-list figure { position: relative; width: 5rem; height: 5rem; margin: 0; overflow: hidden; border: 1px solid var(--edge); border-radius: var(--radius-ctl); background: var(--void); }
.admin-photo-list img { width: 100%; height: 100%; object-fit: cover; }
.admin-photo-list button { position: absolute; top: .15rem; right: .15rem; width: 1.5rem; height: 1.5rem; border: 0; border-radius: 50%; background: var(--void); color: var(--chalk); cursor: pointer; }
.product-dialog__actions > span { flex: 1; }
.btn--danger { border-color: color-mix(in srgb, var(--warn) 65%, var(--edge)); color: var(--warn); }
@media (max-width: 760px) {
  .admin-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .admin-layout { grid-template-columns: 1fr; }
  .admin-product-list { max-height: 22rem; }
}
@media (max-width: 520px) {
  .admin-head, .admin-toolbar { align-items: start; flex-direction: column; }
  .admin-toolbar__actions { justify-content: start; }
  .admin-fields { grid-template-columns: 1fr; }
  .admin-fields__full { grid-column: auto; }
  .product-dialog__actions { flex-wrap: wrap; }
}
.acctcard__big {
  font-family: var(--f-display); font-size: 1.5rem; text-transform: uppercase;
  line-height: 1.05; letter-spacing: .005em;
}
.acctcard__note { font-size: .84rem; color: var(--ash); line-height: 1.5; }

.orderlist { list-style: none; padding: 0; display: grid; gap: 1rem;
             grid-template-columns: 1fr; }
@media (min-width: 700px)  { .orderlist { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .orderlist { grid-template-columns: repeat(3, 1fr); } }
.order {
  background: var(--slab); border: 1px solid var(--edge);
  border-radius: var(--radius); padding: 1.25rem;
  display: flex; flex-direction: column; gap: .5rem;
}
.order__head { display: flex; align-items: center; gap: .7rem; }
.order__id {
  font-family: var(--f-label); font-weight: 700; font-size: .8rem;
  letter-spacing: var(--track-label); margin-right: auto;
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
}
.order__meta { font-size: .82rem; color: var(--ash); }
.order__total {
  font-family: var(--f-label); font-weight: 700; font-size: 1.05rem;
  color: var(--ember);        /* prices carry the accent — see §9 */
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
}

/* Signed-in marker on the header account icon. A dot rather than a label:
   the header is already tight at the 1000px breakpoint. */
.icon-btn.is-authed::after {
  content: ""; position: absolute; top: 7px; right: 8px;
  width: 7px; height: 7px; border-radius: var(--radius-pill);
  background: var(--live); border: 2px solid var(--void);
}

/* ============================================================
   20. SMOKE VEIL CURSOR  (index.html only, hero-scoped)
   WebGL2 fluid-sim veil that follows the pointer — see smoke-veil.js for the
   simulation and its three gates (hover+fine pointer, reduced-motion,
   WebGL2 feature detection). This block is the CSS half of that same
   belt-and-suspenders approach: even if smoke-veil.js never runs (script
   blocked, JS disabled, older browser), the canvas stays inert and invisible
   rather than sitting on-screen as an empty rectangle.

   `position: absolute` inside `.hero` — deliberately NOT `fixed` to the
   viewport. `.hero` is already `position: relative; overflow: hidden;
   isolation: isolate`, so nesting the canvas here does three things for
   free, with no extra JS:
     · Clips it to the hero's box — it cannot bleed into the section below.
     · Scrolls it away naturally once the hero scrolls out of view.
     · Contains its stacking — z-index below is LOCAL to .hero, not the page.

   `z-index: -1` places it above `.hero__bed` (the photo + scrim, at -2) but
   below `.hero__inner` (the h1/CTA/ZIP form, plain in-flow content, which
   therefore paints above any negative-z-index sibling regardless of DOM
   order). That ordering matters here specifically: `.hero--poster` runs
   near-black text measured down to a ~4:1 contrast floor against the photo
   (see README "Re-tuning the hero scrim") — a screen-blended, lightening
   smoke layer drifting ABOVE that text would locally wash out exactly the
   contrast that was tuned pixel-by-pixel. Below the text, above the photo,
   is the only placement that can't regress it.

   `mix-blend-mode: screen` is what lets it sit over the photo without ever
   reading as an opaque layer — screen only lightens. `pointer-events: none`
   is load-bearing: without it this canvas would sit above the ZIP form and
   the hero CTA and swallow every click on the one part of the page that
   converts. */
#smoke-veil-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: -1;
  pointer-events: none;
  mix-blend-mode: screen;
}
@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) {
  #smoke-veil-canvas { display: none; }
}

/* ============================================================
   21. LEGAL PAGES  (terms / privacy / refunds / accessibility)
   Long-form reading columns. Capped measure so body copy stays
   readable on a wide desktop rather than running the full 1320px.
   ============================================================ */
.legal__body { max-width: 68ch; }
.legal__body h2 {
  font-family: var(--f-label); font-size: 1rem; letter-spacing: .02em;
  text-transform: none; color: var(--chalk);
  margin: 2.2rem 0 .6rem; padding-top: 1.4rem;
  border-top: 1px solid var(--edge);
}
.legal__body h2:first-of-type { margin-top: 1.6rem; }
.legal__body p, .legal__body li { color: var(--ash); font-size: var(--t-small); line-height: 1.7; }
.legal__body p { margin-bottom: .9rem; }
.legal__body ul { margin: 0 0 1rem 1.1rem; display: grid; gap: .45rem; }
.legal__body a { color: var(--ember); text-underline-offset: 3px; }
.legal__body code {
  font-family: var(--f-label); font-size: .82em;
  background: var(--slab-2); border: 1px solid var(--edge);
  border-radius: 4px; padding: .05em .35em;
}
/* Unfinished-policy banner. Deliberately loud: these pages must not go live
   as-is, and a quiet note would be missed. */
.legal__pending {
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 9%, transparent);
  border-radius: var(--radius); padding: .95rem 1.1rem; margin-bottom: 1.6rem;
}
.legal__pending strong { color: var(--warn); }

/* A control that reads as a link but is a <button> — used where the action
   is in-page and does not navigate. */
.linklike {
  background: none; border: 0; padding: 0;
  color: var(--ember); font: inherit; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.linklike:hover { color: var(--chalk); }

/* ============================================================
   22. TOUCH TARGETS
   The remaining compact controls receive 44px hit areas without changing
   the visual rhythm of the interface.
   ============================================================ */
/* The card title button and the locked-card CTA are wide enough already;
   they only need vertical reach. */
.exotic-lock__cta { position: relative; }
.exotic-lock__cta::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  transform: translateY(-50%);
  height: 44px;
}
.icon-btn--social { min-width: 44px; }
/* Header icon buttons sit at 42x42 (39x40 under 520px). Two pixels short of
   the minimum; grow the hit box only — the header row is min-height 58px so
   nothing reflows. */
.icon-btn { min-width: 44px; min-height: 44px; }

/* Below 560px the header cannot hold five 44px controls next to a 120px
   brand — measured at 375px it wanted 369px of a 343px content width and
   pushed the cart button off the right edge; at 320px the menu button went
   with it. The two social controls step out here and app.js mirrors them
   into the mobile nav, so nothing becomes unreachable (Instagram has no
   other link on the site).

   This rule has to sit AFTER the .icon-btn block, not up with the other
   breakpoints: .icon-btn--social and .icon-btn have equal specificity, so
   whichever is declared last wins, and a media query does not change that.
   Do not answer this by shrinking .icon-btn — 44px is the tap target. */
@media (max-width: 560px) { .icon-btn--social { display: none; } }

/* Filter checkboxes are 13x13, but each sits inside a label. Give the label
   the full row so the whole thing is tappable rather than the box alone. */
.category-filter__panel label { min-height: 44px; display: flex; align-items: center; gap: .55rem; }

@media (max-width: 699px) {
  /* Marquee links are 18px tall inside a 40px bar; let them fill it. */
  .bar__item > a { padding-block: .6rem; }
}

/* ============================================================
   23. FLOATING CONTROL STACK
   .totop sat at bottom:5.4rem and .support-help at bottom:5.35rem, both
   pinned to right:1rem. On mobile the 42px back-to-top button therefore sat
   entirely underneath the 105px support cluster, and with z-index 20 against
   the cluster's 40 it was invisible and untappable — measured at 375px, both
   occupying 678-726px from the top of an 812px viewport.

   Stack them instead: support cluster on the bottom row, back-to-top above
   it. The values are derived from the cluster's own height (3rem control +
   its bottom offset) so the two stay related if either moves.
   ============================================================ */
@media (max-width: 999px) {
  .totop {
    /* support-help bottom (5.35rem) + its control height (3rem) + gap */
    bottom: calc(5.35rem + 3rem + 0.6rem);
    z-index: var(--z-actionbar);
  }
}

/* On desktop the support cluster sits at bottom:1.25rem and is wider (it
   shows its labels), so give the back-to-top the same treatment there. */
@media (min-width: 1000px) {
  .totop { bottom: calc(1.25rem + 3rem + 0.6rem); z-index: var(--z-actionbar); }
}

/* ============================================================
   25. HERO CTA vs THE FLOATING SUPPORT CLUSTER
   Third collision in this stack (see §23 for the .totop one). The support
   cluster is fixed at bottom:5.35rem and is 3rem tall, so on a 375x812
   viewport it occupies 678–726px from the top. The hero's CTA row ended at
   692px, which put the cluster over the bottom-right corner of the
   secondary CTA — a 59x14px overlap, measured, where a tap landed on the
   support launcher instead of "How it works".

   Reserve the cluster's own footprint as hero bottom padding rather than
   nudging either element by a magic number, so the two stay related if the
   cluster ever moves. The hero is `align-items: end`, so extra bottom
   padding lifts the copy block rather than growing the section.
   ============================================================ */
@media (max-width: 999px) {
  .hero__inner { padding-bottom: calc(clamp(3rem, 9vw, 6rem) + 3.4rem); }
}

/* ============================================================
   26. CATEGORY PAGES
   flower / carts / vapes / edibles / concentrates / prerolls /
   mushrooms — one real URL per shelf instead of shop.html?cat=.

   Three parts, in source order down the page:
     .crumbs             breadcrumb back to Home → Shop
     .cat-hero-overlay   the photograph with the copy laid over it
     .cat-chips          the sticky category switcher

   The hero comes in two flavours that must read as the same object:
     (default)  the shot ylo1/ylo2/ylo3, hand-reaching-for-product on yellow
     --art      a cut-out on a gradient built to match those shots' backdrop

   The gradient is sampled from the photography, not invented: the bright
   centre of ylo1 measures #FDC602 and its corners fall to #CD7D1E, so an
   art hero sitting on a different page still looks like it came off the
   same roll. Do not "tidy" it to a flat --ember; the flat version reads as
   a swatch next to the photographs.
   ============================================================ */

.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  padding-top: 1.15rem; color: var(--ash);
  font: 700 .62rem/1.5 var(--f-label); letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.crumbs a { color: var(--ash); text-decoration: none; }
.crumbs a:hover { color: var(--ember); }
.crumbs [aria-current="page"] { color: var(--chalk); }
.crumbs span[aria-hidden] { color: color-mix(in srgb, var(--ash) 60%, transparent); }

/* ---------- THE HERO -------------------------------------------------
   One object: the photograph at its native aspect with the copy sitting on
   top of it. The image is never cropped — the container takes the image's
   own ratio (1600 x 901) rather than a fixed height, so the reaching hand
   and the product both survive at every width.

   Legibility comes from a scrim, not from a box. The gradient runs left to
   right and is opaque black through the first 30% of the frame, which is
   where the yellow field is empty in all three shots; by 70% it is gone and
   the photograph is untouched. The copy is capped at 42% of the frame so no
   line of text ever reaches past the .55 stop — measured, that is 6.6:1 for
   body text at the very worst pixel, and better everywhere else.

   Below 800px the frame is only ~210px tall, which cannot hold an H1, a
   lede and two buttons. There the scrim turns vertical, the copy leaves the
   absolute layer and flows underneath, and a negative top margin tucks it
   into the faded bottom edge so the two still read as one object.
   --------------------------------------------------------------------- */
.cat-hero-overlay {
  position: relative; overflow: hidden;
  width: 100%; background: var(--void);
  /* Full-bleed to match .shop-header. The prior 88rem cap left a --void
     bar on either side above the fold on wide desktops — the copy is
     still measure-capped by .cat-hero-overlay__inner, so a full-width
     frame does not run the lede across the whole viewport. */
}
.cat-hero-overlay__media { position: relative; }
.cat-hero-overlay__img { display: block; width: 100%; height: auto; }
/* This is a VERBATIM COPY of the .shop-header scrim (§ above). The two
   heroes sit one click apart and have to read as the same treatment, so if
   you change one, change the other — there is no shared token for it
   because .shop-header composites its scrim into a background-image stack
   and this one is a real element over a real <img>.

   It is much lighter than the ramp it replaced (.78 → .48 rather than
   .98 → 0), which is the point: the photograph survives across the whole
   frame. The cost is that --ash body copy no longer clears 4.5:1 over it —
   measured 3.0:1 on the edibles shot. .shop-header has exactly the same
   problem and answers it the same way, by putting its lede in --chalk
   (.shop-hero .lede). So does this hero now; see .cat-hero__lede below. */
.cat-hero-overlay__scrim {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg,
    rgba(11, 11, 13, .78) 0%,
    rgba(11, 11, 13, .58) 52%,
    rgba(11, 11, 13, .48) 100%);
}
.cat-hero-overlay__copy {
  position: absolute; inset: 0; z-index: 1;
  display: grid; align-content: center;
  box-sizing: border-box;
  padding-block: clamp(2rem, 4vw, 3.5rem);
}
/* Kept at 39% for measure, not for contrast — the scrim is flat enough now
   that where the line ends barely moves the ratio (.78 at the left edge,
   .70 at 39%). 44ch is the readable-line cap; the percentage stops the
   copy running under the product on a wide frame. */
.cat-hero-overlay__inner { max-width: min(44ch, 39%); }

/* Art variant — the four shelves with no photograph. Same frame, same
   ratio, so a visitor moving between category pages sees one component and
   not two. The gradient is sampled from the photography (bright centre
   #FDC602, corners falling to #CD7D1E) so it belongs to the same set. */
.cat-hero-overlay--art .cat-hero-overlay__media {
  aspect-ratio: 1600 / 901;
  background: radial-gradient(118% 132% at 62% 46%, #FDC602 0%, #E8A712 52%, #CB7A1D 100%);
}
.cat-hero-overlay__cutout {
  position: absolute; top: 50%; right: var(--cutout-x, 7%);
  height: var(--cutout-h, 84%); width: auto; max-width: 46%;
  object-fit: contain;
  transform: translateY(-50%) rotate(var(--cutout-r, 0deg));
  filter: drop-shadow(0 1rem 1.4rem rgba(70, 38, 0, .3));
  pointer-events: none;
}

.cat-hero-overlay h1 {
  margin-top: .55rem;
  font-size: clamp(2.4rem, 5.4vw, 4.6rem); line-height: .9;
}
/* --chalk, not --ash, and not a style preference: over the All Products
   scrim the muted grey measures 3.0:1 against the brightest of the three
   photographs and chalk measures 7.5:1. .shop-hero .lede is chalk for the
   same reason. Reverting this to --ash silently fails contrast. */
.cat-hero__lede {
  margin-top: .95rem;
  color: var(--chalk); font-size: clamp(.92rem, 1.25vw, 1.05rem); line-height: 1.55;
}
.cat-hero__actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1.4rem; }
.cat-hero__meta {
  margin-top: .9rem; color: var(--chalk);   /* same reason as the lede */
  font: 700 .66rem/1.4 var(--f-label); letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.cat-hero__meta strong { color: var(--ember); }

/* Desktop crop — trim the top and bottom 30% of the frame so the hero
   sits as a wide band above the shelf on larger viewports. The image
   is object-cover-centred so only the middle 40% of the source photo
   shows; the copy overlay stays vertically centred inside the shorter
   frame. Kept behind (min-width: 801px) so the mobile stacked layout
   below is untouched. */
@media (min-width: 801px) {
  /* The copy is intentionally taller than the original 360px band on wide
     screens, especially on Flower and Edibles. A fixed aspect ratio let the
     grid content overflow until its first and last lines touched the hero
     edges. Give every category hero a content-safe floor so the copy's
     vertical padding remains visible at every desktop width. */
  .cat-hero-overlay {
    aspect-ratio: auto;
    min-height: clamp(34rem, 28vw, 38rem);
  }
  .cat-hero-overlay__media { position: absolute; inset: 0; overflow: hidden; }
  .cat-hero-overlay__img,
  .cat-hero-overlay--art .cat-hero-overlay__media {
    width: 100%; height: 100%; object-fit: cover; object-position: center;
  }
  .cat-hero-overlay--art .cat-hero-overlay__media { aspect-ratio: auto; }
}

@media (max-width: 800px) {
  /* .shop-header's mobile wash is a flat .70 → .76 and nothing else. This
     hero cannot use it unchanged: below 800px the copy leaves the absolute
     layer and flows underneath the frame, so a scrim that stops at .76
     leaves a hard horizontal seam where the photograph ends and --void
     begins. The .70 → .76 range is matched over the first three quarters,
     then it carries on to --void so the image and the copy under it still
     read as one object. */
  .cat-hero-overlay__scrim {
    background: linear-gradient(180deg,
      rgba(11, 11, 13, .70) 0%,
      rgba(11, 11, 13, .74) 55%,
      rgba(11, 11, 13, .76) 72%,
      rgba(11, 11, 13, .88) 84%,
      rgba(11, 11, 13, .97) 93%,
      var(--void) 100%);
  }
  /* Both children share one grid cell, so the copy sits ON the photograph
     rather than after it, centred against the frame. This replaced a flow
     layout with a negative top margin, which could only ever tuck the copy
     under the bottom edge — never centre it — and needed the margin
     re-tuned for every width.

     The row takes the height of whichever child is taller. Above roughly
     480px that is the image, and `align-self: center` lands the copy in the
     middle of it, which is what "centred on the hero image" means. On a
     narrow phone the copy is the taller of the two, so it fills the row and
     the image sits behind its upper part — still an overlay, still legible
     (the mobile scrim never drops below .70, which measures 9:1 for chalk
     and 7.1:1 for the ember eyebrow over the brightest part of the frame).

     `position: relative` is load-bearing, not decoration: .cat-hero-overlay__media
     is positioned, so it paints in step 8 of the painting order while static
     block content paints in step 7 — underneath it. A static copy box gets
     covered by the photograph and the z-index below is inert on it. */
  .cat-hero-overlay { display: grid; }
  .cat-hero-overlay__media { grid-area: 1 / 1; align-self: start; }
  .cat-hero-overlay__copy {
    grid-area: 1 / 1; align-self: center;
    position: relative; z-index: 1;
    padding-block: clamp(2rem, 8vw, 3.5rem);
  }
  .cat-hero-overlay__inner { max-width: none; }
}

/* Category switcher. This is the "product filter" on a scoped page: every
   chip is a real link to a real page, so it works with JS off and it is
   crawlable, which the checkbox rail on shop.html is not. */
.cat-chips {
  display: flex; gap: .4rem; overflow-x: auto; scrollbar-width: none;
  padding-bottom: .15rem; -webkit-overflow-scrolling: touch;
}
.cat-chips::-webkit-scrollbar { display: none; }
.cat-chip {
  flex: none; display: inline-flex; align-items: center; min-height: 2.4rem;
  padding: .4rem .85rem; border: 1px solid var(--edge); border-radius: 999px;
  background: var(--slab); color: var(--ash); text-decoration: none;
  font: 700 .66rem/1 var(--f-label); letter-spacing: var(--track-label);
  text-transform: uppercase; white-space: nowrap;
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.cat-chip:hover { color: var(--chalk); border-color: color-mix(in srgb, var(--ember) 55%, var(--edge)); }
.cat-chip--all { color: var(--ember); border-color: color-mix(in srgb, var(--ember) 42%, var(--edge)); }
.cat-chip[aria-current="page"] {
  background: var(--ember-lo); border-color: var(--ember-lo); color: var(--on-ember);
}

/* Sibling shelves at the foot of the page — a second chance to convert
   rather than a dead end after the last card.

   Four tiles, laid out so no row is ever left ragged: 2x2 on a phone,
   4-across from 700px. The homepage's own six-tile grid keeps its 2/3
   columns, which is why this is scoped to .cat-next rather than changing
   .category-tiles for everyone.

   The tiles also have to shrink on a narrow phone — at two columns a 320px
   viewport gives each one ~136px, and the display word inside was sized for
   a half-width desktop tile. The overrides below step the type and the
   min-height down so "CONCENTRATES" still fits on one line instead of
   overflowing its own tile. */
.cat-next { border-top: 1px solid var(--edge); }
.cat-next .category-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 700px) {
  .cat-next .category-tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 699px) {
  /* --tile-h, not min-height: the art geometry is expressed in multiples of
     it, so setting it here shrinks the packshots along with the tile. */
  .cat-next .category-tile { --tile-h: clamp(7rem, 26vw, 10rem); min-height: var(--tile-h); padding: .8rem; }
  .cat-next .category-tile strong { font-size: clamp(1rem, 5.2vw, 1.6rem); }
  .cat-next .category-tile small { font-size: .55rem; }
  .cat-next .category-tile span { top: .6rem; left: .7rem; font-size: .58rem; }
}

/* #shelf is the target of the hero's primary CTA. The rail above it is not
   pinned on these pages, so only the header has to be cleared. */
.cat-shelf { scroll-margin-top: calc(var(--header-stack) + 1.25rem); }

@media (max-width: 520px) {
  .cat-hero__actions .btn { flex: 1 1 100%; justify-content: center; }
}
