/*  Leaf & Bloom - Homepage sections
 *  ---------------------------------------------------------------------------
 *  Loads on the front page only (front-page.php). Section order and intent
 *  follow spec 3.2 - every section earns its place.
 *  ------------------------------------------------------------------------- */

/* == 3. Hero - 3-second brand comprehension =============================== */
.hero {
  position: relative;
  display: grid;
  align-items: end;
  min-height: 78svh;
  /* Pulled up under the sticky transparent header - see base.css. */
  margin-top: calc(var(--header-h) * -1);
  padding-block: calc(var(--header-h) + var(--sp-10)) var(--sp-10);
  isolation: isolate;
  /* Botanical gradient fallback: paints instantly, and still reads correctly
     if the owner never uploads a hero image. */
  background:
    radial-gradient(120% 90% at 78% 12%, color-mix(in srgb, var(--c-ground-3) 55%, transparent), transparent 62%),
    linear-gradient(168deg, var(--c-ground), var(--c-ground-2) 46%, var(--c-ground-3) 100%);
}
@media (min-width: 1024px) { .hero { min-height: 86svh; } }

.hero__media { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }
/* Scrim keeps the headline AA over any photograph the owner uploads. */
.hero__media::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--c-ground) 42%, transparent) 0%,
    color-mix(in srgb, var(--c-ground) 20%, transparent) 38%,
    color-mix(in srgb, var(--c-ground) 74%, transparent) 100%);
}

/*  Editorial split: copy on the left, the real product in the logo's arch on
 *  the right. The arch is the one shape carried straight off the logo, so the
 *  hero reads as the brand rather than as a stock template. */
/* -- dappled light -------------------------------------------------------
 *  A grid lies over the hero, barely there. Where the pointer goes, the cells
 *  under it warm to gold and fade out again behind it — light finding its way
 *  through a canopy, which is the one metaphor this brand already owns.
 *
 *  It is built out of masks rather than a canvas: three mask layers
 *  intersected — vertical gutters, horizontal gutters, and a soft disc at the
 *  pointer. The intersection is a patch of lit cells, and the browser
 *  composites it on the GPU, so the only thing script does per frame is write
 *  two numbers. No canvas, no per-cell loop, nothing to drop frames.
 *
 *  Everything here is additive: with no JavaScript the disc simply stays where
 *  it started, and the hero is exactly what it was before.
 *  ---------------------------------------------------------------------- */
.hero__dapple {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;          /* never in the way of the buttons beneath */
  overflow: hidden;

  --cell: 68px;
  --gutter: 3px;
  --reach: 260px;
  /*  Where the light is. Script moves these; this is where it rests. */
  --mx: 32%;
  --my: 46%;
}

/*  1. The grid itself, always present, only just visible. */
.hero__dapple::before {
  content: '';
  position: absolute;
  inset: -1px;
  background-image:
    linear-gradient(to right,  color-mix(in srgb, var(--c-leaf) 15%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--c-leaf) 15%, transparent) 1px, transparent 1px);
  background-size: var(--cell) var(--cell);
  /*  Faint on purpose. The grid is the thing the light lands on, not a
   *  feature of its own — the moment it competes with the headline it has
   *  stopped being texture and started being decoration. */
  opacity: .3;
}

/*  2. The lit cells. Guarded, because a browser without mask-composite would
 *     paint the whole rectangle gold rather than a few squares of it. */
