/* ============================================================
   AgroLoaf Ghana — Nova Terra
   Palette: Skin Tone Bridal (warm peach ground, deep wine accent
   carries both the CTA and the numbers/eyebrows — one neutral
   ink color instead of a separate loud accent).
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* Kill the blue/grey tap flash mobile browsers paint over links, buttons,
   photos and text on touch (client request). */
* { -webkit-tap-highlight-color: transparent; }

/* Self-hosted Bebas Neue (preloaded in each page's <head>). font-display:
   block means the browser shows NOTHING until the font is ready rather than
   flashing the bolder/wider Impact fallback and then snapping to Bebas —
   which is exactly the "AgroLoaf grows, goes bold, then shrinks" the header
   was doing. With the font preloaded and same-origin (~14KB) the block
   period is a blink. */
@font-face {
  font-family: "Bebas Neue";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("../fonts/bebasneue-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bebas Neue";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("../fonts/bebasneue-latinext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Display face: Fraunces, loaded from Google at weight 600 only (see the
   <link> in <head>) — same trick the previous Cormorant self-host used:
   with no 400 face registered, every heading's "font-weight: 400" rule
   still resolves to the one loaded (SemiBold) face instead of going thin.
   opsz is requested as a range (9..144) so the variable font auto-selects
   a heavier/more "displayy" optical size at the large heading font-sizes
   and a lighter cut at small ones (font-optical-sizing: auto, on by default). */

/* Metric-matched fallback kept only as a safety if the block period times
   out (~3s): local Impact scaled to ~85% so its width nearly matches Bebas. */
@font-face {
  font-family: "Bebas Fallback";
  src: local("Impact"), local("Haettenschweiler"), local("Arial Narrow Bold");
  size-adjust: 85%;
  ascent-override: 100%;
  descent-override: 0%;
  line-gap-override: 0%;
}

/* Metric-matched fallback for DM Mono (used by the nav + body). Local
   monospace is ~6% narrower than DM Mono (nav row measured 623px vs 661px),
   so the nav visibly widened and shifted when DM Mono swapped in — the
   "header text jerks" between pages, replaying on every navigation because
   the dev server sends no font cache headers. size-adjust:106% pre-widens
   the fallback to DM Mono's width, so the swap no longer changes layout. */
@font-face {
  font-family: "DM Mono Fallback";
  src: local("Consolas"), local("Menlo"), local("DejaVu Sans Mono"), local("Courier New");
  size-adjust: 108.5%;
}

:root {
  --bg:        #fbe9db;
  --bg-2:      #f5dcc0;
  --fg:        #3a0e18;
  --muted:     #6b5245;
  --gold:       #741a2f;
  --gold-bg:    rgba(116, 26, 47, 0.12);
  --teal:       #741a2f;
  --teal-dim:   #5c1524;
  --terracotta: #8a3d1a;
  --line:      rgba(58, 14, 24, 0.14);
  --card:      rgba(255, 255, 255, 0.55);

  /* Client's pick from font-test.html (pair 11): Fraunces for display, Work
     Sans for body copy (was Cormorant Garamond + Karla). --font-mono keeps
     its name — it is referenced ~50 times as "the body face", it is just
     no longer mono (hasn't been since the Bebas/DM Mono era). */
  --font-display: "Fraunces", Georgia, serif;
  --font-mono:    "Work Sans", system-ui, -apple-system, "Segoe UI", sans-serif;

  --pad: 6vw;
}

html {
  color-scheme: light; scroll-behavior: auto;
  /* Font boosting off. Without this, pinch-zooming makes Chrome/Safari inflate
     the text of whatever block it thinks is the main content — so one pinch
     zooms again on its own and the layout jumps (client hit it on the open
     blog books). Pinch-zoom itself stays available. */
  -webkit-text-size-adjust: 100%; text-size-adjust: 100%;
  /* Always reserve the scrollbar's width, even on short pages that don't
     need to scroll. Otherwise navigating from a tall page (with a 15px
     scrollbar) to a short one (no scrollbar) shifts the whole centred
     layout sideways by 15px — the "screen jump" between tabs. */
  scrollbar-gutter: stable;
}

/* ============================================================
   Brand splash / preloader — cream ground, wine mark + fill bar.
   JS (inline in index.html) drops #preloader after ~2s or window.load,
   whichever is later; the fade is the .is-hidden transition here. */
#preloader {
  position: fixed; inset: 0; z-index: 10000;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1.6rem;
  background: var(--bg);
  transition: opacity 0.7s ease, visibility 0.7s ease;
}
#preloader.is-hidden { opacity: 0; visibility: hidden; pointer-events: none; }
#preloader .pre-mark {
  width: clamp(110px, 22vw, 150px); height: auto;
  animation: prePulse 1.6s ease-in-out infinite;
}
#preloader .pre-bar {
  width: 180px; height: 3px; border-radius: 3px;
  background: var(--gold-bg); overflow: hidden;
}
#preloader .pre-bar span {
  display: block; height: 100%; border-radius: 3px;
  background: var(--gold);
  transform-origin: left center; transform: scaleX(0);
  animation: preFill 2s ease-out forwards;
}
@keyframes prePulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.05); opacity: 0.82; } }
@keyframes preFill  { to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
  #preloader .pre-mark { animation: none; }
  #preloader .pre-bar span { animation: none; transform: scaleX(1); }
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-mono);
  font-weight: 400;
  line-height: 1.65;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }

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

/* Hide until GSAP has measured — prevents a flash of unstyled scroll */
.stage { visibility: hidden; }

h1, h2, h3, .fact__num, .brand__txt {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 1.02;
}

h2 { font-size: clamp(2.2rem, 5vw, 4rem); margin-bottom: 1.2rem; }
h3 { font-size: clamp(1.4rem, 2.4vw, 2rem); }

.eyebrow, .section-eyebrow {
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 1rem;
}

/* ============ HEADER ============ */
.site-head {
  position: fixed; top: 0; left: 0; width: 100%;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.8rem var(--pad);
  z-index: 100;
  background: rgba(251, 233, 219, 0.86);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
  transition: padding 0.4s ease;
}
.site-head.is-stuck {
  padding-block: 0.55rem;
}
.brand { display: flex; align-items: center; gap: 0.7rem; }
.brand__mark { width: 46px; height: 46px; object-fit: contain; }
.brand__txt { font-size: 1.35rem; letter-spacing: 0.06em; }
.brand__txt em { color: var(--gold); font-style: normal; margin-left: 0.35em; }

.site-nav { display: flex; align-items: center; gap: 1.4rem; }
.site-nav a {
  font-size: 0.85rem; font-weight: 500; letter-spacing: 0.08em;
  position: relative; padding-block: 0.3rem;
  color: rgba(58, 14, 24, 0.88);
  transition: color 0.3s ease;
}
.site-nav a:hover { color: var(--fg); }
.site-nav a:not(.nav-cta)::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px; background: var(--gold);
  transform: scaleX(0); transform-origin: right;
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
.site-nav a:not(.nav-cta):hover::after { transform: scaleX(1); transform-origin: left; }

.nav-cta {
  border: 1px solid var(--teal);
  background: var(--teal);
  color: var(--bg) !important;
  padding: 0.55rem 1.2rem !important;
  border-radius: 2px;
  transition: background 0.35s ease, color 0.35s ease, opacity 0.35s ease;
}
.nav-cta:hover { opacity: 0.85; }

/* ============ HERO PHOTO LAYER ============
   Painted BEFORE #scene in the DOM at the same z-index tier, so the paint
   order is: photo → cube → #smooth-content. The transparent hero shows
   the photo with the cube on top; the opaque Certs section (z-2) slides
   up OVER both — the cube tucks behind its edge exactly like the original
   design. The photo itself never fades: the inner layer is translated up
   in lockstep with the hero scrolling out (see main.js), so its bottom
   edge tracks the hero/Certs boundary and it exits the way any in-flow
   section background would — gone before the transparent About arrives.
   The wrapper starts below the header line, clipping the moving photo so
   it never slides up behind the translucent blurred header. */
#hero-photo {
  position: fixed; top: 72px; left: 0; right: 0; bottom: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
#hero-photo-img {
  position: absolute; inset: 0;
  background:
    linear-gradient(100deg, rgba(12,9,6,0.92) 0%, rgba(12,9,6,0.82) 40%, rgba(12,9,6,0.55) 65%, rgba(12,9,6,0.25) 100%),
    url('../img/hero-bg.webp?v=79') center 38% / cover no-repeat;
  will-change: transform;
}

/* ============ CUBE SCENE ============ */
#scene {
  position: fixed; inset: 0; z-index: 0;
  display: flex; align-items: center; justify-content: flex-end;
  padding-right: 9vw;
  /* Origin tracks where the cube actually sits. Left at centre, the cube is
     viewed off-axis and its projection balloons past the right edge. */
  perspective: 1500px;
  perspective-origin: 74% 50%;
  pointer-events: none;
}
#cube {
  --s: min(28vw, 48vh, 300px);
  width: var(--s); height: var(--s);
  position: relative;
  top: -61px; left: 0px;
  transform-style: preserve-3d;
  transform: rotateX(90deg) rotateY(0deg);
  will-change: transform;
}
.face {
  position: absolute; inset: 0;
  overflow: hidden;
  backface-visibility: hidden;
  background: #1f0d10;
  border: 1px solid rgba(116, 26, 47, 0.35);
  box-shadow: inset 0 0 80px rgba(0, 0, 0, 0.5);
}
.face img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.95) contrast(1.03) brightness(0.92);
}
.face::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(160deg, rgba(116,26,47,0.14), rgba(31,13,16,0.45));
}
/* About cube only — wheat-ear frame stamped on top of the face photo,
   same wine tone as the rest of the badges. Not on the small hero cube.
   Source photos (milling floor, lab, etc.) run cool/grey and read as
   flat and out of place against the site's warm peach/wine palette, so
   a stronger wine wash unifies them into one warm object; the photo
   layer itself gets a matching sepia+saturate push (below) rather than
   staying neutral. */
/* Wash kept deliberately light: the faces are now portraits of the team in
   branded uniform, and the client wants faces and shirt logos legible.
   Wheat-ear frame removed on the client's request (2026-07-26) — the sprigs
   sat over the portraits' edges; only the light wine wash is left. */
#about-cube .face::after {
  background: linear-gradient(160deg, rgba(140,50,30,0.08), rgba(58,14,24,0.18));
}
#about-cube .face img {
  filter: sepia(0.08) saturate(1.06) hue-rotate(-3deg) contrast(1.02);
}
/* Stop 05 (left face) is a merch flatlay, not a photograph — its grey half
   runs cooler and brighter than the portraits either side of it, so this one
   face carries a slightly deeper warm wash to sit in the same light. */
#about-cube .face[data-face="left"] img {
  filter: sepia(0.24) saturate(1.04) hue-rotate(-6deg) contrast(1.02) brightness(0.95);
}
#about-cube .face[data-face="left"]::after {
  background: linear-gradient(160deg, rgba(150,60,32,0.14), rgba(58,14,24,0.26));
}
#about-cube .face { box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.18); }
.face[data-face="front"]  { transform: translateZ(calc(var(--s) / 2)); }
.face[data-face="back"]   { transform: rotateY(180deg) translateZ(calc(var(--s) / 2)); }
.face[data-face="right"]  { transform: rotateY(90deg)  translateZ(calc(var(--s) / 2)); }
.face[data-face="left"]   { transform: rotateY(-90deg) translateZ(calc(var(--s) / 2)); }
.face[data-face="top"]    { transform: rotateX(-90deg) translateZ(calc(var(--s) / 2)); }
.face[data-face="bottom"] { transform: rotateX(90deg)  translateZ(calc(var(--s) / 2)); }

/* ============ HERO ============ */
.hero {
  min-height: 100vh;
  display: flex; flex-direction: column; justify-content: center;
  /* Top padding clears the fixed header (72px) — without it, vertically
     centered copy could render its first line (the eyebrow) underneath
     the translucent header on shorter viewports. */
  padding: 72px var(--pad) 0;
  position: relative;
}
.hero__copy { max-width: 620px; position: relative; z-index: 2; }
/* Photo backdrop reads dark under the copy column, so the hero overrides the
   page's default dark-ink palette with a light one scoped to this section —
   the rest of the site (light ground) keeps the original dark-on-cream. */
.hero .eyebrow { color: var(--bg); opacity: 0.85; }
.hero__title {
  /* Sized to break into 2 lines, not 4 — 4 lines overflow the 100vh hero
     and push the copy out of centre. */
  font-size: clamp(3rem, 6vw, 5.6rem);
  margin-bottom: 1.4rem;
  overflow: hidden;
  color: var(--bg);
}
.hero-line { overflow: hidden; }
.hero__txt { max-width: 44ch; color: rgba(251,233,219,0.82); margin-bottom: 2.2rem; }
.hero__actions { display: flex; gap: 1rem; flex-wrap: wrap; }

.btn-gold, .btn-ghost {
  display: inline-block;
  padding: 0.9rem 1.8rem;
  font-size: 0.85rem; letter-spacing: 0.12em; text-transform: uppercase;
  border-radius: 2px;
  transition: transform 0.35s cubic-bezier(0.22,1,0.36,1), background 0.35s ease, color 0.35s ease;
}
.btn-gold { background: var(--teal); color: var(--bg); font-weight: 500; }
.btn-gold:hover { transform: translateY(-3px); background: var(--teal-dim); }
.btn-ghost { border: 1px solid var(--line); color: var(--fg); }
.btn-ghost:hover { border-color: var(--teal); color: var(--teal); transform: translateY(-3px); }
.hero .btn-ghost { border-color: rgba(251,233,219,0.55); color: var(--bg); }
.hero .btn-ghost:hover { border-color: var(--bg); color: var(--bg); background: rgba(251,233,219,0.12); }

.hero__specs {
  list-style: none;
  display: flex; gap: 3.5rem;
  margin-top: 3rem;
  position: relative; z-index: 2;
}
.hero__specs li { display: flex; flex-direction: column; }
.hero__specs b {
  font-family: var(--font-display);
  font-size: 2.4rem; color: #e2a479; line-height: 1;
}
.hero__specs span {
  font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: rgba(251,233,219,0.68); margin-top: 0.4rem;
}

/* ============ CERTS ============ */
.certs {
  padding: 8vh var(--pad);
  border-top: 1px solid var(--line);
  text-align: center;
  position: relative; z-index: 2;
  background: var(--bg);
}
/* Five seals, standing still (client, 2026-07-26). The old marquee (six
   duplicated sets sliding left behind an edge mask) is gone: at this count
   there is nothing to scroll, and a moving row made them harder to read, not
   easier. No overflow/mask here any more — the row simply centres. */
.certs__viewport { margin-top: 3rem; }
.certs__row {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start;
  gap: clamp(2.5rem, 6vw, 5.5rem);
}
/* Bigger and heavier than the marquee version — the client wanted them noticeable.
   The source art is 600x600 with alpha, so it stays crisp even at 150px on a
   2x screen. */
