/*
 * Farmhouse Butter — main stylesheet
 *
 * Ported from the approved homepage mockup (farmhousebutter-mockups/mockups/homepage.html).
 * Two deliberate changes from the mockup:
 *   1. Nav selectors use WordPress's native menu classes (.menu-item-has-children,
 *      .sub-menu) rather than the mockup's .has-sub / .submenu, so no custom
 *      Walker_Nav_Menu is needed.
 *   2. The hero background image comes in as the --hero-bg custom property, set inline
 *      by fhb_render_hero() in inc/block-renderers.php — CSS files can't call
 *      get_template_directory_uri(). (This said "front-page.php" until 2026-08-24;
 *      that template's hardcoded fallback was dropped on 2026-08-17 and it now has no
 *      hero reference at all.)
 */

:root{
  --green:#1f4d34;         /* deep forest green (logo bg)        */
  --green-900:#143423;     /* darker green                       */
  --green-700:#295d40;     /* lighter green for hover            */
  --gold:#d4a53a;          /* warm gold / mustard rule lines     */
  --gold-600:#bd8f27;      /* deeper gold for text on cream      */
  --cream:#f7f2e7;         /* off-white / cream background       */
  --cream-2:#efe7d5;       /* slightly deeper cream panel        */
  --ink:#2a2823;           /* warm near-black body text          */
  --ink-soft:#5b564c;      /* muted body text                    */
  --white:#ffffff;
  --shadow:0 18px 45px -20px rgba(20,52,35,.45);
  --shadow-sm:0 8px 24px -14px rgba(20,52,35,.4);
  /* Offset-highlight sizes — the hard-shadow counterpart to the two blurred shadows
     above, used only by is-style-fhb-highlight-* (see CARD HIGHLIGHT). Fluid, so the
     block scales with the card rather than needing a breakpoint: 18px on a desktop
     viewport, ~11px at 680px. */
  --fhb-hl-sm:clamp(6px,1.2vw,12px);
  --fhb-hl-md:clamp(8px,1.6vw,18px);
  --maxw:1180px;
  --serif:"Playfair Display",Georgia,serif;
  --serif-soft:"Cormorant Garamond",Georgia,serif;
  --sans:"Montserrat",-apple-system,Segoe UI,Roboto,sans-serif;
  /* Display face for eyebrows and badges only — never for running text. One
     weight (400), no italic; see fhb_fonts_url() in functions.php. */
  --uncial:"Uncial Antiqua",Georgia,serif;

  /* ---- core block support (see the three rules below) ----
     Named measures, so theme.json and this file cannot drift apart. */
  --fhb-gutter:clamp(20px,5vw,40px);  /* was inline in .wrap                     */
  --fhb-wide:var(--maxw);             /* == theme.json settings.layout.wideSize  */
  --fhb-content:760px;                /* == theme.json settings.layout.contentSize */
}

/* =====================================================================
   CORE BLOCK SUPPORT — the three rules
   Added 2026-08-22 so an editor can use core WordPress blocks alongside the
   theme's own and have them render on brand at any nesting depth.

   1. CONTEXT TRAVELS BY CUSTOM PROPERTY, NOT BY SELECTOR.
      Custom properties inherit through arbitrary nesting for free, so
      `.section--green{--fhb-heading:var(--cream)}` reaches an h4 six Groups
      deep with no selector work and no specificity risk. Every colour that
      depends on which band it sits in is a token: --fhb-heading, --fhb-body,
      --fhb-link. Never write `.section--green .some-thing{color:…}` again.

   2. SPACING INSIDE CORE CONTAINERS IS CORE'S JOB.
      .wrap is plain PHP markup, so the theme owns its rhythm. A core/group
      gets core's own layout CSS, driven by theme.json settings.spacing.blockGap.
      If this file also wrote nested flow rules they would beat core's (0,1,0)
      and the Block spacing control would silently do nothing — the same class
      of bug as shipping align-wide with no .alignwide CSS. So: NO nested flow
      rules. Set the default in theme.json styles.spacing.blockGap instead.

   3. DEFAULTS GO IN :where(); VARIANTS KEEP REAL SPECIFICITY.
      The eyebrow bug (see the note further down) and the identical, unfixed
      lead-paragraph bug both happened because a *default* was written at
      higher specificity than a *variant*. The fix is to drop the default to
      (0,0,0), not to bump the variant — bumping is what the eyebrow patch did,
      and it has to be "kept in step by hand" forever.

   Corollary: new rules stay in PHYSICAL properties (margin-left, not
   margin-inline). This file is 100% physical; mixing the two resolves by
   declaration order across separate properties and is a footgun for no gain.
   ===================================================================== */

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0;font-family:var(--sans);color:var(--ink);background:var(--cream);
  font-size:17px;line-height:1.65;-webkit-font-smoothing:antialiased;
}
img{max-width:100%;display:block}

/* The `hidden` attribute has to beat a component's own `display`.
   [hidden]{display:none} lives ONLY in the UA stylesheet, and any author declaration
   beats the UA origin regardless of specificity. So `.recipe{display:flex}` silently
   defeated `card.hidden = true`, and the recipe filter hid nothing from the day it
   shipped (both arrived in cc70ed6, 2026-08-19). Two more were broken the same way and
   for the same reason: the filter bar ships `hidden` so it is absent without JavaScript,
   but `.rfilter{display:flex}` revealed it anyway — inert controls, the exact failure
   its own comment claims to prevent — and `.rfilter__badge{display:grid}` left the
   checked-count badge showing a literal "0".

   !important, not source order. Ordering this above the components would work today and
   then break the moment someone adds a `display` below it, with no error and nothing to
   see. Hidden means hidden; it is not a cascade opinion. This is the one rule in the
   file that is allowed to say so. */
[hidden]{display:none !important}

/* These three are a RESET, not a design decision — everything downstream is
   meant to win. :where() puts them at (0,0,0), so a component rule, a block
   style variation or an editor's own output all beat them without a fight.
   The author sheet still beats the UA sheet at (0,0,0). */
:where(a){color:inherit;text-decoration:none}
:where(h1,h2,h3,h4,h5,h6){font-family:var(--serif);font-weight:600;line-height:1.12;
  margin:0;color:var(--fhb-heading,var(--green))}
:where(p){margin:0 0 1rem}

/* Heading sizes, also at (0,0,0). Every existing component rule
   (.hero h1, .product__body h3, .recipe__body h3, .page-content h2 …) keeps
   winning untouched. This is what gives a heading NESTED inside a core Group
   or Column a sensible size — before this it fell back to the UA's 1.5em,
   because the only size rule was `.section > .wrap > .wp-block-heading`.

   Deliberate consequence: a top-level h2 in a section keeps the larger
   clamp(1.9rem,4vw,3rem); the same h2 wrapped in a Group drops to the size
   below. That reads as a sub-heading, which is the intent. */
:where(h1){font-size:clamp(2.2rem,5vw,3.2rem)}
:where(h2){font-size:clamp(1.7rem,3.2vw,2.4rem)}
:where(h3){font-size:1.5rem}
:where(h4){font-size:1.15rem}
:where(h5){font-size:1rem}
:where(h6){font-family:var(--sans);font-size:.85rem;letter-spacing:.1em;text-transform:uppercase}

.wrap{max-width:var(--fhb-wide);margin:0 auto;padding:0 var(--fhb-gutter)}
/* .eyebrow is used by the PHP templates; .is-style-fhb-eyebrow is the core-block
   style variation registered in inc/blocks.php. Same treatment, two entry points.

   Uncial Antiqua as of 2026-08-22, which finally settles the contradiction
   brand-voice.md has carried since the start: it specified an uncial for eyebrows
   and badges while nothing in the theme ever loaded one.

   Three values changed with the face, and none of them is cosmetic:
     · font-weight 700 → 400. The family ships one weight. Asking for 700 gets a
       browser-synthesised bold, which smears an already-decorative face.
     · letter-spacing .22em → .12em, the reference mockup's value. Uncial letterforms
       are wide and irregular to begin with; Montserrat's tracking pulled them apart
       into individual glyphs.
     · .74rem → .78rem, because the face reads smaller than Montserrat at a given
       size — its capitals sit lower and its strokes are lighter. */
.eyebrow,
.is-style-fhb-eyebrow{
  font-family:var(--uncial);font-size:.78rem;font-weight:400;letter-spacing:.12em;
  text-transform:uppercase;color:var(--fhb-eyebrow,var(--gold-600));margin:0 0 .9rem
}
.eyebrow.on-dark{color:var(--gold)}
/* The colour is a TOKEN, not a literal. It was var(--ink-soft) at (0,1,0), which beats
   the (0,0,0) prose default, so a Lead paragraph on a dark band rendered warm grey on
   deep green — about 1.3:1. Latent until now because every use was on a light band,
   where --fhb-body IS --ink-soft and the output is unchanged. */
.is-style-fhb-lead{font-size:1.18rem;color:var(--fhb-body,var(--ink-soft));font-family:var(--serif-soft)}
.screen-reader-text{position:absolute;left:-999px;width:1px;height:1px;overflow:hidden}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;gap:.55em;font-family:var(--sans);
  font-weight:600;font-size:.86rem;letter-spacing:.05em;text-transform:uppercase;
  padding:.9em 1.9em;border-radius:2px;cursor:pointer;border:1.5px solid transparent;
  transition:.2s ease;white-space:nowrap
}
.btn-gold{background:var(--gold);color:var(--green-900)}
.btn-gold:hover{background:#e4b74d;transform:translateY(-1px)}
.btn-green{background:var(--green);color:var(--cream)}
.btn-green:hover{background:var(--green-700)}
.btn-ghost{background:transparent;color:var(--cream);border-color:rgba(247,242,231,.55)}
.btn-ghost:hover{border-color:var(--gold);color:var(--gold)}
.btn-outline{background:transparent;color:var(--green);border-color:var(--green)}
.btn-outline:hover{background:var(--green);color:var(--cream)}
.btn-sm{padding:.7em 1.4em;font-size:.76rem}

/* ================= TOP BAR ================= */
.topbar{background:var(--green-900);color:var(--cream)}
.topbar .wrap{display:flex;align-items:center;justify-content:center;gap:.6em;
  min-height:38px;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;font-weight:500}
.topbar .shamrock{color:var(--gold)}

/* ================= HEADER / NAV ================= */
.site-header{position:sticky;top:0;z-index:60;background:var(--cream);
  border-bottom:1px solid var(--cream-2);box-shadow:0 2px 12px -8px rgba(20,52,35,.4)}
.header-inner{display:flex;align-items:center;justify-content:space-between;gap:1.5rem;
  min-height:92px}
.brand{display:flex;align-items:center;gap:.7rem;flex-shrink:0}
.brand img{height:64px;width:auto}
.brand .fallback{font-family:var(--serif);color:var(--green);font-size:1.35rem;line-height:1;font-weight:700}

.main-nav{display:flex;align-items:center;gap:2.1rem}
.main-nav ul{list-style:none;margin:0;padding:0}
.main-nav > ul{display:flex;align-items:center;gap:2.1rem}
.main-nav a{font-family:var(--sans);font-size:.82rem;font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink);padding:.4em 0;position:relative;transition:.2s}
.main-nav a:hover{color:var(--green)}
.main-nav a::after{content:"";position:absolute;left:0;bottom:-2px;height:2px;width:0;
  background:var(--gold);transition:width .25s}
.main-nav a:hover::after,.main-nav .menu-item-has-children:hover > a::after{width:100%}

.main-nav .menu-item-has-children{position:relative}
.main-nav .menu-item-has-children > a::before{content:"";display:inline-block;width:.4em;height:.4em;margin-right:3px;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translateY(-2px);vertical-align:middle;opacity:.7}
.main-nav .sub-menu{position:absolute;top:calc(100% + 10px);left:50%;transform:translateX(-50%) translateY(8px);
  background:var(--white);min-width:230px;border-radius:4px;box-shadow:var(--shadow);
  padding:.5rem;opacity:0;visibility:hidden;transition:.2s;border-top:3px solid var(--gold)}
.main-nav .menu-item-has-children:hover .sub-menu,
.main-nav .menu-item-has-children:focus-within .sub-menu{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0)}
.main-nav .sub-menu a{display:block;padding:.7em .9em;text-transform:none;letter-spacing:.02em;
  font-size:.92rem;border-radius:3px;color:var(--ink)}
.main-nav .sub-menu a::after{display:none}
.main-nav .sub-menu a:hover{background:var(--cream);color:var(--green)}
/* A non-linking menu item (url set to "#") renders as a small gold section label. */
.main-nav .sub-menu .sub-label > a{font-size:.62rem;letter-spacing:.18em;text-transform:uppercase;
  color:var(--gold-600);padding:.5em .9em 0;font-weight:700;pointer-events:none}

.header-actions{display:flex;align-items:center;gap:1.3rem}
.social{display:flex;align-items:center;gap:.85rem}
.social a{color:var(--green);opacity:.8;transition:.2s;display:grid;place-items:center}
.social a:hover{opacity:1;color:var(--gold-600);transform:translateY(-1px)}
.social svg{width:19px;height:19px;fill:currentColor}

.nav-toggle{display:none;background:none;border:0;cursor:pointer;padding:8px;color:var(--green)}
.nav-toggle svg{width:28px;height:28px;stroke:currentColor;fill:none;stroke-width:2}

/* ================= HERO ================= */
/* 570px as of 2026-08-24, down from 760px.
   THE HEIGHT ONLY GOVERNS BECAUSE THE CONTENT NOW FITS UNDER IT. At 760px this
   min-height was dead code: the natural stack measured ~767px, so content set the
   height and the declaration never applied. Two things came out to make 570 real —
   the stat badges (~127px, removed from fhb_render_hero()) and half the inner padding
   (80px) — leaving ~561px of content. Anything added back here that pushes the stack
   over 570 silently returns this line to being decorative, so measure before trusting it.
   The min() is a viewport guard, as on .page-hero: on a short landscape phone a flat
   570px band would fill the screen before a word of the page showed. */
.hero{position:relative;min-height:min(570px,88vh);display:flex;align-items:center;
  color:var(--cream);overflow:hidden}