@supports ((mask-composite: intersect) or (-webkit-mask-composite: source-in)) {
  .hero__dapple::after {
    content: '';
    position: absolute;
    inset: -1px;
    background:
      radial-gradient(circle var(--reach) at var(--mx) var(--my),
        color-mix(in srgb, var(--c-gold-lit) 66%, transparent) 0%,
        color-mix(in srgb, var(--c-leaf) 34%, transparent) 55%,
        transparent 100%);

    --v: repeating-linear-gradient(to right,  #000 0 calc(var(--cell) - var(--gutter)), transparent calc(var(--cell) - var(--gutter)) var(--cell));
    --h: repeating-linear-gradient(to bottom, #000 0 calc(var(--cell) - var(--gutter)), transparent calc(var(--cell) - var(--gutter)) var(--cell));
    --disc: radial-gradient(circle var(--reach) at var(--mx) var(--my), #000 0%, #000 30%, transparent 72%);

    -webkit-mask-image: var(--v), var(--h), var(--disc);
            mask-image: var(--v), var(--h), var(--disc);
    -webkit-mask-composite: source-in;
            mask-composite: intersect;

    transition: opacity var(--dur-3) var(--ease-settle);
  }
}

/*  The pointer has to have left the building for this to matter, so it fades
 *  rather than snapping off. */
.hero:not(.is-lit) .hero__dapple::after { opacity: 0; }

/*  Touch has no hovering pointer to follow, and a patch of light sitting in
 *  one arbitrary place would just look like a smudge. */
@media (hover: none), (pointer: coarse) {
  .hero__dapple::after { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__dapple::after { display: none; }
  .hero__dapple::before { opacity: .35; }
}

.hero__grid { position: relative; z-index: 1; display: grid; gap: var(--sp-8); align-items: center; }
/*  1.2fr keeps "Rooted in nature," on one line at laptop widths; at 1.05fr the
 *  headline broke into three lines and the copy box grew down over the hero's
 *  leaf artwork, which test:dapple then measured under the text. */
@media (min-width: 900px) { .hero__grid { grid-template-columns: 1.2fr .8fr; gap: var(--sp-10); } }

/*  The product, staged. Shown on phones too (most visitors are on one), smaller
 *  and under the copy. Colours here are fixed rather than tokens where they sit
 *  on the hero ground, which does not invert in dark mode. */
.hero__shot {
  position: relative;
  justify-self: center;
  width: min(220px, 62vw);
  margin: var(--sp-6) auto var(--sp-4);
}
@media (min-width: 900px) { .hero__shot { width: min(300px, 100%); margin: 0 auto; } }

/*  The staging is allowed to spill past the photo (leaves, cards, glow) but never
 *  past the hero: on a 360px phone that spill made the whole page scroll sideways. */
.hero { overflow-x: clip; }

/*  Fixed colours, not tokens: --c-gold brightens in dark mode, and a brighter
 *  glow reached under the headline and took it below AA (test:dapple). Kept to
 *  140% so its visible edge stays inside the product column. */
.hero__glow {
  position: absolute; left: 50%; top: 48%; z-index: 0;
  width: 140%; aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(closest-side,
    rgba(142, 142, 48, .38) 0%,
    rgba(124, 145, 89, .16) 50%,
    transparent 74%);
  pointer-events: none;
}
.hero__shot .arch {
  position: relative; z-index: 1;
  border: 1.5px solid color-mix(in srgb, var(--c-gold) 75%, transparent);
  background: var(--c-well);
  box-shadow:
    0 0 0 7px color-mix(in srgb, var(--c-ground) 55%, transparent),
    0 0 0 8px color-mix(in srgb, var(--c-gold) 35%, transparent),
    0 34px 60px -18px rgba(10, 16, 4, .65);
}
.hero__shot .arch img { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }

.hero__leaf {
  position: absolute; z-index: 2; height: auto; pointer-events: none;
  filter: drop-shadow(0 10px 12px rgba(10, 16, 4, .45));
}
.hero__leaf--rosemary { width: 62%; left: -34%; bottom: -8%; transform: rotate(-16deg); }
.hero__leaf--aloe     { width: 48%; right: -28%; bottom: -5%; transform: rotate(14deg); }
.hero__leaf--curry    { width: 42%; right: -26%; top: 4%;   transform: rotate(38deg); }

.hero__badge {
  position: absolute; z-index: 3;
  display: grid; gap: 1px;
  padding: 10px 16px;
  border-radius: 16px;
  background: #FFFDF7;
  color: #26370B;
  font-size: var(--fs-micro); letter-spacing: var(--ls-wide); text-transform: uppercase; line-height: var(--lh-snug);
  box-shadow: 0 14px 30px -10px rgba(10, 16, 4, .55), inset 0 0 0 1px rgba(142, 142, 48, .35);
  white-space: nowrap;
}
.hero__badge strong {
  font-family: var(--ff-display); font-size: var(--fs-h3); font-weight: 600;
  letter-spacing: 0; text-transform: none; color: var(--c-forest);
  font-variant-numeric: lining-nums;
}
.hero__badge--a { left: -30%; top: 26%; }
.hero__badge--b { right: -30%; top: 56%; }
@media (max-width: 899px) {
  .hero__badge { padding: 8px 12px; }
  .hero__badge strong { font-size: var(--fs-h4); }
  .hero__badge--a { left: -26%; }
  .hero__badge--b { right: -26%; }
}

.hero__shot figcaption {
  position: relative; z-index: 2;
  margin-top: var(--sp-6);
  text-align: center;
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-gold);
}

/*  Trust line under the buttons: three confirmed facts, quiet, one row on desktop. */
.hero__trust {
  list-style: none; margin: var(--sp-6) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5);
  font-size: var(--fs-small); color: var(--c-on-brand);
}
.hero__trust li { display: inline-flex; align-items: center; gap: var(--sp-2); opacity: .92; }
.hero__trust svg { width: 18px; height: 18px; flex: none; color: var(--c-gold); }

/*  The primary button was forest green on a forest-green hero and all but
 *  disappeared. On the hero it takes the cream fill instead. */
.hero__cta .btn--primary { background: var(--c-on-brand); border-color: var(--c-on-brand); color: var(--c-ground); }
.hero__cta .btn--primary:hover { background: #FFFFFF; border-color: #FFFFFF; color: var(--c-ground); }

.hero__inner { position: relative; z-index: 1; max-width: 640px; color: var(--c-on-brand); }

/* == The rotating line ====================================================
 *  The second line of the headline changes every few seconds. "Rooted in
 *  nature," holds; what the brand is FOR is what rotates.
 *
 *  Two rules make this safe rather than annoying:
 *  1. The box is sized to the longest phrase before anything animates, so a
 *     longer line can never push the paragraph below it down. No layout shift.
 *  2. Only one phrase is ever in the accessibility tree. A screen reader is
 *     read one headline, not four stacked on top of each other, and there is
 *     no aria-live — a headline quietly rotating is not an announcement.
 *  ---------------------------------------------------------------------- */
.rotator {
  display: inline-grid;          /* every phrase in the same grid cell */
  vertical-align: top;
  text-align: left;
}
.rotator__item {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(var(--rise-sm));
  transition: opacity var(--dur-5) var(--ease-settle),
              transform var(--dur-5) var(--ease-settle);
}
/*  The first phrase is visible before any script runs, so with JavaScript off
 *  the headline is simply a headline. */
.rotator__item:first-child { opacity: 1; transform: none; }
.rotator__item.is-on { opacity: 1; transform: none; }
.rotator__item.is-off { opacity: 0; transform: translateY(calc(var(--rise-sm) * -1)); }

@media (prefers-reduced-motion: reduce) {
  /* No rotation at all: one line, held. Changing text is motion too. */
  .rotator__item { transition: none; }
  .rotator__item:not(:first-child) { display: none; }
}
.hero .eyebrow { color: var(--c-gold); }
.hero .title-hero { color: var(--c-on-brand); }
.hero__tag {
  font-family: var(--ff-display);
  font-style: italic;
  font-size: var(--fs-lead-lg);
  color: var(--c-gold);
  margin-top: var(--sp-3);
}
.hero__promise { margin-top: var(--sp-4); font-size: var(--fs-lead); color: var(--c-on-brand); opacity: .92; max-width: 46ch; }
.hero__cta { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-7); }
/* On the hero the secondary button sits on imagery, so it takes the cream outline. */
.hero__cta .btn--secondary { color: var(--c-on-brand); border-color: var(--c-on-brand); }
.hero__cta .btn--secondary:hover { background: var(--c-on-brand); color: var(--c-ground); }

/*  Thin arch flourish echoing the logo, drawn under the hero.
 *  48px tall, not 90: once the hero gained its trust line the copy box reached
 *  down into a 90px arch, and in dark mode (where --c-gold brightens) that 1px
 *  gold line was the brightest thing under the text - test:dapple failed on it.
 *  The colour is fixed for the same reason: the hero ground does not invert. */
.hero__arch {
  position: absolute; left: 50%; bottom: -1px; z-index: 0;
  width: min(760px, 92vw); height: 48px;
  transform: translateX(-50%);
  border-top: 1px solid rgba(142, 142, 48, .55);
  border-radius: 50% 50% 0 0 / 100% 100% 0 0;
  pointer-events: none;
}

/* == 3b. Hero slideshow ===================================================
 *  Three slides stacked in one grid cell (the hero keeps the height of the
 *  tallest, so nothing below ever moves). Grounds cross-fade underneath; the
 *  leaf artwork, the arch and the dappled light sit above the grounds and below
 *  the copy. Hero colours are fixed values, not tokens: this ground never
 *  inverts in dark mode.
 *  ---------------------------------------------------------------------- */

/*  +1px: the overlay header keeps a 1px (transparent) border, so pulling the
 *  hero up by --header-h alone left a 1px strip of page under the announcement
 *  bar - the thin light line under it. */
.hero { margin-top: calc((var(--header-h) + 1px) * -1); background: #26370B; }

/*  On the homepage the announcement bar takes the colour of the slide showing. */
.announcement { background: var(--hero-bar, #30460F); transition: background-color 700ms var(--ease-settle); }

.hero__grounds { position: absolute; inset: 0; z-index: -3; }
.hero__ground { position: absolute; inset: 0; opacity: 0; transition: opacity 900ms var(--ease-settle); }
.hero__ground.is-on { opacity: 1; }
.hero__ground--forest {
  background: radial-gradient(80% 70% at 78% 32%, rgba(124, 145, 89, .22), transparent 62%),
              linear-gradient(165deg, #34500F 0%, #2A3D0C 55%, #1E2C08 100%);
}
/*  The three slide grounds.
 *
 *  These used to be three different colour worlds — forest green, then a
 *  maroon for the lip balm, then a mustard brown for the delivery slide —
 *  on the theory that each slide should take its product's colour. Rendered,
 *  the owner's verdict was that it did not look professional, and the reason
 *  is structural rather than a matter of taste: a hero that changes hue every
 *  six seconds reads as three different shops, and #6A4A12 in particular is a
 *  muddy brown that appears nowhere else in this brand.
 *
 *  One colour world now, and it is the brand's own. The three grounds are the
 *  same deep forest at three different temperatures — a shade warmer for the
 *  lip balm, a shade deeper for the delivery slide — so the hero shifts
 *  rather than changes. The PRODUCT is what changes between slides; the shop
 *  stays the same shop.
 *  ---------------------------------------------------------------------- */
.hero__ground--berry {
  background: radial-gradient(80% 70% at 78% 32%, rgba(214, 209, 122, .14), transparent 62%),
              linear-gradient(165deg, #3A4A18 0%, #2C3A0E 55%, #1E2A08 100%);
}
.hero__ground--amber {
  background: radial-gradient(80% 70% at 78% 32%, rgba(214, 209, 122, .16), transparent 62%),
              linear-gradient(165deg, #2E4310 0%, #24350B 55%, #182306 100%);
}
/*  hero.svg is leaves and a line only now; the scrim is neutral so it does not
 *  tint the berry and amber grounds green. The green leaves step back on those. */
.hero__media::after { background: linear-gradient(180deg, rgba(0, 0, 0, .12) 0%, transparent 40%, rgba(0, 0, 0, .18) 100%); }
.hero__media { transition: opacity 900ms var(--ease-settle); }
.hero[data-slide="2"] .hero__media,
.hero[data-slide="3"] .hero__media { opacity: .4; }

.hero__slides { position: relative; z-index: 1; display: grid; }
.hero__slide { grid-area: 1 / 1; align-self: end; }
/*  No JavaScript: one slide, the first. */
.hero__slide:not(:first-child) { display: none; }
.js .hero__slide:not(:first-child) { display: block; }
.js .hero__slide {
  opacity: 0; visibility: hidden;
  transition: opacity 700ms var(--ease-settle), visibility 0s linear 700ms;
}
.js .hero__slide.is-on { opacity: 1; visibility: visible; transition: opacity 700ms var(--ease-settle), visibility 0s; }

/*  Per-slide accent for the small gold-ish text, chosen to stay AA on each ground. */
.hero__slide { --hero-accent: #D6D17A; }
.hero__slide--berry { --hero-accent: #E4DE95; }
.hero__slide--amber { --hero-accent: #E4DE95; }
.hero__slide .eyebrow,
.hero__slide .hero__tag,
.hero__slide .hero__trust svg,
.hero__slide .hero__shot figcaption { color: var(--hero-accent); }
.hero__slide--berry .hero__glow { background: radial-gradient(closest-side, rgba(214, 209, 122, .26) 0%, rgba(214, 209, 122, .09) 50%, transparent 74%); }
.hero__slide--amber .hero__glow { background: radial-gradient(closest-side, rgba(214, 209, 122, .26) 0%, rgba(214, 209, 122, .09) 50%, transparent 74%); }
.hero__slide--berry .hero__cta .btn--primary,
.hero__slide--berry .hero__cta .btn--primary:hover,
.hero__slide--berry .hero__cta .btn--secondary:hover { color: #26370B; }
.hero__slide--amber .hero__cta .btn--primary,
.hero__slide--amber .hero__cta .btn--primary:hover,
.hero__slide--amber .hero__cta .btn--secondary:hover { color: #26370B; }

/*  Second product on the pair slides: a smaller arch-shaped photo overlapping the main one. */
.hero__inset {
  position: absolute; z-index: 3;
  width: 46%; height: auto; aspect-ratio: 4 / 5; object-fit: cover;
  /*  top, not bottom: anchored from the bottom it sat over the caption under the arch. */
  left: -24%; top: 44%;
  border-radius: 50% 50% 14px 14px / 30% 30% 14px 14px;
  border: 3px solid #FFFDF7;
  box-shadow: 0 20px 40px -12px rgba(0, 0, 0, .55);
}
.hero__shot--pair .hero__badge--a { top: 14%; }
/*  On a phone the shot is only 220px wide; let the caption run past it on one line
 *  rather than break "250 ML" onto a second. */
@media (max-width: 899px) {
  .hero__shot figcaption { white-space: nowrap; margin-inline: -60px; }
}

/*  Dots + pause. Each control is a 44px target; the visible dot is drawn inside. */
.hero__controls { position: relative; z-index: 2; display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-5); }
.hero__dots { display: flex; }
.hero__dot { width: 44px; height: 44px; display: grid; place-items: center; padding: 0; border: 0; background: none; cursor: pointer; }
.hero__dot::before {
  content: ''; width: 10px; height: 10px; border-radius: 999px;
  background: rgba(245, 242, 227, .45);
  transition: transform var(--dur-3) var(--ease-settle), background-color var(--dur-3) var(--ease-settle);
}
.hero__dot[aria-current="true"]::before { background: #F5F2E3; transform: scaleX(2.8); }
.hero__dot:focus-visible { outline: 2px solid #F5F2E3; outline-offset: -8px; border-radius: 999px; }
.hero__pause {
  width: 44px; height: 44px; display: grid; place-items: center;
  border-radius: 999px; border: 1px solid rgba(245, 242, 227, .5);
  background: transparent; color: #F5F2E3; cursor: pointer;
}
.hero__pause:hover { background: rgba(245, 242, 227, .12); }
.hero__pause svg { width: 16px; height: 16px; }
.hero__pause .icon-play { display: none; }
.hero__pause[aria-pressed="true"] .icon-pause { display: none; }
.hero__pause[aria-pressed="true"] .icon-play { display: block; }

@media (prefers-reduced-motion: reduce) {
  .js .hero__slide, .js .hero__slide.is-on, .hero__ground, .hero__media, .announcement { transition: none; }
}

/* == 4. Category tiles - wayfinding ====================================== */
.cats { display: grid; gap: var(--sp-4); }
@media (min-width: 768px) { .cats { grid-template-columns: repeat(2, 1fr); gap: var(--sp-6); } }

.cat {
  position: relative;
  display: grid; align-items: end;
  aspect-ratio: 3 / 4;
  padding: var(--sp-6);
  border-radius: var(--r-md);
  overflow: hidden;
  text-decoration: none;
  isolation: isolate;
}
@media (min-width: 768px) { .cat { aspect-ratio: 4 / 3; padding: var(--sp-7); } }
.cat img { position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%; object-fit: cover; transition: transform 500ms var(--ease); }
.cat::after {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, transparent 28%, color-mix(in srgb, var(--c-ground) 82%, transparent) 100%);
}
.cat:hover img { transform: scale(1.04); }
.cat__label { color: var(--c-on-brand); }
.cat__label h3 { font-size: var(--fs-h2-sm); color: var(--c-on-brand); }
.cat__label p { font-size: var(--fs-small); opacity: .9; margin-top: var(--sp-1); }
.cat__go {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-4);
  font-size: var(--fs-micro); letter-spacing: var(--ls-micro); text-transform: uppercase;
  color: var(--c-gold);
}
.cat__go svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.6; transition: transform var(--dur) var(--ease); }
.cat:hover .cat__go svg { transform: translateX(4px); }

/* == 6. Brand story band ================================================= */
/*  The split was 1fr / 1.05fr — 49/51, which is centred with extra steps and
 *  produces no tension at all. It is 38/62 now, and the picture runs off the
 *  left edge of the viewport instead of stopping politely at the column. That
 *  single escape is what stops the page reading as one long boxed list. */
.story { display: grid; gap: var(--sp-8); align-items: center; }
/*  The photograph used to bleed to the left edge of the window and was then
 *  capped at 360px tall, so the wider the screen the more extreme the crop:
 *  on a wide monitor the lavender jar came out as a letterbox strip of one
 *  enormous word, "LIP BALM", which is what the owner pointed at. A photo
 *  that changes what it shows with the width of the window is a photo
 *  nobody chose. It now sits in its own column at a fixed square, so the
 *  whole jar is in frame at every width. */
@media (min-width: 900px) {
  .story { grid-template-columns: minmax(0, 420px) 1fr; gap: var(--sp-10); }
  .story__body { max-width: 52ch; }
}
.story__media { position: relative; }
.story__media img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--r-lg); }
/* The logo's arch, used as the framing device the spec calls for. */
.story__media .arch { border: 1px solid color-mix(in srgb, var(--c-gold) 45%, transparent); }
/*  Six children at 24px apart spent 120px of the band on gaps alone. */
.story__body > * + * { margin-top: var(--sp-4); }

/* == 7. Benefits strip =================================================== */
/*  Moved to base.css — the shop page uses it too, and it does not load this
 *  file. Left as a signpost so the next person looks in the right place. */

/* == 9. Ingredients / quality - alternating rows ========================= */
/*  This section was 1,251px tall for two rows of a sentence each: two 5:4
 *  images at half of a 1240px wrap come out 456px tall, stacked with 80px
 *  between them. The images are now a landscape 16:10 and capped, the rows sit
 *  closer, and the text column is the wider of the two - so the words lead and
 *  the picture supports, rather than the other way round. */
.ingrow { display: grid; gap: var(--sp-5); align-items: center; }
@media (min-width: 900px) {
  .ingrow { grid-template-columns: 0.85fr 1fr; gap: var(--sp-8); }
  .ingrow + .ingrow { margin-top: var(--sp-8); }
  .ingrow--flip .ingrow__media { order: 2; }
}
.ingrow + .ingrow { margin-top: var(--sp-6); }
.ingrow__media img {
  width: 100%;
  aspect-ratio: 16 / 10;
  max-height: 300px;
  object-fit: cover;
  border-radius: var(--r-md);
}
.ingrow__body > * + * { margin-top: var(--sp-4); }

/* == Ingredients ========================================================
 *  Three passes on this section, each one because the owner said he could
 *  not follow it. First it was a sticky left caption that rewrote itself as
 *  each product scrolled past — a heading that changes while you read under
 *  it. Then it was a plain flowed list of every name, which fixed the
 *  changing part but left a wall of thirteen botanical words.
 *
 *  A wall is what it was. "Rosemary, Curry Leaves, Fenugreek Seed, Bay
 *  Leaves, Vegetable Glycerin, Green Tea, Black Seed, Vitamin E, Rose Water,
 *  Cloves, Distilled Water, Aloe Vera, Sodium Benzoate" tells a shopper
 *  nothing they can hold on to, because the one question behind reading a
 *  label — what IS this, mostly? — is not answered by the names alone.
 *
 *  Grouped, it answers in three seconds: seven plants, four things that
 *  make the liquid, two that keep it stable. Nothing is claimed and nothing
 *  is dropped; every printed name is still here, in its printed order. The
 *  grouping says what each name IS, which is a fact about the ingredient,
 *  never what it DOES, which would be a benefit this brand has not earned.
 *  ---------------------------------------------------------------------- */
/*  Three equal cards across the page rather than three rows down it. Stacked,
 *  the grouped lists measured 1,028px and left the right half of every row
 *  empty; side by side the three products are compared the way a shopper
 *  compares them, and the block is less than half as tall. */
.ingredients__rows { margin-top: 0; display: grid; gap: var(--sp-5); }
@media (min-width: 900px) { .ingredients__rows { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.ing-row {
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: var(--sp-4);
  align-content: start;
  padding: var(--sp-5);
  border-radius: var(--card-radius);
  background: var(--c-surface);
  border: var(--card-edge);
  box-shadow: var(--card-shadow);
}

/*  The photograph is not decoration: three lists of names in the same type
 *  at the same size are indistinguishable at a glance, and the thumbnail is
 *  what tells you which bottle you are reading before you read a word. */
.ing-row__thumb {
  width: 64px; height: 64px;
  object-fit: cover;
  border-radius: var(--r-md);
  background: var(--c-well);
  align-self: start;
}

.ing-row__body { display: contents; }
.ing-row__name {
  align-self: center;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  font-family: var(--ff-display);
  font-size: var(--fs-h4);
  line-height: var(--lh-snug);
  color: var(--c-heading);
}
.ing-row__body > :not(.ing-row__name) { grid-column: 1 / -1; }
/*  The count is checkable against the list beside it, which is the whole
 *  point of printing the list at all. */
.ing-row__count {
  font-family: var(--ff-sans);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-gold-ink);
}

/*  A description list, because that is what this is: a label and the things
 *  it covers. On a phone the label sits above its names; from 560px it sits
 *  beside them, so the left edge becomes a column you can read straight
 *  down — seven, four, two — without reading a single ingredient. */
.ing-group { margin: 0; display: grid; gap: var(--sp-1); padding-top: var(--sp-4); border-top: 1px solid var(--c-line); }
.ing-group dt {
  font-family: var(--ff-sans);
  font-size: var(--fs-body);
  font-weight: 500;
  color: var(--c-heading-deep);
  font-variant-numeric: tabular-nums;
}
.ing-group dd {
  margin: 0;
  /*  The names are the reason this section exists ('Listed in full'), so
   *  they are body size, not fine print. The preservation note and the
   *  footnote under the list stay small, and now read as footnotes. */
  font-size: var(--fs-body);
  color: var(--c-text-soft);
  line-height: var(--lh-copy);
}
.ing-group dd + dt { margin-top: var(--sp-3); }

.ing-group__note { display: block; font-size: var(--fs-nano); opacity: .8; }

.ing-row__note { margin-top: var(--sp-1); font-size: var(--fs-small); color: var(--c-text-soft); }

.ing-row__free {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin-top: var(--sp-1);
}
.ing-row__free span {
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-pill);
  font-size: var(--fs-micro);
  color: var(--c-text-soft);
}

/* == The ledger: an index, not an accordion ===============================
 *  Four questions in four collapsed boxes hide the answers behind four taps
 *  and four chevrons. As a two-column index every answer is already readable,
 *  which is the point of putting them on the homepage at all.
 *  ---------------------------------------------------------------------- */
/*  Four questions, each a 34%/66% row, stacked down a 1240px page — so the
 *  block ran 638px while each row used barely a third of its own width for
 *  the question and left the rest of the page empty either side of a 66%
 *  answer. On a wide screen the four rows sit two-up, which halves the
 *  height and puts the whole ledger on one screen where it can actually be
 *  read as an index. Below 1080px it goes back to one column, because two
 *  narrow columns of question-and-answer is worse than one wide one. */
.ledger { border-top: 1px solid var(--c-line); }
@media (min-width: 1080px) {
  .ledger {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: var(--sp-9);
  }
  /*  The top border belongs to the block, not to each column, so the second
   *  column draws its own rather than starting with a raw edge. */
  .ledger__row:nth-child(2) { border-top: 1px solid var(--c-line); }
}
.ledger__row {
  display: grid;
  gap: var(--sp-2);
  padding-block: var(--sp-5);
  border-bottom: 1px solid var(--c-line);
}
@media (min-width: 720px) and (max-width: 1079px) {
  .ledger__row { grid-template-columns: 34% 1fr; gap: var(--sp-7); align-items: baseline; }
}
.ledger__q {
  font-family: var(--ff-display);
  /*  Heading size, not h4. Cormorant's small x-height made an 18px question
   *  look smaller than the 16px Jost answer beside it; at the h3 step the
   *  question leads again. */
  font-size: var(--fs-h3);
  font-weight: 600;
  color: var(--c-heading);
}
/*  The answers are the point of the ledger, so they are body size. The
 *  --lh-body line-height was already set for body copy; only the size had
 *  been left at the small step. */
.ledger__a { font-size: var(--fs-body); color: var(--c-text-soft); line-height: var(--lh-body); }

/* == The delivered total ==================================================
 *  The one number a cash-on-delivery buyer actually needs and the site never
 *  showed: what they hand the courier. Stated plainly, never as a nudge.
 *  ---------------------------------------------------------------------- */
.delivered {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sp-2);
  margin-top: var(--sp-3);
  font-size: var(--fs-small);
  color: var(--c-text-soft);
}
.delivered strong { font-family: var(--ff-display); font-size: var(--fs-h4); color: var(--c-heading); font-weight: 600; }
.delivered__free { color: var(--c-ok); }

/* == 10. Reviews ========================================================= */
/*  The review card itself lives in base.css - the product page shows the same
 *  component, and only one of the two stylesheets loads at a time. */

/* == 12. Instagram grid ================================================== */
/*  The filmstrip variant: edge to edge, no gaps, no rounded corners. Six
 *  squares in a boxed grid with gutters read as stock imagery in a widget;
 *  butted together and running off both edges they read as a feed. */
.iggrid--film { grid-template-columns: repeat(3, 1fr); gap: 0; }
@media (min-width: 768px) { .iggrid--film { grid-template-columns: repeat(6, 1fr); gap: 0; } }
.iggrid--film a { border-radius: 0; }

.iggrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-2); }
@media (min-width: 768px) { .iggrid { grid-template-columns: repeat(6, 1fr); gap: var(--sp-3); } }
.iggrid a { position: relative; display: block; aspect-ratio: 1; border-radius: var(--r-sm); overflow: hidden; background: var(--c-well); }
.iggrid img { width: 100%; height: 100%; object-fit: cover; transition: transform 400ms var(--ease); }
.iggrid a:hover img { transform: scale(1.06); }
.iggrid a::after {
  content: ''; position: absolute; inset: 0;
  background: color-mix(in srgb, var(--c-ground) 30%, transparent);
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.iggrid a:hover::after { opacity: 1; }

/* == 13. The ending band =================================================
 *  The start of the ending rather than one more green strip. The band sits
 *  on --c-ground-mid, exactly where the footer's gradient begins, so band
 *  and footer read as one surface split by the footer's gold hairline. Like
 *  the footer it does not invert: --c-brand-fill went to a lighter olive in
 *  dark mode and the two stopped matching.
 *
 *  The button is gold: bright gold with --c-on-gold, 8.40:1. The focus ring
 *  is scoped to bright gold for the same reason as in the footer. The eyebrow
 *  opts out of the paragraph dimming, because bright gold at 90% is 4.45:1
 *  and at full strength 5.11:1.
 *
 *  The hover rule restates the label colour. base.css gives
 *  .section--brand .btn--primary:hover a --c-brand-fill-hover label, and
 *  that selector is as specific as this one, so without the restatement the
 *  dark theme's hover label was #587529 on pale gold: 2.86:1.
 *  ---------------------------------------------------------------------- */
.section--ending { background: var(--c-ground-mid); color: var(--c-on-dark); --c-focus: var(--c-gold-bright); }
/*  .ending was .newsletter until 24 Sep 2026. The band survived the email
 *  form that gave it its name, so the name went with the form rather than
 *  being left pointing at something the page no longer has. The three input
 *  rules went with it too: nothing on this band takes typing any more, and
 *  CSS for a control that does not exist is how a stylesheet stops being
 *  readable. The gold button rules stay exactly as they were - the dark-mode
 *  hover above them was measured at 2.86:1 once already and fixed, and that
 *  fix is what these two lines are.                                        */
.ending { display: grid; gap: var(--sp-6); align-items: center; }
@media (min-width: 900px) { .ending { grid-template-columns: 1fr 1fr; gap: var(--sp-10); } }
.ending .section-title, .ending p { color: var(--c-on-dark); }
.ending p { opacity: .9; }
.ending .eyebrow { color: var(--c-gold-bright); opacity: 1; }
.ending__actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.ending__actions .btn--primary { background: var(--c-gold-bright); border-color: var(--c-gold-bright); color: var(--c-on-gold); }
.ending__actions .btn--primary:hover { background: color-mix(in srgb, var(--c-gold-bright) 80%, var(--c-on-dark)); color: var(--c-on-gold); }
.ending__note { font-size: var(--fs-small); opacity: .8; margin-top: var(--sp-3); color: var(--c-on-dark); }

/* == FAQ teaser ========================================================== */
.faq-teaser { display: grid; gap: var(--sp-7); }
@media (min-width: 900px) { .faq-teaser { grid-template-columns: .8fr 1.2fr; gap: var(--sp-10); align-items: start; } }

/* == The ritual ==========================================================
 *  Three cards, one per product the shop sells, because the section directly
 *  above lists three and a two-card answer to "how do I use it" read as a
 *  product had been forgotten.
 *
 *  Each card opens on a deep band carrying the product's own photograph and
 *  its name, so the three are told apart before a word is read, which is the
 *  job the thumbnail already does in the ingredient cards above. The name is
 *  the same size as the name in those cards (--fs-h4): same role, same size,
 *  two sections apart. The band is --c-ground, the hero's colour, and it does
 *  not invert: the cream name measures 12.0:1 light and 11.0:1 dark, and the
 *  bright gold line under it 6.21:1 in both. --c-gold-bright never touches
 *  cream here.
 *
 *  The numbers are gold medallions threaded on a gold hairline. The forest
 *  circles they replace were the colour of every heading on the page, so the
 *  steps read as more text; gold says "these are in order" at a glance. The
 *  numeral on it is --c-on-gold, the Best Seller badge pair. The step text
 *  stays at body size, like every other paragraph inside a card.
 *  ---------------------------------------------------------------------- */
.ritual { display: grid; gap: var(--sp-5); }

.ritual__col {
  display: grid;
  grid-template-rows: auto 1fr auto;
  overflow: hidden;
  border: var(--card-edge);
  border-radius: var(--card-radius);
  background: var(--c-surface);
  box-shadow: var(--card-shadow);
}

.ritual__head {
  display: grid;
  grid-template-columns: var(--sp-10) minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: center;
  padding: var(--sp-4) var(--sp-5);
  background: var(--c-ground);
  /*  A gold edge rather than a grey one: the band ends where the steps
   *  begin, and it is the one line in the card that should look deliberate.
   *  In dark mode the band sits only a shade off the card, and this edge is
   *  what tells them apart. */
  border-bottom: 1px solid color-mix(in srgb, var(--c-gold-bright) 55%, transparent);
}
/*  4:5, the packshots' own shape, so no bottle is cropped to fit a square.
 *  The well stays light in both modes: product photos are never darkened. */
.ritual__photo {
  width: var(--sp-10);
  aspect-ratio: 4 / 5;
  height: auto;
  object-fit: cover;
  border-radius: var(--r-md);
  background: var(--c-well);
  outline: 1px solid color-mix(in srgb, var(--c-gold-bright) 60%, transparent);
}
.ritual__name {
  font-size: var(--fs-h4);
  line-height: var(--lh-snug);
  color: var(--c-on-dark);
}
/*  The same micro caps as the ingredient count above, in the gold that is
 *  only ever allowed on the deep grounds. */
.ritual__facts {
  margin-top: var(--sp-1);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: var(--ls-micro);
  line-height: var(--lh-snug);
  text-transform: uppercase;
  color: var(--c-gold-bright);
}

.ritual__steps {
  display: grid;
  align-content: start;
  margin: 0;
  padding: var(--sp-4) var(--sp-5) 0;
}
.ritual__steps li {
  position: relative;
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr);
  gap: var(--sp-4);
  /*  Baseline, not start: the step's first line sits on the numeral's own
   *  baseline, with no nudge in px. The numeral's baseline is the lower of
   *  the two, so the medallion keeps its place at the top of the row and the
   *  thread below still starts exactly under it. */
  align-items: baseline;
  padding-block: var(--sp-2);
}
/*  The thread, drawn a segment per step: from under this medallion to the
 *  top of the next one. One line down the whole list overshot the last
 *  medallion whenever the last step ran to a second line; a segment per gap
 *  always lands exactly, however long each step is. */
.ritual__steps li:not(:last-child)::after {
  content: '';
  position: absolute;
  left: 17px;
  top: calc(var(--sp-2) + 36px);
  bottom: calc(var(--sp-2) * -1);
  width: 1px;
  background: color-mix(in srgb, var(--c-gold-line) 50%, transparent);
}
.ritual__steps p {
  margin: 0;
  font-size: var(--fs-body);
  line-height: var(--lh-copy);
  color: var(--c-text);
}

.ritual__n {
  display: grid;
  place-items: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--c-gold);
  color: var(--c-on-gold);
  font-family: var(--ff-display);
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: var(--lh-flat);
  font-variant-numeric: lining-nums tabular-nums;
}
.ritual__note {
  margin: var(--sp-3) var(--sp-5) 0;
  padding-block: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--c-line);
  font-size: var(--fs-small);
  line-height: var(--lh-copy);
  color: var(--c-text-soft);
  font-style: italic;
}

/*  Tablet: three cards stacked full width would each be a short band over
 *  a lot of empty right-hand space. So each card turns on its side - the
 *  band becomes a column on the left, the steps sit beside it - and the
 *  three together cost little more height than the two stacked cards did.
 *  The rows are 1fr so the three stacked cards come out the same height as
 *  the tallest, not one short and two long. */
@media (min-width: 640px) and (max-width: 959px) {
  .ritual { grid-auto-rows: 1fr; }
  .ritual__col {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2.4fr);
    grid-template-rows: 1fr auto;
  }
  .ritual__head {
    grid-row: 1 / -1;
    grid-template-columns: 1fr;
    align-content: center;
    padding: var(--sp-5);
    border-bottom: 0;
    border-right: 1px solid color-mix(in srgb, var(--c-gold-bright) 55%, transparent);
  }
  .ritual__steps { padding-top: var(--sp-3); }
}

/*  Desktop: three across, and the rows line up. Each card borrows the
 *  parent's rows (subgrid), so every step 1 starts on one line across all
 *  three cards, every step 2 on the next, and the notes share a line -
 *  equal boxes whose insides are equal too, not three cards that happen to
 *  be the same height with their contents drifting. Where subgrid is not
 *  supported the cards are still equal height; only the row alignment is
 *  lost. */
@media (min-width: 960px) {
  .ritual { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  @supports (grid-template-rows: subgrid) {
    .ritual { grid-template-rows: repeat(5, auto); row-gap: 0; }
    .ritual__col   { grid-row: span 5; grid-template-rows: subgrid; row-gap: 0; }
    .ritual__steps { grid-row: span 3; grid-template-rows: subgrid; row-gap: 0; }
  }
}

/* == What to expect ======================================================
 *  Two columns that are deliberately equal in weight. Making the honest
 *  column quieter than the flattering one would give the whole section away.
 *
 *  It was two white cards with a dash in front of each line, and the owner
 *  read it as too plain — which it was, because nothing on it said that the
 *  two cards are a pair. Now each card opens on a solid band, green for what
 *  these are and gold for what they are not, so the answer is legible before
 *  a word is read. Both bands are the non-inverting grounds, like the hero
 *  and the footer: on dark the inverting gold went to a pale #D9B45C beside
 *  a deep green and the "not" side shouted over the "is" side. Kept fixed,
 *  the pair weighs the same in both modes — cream on --c-ground-2 is 8.21:1
 *  light and 7.49:1 dark, --c-on-gold on --c-gold-lit is 4.52:1 in both.
 *  That last one is normal text, not large (600 is not bold), so there is
 *  no margin: do not lighten the band.
 *
 *  The two lists share their rows (subgrid), so each claim sits level with
 *  the caveat opposite it and the shorter card has no empty foot: the row
 *  heights come from whichever side needs more lines.
 *  ---------------------------------------------------------------------- */
.expect { display: grid; gap: var(--sp-5); }

.expect__col {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--card-radius);
  background: var(--c-surface);
  border: var(--card-edge);
  box-shadow: var(--card-shadow);
}

.expect__title {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: 0;
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-h3);
  line-height: var(--lh-snug);
}
.expect__col--yes .expect__title { background: var(--c-ground-2); color: var(--c-on-dark); }
.expect__col--no  .expect__title { background: var(--c-gold-lit); color: var(--c-on-gold); }
.expect__title svg {
  width: var(--sp-6); height: var(--sp-6);
  flex: none;
  padding: var(--sp-1);
  border-radius: 50%;
  fill: none;
  stroke: currentColor;
  box-shadow: inset 0 0 0 1px currentColor;
}

/*  Lead size, not body size: these six sentences are the brand's position,
 *  and at 16px they read as small print — the one thing an honesty
 *  statement must never look like. On a phone the padding and the icon
 *  tile step down a size, or the sentences are left a 190px column and
 *  break into six lines each. */
.expect__list {
  list-style: none;
  margin: 0;
  padding: 0 var(--sp-5);
  display: grid;
  flex: 1;
}
.expect__list li {
  display: grid;
  grid-template-columns: var(--sp-6) 1fr;
  gap: var(--sp-3);
  align-items: center;
  padding: var(--sp-4) 0;
  font-size: var(--fs-lead);
  line-height: var(--lh-copy);
  color: var(--c-text);
}
.expect__list li + li { border-top: 1px solid var(--c-line); }
/*  Icon on a tint of its own side's colour. Accent on the leaf tint is 7.20:1
 *  light and 4.27:1 dark; gold on the gold tint is 3.24:1 light and 5.75:1
 *  dark — all past the 3:1 a graphic needs, and all decorative anyway. */
.expect__list svg {
  width: var(--sp-6); height: var(--sp-6);
  padding: var(--sp-1);
  border-radius: 50%;
  fill: none;
  stroke: currentColor;
}
.expect__col--yes .expect__list svg { color: var(--c-accent); background: color-mix(in srgb, var(--c-leaf) 18%, transparent); }
.expect__col--no  .expect__list svg { color: var(--c-gold);   background: color-mix(in srgb, var(--c-gold) 14%, transparent); }

@media (min-width: 800px) {
  .expect { grid-template-columns: 1fr 1fr; column-gap: var(--sp-6); }
  .expect__title { padding: var(--sp-4) var(--sp-6); }
  .expect__list { padding: 0 var(--sp-6); }
  .expect__list li { grid-template-columns: var(--sp-7) 1fr; gap: var(--sp-4); }
  .expect__list svg { width: var(--sp-7); height: var(--sp-7); padding: var(--sp-2); }
}
@supports (grid-template-rows: subgrid) {
  @media (min-width: 800px) {
    .expect { grid-template-rows: auto repeat(3, auto); row-gap: 0; }
    .expect__col { display: grid; grid-row: span 4; grid-template-rows: subgrid; row-gap: 0; }
    .expect__list { grid-row: 2 / span 3; grid-template-rows: subgrid; }
  }
}

/* == The pair ============================================================ */
.pair { display: grid; gap: var(--sp-7); align-items: center; }
@media (min-width: 900px) { .pair { grid-template-columns: .9fr 1.1fr; gap: var(--sp-10); } }

.pair__sum { display: flex; align-items: center; justify-content: center; gap: var(--sp-4); }
.pair__item { margin: 0; text-align: center; flex: 0 1 190px; }
.pair__item img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--r-lg);
  background: var(--c-tint);
}
.pair__item figcaption {
  margin-top: var(--sp-3);
  font-size: var(--fs-small);
  color: var(--c-text-soft);
  font-variant-numeric: tabular-nums;
}
.pair__op {
  font-family: var(--ff-display);
  font-size: var(--fs-h2);
  color: var(--c-leaf);
  line-height: var(--lh-flat);
  padding-bottom: 28px;   /* sits against the photographs, not the captions */
}