.cert { display: flex; flex-direction: column; align-items: center; gap: 1rem; flex: 0 0 auto; }
.cert img {
  width: clamp(112px, 10.5vw, 150px); height: auto; object-fit: contain;
  transition: transform 0.4s ease;
}
.cert:hover img { transform: translateY(-5px); }
.cert figcaption {
  font-size: 1.02rem; letter-spacing: 0.1em; text-transform: uppercase;
  line-height: 1.25;
}
.cert small { color: var(--muted); font-size: 0.76rem; letter-spacing: 0.06em; text-transform: none; }

/* ============ ABOUT ============ */
/* ============ ABOUT CUBE SCENE (fixed backdrop) ============ */
#about-scene {
  position: fixed; inset: 0; z-index: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
/* Looping backdrop video behind the cube, whole About section. Vignette
   overlay fades it to --bg at the edges so it reads as a glow behind the
   cube, not a full-bleed video fighting the translucent stop cards. Plays
   continuously, not tied to scroll AND not tied to the cube's own
   grow-in — it has its own simple opacity toggle (see initAboutCube in
   main.js) so the video is already playing by the time the cube appears. */
.about-bg { position: absolute; inset: 0; overflow: hidden; opacity: 0; }
.about-bg video { width: 100%; height: 100%; object-fit: cover; }
.about-bg::after {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(ellipse 85% 60% at center, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 50%, var(--bg) 100%);
}
/* Wrapper carries the perspective + the grow-in scale/opacity tween;
   #about-cube itself keeps getting its rotation written directly (see
   setAboutCubeTransform), so the two transforms never fight over the
   same element's inline style. */
#about-cube-wrap {
  perspective: 1800px;
  opacity: 0;
}
#about-cube {
  --s: min(48vw, 62vh, 640px);
  width: var(--s); height: var(--s);
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(90deg) rotateY(0deg);
  will-change: transform;
}

/* ============ ABOUT (cube gallery) ============ */
.about {
  position: relative; z-index: 2;
  background: transparent;
}
.about-track { position: relative; }
/* Illustrated-wheat-corner photo behind every stop (was a plain
   translucent var(--card) panel). A radial scrim (transparent at the
   corners, opaque toward the centre) keeps the wheat art crisp at the
   edges while still giving the text a clean, legible patch behind it. */
.about-stop {
  min-height: 100vh;
  display: flex; flex-direction: column; justify-content: center;
  max-width: 480px;
  padding: 6vh var(--pad);
  background:
    radial-gradient(ellipse at center, rgba(251,233,219,0.82) 0%, rgba(251,233,219,0.3) 55%, transparent 78%),
    url('../img/about-stop-04-bg.webp?v=115') center / cover no-repeat;
  border-left: 1px solid var(--line);
  backdrop-filter: blur(10px);
}
.about-stop--right {
  margin-left: auto;
  border-left: none;
  border-right: 1px solid var(--line);
  text-align: right;
}
.about-stop--right .link-arrow { justify-content: flex-end; margin-left: auto; }
.about-stop p { color: rgba(58,14,24,0.82); margin-bottom: 1rem; }
.about-stop b { color: var(--fg); font-weight: 500; }
.about-stop h2, .about-stop h3 { margin-bottom: 1rem; }
.about-stop__num {
  font-family: var(--font-display); font-size: 1.2rem; letter-spacing: 0.1em;
  color: var(--gold); opacity: 0.75; margin-bottom: 0.6rem;
}
.about-stop__pull {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.9rem, 3vw, 2.7rem);
  line-height: 1.08;
  color: var(--fg) !important;
  margin-bottom: 1.6rem !important;
}
.about-stop--last {
  align-items: flex-end;
  gap: 0.4rem;
  /* Narrower side padding than the other stops: the closing quote is set in
     display type and breaks by hand (see the <br>s in index.html), and its
     longest line — "of everything we do." — needs ~410px at the top clamp.
     With the shared var(--pad) the text box was only 249-325px, so that line
     always split in two (client: "'of everything we do.' must be one line"). */
  padding-inline: 32px;
}
.link-arrow {
  display: inline-flex; align-items: center; gap: 0.8rem;
  font-size: 0.82rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--teal);
}
.link-arrow__line {
  width: 38px; height: 1px; background: var(--teal);
  transition: width 0.4s cubic-bezier(0.22,1,0.36,1);
}
.link-arrow:hover .link-arrow__line { width: 60px; }

/* ============ FACTS ============ */
.facts {
  position: relative; z-index: 2;
  background: var(--bg-2);
  border-block: 1px solid var(--line);
  padding: 8vh var(--pad);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2rem;
}
.fact { text-align: center; }
.fact__num { display: block; font-size: clamp(2.6rem, 5vw, 4.2rem); color: var(--gold); }
.fact__label {
  display: block; margin-top: 0.6rem;
  font-size: 0.8rem; letter-spacing: 0.12em; text-transform: uppercase;
}
.fact__label small {
  display: block; margin-top: 0.3rem;
  color: var(--muted); font-size: 0.72rem; letter-spacing: 0.04em; text-transform: none;
}

/* ============ PROCESS ============ */
/* Facility photo backdrop, same dark-photo/light-text treatment as the
   hero — the section's own dark ink palette doesn't read on a photo, so
   colors are overridden scoped to .process only. */
.process {
  position: relative; z-index: 2;
  background:
    linear-gradient(180deg, rgba(15,8,7,0.72) 0%, rgba(15,8,7,0.58) 45%, rgba(15,8,7,0.78) 100%),
    url('../img/bakery-hands.webp?v=89') center / cover no-repeat;
  padding: 12vh var(--pad);
}
.process__head { margin-bottom: 6vh; max-width: 22ch; }
.process .section-eyebrow { color: var(--bg); opacity: 0.85; }
.process__head h2 { color: var(--bg); }

.arch {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: 4vw;
  padding-block: 6vh;
  border-top: 1px solid rgba(251,233,219,0.16);
}
.arch:nth-child(even) .arch__info { order: 2; }
/* Bread-slice outline behind the text — a stretched background-image
   (like the About cube's wheat frame), so the whole block reads as a
   crumb-tinted slice of bread. Crust wiggle is compressed into the top
   ~17.5% of the shape (img/bread-outline.svg) and padding-top clears it
   with real margin to spare, so both the heading and paragraph land
   fully inside the body, not straddling the crust line. Max-width keeps
   the proportions bread-shaped instead of stretching wide and flat. */
.arch__info {
  position: relative;
  max-width: 380px;
  padding: 6.5rem 1.8rem 2.4rem;
}
.arch__info::before {
  content: "";
  position: absolute; inset: 0;
  background: url('../img/bread-outline.svg') center / 100% 100% no-repeat;
  pointer-events: none;
  z-index: -1;
}
.arch__info h3 { margin-bottom: 0.9rem; color: var(--bg); }
.arch__info p { color: rgba(251,233,219,0.78); max-width: 44ch; }

/* Certification card sitting at the foot of an arch — a real cream panel,
   inset from the frame with a soft shadow so it reads as a designed badge
   rather than type dropped onto the photo. Rendered as markup (not baked
   into the image) so it stays sharp and ignores the img's parallax. */
.cert-bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; gap: 0.8rem;
  padding: 0.72rem 1.2rem;
  background: var(--bg);
  border-top: 1px solid rgba(116, 26, 47, 0.2);
  border-radius: 0;
  box-shadow: 0 12px 28px rgba(20, 8, 10, 0.4);
  pointer-events: none;
  z-index: 1;
}
.cert-bar__shield { width: 30px; height: 35px; flex-shrink: 0; }
.cert-bar__shield path { fill: var(--teal); }
.cert-bar__tick {
  fill: none; stroke: var(--bg); stroke-width: 2.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.cert-bar__txt { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.1; }
.cert-bar__txt b {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.02rem, 1.35vw, 1.24rem);
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--fg);
}
/* Registration number as a wine chip — the one detail that makes it read as
   an official mark instead of a caption. */
.cert-bar__txt span {
  margin-top: 0.3rem;
  background: var(--teal); color: var(--bg);
  font-family: var(--font-mono); font-size: 0.62rem; font-weight: 500;
  letter-spacing: 0.07em;
  padding: 0.17rem 0.45rem; border-radius: 4px;
}
.arch__img {
  overflow: hidden;
  border-radius: 200px 200px 4px 4px; /* the arch */
  height: 62vh;
  position: relative;
}
.arch__img img {
  width: 100%; height: 118%; object-fit: cover;
  position: absolute; top: -9%; left: 0;
  /* Light touch only — a heavy sepia/darkening pass was muddying the photos
     and hiding detail the client wants legible (bag text, FDA badge). */
  filter: sepia(0.1) saturate(1.08) hue-rotate(-4deg) contrast(1.02);
}
/* Warm wine/terracotta color-grade wash so the four source photos (cool
   mill/lab shots) read as one family with the rest of the page, same
   technique as the about-cube tinting. */
.arch__img::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(140,50,30,0.05) 0%, rgba(58,14,24,0.14) 100%);
  pointer-events: none;
}

/* ============ PRODUCTS (3D book) ============ */
/* Bakery-floor photo backdrop, same dark-photo/light-text treatment as
   Process/Hero — the tilt-cards already carry their own dark gradient +
   light text, so only .products__head needs its colors overridden here. */
.products {
  position: relative; z-index: 2;
  background:
    linear-gradient(180deg, rgba(15,8,7,0.74) 0%, rgba(15,8,7,0.6) 45%, rgba(15,8,7,0.8) 100%),
    url('../img/products-bg.webp?v=90') center / cover no-repeat;
  border-block: 1px solid var(--line);
  padding: 12vh var(--pad);
  text-align: center;
  overflow: hidden;
}
.products__head { max-width: 52ch; margin: 0 auto 7vh; }
.products__icon { width: 62px; height: auto; margin: 0 auto 1.2rem; }
.products__head .section-eyebrow { color: var(--bg); opacity: 0.85; }
.products__head h2 { color: var(--bg); }
.products__sub { color: rgba(251,233,219,0.78); }

.book {
  display: flex; justify-content: center; gap: 2.6rem;
  flex-wrap: wrap;
}

/* 3D hover-tilt cards (ref: хлеб\3д карточки круто!\prototype.html).
   Each card is its own 3D viewport (perspective on the card). The mouse
   drives --rotateX/--rotateY (see js/main.js initTiltCards, rAF-lerped),
   and four layers sit at different translateZ so they parallax as one
   object: blurred shadow behind, the photo, a raised "cutout" copy that
   pops over the frame, the frame border, and the caption on top. Bread
   photos are opaque (no alpha), so the cutout mask shows the full frame —
   the pop still reads from the raised Z, not an actual knockout. */
.tilt-card {
  --rotateX: 0deg;
  --rotateY: 0deg;
  position: relative;
  width: 285px;
  aspect-ratio: 5 / 7;
  color: #fff;
  perspective: 50rem;
  cursor: pointer;
}
.tilt-card__shadow {
  position: absolute; inset: 0;
  background: var(--url) center / cover no-repeat;
  opacity: 0.8;
  filter: blur(2rem) saturate(0.9);
  box-shadow: 0 -1.5rem 2rem -0.5rem rgba(0, 0, 0, 0.7);
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 2rem, -2rem);
}
.tilt-card__img {
  position: absolute; inset: 0;
  background:
    linear-gradient(to top, rgba(12,9,6,0.55), transparent 42%),
    var(--url) center / cover no-repeat;
  -webkit-mask-image: var(--url); mask-image: var(--url);
  -webkit-mask-size: cover; mask-size: cover;
  -webkit-mask-position: center; mask-position: center;
}
.tilt-card__img--bg {
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 0);
}
.tilt-card__img--cutout {
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 4rem) scale(0.92);
  z-index: 3;
}
.tilt-card__content {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 1.8rem;
  text-align: left;
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 6rem);
  z-index: 4;
}
.tilt-card__content h3 {
  font-size: 1.3rem; margin-bottom: 0.2rem;
  color: #f7ecdd;
  text-shadow: 0 0 1.4rem rgba(0, 0, 0, 0.55);
}
.tilt-card__content p {
  font-size: 0.74rem; color: rgba(247,236,221,0.85);
  text-shadow: 0 0 1.4rem rgba(0, 0, 0, 0.55);
}
/* Two frames: ::after behind the cutout, ::before in front — so a side of
   the border can be "behind" the photo pop (border-*-behind kills that
   edge on the front frame, leaving only the back one). */
.tilt-card::after,
.tilt-card::before {
  content: "";
  position: absolute; inset: 1.3rem;
  border: 0.45rem solid var(--gold);
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 2rem);
  pointer-events: none;
}
.tilt-card::before { z-index: 4; }
.tilt-card.border-left-behind::before   { border-left-color: transparent; }
.tilt-card.border-right-behind::before  { border-right-color: transparent; }
.tilt-card.border-bottom-behind::before { border-bottom-color: transparent; }

/* ============ PRODUCT / SPEC ============ */
.product {
  position: relative; z-index: 2;
  background: var(--bg);
  padding: 12vh var(--pad);
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: 5vw;
  align-items: center;
}
.product__media { position: relative; }
.product__media img {
  width: 100%; object-fit: contain;
  filter: drop-shadow(0 30px 60px rgba(0,0,0,0.7));
}
.product__stock {
  position: absolute; top: 1rem; left: 1rem;
  background: var(--terracotta); color: var(--bg);
  font-size: 0.7rem; letter-spacing: 0.1em; text-transform: uppercase;
  padding: 0.4rem 0.9rem; border-radius: 2px; font-weight: 500;
}
.product__lead { color: rgba(58,14,24,0.82); margin-bottom: 2.5rem; max-width: 48ch; }

/* Standalone page variant — no hero above it, so it needs its own top
   clearance, and its heading is an h1 (only h2 gets a clamp() size
   elsewhere in the sheet). */
.product--page { padding-top: 16vh; align-items: start; background: none; }
/* Blurred-bakery + gold-wheat-corners backdrop (img/product-bg.webp).
   Lighter gradient than a first pass (which milked the photo down to a
   near-invisible wash) — Jeka's call: keep the backdrop legible, drop the
   card backings under the feature items instead of hiding the photo. */
.product--page::before {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  background:
    linear-gradient(180deg, rgba(251,233,219,0.5) 0%, rgba(251,233,219,0.36) 45%, rgba(251,233,219,0.6) 100%),
    url('../img/product-bg.webp?v=178') center / cover no-repeat;
}
.product--page > * { position: relative; z-index: 1; }

/* 3D hover-tilt on the product photo — faithful port of
   хлеб\3д карточки круто!\prototype.html (shadow / flat image / raised
   masked "cutout" / border frame), mouse-driven --rotateX/--rotateY via
   js/product.js initProductTilt, 60fps lerp. Sized to fill the media
   column at the bag photo's own aspect ratio (890/676). Classes are
   scoped under .product__media so the generic layer names can't collide
   elsewhere. */