.hero__bg{position:absolute;inset:0;background:#111 var(--hero-bg) center 30%/cover no-repeat}
.hero__scrim{position:absolute;inset:0;
  background:linear-gradient(100deg,rgba(15,35,24,.92) 0%,rgba(20,52,35,.72) 42%,rgba(20,52,35,.15) 100%)}
.hero .wrap{position:relative;z-index:2;width:100%}
.hero__inner{max-width:620px;padding:2.5rem 0}
.hero h1{color:var(--cream);font-size:clamp(2.6rem,6vw,4.5rem);line-height:1.03;margin:.2rem 0 1.2rem;
  font-weight:600;text-shadow:0 2px 30px rgba(0,0,0,.35)}
.hero h1 em{font-family:var(--serif-soft);font-style:italic;color:var(--gold);font-weight:500}
.hero__lead{font-size:1.15rem;max-width:37ch;color:rgba(247,242,231,.92);margin-bottom:2rem}
.hero__cta{display:flex;flex-wrap:wrap;gap:1rem}
/* .hero__badges / .hero__badge were removed 2026-08-24 with the stat badges themselves
   (see fhb_render_hero()); nothing emits those classes any more, and unreachable rules
   are not kept here. The 82% / 100% / Traditional Irish Recipe claims still run in the
   topbar on every page — see header.php. */

/* ================= PAGE HERO (fhb/page-hero) =================
   Masthead for pages other than the homepage. A palette colour instead of a mandatory
   photograph, left-aligned display type, and none of .hero's stat badges. Text colour
   comes from the --tone-* modifier, which render.php derives from the chosen background
   — it is never a separate editor choice, so cream-on-cream cannot be saved. */
/* Rebuilt 2026-08-23 from the approved Whey Butter Difference mockup (v2), replacing the
   340px centred band ported from the Our Heritage mockup. Height, alignment and type
   scale are one decision, not three: the band is shorter, so the type has to carry the
   masthead, and display type at this size cannot be centred in a 720px measure without
   reading as a poster.

   This block disables the color, spacing and typography supports, so there is no
   per-instance override path — every value here is universal by construction. The two
   pages that already use it are /recipes and /whey-butter-difference/our-heritage.

   min-height, NOT the mockup's flat `height:280px`. The mockup hero carries no lead
   paragraph; the Recipes hero carries eyebrow + heading + lead, and a fixed height would
   clip it. min-height lets that one case grow past 280px instead. The min() keeps the
   viewport guard the old min(46vh,400px) provided — on a short landscape phone a flat
   band would fill the screen before a word of the page showed. */
.page-hero{position:relative;overflow:hidden;display:flex;align-items:center;
  min-height:min(280px,60vh);padding:1.25rem 0;text-align:left}
.page-hero .wrap{position:relative;z-index:2;width:100%}
.page-hero__inner{max-width:720px;margin:0}
.page-hero h1{font-size:clamp(3rem,6vw,5.2rem);font-weight:500;line-height:.93;
  letter-spacing:-.055em;margin:0 0 1rem}
/* The accent is the same Playfair face as the heading, upright, one weight lighter —
   not the italic Cormorant the homepage marquee uses. At 5.2rem with -.055em tracking the
   italic broke the line's rhythm rather than accenting it. Colour still comes from the
   --tone-* rules below, never from here.

   WORTH KNOWING: Our Heritage's hero is `Our` + accent `Heritage`, so this is the whole
   composition of that page's masthead. Reverting is these three declarations. */
.page-hero h1 em{font-family:inherit;font-style:normal;font-weight:500}
.page-hero__lead{font-size:1.08rem;max-width:56ch;margin:0}
/* Scoped to the hero on purpose. The global .eyebrow stays at .78rem — that value was
   chosen for the Uncial face in 2026-08-22 and is used site-wide; only the masthead's
   eyebrow drops to the mockup's .7rem, against much larger display type. */
.page-hero .eyebrow{font-size:.7rem;margin-bottom:.9rem}

/* --fhb-ph-a / --fhb-ph-b are the two tones of the diagonal split applied under
   --tone-light below. Declared per band so each light background splits against its own
   neighbour; cream-deep inverts its pair rather than inventing a third cream, there being
   no --cream-3. The dark and gold bands declare neither and stay flat.
   NB: tools/check-block-consistency.js string-matches the literal `.page-hero--{slug}{`,
   so these six selectors must keep this exact shape — no space before the brace. */
.page-hero--white{background:var(--white);--fhb-ph-a:var(--white);--fhb-ph-b:var(--cream)}
.page-hero--cream{background:var(--cream);--fhb-ph-a:var(--cream);--fhb-ph-b:var(--cream-2)}
.page-hero--cream-deep{background:var(--cream-2);--fhb-ph-a:var(--cream-2);--fhb-ph-b:var(--cream)}
.page-hero--gold{background:var(--gold)}
.page-hero--green{background:var(--green)}
.page-hero--green-dark{background:var(--green-900)}

/* ---- the light-band background pattern ----
   A diagonal two-tone split, a soft gold bloom, and a rotated green ellipse bleeding off
   the bottom-right corner, from the Whey Butter Difference mockup.

   Keyed off --tone-light rather than the three background slugs, because render.php
   already derives that class from exactly white | cream | cream-deep, AND forces tone
   dark whenever an image is set. So one selector covers the light bands and image heroes
   are excluded for free — their scrim is the treatment and a gradient under it would
   never be seen.

   MUST STAY AFTER the six band rules above: those use the `background` shorthand, which
   resets background-image to none. Same specificity, so cascade order is what decides. */
.page-hero--tone-light{background-image:
  radial-gradient(circle at 77% 38%,rgba(212,165,58,.19),transparent 19%),
  linear-gradient(112deg,var(--fhb-ph-a) 0 59%,var(--fhb-ph-b) 59%)}
/* Clipped by .page-hero's overflow:hidden; sits under the copy because .page-hero .wrap
   is already position:relative with z-index 2. */
.page-hero--tone-light::after{content:"";position:absolute;right:-8vw;bottom:-37vw;
  width:69vw;height:50vw;background:var(--green);border-radius:50% 50% 0 0;
  transform:rotate(-8deg);pointer-events:none}

/* Background image, using the same dark-green scrim inks as the homepage marquee.
   The angle differs on purpose: the marquee's 100deg gradient is weighted to the left
   because its text is left-aligned, which would leave centred text sitting on the
   transparent side. Symmetrical vertical falloff keeps the same treatment readable
   under centred copy. */
.page-hero__bg{position:absolute;inset:0;
  background:#111 var(--page-hero-bg) center/cover no-repeat}
.page-hero__scrim{position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(15,35,24,.86) 0%,rgba(20,52,35,.66) 50%,rgba(15,35,24,.86) 100%)}

.page-hero--tone-light h1{color:var(--green)}
/* The base rule paints h1 em in --gold, which is ~1.9:1 on cream — the accent half of
   the page title, unreadable. --gold-600 clears 3:1 at this size. Latent until now
   because no page-hero on a light background had used the accent. */
.page-hero--tone-light h1 em{color:var(--gold-600)}
.page-hero--tone-light .page-hero__lead{color:var(--ink-soft)}
.page-hero--tone-light .eyebrow{color:var(--gold-600)}
/* Gold needs its own tone: cream is illegible on it and the ink used on cream is
   muddy. Values match the newsletter band, which is the same gold. */
.page-hero--tone-gold h1{color:var(--green-900)}
.page-hero--tone-gold .page-hero__lead{color:#4a3c14}
.page-hero--tone-gold .eyebrow{color:var(--green-900)}
.page-hero--tone-gold h1 em{color:var(--green)}
.page-hero--tone-dark h1{color:var(--cream)}
.page-hero--tone-dark .page-hero__lead{color:rgba(247,242,231,.9)}
.page-hero--tone-dark .eyebrow{color:var(--gold)}

/* ================= INTRO ================= */
.intro{background:var(--cream);text-align:center;padding:clamp(4rem,8vw,6.5rem) 0}
.intro__inner{max-width:760px;margin:0 auto}
.intro h2{font-size:clamp(1.9rem,4vw,2.9rem);margin-bottom:1.4rem}
.intro p{font-size:1.18rem;color:var(--ink-soft);font-family:var(--serif-soft)}
.rule{display:flex;align-items:center;justify-content:center;gap:.9rem;margin:0 auto 1.6rem;color:var(--gold)}
.rule::before,.rule::after{content:"";height:1px;width:56px;background:var(--gold)}
.rule .shamrock{font-size:1.1rem;color:var(--gold)}

/* ================= SECTION SHELL ================= */
.section{padding:clamp(4rem,8vw,7rem) 0}
.section-head{text-align:center;max-width:640px;margin:0 auto clamp(2.5rem,5vw,3.8rem)}
.section-head h2{font-size:clamp(1.9rem,4vw,3rem);margin-bottom:1rem}
/* (0,0,0) per Rule 3, like the other prose defaults. At its ported specificity of
   (0,1,1) this beat both `.is-style-fhb-lead` and `.is-style-fhb-eyebrow` — the third
   instance of the bug those two have already hit twice. No PHP in this theme renders
   .section-head today (only the mockups do), so nothing changes visually; the rule is
   defused rather than left as a trap for whoever uses the class next. */
:where(.section-head) > :where(p){color:var(--fhb-body,var(--ink-soft));font-size:1.08rem}
.section--cream2{background:var(--cream-2)}
.section-cta{text-align:center;margin-top:3rem}

/* ---- generic modifiers used by the fhb/section block ----
   Slugs match the `background` enum in src/section/block.json. The older
   .section--cream2 / .locator / .intro classes remain for the PHP templates
   that still render those sections directly.

   Per Rule 1 each band declares TOKENS, not per-element colour rules. That is
   what carries the right colour into a heading, paragraph or link nested inside
   a core Group or Column — the old `.section--green h2,h3{color:…}` patches
   only ever reached one level, and they retired three bugs on the way out:
     · no rule anywhere recoloured h1, so an h1 on --green was forest green on
       forest green;
     · h4 was invisible on --green, because that patch listed only h2 and h3;
     · h5/h6 were absent from the base heading rule entirely. */
/* --fhb-band is the band's OWN background colour, published as a token so a component
   sitting on it can paint with it. The timeline's rail node needs exactly this: a ring
   in the band colour, so the hairline rail reads as passing behind the dot rather than
   butting into it. Each modifier declares it once and then uses it for `background`,
   so the token and the paint cannot drift apart.

   --fhb-hair is the divider colour for component rules (hairline card grids, card
   borders). Translucent rather than a flat value, so it works over an image or a
   nested background too; only the dark bands need to flip it. */
.section{--fhb-body:var(--ink-soft);--fhb-link:var(--green);--fhb-link-hover:var(--gold-600)}
/* White and cream declare no colour tokens beyond the band. The .section base above
   already sets body and link, and --fhb-heading/--fhb-eyebrow fall through to their
   (0,0,0) defaults, which are the green and gold-600 they want. Restating them would
   be four more values to keep in step for no change in output. */
/* --fhb-card-bg always contrasts with the band: a white card on white is a 1px border
   and nothing else. Cream-deep and the dark bands take the var() default of --cream. */
.section--white{--fhb-band:var(--white);--fhb-card-bg:var(--cream);background:var(--fhb-band)}
.section--cream{--fhb-band:var(--cream);--fhb-card-bg:var(--white);background:var(--fhb-band)}
.section--cream-deep{--fhb-band:var(--cream-2);background:var(--fhb-band)}
/* Gold. Text is deep green rather than cream — cream on gold fails contrast badly, and
   the --ink-soft used on the cream sections is muddy against it. These are the values
   the newsletter band was ported with, kept identical so the two match. */
.section--gold{--fhb-band:var(--gold);background:var(--fhb-band);color:var(--green-900);
  --fhb-heading:var(--green-900);--fhb-body:#4a3c14;--fhb-eyebrow:var(--green-900);
  --fhb-link:var(--green-900);--fhb-link-hover:var(--green)}
.section--green{--fhb-band:var(--green);background:var(--fhb-band);color:var(--cream);
  --fhb-heading:var(--cream);--fhb-body:rgba(247,242,231,.85);--fhb-eyebrow:var(--gold);
  --fhb-link:var(--gold);--fhb-link-hover:var(--cream);--fhb-hair:rgba(247,242,231,.22)}
.section--green-dark{--fhb-band:var(--green-900);background:var(--fhb-band);color:var(--cream);
  --fhb-heading:var(--cream);--fhb-body:rgba(247,242,231,.85);--fhb-eyebrow:var(--gold);
  --fhb-link:var(--gold);--fhb-link-hover:var(--cream);--fhb-hair:rgba(247,242,231,.22)}

/* The inverse case. A light card sitting inside a dark band inherits that band's
   tokens, so an unstyled heading would come out cream on white. Reset them at the
   card. */
/* --fhb-hair is in the list for the same reason as the colours: on a dark band it
   inherits the band's cream hairline, which is invisible on a light card. */
.product,.recipe,.quote-card,.modal,.rfilter__menu,.wp-block-details,.info-card{
  --fhb-heading:var(--green);--fhb-body:var(--ink-soft);
  --fhb-eyebrow:var(--gold-600);--fhb-link:var(--green);--fhb-link-hover:var(--gold-600);
  --fhb-hair:rgba(20,52,35,.16)}
/* Core Cover ships a dark overlay and light body text of its own; without this its
   heading renders forest green on the photograph. */
.wp-block-cover{--fhb-heading:var(--cream);--fhb-body:rgba(247,242,231,.9);
  --fhb-eyebrow:var(--gold);--fhb-link:var(--gold);--fhb-link-hover:var(--cream)}

/* ---- palette backgrounds chosen in the editor ----
   Rule 1 applied to core's own colour control. When an editor gives a Group, Column
   or Cover a background from the locked palette, core emits only
   `.has-<slug>-background-color` — it has no idea the theme's headings read their
   colour from --fhb-heading, so an h3 on a deep-green Group came out forest green on
   forest green. That is the same bug the .section--* bands were rewritten to kill,
   reappearing one level down because the band classes only cover fhb/section.

   Keyed off core's generated class, so it needs no block support and no attribute:
   choosing the colour is what turns it on. Values are copied from the matching
   .section--* modifier above so the two cannot read differently. */
.has-green-background-color,
.has-green-dark-background-color,
.has-ink-background-color{
  --fhb-heading:var(--cream);--fhb-body:rgba(247,242,231,.85);--fhb-eyebrow:var(--gold);
  --fhb-link:var(--gold);--fhb-link-hover:var(--cream);
  --fhb-qcard-bg:var(--cream);--fhb-qcard-fg:var(--ink);
  --fhb-qcard-accent:var(--gold-600);--fhb-qcard-shadow:var(--gold);--fhb-quote:var(--cream);
  --fhb-hair:rgba(247,242,231,.22);--fhb-band:var(--green-900);--fhb-motif-ink:var(--cream)
}
.has-gold-background-color{
  --fhb-heading:var(--green-900);--fhb-body:#4a3c14;--fhb-eyebrow:var(--green-900);
  --fhb-link:var(--green-900);--fhb-link-hover:var(--green);
  --fhb-qcard-bg:var(--green-900);--fhb-qcard-fg:var(--cream);
  --fhb-qcard-accent:var(--gold);--fhb-qcard-shadow:var(--green);--fhb-quote:var(--green-900);
  --fhb-motif-ink:var(--green-900)
}
/* The reverse: a light Group nested inside a dark band has to put the tokens back,
   or its own headings inherit the band's cream. */
.has-white-background-color,
.has-cream-background-color,
.has-cream-deep-background-color{
  --fhb-heading:var(--green);--fhb-body:var(--ink-soft);--fhb-eyebrow:var(--gold-600);
  --fhb-link:var(--green);--fhb-link-hover:var(--gold-600);
  --fhb-qcard-bg:var(--green);--fhb-qcard-fg:var(--cream);
  --fhb-qcard-accent:var(--gold);--fhb-qcard-shadow:var(--gold);--fhb-quote:var(--green)
}

.section--centered{text-align:center}
/* Cap the centred *text*, not the wrap. The mockup constrained .section-head (640px)
   and .intro__inner (760px) while .wrap stayed at --maxw, so the card grids beneath a
   centred heading ran the full width. Capping .wrap itself squeezed those grids to
   760px as well — wrong on the Products and Recipes sections, and fatal to the
   testimonial rail, which needs the full width for the next card to peek into view. */
/* The measure itself now lives in one place — see "reading measure for core text
   blocks" in the CORE BLOCK ALIGNMENT section below, which caps every core text
   block rather than just headings and paragraphs, and centres them here. */
.section--centered .wp-block-buttons{justify-content:center}

/* ---- typographic rhythm for core blocks inside fhb/section ----
   The mockup's spacing lived in .section-head, which a composed section (core
   heading + core paragraph + a component block) never produces. These rules
   reproduce that rhythm from the block markup instead: a consistent 1rem flow
   between text blocks, and the larger section-head gap before whatever component
   follows the copy. */
.section > .wrap > *{margin-top:0;margin-bottom:0}
.section > .wrap > * + *{margin-top:1rem}
.section > .wrap > .wp-block-heading{font-size:clamp(1.9rem,4vw,3rem);line-height:1.12}

/* Prose defaults, at (0,0,0) — Rule 3.
   This replaces `.section > .wrap > p{color:…;font-size:1.08rem}`, which was (0,2,1)
   and silently beat two things it had no business beating:
     · `.is-style-fhb-lead` (0,1,0), so the Lead style rendered at body size — the same
       bug the eyebrow hit, and the reason the eyebrow needed a (0,3,0) patch that had
       to be kept in step by hand. Both are now structurally gone: the default lost the
       fight instead of the variants winning it, and the eyebrow patch is deleted.
     · anything an editor set via Additional CSS class, which is also (0,1,0).

   The second selector group is what reaches INTO core containers, which the old child
   combinator could not. The `>` throughout keeps `.quote-card blockquote p`,
   `.product__body p`, `.benefit p` and `.recipe__body p` out of scope — they are
   children of component wrappers that are not listed here.

   The size is a TOKEN, not a literal. The timeline and the info cards both want
   smaller prose (.98rem and .88rem), and the obvious way to get it —
   `.info-card p{font-size:.88rem}` at (0,1,1) — is precisely the bug this comment is
   about: it would beat `.is-style-fhb-lead` at (0,1,0) all over again. Setting
   --fhb-prose-size on the component keeps the cascade flat. */
:where(.section > .wrap,.page-content) > :where(p,ul,ol,dl,table,blockquote),
:where(.section .wp-block-group,.section .wp-block-column,
       .section .wp-block-media-text__content,.section .wp-block-details,
       .section .wp-block-cover__inner-container,
       .section .timeline__body,.section .info-card__body)
  > :where(p,ul,ol,dl,table,blockquote){
  color:var(--fhb-body,inherit);font-size:var(--fhb-prose-size,1.08rem)
}
.page-content{--fhb-body:var(--ink)}

/* Links in editorial copy.
   Scoped by the PROSE ELEMENT, not by the container. A container-scoped rule would
   also hit `.recipe__body h3 a` — the bare anchor stretched over an entire recipe
   card — and underline every card title.
   (0,1,1): the scope is in :where() so it contributes nothing, leaving `a` plus
   `:not([class])`. Just enough to beat the :where(a) reset, low enough that the
   editor's own link colour output (.wp-elements-<hash> a, (0,2,0)) still wins —
   settings.color.link is true, so that control has to keep working. `:not([class])`
   excludes every link core gives a class to: buttons, page numbers, coloured links. */
:where(.section > .wrap,.page-content,.recipe-detail,
       .section .wp-block-group,.section .wp-block-column,
       .section .wp-block-media-text__content,.section .wp-block-details)
  :where(p,li,dd,td,th,blockquote,figcaption) > a:not([class]){
  color:var(--fhb-link,var(--green));text-decoration:underline;
  text-decoration-color:var(--gold);text-decoration-thickness:1px;
  text-underline-offset:.18em
}
:where(.section > .wrap,.page-content,.recipe-detail,
       .section .wp-block-group,.section .wp-block-column,
       .section .wp-block-media-text__content,.section .wp-block-details)
  :where(p,li,dd,td,th,blockquote,figcaption) > a:not([class]):hover{
  color:var(--fhb-link-hover,var(--gold-600));text-decoration-color:currentColor
}

/* core/spacer carries its height as an inline style; the owl rule above then added
   1rem on top, so a 100px spacer measured 116px and the editor's number never
   matched the render. (0,3,0) beats the (0,2,0) owl. */
.section > .wrap > .wp-block-spacer{margin-top:0;margin-bottom:0}
.section > .wrap > .wp-block-spacer + *{margin-top:0}

/* The component that follows the copy gets the old .section-head bottom margin. */
.section > .wrap > .products,
.section > .wrap > .recipes,
.section > .wrap > .recipes-wrap,
.section > .wrap > .benefits,
.section > .wrap > .proof,
.section > .wrap > .proof-carousel,
.section > .wrap > .locator-map,
.section > .wrap > .wp-block-buttons{margin-top:clamp(2.5rem,5vw,3.8rem)}

/* ================= CORE BLOCK ALIGNMENT =================
   The half that was missing when align-wide was removed on 2026-08-19. See the
   long note in functions.php → fhb_setup() for the history and the invariant.

   Written longhand rather than with a leading :is(). add_editor_style() runs every
   selector through the editor's prefixer to scope it to .editor-styles-wrapper, and
   a selector that BEGINS with :is( / :where( is the case most likely to be mangled.
   Keeping the first compound a plain class is cheap insurance and makes each
   selector's specificity legible on its own.

   Scoped to the theme's two measured columns. Core's own constrained containers are
   deliberately excluded: inside a core Group, `.is-layout-constrained > .alignwide`
   is already correct (driven by theme.json wideSize), and a viewport-relative
   breakout there would escape the Group entirely.

   .split__text is excluded too. It is a grid cell, so the 50% below would be half
   the CELL, and an .alignfull inside a split would land off-centre. Alignment is a
   deliberate no-op there. */
.section > .wrap > .alignwide,
.page-content > .alignwide{
  --_w:min(var(--fhb-wide), 100vw - 2 * var(--fhb-gutter));
  width:var(--_w);max-width:none;
  /* 50% is half the containing block, so the box centres on the column it is
     breaking out of — which is itself centred in the viewport. */
  margin-left:calc(50% - var(--_w) / 2);
  margin-right:calc(50% - var(--_w) / 2)
}

.section > .wrap > .alignfull,
.page-content > .alignfull{
  width:100vw;max-width:none;
  margin-left:calc(50% - 50vw);
  margin-right:calc(50% - 50vw)
}

/* A full-bleed container still needs the page gutter inside it, or its text runs into
   the viewport edge. An editor-set padding is an inline style and still wins. */
.section > .wrap > .alignfull.wp-block-group,
.section > .wrap > .alignfull.wp-block-columns,
.section > .wrap > .alignfull.wp-block-cover,
.section > .wrap > .alignfull.wp-block-media-text,
.page-content > .alignfull.wp-block-group,
.page-content > .alignfull.wp-block-columns,
.page-content > .alignfull.wp-block-cover,
.page-content > .alignfull.wp-block-media-text{
  padding-left:var(--fhb-gutter);padding-right:var(--fhb-gutter)
}

/* 100vw includes the scrollbar, so .alignfull is a few pixels wider than the viewport
   and would push a horizontal scrollbar.

   `clip`, NOT `hidden`. `hidden` creates a scroll container, which would break
   position:sticky on .site-header and would also force overflow-y to auto, clipping
   the recipe filter dropdown. `clip` does neither, and overflow-x:clip paired with
   overflow-y:visible is legal.

   On #main rather than body, because overflow on body propagates to the viewport.
   .site-header renders before <main> opens (header.php), so the sticky header is
   structurally out of reach of this rule either way. */
#main{overflow-x:clip}

/* ---- reading measure for core text blocks ----
   Without this a paragraph in a non-centred section runs the full ~1100px,
   contentSize:760px is fiction, and .alignwide has almost nothing to widen from.

   An explicit allow-list of CORE selectors, never `.section > .wrap > *` with :not()
   exclusions — see the .section--centered note above for why capping .wrap's children
   wholesale broke the product and recipe grids and the testimonial rail. An allow-list
   also fails better: a future fhb/* block defaults to full width, which is today's
   behaviour, rather than silently to 760px. */
.section > .wrap > p,
.section > .wrap > .wp-block-heading,
.section > .wrap > .wp-block-list,
.section > .wrap > .wp-block-quote,
.section > .wrap > .wp-block-pullquote,
.section > .wrap > .wp-block-table,
.section > .wrap > .wp-block-details,
.section > .wrap > .wp-block-accordion,
.section > .wrap > .wp-block-preformatted,
.section > .wrap > .wp-block-code,
.section > .wrap > .wp-block-group:not(.alignwide):not(.alignfull),
.section > .wrap > .wp-block-columns:not(.alignwide):not(.alignfull),
.section > .wrap > .wp-block-image:not(.alignwide):not(.alignfull),
.section > .wrap > .wp-block-media-text:not(.alignwide):not(.alignfull){
  max-width:var(--fhb-content)
}
/* Centred sections additionally pull the measure to the middle.

   This list must stay in step with the one above it. Five entries were missing —
   pullquote, accordion, preformatted, code, columns and media-text were capped at
   760px by the measure list but never centred, so inside a .section--centered they
   were narrow AND hugging the left edge. Nothing on the site used that combination
   yet; Our Heritage uses two of them. */
.section--centered > .wrap > p,
.section--centered > .wrap > .wp-block-heading,
.section--centered > .wrap > .wp-block-list,
.section--centered > .wrap > .wp-block-quote,
.section--centered > .wrap > .wp-block-pullquote,
.section--centered > .wrap > .wp-block-table,
.section--centered > .wrap > .wp-block-details,
.section--centered > .wrap > .wp-block-accordion,
.section--centered > .wrap > .wp-block-preformatted,
.section--centered > .wrap > .wp-block-code,
.section--centered > .wrap > .wp-block-group:not(.alignwide):not(.alignfull),
.section--centered > .wrap > .wp-block-columns:not(.alignwide):not(.alignfull),
.section--centered > .wrap > .wp-block-image:not(.alignwide):not(.alignfull),
.section--centered > .wrap > .wp-block-media-text:not(.alignwide):not(.alignfull){
  margin-left:auto;margin-right:auto
}

/* ---- core Button (style variations registered in inc/blocks.php) ----
   The geometry is declared ONCE for every core Button, not per variation. It used to
   be restated only inside the four is-style-fhb-* selectors, which duplicated .btn
   verbatim and left a Button with no variation selected rendering as core's stock
   pill. An editor who drops in a Button and types a label now gets a brand button by
   default; the variations only change colour. */
.wp-block-button__link{
  display:inline-flex;align-items:center;gap:.55em;font-family:var(--sans);
  font-weight:600;font-size:.86rem;letter-spacing:.05em;text-transform:uppercase;
  padding:.9em 1.9em;border-radius:2px;border:1.5px solid transparent;transition:.2s ease
}
/* Default colours, matching .btn-green. Excluded when a variation is chosen, and when
   the editor has set its own colour (core emits .has-*-background-color). */
.wp-block-button:not([class*="is-style-fhb-"]) .wp-block-button__link:not([class*="has-"]){
  background:var(--green);color:var(--cream)
}
.wp-block-button:not([class*="is-style-fhb-"]) .wp-block-button__link:not([class*="has-"]):hover{
  background:var(--green-700)
}
.wp-block-button.is-style-fhb-gold .wp-block-button__link{background:var(--gold);color:var(--green-900)}
.wp-block-button.is-style-fhb-gold .wp-block-button__link:hover{background:#e4b74d}
.wp-block-button.is-style-fhb-green .wp-block-button__link{background:var(--green);color:var(--cream)}
.wp-block-button.is-style-fhb-green .wp-block-button__link:hover{background:var(--green-700)}
.wp-block-button.is-style-fhb-outline .wp-block-button__link{background:transparent;color:var(--green);border-color:var(--green)}
.wp-block-button.is-style-fhb-outline .wp-block-button__link:hover{background:var(--green);color:var(--cream)}
.wp-block-button.is-style-fhb-ghost .wp-block-button__link{background:transparent;color:var(--cream);border-color:rgba(247,242,231,.55)}
.wp-block-button.is-style-fhb-ghost .wp-block-button__link:hover{border-color:var(--gold);color:var(--gold)}

/* ---- fhb/split media position ---- */
.split--media-right .split__media{order:2}
.split--media-right .split__text{order:1}

/* ================= PRODUCTS ================= */
/* Column count is driven by the card count (.products--2 / --3) from the
   fhb/product-cards block. The bare .products rule keeps two-up as the default
   for any markup that doesn't carry a count class. */
.products{display:grid;grid-template-columns:1fr 1fr;gap:clamp(1.5rem,3vw,2.5rem)}
.products--1{grid-template-columns:minmax(0,460px);justify-content:center}
.products--2{grid-template-columns:repeat(2,1fr)}
.products--3{grid-template-columns:repeat(3,1fr)}
.product{background:var(--white);border-radius:6px;overflow:hidden;box-shadow:var(--shadow-sm);
  display:flex;flex-direction:column;transition:.25s}
.product:hover{transform:translateY(-4px);box-shadow:var(--shadow)}
.product__media{position:relative;aspect-ratio:4/3;overflow:hidden;background:var(--white);
  display:grid;place-items:center;padding:1.4rem}
.product__media img{width:100%;height:100%;object-fit:contain;transition:.5s}
.product:hover .product__media img{transform:scale(1.04)}
.product__tag{position:absolute;top:16px;left:16px;background:var(--gold);color:var(--green-900);
  font-size:.66rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  padding:.45em 1em;border-radius:2px}
.product__body{padding:1.8rem 1.9rem 2rem;display:flex;flex-direction:column;flex:1}
.product__body h3{font-size:1.6rem;margin-bottom:.5rem}
.product__body p{color:var(--ink-soft);font-size:.98rem;flex:1}
.product__actions{display:flex;gap:.9rem;margin-top:1.3rem;flex-wrap:wrap}
.product__actions .btn{font-size:.76rem;padding:.75em 1.4em}

/* ================= BENEFITS ================= */
.benefits{display:grid;grid-template-columns:repeat(5,1fr);gap:1.4rem}
.benefit{text-align:center;padding:1.2rem .6rem}
.benefit__icon{width:76px;height:76px;margin:0 auto 1.1rem;border-radius:50%;
  border:2px solid var(--gold);display:grid;place-items:center;color:var(--gold)}
/* Round caps and joins were added with the refined icon set (2026-08-19): these are
   organic shapes — a heart, a feather, a sprout — and mitred corners left visible
   spurs on the curves at 34px. */
.benefit__icon svg{width:34px;height:34px;stroke:currentColor;fill:none;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round}
.benefit h3{margin-bottom:.5rem;font-family:var(--sans);font-weight:700;
  letter-spacing:.02em;color:var(--cream);text-transform:uppercase;font-size:.82rem}
.benefit p{font-size:.92rem;color:rgba(247,242,231,.82);margin:0}

/* ================= STORY / HERITAGE ================= */
.split{display:grid;grid-template-columns:1.05fr 1fr;align-items:center;
  gap:clamp(2rem,5vw,4.5rem)}
.split__media{position:relative}
.split__media img{border-radius:6px;box-shadow:var(--shadow);aspect-ratio:4/3.2;object-fit:cover;width:100%}
.split__media .stamp{position:absolute;bottom:-26px;right:-10px;background:var(--gold);
  color:var(--green-900);width:118px;height:118px;border-radius:50%;display:grid;place-items:center;
  text-align:center;font-family:var(--serif);line-height:1.05;font-size:.82rem;box-shadow:var(--shadow-sm);
  transform:rotate(-8deg)}
.split__media .stamp b{font-size:1.7rem;display:block}
.story h2{font-size:clamp(2rem,4vw,3rem);margin-bottom:1.3rem}
.story p{color:var(--ink-soft)}
.story .btn{margin-top:.8rem}

/* ================= TIMELINE (fhb/timeline + fhb/timeline-item) =================
   An <ol>, because a dated sequence of events has a meaningful order and a count —
   both announced for free by the list semantics, even though the numerals themselves
   are decorative and come from CSS.

   THE NUMERALS ARE A COUNTER, NOT SAVED TEXT. Reordering or deleting a step renumbers
   the whole sequence, and no editor can save 01, 02, 02, 04. The per-item `label`
   attribute overrides it when a story page wants years instead of ordinals, which is
   the only reason the two are separate elements. */
.timeline{
  counter-reset:fhb-tl;list-style:none;position:relative;
  width:min(980px,100%);margin-left:auto;margin-right:auto;padding:0;
  /* The section head above is centred and .section--centered sets text-align on the
     whole band, which lands on every step. Same reset as .info-cards. */
  text-align:left
}
/* The spine. Fades downward so it reads as a receding thread rather than a border. */
.timeline::before{
  content:"";position:absolute;top:.5rem;bottom:0;left:50%;width:1px;
  background:linear-gradient(var(--gold),rgba(212,165,58,.18))
}
.timeline__item{
  counter-increment:fhb-tl;position:relative;width:50%;
  padding:0 clamp(1.8rem,4.5vw,4.4rem) clamp(2.4rem,5vw,5rem) 0
}
.timeline__item:nth-child(even){
  margin-left:50%;
  padding:0 0 clamp(2.4rem,5vw,5rem) clamp(1.8rem,4.5vw,4.4rem)
}
.timeline__item:last-child{padding-bottom:0}
/* Node on the spine. The wide ring is painted in the band's own colour (--fhb-band),
   so the spine appears to pass behind the dot. A hardcoded green-900 here — which is
   what the reference mockup used — turns into a dark halo the moment the timeline is
   dropped on any other background. */
.timeline__item::after{
  content:"";position:absolute;top:.6rem;right:-7px;width:14px;height:14px;
  border-radius:50%;background:var(--gold);
  box-shadow:0 0 0 6px var(--fhb-band,var(--green-900)),0 0 0 7px var(--gold)
}
.timeline__item:nth-child(even)::after{left:-7px;right:auto}
.timeline__num{
  display:block;font-family:var(--serif);font-weight:600;line-height:1;
  color:var(--gold);font-size:clamp(2.4rem,5.5vw,4.3rem)
}
.timeline__num--auto::before{content:counter(fhb-tl,decimal-leading-zero)}
.timeline__body{--fhb-prose-size:.98rem}
.timeline__body h3{font-size:clamp(1.2rem,2.4vw,1.6rem);line-height:1.2;margin:.55rem 0 .85rem}
.timeline__body > :last-child{margin-bottom:0}

/* ================= INFO CARDS (fhb/info-cards + fhb/info-card) =================
   One block pair, two presentations, because they are the same content object — a
   headed card with a body and optional link:

     --grid     flat cards divided by hairlines, indexed with a roman numeral.
     --gallery  separated cards with a 16:10 thumbnail tile, a media badge and a link.

   The variant is an editor choice on the parent, like fhb/section's background. What
   is NOT a choice, and is derived instead: the column count (from the number of
   children), the numerals (a counter), the tile (renders iff the card has a badge or
   an image), and the placeholder tile's colour (rotates by position). */
.info-cards{list-style:none;margin:0;padding:0;display:grid;text-align:left}
.info-cards--1{grid-template-columns:minmax(0,460px);justify-content:center}
.info-cards--2{grid-template-columns:repeat(2,1fr)}
.info-cards--3{grid-template-columns:repeat(3,1fr)}
.info-cards--4{grid-template-columns:repeat(4,1fr)}

/* Hairline grid. The dividers are the 1px grid gap showing the container's own
   background through — one colour value instead of a border-collapse puzzle over
   which card owns which edge. */
.info-cards--grid{
  counter-reset:fhb-ic;gap:1px;
  background:var(--fhb-hair,rgba(20,52,35,.16));
  border:1px solid var(--fhb-hair,rgba(20,52,35,.16))
}
.info-cards--gallery{gap:clamp(1.2rem,2.4vw,1.75rem)}
/* Gallery caps at two columns however many cards there are. A 16:10 tile above a
   heading, a paragraph and a link cannot survive a quarter of the content width, so
   the cap is derived from the variant rather than added as a second control. */
.info-cards--gallery.info-cards--3,
.info-cards--gallery.info-cards--4{grid-template-columns:repeat(2,1fr)}

.info-card{
  position:relative;display:flex;flex-direction:column;
  --fhb-prose-size:.88rem;
  background:var(--fhb-card-bg,var(--cream))
}
.info-cards--grid > .info-card{
  counter-increment:fhb-ic;
  padding:clamp(1.7rem,3.4vw,2.5rem) clamp(1.2rem,2.4vw,1.75rem)
}
.info-cards--grid > .info-card::before{
  content:counter(fhb-ic,upper-roman);display:block;
  font-family:var(--uncial);font-size:.72rem;letter-spacing:.1em;
  color:var(--fhb-eyebrow,var(--gold-600));margin-bottom:.75rem
}
/* Levels the paragraphs across a row when the headings run to different lengths.
   Dropped below 680px, where the cards stack and there is nothing to level against. */
.info-cards--grid > .info-card h3{
  font-size:clamp(1.1rem,2.2vw,1.4rem);line-height:1.25;
  margin-bottom:.8rem;min-height:3.5em
}
.info-cards--grid > .info-card > :last-child{margin-bottom:0}
/* Both variants emit identical markup, so switching Presentation never needs the cards
   re-saved. The difference is only which element carries the padding: the card in the
   hairline grid (where the numeral has to sit inside it) and the body in the gallery
   (where the tile has to run edge to edge). */
.info-cards--grid .info-card__body{padding:0}

.info-cards--gallery > .info-card{
  border:1px solid var(--fhb-hair,rgba(20,52,35,.16));
  transition:transform .25s ease,box-shadow .25s ease
}
.info-cards--gallery > .info-card:hover{transform:translateY(-5px);box-shadow:var(--shadow)}
/* One stretched link per card (see .info-card__link a::after), so the whole card is
   clickable with a mouse while keyboard users get exactly one tab stop. The ring has
   to move to the card, since the anchor itself is a 1px-tall label. */
.info-card:focus-within{outline:2px solid var(--gold);outline-offset:2px}

.info-card__tile{
  position:relative;aspect-ratio:16/10;overflow:hidden;
  display:grid;place-items:center;padding:1.4rem;text-align:center
}
.info-card__tile img{width:100%;height:100%;object-fit:cover}
.info-card__tile::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(transparent 45%,rgba(20,52,35,.72))
}
.info-card__tile-label{position:relative;z-index:1;font-family:var(--serif);
  font-size:clamp(1rem,1.9vw,1.2rem);line-height:1.2}
/* Placeholder colours rotate by position rather than by an attribute, so an editor
   cannot pick an unreadable pairing and four cards in a row cannot come out identical. */
.info-cards > .info-card:nth-child(4n+1) .info-card__tile--placeholder{background:var(--green);color:var(--cream)}
.info-cards > .info-card:nth-child(4n+2) .info-card__tile--placeholder{background:var(--green-900);color:var(--gold)}
.info-cards > .info-card:nth-child(4n+3) .info-card__tile--placeholder{background:var(--gold);color:var(--green-900)}
.info-cards > .info-card:nth-child(4n+4) .info-card__tile--placeholder{background:var(--green-700);color:var(--cream)}

.info-card__kind{
  position:absolute;left:14px;top:13px;z-index:3;line-height:1;
  background:var(--cream);color:var(--green-900);padding:.55em .75em;
  font-family:var(--sans);font-size:.61rem;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase
}
.info-card__body{display:flex;flex-direction:column;flex:1;padding:clamp(1.4rem,2.6vw,1.9rem)}
.info-card__body h3{font-size:clamp(1.15rem,2.3vw,1.5rem);line-height:1.25;margin-bottom:.9rem}
.info-card__body > :last-child{margin-bottom:0}
.info-card__link{margin-top:auto;padding-top:1.1rem}
.info-card__link a,
.info-card__link span{
  display:inline-block;padding-bottom:2px;font-family:var(--sans);font-size:.72rem;
  font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--fhb-link,var(--green));border-bottom:2px solid var(--gold)
}
.info-card__link a::after{content:"";position:absolute;inset:0;z-index:4}
.info-card:hover .info-card__link a{color:var(--fhb-link-hover,var(--gold-600))}
/* No URL for this reference yet, so the label is plain text. Never a dead href="#" —
   that is a link that announces itself to a screen reader and then does nothing. */
.info-card__link--inert span{color:var(--ink-soft);border-bottom-color:rgba(20,52,35,.18)}

/* ================= PANEL (core/group style variations) =================
   Registered on core/group in inc/blocks.php as Panel, Panel · Ireland map and
   Panel · England map. A tall bordered block with an optional country silhouette
   bleeding off the bottom-right corner.

   NO CUSTOM BLOCK. A block with a `country` attribute fails the reusability test
   outright, and the generic thing underneath — a padded panel with a decorative
   corner motif — is a core Group that already accepts a palette background, a
   heading and body copy. Only the treatment was missing, and a treatment is a style
   variation.

   The motif is a MASK, not a background-image: mask + background-color:currentColor
   means the silhouette takes the panel's own text colour, so it reads as a dark ghost
   on the cream panel and a light one on the green panel with no second asset and no
   per-panel rule. A `fill="currentColor"` SVG in background-image would inherit
   nothing — the browser loads it as a separate document. The url()s are relative to
   this stylesheet; WordPress rewrites them to absolute for the editor canvas via
   _wp_normalize_relative_css_links(). */
.is-style-fhb-panel,
.is-style-fhb-panel-ireland,
.is-style-fhb-panel-england{
  position:relative;overflow:hidden;
  padding:clamp(1.7rem,4vw,3.1rem);
  border:1px solid var(--fhb-hair,rgba(20,52,35,.12));
  /* The section head above these is centred; a tall slab of centred body copy is
     not. Same reasoning as the quote card. */
  text-align:left
}
/* THE TALL FLOOR BELONGS TO THE MAP PANELS ONLY. Split off the shared rule 2026-08-24.
   It is load-bearing here twice over: it is the sole reason the Ireland/England pair
   reads as equal height — their columns do not stretch, so both simply bottom out on this
   same value — and the silhouette is ~341px tall under overflow:hidden, so a shorter
   panel crops it to a stub. */
.is-style-fhb-panel-ireland,
.is-style-fhb-panel-england{min-height:clamp(300px,40vw,540px)}
/* Plain Panel keeps a modest floor so the band still has weight, but CENTRES its content.
   That second part is the fix: a floor above content height on a normal-flow block box
   always dumps its slack below the content, never around it, which is why the one card
   using this style carried ~180px of dead space under its copy against the old 540px.
   Grid + align-content splits whatever slack remains.

   Note .is-style-fhb-panel does not match .is-style-fhb-panel-ireland — different class
   tokens — so the map variants keep block flow and their own floor above. */
.is-style-fhb-panel{min-height:380px;display:grid;align-content:center}
.is-style-fhb-panel-ireland::after,
.is-style-fhb-panel-england::after{
  content:"";position:absolute;right:-4%;bottom:-7%;pointer-events:none;
  width:min(260px,58%);aspect-ratio:var(--fhb-motif-ratio);
  /* currentColor by default, but NOT currentColor directly. Core only emits a `color`
     when the editor picks a text colour, so a Group given a deep-green background and
     nothing else still has ink as its currentColor — the silhouette came out
     near-black on near-black and vanished. The dark background sets below override the
     token. Overriding `color` there instead would beat core's own text-colour control,
     which prints earlier in the cascade at the same specificity. */
  background-color:var(--fhb-motif-ink,currentColor);opacity:.12;
  -webkit-mask:var(--fhb-motif) no-repeat center/contain;
  mask:var(--fhb-motif) no-repeat center/contain
}
.is-style-fhb-panel-ireland{
  --fhb-motif:url("../images/maps/ireland.svg");--fhb-motif-ratio:299.2/393
}
.is-style-fhb-panel-england{
  --fhb-motif:url("../images/maps/england.svg");--fhb-motif-ratio:451/581
}

/* ================= RECIPES ================= */
/* Column count comes from how many cards actually rendered (.recipes--1/2/3), so
   asking for three when two are published still produces a correct grid. */
.recipes{display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(1.2rem,2.5vw,2rem)}
.recipes--1{grid-template-columns:minmax(0,420px);justify-content:center}
.recipes--2{grid-template-columns:repeat(2,1fr)}
.recipes--3{grid-template-columns:repeat(3,1fr)}
/* Card style follows mockups/homepage-2.html (.rcard there): softer 10px radius, the
   category as a pill overlaid on the image rather than a meta line above the title, and
   a bordered footer carrying the time beside a clock. Typography stays on this theme's
   tokens — h3 inherits --serif from the global heading rule, micro-labels use --sans. */
.recipe{background:var(--white);border-radius:10px;overflow:hidden;box-shadow:var(--shadow-sm);
  transition:.25s;position:relative;display:flex;flex-direction:column}
/* One link per card, on the title, stretched over the whole card. Keeps the card
   navigable by keyboard and screen reader while staying clickable by mouse. */
.recipe__body h3 a{color:inherit;text-decoration:none}
.recipe__body h3 a::after{content:"";position:absolute;inset:0;z-index:2}
.recipe__body h3 a:focus-visible{outline:none}
.recipe:focus-within{outline:2px solid var(--gold);outline-offset:2px;box-shadow:var(--shadow)}
.recipe:hover{transform:translateY(-5px);box-shadow:var(--shadow)}
.recipe__media{position:relative;aspect-ratio:3/2;overflow:hidden;background:var(--cream-2)}
.recipe__media img{width:100%;height:100%;object-fit:cover;transition:.5s}
.recipe:hover .recipe__media img{transform:scale(1.05)}
/* Sits above the image, below the stretched title link (z-index 2).
   A recipe can carry more than one category and the card shows all of them, so the
   positioning lives on the wrapper and the pills wrap along the top edge rather than
   stacking on top of each other. max-width keeps them inside the 12px inset on both
   sides. */
.recipe__cats{position:absolute;top:12px;left:12px;z-index:1;
  display:flex;flex-wrap:wrap;gap:.35rem;max-width:calc(100% - 24px)}
.recipe__cat{background:rgba(20,52,35,.88);
  color:var(--cream);font-family:var(--sans);font-size:.6rem;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;padding:.45em .9em;border-radius:50px;backdrop-filter:blur(2px)}
.recipe__body{padding:1.3rem 1.4rem 1.5rem;display:flex;flex-direction:column;flex:1}
.recipe__body h3{font-size:1.5rem;line-height:1.05;margin-bottom:.5rem}
/* flex:1 pushes the footer down, so footers line up across cards whose descriptions
   differ in length. */
.recipe__body p{font-size:.9rem;color:var(--ink-soft);flex:1;margin:0 0 1rem}
.recipe__foot{display:flex;justify-content:space-between;align-items:center;gap:1rem;
  border-top:1px solid var(--cream-2);padding-top:.9rem;font-family:var(--sans);
  font-size:.7rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-soft)}