.pair__lede { margin-top: var(--sp-4); color: var(--c-text-soft); max-width: 44ch; }

/*  The sum, written out. Tabular figures so the numbers line up rather than
 *  wobble, which is the difference between arithmetic and decoration. */
.pair__math {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-tint);
  font-variant-numeric: tabular-nums;
}
.pair__math strong { font-size: var(--fs-lead); color: var(--c-heading-deep); }
.pair__math-op { color: var(--c-text-soft); }
.pair__math-free {
  margin-left: auto;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--c-accent);
}
.pair__cta { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-6); }

@media (max-width: 520px) {
  .pair__item { flex-basis: 130px; }
  .pair__math-free { margin-left: 0; flex-basis: 100%; }
}

/* == How a batch is made (about) ==========================================
 *  NOTE ON WHERE THIS LIVES: about.html loads home.css, which is why these
 *  are here rather than in base.css — no reason to send them to all nine
 *  pages for the sake of one. If .steps or .holds are ever used on a page
 *  that does not load home.css they will arrive unstyled, exactly as the
 *  benefits strip did on the shop page. Move them to base.css at that point.
 *  ---------------------------------------------------------------------- */
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-6); }
@media (min-width: 640px)  { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1080px) { .steps { grid-template-columns: repeat(4, 1fr); gap: var(--sp-6); } }