.product__media .ptilt {
  --rotateX: 0deg; --rotateY: 0deg;
  /* url() in a custom property resolves relative to THIS stylesheet
     (css/), hence ../img — a path set inline on the HTML element would
     resolve against css/ too and 404, so it lives here where there's one
     fixed product photo anyway. */
  --url: url('../img/finsihed.webp');
  position: relative;
  width: 100%;
  aspect-ratio: 890 / 676;
  color: #fff;
  perspective: 50rem;
}
.product__media .ptilt__shadow {
  position: absolute; inset: 0;
  background: var(--url) center / cover no-repeat;
  opacity: 0.8;
  filter: blur(2rem) saturate(0.9);
  box-shadow: 0 -1.5rem 2rem -0.5rem rgba(0, 0, 0, 0.7);
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 2rem, -2rem);
}
.product__media .ptilt__img {
  position: absolute; inset: 0;
  /* Flat warm wash on top of the studio photo — the bag was shot on
     neutral grey, which read as a cold, disconnected patch against the
     page's peach/wine palette; this ties it into the same tone without
     hiding the packaging print underneath. */
  background:
    linear-gradient(rgba(138,61,26,0.26), rgba(138,61,26,0.26)),
    linear-gradient(to top, rgba(0,0,0,0.5), transparent 40%),
    var(--url);
  background-size: cover; background-position: center;
  -webkit-mask-image: var(--url); mask-image: var(--url);
  -webkit-mask-size: cover; mask-size: cover;
  -webkit-mask-position: center; mask-position: center;
}
.product__media .ptilt__img--bg {
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 0);
}
.product__media .ptilt__img--cutout {
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 4rem) scale(0.92);
  z-index: 3;
}
.product__media .ptilt::after,
.product__media .ptilt::before {
  content: "";
  position: absolute; inset: 1.5rem;
  border: var(--gold) 0.5rem solid;
  transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 2rem);
}
.product__media .ptilt::before { z-index: 4; }
.product__media .ptilt.border-left-behind::before { border-left-color: transparent; }
.product__media .ptilt.border-right-behind::before { border-right-color: transparent; }
.product__media .ptilt.border-bottom-behind::before { border-bottom-color: transparent; }
.product--page .product__copy h1 { font-size: clamp(2.2rem, 5vw, 4rem); margin-bottom: 1.2rem; }
.product__cta { display: inline-block; margin-top: 2.2rem; }

.spec details {
  border-top: 1px solid var(--line);
  padding-block: 0.2rem;
}
.spec details:last-child { border-bottom: 1px solid var(--line); }
.spec summary {
  list-style: none;
  cursor: pointer;
  padding: 1.1rem 2.5rem 1.1rem 0;
  position: relative;
  font-size: 0.95rem; letter-spacing: 0.06em;
  transition: color 0.3s ease;
}
.spec summary::-webkit-details-marker { display: none; }
.spec summary:hover { color: var(--gold); }
.spec summary::after {
  content: ""; position: absolute; right: 0.4rem; top: 50%;
  width: 11px; height: 11px;
  border-right: 1px solid var(--gold); border-bottom: 1px solid var(--gold);
  transform: translateY(-60%) rotate(45deg);
  transition: transform 0.4s cubic-bezier(0.22,1,0.36,1);
}
.spec details[open] summary::after { transform: translateY(-30%) rotate(225deg); }
.spec__body { padding: 0 0 1.4rem; color: rgba(58,14,24,0.8); }
.spec__body b { color: var(--gold); font-weight: 500; }

.nutri { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.nutri th, .nutri td { text-align: left; padding: 0.5rem 0.8rem 0.5rem 0; }
.nutri th { color: var(--muted); font-weight: 500; }
.nutri td { color: var(--fg); }
.nutri-note { font-size: 0.85rem; color: rgba(58,14,24,0.8); margin-top: 0.9rem; }
.nutri-note b { color: var(--gold); font-weight: 500; }
.uses { list-style: none; display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
.uses li { position: relative; padding-left: 1.2rem; }
.uses li::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 5px; height: 5px; background: var(--gold); border-radius: 50%;
}
.uses--detailed {
  display: block;
}
.uses--detailed li {
  padding-left: 1.3rem;
  margin-bottom: 1.1rem;
  color: rgba(58,14,24,0.82);
}
.uses--detailed li:last-child { margin-bottom: 0; }
.uses--detailed li b { color: var(--fg); font-weight: 500; }

/* Product page — rating line, in-stock line, the 4 feature-icon cards,
   and the Description/Content/Uses tabs (replaced the earlier <details>
   accordion so the page matches the live agroloafghana.com structure). */
.product__rating {
  display: flex; align-items: center; gap: 0.6rem;
  margin-bottom: 0.7rem;
}
.product__stars { color: var(--gold); font-size: 1rem; letter-spacing: 0.1em; }
.product__reviews { color: var(--muted); font-size: 0.85rem; }
.product__instock {
  display: flex; align-items: center; gap: 0.5rem;
  color: var(--gold); font-weight: 500; font-size: 0.9rem;
  margin-bottom: 2.2rem;
}
.product__instock svg { width: 14px; height: 14px; flex: none; }

.product-features {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 0 1.8rem;
  margin-bottom: 2.6rem;
}
/* No card backing — a premium look wants fewer boxes, more air. Items sit
   directly on the section backdrop, separated by a hairline instead (the
   last row's line is dropped so the grid doesn't dead-end on a rule). */
.product-features__item {
  display: flex; align-items: flex-start; gap: 1rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid rgba(116,26,47,0.16);
}
.product-features__item:nth-last-child(-n+2) { border-bottom: none; }
/* Dark slate + carved-wood medallion (img/slate-*2.webp — folder 6, round
   2: replaced the first glossy 3D-render badges, which read as game icons
   against this palette). Thin wine ring keeps the circle's edge crisp
   against a backdrop that's often similarly dark-toned. background-image
   lives in per-name modifiers below — a url() in an inline style
   attribute resolves against css/ and 404s, so paths stay in this
   stylesheet with ../img. */
.product-features__icon {
  width: 96px; height: 96px; flex: none;
  border-radius: 50%;
  background-position: center; background-size: cover; background-repeat: no-repeat;
  box-shadow: inset 0 0 0 2px rgba(116,26,47,0.3), 0 6px 16px rgba(58,14,24,0.2);
}
.product-features__icon--wheat     { background-image: url('../img/slate-wheat2.webp?v=1'); }
.product-features__icon--protein   { background-image: url('../img/slate-protein2.webp?v=1'); }
.product-features__icon--bromate   { background-image: url('../img/slate-bromate2.webp?v=1'); }
.product-features__icon--fortified { background-image: url('../img/slate-fortified2.webp?v=1'); }
.product-features__item h4 {
  font-family: var(--font-display); font-weight: 400;
  font-size: 1rem; color: var(--fg); margin-bottom: 0.2rem;
}
/* --muted (#6b5245) was too light against the photo backdrop behind this
   section — washed out next to the bold h4 above it. Same dark-wine-at-
   opacity treatment already used for other secondary text over this
   backdrop (.product__lead, tab panel body copy). */
.product-features__item p { color: rgba(58,14,24,0.92); font-size: 0.82rem; font-weight: 500; }

.product-tabs__nav {
  display: flex; gap: 2rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1.8rem;
}
.product-tabs__nav button {
  font-family: var(--font-mono); font-size: 0.85rem; letter-spacing: 0.04em;
  font-weight: 500;
  color: rgba(58,14,24,0.65); background: none; border: none; cursor: pointer;
  padding: 0 0 0.9rem; position: relative;
}
.product-tabs__nav button.is-active { color: var(--gold); }
.product-tabs__nav button.is-active::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
  background: var(--gold);
}
/* All 3 panels stack in the SAME grid cell (grid-area) instead of the
   inactive ones being display:none — so the container's height is always
   the tallest panel's height, constant across all 3 tabs. Without this,
   switching to a taller/shorter panel changed .product--page's own
   height, and since its ::before backdrop photo is background-size:cover
   sized to that box, the photo visibly re-cropped/jumped on every click —
   looked like the whole background was reloading. */
.product-tabs__panels { display: grid; }
.product-tabs__panel {
  grid-area: 1 / 1;
  visibility: hidden; pointer-events: none;
  color: rgba(58,14,24,0.85);
}
.product-tabs__panel.is-active { visibility: visible; pointer-events: auto; }
.product-tabs__panel h3 {
  font-family: var(--font-display); font-weight: 400; font-size: 1.4rem;
  color: var(--fg); margin-bottom: 1rem;
}
.product-tabs__panel p { margin-bottom: 1rem; max-width: 60ch; }
.product-tabs__panel b { color: var(--gold); font-weight: 500; }

.product-spec-list { margin-bottom: 2rem; }
.product-spec-list__row {
  padding: 0.9rem 0;
  border-top: 1px solid var(--line);
}
.product-spec-list__row:last-child { border-bottom: 1px solid var(--line); }
.product-spec-list__row dt {
  font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 0.3rem;
}
.product-spec-list__row dd { color: rgba(58,14,24,0.85); }
.product-spec-list__row dd b { color: var(--gold); font-weight: 500; }

/* ============ BG STRIP ============
   Delivery + Blog + the footer's "Let's talk" CTA share ONE continuous
   background photo instead of three independently-cropped copies: the
   image is painted once on this wrapper (height = the three sections'
   combined height in normal flow) via ::before, so each section shows a
   different vertical slice of the same photo, like a single backdrop
   running behind all three. */
.bg-strip { position: relative; }
.bg-strip::before {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  background:
    linear-gradient(180deg, rgba(40,18,12,0.3) 0%, rgba(40,18,12,0.42) 50%, rgba(40,18,12,0.55) 100%),
    url('../img/strip-bg.webp?v=115') center / cover no-repeat;
}
.bg-strip > * { position: relative; z-index: 1; }

/* ============ DELIVERY ============ */
.delivery {
  border-block: 1px solid var(--line);
  padding: 12vh var(--pad);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 5vw;
  align-items: center;
}
.delivery__copy p { color: rgba(58,14,24,0.82); }
.delivery__copy b { color: var(--gold); font-weight: 500; }
.delivery__nodes { list-style: none; margin-top: 2.5rem; display: grid; gap: 1.4rem; }
.delivery__nodes li {
  border-left: 2px solid var(--gold);
  padding-left: 1.2rem;
}
.delivery__nodes b { display: block; font-family: var(--font-display); font-size: 1.4rem; color: var(--fg); }
.delivery__nodes span { font-size: 0.85rem; color: rgba(58,14,24,0.82); }

.delivery__map { position: relative; }
.route {
  width: 100%; height: auto;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
}
.route__grid-dot { fill: rgba(58,14,24,0.14); }
.route__compass {
  fill: var(--muted); font-family: var(--font-mono);
  font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase;
}
.route__bg   { stroke: rgba(58,14,24,0.16); }
.route__line { stroke: var(--teal); }
.route__pin circle {
  fill: var(--bg); stroke: var(--teal); stroke-width: 2;
  transform-box: fill-box; transform-origin: center;
}
.route__pin--mid circle { stroke-width: 1.6; }
.route__pin text {
  fill: var(--fg); font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.14em;
}
.route__pin--mid text { font-size: 9px; }

/* Delivery truck — travels along .route__line in step with the scroll
   scrub (see initMap in main.js). Top-down silhouette (see markup
   comment) so rotate() stays legible across this route's ~150deg sweep.
   Pivots near the front (local origin sits at the cab divider), which
   reads more naturally than pivoting on the geometric center. */
.route__truck rect {
  fill: var(--bg); stroke: var(--teal); stroke-width: 1.6; stroke-linejoin: round;
}
.route__truck line { stroke: var(--teal); stroke-width: 1; }
.route__truck rect.route__truck-wheel { fill: var(--teal); stroke: none; }

.map-expand {
  position: absolute; bottom: 1rem; right: 1rem; z-index: 3;
  display: flex; align-items: center; gap: 0.55rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 0.55rem 0.9rem;
  font-family: var(--font-mono); font-size: 0.68rem; font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--fg); cursor: pointer;
  backdrop-filter: blur(6px);
  transition: border-color 0.3s ease, color 0.3s ease;
}
.map-expand svg { width: 12px; height: 12px; fill: currentColor; }
.map-expand:hover { border-color: var(--teal); color: var(--teal); }

.map-modal {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center;
  padding: 6vh 5vw;
  background: rgba(58, 14, 24, 0.55);
  backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
}
.map-modal.is-open { pointer-events: auto; }
.map-modal__slot {
  position: relative;
  width: min(100%, 920px);
  aspect-ratio: 4 / 3;
}
.map-modal__slot .route { width: 100%; height: 100%; border-radius: 6px; }
.map-close {
  position: absolute; top: 1.4rem; right: 1.4rem; z-index: 301;
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--card);
  backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: border-color 0.3s ease;
}
.map-close svg { width: 15px; height: 15px; fill: var(--fg); }
.map-close:hover { border-color: var(--teal); }

/* ============ PARTNER ============ */
.partner {
  position: relative; z-index: 2;
  background: var(--bg);
  padding: 12vh var(--pad);
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: 5vw;
  align-items: start;
}
.partner__copy p { color: rgba(58,14,24,0.82); }
.partner__perk {
  margin-top: 1.4rem;
  font-size: 0.78rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--gold) !important;
}

/* minmax(0, 1fr), not a bare implicit track: an auto-sized grid track takes the
   min-content of its widest item as a hard floor, and this form contains a
   320px-wide SVG button. That floor made the single column 320px wide inside a
   281px form, so every field overhung it and partner.html scrolled 20px
   sideways below ~360px. minmax(0,...) removes the floor. */
.form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.4rem; }
.form__row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.4rem; }
/* A grid item's automatic minimum size is its content's min-content width, and an
   <input>/<select> carries a wide intrinsic one (~20 characters). Below ~360px
   that shoved the two columns past the viewport and the WHOLE PAGE scrolled
   sideways — measured 2026-07-28 on partner.html at 320px: +20px. min-width:0
   lets 1fr actually behave like 1fr. No effect at any width that already fit. */
.form__row > label { min-width: 0; }
.form label {
  display: flex; flex-direction: column; gap: 0.5rem;
  font-size: 0.75rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted);
}
/* Label text and its required-asterisk must be ONE flex item, not two —
   inside a flex-column label, every element child (not just block-level
   ones) becomes its own flex row, so a bare "Name <span>*</span>" was
   splitting the asterisk onto its own line below the label instead of
   sitting next to the word. Wrapping both in .form__label-text keeps
   them one inline unit, which becomes row 1; the input is row 2. */