.recipe__time{display:inline-flex;align-items:center;gap:.4em}
.recipe__foot svg{width:13px;height:13px;stroke:var(--gold-600);fill:none;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
/* A span, not a link — the whole card is already the title's link. Pushed right so it
   still reads as the affordance the mockup drew. */
.recipe__view{color:var(--gold-600);margin-left:auto}
/* Retained for the single recipe template's header, which still uses a meta line. */
.recipe__meta{font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;color:var(--gold-600);
  font-weight:700;margin-bottom:.6rem;display:flex;gap:.7rem;flex-wrap:wrap}

/* ---- single recipe ----
   Ingredients beside a numbered method, following the Kerrygold reference. Stacks
   below 820px with ingredients first, which is the order you cook in. */
.recipe-detail{max-width:900px}
.recipe-detail__head{text-align:center;max-width:680px;margin:0 auto 2rem}
.recipe-detail__head h1{font-size:clamp(2.2rem,5vw,3.4rem);margin:.4rem 0 1rem}
.recipe-detail__head .recipe__meta{justify-content:center}
.recipe-detail__head .recipe__meta a{color:var(--gold-600)}
.recipe-detail__hero{margin:0 0 2.6rem}
.recipe-detail__hero img{border-radius:6px;box-shadow:var(--shadow);width:100%}
.recipe-detail__method{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.6fr);
  gap:clamp(2rem,5vw,4rem);align-items:start}
