/* ═══ ez-page.css — the shared sheet for every generated page (D451) ═══════════════════════════
   Until now scripts/build-products.js inlined this whole sheet into all 500 generated pages: the
   same ~8 KB, five hundred times, in dist/. D451 turns /p/<slug>/ into a REAL product page — a
   picture pager, a weight picker, a qty stepper, an add-to-bag button and its own age gate — and
   inlining THAT would have put megabytes of identical bytes on disk and on the wire. So the sheet
   moved here: one content-hashed download for the whole site, and one place where this look lives.

   Everything above the "NEW FURNITURE" banner is carried over VERBATIM from the CSS const that
   used to live at build-products.js:325-425, comments included. It is not re-written, re-ordered
   or "tidied" — those comments are the record of decisions the owner made, and a page that looked
   right yesterday must look byte-identical today. New rules go BELOW the banner.
   ONE DATED EXCEPTION — 2026-10-19, THE CARDS (ANSWERS D685/D686/D688): he retired the boxes
   everywhere a product picture appears, so the rules that DREW a box here (.prod, .prod img,
   .sibcard, .sib-media, and .pdp-media below the banner) are edited in place, each with its date
   and decision, rather than overridden from below with a second, contradicting rule. Everything
   else above the banner is still the verbatim record.

   ⚠️ EVERY var() IN THIS FILE MUST BE DEFINED IN THIS FILE. check-css-vars.js scopes per FILE and
   allows no fallback-less reference to a token it cannot see — and the generated pages no longer
   carry a <style> block of their own, so there is no second sheet to borrow from. Copying a token
   out of index.html without copying its DEFINITION is the one mistake this file cannot survive. */

  :root{--green:#5BE584;--ink:#0A0C0A;--deep:#245C3C;--paper:#FFFFFF;
    --muted:rgba(255,255,255,.64);--line:rgba(255,255,255,.12);
    --panel:rgba(255,255,255,.04);--panel2:rgba(255,255,255,.015)}
  *{box-sizing:border-box}
  html,body{margin:0;padding:0}
  body{background:var(--ink);color:var(--paper);font-family:Syne,Inter,system-ui,-apple-system,sans-serif;
    line-height:1.6;background-image:radial-gradient(ellipse 90% 55% at 18% -5%,rgba(36,92,60,.5),transparent 60%)}
/* 🌌 THE AURORA, on the static pages too (owner's tap D374: "Every page, site-wide"). The SPA runs
   this as real elements with scroll parallax; here it is two body pseudo-elements, because these
   pages are generated by the hundred and must not grow markup to carry a background. Same reading
   of his four rythm.com frames: a near-black ground the colour never fills, drifting pools, and one
   diagonal ribbon with a GOLD edge sitting ON TOP of the dark as an actual light source. The grain
   is load-bearing — wide smooth gradients band into visible steps on an OLED phone, which is the
   "cheap... pixly" he rejected once already. */
body::before,body::after{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none}
body::before{
  background:
    radial-gradient(58% 42% at 16% 12%,rgba(63,191,99,.24),transparent 52%),
    radial-gradient(56% 40% at 88% 86%,rgba(91,229,132,.16),transparent 50%),
    linear-gradient(180deg,rgba(10,12,10,.34),rgba(10,12,10,.68) 50%,rgba(10,12,10,.34)),
    radial-gradient(120% 82% at 50% 44%,transparent 22%,rgba(6,8,6,.8) 100%);
  animation:aurPools 64s ease-in-out infinite}
body::after{
  background:
    radial-gradient(30% 11% at 50% 42%,
      rgba(124,255,155,.80) 0%,
      rgba(63,191,99,.44) 24%,
      rgba(217,192,138,.36) 38%,
      transparent 66%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.085'/%3E%3C/svg%3E");
  /* no mix-blend-mode: it forces a full-size offscreen buffer, which is what
     took the renderer out on the SPA (2026-09-04) */
  animation:aurBand 72s ease-in-out infinite}
@keyframes aurPools{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(3%,2%,0) scale(1.1)}}
@keyframes aurBand{0%,100%{transform:translate3d(-8%,-4%,0) rotate(-28deg) scale(1.5)}
                   50%   {transform:translate3d(7%,6%,0)  rotate(-14deg) scale(1.9)}}
