/* thegranolabakery.com. Palette from the 2026 label system: deep forest, leaf green,
   cream, bakery orange.

   TWO THEMES, ONE SET OF TOKENS. Every colour on this site is declared once, in the
   two token blocks at the top of this file, and every rule below reads it through
   var(). audit-theme in tools/check.py refuses a colour literal written anywhere
   else, which is what keeps the dark theme complete: a colour that is not a token
   cannot follow the theme, so it is not allowed to exist.

   The theme itself is an attribute on <html>. A short script in the head of every
   page sets it before the first paint, from the reader's stored choice if there is
   one and from prefers-color-scheme if there is not, so there is no flash of the
   wrong theme and no @media duplicate of the palette. The site's grid is rendered
   by script, so a page that runs no script has no shop either; the attribute is
   always there in practice.

   Layout law: one compact sticky bar plus the category strip stay under 100px
   together, the promo carousel is capped at about 300px desktop and 200 on a
   phone, and the product grid is the surface. */

:root {
  /* ── ground and ink ── */
  --page: #faf7f0;
  --paper: #ffffff;
  --paper-2: #fcfbf7;
  --ink: #1d2b24;
  --heading: #17352a;
  --muted: #5f6f66;
  --faint: #8d9b93;
  --line: #e4e0d5;
  --line-2: #efece3;
  --line-3: #d5cfbe;

  /* ── brand greens ── */
  --leaf: #3f6f58;
  --leaf-soft: #dce9e0;
  --leaf-soft-2: #cfe1d6;
  --leaf-line: #c6dbcd;

  /* ── bakery orange. Two jobs, two tokens: --accent is a FILL and always carries
     --accent-ink on top of it, --accent-text is the same orange darkened until it
     passes AA as small text on a light ground. Hover goes lighter, not darker,
     because the ink on the button is dark. ── */
  --accent: #e8862c;
  --accent-hover: #f09a44;
  --accent-ink: #17352a;
  --accent-text: #a75a12;

  /* ── the dark bands: promo, signup, footer ── */
  --band: #17352a;
  --band-2: #1f4636;
  --band-3: #2a5b45;
  --band-ink: #faf7f0;
  --band-sub: #cbdcd1;
  --band-faint: #a8cdb8;
  --band-line: #4e7a63;
  --band-field: rgba(255, 255, 255, 0.07);
  --footer: #10241c;
  --footer-ink: #a9c8b5;
  --footer-link: #e6efe9;
  --footer-line: #244636;

  /* ── product tiles. WHITE IN BOTH THEMES: the packshots are shot on white
     canvas, so a white tile on a dark page is the standard shelf treatment and a
     dark tile would put a white box inside it. The coming-soon placeholder is the
     one that follows the theme, since it is drawn rather than photographed. ── */
  --tile: #ffffff;
  --tile-a: #f3f7f4;
  --tile-b: #eaf1ec;
  --tile-ink: #3f6f58;

  /* ── THE BANNER, in the language of the brand's own listing images. Those images
     are a cream field with a soft sweeping boundary, a ribbon of the product's
     colour riding that boundary, an ink drawing on the cream and real food
     photography beside it. The cream is declared HERE ONLY and is deliberately not
     restated by the dark theme, for the same reason the product tiles are not: the
     ink drawings were made for cream, and a dark-adapted cream would be a colour
     the brand does not own. The three ribbon tokens are the three listing families
     the slides quote: macadamia gold, protein-bar orange, cocoa brown. ── */
  --cream-panel: #f4f0e5;
  --ribbon-olive: #d7a12e;
  --ribbon-leaf: #dc7c22;
  --ribbon-cocoa: #a8703c;

  /* the edge arrows sit on the dark band on one side of the carousel and on the
     cream panel on the other, so one quiet disc has to read on both: a deep,
     translucent forest with a cream chevron and a hairline ring */
  --arrow-bg: rgba(16, 36, 28, 0.42);
  --arrow-bg-hover: rgba(16, 36, 28, 0.82);
  --arrow-ink: #faf7f0;
  --arrow-ring: rgba(250, 247, 240, 0.38);

  /* ── effects ── */
  --shadow: 0 6px 20px rgba(23, 53, 42, 0.08);
  --shadow-lift: 0 14px 34px rgba(23, 53, 42, 0.16);
  --shadow-art: 0 16px 34px rgba(0, 0, 0, 0.32);
  --shadow-cut: 0 10px 22px rgba(0, 0, 0, 0.45);
  /* the anchor photo's shadow, and the same shadow at the top of its lift */
  --shadow-piece: 0 8px 14px rgba(16, 36, 28, 0.36);
  --shadow-piece-lift: 0 15px 22px rgba(16, 36, 28, 0.3);
  --shadow-drawer: -18px 0 50px rgba(0, 0, 0, 0.3);
  --scrim: rgba(16, 36, 28, 0.55);
  --scrim-modal: rgba(16, 36, 28, 0.72);
  --scrim-photo: rgba(16, 36, 28, 0.92);
  --bar-edge: rgba(23, 53, 42, 0.04);
  --lb-close-hover: rgba(255, 255, 255, 0.12);

  /* ── metrics ── */
  --radius: 12px;
  --max: 1360px;
  --bar-h: 60px;
  --strip-h: 38px;
  --stack: calc(var(--bar-h) + var(--strip-h));

  /* The slide's own padding, declared as tokens because the cream panel has to
     cancel it exactly to reach the band's edges, and the breakpoints below change
     it. --panel-edge is the width of the swoosh zone: left of it the band is
     forest, right of it the cream is solid, and everything that must land on cream
     measures from it. */
  --slide-pad-x: 20px;
  --slide-pad-t: 22px;
  --slide-pad-b: 30px;
  --panel-edge: clamp(56px, 9vw, 132px);
  /* the width the edge arrows claim at the band's two edges. The slide copy, the
     dots and the drawing all keep clear of it, which is what stops an arrow from
     landing on a headline or on the middle of a sketch at any width. */
  --arrow-rail: 44px;
}

/* THE DARK THEME. Deep charcoal forest ground, cream text, card surfaces one step
   lighter than the page, orange lifted until it reads on a dark ground. */