.recipe-detail__method h2{font-size:1.5rem;margin-bottom:1rem;padding-bottom:.6rem;
  border-bottom:2px solid var(--gold)}
.recipe-detail__ingredients ul{margin:0;padding-left:1.2rem}
.recipe-detail__ingredients li{margin-bottom:.6rem;color:var(--ink-soft)}
.recipe-detail__instructions ol{margin:0;padding-left:1.4rem}
.recipe-detail__instructions li{margin-bottom:1rem;padding-left:.3rem}
.recipe-detail__instructions li::marker{font-family:var(--serif);font-weight:700;color:var(--gold-600)}
.recipe-detail__notes{margin-top:2.6rem;max-width:none}
.recipe-detail__cta{display:flex;flex-wrap:wrap;gap:1rem;margin-top:3rem;
  padding-top:2rem;border-top:1px solid var(--cream-2)}
@media(max-width:820px){
  .recipe-detail__method{grid-template-columns:1fr}
}

/* ---- recipe filter (fhb/recipe-cards with filtering on) ----
   Two dropdown multi-selects, Categories and Times. Ships hidden and is revealed by
   main.js, so without JS visitors get every recipe rather than controls that do
   nothing. Each menu holds plain checkboxes: multiple choices within a group are OR'd,
   and the two groups are AND'd together. */