.form__label-text { display: inline-block; }
.form label span { color: var(--gold); }
.form input, .form select, .form textarea {
  background: transparent;
  min-width: 0;              /* see .form__row > label — same intrinsic-width trap */
  border: 1.5px solid rgba(58, 14, 24, 0.32);
  border-radius: 2px;
  padding: 0.85rem 1rem;
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 0.9rem;
  letter-spacing: 0;
  text-transform: none;
  transition: border-color 0.3s ease, background 0.3s ease;
}
.form input:focus, .form select:focus, .form textarea:focus {
  outline: none;
  border-color: var(--teal);
  background: rgba(116,26,47,0.05);
}
.form input.is-error, .form textarea.is-error { border-color: #c0563f; }
/* Browser autofill (email/phone) forces its own blue fill that can't be
   removed via background — only overridden with a huge inset box-shadow.
   Matches the same wine border/tint already used on :focus. */
.form input:-webkit-autofill,
.form input:-webkit-autofill:hover,
.form input:-webkit-autofill:focus {
  border-color: var(--teal);
  -webkit-text-fill-color: var(--fg);
  -webkit-box-shadow: 0 0 0 1000px rgba(116,26,47,0.05) inset;
  transition: background-color 5000s ease-in-out 0s;
}
.form select option { background: var(--bg-2); color: var(--fg); }
.form textarea { resize: vertical; }

.btn-knockout {
  background: none; border: 0; padding: 0; cursor: pointer;
  width: 320px; max-width: 100%;
  /* THIS is what pushed partner.html sideways below ~360px, not the fields.
     max-width:100% caps the USED width, but a grid item's automatic minimum
     size is its min-content contribution, and with width:320px that minimum is
     320px. The form is a grid, so its single column was sized to 320px, every
     field stretched to it, and the page scrolled 20px sideways at 320px wide.
     min-width:0 lets the column shrink; max-width:100% then does its job. */
  min-width: 0;
  justify-self: start;
}
.btn-knockout svg { width: 100%; height: auto; overflow: visible; }
/* Same reason as .blog-cta .btn-gold: wine on the dark partner photo needs an
   edge. Stroke is in viewBox units (320x84), so ~1.7px on screen. */
.btn-knockout__shape {
  fill: var(--teal); transition: fill 0.35s ease;
  stroke: rgba(0,0,0,0.55); stroke-width: 1.6;
}
.btn-knockout:hover .btn-knockout__shape { fill: var(--teal-dim); }
.btn-knockout text {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-anchor: middle;
}
/* The order form sits on a dark photo panel, so the wine --gold this used to
   use was all but invisible against it (client: "can't see these words on the
   partner page, make them light"). Same warm light tone the project already
   uses for text over dark photography. One colour has to serve both messages
   this element carries — the validation warning and the thank-you. */
.form__status {
  font-size: 0.8rem; letter-spacing: 0.08em; min-height: 1.2em;
  color: #e8b78d; font-weight: 500;
}

/* ============ BLOG ============ */
.blog {
  border-top: 1px solid var(--line);
  padding: 12vh var(--pad);
}
.blog__head { margin-bottom: 5vh; }
.blog__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.6rem; }
/* Bread-loaf silhouette (same crust shape as the Process bread-outline),
   applied via the #breadClip clipPath defined once near <body>. The card
   is locked to the loaf's own 3:4 proportions so the shape doesn't
   stretch into an oval on wider grid columns. */
.post {
  position: relative;
  aspect-ratio: 3 / 4;
  /* Warm cream glass, not var(--card) (translucent white) — the white
     read as a cool, pale panel against the warm bakery photo behind it. */
  background: rgba(251,233,219,0.72);
  backdrop-filter: blur(8px);
  overflow: hidden;
  display: flex; flex-direction: column;
  clip-path: url(#breadClip);
  transition: transform 0.5s cubic-bezier(0.22,1,0.36,1);
}
.post:hover { transform: translateY(-6px); }
.post__img { overflow: hidden; flex: 0 0 52%; }
.post__img img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.95) brightness(0.96);
  transition: transform 0.7s cubic-bezier(0.22,1,0.36,1), filter 0.5s ease;
}
.post:hover .post__img img { transform: scale(1.06); filter: saturate(1.02) brightness(1); }
/* Click affordance: a small arrow badge floating over the photo, bottom
   corner clear of the loaf's crust-wave clip at the top. Rests as a
   quiet outline; on hover it fills solid and nudges toward the arrow,
   the same "grow + shift" language as .link-arrow's line elsewhere, so
   the whole card reads as one clickable unit rather than needing a
   separate "Read more" line competing with the heading for space. */
.post__arrow {
  position: absolute; right: 1rem; bottom: calc(48% + 0.9rem);
  width: 38px; height: 38px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(251,233,219,0.9);
  border: 1px solid rgba(58,14,24,0.14);
  color: var(--teal);
  font-size: 1rem; line-height: 1;
  transform: translate(0, 0);
  transition: transform 0.4s cubic-bezier(0.22,1,0.36,1), background 0.35s ease, color 0.35s ease, border-color 0.35s ease;
  z-index: 2;
}
.post:hover .post__arrow {
  background: var(--teal); border-color: var(--teal); color: var(--bg);
  transform: translate(3px, -3px);
}
/* Horizontal padding must clear the clip-path's own inset (8.33% of the
   box on each side) or the loaf's straight left/right edge slices into
   the text — percentage padding keeps pace with it at any card width. */
.post__body { padding: 1.1rem 10% 2rem; display: flex; flex-direction: column; gap: 0.6rem; flex: 1; }
.post__meta { font-size: 0.68rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--gold); }
.post__body h3 { font-size: 1.05rem; line-height: 1.15; }
.post__by { font-size: 0.75rem; color: var(--muted); margin-top: auto; }

/* ============ BLOG (listing page) ============ */
.site-nav a.is-current { color: var(--fg); }
.site-nav a.is-current::after { transform: scaleX(1); transform-origin: left; }

.blog-main { position: relative; z-index: 2; background: var(--bg); }

/* Hero + books + CTA share ONE continuous background photo — same
   .bg-strip mechanic as the homepage's Delivery+Blog+CTA strip, scoped
   under its own name/image here. Painted once on this wrapper via
   ::before so it runs behind all three sections without stopping short
   after the hero text; ends right before the footer (not touching the
   contact block), per Jeka. */
.blog-strip { position: relative; }
.blog-strip::before {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  background:
    linear-gradient(180deg, rgba(15,8,7,0.66) 0%, rgba(15,8,7,0.48) 45%, rgba(15,8,7,0.68) 100%),
    url('../img/blog-strip-bg.webp?v=172') center / cover no-repeat;
}
/* Only .blog-hero and .blog-cta need lifting above the ::before photo —
   they have no z-index of their own. Deliberately NOT a blanket
   ".blog-strip > *" rule: that also caught .blog-books and
   .blog-book-backdrop, and giving .blog-books an incidental stacking
   context trapped .blog-book.is-open's z-index:50 inside it, so the
   backdrop (z-index:40, fixed at the true page root) painted OVER the
   open book instead of behind it — the open book "went dark" with it.
   .blog-book already carries its own z-index and .blog-book-backdrop is
   already fixed+z-index'd, so both are left alone here. */
.blog-hero, .blog-cta { position: relative; z-index: 1; }

/* Photo backdrop, same dark-photo/light-text treatment as Process and
   Products on the main site (the page's own dark ink palette doesn't
   read reliably over a photo, so colors are overridden scoped to the
   sections riding on .blog-strip). */
.blog-hero {
  /* Fixed header is ~107px tall since the logo grew to 62px — clear it
     with an absolute floor, not a pure vh value (vh alone drops below the
     header on short laptop viewports). .blog-books__hint ("click a page
     to turn it") lives inside this section so it reads directly on the
     strip photo too, right before the books grid. */
  padding: calc(120px + 3vh) var(--pad) 5vh;
  max-width: none;
}
.blog-hero .section-eyebrow { color: var(--bg); opacity: 0.85; }
.blog-hero h1 { color: var(--bg); max-width: 60ch; }
.blog-hero__txt { color: rgba(251,233,219,0.82); margin-top: 1rem; max-width: 60ch; }
.blog-hero .blog-books__hint { color: rgba(251,233,219,0.7); margin-top: 3vh; margin-bottom: 0; }

.blog-books__hint {
  text-align: center;
  font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 2vh;
}

/* ---- flip-book: same mechanic as хлеб\КЛАССНИЙ КАТАЛОГ\prototype.html,
   custom properties registered so the angle/length interpolate smoothly
   instead of snapping. --i (unitless, per leaf) just drives stacking
   order and turn delay via calc(), no registration needed for that. ---- */
@property --page-rotate { syntax: "<angle>"; inherits: true; initial-value: 0deg; }

.blog-books {
  padding: 0 var(--pad) 3vh;
  /* Strict 2-across grid → 4 books read as a tidy 2×2 block. Sized down
     from an earlier 430px cap so the closed 2×2 grid doesn't run so far
     past one screen's height. */
  display: grid;
  grid-template-columns: repeat(2, min(30vw, 320px));
  justify-content: center;
  gap: 3.5rem 7rem;
}

.blog-book {
  position: relative;
  width: 100%; aspect-ratio: 232 / 336;
  counter-reset: pageno;
  z-index: 1;
  /* No perspective/preserve-3d needed at this level — each
     .blog-book__leaf already opens its own 3D context for its two
     faces, so a second nested one here was redundant. */
}

/* Opening a book pulls it OUT of the grid entirely and re-centers it in
   the viewport via position:fixed — the leaf-flip mechanic below swings
   an open page a full book-width further left (spine == leaf's own
   transform-origin, always the book's left edge), so a book that opened
   in place would need 2x its own width of clear space to its left. A
   book sitting in the left grid column never has that much room before
   the viewport edge — growing it there is what clipped off-screen.
   Centering the SPINE (this box's own left edge, via `left:50%` with no
   compensating translateX) in the viewport instead gives the whole
   2-page spread room to fan out symmetrically either side of center,
   whichever column the book started in. Width is clamped by both vw and
   vh (converted through the 232:336 page ratio) so the open spread can
   never exceed the viewport in either axis — no scrolling to see it. */
.blog-book.is-open {
  position: fixed;
  /* Center in the space BELOW the fixed header (~107px), not the whole
     viewport — the +54px nudge is half the header height, so an open
     spread never rides up under the top menu on shorter screens. Height
     budget reserves 160px total so it clears header + leaves a bottom
     margin at any viewport. */
  top: calc(50% + 54px); left: 50%;
  width: min(38vw, calc((100vh - 160px) * 232 / 336), 430px);
  transform: translateY(-50%);
  z-index: 50;
  animation: blog-book-pop 0.4s cubic-bezier(0.22,1,0.36,1);
}
@keyframes blog-book-pop {
  from { transform: translateY(-50%) scale(0.88); opacity: 0; }
  to   { transform: translateY(-50%) scale(1); opacity: 1; }
}

.blog-book-backdrop {
  position: fixed; inset: 0;
  /* FULLY opaque. At 0.92 the 8% that showed through was enough to read
     the cover text of whichever book reflowed directly behind the open,
     centred one (the grid closes the gap left by the fixed book, sliding
     a neighbour right behind it) — that was the "text from the third book
     showing through the background". Solid kills the bleed entirely. */
  background: rgb(22, 10, 12);
  opacity: 0; pointer-events: none;
  /* Slightly longer than the 0.3s before so the backdrop still covers the
     other books while the closing book finishes its ~0.6s page-flip,
     instead of clearing early and revealing the grid mid-close. */
  transition: opacity 0.5s ease;
  z-index: 40;
}
/* Appear INSTANTLY (no fade-in) so the grid reflowing behind the book that
   just went position:fixed is never visible — that momentary reshuffle
   showing through a fading backdrop was the "background flickering when you
   open a book". Fade-out on close uses the base transition. */
.blog-book-backdrop.is-visible { opacity: 1; pointer-events: auto; transition: none; }

.blog-book__leaf {
  position: absolute; inset: 0;
  perspective: 1400px;
  transform-style: preserve-3d;
  transform-origin: left center;
  --page-rotate: 0deg;
  transform: rotateY(var(--page-rotate));
  transition: --page-rotate 0.6s cubic-bezier(0.22,1,0.36,1), z-index 0s;
  transition-delay: calc((4 - var(--i)) * 0.08s), calc((4 - var(--i)) * 0.08s + 0.2s);
  box-shadow: 3px 4px 16px rgba(58,14,24,0.28);
  cursor: pointer;
  border-radius: 2px;
  z-index: calc(10 - var(--i));
}
.blog-book__leaf.is-open {
  --page-rotate: -180deg;
  transition-delay: 0s, 0s;
  z-index: calc(20 + var(--i));
}
/* Once the flip has settled, JS adds .is-flat (see js/blog.js). A leaf at
   rest as an open LEFT page is otherwise a 3D-rotated back face —
   matrix3d, GPU-composited, so its text renders a touch softer than the
   RIGHT page (a plain, un-transformed front face: transform:none, crisp).
   Flattening swaps the leaf to a pure 2D translateX that lands in the
   exact same left-page slot, and un-mirrors the back face — identical
   position, but now a 2D layer, so both pages are equally sharp. transform
   isn't in the leaf's transition list, so this swap is instant/invisible
   (translateX(-100%) and the settled rotateY(-180deg) look the same). */
.blog-book__leaf.is-flat {
  transform-style: flat;
  transform: translateX(-100%);
}
.blog-book__leaf.is-flat > .blog-book__face:first-child { visibility: hidden; }
.blog-book__leaf.is-flat > .blog-book__face + .blog-book__face { transform: none; }

.blog-book__face {
  position: absolute; inset: 0;
  backface-visibility: hidden;
  background: #fdf1e2; /* opaque paper — var(--card) is a translucent glass
                           tint meant for the About section; stacked pages
                           need a solid backing or the leaf underneath
                           bleeds through. Stays visible as the rim around
                           .blog-book__face--page's inset white panel. */
  border: 1px solid var(--line);
  border-radius: 2px;
  overflow-y: auto;
  /* A page is type-fitted to its box (js/blog.js → fitPageType), so this
     scrollbar should never be needed; when a rounding pixel did overflow it
     drew a grey native bar right down the middle of the spread, which the
     client (correctly) read as a stray plate between the pages. Kept scrollable
     as a safety net, but never painted. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.blog-book__face::-webkit-scrollbar { width: 0; height: 0; }
.blog-book__leaf > .blog-book__face + .blog-book__face {
  transform: rotateY(180deg) translateZ(1px);
}

.blog-book__face--cover { padding: 0; background: #1f0d10; }
.blog-book__face--cover img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.95) brightness(0.9);
}
/* Book 3's cover photo (finsihed.webp) is a grey studio product shot,
   unlike the other 3 covers' warm-toned bakery/kitchen photos — it stood
   out as a cold patch in the 2x2 grid. Sepia+saturate warms the grey
   toward the same palette without needing a different photo. */
.blog-books .blog-book:nth-of-type(3) .blog-book__face--cover img {
  filter: sepia(0.55) saturate(1.4) brightness(0.8) contrast(1.05);
}
.blog-book__face--cover::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(12,9,6,0.92) 0%, rgba(12,9,6,0.5) 45%, transparent 100%);
}
.blog-book__coverbody {
  position: absolute; left: 1.1rem; right: 1rem; bottom: 1rem; z-index: 2;
  display: flex; flex-direction: column; gap: 0.5rem;
}
.blog-book__coverbody h2 {
  font-size: 1.55rem; line-height: 1.1; color: #f5ead9;
}
/* The client wants all four covers to carry the same number of title lines AND
   the same type size (2026-07-26: "book 3's cover text is bigger — it must
   match the others; if 4 lines don't fit, add a couple of words"). So book 3
   keeps the base 1.55rem and its headline was restored to the full original
   wording (".. at Home and in Professional Kitchens"), which wraps to 4 lines
   on its own. The per-book override below is gone; the note is kept because
   the same trap will come back if a headline is ever shortened again.
   Old approach (superseded): the headlines ran 9 to 13 words, so one size
   can't do it: at 1.55rem the shelf came out 4 / 4 / 3 / 4. Book 3's headline
   is the short one, so it alone is set larger until it also fills 4 lines.
   Measured, not guessed — the page was loaded at each candidate size and the
   rendered line count read back; 4 lines holds from 31px to 34px here, so 2rem
   sits in the middle of that band. (This cannot be done at runtime: the book
   subtree does not re-layout within a task — see the note in js/blog.js.) */