:root[data-theme="dark"] {
  --page: #101f19;
  --paper: #182e26;
  --paper-2: #1d362c;
  --ink: #eae4d7;
  --heading: #f4efe4;
  --muted: #a5b9ad;
  --faint: #8ba295;
  --line: #2a463a;
  --line-2: #22392f;
  --line-3: #3a5b4b;

  --leaf: #89c4a4;
  --leaf-soft: #224234;
  --leaf-soft-2: #2b5142;
  --leaf-line: #31584a;

  --accent: #ec8f38;
  --accent-hover: #f5a95c;
  --accent-ink: #17352a;
  --accent-text: #f0a35a;

  --band: #1b3a2d;
  --band-2: #24523e;
  --band-3: #2f6a4f;
  --band-ink: #f4efe4;
  --band-sub: #cbdcd1;
  --band-faint: #a8cdb8;
  --band-line: #497359;
  --band-field: rgba(255, 255, 255, 0.06);
  --footer: #0c1a14;
  --footer-ink: #a9c8b5;
  --footer-link: #e6efe9;
  --footer-line: #234334;

  --tile-a: #1b3529;
  --tile-b: #223f31;
  --tile-ink: #8fc3a6;

  /* --cream-panel is not restated on purpose: the banner's cream field is a brand
     colour, not a theme colour. The ribbons ride that cream and stay with it. The
     arrow disc goes a shade heavier so it still reads against a darker band. */
  --arrow-bg: rgba(6, 16, 12, 0.5);
  --arrow-bg-hover: rgba(6, 16, 12, 0.86);

  --shadow: 0 6px 20px rgba(0, 0, 0, 0.44);
  --shadow-lift: 0 14px 34px rgba(0, 0, 0, 0.56);
  --shadow-art: 0 16px 34px rgba(0, 0, 0, 0.5);
  --shadow-cut: 0 10px 22px rgba(0, 0, 0, 0.6);
  --shadow-piece: 0 8px 14px rgba(0, 0, 0, 0.48);
  --shadow-piece-lift: 0 15px 22px rgba(0, 0, 0, 0.42);
  --shadow-drawer: -18px 0 50px rgba(0, 0, 0, 0.6);
  --scrim: rgba(4, 12, 9, 0.66);
  --scrim-modal: rgba(4, 12, 9, 0.8);
  --scrim-photo: rgba(4, 12, 9, 0.94);
  --bar-edge: rgba(0, 0, 0, 0.3);
}

* { margin: 0; padding: 0; box-sizing: border-box; }
/* Every panel on this site is opened and closed with the hidden attribute, and a
   class that sets display beats the browser's own [hidden] rule. So the attribute
   is given teeth here, once, rather than remembered at every panel. */
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--page);
  color: var(--ink);
  line-height: 1.55;
  font-size: 15px;
  transition: background-color 0.2s ease, color 0.2s ease;
}
img { max-width: 100%; display: block; }
a { color: var(--leaf); }
button, input, select { font: inherit; color: inherit; }

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

.wrap { max-width: var(--max); margin: 0 auto; padding: 0 20px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: 12px; top: -60px; z-index: 200;
  background: var(--band); color: var(--band-ink); padding: 10px 16px;
  border-radius: 0 0 10px 10px; text-decoration: none; font-weight: 700;
}
.skip:focus { top: 0; }

/* ── buttons ── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 20px; border-radius: 999px; font-weight: 700; font-size: 0.92rem;
  text-decoration: none; border: 2px solid transparent; cursor: pointer;
  transition: background 0.16s ease, color 0.16s ease, border-color 0.16s ease;
}
.btn.block { width: 100%; }
.btn.primary { background: var(--accent); color: var(--accent-ink); }
.btn.primary:hover { background: var(--accent-hover); }
.btn.leaf { background: var(--band); color: var(--band-ink); }
.btn.leaf:hover { background: var(--band-2); }
.btn.quiet { background: var(--leaf-soft); color: var(--heading); }
.btn.quiet:hover { background: var(--leaf-soft-2); }
.btn.outline { border-color: var(--line); background: var(--paper); color: var(--heading); }
.btn.outline:hover { border-color: var(--leaf); }
.iconbtn {
  background: none; border: none; cursor: pointer; color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 8px;
}
.iconbtn:hover { color: var(--ink); background: var(--line-2); }

/* ── top bar ── */
.topbar {
  position: sticky; top: 0; z-index: 60;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 var(--bar-edge);
  transition: background-color 0.2s ease;
}
.topbar-in {
  height: var(--bar-h);
  max-width: var(--max); margin: 0 auto; padding: 0 20px;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: 20px;
}
/* THE BRAND LOCKUP, read first. The wordmark carries the weight and the size; the
   slogan sits under it at a size that is meant to be read rather than decoded, so
   the old wide tracking is gone. */
.brand { text-decoration: none; color: var(--heading); line-height: 1.12; }
.brand-name {
  display: block; font-weight: 800; font-size: 1.12rem;
  letter-spacing: -0.01em;
}
.brand-tag {
  display: block; font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.01em; color: var(--leaf);
}

.search { position: relative; display: flex; align-items: center; max-width: 460px; width: 100%; justify-self: center; }
.search-icon { position: absolute; left: 13px; color: var(--muted); pointer-events: none; }
.search input {
  width: 100%; height: 38px; padding: 0 38px 0 38px;
  border: 1.5px solid var(--line); border-radius: 999px; background: var(--page);
  outline: none; font-size: 0.9rem;
}
.search input::placeholder { color: var(--faint); }
.search input:focus { border-color: var(--leaf); background: var(--paper); }
.search-clear { position: absolute; right: 6px; background: none; border: none; cursor: pointer; color: var(--muted); padding: 4px; border-radius: 999px; display: flex; }
.search-clear:hover { color: var(--ink); }

.topbar-right { display: flex; align-items: center; gap: 14px; }

/* THE UTILITY BLOCKS, in Amazon's two-line shape: a quiet line of context above a
   bold line that says where the link goes. Both routes are real places on this
   page; there is no account, no orders and no deliver-to behind them. */
.util {
  text-decoration: none; color: var(--ink); line-height: 1.16;
  padding: 4px 6px; border-radius: 8px; white-space: nowrap;
}
.util:hover { background: var(--line-2); }
.util-top { display: block; font-size: 0.68rem; color: var(--muted); }
.util-main { display: block; font-size: 0.85rem; font-weight: 800; color: var(--heading); }