.rfilter{display:flex;flex-wrap:wrap;align-items:center;gap:.8rem;
  margin-bottom:clamp(1.8rem,4vw,2.6rem);padding-bottom:1.4rem;
  border-bottom:1px solid var(--cream-2)}
.rfilter__group{position:relative}
.rfilter__toggle{display:inline-flex;align-items:center;gap:.6em;cursor:pointer;
  font-family:var(--sans);font-size:.74rem;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink);background:var(--white);
  border:1.5px solid var(--cream-2);border-radius:50px;padding:.75em 1.2em;
  transition:.2s ease}
.rfilter__toggle:hover{border-color:var(--gold)}
.rfilter__toggle[aria-expanded="true"]{border-color:var(--green);color:var(--green)}
.rfilter__chev{display:grid;place-items:center;transition:transform .2s ease}
.rfilter__chev svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2;
  stroke-linecap:round;stroke-linejoin:round}
.rfilter__toggle[aria-expanded="true"] .rfilter__chev{transform:rotate(180deg)}
/* Count of checked options, so a collapsed menu still says how much is filtered. */
.rfilter__badge{display:grid;place-items:center;min-width:1.5em;height:1.5em;padding:0 .35em;
  border-radius:50px;background:var(--green);color:var(--cream);font-size:.66rem;
  letter-spacing:0}

.rfilter__menu{position:absolute;top:calc(100% + 8px);left:0;z-index:30;min-width:220px;
  max-height:min(340px,60vh);overflow-y:auto;background:var(--white);border-radius:6px;
  border-top:3px solid var(--gold);box-shadow:var(--shadow);padding:.5rem}
.rfilter__opt{display:flex;align-items:center;gap:.7em;padding:.6em .8em;border-radius:3px;
  cursor:pointer;font-family:var(--sans);font-size:.9rem;color:var(--ink);transition:.15s ease}
.rfilter__opt:hover{background:var(--cream)}
.rfilter__opt input{width:17px;height:17px;flex:0 0 auto;accent-color:var(--green);cursor:pointer;
  margin:0}
.rfilter__opt:has(input:checked){color:var(--green);font-weight:600}

.rfilter__clear{font-family:var(--sans);font-size:.72rem;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft);background:none;border:0;cursor:pointer;
  padding:.6em .4em;text-decoration:underline;text-underline-offset:3px}
.rfilter__clear:hover{color:var(--green)}
.rfilter__empty{text-align:center;color:var(--ink-soft);font-family:var(--serif-soft);
  font-size:1.1rem;margin:2rem 0 0}

/* On a dark section the closed control has to invert; the open menu stays a light
   panel, because it sits above the section rather than in it. */
.section--green .rfilter__toggle,
.section--green-dark .rfilter__toggle{background:transparent;color:rgba(247,242,231,.85);
  border-color:rgba(247,242,231,.3)}
.section--green .rfilter__toggle:hover,
.section--green-dark .rfilter__toggle:hover{border-color:var(--gold);color:var(--gold)}
.section--green .rfilter__toggle[aria-expanded="true"],
.section--green-dark .rfilter__toggle[aria-expanded="true"]{border-color:var(--gold);
  color:var(--gold)}
.section--green .rfilter__badge,
.section--green-dark .rfilter__badge{background:var(--gold);color:var(--green-900)}
.section--green .rfilter__clear,
.section--green-dark .rfilter__clear{color:rgba(247,242,231,.75)}
.section--green .rfilter__clear:hover,
.section--green-dark .rfilter__clear:hover{color:var(--gold)}
.section--green .rfilter__empty,
.section--green-dark .rfilter__empty{color:rgba(247,242,231,.85)}

/* ================= SOCIAL PROOF ================= */
/* Resized 2026-08-24: 50/50 rather than the mockup's 1.15fr/.85fr, both cards floored at
   520px (440px once stacked), and the quote and play control scaled up to suit.

   THE FLOORS GO ON THE TWO CHILDREN, NOT ON .proof. A min-height on the grid container
   is distributed across its rows, so once the grid collapses to one column at 960px a
   520px container would give two ~260px cards rather than two 520px ones. On the children
   it reads correctly in both layouts: at desktop align-items:stretch equalises them to
   the taller of the two, and stacked each floors independently. */
.proof{display:grid;grid-template-columns:1fr 1fr;gap:clamp(2rem,4vw,3.5rem);align-items:stretch}
.quote-card{background:var(--white);border-radius:6px;padding:clamp(2.4rem,4.6vw,4rem);
  min-height:520px;
  box-shadow:var(--shadow-sm);display:flex;flex-direction:column;justify-content:center;
  border-left:4px solid var(--gold)}
.quote-card .qmark{font-family:var(--serif);font-size:4.8rem;color:var(--gold);line-height:.6;height:2.4rem}
/* The prose default deliberately does not reach .quote-card blockquote p (see the
   "Prose defaults" note), so this font-size is the single source and the paragraphs
   inherit it. Do not add --fhb-prose-size here. */
.quote-card blockquote{margin:0;font-family:var(--serif-soft);font-size:clamp(1.55rem,2.4vw,2.05rem);
  line-height:1.35;color:var(--green);font-style:italic}
.quote-card cite{display:block;margin-top:1.9rem;font-style:normal;font-family:var(--sans);
  font-size:.82rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-soft);font-weight:600}
.quote-card cite b{color:var(--gold-600);display:block;letter-spacing:.12em}
.quote-card .placeholder-note{display:block;font-weight:400;letter-spacing:.04em;text-transform:none;
  margin-top:.4rem;font-family:var(--serif-soft);font-style:italic;color:var(--ink-soft)}
/* No min-height here: the renderer always emits `class="proof__media video"`, so the
   floor is declared once on .proof__media below rather than in both places. */
.video{position:relative;border-radius:6px;overflow:hidden;box-shadow:var(--shadow-sm);
  background:var(--green-900);display:grid;place-items:center;text-align:center;
  color:var(--cream)}
.video img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:.45}
.video__inner{position:relative;z-index:2;padding:2.4rem}
.video__label{font-family:var(--serif);font-size:1.3rem}
.video .play{width:96px;height:96px;border-radius:50%;background:rgba(247,242,231,.15);
  border:2px solid var(--cream);display:grid;place-items:center;margin:0 auto 1.1rem;cursor:pointer;
  transition:.2s}
.video .play:hover{background:var(--gold);border-color:var(--gold)}
.video .play svg{width:34px;height:34px;fill:var(--cream);margin-left:5px}
.video .play:hover svg{fill:var(--green-900)}
.video small{display:block;font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;
  opacity:.75;margin-top:.6rem}
/* .play is a real <button> or <a> in the carousel, not the mockup's div[role=button],
   so the UA's own button styling has to be reset back to the mockup's circle. */
.video .play{padding:0;font:inherit;color:inherit;text-decoration:none}

/* ---- testimonial carousel (fhb/testimonials) ----
   The approved .proof two-column layout above, one testimonial at a time, scrolled
   between. A full-width scroll-snap track rather than hiding all but one slide: no
   slide is ever `hidden`, so there is no focusable content buried behind a control —
   the failure that makes most carousels unusable by keyboard — and swipe, trackpad and
   arrow keys work natively with no JavaScript. main.js only adds the arrows, dots and
   counter, which is why they ship hidden. */