.step {
  position: relative;
  padding-top: var(--sp-5);
  border-top: 2px solid color-mix(in srgb, var(--c-leaf) 45%, transparent);
}
.step__n {
  display: block;
  margin-bottom: var(--sp-3);
  font-family: var(--ff-display);
  font-size: var(--fs-h3);
  color: var(--c-gold-ink);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-flat);
}
.step h3 {
  font-family: var(--ff-sans);
  font-size: var(--fs-h4);
  font-weight: 500;
  line-height: var(--lh-snug);
  color: var(--c-heading-deep);
  margin-bottom: var(--sp-2);
}
/*  Body size: each step is one full sentence a reader follows in order. */
.step p { font-size: var(--fs-body); color: var(--c-text-soft); }

.steps__foot {
  margin-top: var(--sp-7);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--c-line);
  max-width: var(--measure);
  color: var(--c-text-soft);
  font-style: italic;
}

/* == What you can hold us to (about) ===================================== */
.holds { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-5); }
@media (min-width: 800px) { .holds { grid-template-columns: 1fr 1fr; gap: var(--sp-6) var(--sp-8); } }

.hold {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: var(--sp-4);
  align-items: start;
}
.hold svg {
  width: 24px; height: 24px;
  margin-top: 2px;
  padding: 4px;
  border-radius: 50%;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  color: var(--c-accent);
  background: color-mix(in srgb, var(--c-leaf) 22%, transparent);
}
.hold p { color: var(--c-text-soft); }