.modeswitch {
  display: inline-flex; background: var(--page); border: 1.5px solid var(--line);
  border-radius: 999px; padding: 2px;
}
.mode {
  border: none; background: transparent; color: var(--muted);
  font-weight: 700; font-size: 0.8rem; letter-spacing: 0.02em;
  padding: 5px 15px; border-radius: 999px; cursor: pointer;
  transition: background 0.16s ease, color 0.16s ease;
}
.mode.is-on { background: var(--band); color: var(--band-ink); }
body.wholesale .mode.is-on { background: var(--accent); color: var(--accent-ink); }

/* the theme switch: one button, two icons, the theme decides which one shows */
.themebtn {
  background: none; border: none; cursor: pointer; color: var(--heading);
  padding: 6px; border-radius: 8px; display: flex;
}
.themebtn:hover { background: var(--line-2); }
.themebtn .moon { display: none; }
:root[data-theme="dark"] .themebtn .moon { display: block; }
:root[data-theme="dark"] .themebtn .sun { display: none; }

.cartbtn { position: relative; background: none; border: none; cursor: pointer; color: var(--heading); padding: 6px; border-radius: 8px; display: flex; }
.cartbtn:hover { background: var(--line-2); }
.cart-count {
  position: absolute; top: -2px; right: -4px; min-width: 18px; height: 18px;
  padding: 0 5px; border-radius: 999px; background: var(--accent); color: var(--accent-ink);
  font-size: 0.68rem; font-weight: 800; line-height: 18px; text-align: center;
}
.cart-count[data-empty="true"] { background: var(--line); color: var(--muted); }

/* ── category strip ── */
.catstrip { border-top: 1px solid var(--line-2); background: var(--paper); }
.catstrip-in {
  height: var(--strip-h); max-width: var(--max); margin: 0 auto; padding: 0 20px;
  display: flex; align-items: center; gap: 6px;
  overflow-x: auto; scrollbar-width: none;
}
.catstrip-in::-webkit-scrollbar { display: none; }
.pill {
  flex: 0 0 auto; border: 1px solid transparent; background: transparent;
  color: var(--muted); border-radius: 999px; padding: 4px 13px;
  font-size: 0.82rem; font-weight: 700; cursor: pointer; white-space: nowrap;
}
.pill:hover { color: var(--heading); background: var(--page); }
.pill.is-on { background: var(--leaf-soft); color: var(--heading); border-color: var(--leaf-line); }

/* ── the promo carousel ─────────────────────────────────────────────────────────
   Three slides on a slow crossfade. It is capped by its content the way the old
   band was: the stage has a floor, the type scale and the padding hold the
   ceiling, so copy that outgrows the slide pushes it visibly rather than being
   clipped. Every moving part is a CSS keyframe, so the reduced-motion guard at
   the foot of this file switches all of it off at once, and the script stops the
   auto-advance for the same reader. */
.promo { background: var(--band); color: var(--band-ink); position: relative; }
/* THE SLIDES ARE STACKED IN ONE GRID CELL rather than positioned on top of each
   other, so the stage is as tall as its tallest slide: copy that outgrows the band
   pushes it visibly instead of being clipped behind the controls. The floor below
   is what holds the height when the copy is short. */
.promo-stage {
  position: relative; display: grid;
  min-height: 236px;
}
/* overflow hidden is load bearing here: the cream panel and the ingredient photos
   are deliberately drawn past the band's edges so the composition is cropped by the
   frame, the way the listing images are */
.slide {
  grid-area: 1 / 1; overflow: hidden;
  opacity: 0; visibility: hidden;
  transition: opacity 0.7s ease, visibility 0.7s ease;
}
.slide.is-on { opacity: 1; visibility: visible; }
/* THREE TRACKS, and the middle one is why the art can never touch the words: copy,
   then a corridor that belongs to the ingredient photography, then the art column
   whose left edge is where the cream panel begins. Nothing is positioned against
   the text; the grid keeps them apart at every width. */
.slide-in {
  position: relative;
  max-width: var(--max); margin: 0 auto;
  padding: var(--slide-pad-t) var(--slide-pad-x) var(--slide-pad-b);
  height: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(70px, 12%, 170px) clamp(190px, 40%, 440px);
  align-items: center; gap: 20px;
}
.slide[data-tone="leaf"] { background: linear-gradient(100deg, var(--band) 42%, var(--band-2) 100%); }
.slide[data-tone="cocoa"] { background: linear-gradient(100deg, var(--band) 34%, var(--band-3) 100%); }
.slide-copy { position: relative; z-index: 2; padding-left: var(--arrow-rail); }
.slide h1, .slide h2 {
  font-size: clamp(1.5rem, 2.6vw, 2.1rem); line-height: 1.12;
  font-weight: 800; letter-spacing: -0.01em; color: var(--band-ink);
  max-width: 22ch;
}
.slide-sub { margin-top: 8px; color: var(--band-sub); font-size: 0.95rem; max-width: 46ch; }
.slide .btn { margin-top: 14px; }

/* THE BANNER ART, ruled 2026-08-17 after Bryant's markup on the first attempt. The
   old version put white silhouettes on olive ellipses, which is not what the brand
   sells under. The listing images do this instead, and so does this band now:

     a CREAM FIELD on the right, its left boundary an irregular sweep rather than
     any geometric shape, with a RIBBON of the product family's colour riding that
     boundary the way the gold ribbon rides the green on the macadamia sales-pitch
     image;
     the INK DRAWING on the cream, at its own original colours and at generous
     scale, and completely still, because a sketch that bobs reads as a sticker;
     REAL FOOD PHOTOGRAPHY in the corridor beside it, two or three cutouts, one of
     them the anchor and the only moving thing on the page.

   The panel is built from two pieces because its width is unbounded: an SVG that
   draws the swoosh at a fixed zone width, and a flat cream rectangle that carries
   on from there to the edge of the screen. Stretching one SVG across both would
   have distorted the sweep at every viewport. */
.slide-art { position: relative; z-index: 1; align-self: stretch; min-height: 150px; }

.slide-panel {
  position: absolute; z-index: 0; pointer-events: none;
  left: 0; right: -50vw;
  top: calc(var(--slide-pad-t) * -1);
  bottom: calc(var(--slide-pad-b) * -1);
}
.panel-edge {
  position: absolute; top: 0; bottom: 0; left: 0;
  width: var(--panel-edge); height: 100%; overflow: visible;
}
.panel-fill {
  position: absolute; top: 0; bottom: 0; right: 0;
  left: calc(var(--panel-edge) - 1px);
  background: var(--cream-panel);
}
.panel-cream { fill: var(--cream-panel); }
.panel-ribbon {
  fill: none; stroke: var(--ribbon-olive); stroke-width: 13;
  stroke-linecap: round; opacity: 0.92;
}
.slide[data-tone="leaf"] .panel-ribbon { stroke: var(--ribbon-leaf); }
.slide[data-tone="cocoa"] .panel-ribbon { stroke: var(--ribbon-cocoa); }