.proof-carousel{position:relative}
.proof-carousel__viewport{overflow-x:auto;overflow-y:hidden;scroll-snap-type:x mandatory;
  scroll-behavior:smooth;scrollbar-width:none;-ms-overflow-style:none;
  /* Vertical only, to let the card shadow bleed out of the scroll clip. Deliberately
     NOT horizontal: inline padding shifts the snapport away from the padding box, so
     the browser's snap position and main.js's offsetLeft maths would disagree by the
     padding on every slide. */
  margin-block:-10px;padding-block:10px}
.proof-carousel__viewport::-webkit-scrollbar{display:none}
.proof-carousel__track{display:flex;align-items:stretch;list-style:none;margin:0;padding:0}
/* Full width, so exactly one testimonial is in view. Equal heights come from
   align-items:stretch above — without it the carousel would change height as you
   scroll between a short quote and a long one, jolting the page. */
.proof-carousel__slide{flex:0 0 100%;scroll-snap-align:start;display:flex}
.proof-carousel__slide > .proof{flex:1 1 auto}
/* The section head above is centred; the testimonial is not. */
.proof-carousel .proof{text-align:left}
/* The quote is post content, so it arrives wrapped in paragraphs. */
.quote-card blockquote p{margin:0}
.quote-card blockquote p + p{margin-top:.8em}

/* Shared panel for the image and the video, so slide proportions don't shift between
   an image testimonial and a video one. Generalised from the mockup's .video rule. */
/* 520px, up from 280px, and the single source of the floor for both media types (the
   video panel carries this class too). It is also the ONLY thing sizing a playing video:
   the facade swap replaces the panel's contents with an absolutely-positioned iframe, so
   there is no in-flow child left to give it height. */
.proof__media{position:relative;margin:0;min-height:520px;border-radius:6px;overflow:hidden;
  box-shadow:var(--shadow-sm);background:var(--green-900)}
/* position:absolute is load-bearing, and is why the mockup's .video img has it too.
   In normal flow the image's natural height sets the panel height, which sets the grid
   row height, which stretches the quote card to match — so a tall or square upload
   inflated the whole slide. Out of flow the panel contributes no intrinsic height: the
   row is sized by the quote (floored by min-height above) and the image covers whatever
   that turns out to be. Any upload dimensions now render identically. */