/* == The closing invitation ===============================================
 *  A last, quiet ask, placed after the questions have been answered rather
 *  than shouted at the top of the page. The layout is the one the owner
 *  brought back from a competitor — copy on the left, a 2x2 grid of
 *  reassurances on the right — because the layout is sound: at the moment
 *  someone decides to buy, the four things they want to know are delivery,
 *  payment, provenance and what is actually in the bottle.
 *
 *  What is NOT borrowed is the wording. The reference's badges read
 *  "100% Organic" and "Dermatologist Tested"; neither is true here and
 *  neither is provable, so every line in this section is a fact that is
 *  already written somewhere else on this site, with a number wherever a
 *  number exists. A reassurance a shopper can check is worth four they
 *  cannot.
 *
 *  It is a panel rather than a full-width band: the page already has two
 *  tinted bands and a brand band, and a third band here would flatten the
 *  ending. An inset card with a soft shadow reads as a close.
 *  ---------------------------------------------------------------------- */
.closer {
  position: relative;
  overflow: hidden;
  display: grid;
  gap: var(--sp-7);
  padding: var(--sp-7) var(--sp-6);
  border-radius: var(--card-radius);
  background: var(--g-warm);
  border: var(--card-edge);
  box-shadow: var(--card-shadow);
}
@media (min-width: 560px)  { .closer { padding: var(--sp-8) var(--sp-8); } }
@media (min-width: 1000px) { .closer { grid-template-columns: .92fr 1.08fr; gap: var(--sp-10); align-items: center; } }