/* the drawing, boxed rather than sized, so it is always inside the solid cream and
   always as big as that box allows */
.slide-stencil {
  position: absolute; z-index: 2;
  left: calc(var(--panel-edge) + 8px); right: calc(var(--arrow-rail) + 6px);
  top: 5%; bottom: 5%;
  display: flex; align-items: center; justify-content: center;
}
.slide-stencil img { width: auto; height: auto; max-width: 100%; max-height: 100%; }

/* THE INGREDIENT PHOTOGRAPHY. Its own grid track, so it is beside the words and
   never over them. Each piece is a box that the photo is centred inside, which is
   what lets one rule size a pile of macadamias and a pair of cinnamon sticks
   without either of them growing taller than the band. Sparse is the ruling: the
   second accent waits for a wide screen and the third for a wider one. */
.slide-ing { position: relative; z-index: 4; align-self: stretch; }
.ing { position: absolute; display: flex; pointer-events: none; }
.ing img { margin: auto; width: auto; height: auto; max-width: 100%; max-height: 100%; }
/* ONE thing moves on this banner: the anchor photo, six pixels over six seconds,
   with its shadow opening as it lifts. Everything else is still. Every box sits
   FULLY INSIDE the band: the first cut of this banner bled the anchor off the
   bottom and the accents off the top, and the stage's overflow clipping turned
   both into flat amputated edges (Bryant's markup, 2026-08-17). The accents
   carry the same drop shadow as the anchor so a dark piece separates from the
   forest ground instead of sinking into it. */
.ing.a0 {
  left: 4%; right: -48%; bottom: 6%; height: 68%;
  filter: drop-shadow(var(--shadow-piece));
  animation: floatpiece 6s ease-in-out infinite;
}
.ing.a1 {
  left: 2%; right: 34%; top: 7%; height: 30%; display: none;
  filter: drop-shadow(var(--shadow-piece));
}
.ing.a2 {
  left: 48%; right: -30%; top: 9%; height: 20%; display: none;
  filter: drop-shadow(var(--shadow-piece));
}
@media (min-width: 900px) { .ing.a1 { display: flex; } }
@media (min-width: 1280px) { .ing.a2 { display: flex; } }

@keyframes floatpiece {
  0%, 100% { transform: translateY(0); filter: drop-shadow(var(--shadow-piece)); }
  50% { transform: translateY(-6px); filter: drop-shadow(var(--shadow-piece-lift)); }
}

/* THE CONTROLS, ruled 2026-08-17: the corner circles and the pause pill are gone
   and the band carries edge arrows instead. They are real buttons at a 44px hit
   target, quiet at rest and bright on hover or focus, and the disc is dark enough
   to read on the cream panel and light enough at its ring to read on the forest. */
.promo-arrow {
  position: absolute; z-index: 5; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; border-radius: 999px; cursor: pointer;
  background: var(--arrow-bg); border: 1px solid var(--arrow-ring);
  color: var(--arrow-ink);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background 0.16s ease;
}
.promo-arrow.prev { left: 8px; }
.promo-arrow.next { right: 8px; }
.promo-arrow:hover, .promo-arrow:focus-visible { background: var(--arrow-bg-hover); }

/* the dots stay, at the foot of the band on the forest side, where the copy's own
   bottom padding has already reserved the room for them */
.promo-dots {
  position: absolute; z-index: 5; bottom: 10px; left: 0; right: 0;
  max-width: var(--max); margin: 0 auto;
  padding: 0 var(--slide-pad-x) 0 calc(var(--slide-pad-x) + var(--arrow-rail));
  display: flex; align-items: center; gap: 7px;
  pointer-events: none;
}
.dot {
  width: 9px; height: 9px; padding: 0; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--band-line); background: transparent;
  pointer-events: auto;
}
.dot.is-on { background: var(--accent); border-color: var(--accent); }

/* ── sections ── */
section { padding: 34px 0; }
.promo { padding: 0; }
.kicker { color: var(--accent-text); font-weight: 800; letter-spacing: 0.2em; text-transform: uppercase; font-size: 0.7rem; }
.kicker.light { color: var(--band-faint); }
h2 { font-size: clamp(1.25rem, 2vw, 1.6rem); color: var(--heading); line-height: 1.2; }
.sub { margin-top: 8px; color: var(--muted); max-width: 62ch; }
.sub.light { color: var(--band-sub); }

.grid-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
.grid-count { color: var(--muted); font-size: 0.85rem; }
.grid-empty { color: var(--muted); padding: 40px 0; text-align: center; }

/* ── the product grid ── */
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
@media (min-width: 768px) { .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1280px) { .grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }

.card {
  position: relative; background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 10px; display: flex; flex-direction: column;
  gap: 6px; transition: box-shadow 0.16s ease, border-color 0.16s ease;
}
.card:hover { border-color: var(--line-3); box-shadow: var(--shadow); }
.card.soon { background: var(--paper-2); }
.cat-tag {
  align-self: flex-start; background: var(--leaf-soft); color: var(--heading);
  font-size: 0.62rem; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase;
  padding: 2px 8px; border-radius: 999px;
}
.card-art {
  width: 100%; aspect-ratio: 1 / 1; border: none; padding: 0; margin-top: 2px;
  border-radius: 8px; background: var(--tile); cursor: pointer;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.card-art img { width: 100%; height: 100%; object-fit: contain; }
.noart {
  width: 100%; height: 100%; display: grid; place-items: center; text-align: center;
  background: repeating-linear-gradient(135deg, var(--tile-a) 0 10px, var(--tile-b) 10px 20px);
  color: var(--tile-ink); font-size: 0.66rem; font-weight: 800;
  letter-spacing: 0.14em; text-transform: uppercase; padding: 10px; line-height: 1.5;
}
.card-name {
  background: none; border: none; padding: 0; text-align: left; cursor: pointer;
  color: var(--heading); font-size: 0.93rem; font-weight: 700; line-height: 1.28;
}
.card-name:hover { text-decoration: underline; }
.card-size { color: var(--muted); font-size: 0.78rem; }

.price-block { margin-top: 2px; }
.price-main { color: var(--heading); font-weight: 800; font-size: 1.2rem; letter-spacing: -0.01em; }
.price-unit { color: var(--muted); font-size: 0.76rem; margin-left: 5px; }
.price-quote { color: var(--heading); font-weight: 700; font-size: 0.92rem; }
.price-soon { color: var(--muted); font-weight: 700; font-size: 0.85rem; }
.price-link { color: var(--accent-text); font-weight: 700; font-size: 0.85rem; }
.price-draft {
  display: block; color: var(--muted); font-size: 0.68rem; letter-spacing: 0.04em;
}
/* THE v1 AS-OF LINE. The price on a v1 card is the one the snapshot read off Amazon,
   and the date it was read sits beside it always, which is the standard the run
   adopted on 2026-08-15 for showing an Amazon price anywhere. */
.price-asof { display: block; color: var(--muted); font-size: 0.7rem; letter-spacing: 0.02em; }

.card-actions { margin-top: auto; padding-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.card-actions .btn { width: 100%; padding: 9px 10px; font-size: 0.86rem; }
.secondary-line { font-size: 0.74rem; color: var(--muted); line-height: 1.35; }
.secondary-line a { color: var(--leaf); font-weight: 600; }

/* ── qty stepper ── */
.qty { display: inline-flex; align-items: center; border: 1.5px solid var(--line); border-radius: 999px; overflow: hidden; }
.qty button {
  width: 30px; height: 30px; border: none; background: var(--paper); color: var(--heading);
  font-size: 1rem; font-weight: 700; cursor: pointer; line-height: 1;
}
.qty button:hover { background: var(--leaf-soft); }
.qty output { min-width: 30px; text-align: center; font-weight: 700; font-size: 0.88rem; }

/* ── story and signup ── */
.story { background: var(--paper); border-top: 1px solid var(--line); }
.story-in { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 28px; align-items: center; }
.story-img { border-radius: var(--radius); }
.story h2 { margin-top: 6px; }

.signup { background: var(--band); color: var(--band-ink); }
.signup h2 { color: var(--band-ink); }
.signup-in { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 420px); gap: 30px; align-items: center; }
.signup form { display: flex; flex-wrap: wrap; gap: 10px; }
.signup input[type="email"] {
  flex: 1 1 220px; padding: 11px 16px; border-radius: 999px; border: 2px solid var(--band-line);
  background: var(--band-field); color: var(--band-ink); outline: none; font-size: 0.95rem;
}
.signup input[type="email"]::placeholder { color: var(--band-faint); }
.signup input[type="email"]:focus { border-color: var(--accent); }
.signup .note { flex: 1 1 100%; font-size: 0.74rem; color: var(--band-faint); }
.signup .note a { color: var(--band-sub); }

/* ── the v1 forms: the honeypot, the challenge and the answer ──────────────────
   THE HONEYPOT is a real field with a real name, hidden from sight and from the
   tab order but never with display:none, because a field that is not rendered is
   a field a scripted filler skips and the trap catches nobody. It is off-screen,
   unreachable by keyboard, and labelled for the one reader who does meet it.

   THE CHALLENGE keeps its own height reserved, so the band does not jump when
   Cloudflare's widget arrives a moment after the page.

   THE ANSWER is the one line the page is allowed to write when a submission does
   not go through. There is no success line here: a success replaces the form. */
.hp {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}
/* Cloudflare's widget is 300px wide and does not shrink below it, so the two places
   it sits have to leave it 300px of content on the narrowest phone this site is built
   for. The signup band already does at 360px; the landing panel gets the room back in
   the query at the foot of this file. */
.turnstile-slot { flex: 1 1 100%; min-height: 68px; max-width: 100%; }
.form-status { flex: 1 1 100%; font-size: 0.8rem; font-weight: 700; color: var(--heading); }
.signup .form-status { color: var(--accent); }
.landing .form-status { color: var(--accent-text); }
.signup .said { color: var(--band-ink); }

/* ── the footer, rebuilt as the close of the page ──────────────────────────────
   The pattern is the one the yoleez.com footer uses, sized to a shop: a brand
   block that says who bakes this and how to reach them, then link columns grouped
   by what a reader is trying to do rather than by where a route happens to live,
   then a base line with the copyright and the privacy route. The contact band that
   used to sit above it is folded in here, which is where a reader looks for it.
   Every link goes somewhere real on this site. */
footer.site {
  background: var(--footer); color: var(--footer-ink);
  padding: 40px 0 22px; font-size: 0.85rem;
}
footer.site a { color: var(--footer-link); }
.ft-top {
  display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 2fr);
  gap: 34px 48px; align-items: start;
}
.ft-brand { max-width: 46ch; }
.ft-wordmark { font-size: 1.15rem; font-weight: 800; color: var(--footer-link); letter-spacing: -0.01em; }
.ft-tag { font-size: 0.8rem; color: var(--band-faint); margin-top: 2px; }
.ft-blurb { margin-top: 12px; line-height: 1.6; }
.ft-mail { display: inline-block; margin-top: 12px; font-weight: 700; }
.ft-note { margin-top: 10px; font-size: 0.78rem; line-height: 1.55; }

.ft-nav { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 26px; }
/* the QR landing pages carry the same footer with two columns instead of three */
.ft-nav.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.ft-col { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; min-width: 0; }
.ft-h {
  font-size: 0.7rem; font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--band-faint); margin-bottom: 2px;
}
.ft-col a, .ft-link {
  background: none; border: none; padding: 0; cursor: pointer; text-align: left;
  color: var(--footer-link); font-size: 0.85rem; text-decoration: none; line-height: 1.3;
}
.ft-col a:hover, .ft-link:hover { text-decoration: underline; }
/* the baked product list: the whole shelf as plain links, in the footer's own
   register so it reads as a directory rather than as a second navigation */
.ft-products { margin-top: 28px; }
.ft-plist {
  list-style: none; margin-top: 10px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 6px 24px;
}
.ft-plist a { font-size: 0.82rem; text-decoration: none; line-height: 1.35; }
.ft-plist a:hover { text-decoration: underline; }

.ft-base {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px 24px;
  flex-wrap: wrap; margin-top: 32px; padding-top: 18px; border-top: 1px solid var(--footer-line);
  font-size: 0.8rem;
}