.proof__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  display:block}
.proof--solo{grid-template-columns:1fr}
.video.is-playing{background:#000}
.video__frame,.video.is-playing iframe{position:absolute;inset:0;width:100%;height:100%;border:0}

/* Centre column holds the controls; the counter sits right without pulling them
   off-centre, which a plain flex row with margin-left:auto would do. */
.proof-carousel__nav{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  margin-top:clamp(1.6rem,3vw,2.2rem)}
.proof-carousel__controls{grid-column:2;display:flex;align-items:center;
  gap:clamp(.8rem,2vw,1.4rem)}
.proof-carousel__count{grid-column:3;justify-self:end;margin:0;font-family:var(--sans);
  font-size:.74rem;font-weight:700;letter-spacing:.16em;color:var(--gold-600)}
.proof-carousel__arrow{width:44px;height:44px;padding:0;border-radius:50%;cursor:pointer;
  border:1.5px solid var(--gold-600);background:transparent;color:var(--gold-600);
  display:grid;place-items:center;transition:.2s ease}
.proof-carousel__arrow svg{width:22px;height:22px;fill:currentColor}
.proof-carousel__arrow:hover:not([disabled]){background:var(--gold);border-color:var(--gold);
  color:var(--green-900)}
.proof-carousel__arrow[disabled]{opacity:.3;cursor:default}
.proof-carousel__dots{display:flex;align-items:center;gap:.2rem}
/* 24px hit area around a 9px dot, so the target clears WCAG 2.5.8 without the
   visual weight of a 24px dot. */
.proof-carousel__dot{width:24px;height:24px;padding:0;border:0;background:transparent;
  cursor:pointer;display:grid;place-items:center}
.proof-carousel__dot::before{content:"";width:9px;height:9px;border-radius:50%;
  border:1.5px solid var(--gold-600);transition:.2s ease}
.proof-carousel__dot[aria-current="true"]::before{background:var(--gold);
  border-color:var(--gold);transform:scale(1.3)}

.section--green .proof-carousel__arrow,
.section--green-dark .proof-carousel__arrow{border-color:rgba(247,242,231,.4);color:var(--cream)}
.section--green .proof-carousel__arrow:hover:not([disabled]),
.section--green-dark .proof-carousel__arrow:hover:not([disabled]){background:var(--gold);
  border-color:var(--gold);color:var(--green-900)}
.section--green .proof-carousel__dot::before,
.section--green-dark .proof-carousel__dot::before{border-color:rgba(247,242,231,.5)}
.section--green .proof-carousel__dot[aria-current="true"]::before,
.section--green-dark .proof-carousel__dot[aria-current="true"]::before{background:var(--gold);
  border-color:var(--gold)}
.section--green .proof-carousel__count,
.section--green-dark .proof-carousel__count{color:var(--gold)}

/* ================= STORE LOCATOR CTA ================= */
.locator{background:var(--green-900);color:var(--cream);text-align:center;
  padding:clamp(4rem,8vw,6rem) 0}
.locator h2{color:var(--cream);font-size:clamp(2rem,4vw,3rem);margin-bottom:1rem}
.locator p{max-width:52ch;margin:0 auto 2rem;color:rgba(247,242,231,.85)}

/* ================= STORE LOCATOR MAP (fhb/store-locator) =================
   READ THIS BEFORE ADDING A RULE HERE.

   The Stockist widget renders inside a CLOSED SHADOW ROOT. Its account is on layout v2
   with css_isolation on, and the script only forgoes the shadow root in a browser with
   no customElements support. So a selector written in this file for anything inside the
   map — `.stockist-result-name`, `#stockist-widget .stockist-search-button` — matches
   NOTHING. It does not error and it does not warn; it silently never applies. That is
   the same "control that lies" failure as shipping align-wide with no .alignwide rules,
   and it is why the brand styling for the map's internals is NOT in this file.

   Three things do cross the shadow boundary, and they are the whole handoff below:

     1. INHERITED PROPERTIES, via the host element. The widget declares no font-family of
        its own (its inputs and buttons are `font:inherit`) and its text colour is
        `var(--stockist-text-color, inherit)`. Setting font-family and color on
        .locator-map__mount is therefore what puts Montserrat and --ink INSIDE the map.
        This is also why the theme needs no dashboard font setting.
     2. THE BOX. Width, position and reserved height are ours. The widget fills what it
        is given and never reaches back out.
     3. Custom properties would inherit as well, but every --stockist-* worth setting is
        already claimed on the far side: --stockist-feature-color, -feature-contrast-color,
        -geolocation-color, -desktop-height, -side-panel-width and -mobile-list-height are
        written INLINE onto the widget root from the dashboard config, and
        --stockist-border-radius / -input-border / -input-height / -form-spacing are
        declared by its own stylesheet on that same root. Inline and same-element rules
        both beat inheritance. Do not try; it reads like it should work and does not.

   Everything else on brand — feature colour, map pins, result fields, input and radius
   styling — belongs to the Stockist dashboard. Its CSS is kept in
   docs/integrations/stockist-custom-css.css and is pasted into Settings → Appearance →
   Advanced settings → Custom CSS. That file is deliberately NOT enqueued: pasted there
   the widget injects it inside the shadow root, after its own stylesheet, which is the
   only place and order in which it applies.
   ===================================================================== */
.locator-map{
  background:var(--white);border:1px solid var(--fhb-hair,rgba(20,52,35,.16));
  border-radius:6px}
/* No overflow:hidden, deliberately, and not an omission to be tidied up: the widget's
   address autocomplete is an absolutely positioned list that can extend past the search
   field's panel, and clipping it here would cut the suggestions off — a functional break
   traded for four tidier corners. The radius stays because the map's own inset panels
   are white on a white band, so the corners it squares off over are not visible. */

/* display:block is load-bearing: <stockist-store-locator> is an unknown element until
   the async script defines it, and an inline element ignores min-height — so without
   this the reserve below does nothing at the exact moment it is needed.

   The reserve is a RESERVE, not a chosen height. The widget writes its real height
   inline onto its root from the dashboard's container height, currently 600px, so this
   cannot resize the map; it exists so that 600px does not arrive as a layout shift when
   the async script builds. Keep it in step with Settings → Appearance → container
   height: too small only costs a smaller shift, too large leaves dead space below.

   font-family and color are the shadow-boundary handoff described above — the map's own
   surfaces are white on every band, so --ink is right even when .locator-map sits in a
   green section and --fhb-body has flipped to cream. */
.locator-map__mount{
  display:block;min-height:600px;font-family:var(--sans);color:var(--ink)}
.locator-map__noscript{margin:0;padding:clamp(2rem,6vw,4rem);text-align:center;
  color:var(--ink-soft)}
/* Below the dashboard's 700px desktop breakpoint the widget switches to its stacked
   mobile layout and derives that height at runtime, so the reserve only approximates. */
@media(max-width:699px){.locator-map__mount{min-height:520px}}

/* ================= NEWSLETTER ================= */
/* The gold band and its text colours now come from .section--gold, so the mockup's
   bespoke .newsletter / .newsletter__inner rules are gone — they were unreachable
   (nothing rendered that class) and duplicated what the section shell already does.
   .nl-* below is the form itself, which is what fhb/newsletter renders. */
.nl-wrap{max-width:480px;margin:0 auto}
.nl-form{display:flex;gap:.7rem;flex-wrap:wrap}
.nl-form input{flex:1;min-width:220px;padding:1em 1.2em;border:1.5px solid var(--green-900);
  border-radius:2px;font-family:var(--sans);font-size:.95rem;background:var(--cream);
  color:var(--ink)}
.nl-form input:focus{outline:2px solid var(--green-900);outline-offset:1px}
/* Shown by main.js when a submit is cancelled, because the form is not wired to a
   provider yet. Deleting this rule is part of the wire-up checklist in the block's
   render.php. */
.nl-form__status{margin:.9rem 0 0;font-family:var(--sans);font-size:.85rem;font-weight:600;
  color:var(--green-900)}
.nl-note{margin:.9rem 0 0;font-size:.8rem;color:#4a3c14}

/* ================= CARD HIGHLIGHT ================= */
/* is-style-fhb-highlight-gold / -green, registered on fhb/product-cards,
   fhb/recipe-cards, fhb/split and fhb/testimonials in inc/blocks.php.
 *
 * The Our Story visual system's card treatment: the blurred drop shadow is replaced by
 * a solid, zero-blur block of colour offset behind the card. The theme already ships
 * this once, inlined on core's quote blocks — see "quote / pullquote" below, which this
 * generalises into --fhb-hl-sm / --fhb-hl-md.
 *
 * A style variation rather than a per-block attribute, per "Style Variations Before New
 * Blocks" in docs/architecture/blocks.md: only the treatment was missing, and
 * get_block_wrapper_attributes() already carries the is-style-* class to the very
 * element each component's CSS hangs off. So there is no block.json, edit.js or
 * render.php change here and nothing to rebuild — which also means build/ cannot go
 * stale behind this feature.
 *
 * BORDER-RADIUS IS DELIBERATELY UNTOUCHED. The mockup squared every corner too; that is
 * a separate visual-system decision, so each component keeps its own radius (.product
 * 6px, .recipe 10px, .quote-card 6px) and only the shadow changes.
 *
 * TWO SIZES, MIRRORING --shadow-sm / --shadow. The cards that lift on hover take the
 * small block at rest and the large one raised, so the block appears to grow as the card
 * moves into it. The static components take the large block outright.
 *
 * COLOUR IS A DOUBLE INDIRECTION, ON PURPOSE. Each variation sets --fhb-hl-c from
 * var(--fhb-hl-gold, …) rather than from var(--gold) directly. Per Rule 1 the default
 * therefore lives in the fallback and a band can remap --fhb-hl-gold from any depth,
 * which it could not do if the component named the colour itself — a component's own
 * declaration beats an ancestor's. */

/* A gold block on a gold band is invisible, and so is a green one on the green bands.
   Each band remaps only the colour that would disappear, so no combination the editor
   can pick renders nothing. Both selector families are covered: fhb/section's own
   bands, and core's palette classes for a Group or Cover coloured in the editor. */
.section--gold,
.has-gold-background-color{--fhb-hl-gold:var(--green-900)}
.section--green,.section--green-dark,.wp-block-cover,
.has-green-background-color,
.has-green-dark-background-color,
.has-ink-background-color{--fhb-hl-green:var(--gold)}

.products.is-style-fhb-highlight-gold,
.recipes-wrap.is-style-fhb-highlight-gold,
.split.is-style-fhb-highlight-gold,
.proof-carousel.is-style-fhb-highlight-gold{--fhb-hl-c:var(--fhb-hl-gold,var(--gold))}
.products.is-style-fhb-highlight-green,
.recipes-wrap.is-style-fhb-highlight-green,
.split.is-style-fhb-highlight-green,
.proof-carousel.is-style-fhb-highlight-green{--fhb-hl-c:var(--fhb-hl-green,var(--green))}

/* ---- product cards ---- */
/* The hairline carries the definition the dropped blur was providing: on a --white band
   a white card with neither has no visible top or left edge, the bottom and right being
   drawn by the block itself. --fhb-hair, not a new colour — the card reset above puts
   the dark value back on light cards inside dark bands. */
.products:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .product{
  border:1px solid var(--fhb-hair,rgba(20,52,35,.16));
  box-shadow:var(--fhb-hl-sm) var(--fhb-hl-sm) 0 var(--fhb-hl-c)}
/* translate, not translateY: the two-axis lift moves the card up and left into its
   block, which is what makes the block read as growing rather than sliding. */
.products:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .product:hover{
  transform:translate(-4px,-4px);
  box-shadow:var(--fhb-hl-md) var(--fhb-hl-md) 0 var(--fhb-hl-c)}

/* ---- recipe cards ---- */
/* is-style-* lands on .recipes-wrap, the block wrapper, not on the .recipes grid inside. */
.recipes-wrap:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .recipe{
  border:1px solid var(--fhb-hair,rgba(20,52,35,.16));
  box-shadow:var(--fhb-hl-sm) var(--fhb-hl-sm) 0 var(--fhb-hl-c)}
.recipes-wrap:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .recipe:hover{
  transform:translate(-5px,-5px);
  box-shadow:var(--fhb-hl-md) var(--fhb-hl-md) 0 var(--fhb-hl-c)}
/* :focus-within is a separate rule on .recipe that swaps in the blurred --shadow, so it
   needs covering too or tabbing to a card drops it back to the wrong shadow language.
   The gold outline it also sets is left alone. */
.recipes-wrap:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .recipe:focus-within{
  box-shadow:var(--fhb-hl-md) var(--fhb-hl-md) 0 var(--fhb-hl-c)}

/* ---- split (heritage image) ---- */
/* Static, and on the <img> rather than .split__media, so the block hugs the photograph
   and not the positioning context the stamp is placed against. */
.split:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .split__media img{
  box-shadow:var(--fhb-hl-md) var(--fhb-hl-md) 0 var(--fhb-hl-c)}
/* The "Since 1745" stamp is placed bottom-right, the exact corner the block occupies, so
   it moves to the opposite corner here. Moved rather than hidden: the block's Show stamp
   toggle has to keep meaning something. */
.split:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .split__media .stamp{
  right:auto;left:-10px}

/* ---- testimonial quote card ---- */
/* Small block, static. The 4px gold left rule goes: beside a gold block it reads as a
   second, competing accent. */
.proof-carousel:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .quote-card{
  border:1px solid var(--fhb-hair,rgba(20,52,35,.16));
  box-shadow:var(--fhb-hl-sm) var(--fhb-hl-sm) 0 var(--fhb-hl-c)}
/* The carousel viewport is a scroll clip. Its vertical bleed exists so the card shadow
   can escape (see .proof-carousel__viewport) and was sized for the blurred shadow, so it
   is widened here to clear the block. Vertical only — the note there is emphatic that
   INLINE padding must never be added, because it shifts the snapport away from the
   padding box and main.js's offsetLeft maths would then disagree with the browser's snap
   position on every slide. */
.proof-carousel:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .proof-carousel__viewport{
  margin-block:-20px;padding-block:20px}
/* Horizontally the block normally sits in .proof's column gap, which is always wider
   than it. A quote with no media is the exception: .proof--solo is a single full-width
   column, so the block would land on the slide's right edge and be clipped. Pulled
   inside the card's own box instead of padding the scroller, per the note above. */
.proof-carousel:is(.is-style-fhb-highlight-gold,.is-style-fhb-highlight-green) .proof--solo .quote-card{
  margin-right:var(--fhb-hl-md)}

/* ================= EDITORIAL TREATMENTS ================= */
/* is-style-fhb-statement | -display | -fact | -flush | -numerals, registered in
   inc/blocks.php. All five come from the Whey Butter Difference mockup (v2) and are
   variations rather than blocks, per "Style Variations Before New Blocks" — each is a
   treatment applied to copy or an image core already models.

   NONE OF THESE DECLARES A TEXT COLOUR, and that is the point. The prose default sits at
   (0,0,0) reading var(--fhb-body,inherit) and already reaches into .wp-block-group,
   .wp-block-column and .wp-block-media-text__content, so leaving colour alone means
   these read correctly on a cream band and on a deep green one with no per-band rules.
   Setting a literal colour here is exactly the bug documented at .is-style-fhb-lead. */

/* ---- statement paragraph ----
   Cormorant at display scale, for the mockup's intro lead, section intros and chef's
   notes. Distinct from fhb-lead: that one opens a page and is smaller, this is body copy
   used repeatedly down a long page. (0,1,0), so it beats the (0,0,0) prose size. */
.is-style-fhb-statement{
  font-family:var(--serif-soft);font-size:clamp(1.35rem,2.4vw,1.75rem);line-height:1.48
}

/* ---- display heading ----
   The theme's nested :where(h2) is clamp(1.7rem,3.2vw,2.4rem), deliberately a
   sub-heading size, so a heading inside a Group or Column cannot reach display scale.
   Weight 500 and negative tracking match the rebuilt page-hero h1 — at this size
   Playfair's 600 is too heavy and its default tracking too loose. */
.is-style-fhb-display{
  font-size:clamp(2.4rem,5vw,3.8rem);font-weight:500;line-height:1.08;letter-spacing:-.03em
}

/* ---- fact card ----
   A dark aside that tracks the reader down a long column, with the offset block behind
   it. Reuses the CARD HIGHLIGHT tokens rather than restating a shadow, so the band
   remaps apply here too: on a gold band --fhb-hl-gold is already green-900.

   Background and text colour come from the block's OWN palette background, which emits
   .has-*-background-color and with it the cream/gold token flips — so this variation
   never names a colour and works on whichever background the editor picks.

   position:sticky needs an ancestor with room to travel: do NOT set vertical alignment
   on the wrapping column, because align-self:center collapses it to content height and
   sticky then has nowhere to go. */
.is-style-fhb-fact{
  position:sticky;top:2rem;text-align:left;
  padding:clamp(1.8rem,3.4vw,2.9rem);
  box-shadow:var(--fhb-hl-md) var(--fhb-hl-md) 0 var(--fhb-hl-gold,var(--gold))
}
.is-style-fhb-fact :where(h2,h3,h4){
  font-size:clamp(1.6rem,3vw,2.1rem);font-weight:500;line-height:1.16
}
.is-style-fhb-fact{--fhb-prose-size:.85rem}

/* ---- flush image card ----
   The mockup's bakery rows: a bordered card whose image runs flush to the edge and fills
   the full height, alternating side to side. On core/media-text, NOT on fhb/split —
   split's render.php emits its own .section > .wrap, so five stacked splits would stack
   five bands of section padding against the mockup's single band.

   Overrides core's own gap and 8% content padding, and resets the theme's blurred,
   6px-rounded media-text image (see the CORE BLOCK LAYER) to square and flat: the card's
   border is the edge now. */
.wp-block-media-text.is-style-fhb-flush{
  align-items:stretch;gap:0;
  background:var(--fhb-card-bg,var(--cream));
  border:1px solid var(--fhb-hair,rgba(20,52,35,.16))
}
.wp-block-media-text.is-style-fhb-flush > .wp-block-media-text__media{
  align-self:stretch;height:100%
}
/* 290px flat, not the clamp(290px,32vw,460px) this carried until 2026-08-24. As a clamp
   it was the card's effective desktop floor AND it shrank with the viewport while the copy
   did not, so between ~1120px (where the type saturates) and ~1437px (where the image
   did) the five rows drifted apart by up to 66px. The desktop floor now lives on the card
   below; this value only governs the stacked layout, where 290px was already the clamp's
   lower bound. */
.wp-block-media-text.is-style-fhb-flush > .wp-block-media-text__media img{
  border-radius:0;box-shadow:none;
  width:100%;height:100%;object-fit:cover;min-height:290px
}
/* line-height 1.5 rather than the 1.65 inherited from body: generous for .93rem type in a
   ~465px measure, and the ~25px it frees on the longest row is part of what lets every
   card settle on the 460px floor rather than one of them overshooting it. The h3 and the
   numeral set their own line-heights, so neither is touched. */
.wp-block-media-text.is-style-fhb-flush > .wp-block-media-text__content{
  align-self:center;padding:clamp(2rem,4.2vw,3.5rem);line-height:1.5
}
/* :where(p){margin:0 0 1rem} leaves a dead 1rem inside the padding box under the last
   paragraph. The theme already zeroes it for .timeline__body, .info-card and
   .info-card__body; media-text was simply missed. */
.wp-block-media-text.is-style-fhb-flush > .wp-block-media-text__content > :last-child{
  margin-bottom:0
}
.wp-block-media-text.is-style-fhb-flush :where(h2,h3,h4){
  font-size:clamp(1.8rem,3vw,2.5rem);font-weight:500;line-height:1.15;margin-bottom:1.1rem
}
.wp-block-media-text.is-style-fhb-flush{--fhb-prose-size:.93rem}

/* 460px, so the five bakery rows share one bottom edge. THE FLOOR IS ON THE CARD, not on
   the image as it was before — see the note on the image above for why a clamp there could
   not hold a consistent height.

   min-height, NOT height. These cards set no overflow and their content is
   align-self:center, so a fixed height would push a long row's copy out through BOTH the
   top and bottom borders onto the band; overflow:hidden would clip the numeral along with
   the text. A floor degrades gracefully instead: the worst case is one card a few pixels
   taller, never copy that is cut off. The three spacing reclaims above exist to keep the
   longest row (row 01, 379 characters — 1.9x the next longest) under this value.

   Guarded on min-width:601px because that is core's own is-stacked-on-mobile breakpoint —
   deliberately core's number rather than one of the theme's 680/820/960, since this has to
   align with the query that actually changes the layout. Stacked, image plus copy exceeds
   460px anyway. */
@media(min-width:601px){
  .wp-block-media-text.is-style-fhb-flush{min-height:460px}
}

/* THE NUMERAL IS A COUNTER, NOT SAVED TEXT — the same rule the timeline documents at
   length in src/timeline-item/render.php. Reordering or deleting a row renumbers the
   sequence, and nobody can save 01, 02, 02, 04. Reset per band rather than per page, so
   a second run of rows further down starts at 01 again. */
.section > .wrap{counter-reset:fhb-res}
.wp-block-media-text.is-style-fhb-flush{counter-increment:fhb-res}
.wp-block-media-text.is-style-fhb-flush > .wp-block-media-text__content::before{
  content:counter(fhb-res,decimal-leading-zero);display:block;
  font-family:var(--serif);font-size:clamp(2.4rem,5vw,3.5rem);font-weight:600;
  line-height:1;color:var(--gold);margin-bottom:.5rem
}

/* A separator inside a column follows that column's copy. The base rule centres it with
   auto margins, which reads as detached beside left-aligned text — but stays correct
   inside a centred section, hence the :not(). */
.section:not(.section--centered) .wp-block-column > .wp-block-separator{margin-left:0}

/* ---- info-cards numerals ----
   The grid variant prints upper-roman by default. (0,3,0) against the base rule's
   (0,2,0). The min-height that levels headings across a row is also relaxed: it exists
   for two-line headings and leaves a large gap under one-word ones. */
.info-cards.is-style-fhb-numerals.info-cards--grid > .info-card::before{
  content:counter(fhb-ic,decimal-leading-zero)
}
.info-cards.is-style-fhb-numerals.info-cards--grid > .info-card h3{min-height:0}

/* ================= PAGE / ARTICLE ================= */
.page-content{padding:clamp(3rem,6vw,5rem) 0;max-width:760px}
.page-content h1{font-size:clamp(2.2rem,5vw,3.2rem);margin-bottom:1.5rem}
.page-content h2{font-size:clamp(1.6rem,3vw,2.2rem);margin:2.4rem 0 1rem}
.page-content ul,.page-content ol{padding-left:1.4rem}
.page-content blockquote{border-left:3px solid var(--gold);margin:2rem 0;padding-left:1.4rem;
  font-family:var(--serif-soft);font-style:italic;font-size:1.25rem;color:var(--green)}
/* core/pullquote is <figure class="wp-block-pullquote"><blockquote>, so the rule above
   (0,1,1) matches its inner element and stacks a gold LEFT border and left padding onto
   core's centred, top/bottom-bordered pullquote — a broken hybrid. Reset it here at
   (0,3,1); the pullquote's own treatment is in the core block layer below. */
.page-content .wp-block-pullquote blockquote{border-left:0;padding-left:0;margin:0;
  font-size:inherit}

/* ================= FOOTER ================= */
.site-footer{background:var(--green-900);color:rgba(247,242,231,.85);
  padding:clamp(3.5rem,7vw,5rem) 0 0}
.footer-grid{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:2.5rem;
  padding-bottom:3rem;border-bottom:1px solid rgba(247,242,231,.14)}
.footer-brand img{height:70px;margin-bottom:1.2rem}
.footer-brand p{font-size:.92rem;max-width:34ch;color:rgba(247,242,231,.7)}
.footer-col h4{color:var(--gold);font-family:var(--sans);font-size:.74rem;font-weight:700;
  letter-spacing:.18em;text-transform:uppercase;margin-bottom:1.2rem}
/* The Contact column's second heading, above the social row. Was an inline style on the
   element until the column moved behind fhb_footer_menu_column() on 2026-08-24. */
.footer-col__follow{margin-top:1.8rem}
.footer-col ul{list-style:none;margin:0;padding:0}
.footer-col li{margin-bottom:.7rem}
.footer-col a{font-size:.95rem;color:rgba(247,242,231,.82);transition:.2s}
.footer-col a:hover{color:var(--gold);padding-left:3px}
.footer-social{display:flex;gap:.9rem;margin-top:1.4rem}
.footer-social a{width:40px;height:40px;border-radius:50%;border:1px solid rgba(247,242,231,.3);
  display:grid;place-items:center;color:var(--cream);transition:.2s}
.footer-social a:hover{background:var(--gold);border-color:var(--gold);color:var(--green-900)}
.footer-social svg{width:18px;height:18px;fill:currentColor}
.footer-bottom{display:flex;align-items:center;justify-content:space-between;gap:1rem;
  padding:1.6rem 0;flex-wrap:wrap;font-size:.8rem;color:rgba(247,242,231,.6)}
.footer-bottom .links{display:flex;gap:1.4rem;flex-wrap:wrap;list-style:none;margin:0;padding:0}

/* ================= MODAL (nutrition) ================= */
.modal-overlay{position:fixed;inset:0;background:rgba(15,35,24,.72);z-index:100;
  display:none;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(3px)}
.modal-overlay.open{display:flex}
.modal{background:var(--white);border-radius:6px;max-width:420px;width:100%;
  max-height:90vh;overflow:auto;box-shadow:var(--shadow);animation:pop .25s ease}
@keyframes pop{from{opacity:0;transform:translateY(14px) scale(.98)}to{opacity:1;transform:none}}
.modal__head{background:var(--green);color:var(--cream);padding:1.4rem 1.6rem;
  display:flex;align-items:center;justify-content:space-between;position:sticky;top:0}
.modal__head h3{color:var(--cream);font-size:1.35rem}
.modal__head small{display:block;font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold);margin-top:.2rem}
.modal__close{background:none;border:0;color:var(--cream);cursor:pointer;font-size:1.6rem;
  line-height:1;padding:4px 8px;border-radius:3px}
.modal__close:hover{background:rgba(247,242,231,.15)}
.nf{padding:1.4rem 1.6rem 1.8rem;font-family:var(--sans)}
.nf__title{font-family:var(--serif);font-size:2rem;font-weight:700;border-bottom:9px solid var(--ink);
  padding-bottom:.2rem;margin-bottom:.3rem;color:var(--ink)}
.nf__serv{display:flex;justify-content:space-between;font-size:.85rem;padding:.35rem 0;
  border-bottom:1px solid var(--ink)}
.nf__cal{display:flex;align-items:flex-end;justify-content:space-between;
  border-bottom:5px solid var(--ink);padding:.3rem 0}
.nf__cal b{font-size:2rem;font-family:var(--serif)}
.nf__row{display:flex;justify-content:space-between;font-size:.9rem;padding:.32rem 0;
  border-bottom:1px solid #ccc}
.nf__row.indent{padding-left:1.2rem;font-weight:400}
.nf__row b{font-weight:700}
.nf__row .dv{font-weight:700}
.nf__note{font-size:.72rem;color:var(--ink-soft);margin-top:.9rem;line-height:1.4}
.nf__placeholder{background:var(--cream-2);color:var(--gold-600);font-size:.66rem;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;text-align:center;padding:.5em;border-radius:3px;margin-bottom:1rem}

/* ================= CORE BLOCK LAYER =================
   Brand treatment for the core blocks an editor will actually reach for. Added
   2026-08-22; see the three rules at the top of this file.

   Not here on purpose: Group and Columns need no structural CSS — core's own layout
   handles them and their gap now reads theme.json's blockGap (Rule 2, do not write
   nested flow rules). Gallery, Video, Audio, Embed and Query Loop keep core defaults.

   Colours are tokens throughout (Rule 1), so every block below stays legible on cream,
   deep cream, gold, forest green, deep green, and inside a core Cover. */

/* ---- media ---- */
.wp-block-image img{border-radius:6px}
.wp-block-media-text__media img{border-radius:6px;box-shadow:var(--shadow)}
.wp-block-image figcaption,
.wp-block-embed figcaption,
.wp-block-table figcaption,
.wp-block-audio figcaption,
.wp-block-video figcaption{
  font-family:var(--sans);font-size:.78rem;letter-spacing:.04em;
  color:var(--fhb-body,var(--ink-soft));text-align:center;margin-top:.7rem
}

/* ---- quote / pullquote ----
   Rebuilt 2026-08-22 from the approved Our Heritage mockup: a solid card with an
   offset gold block behind it and an oversized quote mark straddling the top edge.
   Both core quote blocks get it, because an editor reaching for "Quote" and one
   reaching for "Pull quote" should not land on two different brand treatments.

   The theme's own .quote-card (the testimonial carousel) is a separate approved
   design and is deliberately not touched.

   FOUR TOKENS, NOT A FIXED GREEN CARD. The mockup's card is green on a cream band,
   which inverts to invisible the moment it lands on --green. Per Rule 1 the colours
   are tokens with fallbacks, so the DEFAULT lives in the var() fallback and a band
   can override it from any depth. Setting the defaults on the component instead
   would make it unoverridable — a component's own declaration beats an ancestor's. */
.wp-block-quote:not(.is-style-plain),
.wp-block-pullquote{
  position:relative;margin:0;border:0;
  padding:clamp(1.8rem,4vw,3.4rem);
  /* Left, even inside .section--centered — a centred slab of 1.6rem serif is
     unreadable, and the mockup sets it left. */
  text-align:left;
  background:var(--fhb-qcard-bg,var(--green));
  color:var(--fhb-qcard-fg,var(--cream));
  box-shadow:clamp(8px,1.6vw,18px) clamp(8px,1.6vw,18px) 0 var(--fhb-qcard-shadow,var(--gold))
}
/* The mark overhangs the top edge on purpose. Anything stacked directly above needs
   clearance or the glyph lands on it; blockGap alone is not enough. Skipped when the
   quote is first in its container, which is the common case in a two-column layout. */
.wp-block-quote:not(.is-style-plain):not(:first-child),
.wp-block-pullquote:not(:first-child){margin-top:2.4rem}
.wp-block-quote:not(.is-style-plain)::before,
.wp-block-pullquote::before{
  content:"\201C";position:absolute;top:-.38em;left:.17em;
  font-family:var(--serif);font-size:clamp(5rem,9vw,9rem);line-height:1;
  color:var(--fhb-qcard-accent,var(--gold));pointer-events:none
}
/* position:relative lifts the text above the mark's glyph box. Real class
   specificity per Rule 3 — this is a variant, not a default. */
.wp-block-quote:not(.is-style-plain) > p,
.wp-block-pullquote blockquote > p{
  position:relative;font-family:var(--serif-soft);font-style:normal;
  font-size:clamp(1.25rem,2.4vw,1.65rem);line-height:1.38;color:inherit;margin:0 0 1rem
}
.wp-block-quote:not(.is-style-plain) > p:last-of-type,
.wp-block-pullquote blockquote > p:last-of-type{margin-bottom:0}
.wp-block-pullquote blockquote{margin:0}
.wp-block-quote:not(.is-style-plain) cite,
.wp-block-pullquote cite{
  position:relative;display:block;margin-top:1.4rem;font-style:normal;
  font-family:var(--sans);font-size:.7rem;font-weight:600;line-height:1.5;
  letter-spacing:.09em;text-transform:uppercase;
  color:var(--fhb-qcard-accent,var(--gold))
}

/* Band inversions. On the green bands the default green card would disappear, so it
   flips to cream; on gold the shadow needs a dark colour or it vanishes into the
   background, which is why the shadow is a separate token from the accent. */
.section--green,.section--green-dark,.wp-block-cover{
  --fhb-qcard-bg:var(--cream);--fhb-qcard-fg:var(--ink);
  --fhb-qcard-accent:var(--gold-600);--fhb-qcard-shadow:var(--gold)
}
.section--gold{
  --fhb-qcard-bg:var(--green-900);--fhb-qcard-fg:var(--cream);
  --fhb-qcard-accent:var(--gold);--fhb-qcard-shadow:var(--green)
}

/* Core's "Plain" quote style keeps the old inline treatment, so there is still a
   lightweight option for a quotation inside running prose. */
.wp-block-quote.is-style-plain{
  border-left:3px solid var(--gold);padding-left:1.4rem;margin:0;background:none;
  font-family:var(--serif-soft);font-style:italic;font-size:1.25rem;
  color:var(--fhb-quote,var(--green))
}
.wp-block-quote.is-style-plain cite{
  display:block;margin-top:1rem;font-style:normal;font-family:var(--sans);
  font-size:.8rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:var(--fhb-body,var(--ink-soft))
}
.section--green,.section--green-dark,.wp-block-cover{--fhb-quote:var(--cream)}
.section--gold{--fhb-quote:var(--green-900)}

/* ---- lists ---- */
.wp-block-list{padding-left:1.4rem}
.wp-block-list li{margin-bottom:.4rem}
.wp-block-list li:last-child{margin-bottom:0}
/* Mirrors .recipe-detail__instructions li::marker. */
.wp-block-list li::marker{color:var(--gold-600)}
.section--green .wp-block-list li::marker,
.section--green-dark .wp-block-list li::marker{color:var(--gold)}

/* ---- table ---- */
.wp-block-table table{width:100%;border-collapse:collapse;font-size:.94rem}
.wp-block-table th{
  background:var(--green);color:var(--cream);font-family:var(--sans);font-size:.72rem;
  font-weight:700;letter-spacing:.1em;text-transform:uppercase;text-align:left
}
.wp-block-table th,
.wp-block-table td{border:1px solid var(--cream-2);padding:.7em .9em}
.wp-block-table.is-style-stripes tbody tr:nth-child(odd){background:var(--cream-2)}
.wp-block-table.is-style-stripes th,
.wp-block-table.is-style-stripes td{border-color:transparent}
/* On a dark band the cell borders would otherwise be near-invisible cream-2 on green. */
.section--green .wp-block-table th,
.section--green .wp-block-table td,
.section--green-dark .wp-block-table th,
.section--green-dark .wp-block-table td{border-color:rgba(247,242,231,.25)}

/* ---- separator ----
   The theme's own divider is .rule (gold hairlines flanking a shamrock), emitted by
   fhb/section's showRule attribute. This is the core block's plainer equivalent. */
.wp-block-separator{
  border:0;border-top:1px solid var(--gold);opacity:1;
  width:120px;margin-left:auto;margin-right:auto
}
.wp-block-separator.is-style-wide{width:100%}
.wp-block-separator.has-background{border-top-width:2px}

/* ---- code ---- */
.wp-block-code,
.wp-block-preformatted{
  background:var(--cream-2);border-left:3px solid var(--gold);padding:1rem 1.2rem;
  font-size:.9rem;overflow-x:auto;color:var(--ink)
}

/* ---- details / accordion ----
   core/details is the certainty; a native core/accordion may or may not exist on the
   WordPress version in use, so its selectors are included and are simply inert if the
   block is never registered.

   NOTE: neither satisfies docs/specs/faq.md. That spec requires aria-expanded — which
   <details>/<summary> does not expose, the state being carried by the `open` attribute
   — and FAQPage structured data, which neither block emits. The FAQ page still needs
   its own fhb/faq block; this styling does not close that item. */
.wp-block-details,
.wp-block-accordion-item{
  background:var(--white);border:1px solid var(--cream-2);border-radius:6px;
  padding:1rem 1.2rem
}
.wp-block-details summary,
.wp-block-accordion-heading{
  font-family:var(--sans);font-weight:600;font-size:.95rem;color:var(--green);
  cursor:pointer;list-style:none;display:flex;align-items:center;
  justify-content:space-between;gap:1rem
}
.wp-block-details summary::-webkit-details-marker{display:none}
.wp-block-details summary::after{
  content:"";width:.5em;height:.5em;flex:0 0 auto;
  border-right:1.5px solid var(--gold-600);border-bottom:1.5px solid var(--gold-600);
  transform:rotate(45deg);transition:transform .2s ease
}
.wp-block-details[open] summary::after{transform:rotate(-135deg)}
.wp-block-details summary:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.wp-block-details > :not(summary){margin-top:.9rem}

/* ================= RESPONSIVE ================= */
@media(max-width:960px){
  .benefits{grid-template-columns:repeat(3,1fr);gap:1.6rem 1rem}
  .info-cards--3,.info-cards--4{grid-template-columns:repeat(2,1fr)}
  .recipes,.recipes--3{grid-template-columns:repeat(2,1fr)}
  .products--3{grid-template-columns:repeat(2,1fr)}
  .footer-grid{grid-template-columns:1fr 1fr}
  .proof{grid-template-columns:1fr}
  /* Stacked, each card carries its own floor — two 520px cards one above the other is
     more scrolling than a testimonial warrants, and the quote has the full width now. */
  .quote-card,.proof__media{min-height:440px}
}
@media(max-width:820px){
  .header-actions .social{display:none}
  .nav-toggle{display:block}
  .main-nav{position:fixed;inset:0 0 0 auto;width:min(85vw,340px);background:var(--cream);
    flex-direction:column;align-items:stretch;padding:6rem 1.6rem 2rem;transform:translateX(100%);
    transition:transform .3s ease;box-shadow:var(--shadow);overflow:auto;z-index:70}
  .main-nav.open{transform:translateX(0)}
  .main-nav > ul{flex-direction:column;align-items:stretch;gap:0;width:100%}
  .main-nav > ul > li{border-bottom:1px solid var(--cream-2)}
  .main-nav a{display:block;padding:1em 0}
  .main-nav .menu-item-has-children > a::before{float:right;margin-top:.4em}
  .main-nav .sub-menu{position:static;transform:none;opacity:1;visibility:visible;box-shadow:none;
    background:transparent;border-top:0;padding:0 0 .6rem 1rem;min-width:0}
  .main-nav .sub-menu a{padding:.55em 0}
  .mobile-social{display:flex;gap:1.2rem;padding-top:1.5rem;margin-top:.5rem}
}
@media(min-width:821px){.mobile-social{display:none}}
@media(max-width:680px){
  /* The ellipse is vw-sized while the band is not, so as the viewport narrows it climbs
     relative to a fixed 280px band and its arc reaches under the now left-aligned
     headline — where it is --green behind --green h1 text, i.e. invisible copy. The
     gradient and the gold bloom carry the treatment on their own at this width. */
  .page-hero--tone-light::after{display:none}
  /* Not enough width for the counter to sit beside centred controls, so drop it
     underneath rather than squeezing the arrows off-centre. */
  .proof-carousel__nav{grid-template-columns:1fr;justify-items:center;gap:.9rem}
  .proof-carousel__controls{grid-column:1}
  .proof-carousel__count{grid-column:1;justify-self:center}
  .products,.products--1,.products--2,.products--3{grid-template-columns:1fr}
  .split{grid-template-columns:1fr}
  .split__media{order:-1;max-width:460px;margin:0 auto 1rem}
  .recipes,.recipes--1,.recipes--2,.recipes--3{grid-template-columns:1fr}
  .footer-grid{grid-template-columns:1fr;gap:2rem}
  .benefits{grid-template-columns:1fr 1fr}
  .info-cards--2,.info-cards--3,.info-cards--4{grid-template-columns:1fr}
  /* Nothing left to level against once the cards stack. */
  .info-cards--grid > .info-card h3{min-height:0}
  /* Spine moves to the left edge and every item hangs off it — the alternating
     layout needs two columns of width it no longer has. */
  .timeline::before{left:6px}
  .timeline__item,
  .timeline__item:nth-child(even){width:100%;margin-left:0;padding:0 0 2.4rem 2.6rem}
  .timeline__item::after,
  .timeline__item:nth-child(even)::after{left:-1px;right:auto}
}
.skip-link{position:absolute;left:-999px;top:0;background:var(--gold);color:var(--green-900);
  padding:.7em 1.2em;z-index:200;font-weight:700}
.skip-link:focus{left:8px;top:8px}
:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.nav-backdrop{display:none;position:fixed;inset:0;background:rgba(15,35,24,.4);z-index:65}
.nav-backdrop.open{display:block}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  /* The blanket rule below covers animation and transition, not scroll-behavior, and
     the carousel's arrows and dots scroll it programmatically. */
  .proof-carousel__viewport{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.01ms !important;transition-duration:.01ms !important}
}