/* Cover sits over a photo + dark gradient, not the cream page — the
   maroon --gold reads fine on paper but disappears into that dark
   overlay, so the eyebrow tag gets its own warm light tone here. */
.blog-book__coverbody .leaf__tag { color: #e3a875; }

/* Off-screen twin of the shelf, built and thrown away by js/blog.js →
   fitAllBooks: the article type is measured on THIS, never on the books the
   reader can see (opening the real ones took them off the shelf for a few frames
   and they visibly blinked). Absolute so it adds no height, hidden — not
   display:none — because the page boxes still have to lay out to be measured. */
.blog-books--measuring {
  position: absolute;
  top: 0; left: -200vw;
  width: 100%;
  visibility: hidden;
  pointer-events: none;
}
.blog-books--measuring .blog-book { animation: none; }

/* A book at rest paints ONLY its front cover. Five 3D leaves x two
   backface-hidden faces x four books = 40 compositor surfaces sitting on one
   screen; the browser drops and re-rasterises them as they scroll into view and
   the covers visibly stir (client, on the phone first and then on the desktop:
   "books 3 and 4 move as I scroll down to them"). Everything that is not the
   front cover is taken out of rendering until the book opens — 40 surfaces down
   to 4. See the long note in the phone block for the two traps: the 3D must STAY
   (backface-visibility is what hides the mirrored page text through the cover),
   and the state must be .is-quiet, never :not(.is-open), or the closing
   page-turn is swallowed mid-flip. */
.blog-book.is-quiet .blog-book__leaf:not(:first-of-type),
.blog-book.is-quiet .blog-book__leaf:first-of-type > .blog-book__face + .blog-book__face {
  visibility: hidden;
  content-visibility: hidden;
}
/* The twin is measured, not looked at: its pages must lay out even though every
   book in it counts as quiet. */
.blog-books--measuring .blog-book__leaf,
.blog-books--measuring .blog-book__face {
  visibility: inherit;
  content-visibility: visible;
}
.blog-book__face--page {
  padding: 1.9rem 1.9rem 2.3rem;
  display: flex; flex-direction: column; gap: 1rem;
  /* Every page starts at the top, so the heading of one spread sits at exactly
     the same height as the heading of the next (client, 2026-07-26: "it looks
     bad that the headings sit at different heights — they must always be on one
     line"). This replaces the earlier centred layout, which balanced the
     leftover room above and below and therefore moved every heading by a
     different amount. The back cover overrides this with flex-end below. */
  justify-content: flex-start;
  font-size: 1.08rem; font-weight: 500; line-height: 1.62;
  color: rgba(58,14,24,0.94);
  counter-increment: pageno;
  /* Only the SECOND face in a leaf carries its own `transform` (see the
     sibling-combinator rule below main.css), which makes it a stacking
     context on its own — that's why its ::before paints correctly in
     front of ITS OWN background. A first face has no transform of its
     own, so without this it's not a stacking context, and its negative
     z-index ::before escapes up to the shared .blog-book__leaf context —
     painting BEHIND this face's own cream background instead of in front
     of it (the bug: the right-hand page of a spread showed no white
     panel). `isolation: isolate` forces every page to own its stacking
     context so the panel always renders relative to its own face. */
  isolation: isolate;
  /* Text here sits on a page that's briefly a real 3D flip (rotateY +
     backface-visibility on .blog-book__leaf) — GPU-composited text can
     render a shade softer than normal DOM text, so lean on heavier
     weight + tighter smoothing to keep it crisp at rest. */
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Soft notepad-white panel inset within the page, leaving the peach
   .blog-book__face background showing as a rim/border around it — z-index
   -1 so it sits behind the (static, non-positioned) text content instead
   of covering it. */
.blog-book__face--page::before {
  content: "";
  position: absolute; inset: 1.4rem;
  background: #f8f5ee;
  border-radius: 2px;
  z-index: -1;
}
/* Every book — full-bleed photo instead of the plain cream panel,
   overriding the inset above with inset:0 (full page, no rim). One photo
   per book (not per page), each book its own image. */
.blog-books .blog-book:nth-of-type(1) .blog-book__face--page::before {
  inset: 0; border-radius: 2px;
  background: url('../img/blog-book1-bg.webp?v=172') center / cover no-repeat;
}
.blog-books .blog-book:nth-of-type(2) .blog-book__face--page::before {
  inset: 0; border-radius: 2px;
  background: url('../img/blog-book2-bg.webp?v=172') center / cover no-repeat;
}
.blog-books .blog-book:nth-of-type(3) .blog-book__face--page::before {
  inset: 0; border-radius: 2px;
  background: url('../img/blog-book3-bg.webp?v=172') center / cover no-repeat;
}
.blog-books .blog-book:nth-of-type(4) .blog-book__face--page::before {
  inset: 0; border-radius: 2px;
  background: url('../img/blog-book4-bg.webp?v=172') center / cover no-repeat;
}
/* Subtle dark halo behind the text (not a solid card) so it stays legible
   over the photo without hiding it, plus one flat dark text color — no
   separate wine/gold accent on the tag, bullets or bold words. */
.blog-books .blog-book .blog-book__face--page {
  text-shadow: 0 1px 3px rgba(20,8,10,0.28), 0 0 10px rgba(20,8,10,0.15);
  color: rgba(36,7,13,1);
}
.blog-books .blog-book .blog-book__face--page li::marker,
.blog-books .blog-book .blog-book__face--page b,
.blog-books .blog-book .blog-book__face--page .blog-book__hint {
  color: rgba(36,7,13,1);
}
/* Page eyebrow tags and headings — the wine/--gold accent (picked from a
   side-by-side color prototype, book-color-test.html). Scoped to the
   inner page faces only; the back cover keeps its own amber below. */
.blog-books .blog-book .blog-book__face--page .leaf__tag,
.blog-books .blog-book .blog-book__face--page h3 {
  color: #741a2f;
}
/* Back cover (.blog-book__face--cta) is the one page face that's dark
   photo + dark scrim, not the light book-page photo — needs its tag back
   to a light tone, or it reads as near-invisible dark-on-dark. Same
   specificity as the flat-dark rule above, placed after so it wins. Left
   untouched per Jeka — only the inner pages get the new wine accent. */
.blog-books .blog-book .blog-book__face--cta .leaf__tag {
  color: #e3a875;
}
/* Page number, pinned bottom-right in the peach rim like a real book
   folio so you always know which spread you're on. */
.blog-book__face--page::after {
  content: counter(pageno);
  position: absolute; bottom: 0.5rem; right: 0.6rem;
  width: 1.5rem; height: 1.5rem;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: 0.85rem;
  letter-spacing: 0.02em; color: rgba(36,7,13,0.9); pointer-events: none;
  background: rgba(251,233,219,0.88);
  border-radius: 50%;
}
.blog-book__face--page h3 { font-size: 1.65rem; margin-bottom: 0.15rem; }
.blog-book__face--page ul, .blog-book__face--page ol { padding-left: 1.2rem; display: flex; flex-direction: column; gap: 0.55rem; }
.blog-book__face--page li::marker { color: var(--gold); }
.blog-book__face--page b { color: var(--gold); font-weight: 500; }
.blog-book__hint { color: var(--muted); font-size: 0.88rem; font-style: italic; }

.blog-book__face--cta {
  align-items: flex-start; justify-content: flex-end; gap: 0.8rem;
  text-align: left;
  /* NOTE: no position:relative here — the base .blog-book__face is already
     position:absolute; inset:0, which both fills the whole leaf AND is a
     containing block for the absolute <img> below. Overriding it with
     relative collapsed this face to text height, so the photo only showed
     as a strip at the top. */
}
/* Back page gets a full-bleed photo instead of the paper look — kill the
   white panel (needs both classes to outrank the plain .blog-book__face--
   page::before rule above) and let the photo + scrim sit behind the text
   at z-index -2/-1 (safe because .blog-book__face--page already isolates
   its own stacking context, so these stay local to this face). */
.blog-book__face--page.blog-book__face--cta::before { display: none; }
.blog-book__face--cta img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  z-index: -2; filter: saturate(0.95) brightness(0.82);
}
/* Book 3's back cover (cta-book3.webp) is the same grey studio product
   shot as its front cover — same warm fix, other end of the book. */
.blog-books .blog-book:nth-of-type(3) .blog-book__face--cta img {
  filter: sepia(0.55) saturate(1.4) brightness(0.75) contrast(1.05);
}
.blog-book__face--cta .cta-scrim {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(to top, rgba(12,9,6,0.85) 0%, rgba(12,9,6,0.55) 50%, rgba(12,9,6,0.2) 100%);
}
.blog-book__face--cta .leaf__tag { color: #e3a875; }
.blog-book__by { font-size: 1rem; color: var(--muted); }
.blog-book__face--cta .blog-book__by { color: #f5ead9; }
.blog-book__face--cta::after { content: none; } /* back cover, not a numbered page */
.blog-book__face--cta .link-arrow { color: #f5ead9; }
.blog-book__face--cta .link-arrow__line { background: #f5ead9; }

.leaf__tag {
  font-size: 0.82rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--gold); font-weight: 500;
}

.blog-cta {
  text-align: center;
  padding: 6vh var(--pad) 12vh;
  display: flex; flex-direction: column; align-items: center; gap: 1.4rem;
}
.blog-cta p { font-family: var(--font-display); font-size: clamp(1.6rem, 3vw, 2.2rem); color: var(--bg); }
/* Wine button on the dark strip photo read as part of the background — a thin
   black rim and a soft shadow lift it off. */
.blog-cta .btn-gold {
  border: 1px solid rgba(0,0,0,0.55);
  box-shadow: 0 2px 12px rgba(0,0,0,0.35);
}

/* Below ~2 full-width books the grid can't hold two columns — drop to a
   single centered column and cancel the spine shift so nothing clips. */
@media (max-width: 1000px) {
  .blog-books { grid-template-columns: min(88vw, 430px); gap: 5rem 0; }
}

/* ============ PARTNER (standalone page) ============ */
/* Shared warehouse/port photo behind hero+form, dimmed to darkness level
   4 of 5 tried (Jeka picked it live via an on-page 1-5 switcher). */
.partner-main { position: relative; z-index: 2; background: none; }
.partner-main::before {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  background: url('../img/partner-bg-f.webp?v=6') center / cover no-repeat;
  background-attachment: fixed;
  filter: blur(6px);
}
.partner-main > * { position: relative; z-index: 1; }

.partner-hero {
  /* The photo column clears the fixed header with its own cream padding-top
     (.partner-hero__media) so the heads sit below the menu; the text column
     clears it via .partner-hero__copy's 22vh top padding. */
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  /* become.jpg was cropped 10% off the top and bottom (removes empty
     sky/street, tightens the frame on the worker) — the box is shortened
     by the same ~20% so the photo actually reads shorter, instead of
     object-fit:cover just re-zooming the cropped image to fill the old
     tall box. */
  min-height: 50vh;
}
.partner-hero__media {
  overflow: hidden;
  /* Only the cream BACKGROUND runs up under the fixed menu — the photo
     itself starts below the header so the two heads are never tucked behind
     (or cropped by) the translucent menu bar. */
  padding-top: 76px;
  background: var(--bg);
}
.partner-hero__media img {
  width: 100%; height: calc(100% + 10px); min-height: 37vh; object-fit: cover;
  filter: saturate(1.02) contrast(1.02);
  margin-top: -10px;
}
.partner-hero__copy {
  padding: calc(22vh + 9px) var(--pad) 8vh;
  display: flex; flex-direction: column; justify-content: flex-start;
}
.partner-hero__copy h1 { font-size: clamp(2.6rem, 5vw, 4.4rem); margin-bottom: 1.2rem; }
.partner-hero__copy p { max-width: 42ch; color: rgba(251,233,219,0.82); }

.partner-form-section {
  padding: 10vh var(--pad) 12vh;
  max-width: 900px;
  margin: 0 auto;
}
/* "Request A Sample" jumps straight to #order-form, which is the <form>
   itself (js/partner.js looks it up by that id) — scroll-margin-top has to
   live on the actual anchor target, not its section wrapper, or the
   browser ignores it. Default jump landed with the whole first field row
   hidden behind the fixed header (row height ~76px ≈ header height); this
   pulls the stop point up so Business Name/Contact Person are visible. */
#order-form { scroll-margin-top: 370px; }
.partner-form-section__head { max-width: 56ch; margin-bottom: 5vh; }
.partner-form-section__head p:not(.section-eyebrow):not(.partner__perk) { color: rgba(251,233,219,0.82); }
.partner__perk {
  margin-top: 1rem;
  font-size: 0.78rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: #e3a875 !important;
}
/* Whole page sits over the dark warehouse photo (.partner-main::before), so
   every text element here needs the site's light-on-dark palette instead of
   the default dark-ink one — same override .hero uses on index.html. */
.partner-hero .section-eyebrow,
.partner-form-section__head .section-eyebrow { color: var(--bg); opacity: 0.9; }
.partner-hero__copy h1,
.partner-form-section__head h2 { color: var(--bg); }
.partner-form-section .form label { color: rgba(251,233,219,0.85); }
.partner-form-section .form label span { color: #e3a875; }
.partner-form-section .form input,
.partner-form-section .form select,
.partner-form-section .form textarea {
  color: var(--bg);
  border-color: rgba(251,233,219,0.4);
}
.partner-form-section .form input::placeholder,
.partner-form-section .form textarea::placeholder { color: rgba(251,233,219,0.5); }

@media (max-width: 1100px) {
  .partner-hero { grid-template-columns: 1fr; }
  .partner-hero__media img { min-height: 27vh; }
}

/* ============ FOOTER ============ */
.site-foot {
  position: relative; z-index: 2;
  background: var(--bg);
  border-top: 1px solid var(--line);
  padding: 10vh var(--pad) 3vh;
}
/* blog.html's .blog-cta ("Ready to see the difference...") already closes
   the page visually with its own CTA button — the footer's OWN top
   border right underneath it was orphaned once the redundant "Let's
   talk" block was removed, so drop it for this page only. .foot__grid's
   border-top stays — that one separates the brand/links/contact columns
   from whatever's above and is still needed. */
.blog-main + .site-foot { border-top: none; }
/* Same fix, partner.html: the "Let's talk" CTA was redundant with the
   form's own submit button right above it, so it's gone — leaving the
   footer's own top border orphaned right under the form. .foot__grid's
   border-top stays, same reasoning as blog.html. */
.partner-main + .site-foot { border-top: none; }
/* Same fix, contact.html: the page IS the contact/CTA destination, so
   the footer's own "Let's talk" was redundant. .foot__grid's border-top
   stays, same reasoning as blog.html/partner.html. */
.contact-main + .site-foot { border-top: none; }
/* Same fix, product.html: missed in the earlier blog/partner/contact pass
   — the page has its own CTA button right above ("Request a sample"), so
   the footer's "Let's talk" was redundant here too. .foot__grid's
   border-top stays, same reasoning as the other three pages. */
.product-main + .site-foot { border-top: none; }
.foot__cta {
  text-align: center;
  padding: 10vh var(--pad) 8vh;
}
.foot__link {
  display: inline-block;
  animation: foot-link-pulse 2.4s ease-in-out infinite;
}
/* "Let's Talk" lockup — live text in the display face between the two
   ornament rules (was a flat PNG whose lettering kept the pre-Fraunces face).
   The SVG rules are 400 units wide, so the lockup's own width drives the whole
   mark and the type just scales with it. */
.foot-lockup {
  display: flex; flex-direction: column; align-items: center;
  width: clamp(280px, 42vw, 560px);
  gap: 0.35rem;
}
.foot-lockup__orn { width: 100%; height: auto; display: block; }
.foot-lockup__txt {
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 6.4vw, 5.2rem);
  line-height: 1.02;
  color: #fbe9db;
  white-space: nowrap;
  /* The mark sits on a photo — a soft drop keeps the cream letters readable
     over the light crumb without a scrim over the picture. */
  text-shadow: 0 2px 6px rgba(12, 9, 6, 0.45), 0 8px 28px rgba(12, 9, 6, 0.4);
  transition: filter 0.4s ease;
}
.foot__link:hover { animation-play-state: paused; }
.foot__link:hover .foot-lockup__txt { filter: brightness(1.2); }
@keyframes foot-link-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.045); opacity: 0.85; }
}
@media (prefers-reduced-motion: reduce) {
  .foot__link { animation: none; }
}
.foot__grid {
  display: grid;
  grid-template-columns: 1.4fr 0.85fr 0.85fr 1.1fr;
  gap: 4vw;
  padding-block: 5vh;
  border-top: 1px solid var(--line);
}
.foot__col { display: flex; flex-direction: column; gap: 0.6rem; }
.foot__col h4 {
  font-size: 0.75rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--gold); font-weight: 400; margin-bottom: 0.6rem;
}
.foot__col a { font-size: 0.85rem; font-weight: 500; color: rgba(58,14,24,0.82); transition: color 0.3s ease; width: fit-content; }
.foot__col a:hover { color: var(--teal); }
.foot__mark { width: 90px; margin-bottom: 0.8rem; }
.foot__col--brand p { font-size: 0.82rem; color: var(--muted); max-width: 46ch; }
.foot__line { font-size: 0.85rem; color: rgba(58,14,24,0.82); }
.foot__line--dim { color: var(--muted); font-size: 0.78rem; margin-top: 0.6rem; }
.foot__legal {
  text-align: center; padding-top: 3vh;
  border-top: 1px solid var(--line);
  font-size: 0.75rem; color: var(--muted); letter-spacing: 0.08em;
}
/* Developer credit — quiet by default, wakes up on hover. Kept on the same
   line as the copyright so the footer does not grow a whole extra row. */