/* ── scrim, drawer, modals ── */
.scrim { position: fixed; inset: 0; z-index: 90; background: var(--scrim); }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 100;
  width: min(400px, 100%); background: var(--page);
  display: flex; flex-direction: column; box-shadow: var(--shadow-drawer);
  transform: translateX(0);
}
.drawer-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--line); background: var(--paper);
}
.drawer-body { flex: 1; overflow-y: auto; padding: 12px 16px; }
.drawer-foot { border-top: 1px solid var(--line); background: var(--paper); padding: 14px 16px; }
.cart-sum { display: flex; justify-content: space-between; align-items: baseline; font-size: 0.9rem; color: var(--muted); margin-bottom: 10px; }
.cart-sum strong { color: var(--heading); font-size: 1.25rem; }
.cart-empty { color: var(--muted); padding: 30px 0; text-align: center; }

.cart-line { display: grid; grid-template-columns: 54px minmax(0, 1fr); gap: 10px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.cart-line:last-child { border-bottom: none; }
.cart-line img { width: 54px; height: 54px; object-fit: contain; background: var(--tile); border: 1px solid var(--line); border-radius: 8px; }
.cart-line .noart { height: 54px; border-radius: 8px; font-size: 0.55rem; }
.cart-line .cl-name { font-weight: 700; color: var(--heading); font-size: 0.88rem; line-height: 1.25; }
.cart-line .cl-meta { color: var(--muted); font-size: 0.76rem; margin-top: 2px; }
.cart-line .cl-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; }
.cart-line .cl-total { font-weight: 800; color: var(--heading); }
.cl-remove { background: none; border: none; color: var(--muted); font-size: 0.76rem; cursor: pointer; text-decoration: underline; padding: 0; }
.cl-remove:hover { color: var(--accent-text); }

.modal { position: fixed; inset: 0; z-index: 110; display: grid; place-items: center; padding: 4vh 4vw; background: var(--scrim-modal); }
.modal-panel {
  position: relative; background: var(--paper); border-radius: 16px;
  width: min(880px, 100%); max-height: 92vh; overflow-y: auto;
  padding: 26px; box-shadow: var(--shadow-lift);
}
.modal-panel.narrow { width: min(420px, 100%); }
.modal-close { position: absolute; top: 10px; right: 10px; }
.modal-panel h2 { margin-right: 40px; }
.modal-panel label { display: block; font-size: 0.8rem; font-weight: 700; color: var(--heading); margin-top: 10px; margin-bottom: 4px; }
.modal-panel input[type="email"], .modal-panel input[type="text"], .modal-panel select {
  width: 100%; padding: 10px 14px; border-radius: 10px; border: 1.5px solid var(--line);
  background: var(--paper); outline: none; font-size: 0.92rem;
}
.modal-panel input:focus, .modal-panel select:focus { border-color: var(--leaf); }
.modal-panel form .btn { margin-top: 14px; }
.fine { font-size: 0.74rem; color: var(--muted); margin-top: 10px; }
.co-lines { margin: 12px 0; border-top: 1px solid var(--line); }
.co-row { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 0.88rem; }
.co-row span { color: var(--muted); }
.co-line { background: var(--leaf-soft); color: var(--heading); border-radius: 10px; padding: 10px 14px; font-size: 0.86rem; font-weight: 600; }
.said { font-weight: 700; color: var(--heading); }
.said small { display: block; font-weight: 400; color: var(--muted); margin-top: 4px; }