/*  The tonal blob behind the top-right corner. Decorative only, and built
 *  from the tint token so it disappears politely in dark mode rather than
 *  becoming a grey smudge. */
.closer::before {
  content: '';
  position: absolute;
  top: -22%;
  right: -12%;
  width: 46%;
  aspect-ratio: 1;
  border-radius: 50%;
  /*  A radial fade, not a flat circle: a hard-edged disc behind a corner
   *  reads as a rendering fault rather than light. */
  background: radial-gradient(circle at 50% 50%,
                color-mix(in srgb, var(--c-gold) 17%, transparent) 0%,
                transparent 68%);
  pointer-events: none;
}
.closer > * { position: relative; }

/*  The eyebrow keeps the reference's small leading dot, because it is the
 *  one decorative touch in the whole block and it earns its place by
 *  marking where the section starts without a rule. */
.closer__eyebrow { display: flex; align-items: center; gap: var(--sp-3); }
.closer__eyebrow::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--c-gold-line);
  flex: none;
}

.closer__lede { margin-top: var(--sp-4); color: var(--c-text-soft); max-width: 40ch; }

.closer__cta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); margin-top: var(--sp-6); }
.closer__cta .btn--primary svg {
  width: 17px; height: 17px;
  margin-left: var(--sp-2);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  transition: transform var(--dur-2) var(--ease);
}
.closer__cta .btn--primary:hover svg { transform: translateX(3px); }