.foot__dev { color: var(--muted); }
.foot__dev a {
  color: var(--teal); font-weight: 500;
  border-bottom: 1px solid rgba(116, 26, 47, 0.3);
  transition: color 0.3s ease, border-color 0.3s ease;
}
.foot__dev a:hover { color: var(--fg); border-color: var(--fg); }
@media (max-width: 600px) {
  /* two short lines read better than one wrapped one on a phone */
  .foot__dev { display: block; margin-top: 0.5rem; }
  .foot__dev .foot__dev-sep { display: none; }
}

/* ============ CONTACT (standalone page) ============ */
.contact-main { position: relative; z-index: 2; background: var(--bg); }

.contact-hero {
  position: relative;
  min-height: 124vh; /* video source is portrait (896x1008) — a short wide box
    forces object-fit:cover to zoom into a thin vertical sliver of the frame,
    which reads as blurry; a taller box needs far less crop/zoom. */
  display: flex; flex-direction: column; justify-content: center;
  padding: 4.6rem var(--pad) 6vh; /* clears the fixed header, no split layout beneath it */
  overflow: hidden;
}
.contact-hero__media { position: absolute; inset: 0; z-index: 0; }
.contact-hero__media video {
  width: 100%; height: 100%; object-fit: cover;
  /* Cool-gray industrial warehouse footage clashed with the site's warm
     palette. sepia+hue-rotate read as straight-up yellow — this (slight
     desaturate + darken, barely any sepia) reads as a moodier neutral
     tone instead of a color cast. */
  filter: saturate(0.85) contrast(1.05) brightness(0.9) sepia(0.08);
}
.contact-hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(100deg, rgba(12,9,6,0.86) 0%, rgba(12,9,6,0.72) 45%, rgba(12,9,6,0.4) 100%);
}
.contact-hero__copy { position: relative; z-index: 2; max-width: 60ch; }
.contact-hero .section-eyebrow { color: var(--bg); opacity: 0.85; }
.contact-hero__copy h1 { font-size: clamp(2.6rem, 5vw, 4.4rem); color: var(--bg); margin-bottom: 1.2rem; }
.contact-hero__txt { color: rgba(251,233,219,0.82); max-width: 44ch; }

/* Warehouse aisle photo (grok-image-91f161ba, cropped to img/contact-intro-bg.webp)
   runs behind BOTH the bread cards and the map — one shared backdrop,
   same "one photo behind several sections" trick as .bg-strip on
   index.html. Darkness level picked live (5 tries, midway between the
   "darker" and "darkest" ones: 0.56/0.46/0.62 and 0.38/0.28/0.44 averaged
   to 0.47/0.37/0.53). Dark-wine text throughout, same as any other light
   section on the site. */
.contact-photo-strip { position: relative; z-index: 2; }
.contact-photo-strip::before {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  background:
    linear-gradient(180deg, rgba(251,233,219,0.47) 0%, rgba(251,233,219,0.37) 45%, rgba(251,233,219,0.53) 100%),
    url('../img/contact-intro-bg.webp') center / cover no-repeat;
}
.contact-photo-strip > * { position: relative; z-index: 1; }
.contact-photo-strip .contact-card__label,
.contact-photo-strip .contact-card--bread h3 { color: var(--gold); opacity: 1; }
.contact-photo-strip .contact-card--bread a { color: var(--teal); }
.contact-photo-strip .contact-card--bread .contact-card__line { color: rgba(58,14,24,0.82); }
.contact-photo-strip .contact-map .section-eyebrow,
.contact-photo-strip .contact-map h2 { color: var(--fg); opacity: 1; }
/* Bread cards nudged down off the photo strip's top edge. */
.contact-photo-strip .contact-cards { padding-top: 50px; }
/* Google's own bright white map UI glares against the warm photo backdrop
   around it — dim it a bit more than the base grayscale/sepia filter. */
.contact-photo-strip .contact-map__frame iframe {
  filter: grayscale(0.25) sepia(0.12) saturate(1.1) brightness(0.87);
}

.contact-cards {
  padding: 0 var(--pad) 8vh;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.6rem;
}
.contact-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 2.2rem 1.8rem;
}
.contact-card__icon { font-size: 1.6rem; }
.contact-card__label {
  margin-top: 1rem;
  font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--gold);
}
.contact-card h3 { margin: 0.4rem 0 0.9rem; }
.contact-card a { font-size: 0.92rem; font-weight: 500; color: var(--teal); }
.contact-card a:hover { text-decoration: underline; }
.contact-card__line { font-size: 0.88rem; color: rgba(58,14,24,0.82); }

/* Bread cross-section cards — same silhouette as the Process info panels
   (img/bread-outline.svg, crust wave on top). Content is centered on
   both axes rather than pinned to the top: the shape is tall and
   symmetric, and these cards' content is short, so top-alignment left a
   slab of dead space at the bottom that read as unfinished. */
.contact-cards--bread {
  grid-template-columns: repeat(3, minmax(0, 260px));
  justify-content: center;
}
.contact-card--bread {
  background: none; border: none; border-radius: 0;
  position: relative;
  aspect-ratio: 3 / 4;
  padding: 5rem 1.8rem 2.6rem;
  display: flex; flex-direction: column; align-items: center;
  /* flex-start, not center: centering the whole content block meant the
     card with more trailing text (3-line address vs 1-line email/phone)
     had its icon/label/h3 pushed up higher than the other two cards
     (measured 23px of drift). Anchoring to the top keeps headings level
     across all three regardless of how much text follows. */
  justify-content: flex-start;
  gap: 0.3rem;
  text-align: center;
  transition: transform 0.35s cubic-bezier(0.22,1,0.36,1);
}
.contact-card--bread:hover { transform: scale(1.045); }
/* A real photo (Jeka's own reference mockup, grok-image-5ef41504 — sliced
   bread crumb clipped to this exact loaf silhouette) replaced the flat
   tinted-outline fill. First attempt clipped it with a CSS `mask` pointing
   at an SVG silhouette — worked in this session's browser, but came back
   invisible for Jeka every time (no `mask`/`-webkit-mask` bug ever isolated,
   just unreliable). Removed CSS masking entirely: img/bread-photo-fill.png
   now has the loaf shape baked in as REAL pixel transparency (cut with
   Pillow, same path as the SVG, 4x supersampled then downscaled for a
   smooth edge) — a plain background-image, no masking property involved,
   so there's nothing browser-support-dependent left to fail. The original
   bread-outline.svg (translucent wine fill + stroke) sits on ::after, on
   top of the photo, so the crust-wave edge is still a crisp wine line and
   the photo gets the same faint wine tint the site's other photo+outline
   combos use (About-stops). The drop-shadow on ::after follows the loaf
   silhouette itself (not the invisible rectangular box), lifting the card
   off the photo behind it. */
.contact-card--bread::before {
  content: "";
  position: absolute; inset: 0;
  background: url('../img/bread-photo-fill.webp') center / cover no-repeat;
  pointer-events: none;
  z-index: -2;
}
.contact-card--bread::after {
  content: "";
  position: absolute; inset: 0;
  background: url('../img/bread-outline.svg') center / 100% 100% no-repeat;
  pointer-events: none;
  z-index: -1;
  filter: drop-shadow(0 12px 20px rgba(10,4,6,0.4));
}
/* Text-glyph icons (✉ ☎) render in a ~50px line box; an SVG replacement
   (the location pin, below) has its own smaller intrinsic box, which
   threw off the flex-start alignment above. Force all three icons into
   the same explicit box regardless of glyph vs SVG content. */
.contact-card--bread .contact-card__icon {
  display: flex; align-items: center; justify-content: center;
  height: 2.6rem; font-size: 1.9rem; margin-bottom: 0.4rem;
}
.contact-card__icon--pin svg { display: block; width: 1em; height: 1em; }
.contact-card--bread .contact-card__label { margin-top: 0; }
.contact-card--bread h3 { margin: 0.3rem 0 0.7rem; }
.contact-card--bread .contact-card__line { max-width: 20ch; }

.contact-map { padding: 0 var(--pad) 12vh; }
.contact-map__frame {
  margin-top: 2.5rem;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.6rem;
  overflow: hidden;
}
.contact-map__frame iframe {
  display: block; width: 100%; aspect-ratio: 16 / 7; min-height: 320px;
  border: 0; border-radius: 2px;
  filter: grayscale(0.25) sepia(0.12) saturate(1.1);
}

/* ============ LEGAL PAGES (privacy.html / terms.html) ============ */
/* img/legal-bg-56.webp (sharp baker photo, folder 5) under a darker cream
   gradient — chosen over product.html's product-bg.webp after an A/B test
   (legal-bg-test.html) because that photo, cover-sized to this page's
   full (very tall) content height, had to zoom in so far it went soft;
   fixed attachment keeps it scaled to the viewport instead of the whole
   scrollable page, so it stays sharp regardless of how long the legal
   text runs. */
.legal-main { position: relative; z-index: 2; background: none; }
.legal-main::before {
  content: "";
  position: absolute; inset: 0; z-index: 0;
  background:
    linear-gradient(180deg, rgba(251,233,219,0.32) 0%, rgba(251,233,219,0.2) 45%, rgba(251,233,219,0.42) 100%),
    url('../img/legal-bg-56.webp') center / cover no-repeat;
  background-attachment: fixed, fixed;
}
.legal-main > * { position: relative; z-index: 1; }
.legal-hero {
  padding: calc(120px + 4vh) var(--pad) 3vh;
  max-width: 68ch;
}
.legal-hero__updated {
  font-size: 0.8rem; letter-spacing: 0.06em; color: rgba(58,14,24,0.6);
  margin-top: 0.6rem;
}
.legal-body {
  padding: 2vh var(--pad) 12vh;
  max-width: 68ch;
}
.legal-body h2 {
  font-size: 1.4rem;
  margin: 2.6rem 0 0.9rem;
}
.legal-body h2:first-child { margin-top: 0; }
.legal-body p, .legal-body li {
  color: rgba(58,14,24,0.82);
  line-height: 1.65;
  margin-bottom: 0.9rem;
}
.legal-body ul, .legal-body ol { padding-left: 1.4rem; margin-bottom: 0.9rem; }
.legal-body a { color: var(--teal); text-decoration: underline; }
.legal-body strong { color: var(--fg); font-weight: 500; }

/* Analytics opt-out on privacy.html. Lives here rather than in cookie.js
   because it is part of the page, not of the floating banner — and privacy
   .html is a desktop-styled page that phones also read (no mobile twin). */