@media (prefers-reduced-motion:reduce){body::before,body::after{animation:none!important}}

  a{color:var(--green);text-decoration:none}
  img{max-width:100%}
  .wrap{max-width:860px;margin:0 auto;padding:20px}
  header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px 0 4px}
  .brand img{height:34px;width:auto;display:block}
  .navcta{font-weight:700;font-size:14px;border:1px solid var(--line);padding:9px 15px;border-radius:999px;color:var(--paper)}
  .eyebrow{font-family:Syne,sans-serif;font-weight:700;font-size:11.5px;letter-spacing:.22em;
    text-transform:uppercase;color:var(--green);margin:26px 0 8px}
  .eyebrow a{color:var(--green)}
  h1{font-family:Syne,sans-serif;font-weight:800;font-size:clamp(30px,6.5vw,48px);line-height:1.05;
    letter-spacing:-.02em;margin:0 0 10px;text-wrap:balance}
  h1 .g{color:var(--green)}
  .brand-mark{display:block;max-height:56px;width:auto;object-fit:contain;margin:0 0 10px;filter:drop-shadow(0 0 12px rgba(91,229,132,.35))}
  .brand-mark-sm{display:block;max-height:34px;width:auto;object-fit:contain;margin:24px 0 -18px;filter:drop-shadow(0 0 10px rgba(91,229,132,.3))}
  /* display:block above beats the UA's [hidden]{display:none} — without this, onerror's hide is a no-op */
  .brand-mark[hidden],.brand-mark-sm[hidden]{display:none}
  .sub{font-size:clamp(15px,2.3vw,18px);color:var(--muted);max-width:640px;margin:0 0 18px}
  .meta{color:var(--muted);font-size:14px;font-weight:600;margin:0 0 6px}
  .potency{color:var(--green);font-size:14px;font-weight:700;letter-spacing:.04em;margin:0 0 14px}
  .price{font-family:Syne,sans-serif;font-weight:800;font-size:clamp(28px,5vw,36px);color:var(--green);margin:6px 0 16px}
  .price s{color:var(--muted);font-weight:600;font-size:.55em;margin-right:8px;vertical-align:middle}
  .dealnote{font-family:Syne,sans-serif;font-weight:700;font-size:14px;color:var(--green);margin:-8px 0 16px}
  .soldbadge{display:inline-block;font-family:Syne,sans-serif;font-weight:800;font-size:13px;letter-spacing:.08em;
    text-transform:uppercase;color:var(--paper);background:rgba(255,255,255,.06);border:1px solid var(--line);
    border-radius:999px;padding:7px 14px}
  .soldwrap{margin:0 0 12px}
  .hero-img{width:min(340px,80vw);height:auto;border-radius:20px;background:rgba(255,255,255,.04);
    border:1px solid var(--line);display:block;margin:18px 0 6px}
  .cta-row{display:flex;flex-wrap:wrap;gap:12px;margin:0 0 12px}
  .btn{display:inline-block;font-family:Syne,sans-serif;font-weight:800;font-size:16px;padding:15px 28px;border-radius:14px}
  .btn.primary{background:var(--green);color:var(--ink)}
  .btn.ghost{border:1px solid var(--line);color:var(--paper)}
  .trust{display:flex;flex-wrap:wrap;gap:8px 16px;color:var(--muted);font-size:13.5px;font-weight:600;margin:6px 0 28px}
  .trust b{color:var(--green)}
  .rating{color:var(--paper);font-size:14px;font-weight:600;margin:2px 0 16px}
  h2{font-family:Syne,sans-serif;font-weight:700;font-size:clamp(21px,4vw,27px);margin:34px 0 12px}
  .prods{display:grid;gap:0;margin:6px 0}
  /* min-width:0 on the GRID ITEM — same shrink trap build-days documented at 390px */
  /* --pg is set inline only on rows whose product carries a glow; the transparent fallback makes
     the radial layer invisible, so a glow-less row renders EXACTLY as before this layer existed */
  /* 🪶 BOXLESS ROWS — D685 (2026-10-19, "get rid of the boxes completely and just have the product").
     The panel gradient, the border and the radius are gone; what separates two rows is the one
     hairline under each (the grid gap went to 0 so the hairlines are the rhythm). The product's own
     colour stays, as D688 asks ("give product pictures a hue so they don't look awkward just sitting
     there"): the --pg radial is no longer a wash across the row but a soft halo the size of the thumb,
     centred on it (14px padding + half of 56px), never a tile. */
  .prod{min-width:0;display:flex;align-items:center;gap:13px;padding:13px 14px;border-radius:0;
    border:0;border-bottom:1px solid var(--line);background:radial-gradient(circle 46px at 42px 50%,color-mix(in srgb,var(--pg,transparent) 34%,transparent),transparent);color:var(--paper)}
  /* no plate behind the thumb (D685) — and contain, not cover: with nothing framing it, the whole
     cutout shows rather than a crop of it */
  .prod img,.prod-noimg{width:56px;height:56px;flex:0 0 auto;border-radius:0;object-fit:contain;background:transparent}
  .prod-b{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
  .prod-b>*{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .prod-brand{font-size:11.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--green);font-weight:700}
  .prod-n{font-family:Syne,sans-serif;font-weight:700;font-size:15.5px;line-height:1.25;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .prod-m{font-size:12.5px;color:var(--muted)}
  /* one price → the green Syne number, exactly as before. A day deal → <s>standing</s><b>today</b>
     stacked right-aligned, the SAME pair build-days.js:297-299 prints on /deals/<day>/, so the two
     generated trees read as one system. The <s>/<b> rules only bite when the pair is painted. */
  .prod-p{flex:0 0 auto;text-align:right;display:flex;flex-direction:column;line-height:1.2;
    font-family:Syne,sans-serif;font-weight:800;font-size:19px;color:var(--green)}
  .prod-p s{font-family:Syne,Inter,system-ui,-apple-system,sans-serif;font-weight:400;
    font-size:12.5px;color:var(--muted)}
  .prod-p b{font-family:Syne,sans-serif;font-weight:800;font-size:19px;color:var(--green)}
  .note{color:var(--muted);font-size:13.5px;margin:12px 0 0}
  /* 🖼 MORE FROM <BRAND> — the card shape he circled (owner, 2026-09-11: "I liked the older style
     where you could see product pictures as well, don't make full product boxes, have them in the
     style of boxes I circled around"). Ported from index.html's .hf-card rail (index.html:2715-2727)
     rather than invented, so the row on a /p/ page and the FEATURED row he pointed at are the same
     object to the eye. Two deliberate departures:
       · It SCROLLS WITH A FINGER, it does not auto-animate. The featured rail is a marquee because
         it is a shop window; this is a list of six things he might want next, and a row that moves
         under the thumb is the D477 "glitchy tapping" complaint in miniature. It also keeps this off
         the composited-layer budget gpu-test guards.
       · No price line — build-products.js says why (nothing re-prices a sibling at runtime).
     scroll-snap keeps a card from being left half-cut at rest; the negative margin lets the row
     bleed to the page edge so the last card reads as "there is more", while the padding keeps the
     first one aligned with the copy above it. */
  .sibrail{display:flex;gap:10px;margin:10px -18px 0;padding:2px 18px 6px;overflow-x:auto;
    scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .sibrail::-webkit-scrollbar{display:none}
  /* 🪶 BOXLESS — D685 (2026-10-19): the card's panel and border are gone, the photo and the two lines
     stay exactly where they were (the same rule the brief gives the .hf-card rail these were ported
     from: "loses background and border — photo + text as today"). The thumb keeps its soft green
     halo (a window, not a plate — the rail's own words) and loses the 3% plate under it. */
  .sibcard{flex:0 0 auto;width:196px;display:flex;align-items:center;gap:11px;text-align:left;
    background:transparent;border:0;border-radius:14px;
    padding:9px 11px 9px 9px;scroll-snap-align:start;transition:transform .18s ease}
  .sibcard:hover{transform:translateY(-3px)}
  .sib-media{flex:0 0 auto;width:54px;height:54px;border-radius:11px;display:flex;align-items:center;
    justify-content:center;overflow:hidden;background:radial-gradient(110% 90% at 50% 30%,rgba(91,229,132,.22),transparent 70%)}
  .sib-media img{width:100%;height:100%;object-fit:contain}
  .sib-info{display:flex;flex-direction:column;min-width:0;gap:2px}
  .sib-name{font-size:13px;font-weight:600;color:var(--white,#fff);white-space:nowrap;overflow:hidden;
    text-overflow:ellipsis;max-width:120px}
  .sib-size{font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted-2,rgba(255,255,255,.5))}
  @media(max-width:440px){.sibcard{width:184px}}
  @media (prefers-reduced-motion:reduce){.sibcard{transition:none}.sibcard:hover{transform:none}}
  .links{display:flex;flex-wrap:wrap;gap:9px;margin:10px 0 0}
  .links a{border:1px solid var(--line);padding:10px 16px;border-radius:999px;font-weight:700;font-size:14px;color:var(--paper)}
  .fine{border-top:1px solid var(--line);margin-top:40px;padding-top:18px;color:var(--muted);font-size:12.5px}
  .fine a{color:var(--muted);text-decoration:underline}
  @media (max-width:430px){ .prod img,.prod-noimg{width:48px;height:48px} .prod-p,.prod-p b{font-size:17px}
    /* the halo follows the smaller thumb: 14px + half of 48px */
    .prod{background:radial-gradient(circle 40px at 38px 50%,color-mix(in srgb,var(--pg,transparent) 34%,transparent),transparent)} }

/* ═══ NEW FURNITURE — D451 / D452 ═══════════════════════════════════════════════════════════════
   Above: the sheet as it shipped. Below: what a page needs once it stops being a lander and becomes
   THE product page. Measurements are lifted from index.html's PDP and gate on purpose — the two
   surfaces must not read as two websites. */

/* index.html's vocabulary, copied WITH its definitions (check-css-vars allows no fallback-less
   reference, and these pages carry no <style> block to borrow from). --brand/--brand-2/--pd-glow
   default to the house green; a per-page inline style on .pdp-media overrides them, as the SPA does. */
:root{
  --white:#f3f6f2;--muted-2:#6f786f;--line-strong:rgba(255,255,255,.16);
  --green-bright:#5BE584;--green-glow:rgba(63,191,99,.45);--panel-2:#151915;
  --glass-2:rgba(28,36,29,.44);--glass-line:rgba(255,255,255,.13);--glass-hi:rgba(255,255,255,.22);
  --radius-lg:26px;
  --ease:cubic-bezier(.22,.61,.36,1);--ease-out:cubic-bezier(.16,1,.3,1);
  --ease-soft:cubic-bezier(.32,.72,0,1);--spring:cubic-bezier(.34,1.56,.64,1);
  --brand:#5BE584;--brand-2:#3FBF63;--pd-glow:rgba(91,229,132,.33)}

/* 🎬 THE FILM. position:fixed, not absolute — these pages have no .bg-fx wrapper; the aurora is two
   body pseudo-elements and the film is the layer below them. NO poster, ever (D446: it is opacity 0
   until it plays, so a poster paints never and cost 2.7s on cold LTE), and it holds at 0 until
   `playing` fires, so a stall shows the aurora and never a frozen frame. `.bg-vid{…opacity:0…}` is
   the literal shape filmfile-test greps for. */
.bg-vid{position:fixed;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;z-index:-2;
  transition:opacity 1.4s var(--ease-soft);pointer-events:none}
.bg-vid.on{opacity:1}
/* 🔄 THE HANDOFF. deals.html display:none's BOTH pseudo-elements when the film takes over — it can,
   it keeps a separate .aur-veil. There is none here, and a headline over an ungraded film is the
   "too dim / goes away" complaint in reverse. So ::before is repainted as the D419 veil and stopped;
   ::after (the gold band + grain) goes. animation:none matters as much as the opacity — a RUNNING
   animation on a full-bleed element stays composited whatever it looks like. */
html.bgvid body::before{animation:none;
  background:linear-gradient(180deg,rgba(10,12,10,.18) 0%,rgba(10,12,10,.42) 26%,rgba(10,12,10,.50) 50%,rgba(10,12,10,.42) 74%,rgba(10,12,10,.18) 100%),
   radial-gradient(120% 82% at 50% 44%,transparent 22%,rgba(6,8,6,.62) 100%)}
html.bgvid body::after{display:none}
@media (prefers-reduced-motion:reduce){ .bg-vid{display:none} }

/* ── 🖼 THE PICTURE BOX. ALWAYS a square (D442: "lets make sure they are all in the same sized
   squares"). Two modes and no third: a PHOTOGRAPH takes the whole square (D433), and his 🔬 macro
   flag — the only road to a crop, reviewed by eye, never guessed from a filename (D434/D435) — is
   the circle. */
/* 🪶 THE FRAME IS GONE — D685 (2026-10-19, "Can we get rid of the boxes completely and just have the
   product?"). The square is still the square (the photo's geometry, the pager, the swipe box are all
   measured off it) but it draws nothing of its own any more: no border, no panel gradient, and it
   no longer clips — the product's halo is allowed past its edges, which is what makes it float
   instead of sit in a window. */
.pdp-media{position:relative;width:100%;aspect-ratio:1/1;height:auto;margin:8px 0 14px;overflow:visible;
  display:flex;align-items:center;justify-content:center;
  border:0;border-radius:var(--radius-lg);background:transparent}
/* 🌕 THE AURA — D688 ("give product pictures a hue so they don't look awkward just sitting there" →
   "Each product's own color aura"). The storefront's product page has had this disc since D240
   (.pdp-media .disc: 300px, blur 24, opacity .6, in --pd-glow = the product's own photo-derived glow
   with the brand colour as its fallback); these pages set --pd-glow inline per product since D451 and
   never drew it. Drawn now as a pseudo-element — no markup on 500 pages — and, per the brief, BIGGER
   and SOFTER than the SPA's: the whole square wide, blur 32, sitting a little below the photo's
   middle where the product's weight is. z-index 0 keeps it under the blurred photo copy (1) and the
   photo (4), and it takes no taps. */
.pdp-media::before{content:"";position:absolute;left:0;right:0;top:6%;bottom:-6%;z-index:0;border-radius:50%;
  background:radial-gradient(circle,var(--pd-glow) 0%,transparent 64%);filter:blur(32px);opacity:.6;pointer-events:none}
/* 🟩 DEFAULT = THE WHOLE SQUARE (D433: "Can we use that entire square box for product pictures?").
   object-fit:CONTAIN over a blurred copy of the same photo — a square photo fills edge to edge, a
   portrait one shows WHOLE with its own colours filling the rest. NOTHING is cropped unless he asks:
   these are designed packages whose brand mark and strain name live in the bands a circle deletes
   first. The backdrop bleeds past the box (1.08) and the photo takes the box's own radius, so no
   straight seam shows inside the curve ("theres a squared line behind it!"). */
.pdp-photo{position:absolute;left:0;top:0;width:100%;height:100%;object-fit:contain;z-index:4;
  border-radius:var(--radius-lg);display:block}
.pdp-bg{position:absolute;left:0;top:0;width:100%;height:100%;object-fit:cover;z-index:1;
  pointer-events:none;transform:scale(1.08);filter:blur(28px) saturate(1.2);opacity:.9;display:none}
.pdp-bg.on{display:block}
/* the .ring rule that stood here (hidden since D453) went with its markup on 2026-10-19 — D685 */
/* 🔬 THE MACRO CIRCLE IS GONE — D453 (owner, 2026-09-09, shown the live box): "Show the whole
   picture, no circle." D435 had circled his 🔬 shots and D448 tried to make that circle sit right in
   the square; the answer was that the circle WAS the problem — cover-fit inside a round frame is a
   crop, and it was cutting the top and bottom off the jar. A macro now takes the identical square
   treatment as every other photograph (object-fit:contain, whole, in a fixed 1:1 box, its own
   blurred copy behind it), so there is no .mac rule left to write. The 🔬 flag still earns the MACRO
   chip below — that label is his, and it is information, not a shape. */
/* 🏷 THE TWO CHIPS (D435: "different color, and also the placement"). Dark glass, white small caps,
   off the brand green — which fought every scene. index.html stacks them in a .pdp-tags column; the
   DOM contract here has no wrapper, and a stack without one leaves a hole when only one chip shows,
   so they take opposite corners. The [hidden] rule is required: inline-flex beats the UA's
   [hidden]{display:none}, the same trap .brand-mark documents above. */
.pdp-media .chip{position:absolute;right:14px;z-index:6;display:inline-flex;align-items:center;gap:6px;
  font:700 10.5px/1 Inter,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#fff;
  background:rgba(10,13,10,.68);border:1px solid rgba(255,255,255,.22);border-radius:999px;
  padding:6px 10px;box-shadow:0 6px 16px rgba(0,0,0,.35);pointer-events:none}
.pdp-media .chip.real{top:14px}
.pdp-media .chip.macro{bottom:14px}
.pdp-media .chip[hidden]{display:none}

/* ── 🎞 THE PAGER (D439: "These boxes dont look good… pictures don't have to be show, but it hints
   that this is a sliding picture section") — so no thumbnails. THE STRETCHED DOT IS THE HINT: 24px
   against its neighbours' 7px, and that stretch is the entire "this slides" signal he asked for.
   Bare arrows, NO circle (D442: "remove circle around flashes") — the 38px tap target stays. */
.pdp-pager{display:flex;align-items:center;justify-content:center;gap:12px;margin:0 0 16px}
.pdp-pager[hidden]{display:none}
.pg-arrow{width:38px;height:38px;flex:none;border:0;background:transparent;padding:0;cursor:pointer;
  color:var(--white);font:400 26px/1 Inter,system-ui,sans-serif;opacity:.85;
  display:inline-flex;align-items:center;justify-content:center;transition:color .2s,opacity .2s,transform .2s}
.pg-arrow:hover{color:var(--brand);opacity:1}
.pg-arrow:active{transform:scale(.94)}
.pg-dots{display:flex;align-items:center;gap:7px;padding:9px 13px;border-radius:999px;
  background:var(--glass-2);border:1px solid var(--glass-line);box-shadow:inset 0 1px 0 rgba(255,255,255,.08)}
.pg-dot{position:relative;width:7px;height:7px;padding:0;border:0;border-radius:999px;cursor:pointer;
  background:rgba(255,255,255,.3);transition:width .28s var(--ease-out),background .28s,box-shadow .28s}
/* a 31px-tall tap zone on a 7px dot — only ±3px sideways (half the gap), so a neighbour's zone
   never covers this dot's own centre */
.pg-dot::before{content:"";position:absolute;inset:-12px -3px}
.pg-dot.macro{background:transparent;box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.45)}
.pg-dot.on{width:24px;background:var(--brand);box-shadow:0 0 12px color-mix(in srgb,var(--brand) 60%,transparent)}
.pg-count{font:700 11px/1 Inter,system-ui,sans-serif;letter-spacing:.08em;color:var(--muted);
  min-width:34px;text-align:center;white-space:nowrap}
.pg-count b{color:var(--white)}

/* ── ⚖️ THE WEIGHT PICKER. On the SPA these are buttons that re-render in place; here each weight is
   a REAL PAGE, so they are anchors — the whole point of D451 ("let's make sure absolutely everything
   has it's own page"). Being anchors, :link and :visited are styled IDENTICALLY: a customer who
   already looked at the 7g must not return to a picker where one pill is a different colour. */
.ezweights{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 20px}
.wpick,.wpick:link,.wpick:visited{display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;min-width:74px;padding:9px 15px;border-radius:13px;
  border:1px solid var(--line-strong);background:rgba(255,255,255,.03);
  font-family:Syne,sans-serif;font-weight:700;font-size:14px;color:var(--white);
  text-decoration:none;transition:border-color .15s,background .15s,color .15s}
/* A rung with no page of its own is a <button> (a hidden weight — see ez-page.js's weightPick).
   It must be pixel-identical to the <a> rungs beside it, so the browser's native button styling is
   reset outright: without appearance:none iOS draws its own rounded chrome over this one and the
   ladder comes out with one pill that does not match the others. */
button.wpick{appearance:none;-webkit-appearance:none;margin:0;cursor:pointer;font:inherit;
  font-family:Syne,sans-serif;font-weight:700;font-size:14px;line-height:normal}
.wpick:hover{border-color:color-mix(in srgb,var(--brand) 55%,transparent)}
.wpick.on,.wpick.on:link,.wpick.on:visited{border-color:var(--brand);
  background:color-mix(in srgb,var(--brand) 14%,transparent);color:var(--green-bright)}

/* ── ⚫ THE SWATCH CIRCLES — D686 (2026-10-19, his tap: "Swatch circles under the photo", "the
   chosen one ringed in green, the price beside it when it differs"). The SAME rungs as the ladder
   above (a .wpick link or button, .on on the one standing — ez-page.js tells them apart by nothing),
   so a color sibling (Puffco, D682) is picked exactly as a hidden weight is; what changes is the
   drawing. Each rung is a 22px circle in its own swatch (`--sw`, set inline by build-products from
   the catalog's #hex), 10px apart, centred under the photo; the chosen one wears a 2px green ring
   standing 3px off its edge (the ::after's inset: 3px of air + the 2px border = -5px). The hairline
   and the inner shadow are the storefront's own .wsw dot treatment — without them Black is invisible
   on ink and White has no edge. The ::before is the tap zone: 22px is a dot, not a target, so each
   circle answers to 32×44 — the full pitch sideways (half the gap each way), never a neighbour's.
   The name lives in aria-label/title and in the caption under the row. */
.ezweights.colors{justify-content:center;gap:10px;margin:0 0 8px}
.ezweights.colors .wpick,.ezweights.colors .wpick:link,.ezweights.colors .wpick:visited{position:relative;
  width:22px;height:22px;min-width:0;min-height:0;padding:0;border-radius:50%;
  border:1px solid rgba(255,255,255,.3);background:var(--sw,var(--muted-2));
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.28);transition:border-color .15s}
.ezweights.colors .wpick::before{content:"";position:absolute;inset:-11px -5px}
.ezweights.colors .wpick:hover{border-color:rgba(255,255,255,.6)}
.ezweights.colors .wpick.on,.ezweights.colors .wpick.on:link,.ezweights.colors .wpick.on:visited{
  border-color:rgba(255,255,255,.3);background:var(--sw,var(--muted-2))}
.ezweights.colors .wpick.on::after{content:"";position:absolute;inset:-5px;border:2px solid var(--green);
  border-radius:50%;pointer-events:none}
/* the caption: `Blue · $180` — the colour's name (never translated, translate="no" in the markup)
   and its price only when it is not this page's price; baked as the page's own colour, repainted on
   every tap by ez-page.js. Syne 700 14px: the chip's own face, so the word reads as the same label
   it was, now under the circles instead of inside a pill. */
.swcap{text-align:center;font-family:Syne,sans-serif;font-weight:700;font-size:14px;color:var(--white);margin:0 0 4px}
.swcap[hidden]{display:none}

/* ── 🏷 THE BADGE ROW — D685 (2026-10-19): "a NEW label is small green text; nothing is drawn over
   the photo." One line of small caps directly above the eyebrow: the day-deal note (#pdeal, the
   clock's element, amber) and the product's badges (New · Popular · Sale — Sale amber like the
   deal, the rest green). 10.5px, .12em tracking: the card's own badge measure.
   THE MARGINS ARE ARITHMETIC, NOT TASTE. The row is printed on every page (#pdeal has to be
   there for the clock) and is usually EMPTY — every child hidden — and an empty block collapses
   through: its 26px top, 0 bottom and the eyebrow's 6px top fold with the brand mark's -18px bottom
   to 26 − 18 = 8px, which is exactly the gap the eyebrow's own 26px top gave yesterday (and 26px
   after a bare pager, also as yesterday). With something showing: 8px under the mark, 6px above
   the eyebrow. The [hidden] rule is required: inline-block beats the UA's [hidden]{display:none},
   the trap .brand-mark documents above — and a hidden span that still painted would also give the
   row a line box and break the collapse. */
.pbadges{margin:26px 0 0;font:700 10.5px/1 Inter,Syne,system-ui,sans-serif;letter-spacing:.12em;
  text-transform:uppercase;color:var(--green)}
.pbadges+.eyebrow{margin-top:6px}
.pb{display:inline-block;margin:0 12px 0 0}
.pb:last-child{margin-right:0}
.pb.deal,.pb.sale{color:#ffb347}
.pb[hidden]{display:none}

/* ── ● THE STRAIN DOT — D686 ("Dot + word"): the 7px dot before Indica · Sativa · Hybrid in a /b/
   row's meta line, its colour inline from build-products (one hex per type, the cards' three). */
.sdot{display:inline-block;width:7px;height:7px;border-radius:50%;margin:0 5px 0 0;vertical-align:middle}

/* ── 🛒 THE BUY ROW. 44px is the floor on every control, and it is not a style preference: he ships
   from a phone and these are the two taps that end in an order. */
.ezbuy{display:flex;gap:12px;align-items:stretch;margin:0 0 6px}
.qty{display:inline-flex;align-items:center;border:1px solid var(--line-strong);border-radius:14px;overflow:hidden}
.qbtn{width:46px;min-height:44px;border:0;padding:0;cursor:pointer;font-size:20px;color:var(--white);
  background:rgba(255,255,255,.03);transition:background .15s,color .15s}
.qbtn:hover{background:rgba(63,191,99,.16);color:var(--green-bright)}
.qn{min-width:42px;text-align:center;font-family:Syne,sans-serif;font-weight:700;color:var(--white)}
#ezAdd{flex:1;min-height:48px;border:0;cursor:pointer;font-family:Syne,sans-serif;font-weight:800;
  font-size:16px;padding:15px 22px;border-radius:14px;background:var(--green);color:var(--ink);
  box-shadow:0 10px 30px -8px var(--green-glow);transition:transform .2s var(--ease),box-shadow .2s,filter .2s}
#ezAdd:hover{filter:brightness(1.05);box-shadow:0 16px 40px -8px var(--green-glow)}
#ezAdd:active{transform:translateY(1px)}
/* ⚠️ DISABLED MUST READ AS UNAVAILABLE, NOT AS DIM. The button is enabled from first paint on
   purpose (fail-open — if the catalog overlay never answers, a real product stays buyable), so OFF
   is the state a customer meets when we are telling them something. A 45%-opacity green slab still
   looks pressable; a grey one that will not move under a thumb does not. */
#ezAdd[disabled]{background:rgba(255,255,255,.06);border:1px solid var(--line);color:var(--muted-2);
  box-shadow:none;cursor:not-allowed;filter:none}
#ezAdd[disabled]:hover,#ezAdd[disabled]:active{transform:none;filter:none;box-shadow:none}

/* ── 🛍 THE HEADER BAG. index.html's #cartCount badge and its hidden state: scale(0), NOT
   display:none — it springs in so a tap reads as "that went in", and a box that never reflows
   cannot shift the header under a thumb mid-tap. .on is added at qty > 0. */
.ezbag{position:relative;display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:9px 13px;font-size:17px}
.ezbag .count{position:absolute;top:-5px;right:-5px;min-width:18px;height:18px;border-radius:999px;
  background:var(--green-bright);color:#04210f;font-size:11px;font-weight:800;padding:0 4px;
  display:flex;align-items:center;justify-content:center;
  transform:scale(0);transition:transform .25s var(--ease)}
.ezbag .count.on{transform:scale(1)}

/* ══ 🚪 D452 — THE GATE ON THE GENERATED PAGES → assets/ez-gate.css (2026-09-13, sweep high #2).
   The gate's rules lived here and so reached only the pages that link THIS sheet (/p/, /b/); the day
   and city pages and deals.html never had them. They are their own hashed sheet now, linked by every
   public generated page next to this one. Only the veil's deference to the gate stays below. */

/* the guard above covers the aurora; these are the movements this file added */
@media (prefers-reduced-motion:reduce){
  .bg-vid,.ezbag .count,.pg-dot,.wpick,#ezAdd{transition:none}
}
@media (max-width:430px){
  .ezbuy{gap:9px}
  .qbtn{width:42px}
}
/* fields do not inherit the body face from the user agent — see index.html */
input,textarea,select{font-family:inherit}

/* ═══ 🧭 THE REAL HEADER, ON EVERY GENERATED PAGE — D500 ═══════════════════════════════════════
   > "Also on product pages, there is no header!? Keep same header style for all product pages"
   > — owner, 2026-09-10, with a screenshot of a /p/ page next to the home page

   These pages had a two-anchor strip — logo · Full menu · 🛍 — while the storefront had cart ·
   search · centred wordmark · 🌐 · ☰ since D402. A product page is where a search result LANDS, so
   for a first-time visitor that strip was the whole site's chrome, and it did not look like the
   site. What follows is the storefront's bar rebuilt with the same geometry and the same rules:
   three columns with the mark dead centre (D402), NOT a box — no bar, no frost, no framed buttons,
   just marks over a fade (D412) — and the ☰ opening a real sheet, which was his tap out of two
   ("A real drawer, same rows as the site") rather than a link that pretends to be one.

   ⚠️ IT IS BUILT ONCE, HERE. Both halves — this sheet and the drawer wiring in ez-page.js — are
   ONE content-hashed download shared by all ~233 pages and their /es/ twins. Putting it in the
   page generator instead would have added ~3 KB of identical markup and CSS to every page, which
   is the exact mistake D451 spent a release undoing.

   Two things differ from the storefront, both because these are separate documents:
   · the SEARCH mark is an ANCHOR to /#/search, not a button — a static page cannot open an overlay
     that lives in index.html. The route was added there in the same change so the two marks do the
     identical thing from either side of the site.
   · there is no menu FILM. The clip is a 1.6 MB decode that D405 will only allow one of at a time,
     and these pages already run the page film; the sheet takes the aurora as its ground instead. */
  :root{--ezhead-h:74px;--ezgold:#D9C08A;--ezline-2:rgba(255,255,255,.16)}
  /* the fixed bar clears the content: .wrap's own padding was 20px all round */
  .wrap{padding-top:calc(var(--ezhead-h) + 8px)}
  .ezhead{position:fixed;top:0;left:0;right:0;z-index:120;height:var(--ezhead-h);
    display:flex;align-items:center;background:transparent;padding:0;
    transition:height .3s cubic-bezier(.22,1,.36,1)}
  /* the fade runs PAST the bar so there is no edge anywhere — a gradient that stops at the bar's
     own bottom is a box with a soft floor, which is still a box (index.html:1001) */
  .ezhead::before{content:"";position:absolute;left:0;right:0;top:0;height:calc(100% + 44px);z-index:-1;pointer-events:none;
    background:linear-gradient(180deg,rgba(6,8,6,.82) 0%,rgba(6,8,6,.55) 48%,rgba(6,8,6,0) 100%);transition:opacity .3s}
  .ezhead.scrolled{height:calc(var(--ezhead-h) - 8px)}
  .ezhead.scrolled::before{background:linear-gradient(180deg,rgba(6,8,6,.94) 0%,rgba(6,8,6,.74) 48%,rgba(6,8,6,0) 100%)}
  .ezh-nav{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;width:100%;max-width:860px;margin:0 auto;padding:0 20px;gap:14px}
  .ezh-left{display:flex;align-items:center;gap:10px;justify-self:start;min-width:0}
  .ezh-right{display:flex;align-items:center;gap:10px;justify-self:end;min-width:0}
  .ezh-brand{display:flex;align-items:center;justify-content:center;justify-self:center;height:50px;transition:height .3s cubic-bezier(.22,1,.36,1)}
  .ezhead.scrolled .ezh-brand{height:42px}
  .ezh-brand img{height:44px;width:auto;display:block;max-width:none}
  .ezhead.scrolled .ezh-brand img{height:38px}
  /* one bare mark, no frame — D412, and D499 for the globe that joined them */
  .ezh-icon{position:relative;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
    width:42px;height:42px;border:0;border-radius:12px;background:transparent;color:var(--paper);cursor:pointer;
    transition:color .2s,transform .15s;-webkit-tap-highlight-color:transparent}
  .ezh-icon:hover{color:var(--green)}
  .ezh-icon:active{transform:scale(.93)}
  .ezh-icon svg{width:22px;height:22px;pointer-events:none;display:block}
  .ezh-icon .count{position:absolute;top:-4px;right:-4px;min-width:18px;height:18px;border-radius:999px;
    background:var(--green);color:var(--ink);font-family:Syne,Inter,sans-serif;font-weight:800;font-size:11px;line-height:18px;
    text-align:center;padding:0 4px;transform:scale(0);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}
  .ezh-icon .count.on{transform:scale(1)}
  /* ☰ → ✕ in place, the same swap the storefront does */
  /* ⚠️ these four selectors all carry .ezh-icon on purpose. `.ezh-icon svg{display:block}` above is
     (0,1,1), so a bare `.ezh-x{display:none}` at (0,1,0) LOSES to it and both glyphs paint at once —
     which is exactly what the first build did, a ☰ and an ✕ side by side in the corner. */
  .ezh-icon .ezh-bars,.ezh-icon .ezh-x{width:22px;height:22px;display:block}
  .ezh-icon .ezh-x{display:none}
  html.ezmnav-up .ezh-icon .ezh-bars{display:none}
  html.ezmnav-up .ezh-icon .ezh-x{display:block}
  html.ezmnav-up .ezhead{z-index:140}
  html.ezmnav-up .ezhead::before{display:none}   /* the sheet's own veil is the ground */

  /* ── the sheet ──────────────────────────────────────────────────────────────────────────────── */
  .ezmnav{position:fixed;inset:0;z-index:130;visibility:hidden;
    background:linear-gradient(180deg,rgba(6,8,6,.42) 0%,rgba(6,8,6,.26) 40%,rgba(6,8,6,.36) 72%,rgba(6,8,6,.62) 100%);
    -webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);
    display:flex;flex-direction:column;align-items:center;justify-content:flex-start;
    padding:calc(var(--ezhead-h) + 26px) 20px calc(34px + env(safe-area-inset-bottom));overflow-y:auto;overscroll-behavior:contain;
    transform:translateX(100%);transition:transform .42s cubic-bezier(.22,1,.36,1),visibility 0s linear .42s}
  .ezmnav.open{transform:none;visibility:visible;transition:transform .42s cubic-bezier(.22,1,.36,1),visibility 0s}
  .ezmn-panel{position:relative;z-index:2;margin:auto 0;display:flex;flex-direction:column;align-items:center;width:100%;gap:0}
  /* one line each, centred, in the site's face — D407 as amended by D488 (Syne, not Helvetica) */
  .ezmnav a{position:relative;display:block;width:auto;max-width:100%;text-align:center;white-space:nowrap;
    font-family:Syne,Inter,sans-serif;font-weight:700;font-size:clamp(19px,6.3vw,40px);line-height:1.12;
    text-transform:uppercase;padding:8px 22px;color:var(--paper);
    text-shadow:0 1px 2px rgba(0,0,0,.5),0 4px 18px rgba(0,0,0,.4);
    transition:color .2s,transform .2s cubic-bezier(.22,1,.36,1)}
  .ezmnav a:hover,.ezmnav a:active{color:var(--green);transform:scale(1.04)}
  .ezmnav a.ezmn-call{color:var(--green)}
  .ezmnav a.ezmn-gold{color:var(--ezgold)}
  .ezmnav a.ezmn-gold:hover,.ezmnav a.ezmn-gold:active{color:var(--ezgold)}
  .ezmnav a.ezmn-lang{font-family:Syne,Inter,sans-serif;font-weight:700;font-size:13px;letter-spacing:.16em;
    text-transform:uppercase;color:var(--muted);padding:6px 18px;margin-top:6px}
  .ezmnav a.ezmn-lang:hover,.ezmnav a.ezmn-lang:active{color:var(--paper);transform:none}
  /* 🔒 LOCKED LIKE THE GATE — D414. iOS Safari scrolls straight through overflow:hidden on a touch,
     so the page under the sheet kept moving and closing it dropped him somewhere else. The body is
     pinned at top:-scrollY (--ezlock-y, set when the sheet opens) so it does not jump to the top
     under the sheet, and closing puts the scroll back to the pixel. */
  html.ezmnav-up{overscroll-behavior:none}
  html.ezmnav-up body{position:fixed;left:0;right:0;width:100%;overflow:hidden;top:var(--ezlock-y,0px)}
  @media (prefers-reduced-motion:reduce){.ezmnav{transition:none}.ezhead,.ezh-brand{transition:none}}

/* ═══ 🎬 THE LOADING SCREEN — D505 ══════════════════════════════════════════════════════════════
   > "the stuff for the product boxes should be sitting there before the customer gets there, the
   >  correct and not glitchy stuff, so a second or two loading page can make sure all those are
   >  right before customer sees errors" — owner, 2026-09-11

   Built like the storefront's old one (index.html .ez-veil-el, removed by D623 — the cross-fade
   replaced it there; this one stays): the wordmark breathing on flat ink, no spinner, no progress
   bar — a loader that reports progress it does not have is a lie with an animation. It is raised by an inline stamp in the head and dropped by ez-page.js the moment
   the catalog overlay has been applied, so every correction that pass makes — price, sale, photo,
   sold out, the weights row, the "More from" list — happens behind it rather than under his
   customer's eyes.
   NOTE the opacity/visibility pair rather than display: a display swap cannot transition, and the
   whole point is that it LEAVES softly instead of blinking off. */
  .ez-veil-el{position:fixed;inset:0;z-index:900;display:flex;align-items:center;justify-content:center;
    background:var(--ink);opacity:0;visibility:hidden;pointer-events:none;
    transition:opacity .18s ease,visibility 0s linear .18s}
  html.ez-veil .ez-veil-el{opacity:1;visibility:visible;pointer-events:auto;transition:opacity .12s ease,visibility 0s}
  .ez-veil-el img{width:132px;height:auto;opacity:.92;animation:ezVeilPulse 1.6s ease-in-out infinite}
  @keyframes ezVeilPulse{0%,100%{opacity:.45}50%{opacity:.95}}
  @media (prefers-reduced-motion:reduce){.ez-veil-el img{animation:none;opacity:.8}}
  /* the gate outranks it: two covers stacked would put a dark square over his ZIP field */
  html.gate-up .ez-veil-el{display:none}