/* ── quick view ── */
.qv-panel { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); gap: 24px; padding: 22px; }
.qv-media { background: var(--tile); border: 1px solid var(--line); border-radius: 12px; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.qv-zoom { border: none; background: none; padding: 10px; cursor: zoom-in; width: 100%; }
.qv-zoom img { width: 100%; max-height: min(62vh, 460px); object-fit: contain; }
.qv-noart { width: 100%; min-height: 260px; display: grid; place-items: center; background: repeating-linear-gradient(135deg, var(--tile-a) 0 12px, var(--tile-b) 12px 24px); color: var(--tile-ink); font-weight: 800; letter-spacing: 0.16em; text-transform: uppercase; font-size: 0.74rem; }
.qv-info { display: flex; flex-direction: column; gap: 8px; padding-right: 6px; }
.qv-info h2 { font-size: 1.3rem; }
.qv-info .size { color: var(--muted); font-size: 0.85rem; }
.qv-bullets { margin: 4px 0 2px 0; padding-left: 18px; color: var(--ink); font-size: 0.88rem; }
.qv-bullets li { margin-bottom: 4px; }
.qv-info .price-main { font-size: 1.7rem; }
.qv-buy { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.qv-row { display: flex; align-items: center; gap: 10px; }
.qv-row .btn { flex: 1; }
.ws-form { display: block; }

/* ── lightbox ── */
.lightbox { position: fixed; inset: 0; z-index: 120; display: grid; place-items: center; padding: 4vh 4vw; background: var(--scrim-photo); }
.lightbox img { max-width: 100%; max-height: 92vh; object-fit: contain; background: var(--tile); border-radius: 12px; }
.lb-close { position: absolute; top: 14px; right: 16px; color: var(--band-ink); }
.lb-close:hover { color: var(--band-ink); background: var(--lb-close-hover); }
.zoomable { cursor: zoom-in; }

/* ── QR landing pages ── */
.landing { min-height: 100vh; display: flex; flex-direction: column; background: var(--page); }
.landing .topbar-in { grid-template-columns: minmax(0, 1fr) auto; }
.landing main { flex: 1; display: grid; place-items: center; padding: 34px 20px; }
.landing .panel {
  background: var(--paper); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: var(--shadow); max-width: 440px; width: 100%; padding: 30px; text-align: center;
}
.landing .panel .bag { max-width: 190px; margin: 0 auto 16px; background: var(--tile); border-radius: 10px; }
.landing .panel h1 { color: var(--heading); font-size: 1.35rem; line-height: 1.25; }
.landing .panel p { margin-top: 9px; color: var(--muted); font-size: 0.94rem; }
.landing .panel .product { color: var(--heading); font-weight: 700; }
.landing form { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; text-align: left; }
.landing label { font-size: 0.8rem; font-weight: 700; color: var(--heading); }
.landing input[type="email"] { padding: 11px 16px; border-radius: 999px; border: 1.5px solid var(--line); background: var(--paper); font-size: 0.95rem; outline: none; }
.landing input[type="email"]:focus { border-color: var(--leaf); }
.landing .fine { margin-top: 14px; font-size: 0.72rem; }
.landing .alt { margin-top: 16px; font-size: 0.86rem; }
.landing .said { margin-top: 18px; }

/* ── the policy pages ──
   shipping.html and returns.html, generated by tools/make_site_meta.py. One measured
   column of reading, the site's own chrome above and below it, and a draft banner that
   is part of the page rather than a footnote on it: neither policy is in force, and a
   reader has to meet that fact before the terms and not after them. */
.policy-in { max-width: 68ch; }
.policy-in h1 { font-size: clamp(1.5rem, 3vw, 2rem); color: var(--heading); line-height: 1.15; margin-top: 6px; }
.policy-in h2 { margin-top: 30px; }
.policy-in .sub { max-width: none; }
.policy-in .fine { margin-top: 30px; }
.policy-draft {
  margin-top: 14px; padding: 10px 14px; border-radius: 10px;
  border: 1px solid var(--accent); background: var(--paper-2);
  color: var(--accent-text); font-weight: 700; font-size: 0.9rem;
}

/* ── responsive ── */
@media (max-width: 1180px) {
  .util { display: none; }
}
@media (max-width: 1100px) {
  .story-in { grid-template-columns: 200px minmax(0, 1fr); }
  .ft-top { grid-template-columns: minmax(0, 1fr); gap: 30px; }
  .ft-brand { max-width: none; }
}
@media (max-width: 900px) {
  .topbar-in { grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto auto; height: auto; padding-top: 8px; padding-bottom: 8px; row-gap: 8px; }
  .search { grid-column: 1 / -1; grid-row: 2; max-width: none; }
  .topbar-right { grid-column: 2; justify-self: end; }
  .landing .topbar-in { grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto; }
  .signup-in, .story-in { grid-template-columns: minmax(0, 1fr); }
  .story-img { max-width: 260px; }
  .qv-panel { grid-template-columns: minmax(0, 1fr); }
}
/* BELOW 900 THE CORRIDOR CLOSES. There is no width left for a track of its own, so
   the ingredient layer moves into the art cell and shares it with the drawing: the
   drawing takes the top of the cream, the anchor photo takes the bottom and leans
   on the swoosh the way the bowls do on the listing images. The accents are already
   switched off at this width, so nothing else has to move. */
@media (max-width: 899px) {
  .slide-in { grid-template-columns: minmax(0, 1fr) clamp(160px, 42%, 380px); }
  .slide-art { grid-column: 2; grid-row: 1; }
  .slide-ing { grid-column: 2; grid-row: 1; }
  .slide-stencil {
    left: auto; right: calc(var(--arrow-rail) + 6px); width: 58%;
    top: 4%; bottom: 46%;
  }
  .ing.a0 { left: 2%; right: 40%; bottom: -12%; height: 46%; }
}
@media (max-width: 620px) {
  .promo {
    --slide-pad-t: 16px; --slide-pad-b: 26px;
    --panel-edge: clamp(18px, 6vw, 44px);
  }
  .promo-stage { min-height: 172px; }
  .slide-in { grid-template-columns: minmax(0, 1fr) clamp(140px, 44%, 230px); gap: 12px; }
  .slide-art { min-height: 116px; }
  .promo-arrow.prev { left: 4px; }
  .promo-arrow.next { right: 4px; }
  .slide-stencil {
    left: calc(var(--panel-edge) + 6px); right: calc(var(--arrow-rail) - 4px);
    width: auto; top: 4%; bottom: 44%;
  }
  /* the swoosh zone is a fraction of its desktop width here, so the ribbon thins
     with it rather than swallowing the sweep it is meant to trace */
  .panel-ribbon { stroke-width: 8; }
  .slide h1, .slide h2 { font-size: 1.22rem; }
  .slide-sub { font-size: 0.86rem; }
  .brand-tag { display: none; }
  .drawer { width: 100%; }
  .modal { padding: 0; place-items: end stretch; }
  .modal-panel { max-height: 94vh; border-radius: 16px 16px 0 0; width: 100%; }
  .ft-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 18px; }
}
/* At 480 the slide keeps its headline and its one call to action and drops the sub
   copy, which is what holds the carousel to about 200px on a phone. */
@media (max-width: 480px) {
  .slide-sub { display: none; }
  .slide .btn { margin-top: 12px; }
}
/* 360px safety: the bar's parts have to fit one row above the search field. */
@media (max-width: 420px) {
  .brand-name { font-size: 0.98rem; }
  .mode { padding: 5px 10px; font-size: 0.72rem; }
  .topbar-right { gap: 8px; }
  .wrap, .topbar-in, .catstrip-in { padding-left: 14px; padding-right: 14px; }
  .promo { --slide-pad-x: 14px; --arrow-rail: 38px; }
  /* the landing panel gives back enough of its padding for the 300px challenge
     widget to sit inside it without pushing the page sideways */
  .landing main { padding: 24px 10px; }
  .landing .panel { padding: 22px 14px; }
}

/* ── THE PRODUCT PAGES ─────────────────────────────────────────────────────────
   Ruled 2026-08-17: most shoppers arrive on a phone, so the phone layout is the
   design and the desktop one is the enhancement. Everything below starts as one
   column and earns its second at a width where a second column is actually better,
   which is why the media queries here open UP rather than clamping down.

   Three rules hold the whole page together:
     one thumb reaches Add to Cart at every scroll position, through the buy panel
     first and the sticky bar after it;
     nothing moves while the photographs load, because the generator measures every
     image and writes its width and height into the tag;
     nothing on the page is narrower than 360px worth of content. */

/* the breadcrumb: never lost, and the category is a real link back to the filtered
   shelf rather than a decoration */
.crumbs { background: var(--paper); border-bottom: 1px solid var(--line-2); }
.crumbs-in {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 7px;
  padding-top: 9px; padding-bottom: 9px; font-size: 0.8rem; color: var(--faint);
}
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--heading); text-decoration: underline; }
.crumb-here { color: var(--heading); font-weight: 700; }

/* THE BUY FOLD. Phone: photographs, then the panel, because a shopper looks before
   they read. Desktop: the two side by side, gallery leading. */
.buyfold { padding-top: 18px; }
.buyfold-in { display: grid; gap: 20px; align-items: start; }