.closer__note {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
  font-size: var(--fs-small);
  color: var(--c-text-soft);
}
.closer__note svg {
  width: 18px; height: 18px;
  margin-top: 2px;
  flex: none;
  fill: none;
  stroke: var(--c-accent-soft);
  stroke-width: 1.6;
}

/*  Two by two on anything wider than a phone, and a single column below it.
 *  Four cards stacked on a 360px screen is a long scroll for four short
 *  facts, so they go two-up from 420px — the cards are deliberately terse
 *  enough to survive a 160px column. */
.closer__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-4);
}
/*  Two-up, both rows take the taller row's height. At 1440 "Every
 *  ingredient printed" runs to two lines and the row above it does not, so
 *  the four measured 114, 114, 137, 137: the unequal boxes the owner named. */
@media (min-width: 420px) { .closer__grid { grid-template-columns: 1fr 1fr; grid-auto-rows: 1fr; } }

.closer__card {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: var(--sp-4);
  align-items: start;
  padding: var(--sp-5);
  border-radius: var(--card-radius);
  background: var(--c-surface);
  border: var(--card-edge);
  box-shadow: var(--card-shadow);
}
.closer__card h3 {
  font-family: var(--ff-sans);
  font-size: var(--fs-h4);
  font-weight: 500;
  color: var(--c-heading-deep);
  line-height: var(--lh-snug);
}
.closer__card p {
  margin-top: var(--sp-1);
  font-size: var(--fs-small);
  color: var(--c-text-soft);
  line-height: var(--lh-snug);
}
.closer__icon {
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--c-leaf) 20%, transparent);
  color: var(--c-accent);
}
.closer__icon svg {
  width: 20px; height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
}