.consent-box {
  margin: 1.4rem 0 0.6rem;
  padding: 1.15rem 1.3rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--teal);
  border-radius: 3px;
  background: var(--card);
}
.consent-box__state { margin-bottom: 0.9rem !important; font-size: 0.92rem; }
.consent-box__btn {
  font-family: var(--font-mono);
  font-size: 0.74rem; letter-spacing: 0.12em; text-transform: uppercase;
  font-weight: 500; padding: 0.7rem 1.4rem; border-radius: 2px;
  cursor: pointer; border: 1px solid var(--teal);
  background: var(--teal); color: var(--bg);
  transition: transform .3s ease, background .3s ease, color .3s ease;
}
.consent-box__btn:hover { transform: translateY(-2px); }
.consent-box__btn[data-action="grant"] { background: transparent; color: var(--teal); }
.consent-box__btn[data-action="grant"]:hover { background: var(--teal); color: var(--bg); }
@media (prefers-reduced-motion: reduce) { .consent-box__btn { transition: none; } }

/* ============ RESPONSIVE (desktop-down guard; mobile.html handles phones) ============ */
@media (max-width: 1100px) {
  .facts, .blog__grid { grid-template-columns: repeat(2, 1fr); }
  .product, .delivery, .partner, .arch { grid-template-columns: 1fr; }
  .arch:nth-child(even) .arch__info { order: 0; }
  .arch__img { height: 46vh; }
  #scene { justify-content: center; opacity: 0.25; }
  #about-cube { --s: min(70vw, 60vh, 480px); }
  .about-stop { max-width: 86vw; }
  .hero__specs { gap: 2rem; flex-wrap: wrap; }
  .contact-cards { grid-template-columns: 1fr; }
  .contact-map__frame iframe { aspect-ratio: 4 / 5; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  /* The blanket 0.01ms override above turns an infinite marquee into a
     frozen-looking flicker (still "infinite", just imperceptibly fast) —
     stop it cleanly instead, which is also the correct a11y behaviour. */
  .certs__row { animation: none !important; }
}

/* ============ WHATSAPP WIDGET (site-wide, all pages) ============ */
/* Ported from the live agroloafghana.com/product.html reference (same
   widget also present there on index.html/contact.html — site-wide, not
   product-only). Recolored from WhatsApp's own green to the site's wine
   (var(--teal)) so the widget reads as part of the brand, not a bolted-on
   green sticker — icon shape stays the same so it's still recognizable
   as WhatsApp. Phone number is Jeka's own for now. */
.whatsapp-widget {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 1000;
}
/* Collapsed to a plain circle at rest (icon only) and expands into a
   pill with the label on hover — the "hover-reveal FAB" pattern, quieter
   than a permanently-labelled button sitting on screen. No looping
   attention-pulse animation (dated); the only motion is the hover
   expand + a slow, subtle breathing shadow. */
.whatsapp-button {
  background: var(--teal);
  border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  width: 58px; height: 58px;
  padding: 0 17px;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(58,14,24,0.32);
  transition: width 0.4s cubic-bezier(0.22,1,0.36,1), background 0.3s ease,
              box-shadow 0.3s ease, transform 0.3s ease;
  animation: whatsapp-breathe 3.4s ease-in-out infinite;
  position: relative;
  overflow: hidden;
}
.whatsapp-button:hover {
  width: 176px;
  background: var(--teal-dim);
  transform: translateY(-2px);
  box-shadow: 0 12px 28px rgba(58,14,24,0.4);
  animation-play-state: paused;
}
.whatsapp-button svg { width: 22px; height: 22px; fill: #fff; flex-shrink: 0; }
.whatsapp-button .button-text {
  color: var(--bg); font-family: var(--font-mono); font-weight: 500;
  font-size: 0.82rem; letter-spacing: 0.02em; white-space: nowrap;
  max-width: 0; opacity: 0; margin-left: 0;
  overflow: hidden;
  transition: max-width 0.4s cubic-bezier(0.22,1,0.36,1), opacity 0.25s ease,
              margin-left 0.4s cubic-bezier(0.22,1,0.36,1);
}
.whatsapp-button:hover .button-text { max-width: 120px; opacity: 1; margin-left: 10px; }
@keyframes whatsapp-breathe {
  0%, 100% { box-shadow: 0 8px 24px rgba(58,14,24,0.32); }
  50%      { box-shadow: 0 8px 24px rgba(58,14,24,0.32), 0 0 0 8px rgba(116,26,47,0.12); }
}
/* The button is a circle, so the dot has to sit inside the ARC, not in the
   corner of the bounding box — at 6px it overshot the curve and got sliced
   off by the button's overflow:hidden. 8px keeps it fully on the fill. */
.whatsapp-notification-dot {
  position: absolute;
  top: 8px; right: 8px;
  width: 9px; height: 9px;
  background: var(--terracotta);
  border-radius: 50%;
  border: 1.5px solid var(--bg);
}
.whatsapp-popup {
  position: absolute;
  bottom: 76px; right: 0;
  width: 320px;
  background: var(--bg);
  border-radius: 20px;
  box-shadow: 0 20px 48px rgba(20,8,10,0.3);
  transform: scale(0.92) translateY(8px);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.35s cubic-bezier(0.22,1,0.36,1), opacity 0.3s ease;
  transform-origin: bottom right;
}
.whatsapp-popup.active { transform: scale(1) translateY(0); opacity: 1; pointer-events: auto; }
.whatsapp-popup .popup-header {
  background: var(--teal); color: var(--bg);
  padding: 18px 20px; border-radius: 20px 20px 0 0;
  display: flex; align-items: center; gap: 12px;
}
.whatsapp-popup .popup-avatar {
  width: 42px; height: 42px; border-radius: 50%;
  border: 2px solid rgba(251,233,219,0.35); object-fit: cover;
}
.whatsapp-popup .popup-info h3 {
  font-size: 0.95rem; font-family: var(--font-display); font-weight: 400;
  letter-spacing: 0.02em; margin-bottom: 2px; color: var(--bg);
}
.whatsapp-popup .popup-info p { font-size: 0.75rem; opacity: 0.85; color: var(--bg); }
.whatsapp-popup .popup-body { padding: 20px; }
.whatsapp-popup .popup-message {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 15px;
  margin-bottom: 16px; font-size: 0.85rem; line-height: 1.5; color: var(--fg);
}
.whatsapp-popup .popup-actions { display: flex; gap: 10px; }
.whatsapp-popup .popup-btn {
  flex: 1; padding: 12px 16px; border: none; border-radius: 8px;
  font-weight: 600; cursor: pointer; transition: all 0.2s ease; font-size: 0.85rem;
  font-family: var(--font-mono);
}
.whatsapp-popup .popup-btn.primary { background: var(--teal); color: var(--bg); }
.whatsapp-popup .popup-btn.primary:hover { background: var(--teal-dim); transform: translateY(-1px); }
.whatsapp-popup .popup-btn.secondary { background: var(--bg-2); color: var(--muted); }
.whatsapp-popup .popup-btn.secondary:hover { background: var(--line); }
.whatsapp-popup .close-btn {
  position: absolute; top: 12px; right: 12px;
  background: rgba(251,233,219,0.2); border: none; color: var(--bg);
  width: 24px; height: 24px; border-radius: 50%; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; transition: background 0.2s ease;
}
.whatsapp-popup .close-btn:hover { background: rgba(251,233,219,0.32); }

@media (max-width: 480px) {
  .whatsapp-widget { bottom: 14px; right: 14px; }
  .whatsapp-popup { width: calc(100vw - 28px); right: -6px; }
  .whatsapp-button .button-text { display: none; }
  /* Icon centred in the circle, and overflow:visible so the corner badge
     isn't clipped by the round button (client request). */
  .whatsapp-button { padding: 14px; border-radius: 50%; justify-content: center; overflow: visible; }
  /* Badge INSIDE the disc, matching home (.m-nav's widget in mobile.css sits
     at 8px on a 52px button). Geometry, not eyeball: button r=29, dot r=5.5
     +2px border, so at 10px the badge centre is 19.7px out and its far edge
     lands at 27.2 < 29 — fully on the fill. At the old -1px the centre was
     35px out, i.e. past the arc entirely, which is the "small dot hanging
     outside" the client reported on every page except home. */
  .whatsapp-notification-dot { top: 10px; right: 10px; width: 11px; height: 11px; border-width: 2px; }
  /* Touch has no real hover: a tap to open the chat left the desktop
     hover-expand stuck, stretching the circle into the 176px pill. Neutralise
     the hover state on phones so the button stays a clean circle. */
  .whatsapp-button:hover {
    width: 58px; background: var(--teal); transform: none;
    box-shadow: 0 8px 24px rgba(58,14,24,0.32);
  }
  .whatsapp-button:hover .button-text { max-width: 0; opacity: 0; margin-left: 0; }
}

/* ============================================================
   PHONE LAYER (max-width: 760px) — SECONDARY pages only
   (blog/partner/product/contact/privacy/terms). index.html has its
   own dedicated mobile.html (phones are redirected there), so this block
   never applies to the homepage's cube/pin machinery. These pages already
   collapse to single column at the 1100px guard above; this layer fixes
   the shared desktop HEADER (whose full horizontal nav overflows a phone),
   plus the remaining multi-column blocks specific to each page, and dials
   the section padding down for small screens.
   ============================================================ */
/* js/mobile-nav.js injects .m-burger2 + .m-drawer into EVERY secondary
   page regardless of width — hide both on desktop; the mobile block below
   reveals them. Without this the stray burger button and the drawer's link
   list paint on wide screens too. */
.m-burger2, .m-drawer { display: none; }

@media (max-width: 760px) {
  :root { --pad: 6vw; }

  /* --- header: swap the horizontal nav for a burger + slide-in drawer.
     The burger + .m-drawer are injected by js/mobile-nav.js. --- */
  .site-nav { display: none; }
  .site-head { padding: 0 var(--pad); height: 60px; }
  .brand__mark { width: 34px; height: 34px; }
  .brand__txt { font-size: 1.05rem; }

  .m-burger2 {
    width: 40px; height: 40px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
    background: none; border: none; cursor: pointer;
  }
  .m-burger2 span {
    display: block; width: 22px; height: 2px; background: var(--fg);
    transition: transform 0.3s ease, opacity 0.3s ease;
  }
  .m-burger2.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .m-burger2.is-open span:nth-child(2) { opacity: 0; }
  .m-burger2.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  .m-drawer {
    position: fixed; top: 60px; left: 0; right: 0; bottom: 0; z-index: 190;
    /* Same backdrop the home drawer (.m-nav in mobile.css) uses — the Product
       tab's blurred bakery under a cream veil + a warm brown layer. Was a flat
       var(--bg) cream here, so every secondary page opened a menu that looked
       unfinished next to home's. Keep the ?v= in step with main.css's own
       .product rule or the same photo downloads twice. */
    background:
      linear-gradient(180deg, rgba(48,24,14,0.2) 0%, rgba(48,24,14,0.26) 100%),
      linear-gradient(180deg, rgba(251,233,219,0.4) 0%, rgba(251,233,219,0.3) 45%, rgba(251,233,219,0.48) 100%),
      url('../img/product-bg.webp?v=178') center / cover no-repeat;
    padding: 1.6rem var(--pad) 2rem;
    display: flex; flex-direction: column; gap: 0.2rem;
    transform: translateX(100%);
    transition: transform 0.4s cubic-bezier(0.22,1,0.36,1);
    overflow-y: auto;
  }
  .m-drawer.is-open { transform: translateX(0); }
  .m-drawer a {
    font-size: 1.32rem; font-weight: 500; padding: 1.15rem 0;
    border-bottom: 1px solid rgba(58,14,24,0.22); color: var(--fg);
  }
  .m-drawer a.nav-cta {
    margin-top: 1.2rem; text-align: center;
    background: var(--teal); color: var(--bg) !important;
    padding: 1rem !important; border-radius: 2px; border-bottom: none;
  }
  /* WhatsApp FAB would peek through the fullscreen drawer (z 1000 > 190). */
  body.m-drawer-open .whatsapp-widget { display: none; }

  /* --- forms: two-up rows stack --- */
  .form__row { grid-template-columns: 1fr; }

  /* --- product page --- */
  .product, .product--page {
    grid-template-columns: 1fr;
    padding: calc(60px + 4vh) var(--pad) 8vh;
    gap: 2.5rem;
  }
  .product__media { max-width: 420px; margin: 0 auto; width: 100%; }
  .product-features { grid-template-columns: 1fr; gap: 0; }
  .product-features__item:nth-last-child(-n+2) { border-bottom: 1px solid rgba(116,26,47,0.16); }
  .product-features__item:last-child { border-bottom: none; }
  .product-features__icon { width: 68px; height: 68px; }
  .product-tabs__nav { gap: 1.4rem; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .product-tabs__nav button { white-space: nowrap; }
  .uses { grid-template-columns: 1fr; }

  /* --- partner page --- */
  .partner-hero__copy { padding: calc(8vh + 60px) var(--pad) 6vh; }
  .partner-form-section { padding: 6vh var(--pad) 10vh; }
  /* 72 -> 321px: at 72 the jump landed on the first field ("You are a"), with
     the "Request your order" heading already scrolled past (client). The anchor
     is the <form>, so the margin has to clear everything above it: the heading
     block is 245px tall here plus the 60px fixed header, +16 to breathe. */
  #order-form { scroll-margin-top: 321px; }
  /* background-attachment:fixed jitters against the URL-bar show/hide on
     phones — the blurred warehouse photo "jumped" while scrolling up/down.
     Pin it to normal flow so it scrolls with the page (client report). */
  .partner-main::before { background-attachment: scroll; }
  /* Nudge the hero photo up so it tucks ~10px under the top menu bar. */
  .partner-hero__media { padding-top: 66px; }

  /* --- delivery / process / products on any page that carries them --- */
  .arch__img { height: 52vw; }

  /* --- contact page --- */
  /* Full screen on a phone (client): at 70vh the bread cards of the next
     section poked into the first screen under the video, which read as "bits of
     bread sticking out". lvh, not svh/dvh — the LARGE viewport height keeps the
     video covering the screen even when the URL bar hides, instead of letting
     the next section slide into view. */
  .contact-hero { min-height: 100lvh; padding: calc(60px + 4vh) var(--pad) 6vh; }
  .contact-cards { grid-template-columns: 1fr; }
  /* Bread cards: three identical loaves — two on top, one centred below (same
     size as the top two). Content is vertically centred and comfortably sized
     so it sits evenly WITHIN the loaf, the wine outline framing it inside the
     bread. Section padding + gap trimmed so each loaf is as wide as possible,
     giving the long email room to fit the outline (client request). */
  .contact-cards--bread {
    padding: 0 0.4rem 6vh;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.4rem; justify-content: center; max-width: none; margin: 0;
  }
  .contact-cards--bread .contact-card--bread {
    aspect-ratio: 3 / 4.35;
    padding: 2.4rem 0.9rem 2rem; gap: 0.28rem;
    justify-content: center;
  }
  /* Bigger icons and copy inside the loaf (client). The wine outline is the
     hard limit: it is drawn 300x400 fitted to the card width, so its inner
     span is ~0.85 * card width (~157px on a 390 phone) — every line below is
     sized to stay inside that. The email is the binding constraint (21
     monospace characters), so it grows least; the icon, label and heading —
     which are short — grow a lot. */
  .contact-cards--bread .contact-card--bread .contact-card__icon { height: 3rem; font-size: 2.45rem; margin-bottom: 0.1rem; }
  /* Tracking cut from 0.14em: at the bigger size the longest label
     ("Get in touch now") ran into the wine outline on its own tracking. */
  .contact-cards--bread .contact-card--bread .contact-card__label { font-size: 0.8rem; letter-spacing: 0.06em; }
  .contact-cards--bread .contact-card--bread h3 { font-size: 1.5rem; margin: 0.1rem 0 0.3rem; }
  /* Fluid, because the loaf is fluid: the 21-character email is exactly as wide
     as its font, while the outline it must stay inside is half the screen. A
     fixed size that clears the frame on a 390 phone grazes it on a 360 one. */
  .contact-cards--bread .contact-card--bread a {
    font-size: min(0.78rem, calc(3.05vw - 0.5px)); letter-spacing: -0.015em;
  }
  /* 16ch, not 14: the address then breaks into three lines instead of four, so
     the third loaf keeps exactly the same height as the other two. */
  .contact-cards--bread .contact-card--bread .contact-card__line {
    font-size: min(0.9rem, calc(3.7vw - 0.3px)); max-width: 16ch;
  }
  .contact-cards--bread .contact-card--bread:nth-child(3) {
    grid-column: 1 / -1; width: calc(50% - 0.2rem); justify-self: center;
  }
  .contact-map__frame iframe { aspect-ratio: 3 / 4; min-height: 300px; }

  /* --- blog listing --- */
  .blog-hero { padding: calc(72px + 3vh) var(--pad) 5vh; }
  /* Two books per row on the shelf (the 1000px rule drops to 1-up; a phone
     wants the tidy 2×2 back, just smaller — client request). */
  .blog-books {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2.4rem 0.9rem;
    padding: 0 var(--pad) 3vh;
  }
  /* Closed cover: shrink the title/tag so it fits the narrower book. */
  /* Sits lower on the cover (client): 0.7rem left ~12px of dead space under the
     last line on a 276px card. */
  /* The side inset scales with the cover for the same reason the type does
     (see the h2 block below). Left as a fixed 0.7rem it ate a bigger share of a
     narrow cover than of a wide one — the text column shrank FASTER than the
     font, so book 3 still wrapped to 6 lines at 320px even after the type went
     fluid. Both fluid = the column-to-type ratio is constant (10.1) at every
     width, and the shelf holds 5 lines everywhere. 2.872vw == 0.7rem at 390. */
  .blog-book__coverbody {
    left: 2.872vw; right: 2.872vw;
    bottom: 0.35rem; gap: 0.3rem;
  }
  /* Same "one line count for the whole shelf" rule as desktop, but the target
     here is 5 lines (client) and the column is only ~140px, so every cover
     needs its own size — at a shared 1.02rem the shelf came out 6 / 7 / 4 / 5.
     Each value is the middle of the measured band that renders 5 lines.

     ⚠️ min(rem, vw) — NOT a bare rem. The rem values above were tuned against a
     390px-wide phone, where a cover column is 164px. A rem does not know that:
     on a 375 / 360 / 320 screen the column shrinks to 158 / 151 / 134px while
     the type stays 14px, so the titles rewrapped to 6 and 7 lines and sprawled
     up over the cover art (measured 2026-07-28 across 320-430; the client saw
     it in the Threads in-app browser). The vw term is each rem value divided by
     390, so at 390 the size is IDENTICAL to before — nothing changes on the
     width everything was tuned on — and below it the type tracks the cover.
     min() and not clamp(): above 390 the sizes stay put, as tuned. */
  /* ⚠️ TWO THINGS HERE ARE LOAD-BEARING — measured on a real phone, not guessed.

     (a) SAFETY MARGIN. The first fluid pass sat right on the edge: measured with
     find_band, the largest size that still holds 5 lines was 3.862 / 3.944 /
     3.666 / 4.559vw, and book 3 was running at 3.569vw — 2.7% of headroom. A
     real device lays text out fractionally wider than headless Chrome does, ate
     that 2.7%, and the client got 6 lines on his phone while every local
     measurement said 5. Every book now runs at ~89% of its own limit (book 3 at
     93%), so the same rendering difference cannot push any of them over.

     (b) SHARED line-height, as a LENGTH, not a multiplier. .blog-book__coverbody
     is anchored to the bottom of the cover and grows upward, so the tag above the
     headline sits wherever the headline's height leaves it. With line-height:1.14
     the four books had four different line heights, so five lines came to four
     different block heights and the "BAKERY - N MIN READ" tags sat 2-3px apart
     across the shelf (client spotted it between books 1 and 2). One fixed length
     = five lines is the same height on every book = the tags line up. It does not
     affect where the text WRAPS, only how tall the lines are. */
  /* Each book sits in the MIDDLE of its own measured band, not at the edge.
     find_band2 walked every headline at 320/360/375/390 and found the range of
     sizes that renders exactly 5 lines — below it the headline drops to 4, above
     it spills to 6:
         book 1  3.221 .. 3.862vw      book 3  3.354 .. 3.666vw   <- narrowest
         book 2  3.364 .. 3.944vw      book 4  3.595 .. 4.559vw
     The first fluid pass put book 3 at 3.569vw — 2.7% under its ceiling — and a
     real phone, which lays text out a hair wider than headless Chrome, tipped it
     to 6 lines on the client's screen while every local run said 5. Sitting mid-
     band gives every book margin on BOTH sides instead.
     Book 4 is deliberately the largest: its headline is the shortest, so under
     3.595vw it falls to 4 lines, and then its BAKERY tag sits 15px above its
     neighbour's (the tags are bottom-anchored, so equal line COUNT is what keeps
     the shelf level). Bigger type is the cheaper of the two evils.

     line-height is a shared LENGTH, not a multiplier: .blog-book__coverbody is
     anchored to the bottom of the cover and grows upward, so with four different
     line heights, five lines came to four different block heights and the tags
     sat 2-3px apart across the shelf (the client spotted it between books 1 and
     2). One length = five lines is one height everywhere = the tags line up. It
     changes only how tall the lines are, never where the text wraps. */
  /* Pure vw inside this block, no rem ceiling. Freezing the size above 390px was
     what made book 3 fall to 4 lines at 412px while its neighbours held 5, which
     puts its tag 16px off the shelf line again. The ratio is what governs the
     line count, and it is safe at every width: the measured ceiling RISES with
     the screen (book 1: 3.862vw at 320 -> 3.931vw at 390), because the cover's
     rem-sized side padding eats a smaller share of a wider cover. */
  .blog-book__coverbody h2 { font-size: 4.02vw; line-height: 4.179vw; }
  .blog-books .blog-book:nth-of-type(1) .blog-book__coverbody h2 { font-size: 3.542vw; }
  .blog-books .blog-book:nth-of-type(2) .blog-book__coverbody h2 { font-size: 3.654vw; }
  /* 1.22rem -> 0.87rem after book 3's headline was lengthened (it had to grow
     so that on DESKTOP it fills 4 lines at the shared size instead of being
     scaled up). Measured band for 5 lines here is 0.82-0.90rem; 0.87 is the
     middle. */
  .blog-books .blog-book:nth-of-type(3) .blog-book__coverbody h2 { font-size: 3.510vw; }
  /* 1.03rem -> 0.88rem: at the old value book 4 read visibly bigger than its
     neighbours (16.5px against 14 / 14.6 / 13.9) and the client spotted it.
     0.88rem lands in the same band AND still holds the shelf's 5 lines
     (measured: 5L with 151px of headroom). */
  .blog-books .blog-book:nth-of-type(4) .blog-book__coverbody h2 { font-size: 3.850vw; }
  /* Same reasoning as the headlines: the tag is a fixed 8.96px at 390. */
  .blog-book__coverbody .leaf__tag { font-size: 2.297vw; }
  /* Open flip-book: the desktop 38vw-per-page cap makes each page only
     ~125px on a phone (text cramped, per-page scrollbars). Widen so the
     two-page spread nearly fills the screen (47vw each ≈ 94vw spread),
     capped by height through the 232:336 page ratio. Re-center for the
     shorter 60px mobile header (+30px = half its height, vs +54 on
     desktop's taller one). */
  /* Document-anchored, not viewport-anchored: pinch-zoom pans the visual
     viewport but leaves position:fixed glued to the layout viewport, so a
     zoomed-in book used to slide away from its backdrop and expose the strip
     photo behind. --book-top is written by js/blog.js when the book opens. */
  .blog-book.is-open {
    position: absolute;
    top: var(--book-top, calc(50% + 30px));
    width: min(47vw, calc((100svh - 130px) * 232 / 390));
  }
  .blog-book-backdrop { position: absolute; }
  /* Taller page than the desktop 232:336 — at phone width the spread was too
     short for the article and each page grew its own scrollbar. */
  .blog-book { aspect-ratio: 232 / 390; }
  /* Nothing may stay position:fixed while a book is open, or it detaches from
     the zoomed content and jumps (js/blog.js sets the class). */
  body.is-book-open .site-head,
  body.is-book-open .whatsapp-widget { display: none; }
  /* Pinch-zoom re-rasterises every painted layer at the new scale, and while
     you pan the tiles come back one by one — that is the flicker. Everything
     behind the opaque backdrop is invisible anyway, so stop painting it: the
     strip photo, the other covers, the hero and the CTA. */
  body.is-book-open .blog-strip::before,
  body.is-book-open .blog-strip > *:not(.blog-book-backdrop) { visibility: hidden; }
  body.is-book-open .blog-book.is-open { visibility: visible; }
  /* Once the flip has settled there is no 3D left to show, so drop the
     perspective/backface layers too — they are the other thing that blinks. */
  .blog-book__leaf.is-flat { perspective: none; }
  .blog-book__leaf.is-flat > .blog-book__face { backface-visibility: visible; }
  /* SAME PROBLEM ON THE CLOSED SHELF (client: "the books blink when I scroll
     the blog"). Four books x 5 leaves x 2 backface-hidden faces inside a
     perspective = 40 compositor surfaces on one screen; a phone drops and
     re-rasterises them as you scroll, and the covers flash. Nothing but the
     top cover is visible on a closed book, so nothing else is rendered until
     the book opens (.is-open drops the rule and hands everything back for the
     page-turn).
     NOTE: the 3D itself has to STAY on a closed book — the two faces of a leaf
     sit on top of each other and only backface-visibility inside a preserve-3d
     context hides the back one. Flattening it painted the mirrored page text
     straight through the cover. What can go is everything that is not the front
     cover: the four buried leaves and the cover leaf's own back face. That is
     40 painted faces down to 4.
     The state is .is-quiet, NOT :not(.is-open): closing a book drops .is-open
     the instant you tap, while the leaves are still flipping shut for another
     ~0.9s — hiding them right then swallowed the closing page-turn. js/blog.js
     adds .is-quiet only once a book has actually settled.
     The rule itself now lives OUTSIDE this phone block (search .blog-book.is-quiet
     above) — a desktop repaints the same 40 surfaces and the client saw books 3
     and 4 stir as they scrolled into view. */
  /* Each of the five stacked leaves carries a wine drop-shadow. On the shelf
     that reads as depth; over the near-black backdrop the wine is LIGHTER than
     the background, so five overlapping shadows became a reddish smear around
     the open book. Off while a book is open — the pale pages stand out on
     their own. */
  body.is-book-open .blog-book__leaf { box-shadow: none; }
  /* Open-page type scaled down so the full article text fits each small page
     without its own scrollbar (client: "text should fit when the book opens"). */
  /* The article has to fit the page without the book growing into a ribbon
     (client), so the type does the work: tighter leading, tighter padding and
     smaller gaps between the blocks. */
  .blog-book__face--page {
    padding: 0.7rem 0.75rem 1.05rem;
    font-size: 0.66rem; line-height: 1.36; gap: 0.32rem;
  }
  /* em, not rem: js/blog.js sizes each page individually (fitPageType) so the
     article fills its page, and everything on the page has to scale with it. */
  /* Page headings drop the serif display face on phones. At the ~11px the
     pages run here, Fraunces next to Work Sans stopped reading as a heading
     and started reading as a second, mismatched font (client: "looks like two
     different fonts inside the pages, it's ugly"). Desktop pages are twice
     this size, where the serif/sans pairing works, so it keeps it. */
  .blog-book__face--page h3 {
    font-size: 1.394em; line-height: 1.1;
    font-family: var(--font-mono); font-weight: 600; letter-spacing: 0;
  }
  /* Two lines' worth of room, always: a one-line tag ("Practical tips") let its
     list start 13px higher than the page opposite, whose tag wrapped to two
     lines ("How hard wheat flour helps"), so the first bullet of a spread never
     lined up with the first bullet facing it (client). Reserving the taller of
     the two shapes parks every page's body on the same line. */
  .blog-book__face--page .leaf__tag { font-size: 0.879em; min-height: 2.74em; }
  .blog-book__face--page ul,
  .blog-book__face--page ol { padding-left: 0.9rem; gap: 0.3rem; }
  .blog-book__hint { font-size: 1em; }
  .blog-book__by { font-size: 0.82rem; }
  .blog-book__face--page::after {
    width: 1.2rem; height: 1.2rem; font-size: 0.68rem; bottom: 0.35rem; right: 0.4rem;
  }

  /* --- legal pages --- */
  .legal-hero { padding: calc(72px + 3vh) var(--pad) 3vh; }
  /* Legal pages on a phone: the background photo showed through the light wash
     and made the body text hard to read — blur it, lift the cream wash, and
     drop the fixed attachment (URL-bar jitter). inset:-12px hides the blurred
     edge's transparent halo. */
  .legal-main::before {
    background:
      linear-gradient(180deg, rgba(251,233,219,0.74) 0%, rgba(251,233,219,0.68) 45%, rgba(251,233,219,0.8) 100%),
      url('../img/legal-bg-56.webp') center / cover no-repeat;
    background-attachment: scroll;
    filter: blur(5px);
    inset: -12px;
  }

  /* --- footer: stack columns --- */
  .foot__grid { grid-template-columns: 1fr 1fr; gap: 2rem 1rem; }
  .foot__cta { padding: 8vh var(--pad); }
  .foot-lockup { width: min(76vw, 320px); }

  /* --- generic heading scale-down so big clamp()s don't dominate --- */
  h2 { font-size: clamp(1.8rem, 8vw, 2.6rem); }
}

@media (max-width: 460px) {
  /* Brand full width on top, then Quick Links + Resources share one row (they
     were eating a lot of vertical space stacked), Contact full width below. */
  .foot__grid { grid-template-columns: 1fr 1fr; gap: 1.8rem 1.2rem; }
  .foot__col--brand { grid-column: 1 / -1; }
  .foot__col:nth-child(4) { grid-column: 1 / -1; }
}