.pdp-gallery { display: flex; flex-direction: column; gap: 9px; min-width: 0; }
.gal-main {
  width: 100%; aspect-ratio: 1 / 1; padding: 8px;
  background: var(--tile); border: 1px solid var(--line); border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
button.gal-main { cursor: zoom-in; }
.gal-main img { width: 100%; height: 100%; object-fit: contain; }
.gal-main .noart { border-radius: 8px; font-size: 0.8rem; }
.gal-rail { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; }
.gal-rail::-webkit-scrollbar { display: none; }
.gal-thumb {
  flex: 0 0 auto; width: 56px; height: 56px; padding: 3px; cursor: pointer;
  background: var(--tile); border: 1.5px solid var(--line); border-radius: 8px;
}
.gal-thumb img { width: 100%; height: 100%; object-fit: contain; }
.gal-thumb:hover { border-color: var(--line-3); }
.gal-thumb.is-on { border-color: var(--accent); }

.buypanel { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.buypanel h1 {
  font-size: clamp(1.35rem, 4.6vw, 1.85rem); line-height: 1.16;
  color: var(--heading); font-weight: 800; letter-spacing: -0.01em;
}
.buypanel .size { color: var(--muted); font-size: 0.88rem; }
.pdp-bullets { margin: 2px 0; padding-left: 18px; color: var(--ink); font-size: 0.9rem; }
.pdp-bullets li { margin-bottom: 4px; }
.buypanel .price-block { margin-top: 4px; }
.buypanel .price-main { font-size: 1.75rem; }
/* the reviews line, ruled quiet: the aggregate as plain text and a low-visibility
   route to the listing's own reviews. It renders only when a product row carries
   the data, and no row does yet. */
.reviews-fact { font-size: 0.84rem; color: var(--muted); }
.reviews-link { color: var(--muted); font-weight: 600; }
.reviews-link:hover { color: var(--accent-text); }

/* one thumb: the stepper and the button are both at a 44px target */
.buy-row { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.buy-row .btn { flex: 1; min-height: 44px; }
.buy-row .qty button { width: 44px; height: 44px; }
.buy-row .qty output { min-width: 38px; }
.buypanel .secondary-line { font-size: 0.8rem; }
.buypanel .fine { margin-top: 4px; }

/* THE PRICING MODE, on a page that is built before anyone chooses one. Both panels
   are in the HTML and the body class decides which is on, so the retail panel is
   what a crawler reads and a wholesale reader still gets the case form without a
   second page being generated for it. */
.pdp .only-wholesale, .pdp-page .only-wholesale { display: none; }
body.wholesale .pdp .only-retail { display: none; }
body.wholesale .pdp div.only-wholesale { display: block; }
body.wholesale .pdp span.only-wholesale { display: inline; }
body.wholesale .xsell .card-actions { display: none; }
body.wholesale .xsell .card.soon { display: none; }

/* the two content bands: why this one, and what is in the bag */
.pdp-band { padding: 26px 0; border-top: 1px solid var(--line); }
.pdp-band.why { background: var(--paper); }
.band-in { display: grid; gap: 18px; align-items: center; }
.band-img { width: 100%; height: auto; border-radius: var(--radius); background: var(--tile); }
.band-in h3 { font-size: 0.92rem; color: var(--heading); margin-top: 12px; }
.bag-back { font-size: 0.86rem; }
.linkish {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--leaf); font-weight: 600; text-decoration: underline;
}
.linkish:hover { color: var(--accent-text); }
/* the per-serving numbers, when a product row carries them. No row does yet, so
   nothing on the site renders this today and the band shows the label claims. */
.macros { display: grid; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); gap: 8px; margin-top: 8px; }
.macros div { background: var(--leaf-soft); border-radius: 10px; padding: 8px 11px; }
.macros dt { font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.macros dd { font-weight: 800; color: var(--heading); }

.pdp-reviews { padding: 22px 0; border-top: 1px solid var(--line); }

/* the two cross-sell rows, on the grid's own card */
.xsell { padding: 24px 0; border-top: 1px solid var(--line); }
.xsell h2 { margin-bottom: 12px; }
.grid.xsell-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.xsell-more { margin-top: 14px; }
.xsell-more a { font-weight: 700; }
.card-art, .card-name { text-decoration: none; }
.card-name { display: block; }
.qv-full { font-size: 0.8rem; color: var(--muted); }
.qv-full:hover { color: var(--heading); }

/* THE STICKY BUY BAR. It arrives when the buy panel leaves the top of the screen and
   is the reason Add to Cart is always within reach on a phone. It never sits on top
   of a drawer, a modal or a photograph: the body carries a flag while any of those
   is open and this bar reads it. */
.stickybar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 55;
  background: var(--band); color: var(--band-ink);
  border-top: 1px solid var(--band-line); box-shadow: var(--shadow-lift);
  transform: translateY(110%); visibility: hidden;
  transition: transform 0.22s ease, visibility 0.22s ease;
}
.stickybar.is-on { transform: none; visibility: visible; }
body.overlay-open .stickybar { display: none; }
.sticky-in { display: flex; align-items: center; gap: 12px; padding-top: 9px; padding-bottom: 9px; }
.sticky-txt { flex: 1; min-width: 0; }
.sticky-name {
  display: block; font-weight: 700; font-size: 0.9rem; color: var(--band-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sticky-meta { display: block; font-size: 0.72rem; color: var(--band-sub); }
.sticky-price { font-weight: 800; font-size: 1.05rem; white-space: nowrap; }
.sticky-in .btn { min-height: 44px; white-space: nowrap; }
/* the bar owns the bottom of the screen, so the page ends above it */
.pdp-page footer.site { padding-bottom: 84px; }

@media (min-width: 640px) {
  .grid.xsell-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 820px) {
  .band-in { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); gap: 32px; }
  /* the wireframe puts the close-up on the right of the ingredients band and the
     sales pitch on the left of the one above it; on a phone both lead, in order */
  .band-in.flip .band-img { order: 2; }
  .band-in.noart-band { grid-template-columns: minmax(0, 1fr); }
  .grid.xsell-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  .buyfold-in { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); gap: 34px; }
  .pdp-gallery { position: sticky; top: calc(var(--stack) + 12px); }
  .gal-thumb { width: 64px; height: 64px; }
  /* a mouse has the whole panel in view, so the bar is for the phone and for the
     long scroll past the bands */
  .pdp-page footer.site { padding-bottom: 40px; }
}

/* ── reduced motion ──
   The guard covers the carousel too: the anchor photo's lift and its breathing
   shadow are one keyframe, and the crossfade is a transition. Both are switched off
   here, and site.js reads the same query and never starts the auto-advance for a
   reader who asked for stillness. With the pause button removed by ruling, this
   guard and the pointer, focus and hidden-tab holds are what stand in for it. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}