/* == Clips ===============================================================
 *  The row of vertical videos every shop in this category runs, usually
 *  headed "Loved by our customers". These four are real and they are the
 *  shop's own footage, so the heading says what it can show rather than
 *  who loves it, and each card describes what is on screen.
 *
 *  An earlier version of this section had one real clip beside three
 *  dashed "your clip goes here" boxes. Three empty 9:16 panels stood
 *  taller than the one real clip and the block read as broken — the
 *  owner's word was "unprofessional", and he was right. An empty state
 *  has to be smaller than the content it stands in for, never larger.
 *  With four real clips the question no longer arises.
 *
 *  No new JavaScript: .rail carries the scroll-snap and ui.js already
 *  drives the chevrons off [data-rail].
 *  ---------------------------------------------------------------------- */
.clips__head { margin-bottom: 0; }
/*  A card, like the product cards, and stretched, so the four are one
 *  height whatever their captions run to. As a clip with loose text under
 *  it, the longest caption left its column 36px taller than the shortest.
 *  The clip is the card's top, flush, and the caption sits inside on the
 *  same padding as .pcard__body. No hover: the play button is the only
 *  control, and the caption does nothing when clicked. */
.clips { align-items: stretch; }

.clipcard {
  display: grid;
  grid-template-rows: auto 1fr;
  overflow: hidden;
  background: var(--c-surface);
  border: var(--card-edge);
  border-radius: var(--card-radius);
  box-shadow: var(--card-shadow);
}
.clipcard .clip { width: 100%; border: 0; border-radius: 0; }

/*  The description under each clip title is a sentence, so it is body
 *  size, with the card line-height so a five-line description still reads
 *  as copy. .clipcard__what sets its own size and line-height and is
 *  unaffected. */
.clipcard__label { padding: var(--sp-4) var(--sp-5) var(--sp-5); font-size: var(--fs-body); color: var(--c-text-soft); line-height: var(--lh-copy); }
.clipcard__what {
  display: block;
  font-family: var(--ff-sans);
  font-size: var(--fs-h4);
  font-weight: 500;
  color: var(--c-heading-deep);
  line-height: var(--lh-snug);
  margin-bottom: var(--sp-1);
}
