/*! Green Pasture Farms design system — built 2026-09-12T08:30:24Z
 *  Source: GPFSeo/frontend/css/. Do not edit this file directly.
 */

/* ---- css/tokens.css ---- */
/* ==========================================================================
   Green Pasture Farms — design tokens
   --------------------------------------------------------------------------
   Wave 1, step 1. See reports/seo-analysis.md §4l for how every value here was
   derived, and reports/TODO.md for the wave plan.

   HARD RULE: nothing in frontend/css/ may depend on a `.uagb-*` (Spectra) or
   Astra Pro class. The January step removes Spectra; if this layer leans on it,
   that step becomes a second redesign instead of a deletion. Astra core classes
   (`.ast-*`), WordPress core classes (`.wp-block-*`, `.entry-content`) and
   WooCommerce classes (`.woocommerce`, `.button`) are all fine — those stay.
   ========================================================================== */

:root {

  /* --- Palette -----------------------------------------------------------
     Unchanged from Astra's global colours. §4l: the palette is coherent and
     there is no case for changing it. Mirrored here as named tokens so the new
     layer never has to reach for --ast-global-color-N, which is Astra's to
     rename. `#77ac28` is load-bearing twice: it is also one of the three badge
     colours §4a requires be preserved out of the mega-menu block.            */

  --gpf-olive:          #66771b;   /* was --ast-global-color-0 */
  --gpf-grass:          #77ac28;   /* was --ast-global-color-1 */
  --gpf-burgundy:       #9b2627;   /* was --ast-global-color-2 */
  --gpf-ink:            #3a3a3a;   /* was --ast-global-color-3 */
  --gpf-ink-deep:       #242424;
  --gpf-surface:        #ffffff;
  --gpf-surface-sunk:   #f5f5f5;
  --gpf-surface-cool:   #f2f5f7;
  --gpf-rule:           #e2e2dc;

  /* Derived, for state and emphasis. Kept as explicit values rather than
     colour functions so they render identically in every browser we support. */
  --gpf-olive-dark:     #4d5a14;
  --gpf-grass-dark:     #628f1f;
  --gpf-burgundy-dark:  #7d1e1f;

  /* --- Semantic roles ------------------------------------------------------
     Set by direction C ("Modern Grocer"), chosen 2026-09-08: white ground,
     olive chrome, grass green for actions, burgundy retired from the palette
     except as the focus ring. Change these, not the raw hues, when re-skinning.
     Note that burgundy is NOT retired from the site by these tokens alone —
     Astra still colours headings with it until theme.css is applied.          */

  --gpf-text:           #1e2a17;   /* warmer and darker than Astra's #3a3a3a */
  --gpf-text-muted:     #79826f;
  --gpf-text-quiet:     #4f6117;   /* olive text on light ground, meets 4.5:1 */
  --gpf-link:           #4f6117;
  --gpf-link-hover:     var(--gpf-olive);
  --gpf-accent:         var(--gpf-grass);
  --gpf-focus:          var(--gpf-burgundy);

  --gpf-chrome:         var(--gpf-olive);      /* header bar */
  --gpf-chrome-text:    #e2ebcc;
  --gpf-surface-cool-2: #f4f6f2;               /* sub-bar, hero panel */
  --gpf-rule-cool:      #e3e7dd;

  /* The disc behind a tick. Decoration only — it never carries text, so it is
     not held to the 4.5:1 floor, and the tick drawn on it is grass-dark. */
  --gpf-accent-wash:    #e6efd4;

  /* Low stock is a warning, not an error, and must not read as "out of
     stock" — a different hue from --gpf-out-of-stock on purpose. */
  --gpf-low-stock:      #8c5a15;

  /* Stock states. §2/§4l: ~41% of the catalogue is out of stock at any moment,
     so this is a primary state, not an edge case, and it needs real tokens. */
  --gpf-in-stock:       var(--gpf-grass);

  /* Corrected 2026-09-09, from #8a8a83. That grey carries white text on the
     card badge and the disabled Add-to-basket, and is itself text on white in
     `.gpf-stock--out` — three uses, all at 3.47:1, all below the 4.5:1 floor
     base.css §5 states in the course of fixing a *different* contrast bug. It
     was found when the badge gained a second line. #767670 is 4.57:1 both
     ways and is not perceptibly a different grey. */
  --gpf-out-of-stock:   #767670;
  --gpf-out-of-stock-hover: #63635e;   /* badge hover; 6.0:1 on white */

  /* --- Type --------------------------------------------------------------
     Amatic SC is loaded at weight 400 ONLY (five subsets, all 400 — verified
     2026-09-08). Never set a bold weight on it: the browser would synthesise
     one, and synthetic bold on a light condensed face looks broken. The
     display levels below pin font-weight:400 for exactly this reason.        */

  --gpf-font-display:   'Amatic SC', 'Trebuchet MS', 'Segoe UI', sans-serif;
  --gpf-font-text:      -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
                        Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;

  /* Fluid scale, expressed once with clamp() instead of three times across
     breakpoints. §4l found the current scale inverts twice — h2 is larger than
     h1 on desktop AND on phones, and correct only on tablets — because three
     values were set at three breakpoints and never reconciled. A single fluid
     declaration per level makes that class of bug unrepresentable.

     Root font-size is 93.75% (15px), so 1rem = 15px. Resolved sizes:
              375px viewport      1200px viewport
       h1     36.0px              54.0px
       h2     28.5px              40.5px
       h3     22.5px              28.5px
       h4     18.3px              21.3px
     Monotonically decreasing at every width in between.                      */

  --gpf-h1:   clamp(2.4rem,  1.80rem + 2.40vw, 3.60rem);
  --gpf-h2:   clamp(1.9rem,  1.50rem + 1.60vw, 2.70rem);
  --gpf-h3:   clamp(1.5rem,  1.30rem + 0.80vw, 1.90rem);
  --gpf-h4:   clamp(1.22rem, 1.13rem + 0.36vw, 1.42rem);
  --gpf-h5:   1.13rem;
  --gpf-h6:   1rem;

  --gpf-text-sm:   0.87rem;
  --gpf-text-lead: 1.13rem;

  --gpf-leading-tight: 1.12;   /* display face; Amatic SC has little x-height */
  --gpf-leading-snug:  1.28;
  --gpf-leading-body:  1.62;

  --gpf-tracking-display: 0.01em;
  --gpf-tracking-caps:    0.08em;

  /* --- Space -------------------------------------------------------------
     A 4px-based ramp. Deliberately short: a long ramp invites arbitrary picks. */

  --gpf-space-2xs: 0.25rem;
  --gpf-space-xs:  0.5rem;
  --gpf-space-sm:  0.75rem;
  --gpf-space-md:  1.25rem;
  --gpf-space-lg:  2rem;
  --gpf-space-xl:  3.25rem;
  --gpf-space-2xl: 5rem;

  /* --- Measure and containers -------------------------------------------- */

  --gpf-container:  1200px;   /* matches --ast-normal-container-width */
  --gpf-measure:    68ch;     /* readable line length for body copy */

  /* --- Surface treatment -------------------------------------------------- */

  --gpf-radius-sm:  3px;
  --gpf-radius-md:  6px;
  --gpf-radius-lg:  12px;

  --gpf-shadow-sm:  0 1px 2px rgba(36, 36, 36, 0.06);
  --gpf-shadow-md:  0 2px 8px rgba(36, 36, 36, 0.09);
  --gpf-shadow-lg:  0 8px 28px rgba(36, 36, 36, 0.12);

  --gpf-transition: 140ms ease-out;
}

/* Respect the user's motion preference everywhere this layer animates. */
@media (prefers-reduced-motion: reduce) {
  :root { --gpf-transition: 0ms; }
}


/* ---- css/base.css ---- */
/* ==========================================================================
   Green Pasture Farms — corrective layer
   --------------------------------------------------------------------------
   Wave 1, step 1. THIS FILE FIXES BUGS. IT MAKES NO DESIGN DECISIONS.

   Everything here is a defect with a right answer that does not depend on the
   design direction: a heading scale that ranks wrong, body copy too small to
   read, an iOS zoom trap in the checkout, an invisible focus ring, a disabled
   state that fails contrast. All of it stays true whatever the site ends up
   looking like, which is why it can ship before the design is settled.

   Anything that expresses a preference — colours, radii, button treatment,
   link decoration, form chrome — lives in theme.css and is NOT built into the
   bundle yet. See frontend/css/theme.css.

   Specificity note, and it matters:
   Astra sizes headings as `h1, .entry-content :where(h1)`. `:where()`
   contributes nothing, so that selector is (0,1,0). The rules below mirror
   that shape exactly, so they tie on specificity and win on source order —
   which is why the enqueue snippet must load this sheet LAST (priority 999,
   no dependencies). Load it early and the type scale silently reverts.
   ========================================================================== */

/* --- 1. The root shrink --------------------------------------------------
   Astra sets 93.75% (15px), then `@media (max-width:1121px)` drops it to
   85.5% — 13.68px. That covers tablets AND phones, so body copy is ~13.7px on
   every device narrower than a laptop, and mobile is roughly half of all
   orders (§4). Pinning one value removes the shrink: 15px at every width.

   THIS IS THE ONE RULE HERE WITH A SIDE EFFECT: anything Astra sizes in rem —
   chiefly spacing — grows ~9.6% at 1121px and below. On a cramped mobile
   layout that is usually an improvement, but eyeball it on a real phone and a
   tablet. Rollback is this single block.                                     */

html { font-size: 93.75%; }

/* --- 2. The heading scale ------------------------------------------------
   §4l: `h2` is currently larger than `h1` on desktop (50px vs 40px) AND on
   phones (45px vs 30px), and correct only on tablets — three values set at
   three breakpoints and never reconciled. One fluid declaration per level
   makes that class of bug unrepresentable. Verified monotonic at fourteen
   viewport widths.

   SIZES ONLY. Typeface, weight and colour are design decisions and are not
   set here — headings keep Astra's Amatic SC on h1/h2 and its burgundy
   (#9b2627) until a direction is agreed.                                     */

h1, .entry-content :where(h1) { font-size: var(--gpf-h1); }
h2, .entry-content :where(h2) { font-size: var(--gpf-h2); }
h3, .entry-content :where(h3) { font-size: var(--gpf-h3); }
h4, .entry-content :where(h4) { font-size: var(--gpf-h4); }
h5, .entry-content :where(h5) { font-size: var(--gpf-h5); }
h6, .entry-content :where(h6) { font-size: var(--gpf-h6); }

/* Amatic SC is loaded at weight 400 ONLY — five subsets, all 400, verified
   2026-09-08. Pinning it stops a block or plugin asking for a bold the browser
   would have to synthesise, which on a light condensed face looks broken.
   This is a correction, not a preference: the bold does not exist.           */

h1, .entry-content :where(h1),
h2, .entry-content :where(h2) { font-weight: 400; }

/* Amatic SC has very little x-height, so the inherited body leading leaves the
   display sizes looking gappy and the ranking harder to read. Tightened to a
   ratio, not a fixed value, so it tracks whatever size the clamp resolves to. */

h1, .entry-content :where(h1),
h2, .entry-content :where(h2) { line-height: 1.12; }

/* --- 3. The iOS zoom trap ------------------------------------------------
   Safari on iOS zooms the viewport when a form control smaller than 16px takes
   focus, leaving the customer scrolled sideways. On this site the root is
   15px, so every unstyled input is 15px and every one of them does it —
   including the checkout. `max()` keeps the user's own larger setting if they
   have one. This is the one place a px value is correct rather than lazy.     */

input[type="text"], input[type="email"], input[type="url"],
input[type="password"], input[type="search"], input[type="number"],
input[type="tel"], input[type="date"], select, textarea {
  font-size: max(1rem, 16px);
}

/* --- 4. Focus visibility -------------------------------------------------
   Accessibility sits at 92 (§4j). A focus ring is what keyboard and switch
   users navigate by, and several of the controls on this site suppress the
   browser default without replacing it. Colour is taken from the site's own
   burgundy so this introduces no new hue.                                    */

:focus-visible {
  outline: 3px solid var(--gpf-burgundy);
  outline-offset: 2px;
}
:focus:not(:focus-visible) { outline: none; }

/* --- 5. Disabled controls ------------------------------------------------
   WooCommerce dims disabled buttons to opacity .5, which drops them below the
   4.5:1 contrast floor. With ~41% of the catalogue out of stock at any moment
   (§2) this state is on screen constantly, so it has to be legible — a
   customer needs to read *why* they cannot buy. Solid grey at full opacity
   is both readable and unmistakably inactive.                                */

.woocommerce .button.disabled,
.woocommerce button.button:disabled,
.woocommerce button.button[disabled] {
  opacity: 1;
  background-color: var(--gpf-out-of-stock);
  color: #ffffff;
  cursor: not-allowed;
}

/* --- 6. Media overflow ---------------------------------------------------
   Stops an oversized image forcing a horizontal scrollbar on the whole page. */

img, svg, video { max-width: 100%; height: auto; }

/* --- 7. Utilities --------------------------------------------------------
   Needed by later steps; neither has a visual opinion. `gpf-visually-hidden`
   is how the "Read more" link-text fix (§4j) carries a post title without
   changing what a sighted visitor reads.                                     */

.gpf-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.gpf-container {
  width: min(100% - (2 * var(--gpf-space-md)), var(--gpf-container));
  margin-inline: auto;
}


/* ---- css/theme.css ---- */
/* ==========================================================================
   Green Pasture Farms — theme layer
   Direction C ("Modern Grocer"), chosen 2026-09-08.

   Until the direction was settled this file was a holding pen for opinions
   that had been wrongly folded into base.css. It is now the real thing: the
   decisions the chosen direction actually makes. base.css remains bug fixes
   only, so the two can still be reasoned about separately.

   No `.uagb-*` or Astra Pro selector appears here.
   ========================================================================== */

/* --- Typography ----------------------------------------------------------
   Direction C has no display face. Amatic SC currently sits on h1 and h2 via
   Astra's dynamic CSS; these rules take it off.

   That does NOT stop the font downloading. Two files are still preloaded —
   Astra's own copy and Spectra's, 55 KB of the same typeface between them
   (§4l) — until the Astra typography setting is changed in the Customizer.
   Do that as a separate, verifiable step; see APPLY.md.

   The body face stays the system stack for now. Direction C was drawn in
   Instrument Sans, and self-hosting it would cost roughly 30 KB against the
   55 KB Amatic SC removal — a net saving and a consistent face across
   devices. Worth doing, deliberately, once the removal above is confirmed.  */

h1, .entry-content :where(h1),
h2, .entry-content :where(h2) {
  font-family: var(--gpf-font-text);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.14;
}

h1, .entry-content :where(h1),
h2, .entry-content :where(h2),
h3, .entry-content :where(h3),
h4, .entry-content :where(h4),
h5, .entry-content :where(h5),
h6, .entry-content :where(h6) {
  color: var(--gpf-text);
}

h3, .entry-content :where(h3),
h4, .entry-content :where(h4),
h5, .entry-content :where(h5) {
  font-weight: 600;
  line-height: var(--gpf-leading-snug);
  letter-spacing: -0.01em;
}

h6, .entry-content :where(h6) {
  text-transform: uppercase;
  letter-spacing: var(--gpf-tracking-caps);
  color: var(--gpf-text-muted);
}

:where(h1, h2, h3, h4, h5, h6) {
  margin-block: var(--gpf-space-lg) var(--gpf-space-sm);
  text-wrap: balance;
}
:where(h1, h2):first-child { margin-block-start: 0; }

:where(p, ul, ol, blockquote) { margin-block: 0 var(--gpf-space-md); }
:where(p, li):last-child { margin-block-end: 0; }
.entry-content > :where(p, ul, ol) { max-width: var(--gpf-measure); }

/* --- Links ---------------------------------------------------------------
   Olive rather than the current grass green. #77ac28 on white is about 2.6:1
   and fails text contrast; #4f6117 clears 4.5:1. Grass green stays, but as a
   colour for filled buttons and badges where the text sits on top of it.    */

a { color: var(--gpf-link); transition: color var(--gpf-transition); }
a:hover, a:focus { color: var(--gpf-link-hover); }

/* No underline anywhere on the site — Simon's call, 2026-09-10, reversing
   the body-copy-underline rule this project had carried since step 5.

   That rule added `text-decoration: underline` to plain links inside
   `.entry-content`, on the reasoning that a body-copy link needs an
   affordance beyond colour alone. It needed a steadily growing `:not()`
   chain to keep out everything that already stated its own decoration
   (buttons, nav, footer, then WooCommerce's product-card links, twice —
   `.woocommerce-loop-product__link` wraps only the card's thumbnail on
   this theme, not its title; `.ast-loop-product__link` is the one that
   actually needed excluding, found by checking the rendered markup after
   the first guess didn't work).

   With underline off everywhere, that whole mechanism has nothing left to
   guard against, so it is deleted rather than kept around set to `none` —
   dead code with an eight-clause `:not()` chain is worse than no code.
   `a` already renders with no underline by default (Astra's base
   stylesheet: `a, a:focus, a:hover, a:visited { text-decoration: none }`),
   so removing this rule is sufficient on its own for every plain link.
   The three components that stated their own underline explicitly —
   `.gpf-hero__cta2`, `.gpf-postcard__more` in content.css, and
   `.gpf-footer a:hover` in chrome.css — are unset in the same change. */

/* --- Buttons -------------------------------------------------------------
   One treatment across WordPress blocks, Astra and WooCommerce. Grass green
   is the action colour; olive is the quieter secondary. WooCommerce's own
   `.button` is heavily specified, hence the doubled class.                  */

.gpf-button,
.wp-block-button__link,
.ast-button,
.ast-custom-button,
.woocommerce .button.button,
.woocommerce a.button.button,
.woocommerce button.button.button,
.woocommerce input.button.button {
  font-family: var(--gpf-font-text);
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.2;
  padding: var(--gpf-space-sm) var(--gpf-space-md);
  border: 1px solid transparent;
  border-radius: var(--gpf-radius-sm);
  background-color: var(--gpf-accent);
  color: #ffffff;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--gpf-transition);
}

.gpf-button:hover,
.wp-block-button__link:hover,
.ast-button:hover,
.ast-custom-button:hover,
.woocommerce .button.button:hover,
.woocommerce a.button.button:hover,
.woocommerce button.button.button:hover,
.woocommerce input.button.button:hover {
  background-color: var(--gpf-grass-dark);
  color: #ffffff;
}

.gpf-button--quiet {
  background-color: transparent;
  border-color: var(--gpf-rule-cool);
  color: var(--gpf-text-quiet);
}
.gpf-button--quiet:hover {
  background-color: var(--gpf-surface-cool-2);
  color: var(--gpf-olive);
}

/* --- Star ratings ----------------------------------------------------------
   Recovered from the old Customizer Additional CSS field (2026-09-10) —
   see frontend/legacy-additional-css.css. `.product` wraps both a card in
   `ul.products` and the single-product page's own root element, so this one
   selector reaches the rating everywhere it renders without needing an
   archive.css/product.css split.

   The empty-star half ties on specificity with Astra's own
   `.star-rating::before { color: <grey> }` (both (0,1,1)) and wins the same
   way the button treatment above does: gpf.css is enqueued after Astra's
   compiled CSS (verified in APPLY.md step 1), so it wins ties on source
   order alone. The filled-star half has no competing rule at all — Astra
   and WooCommerce both leave `.star-rating span::before` to inherit ambient
   text colour, so this is the only explicit colour either half of a filled
   star ever gets.

   The old field's `!important` never did anything: `color: gold;!important`
   has the `;` before `!important`, which WordPress's Customizer sanitiser
   left untouched — browsers park it in the next declaration slot as
   `!important` alone, which is not a valid property, so it is dropped
   silently rather than applied. It has been winning on cascade order this
   whole time, exactly as it does now. */
.product .star-rating span:before,
.product .star-rating:before {
  color: gold;
}

/* --- Product cards -------------------------------------------------------
   Stock is information, not decoration — that is the part of direction C
   that matters, with 61 of 143 products unavailable. `--gpf-low-stock` is a
   different hue from `--gpf-out-of-stock` on purpose: "only 5 left" must not
   read as "you cannot have this".

   No dispatch estimate anywhere. There is no data behind one on this store.  */

.gpf-stock { font-size: 0.78rem; font-weight: 600; margin: 0.15rem 0 0; }
.gpf-stock--low   { color: var(--gpf-low-stock); }
.gpf-stock--rated { color: var(--gpf-text-quiet); }
.gpf-stock--rated span { color: var(--gpf-text-muted); font-weight: 500; }
.gpf-stock--out   { color: var(--gpf-out-of-stock); }

/* --- Forms --------------------------------------------------------------- */

input[type="text"], input[type="email"], input[type="url"],
input[type="password"], input[type="search"], input[type="number"],
input[type="tel"], input[type="date"], select, textarea {
  font-family: var(--gpf-font-text);
  line-height: 1.4;
  color: var(--gpf-text);
  background-color: var(--gpf-surface);
  padding: var(--gpf-space-sm) var(--gpf-space-md);
  border: 1px solid var(--gpf-rule-cool);
  border-radius: var(--gpf-radius-sm);
  transition: border-color var(--gpf-transition), box-shadow var(--gpf-transition);
}
input:focus, select:focus, textarea:focus {
  border-color: var(--gpf-olive);
  box-shadow: 0 0 0 3px rgba(102, 119, 27, 0.14);
}
label { font-weight: 600; }
::placeholder { color: var(--gpf-text-muted); opacity: 1; }

hr, .wp-block-separator {
  border: 0;
  border-top: 1px solid var(--gpf-rule-cool);
  margin-block: var(--gpf-space-xl);
}

/* WooCommmerce My-Account
   Astra's Modern My Account layout (ast-modern-woo-account-page, on
   site-wide) is meant to replace the old login-box styling, but Astra's own
   dynamic CSS still ships the legacy rule unscoped: `.woocommerce-account
   .entry-content .woocommerce { max-width: 540px; margin: 2em auto; border:
   1px; padding: 40px; }`. That is the entire "crammed into 530/540px" bug -
   confirmed live 2026-09-11 via computed styles, not stale cache (the
   per-page file regenerates and still contains it). Undoing exactly that
   rule, scoped to the modern layout, is cheaper and more correct than
   patching every element that broke as a result of the narrow box. */
.ast-modern-woo-account-page.woocommerce-account .entry-content .woocommerce {
  max-width: none;
  margin: 0;
  border: none;
  padding: 0;
  border-radius: 0;
}

.woocommerce-MyAccount-navigation-link {
  list-style-type: none;
}

.ast-woo-nav-link-name {
  padding-left: 10px;
}


.woocommerce-info {
  display: grid;
  gap: 1em;
}


/* ---- css/chrome.css ---- */
/* ==========================================================================
   Green Pasture Farms — header, navigation, mega menu, footer
   Wave 1, step 2. Direction C ("Modern Grocer"), chosen 2026-09-08.

   Renders the markup emitted by frontend/snippets/03-header-nav.php. No
   `.uagb-*` or `.astra-mm-*` selector appears here, so removing Spectra and
   Astra Pro's mega menu in January takes nothing of this with it.

   The badge colours are the reason §4a forbade a blanket dequeue of
   astra-addon-megamenu-dynamic. They are real CSS here now, so that dequeue
   becomes safe once this menu is live.
   ========================================================================== */


/* --- The header bar -----------------------------------------------------
   Replaces Astra's header entirely (frontend/snippets/05-header.php). Two
   rows: an olive bar carrying identity, search and account, then the light
   category bar below it. Direction C's search is in the chrome rather than in
   a full-width burgundy band of its own - that band was the single biggest
   consumer of vertical space above the fold.                                */

.gpf-header {
  font-family: var(--gpf-font-text);
  position: relative;
  z-index: 50;
}

.gpf-header__bar {
  background: var(--gpf-chrome);
  color: #ffffff;
}

.gpf-header__inner {
  width: min(100% - (2 * var(--gpf-space-md)), var(--gpf-container));
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: var(--gpf-space-md);
  flex-wrap: wrap;
  padding-block: var(--gpf-space-sm);
}

.gpf-header__logo {
  display: inline-flex;
  align-items: center;
  color: #ffffff;
  font-weight: 700;
  font-size: 1.02rem;
  text-decoration: none;
  white-space: nowrap;
  flex: none;
}
.gpf-header__logo img { max-height: 42px; width: auto; display: block; }

/* --- Search ---------------------------------------------------------------
   `.gpf-search` is a *slot*, not a widget. Two things land in it and both are
   skinned to the same olive pill:

     the FiboSearch bar   [fibosearch class="gpf-search" ...], which is what
                          renders on the live site and on the clone
     `.gpf-search--plain` our own form, which renders only when the plugin is
                          not active

   The split is deliberate. Everything that positions the field in the header
   bar lives on `.gpf-search` and names no plugin class, so the layout survives
   FiboSearch being swapped or removed; only the skin below reaches for
   `.dgwt-wcas-*`, and the fallback proves the header still works without it.
   FiboSearch is not on §4n's removal list — that rule is about Spectra and
   Astra Pro — but the same discipline is worth keeping for a plugin the search
   experience now depends on.

   On a phone the field drops to its own row rather than squeezing the logo —
   order:3 with a full basis.                                               */

.gpf-search {
  order: 3;
  flex: 1 0 100%;
  /* The plugin's wrapper sets `min-width: 230px`, which would stop the header
     from ever getting narrower than that. It is a flex item here; let it
     shrink and let the pill's own min-width do the work. */
  min-width: 0;
}

/* The pill. `.dgwt-wcas-sf-wrapp` is the plugin's inner wrapper — the element
   holding the magnifier, the input and the preloader — so it is the exact
   counterpart of the fallback `<form>`, and both get the same skin.

   Only the fallback lays itself out with flex. FiboSearch positions its own
   magnifier and preloader absolutely against this box, and adds an empty
   `.dgwt-wcas-voice-search` sibling that a `gap` would turn into stray padding,
   so there the input's own padding does the spacing instead. */
.gpf-search--plain,
.gpf-search .dgwt-wcas-sf-wrapp {
  background: rgba(255, 255, 255, 0.17);
  border-radius: var(--gpf-radius-sm);
}
.gpf-search--plain {
  display: flex;
  align-items: center;
  gap: var(--gpf-space-xs);
  padding: 0 var(--gpf-space-sm);
}

.gpf-search__icon { width: 15px; height: 15px; color: var(--gpf-chrome-text); flex: none; }

.gpf-search input[type="search"] {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  padding: var(--gpf-space-xs) 0;
  color: #ffffff;
  font-size: max(1rem, 16px);
}
.gpf-search input[type="search"]:focus { outline: none; box-shadow: none; }
.gpf-search input::placeholder { color: var(--gpf-chrome-text); opacity: 1; }

/* FiboSearch's own skin, undone.

   Its defaults are a white 40px field with a 1px #ddd border and a 50%-opacity
   dark magnifier — a light-background design that disappears against olive. The
   selectors below are one class more specific than the plugin's
   (`.dgwt-wcas-sf-wrapp input[type="search"].dgwt-wcas-search-input`), which is
   what it takes to win without `!important`. Nothing here changes behaviour;
   the suggestion list, the keyboard handling and the mobile overlay are the
   plugin's, untouched. */

.gpf-search.dgwt-wcas-search-wrapp { line-height: inherit; }

.gpf-search .dgwt-wcas-sf-wrapp input[type="search"].dgwt-wcas-search-input,
.gpf-search .dgwt-wcas-sf-wrapp input[type="search"].dgwt-wcas-search-input:focus {
  height: auto;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  color: #ffffff;
  font-family: inherit;
  font-size: max(1rem, 16px);
  /* The magnifier is absolutely positioned by the plugin rather than being a
     flex sibling, so the left padding is what keeps the text clear of it:
     `--gpf-space-sm` of inset, the icon's 15px, and a gap. */
  padding: var(--gpf-space-xs) var(--gpf-space-sm) var(--gpf-space-xs) 34px;
}
.gpf-search .dgwt-wcas-sf-wrapp input[type="search"].dgwt-wcas-search-input::placeholder {
  color: var(--gpf-chrome-text);
  opacity: 1;
  font-style: normal;
}

/* The plugin sizes the magnifier as a percentage of a 40px field and then
   dims it to 50% for a white background. Neither holds here: the field is as
   tall as its own text, and half-opacity ink on olive is barely visible. Fixed
   at the 15px the fallback form's icon uses, so the two are the same mark. */
.gpf-search.dgwt-wcas-no-submit .dgwt-wcas-ico-magnifier {
  fill: var(--gpf-chrome-text);
  opacity: 1;
  left: var(--gpf-space-sm);
  width: 15px;
  max-width: 15px;
  height: 15px;
  max-height: 15px;
}

/* The suggestion list. Left as the plugin draws it — white, so it reads as a
   panel dropped over the page rather than a continuation of the olive bar —
   with only the corners and the rule brought into line with the rest of the
   chrome. */
.dgwt-wcas-suggestions-wrapp {
  border-color: var(--gpf-rule-cool);
  border-radius: 0 0 var(--gpf-radius-md) var(--gpf-radius-md);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.13);
}

/* The mobile overlay.

   Below 992px FiboSearch does not draw a second search bar — it *moves this
   one*, wrapper and all, into a full-screen white panel and adds
   `.dgwt-wcas-search-wrapp-mobile` to it. Our skin travels with it, so an olive
   field with white text landed on a white background and disappeared. Rather
   than opt the skin out down there, the overlay's bar takes the header's colour
   and the field stays exactly what it was in the header — which is also the
   less surprising thing for someone who just tapped it.

   `.dgwt-wcas-om-return` is the back arrow, `#ccc` by default. */
.dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar {
  background: var(--gpf-chrome);
  height: auto;
  align-items: center;
  padding: var(--gpf-space-xs) var(--gpf-space-sm) var(--gpf-space-xs) 0;
  gap: var(--gpf-space-xs);
}
.dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar button.dgwt-wcas-om-return,
.dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar button.dgwt-wcas-om-return:hover,
.dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar button.dgwt-wcas-om-return:focus {
  background: transparent;
  flex: none;
  /* The plugin gives the button `height: 100%` and positions the arrow inside
     it absolutely, which only lines up against its own fixed 45px bar. This bar
     is `height: auto`, so the button gets a size of its own and the arrow is
     centred in it instead. */
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
}
.dgwt-wcas-overlay-mobile .dgwt-wcas-om-bar .dgwt-wcas-om-return svg {
  position: static;
  width: 21px;
  height: 21px;
}

/* The out-of-stock badge, injected as `meta_after` by 05-header.php.

   Quiet, not alarming. It sits under the price in the same column, and the row
   stays a working link — the product page is where the back-in-stock notifier
   collects an email, so the badge is there to set the expectation, not to
   discourage the click. */
.gpf-suggestion-oos {
  display: block;
  margin-top: 0.15em;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--gpf-text-muted);
  white-space: nowrap;
}

/* Burger, account, basket — replacing Astra's mobile-trigger, account and
   woo-cart header elements, which went with `astra_header`. Icon-only, which
   is what the live site already shows: `header-account-login-style = icon` and
   both `woo-header-cart-total-display` and `-title-display` are false. The
   labels are real text, visually hidden, not `aria-label` — a title attribute
   or a bare label leaves nothing for a translation or for find-in-page. */

.gpf-header__actions {
  display: flex;
  align-items: center;
  gap: var(--gpf-space-2xs);
  margin-left: auto;
  flex: none;
}

.gpf-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 40px at a 15px root. The three sit next to each other on a phone, so the
     tap targets have to be real ones — Astra's 18px icons were not. */
  width: 2.67rem;
  height: 2.67rem;
  padding: 0;
  border: 0;
  border-radius: var(--gpf-radius-sm);
  background: transparent;
  color: var(--gpf-chrome-text);
  text-decoration: none;
  cursor: pointer;
  position: relative;
  transition: color var(--gpf-transition), background-color var(--gpf-transition);
}
/* Astra's button rule is `button:focus, button:hover, … { background-color:
   var(--ast-global-color-2) }` — burgundy, and at (0,1,1) it beats a bare
   `.gpf-iconbtn` at (0,1,0). Every control this file draws is a real <button>,
   so each one has to restate its background at (0,2,0) or Astra repaints it on
   focus. Found by focusing the drawer's close button and watching it turn into
   a burgundy square; the nav chevron below had the same bug on hover. */
.gpf-iconbtn:focus,
.gpf-iconbtn:active { color: var(--gpf-chrome-text); background: transparent; }
.gpf-iconbtn:hover,
.gpf-iconbtn:focus-visible { color: #ffffff; background: rgba(255, 255, 255, 0.12); }
.gpf-icon { width: 1.47rem; height: 1.47rem; display: block; }

/* Always in the markup so WooCommerce's cart fragments have something to
   replace (see gpf_cart_button()); shown only when it says something. */
.gpf-header__cart-count:empty { display: none; }
.gpf-header__cart-count {
  position: absolute;
  top: 0.28rem;
  right: 0.22rem;
  display: grid;
  place-items: center;
  min-width: 1.05rem;
  height: 1.05rem;
  padding: 0 0.28rem;
  border-radius: 999px;
  background: var(--gpf-accent);
  color: #ffffff;
  font-size: 0.66rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

@media (min-width: 700px) {
  .gpf-header__inner { flex-wrap: nowrap; padding-block: var(--gpf-space-sm); }
  .gpf-search { order: 0; flex: 1 1 auto; }
}

/* --- Primary navigation bar --------------------------------------------- */

.gpf-nav {
  background: var(--gpf-surface-cool-2);
  border-bottom: 1px solid var(--gpf-rule-cool);
  /* Set, not inherited. The chrome renders wherever it is placed - an Astra
     header element, a shortcode inside content - and inheriting picked up a
     serif on the local clone. Direction C has one face; say so. */
  font-family: var(--gpf-font-text);
}

.gpf-nav__list {
  display: flex;
  align-items: stretch;
  gap: var(--gpf-space-xs);
  list-style: none;
  margin: 0 auto;
  padding: 0 var(--gpf-space-md);
  width: min(100%, var(--gpf-container));
  /* Eight top-level items do not fit a phone. Scroll the bar rather than
     wrapping it to two rows, which would push the hero down on every page. */
  overflow-x: auto;
  scrollbar-width: none;
}
.gpf-nav__list::-webkit-scrollbar { display: none; }

.gpf-nav__item {
  position: relative;
  display: flex;
  align-items: center;
  flex: none;
}

.gpf-nav__link {
  display: inline-flex;
  align-items: center;
  gap: var(--gpf-space-2xs);
  padding: var(--gpf-space-sm) var(--gpf-space-xs);
  font-size: 0.87rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--gpf-text);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  transition: color var(--gpf-transition), border-color var(--gpf-transition);
}
.gpf-nav__link:hover,
.gpf-nav__item[data-open="true"] > .gpf-nav__link {
  color: var(--gpf-text-quiet);
  border-bottom-color: var(--gpf-accent);
}

.gpf-nav__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  align-self: stretch;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--gpf-text-muted);
  cursor: pointer;
}
/* Same Astra `button:hover/:focus` rule as the icon buttons above. */
.gpf-nav__toggle:hover,
.gpf-nav__toggle:focus,
.gpf-nav__toggle:active { background: transparent; color: var(--gpf-text); }
.gpf-chevron {
  width: 10px;
  height: 6px;
  transition: transform var(--gpf-transition);
}
.gpf-nav__item[data-open="true"] .gpf-chevron { transform: rotate(180deg); }

/* --- The panel -----------------------------------------------------------
   A full-width sheet under the bar, `position: fixed`.

   FIXED, NOT ABSOLUTE, AND THAT IS THE WHOLE BUG FIX. `.gpf-nav__list` carries
   `overflow-x: auto` so nine categories can scroll on a phone. There is no
   such thing as scrolling one axis and overflowing the other: the spec turns
   `overflow-y: visible` into `auto` the moment the other axis is not visible,
   so the 40px-tall bar silently became a clipping box and every absolutely
   positioned panel inside it was clipped to nothing. The panel opened, the
   chevron turned, the markup was correct, and nothing appeared — which is why
   `check-nav.sh`, which reads the markup, passed it.

   A fixed element is not clipped by an ancestor's overflow. It is only
   contained by an ancestor with a transform, filter, perspective, backdrop
   filter, contain or will-change; there is none on this chain (checked, live).
   The top edge cannot be expressed in CSS, so megamenu.js sets `top` and
   `max-height` from the bar's own rect when it opens, and closes on scroll.

   Full width also retires the `nth-last-child(-n+3)` hack that pinned the last
   three panels to the right, and it is the pattern direction C was after: the
   panel is a shelf under the bar, not a dropdown box.                        */

.gpf-panel {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;                        /* real value set on open; see megamenu.js */
  z-index: 60;
  padding-block: var(--gpf-space-lg);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--gpf-surface);
  border-bottom: 1px solid var(--gpf-rule-cool);
  box-shadow: var(--gpf-shadow-lg, 0 8px 28px rgba(30, 42, 23, .12));
}

.gpf-panel__grid {
  /* The grid, not the panel, holds the container width — so the sheet bleeds
     full width while its columns line up with the rest of the page. Doing it
     with `100vw` padding on the panel instead would be off-centre by the
     scrollbar. */
  width: min(100% - (2 * var(--gpf-space-md)), var(--gpf-container));
  margin-inline: auto;
  display: grid;
  gap: var(--gpf-space-lg) var(--gpf-space-xl);
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  align-items: start;
}

.gpf-panel__heading {
  display: block;
  margin: 0 0 var(--gpf-space-xs);
  padding-bottom: var(--gpf-space-2xs);
  border-bottom: 1px solid var(--gpf-rule-cool);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gpf-text-quiet);
  text-decoration: none;
}
.gpf-panel__heading:hover { color: var(--gpf-olive); }

.gpf-panel__col--flat .gpf-panel__heading {
  border-bottom: 0;
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--gpf-text);
}

.gpf-panel__links { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.gpf-panel__links a {
  display: block;
  padding: 0.3rem 0;
  font-size: 0.87rem;
  line-height: 1.35;
  color: var(--gpf-text);
  text-decoration: none;
}
.gpf-panel__links a:hover { color: var(--gpf-text-quiet); }

/* Nav and footer links never take an underline, wherever they sit or however
   they're interacted with. `.gpf-nav__link:hover` above already answers a
   hover with the 2px accent border under the item, and a text underline on
   top of it was a second line a few pixels higher. The panel inherits this
   rule because its markup is rendered inside `.gpf-nav`.
   Decided 2026-09-09, updated 2026-09-10 when the footer lost its hover
   underline too (no underline anywhere, site-wide — see theme.css). The
   hover case is spelled out rather than left to the base rule because Astra
   underlines menu links itself at some settings, and this has to hold
   whichever way that switch is set. */
.gpf-nav a, .gpf-footer a { text-decoration: none; }
.gpf-nav a:hover, .gpf-nav a:focus, .gpf-footer a:hover { text-decoration: none; }

/* --- Badges --------------------------------------------------------------
   "Popular", "SAVE!", "NEW!", "Save £42!". These are the 8.4 KB of live
   colour rules §4a required be preserved out of the mega-menu inline block.
   Carried here as three real rules instead of 467 generated ones.          */

.gpf-badge {
  display: inline-block;
  margin-left: 0.35rem;
  padding: 0.08em 0.42em;
  border-radius: 3px;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  line-height: 1.5;
  vertical-align: 0.12em;
  /* Colours come from each menu item's own meta as inline custom properties,
     so a colour Simon picks in the menu admin arrives here without a code
     change. The fallbacks are only for an item with no colour set. */
  background: var(--gpf-badge-bg, var(--gpf-grass));
  color: var(--gpf-badge-fg, #ffffff);
  white-space: nowrap;
}

/* --- Narrow: the whole category bar goes ---------------------------------
   Decided 2026-09-09, on Simon's call, in two steps. First the panels came off
   below 861px: they open only through the 24px chevron, and on a touch device
   `hover: none` turns the hover path off entirely, so tapping the category
   *name* — the obvious target, and most of the item's width — navigates
   instead of opening. Then the bar itself, because with the panels gone it was
   nine links duplicating the drawer's Shop section, in a horizontal scroller
   with no affordance saying it scrolls, costing ~40px above the fold on every
   page. On a 390×840 phone that is real estate §4m spent a whole measurement
   pass arguing over.

   So below 861px the shop is reached through the burger, which is where the
   Shop section lives — and the Shop section is hidden above 861px, where the
   mega menu does that job. Exactly one of the two is present at any width.

   This one rule is also the switch megamenu.js reads. It refuses to open a
   panel whose toggle is not rendered, and a descendant of a `display: none`
   ancestor reports a null `offsetParent`, so the breakpoint stays defined here
   once rather than being duplicated as a matchMedia string in the JavaScript.

   Scoped to `.gpf-header` so `[gpf_nav]` keeps working if it is ever placed
   somewhere else — the shortcode is documented as reusable.                 */

@media (max-width: 860px) {
  .gpf-header .gpf-nav { display: none; }
}

@media (min-width: 861px) {
  .gpf-drawer__section--shop { display: none; }
}

/* --- The drawer ----------------------------------------------------------
   What the burger opens. Everything that is not a product category: the Main
   Menu, the Help menu (which the live site links only from the footer), and
   the account links Astra's account dropdown used to hold.

   Open at every width, not only on a phone. The header bar has room for a logo,
   a search field and three icons and no more, and direction C puts the product
   categories in their own bar — so on desktop too there has to be somewhere
   About and Help live. That also retires the separate 74-item Mobile Menu:
   there is one drawer, built from menus Simon already maintains.

   Hidden with visibility rather than `display:none` so it can slide, and so it
   leaves the tab order and the accessibility tree while closed without needing
   `inert`. With JavaScript off it is simply not reachable — the footer carries
   the same links, which is why 04-footer.php renders both menus.

   THE Z-INDEX. Measured on the clone, not guessed: this page already carries
   `ast-hb-account-login` at 1000011, GPFAgent's chat bubble at 99999 and
   Astra's cart drawer at 10000. A modal has to cover all of them, so the
   numbers below are one and two above the highest. They are also why the
   markup sits outside `.gpf-header` (05-header.php) — that element sets
   `z-index: 50`, and a child cannot climb out of its parent's stacking context
   however large its own z-index is. Nested, this scrim rendered *under* the
   chat bubble at 90 and would still have at 1000020.                         */

.gpf-drawer__scrim {
  position: fixed;
  inset: 0;
  z-index: 1000020;
  background: rgba(30, 42, 23, 0.45);
  opacity: 0;
  transition: opacity 200ms ease-out;
}
.gpf-drawer__scrim[data-open="true"] { opacity: 1; }

.gpf-drawer {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 1000021;
  width: min(88vw, 22rem);
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--gpf-surface);
  box-shadow: var(--gpf-shadow-lg);
  font-family: var(--gpf-font-text);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 220ms ease-out, visibility 0s linear 220ms;
}
.gpf-drawer[data-open="true"] {
  transform: none;
  visibility: visible;
  transition: transform 220ms ease-out, visibility 0s;
}
@media (prefers-reduced-motion: reduce) {
  .gpf-drawer,
  .gpf-drawer[data-open="true"],
  .gpf-drawer__scrim { transition: none; }
}

/* Set on <html> while the drawer is open, so the page behind cannot scroll. */
.gpf-drawer-open { overflow: hidden; }

.gpf-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gpf-space-sm);
  padding: var(--gpf-space-xs) var(--gpf-space-xs) var(--gpf-space-xs) var(--gpf-space-md);
  background: var(--gpf-chrome);
  color: #ffffff;
  flex: none;
}
.gpf-drawer__title {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: var(--gpf-tracking-caps);
  text-transform: uppercase;
}

.gpf-drawer__body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--gpf-space-md) var(--gpf-space-md) var(--gpf-space-xl);
}

.gpf-drawer__section + .gpf-drawer__section {
  margin-top: var(--gpf-space-lg);
  padding-top: var(--gpf-space-md);
  border-top: 1px solid var(--gpf-rule-cool);
}
.gpf-drawer__heading {
  margin: 0 0 var(--gpf-space-xs);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: var(--gpf-tracking-caps);
  text-transform: uppercase;
  color: var(--gpf-text-muted);
}

.gpf-drawer__links { list-style: none; margin: 0; padding: 0; }
.gpf-drawer__links a {
  display: block;
  /* 44px of vertical target on a phone; these are read with a thumb. */
  padding: 0.62rem 0;
  font-size: 1rem;
  line-height: 1.3;
  color: var(--gpf-text);
  text-decoration: none;
}
.gpf-drawer__links a:hover { color: var(--gpf-text-quiet); }
.gpf-drawer__links--sub {
  margin-left: var(--gpf-space-sm);
  padding-left: var(--gpf-space-sm);
  border-left: 1px solid var(--gpf-rule-cool);
}
.gpf-drawer__links--sub a { font-size: 0.92rem; padding: 0.44rem 0; color: var(--gpf-text-muted); }
.gpf-drawer__links--sub a:hover { color: var(--gpf-text); }

/* --- Collapsible parents -------------------------------------------------
   A category row is a link plus a separate chevron: the name goes to the
   category page, the chevron opens its children. Two targets in a full-width
   drawer row are fine — the name is ~250px of it and the chevron is a 44px
   square. That is the distinction from the bar's chevron, which failed at 24px
   inside a horizontal scroller.

   A parent with no real URL renders as the button alone, spanning the row.   */

.gpf-drawer__row {
  display: flex;
  align-items: center;
  gap: var(--gpf-space-xs);
}
.gpf-drawer__row > a { flex: 1 1 auto; min-width: 0; }

.gpf-drawer__expand {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.93rem;                   /* 44px at a 15px root */
  height: 2.93rem;
  padding: 0;
  border: 0;
  border-radius: var(--gpf-radius-sm);
  background: transparent;
  color: var(--gpf-text-muted);
  cursor: pointer;
}
/* Astra's `button:hover/:focus` again — see the note on .gpf-iconbtn. */
.gpf-drawer__expand:hover,
.gpf-drawer__expand:focus,
.gpf-drawer__expand:active { background: var(--gpf-surface-sunk); color: var(--gpf-text); }

/* The whole row, for a parent that is a heading rather than a destination. */
.gpf-drawer__expand--row {
  width: 100%;
  height: auto;
  justify-content: space-between;
  gap: var(--gpf-space-xs);
  padding: 0.62rem 0;
  font: inherit;
  font-size: 1rem;
  line-height: 1.3;
  color: var(--gpf-text);
  text-align: left;
}
.gpf-drawer__expand--row:hover,
.gpf-drawer__expand--row:focus,
.gpf-drawer__expand--row:active { background: transparent; color: var(--gpf-text-quiet); }

.gpf-drawer__expand .gpf-icon {
  width: 1.2rem;
  height: 1.2rem;
  transition: transform var(--gpf-transition);
}
.gpf-drawer__expand[aria-expanded="true"] .gpf-icon { transform: rotate(180deg); }

/* --- The basket drawer ---------------------------------------------------
   Same panel as the menu drawer; the contents are WooCommerce's own mini-cart
   template, unoverridden. So these rules style `.woocommerce-mini-cart` and
   friends — WooCommerce classes, which tokens.css allows, unlike Spectra's and
   Astra Pro's. Nothing here depends on the theme.                            */

.gpf-cart-drawer__body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--gpf-space-md);
}

.gpf-cart-drawer .woocommerce-mini-cart {
  list-style: none;
  margin: 0;
  padding: 0;
}
.gpf-cart-drawer .woocommerce-mini-cart__empty-message {
  margin: var(--gpf-space-lg) 0;
  color: var(--gpf-text-muted);
  text-align: center;
}

/* One item. The shape has to survive two different sets of markup: plain
   WooCommerce (remove link, product link with the image and the name, then a
   `<span class="quantity">1 × £8.49</span>`), and the same thing after Astra's
   WooCommerce module has filtered it — which wraps the name in
   `.ast-product-name`, replaces the static quantity with a real -/+ stepper,
   and moves the price into `.ast-mini-cart-price-wrap`.

   So the layout is hung off WooCommerce's own classes and off element
   position, never off an `.ast-*` class: the product link becomes the
   image/name grid, and whatever follows it lines up under the name. Astra's
   additions get the small block at the end, which is presentation only. If
   Astra goes, the stepper reverts to WooCommerce's text and nothing here
   breaks — that is the test this had to pass, given January.                */

.gpf-cart-drawer .woocommerce-mini-cart-item {
  --gpf-thumb: 3.6rem;
  position: relative;
  padding: var(--gpf-space-sm) 0;
  border-bottom: 1px solid var(--gpf-rule-cool);
  font-size: 0.9rem;
  line-height: 1.35;
}

/* Flex, not grid. A grid track sizes to `1fr` and a block-level item in it is
   supposed to stretch — but Astra's `.ast-product-image` wrapper would not,
   at any specificity, including an inline `width: auto`. Flex grows it from
   the container instead of stretching it into a track, and does. */
.gpf-cart-drawer .woocommerce-mini-cart-item > a:not(.remove) {
  display: flex;
  align-items: center;
  gap: var(--gpf-space-sm);
  color: var(--gpf-text);
  font-weight: 600;
  text-decoration: none;
  padding-right: 1.6rem;          /* clear of the remove button */
}
.gpf-cart-drawer .woocommerce-mini-cart-item > a:not(.remove):hover { color: var(--gpf-text-quiet); }
.gpf-cart-drawer .woocommerce-mini-cart-item img {
  flex: none;
  width: var(--gpf-thumb);
  height: auto;
  border-radius: var(--gpf-radius-sm);
}
/* Astra wraps the name in a `.ast-product-image` div that sizes itself to the
   thumbnail, not the space available, so the title came out one word per line.
   Told to grow, it fills. Written as "the child that is not the image" rather
   than by class, so it holds for plain WooCommerce too — where the name is a
   bare text node and this matches nothing. */
.gpf-cart-drawer .woocommerce-mini-cart-item > a:not(.remove) > *:not(img) {
  flex: 1 1 auto;
  min-width: 0;
  width: auto;
}

/* Everything after the product link — quantity, price — sits under the name,
   not under the thumbnail. A sibling selector rather than a class, so it holds
   for both markup shapes. */
.gpf-cart-drawer .woocommerce-mini-cart-item > a:not(.remove) ~ * {
  margin-left: calc(var(--gpf-thumb) + var(--gpf-space-sm));
  margin-top: var(--gpf-space-2xs);
  color: var(--gpf-text-muted);
  font-size: 0.85rem;
}

/* WooCommerce renders the remove link first in source order, which is right
   for a screen reader and wrong on screen. Take it out of flow. */
.gpf-cart-drawer .woocommerce-mini-cart-item a.remove {
  position: absolute;
  top: var(--gpf-space-sm);
  right: 0;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  color: var(--gpf-text-muted);
  font-size: 1.1rem;
  line-height: 1;
  text-decoration: none;
}
.gpf-cart-drawer .woocommerce-mini-cart-item a.remove:hover {
  color: var(--gpf-burgundy);
  background: var(--gpf-surface-sunk);
}
.gpf-cart-drawer .woocommerce-mini-cart-item a.remove svg { width: 0.9rem; height: 0.9rem; }

/* The quantity line — WooCommerce's own "3 × £8.49". Astra Pro's -/+ stepper
   is deliberately filtered back out in gpf_mini_cart(); read the note there
   before adding styles for a control that is not in this panel. */
.gpf-cart-drawer .woocommerce-mini-cart-item .quantity {
  display: block;
  font-variant-numeric: tabular-nums;
}
.gpf-cart-drawer .woocommerce-mini-cart-item .quantity .amount { color: var(--gpf-text); font-weight: 600; }

.gpf-cart-drawer .woocommerce-mini-cart__total {
  display: flex;
  justify-content: space-between;
  gap: var(--gpf-space-sm);
  margin: var(--gpf-space-md) 0;
  padding-top: var(--gpf-space-sm);
  font-size: 1rem;
  font-weight: 700;
}
.gpf-cart-drawer .woocommerce-mini-cart__total strong { font-weight: 700; }

.gpf-cart-drawer .woocommerce-mini-cart__buttons {
  display: grid;
  gap: var(--gpf-space-xs);
  margin: 0;
}
.gpf-cart-drawer .woocommerce-mini-cart__buttons .button { text-align: center; }
/* The first button is "View basket", the second "Checkout". Checkout is the
   action; the other is the way back to the page you came from. */
.gpf-cart-drawer .woocommerce-mini-cart__buttons .button:not(.checkout) {
  background: transparent;
  border: 1px solid var(--gpf-rule-cool);
  color: var(--gpf-text);
}
.gpf-cart-drawer .woocommerce-mini-cart__buttons .button:not(.checkout):hover {
  background: var(--gpf-surface-sunk);
  color: var(--gpf-text);
}

/* --- Footer -------------------------------------------------------------- */

.gpf-footer {
  font-family: var(--gpf-font-text);
  background: var(--gpf-surface-cool-2);
  border-top: 1px solid var(--gpf-rule-cool);
  padding: var(--gpf-space-xl) 0 var(--gpf-space-lg);
  color: var(--gpf-text);
}
/* The row version sits inside Astra's own <footer id="colophon">, between the
   newsletter and the copyright, so it draws no top border of its own and lets
   the rows either side keep their own spacing. */
.gpf-footer--row { border-top: 0; padding-block: var(--gpf-space-xl) var(--gpf-space-lg); }
.gpf-footer__grid {
  width: min(100% - (2 * var(--gpf-space-md)), var(--gpf-container));
  margin-inline: auto;
  display: grid;
  gap: var(--gpf-space-lg);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.gpf-footer h2, .gpf-footer h3 {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gpf-text-quiet);
  margin: 0 0 var(--gpf-space-xs);
}
.gpf-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.35rem; }
.gpf-footer a { color: var(--gpf-text); text-decoration: none; font-size: 0.89rem; }
.gpf-footer a:hover { color: var(--gpf-text-quiet); }

.gpf-footer__legal {
  width: min(100% - (2 * var(--gpf-space-md)), var(--gpf-container));
  margin: var(--gpf-space-lg) auto 0;
  padding-top: var(--gpf-space-md);
  border-top: 1px solid var(--gpf-rule-cool);
  display: flex;
  flex-wrap: wrap;
  gap: var(--gpf-space-sm) var(--gpf-space-md);
  font-size: 0.82rem;
  color: var(--gpf-text-muted);
}


/* ---- css/archive.css ---- */
/* ==========================================================================
   Green Pasture Farms — shop, category archives and product cards
   Wave 1, step 3. Direction C.

   One pass covers 143 products and 73 category archives, which is why the card
   is the piece worth getting right: it is the most-repeated object on the site
   and the thing a search visitor meets first.

   No `.uagb-*` or Astra Pro selector. Astra core (`.ast-*`) and WooCommerce
   classes are fair game - those stay when Spectra goes.
   ========================================================================== */

/* --- Ground --------------------------------------------------------------
   The site currently sits on #d9e6a8, a pale yellow-green. Direction C is
   white: the photography is the colour on this site, and a tinted ground
   competes with every product image at once.                                */

body,
.site,
#page,
.ast-separate-container,
.ast-separate-container .ast-article-single,
.ast-plain-container {
  background-color: var(--gpf-surface);
}

/* --- The archive header --------------------------------------------------
   Moved to `page-header.css` at step 4a. What was here set `min-height: 0` on
   `.ast-advanced-headers-layout` and padding on `.ast-title-bar-wrap`, and
   believed that compressed the band. It did not: Astra's height is PERCENTAGE
   padding on the layout element, which these rules never touched, so the band
   measured 496px on a category page with all of step 3 applied. The band is
   also identical on pages, which is not an archive concern - hence its own
   file. See `page-header.css` for the measurements.

   What stays here is the WooCommerce shop-page title, which is different
   markup and is not rendered by the Advanced Header.                        */

.woocommerce-products-header__title {
  font-family: var(--gpf-font-text);
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* --- Toolbar: result count and sorting ---------------------------------- */

.woocommerce .woocommerce-result-count,
.woocommerce .woocommerce-ordering select {
  font-size: 0.87rem;
  color: var(--gpf-text-muted);
}

/* --- The card ------------------------------------------------------------
   White on white needs an edge, so the card carries a hairline rather than a
   fill. Astra's grey card (#f5f5f5) reads as disabled next to a white ground,
   which matters here more than most: with ~42% of the catalogue out of stock,
   "greyed" has to mean something.                                           */

.woocommerce ul.products li.product,
.woocommerce-page ul.products li.product {
  background: var(--gpf-surface);
  border: 1px solid var(--gpf-rule-cool);
  border-radius: var(--gpf-radius-md);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: border-color var(--gpf-transition), box-shadow var(--gpf-transition);
}
.woocommerce ul.products li.product:hover {
  border-color: #c9d4bb;
  box-shadow: var(--gpf-shadow-md);
}

.woocommerce ul.products li.product .astra-shop-thumbnail-wrap,
.woocommerce ul.products li.product img {
  margin: 0;
  border-radius: 0;
  display: block;
}

/* Everything below the image shares one padding box, so the card's inner
   edges line up whatever the plugins add. */
.woocommerce ul.products li.product > :not(.astra-shop-thumbnail-wrap):not(a.woocommerce-LoopProduct-link) {
  padding-inline: var(--gpf-space-sm);
}
.woocommerce ul.products li.product > *:last-child { padding-bottom: var(--gpf-space-sm); }

.woocommerce ul.products li.product .ast-woo-product-category {
  font-size: 0.72rem;
  color: var(--gpf-text-muted);
  letter-spacing: 0.02em;
  margin-top: var(--gpf-space-sm);
}

.woocommerce ul.products li.product .woocommerce-loop-product__title {
  font-family: var(--gpf-font-text);
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.28;
  color: var(--gpf-text);
  padding-top: 0.15rem;
}

.woocommerce ul.products li.product .price {
  font-size: 0.98rem;
  font-weight: 700;
  color: var(--gpf-text);
  font-variant-numeric: tabular-nums;
}
.woocommerce ul.products li.product .price del { color: var(--gpf-text-muted); font-weight: 500; }
.woocommerce ul.products li.product .price ins { text-decoration: none; }

.woocommerce ul.products li.product .star-rating { font-size: 0.72rem; }

/* Grass green is the action colour in direction C; olive is the chrome. */
.woocommerce ul.products li.product .button,
.woocommerce ul.products li.product .ast-on-card-button {
  background: var(--gpf-accent);
  color: #ffffff;
  border-radius: var(--gpf-radius-sm);
  font-size: 0.82rem;
  font-weight: 600;
  padding: var(--gpf-space-xs) var(--gpf-space-sm);
}
.woocommerce ul.products li.product .button:hover { background: var(--gpf-grass-dark); }

/* --- Out of stock --------------------------------------------------------
   §2: roughly 42% of the catalogue at any moment, so this is a primary state.
   It must read as "not today" rather than "broken", and it must not look like
   a disabled control the customer did something wrong to reach.             */

.woocommerce ul.products li.product.outofstock .astra-shop-thumbnail-wrap img {
  filter: grayscale(0.7) opacity(0.65);
}
.woocommerce ul.products li.product.outofstock .price { color: var(--gpf-text-muted); }

/* Two lines, rendered by `frontend/snippets/06-stock-badge.php`: the state,
   and what the click is still worth. The wrapper keeps Astra's class because
   Astra positions the badge off it. */

.woocommerce ul.products li.product .out-of-stock,
.woocommerce ul.products li.product .ast-shop-product-out-of-stock {
  background: var(--gpf-out-of-stock);
  color: #ffffff;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.3rem 0.55rem;
  line-height: 1.35;
  border-radius: var(--gpf-radius-sm);
}

.gpf-oos__state,
.gpf-oos__cta { display: block; }

/* The invitation is not the alarm. Sentence case, unshouted, and slightly
   quieter than the state above it — but still white, because the whole badge
   sits on one fill and a second opacity would put it back under the floor. */
.gpf-oos__cta {
  text-transform: none;
  letter-spacing: 0.01em;
  font-weight: 600;
  font-size: 0.95em;
}

/* Astra:
     .woocommerce ul.products li.product .woocommerce-loop-product__link:hover
       .ast-shop-product-out-of-stock { background-color: #fff }
   Its badge is dark text on rgba(255,255,255,.88), so opaque white is the right
   hover for *that* design. Ours is white text on a solid fill, and the same
   rule erases it — the badge went blank on hover, which is exactly when it is
   being read.

   `.outofstock` on the item takes this to (0,7,2) against Astra's (0,6,2), so
   it wins on specificity rather than on load order. gpf.css happening to be
   enqueued after the theme is not a thing to depend on. */
.woocommerce ul.products li.product.outofstock .woocommerce-loop-product__link:hover .ast-shop-product-out-of-stock,
.woocommerce-page ul.products li.product.outofstock .woocommerce-loop-product__link:hover .ast-shop-product-out-of-stock {
  background-color: var(--gpf-out-of-stock-hover);
}

/* --- Grid: deliberately not touched (on an actual archive) ---------------
   An earlier version set `gap` on ul.products and `margin: 0 !important` on
   the items. Astra lays the grid out with widths and margins, not flex or
   grid, so the gap did nothing and removing the margins collapsed the rows -
   two cards on one line, three overflowing on the next.

   Astra's column count is a Customizer setting and works. Leave it alone.    */

/* --- Grid: touched here on purpose, for the two homepage shortcode blocks -
   First attempt here assumed Astra's `mobile-columns-1` mechanism (a `<body>`
   class it only adds on its own WooCommerce archive templates) was what the
   shop page had and the homepage lacked. Wrong rule: what actually forces
   the homepage's two `[products]` blocks (`class="instock-only"` per the
   `woocommerce_shortcode_products_query` snippet, §5a) to 2 columns instead
   of 1 is a SEPARATE, unconditional Astra rule that needs no body class at
   all — it matches the shortcode's own wrapper directly:

     .woocommerce[class*="columns-"].columns-3 > ul.products li.product
       { width: calc(50% - 10px); margin-right: 20px; }   @media (max-width: 544px)

   Specificity (0,5,2): two elements (`ul`, `li`), and five class-equivalents
   — `.woocommerce`, the `[class*="columns-"]` attribute selector, `.columns-3`,
   `.products`, `.product`. Confirmed present, unminified, in the homepage's
   own inline Astra CSS.

   `.instock-only` is already on the same element as `.woocommerce` and
   `.columns-3` (the shortcode prints `class="woocommerce columns-3
   instock-only"`), so swapping the attribute selector for it keeps the same
   compound and the same specificity, (0,5,2) — a tie, which is fragile: see
   chrome.css's own note on not depending on load order. `li.product.product`
   deliberately repeats the class once more to clear (0,5,2) outright, the
   same trick and the same reasoning as the out-of-stock hover fix above. */
@media (max-width: 544px) {
  .woocommerce.columns-3.instock-only > ul.products li.product.product {
    width: 100%;
    margin-right: 0;
  }
}


/* ---- css/page-header.css ---- */
/* ==========================================================================
   Green Pasture Farms — the page header band
   Wave 1, step 4a. Direction C.

   Astra Pro's Advanced Header. Two entries drive it and between them they
   render this band on every page and all 73 category archives:

     "Main Header" (46382)  global, minus products, single posts, the front
                            page, and five specific pages
     "Half Page"   (47147)  four terms, of which THREE NO LONGER EXIST
                            (2187, 120, 112) — only `whole-animal` still
                            matches, so the other 72 category archives get the
                            Main Header, not this one

   No `.uagb-*` or Astra Pro selector: `.ast-title-bar-wrap` and
   `.ast-advanced-headers-*` are Astra core markup, and they stay when Spectra
   goes.
   ========================================================================== */

/* --- Why this is CSS and not four settings -------------------------------
   Astra sizes the band with PERCENTAGE padding on the inner element —
   `.ast-advanced-headers-layout { padding-top: 10%; padding-bottom: 10% }`,
   written from Page Header → Design → custom bg size. Percentage padding
   resolves against the container WIDTH, so the one control the settings screen
   offers is 152px a side at 1536px and 39px a side at 390px. There is no value
   that is right at both ends, which is why the height has to come from here.

   Measured on the clone before this file, with the rest of the design system
   already applied. NOTE THE TWO WIDTHS — percentage padding means a figure is
   meaningless without one, which is the whole point of this file:

     at desktop width
       /about/                                   453px
       /product-category/organic-grass-fed-beef/ 496px
       /product-category/whole-animal/           757px  (Half Page, 15% / 25%)

     at 390 × 844
       beef category                             444px, starting at 138px —
                                                 69% of the first screen, and
                                                 the <h1> alone is 266px of it

   After this file, all at 390 wide, measured from the bottom of the header:

     /about/         227px -> 80px
     beef category   448px -> 153px   (the 448 reproduces the 444 above)
     whole-animal    394px -> 80px

   The beef archive stays the tallest of the three because it renders its term
   description, which is the §5 meta work and must keep rendering.

   Step 3 set `min-height: 0` on that same element and left its padding alone,
   which is why the band it describes as "compressed" never got shorter. Those
   rules move here from `archive.css` so one file owns this band, and so it is
   fixed on pages as well as on archives — the two were always the same object.

   EVERY selector below carries `.ast-title-bar-wrap` as an ancestor. That is
   deliberate, and it is what makes this file work without `!important`:

     Astra's own rules             (0,1,0)
     Customizer → Additional CSS   (0,1,0), and it prints at `wp_head`
                                   priority 101 — AFTER every enqueued
                                   stylesheet, so gpf.css cannot win on source
                                   order however late it is enqueued
     these rules                   (0,2,0)

   The `font-size: 74px` on `.ast-advanced-headers-title` in Additional CSS is
   the live example: it is why the title renders at 74px on a 390px phone today
   in spite of `--gpf-h1`. Deleting that rule is still worth doing (see
   APPLY.md step 4a) but this file does not depend on it happening.           */

/* --- The band ------------------------------------------------------------
   Pale ground rather than a photograph. It is the same flank-steak image on
   all 73 category archives, so it carries no information about where you are,
   and on a phone it is most of the first screen.

   `background-image: none` also stops the file downloading — a background
   whose winning declaration is `none` is never fetched. What it does NOT fix
   is the `aria-label` Astra writes onto this element from the image's alt
   text, so every page currently announces its title region as "Organic
   Grass-Fed Flank Steak". That one needs the image cleared in the setting,
   which is why step 4a does both.                                           */

.ast-title-bar-wrap {
  background-color: var(--gpf-surface-cool-2);
  background-image: none;
  border-bottom: 1px solid var(--gpf-rule-cool);
  padding-block: var(--gpf-space-lg);
}

/* The 50% slate overlay (`rgba(44,62,80,0.50)`), which existed to hold white
   text off a photograph. With no photograph it is a grey wash over the band. */
.ast-title-bar-wrap::after { display: none; }

/* `min-height` keeps its `!important` from archive.css: an Advanced Header set
   to a viewport height writes an inline height Astra generates per entry, and
   this is cheap insurance against one being set later. The padding does not
   need it — (0,2,0) is already ahead of Astra's (0,1,0). */
.ast-title-bar-wrap .ast-advanced-headers-layout,
.ast-title-bar-wrap .ast-inside-advanced-header-content {
  min-height: 0 !important;
  padding-block: 0;
}

/* Found 2026-09-12: on every category archive (and anything else using
   Astra's "Separate Container" layout), Astra's own per-page generated CSS
   — `wp-content/uploads/astra-addon/astra-addon-dynamic-css-*.css`, not a
   file in this repo — carries this below 1121px:

     .ast-separate-container #primary, ... { margin-top: -21px; }

   `#primary` holds `.ast-woocommerce-container`, whose first child is
   WooCommerce's own breadcrumb nav. Pulling `#primary` up 21px drags the
   breadcrumb up into the title bar's own box, so it visually reads as part
   of the pale band above rather than the white content below — exactly what
   a screenshot confirmed at 390px (the breadcrumb painted on `#f4f6f2`, the
   band's own colour, not white). Desktop is unaffected only because 1121px
   is above the width Astra applies this at.

   `#primary` is an ID, so simple class overrides lose to Astra's own
   selectors on specificity alone regardless of file order — hence the
   `!important`, the same reasoning `min-height` above already uses. Every
   variant Astra's rule lists is repeated here rather than guessing which
   one this site's container-layout setting actually resolves to. */
@media (max-width: 1121px) {
  .ast-separate-container.ast-right-sidebar #primary,
  .ast-separate-container.ast-left-sidebar #primary,
  .ast-separate-container #primary,
  .ast-plain-container #primary,
  .ast-narrow-container #primary {
    margin-top: 0 !important;
  }
}

/* Zeroing the margin above put the breadcrumb flush against the title band's
   bottom border, no breathing room at all. `#content > .ast-container` is
   the direct parent of `#primary` (`.ast-container` alone is too generic to
   target — Astra reuses it in the header and footer too, which this scoping
   avoids touching). Phone only, per Simon 2026-09-12; tablet widths weren't
   raised as needing it and 1121px's overlap fix above already covers them
   regardless. */
@media (max-width: 600px) {
  #content > .ast-container {
    padding-top: var(--gpf-space-sm);
  }
}

/* Left, not centred. Direction C sets copy left throughout — the hero, the
   cards, the section headings — and a centred title over left-aligned content
   reads as a leftover. One line to flip back if that is wrong. */
.ast-title-bar-wrap .ast-container.ast-title-bar-align-center { text-align: left; }

/* --- The title ------------------------------------------------------------
   `--gpf-h1`, the same fluid scale as every other h1 on the site (§4l), in
   place of the flat 74px. Resolved: 36px at 375px, 54px at 1200px.           */

.ast-title-bar-wrap .ast-advanced-headers-title {
  font-family: var(--gpf-font-text);
  font-size: var(--gpf-h1);
  font-weight: 700;
  line-height: 1.14;
  letter-spacing: -0.02em;
  color: var(--gpf-text);
  margin: 0;
  text-wrap: balance;
}

/* --- The description ------------------------------------------------------
   `.taxonomy-description` is the §5 meta work — 212 titles and descriptions in
   Yoast — and it renders INSIDE this band, under the <h1>. It must keep
   rendering; a `display: none` anywhere above would silently undo that work.
   `.ast-advanced-headers-wrap > p` catches the page-header equivalent.

   Astra colours this whole wrap `#ffffff` from the entry's title-colour
   setting. The rules below are (0,2,0) and take it back, so the band is
   legible whether or not that setting is ever changed.                       */

.ast-title-bar-wrap .ast-advanced-headers-wrap { color: var(--gpf-text); }

.ast-title-bar-wrap .taxonomy-description,
.ast-title-bar-wrap .ast-advanced-headers-wrap > p {
  margin: var(--gpf-space-xs) 0 0;
  max-width: var(--gpf-measure);
  color: var(--gpf-text-muted);
  font-size: var(--gpf-text-lead);
  line-height: var(--gpf-leading-body);
}
.ast-title-bar-wrap .taxonomy-description p:last-child { margin-bottom: 0; }

/* Astra sets breadcrumb and link colours here to white as well. */
.ast-title-bar-wrap a { color: var(--gpf-link); }
.ast-title-bar-wrap a:hover,
.ast-title-bar-wrap a:focus { color: var(--gpf-link-hover); }

.ast-title-bar-wrap .ast-advanced-headers-breadcrumb,
.ast-title-bar-wrap .ast-advanced-headers-breadcrumb .woocommerce-breadcrumb {
  color: var(--gpf-text-muted);
  font-size: var(--gpf-text-sm);
}

/* --- Narrow --------------------------------------------------------------
   The band is worth less of the screen on a phone than on a desktop, where
   there is space to spend. Below 601px the padding halves.                   */

@media (max-width: 600px) {
  .ast-title-bar-wrap { padding-block: var(--gpf-space-md); }
  .ast-title-bar-wrap .taxonomy-description,
  .ast-title-bar-wrap .ast-advanced-headers-wrap > p { font-size: 1rem; }
}


/* ---- css/content.css ---- */
/* ==========================================================================
   Green Pasture Farms — page content: the homepage hero and the post cards
   Wave 1, steps 5 and 6. Direction C ("Modern Grocer").

   This is the layer that dresses content Simon writes in the editor, rather
   than templates. Two components:

     .gpf-hero      one core HTML block, pasted into the homepage.
                    `frontend/blocks/home-hero.html` is the markup.
     .gpf-posts     the output of `[gpf_posts]`, snippet 07, which replaces
                    the Spectra `uagb/post-grid` block.

   Both are deliberately shaped so that removing Spectra in January is a
   deletion: neither depends on a `.uagb-*` or Astra Pro class, and neither
   needs the editor to draw it.
   ========================================================================== */


/* ==========================================================================
   THE HOMEPAGE HERO
   --------------------------------------------------------------------------
   The hero as drawn in `frontend/directions.html` §chosen, which is the
   version Simon signed off on 2026-09-08 — the produce claims moved up into
   the hero on his condition, and measured there at 390 × 844.

   Shape: a bordered card on the pale cool ground. Below 861px the photograph
   sits on top and the copy under it; from 861px up the copy takes the left and
   the photograph a 36% column on the right.

   861px is not a new breakpoint. It is the one chrome.css already uses to swap
   the category bar for the drawer, so the site has one story about where
   "phone" ends — and it is also the width below which a two-column hero would
   leave the claims in a column too narrow to hold three lines each.

   WHY THE SPLIT MATTERS, and it is not only taste: the hero photograph
   (attachment 44271, `MG_5461-800x534-1.jpg`) exists at 800 × 534 and no
   larger. Full-bleed it is upscaled roughly 2× on a desktop viewport. In a 36%
   column of a 1200px container it renders at ~432 CSS px, which 800px covers
   at 2× DPR. The same file stops being soft by being asked to do less.

   It also removes a live fragility. The hero today is white text over that
   photograph, so if the image fails the headline is white on white — which is
   exactly what happens on the local clone, where the homepage `<h1>` is
   currently invisible. Copy on a solid ground cannot fail that way.
   ========================================================================== */

/* --- Measure --------------------------------------------------------------
   EACH COMPONENT CONSTRAINS ITSELF. That is not a style preference, it is what
   the homepage's layout requires: the page is `site-content-layout =
   page-builder` with `ast-site-content-layout = full-width-container`, so
   `.entry-content` runs the full width of the viewport — measured at 1865px on
   a 1920px screen — and nothing above these blocks caps anything.

   Spectra solved it the same way: every root container is `alignfull` with an
   inner wrap capped at 1200px. So this is the existing model, not a new one,
   and it means these components drop onto the page in any order without a
   wrapper and without changing the page's layout setting.

   The consequence worth stating: `.gpf-section-head` is LOAD-BEARING for
   layout, not decoration. A section heading added without it runs edge to edge
   while the grid under it does not. APPLY.md step 5 says so at the point where
   the headings are added.

   `padding-inline` rather than a margin, so the cap is a true 1200px of
   content and the gutter survives at 1220px where `max-width` alone would let
   the text touch the viewport edge. */

.gpf-hero,
.gpf-hero__title,
.gpf-posts,
.gpf-section-head {
  max-width: var(--gpf-container);
  margin-inline: auto;
  box-sizing: border-box;
}

/* The hero's own padding is on `.gpf-hero__body`, inside a bordered card, so
   the card itself takes the gutter; the other three take it directly. */
/* .gpf-hero { margin-inline: var(--gpf-space-md); } */
@media (min-width: calc(1200px + 2 * 1.25rem)) {
  .gpf-hero { margin-inline: auto; }
}
.gpf-hero__title,
.gpf-posts,
.gpf-section-head { padding-inline: var(--gpf-space-md); }

.gpf-hero {
  display: flex;
  flex-direction: column;
  margin-block: var(--gpf-space-md);
  background-color: var(--gpf-surface-cool-2);
  border: 1px solid var(--gpf-rule-cool);
  border-radius: var(--gpf-radius-lg);
  overflow: hidden;
}

/* `order: -1` rather than putting the media first in the DOM. The copy is the
   more important half and leads the document; the photograph is pulled above
   it visually on a phone, where a text-first first screen would be a weak
   opening for a food shop. Restored to source order at 861px. */
/* 2/1 below 601px, 16/9 above it. The phone crop is a FOLD DECISION and the
   number was arrived at by measurement, not by eye: the sixth produce claim
   has to clear 844px at 390px wide (§4m), and every pixel the photograph takes
   is a pixel the claims do not have. At the photograph's own 3/2 it is 197px
   and the claims land 22px below the fold; at 2/1 it is 148px and they clear
   by 35px.

   The subject survives the crop — the cattle sit across the middle of the
   frame, so a centred 2/1 window on the 800 × 534 original keeps roughly
   y 67–467, losing sky at the top and foreground grass at the bottom. Re-check
   that if the photograph is ever replaced with one composed differently, and
   re-check the fold if this ratio is ever made taller.

   THE ASPECT RATIO GOES ON THE `<img>`, NOT ON THE BOX, and that is not a
   stylistic preference — it is the only one of the two that works here.
   `.gpf-hero` is a column flex container, so `.gpf-hero__media` is a flex item
   whose height comes from its content. `aspect-ratio` on it computes (the
   computed value really does read back correctly) and is then ignored, because the
   `<img>` inside resolves `height: 100%` against an indefinite height, falls
   back to `auto`, and sizes the box by its own intrinsic 800 × 534. Measured:
   296 × 197, a ratio of 1.50 — the photograph's, not the one asked for. Set it
   on the replaced element and it is honoured.

   From 861px the hero is a ROW, so the column has a definite height to stretch
   to and `height: 100%` works — that is where the ratio is handed back to the
   layout, in the 861px block below. */
.gpf-hero__media {
  order: -1;
  background-color: var(--gpf-surface-sunk);
}
.gpf-hero__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 2 / 1;
  object-fit: cover;
}
@media (min-width: 601px) {
  .gpf-hero__media img { aspect-ratio: 16 / 9; }
}

.gpf-hero__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: var(--gpf-space-sm) var(--gpf-space-md) var(--gpf-space-md);
}

/* `pretty`, overriding the `balance` that theme.css sets on every heading.
   Balance equalises line lengths, and on this headline it does that by
   breaking the compound adjective at its hyphen — "Organic Grass-" /
   "Fed British Meat", measured at 353px / 402px in a 669px column. `pretty`
   gives 443px / 300px, which is "Organic Grass-Fed" / "British Meat".

   `pretty` is Chromium-only (not shipped by Firefox or Safari as of this
   writing). In a browser that does not recognise it, the declaration is
   invalid and dropped, and `.gpf-hero__title` falls back to theme.css's
   `balance` — silently reproducing the exact break this rule exists to
   avoid. So `pretty` is necessary but not sufficient on its own; see
   `.gpf-hero__title-nowrap` below for the part that holds regardless of
   text-wrap support.

   The alternative fix is a non-breaking hyphen in the markup, and it is
   rejected on purpose: this <h1> is the page's target keyphrase and §5 has 212
   Yoast titles and descriptions built around it. Swapping U+002D for U+2011
   changes what a crawler and a copy-paste see, to fix a line break. CSS is the
   right layer for a line break. */
/* Deliberately bigger than `--gpf-h1` (the shared scale page-header.css also
   uses) rather than changing that token — the hero has room this component
   doesn't, and bumping the shared value would grow every other page's h1 too.
   2026-09-12: the card was under-filling its available height at desktop
   widths once the promise/sell/CTAs were reordered, so type across the whole
   hero was scaled up to use it; this is that pass for the heading.

   THE FLOOR moved AGAIN 2026-09-12, this time down: 2.4rem → 2.1rem, below
   `--gpf-h1`'s own floor rather than matching it. Not a fold-budget concern
   this time (a smaller heading only ever gives the fold more room) — it's
   the opposite problem: smaller text at narrow (mobile) widths means more of
   it fits per line, which lowers the odds that "Grass-Fed" needs to break at
   its own hyphen to fit at all. `.gpf-hero__title-nowrap` below only forces
   that word to stay whole from 861px up; below that it's still down to
   `text-wrap: pretty` doing its best, and a smaller font just gives it an
   easier job. This clamp's linear segment passes through (400px, 2.1rem) and
   (1200px, 4.3rem) — 400px kept as the floor's crossover point for
   consistency with the previous pass, not for any new reason of its own. */
.gpf-hero__title {
  margin-block: var(--gpf-space-md) var(--gpf-space-sm);
  font-size: clamp(2.1rem, 1rem + 4.4vw, 4.3rem);
  line-height: 1.1;
  text-wrap: pretty;
}

/* Belt-and-suspenders for the browsers `pretty` above doesn't cover: pins
   "Grass-Fed" together as one unbreakable run so it can never be split at its
   own hyphen, independent of which text-wrap algorithm is in play. Text
   content is untouched — a <span> boundary is invisible to copy-paste and to
   a crawler, unlike the rejected non-breaking-hyphen approach above.

   Unconditional as of 2026-09-12 (was gated to 861px+) — mobile was still
   splitting at the hyphen because below 861px only `text-wrap: pretty` was
   protecting it, and pretty rebalances line lengths without removing the
   hyphen as a break opportunity. This can still force "Grass-Fed" onto the
   same line as "Organic", or add a third heading line, at some mobile
   widths — RE-MEASURE THE 390 × 844 FOLD (§4m) for the six produce claims if
   that turns out to cost a line, since the previous gate existed for exactly
   that reason. */
.gpf-hero__title-nowrap { white-space: nowrap; }

/* The divider between the claims and the delivery line lives here, not on
   `.gpf-hero__promise` — the promise block is first in the body now (see
   home-hero.html's 2026-09-12 comment) and has nothing above it, within the
   card, to divide from. */
.gpf-hero__sell {
  max-width: 46ch;
  font-size: 1rem;
  line-height: var(--gpf-leading-body);
  color: var(--gpf-text-muted);
  padding-top: var(--gpf-space-sm);
  border-top: 1px solid var(--gpf-rule-cool);
}
@media (min-width: 601px) {
  .gpf-hero__sell { font-size: 1.25rem; }
}

/* Keeps "at checkout." from being stranded alone on a wrapped line — the
   line breaks somewhere in "Free UK delivery over £80. Choose your delivery
   date at checkout." often enough (it's the longest line in the card on
   narrower widths) that leaving the last two words free to split looked like
   a typo. Unconditional, no breakpoint: unlike the `<h1>`'s "Grass-Fed" case,
   pinning two short words together never costs a whole extra line here. */
.gpf-hero__sell-nowrap { white-space: nowrap; }

/* --- The two calls to action ---------------------------------------------
   The primary is the shop. That is a REQUIREMENT of step 5, not a preference
   (Simon, 2026-09-09): hiding the category bar below 861px put the shop two
   taps from a phone — burger, then category — where it used to be one, and a
   prominent unambiguous route to the shop above the fold is what closes that.
   So the filled button goes to /shop/ and says so; "First Visit? Start Here"
   keeps its place beside it as the quieter of the two.

   The secondary is a text link, not a second button. Two filled buttons of
   equal weight is the arrangement the requirement exists to end.            */

.gpf-hero__ctas {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--gpf-space-sm) var(--gpf-space-md);
}

/* `.gpf-button` picks up the one button treatment in theme.css. Only the
   sizing is stated here — the hero's primary action is larger than a card's.

   The narrow padding below 601px is measured, not cosmetic. At `--gpf-space-lg`
   the button is ~204px, which with the gap and the secondary link needs 368px
   inside a 352px content box, so the two CTAs wrap onto separate rows and the
   hero grows 39px. `--gpf-space-md` brings it to ~178px and they sit on one
   row — and those 39px are part of what keeps the six claims above the fold at
   390 × 844. If this padding is ever widened, re-measure the fold.

   2026-09-12: the ≥601px size grew to 1.25rem to use up the hero's spare
   desktop space, but the mobile 1rem stays exactly as measured — the fold
   budget above has zero slack below 601px (a first pass that also grew the
   mobile size pushed the CTAs off the 390×844 fold, caught on the clone), so
   growth here is desktop-only, same reasoning as `.gpf-hero__title`'s floor. */
.gpf-hero__ctas .gpf-button {
  padding: var(--gpf-space-sm) var(--gpf-space-md);
  font-size: 1rem;
}
@media (min-width: 601px) {
  .gpf-hero__ctas .gpf-button {
    padding: var(--gpf-space-sm) var(--gpf-space-lg);
    font-size: 1.25rem;
  }
}

/* No underline site-wide (2026-09-10) — see theme.css's Links section. This
   used to state its own underline as the one exception to the body-copy
   rule; now it's plain colour, like everything else. */
.gpf-hero__cta2 {
  font-weight: 600;
  color: var(--gpf-link);
}
.gpf-hero__cta2:hover,
.gpf-hero__cta2:focus { color: var(--gpf-link-hover); }

/* --- The 100% promise ----------------------------------------------------
   §4m: all six claims are already on the homepage, under "All of Our Produce
   Is 100%:", as two Spectra icon-list blocks in two containers. This is not an
   invention — it reproduces that structure. It now genuinely arrives WITH the
   headline (first in the body, immediately under the card's own top padding)
   — Simon's original condition on direction C, which the first cut of this
   block shipped as "last" rather than "first" and this corrects, 2026-09-12.

   Applying this means DELETING those two icon-list blocks and their heading
   from the page. Left in place they are the same six claims twice.          */

.gpf-hero__promise-head {
  font-size: var(--gpf-text-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--gpf-tracking-caps);
  color: var(--gpf-text-quiet);
}
/* Desktop-only growth, same reasoning as the title and the CTA buttons above:
   the 390×844 fold has no slack to give below 601px. */
@media (min-width: 601px) {
  .gpf-hero__promise-head { font-size: 1rem; }
}

/* Two columns at EVERY width, column-major, so the three positive claims hold
   the left column and the three "free from" claims hold the right — the
   grouping the two icon-list blocks already had. Row-major would interleave
   them and the two groups would stop reading as groups. DOM order is
   untouched, so a screen reader still gets them in the order they were
   written.

   Two columns down to 390px is not a stylistic choice, it is the §4m
   requirement: six claims in one column is six rows instead of three, and the
   130px that costs is the difference between the claims clearing the fold at
   390 × 844 and not. Measured both ways. The claims wrap to two lines each in
   a 170px column, which is why the type steps down below 601px rather than the
   grid collapsing. */
.gpf-hero__claims {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--gpf-space-2xs) var(--gpf-space-sm);
}

/* Two columns from 601px, which is how direction C draws it. Below that it is
   one column: at 390px the two-column grid wrapped five of the six claims onto
   a second line, which was no shorter than one column and much harder to read.
   The mock only ever drew the desktop hero, so the phone is a width it never
   specified rather than a departure from it. */
@media (min-width: 601px) {
  .gpf-hero__claims {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: repeat(3, auto);
    grid-auto-flow: column;
    gap: var(--gpf-space-2xs) var(--gpf-space-md);
  }
}

.gpf-hero__claims li {
  position: relative;
  margin: 0;
  padding-left: 1.5em;
  font-size: 0.84rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--gpf-text);
}

@media (min-width: 601px) {
  .gpf-hero__claims li { padding-left: 1.6em; font-size: 1.05rem; line-height: 1.34; }
}

/* The tick, drawn rather than fetched: a disc and two borders rotated 45°.
   Sized in `em` so it tracks the list's font-size at every width. Both
   pseudo-elements are decoration — the claim is the text. */
.gpf-hero__claims li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.18em;
  width: 1.05em;
  height: 1.05em;
  border-radius: 50%;
  background-color: var(--gpf-accent-wash);
}
.gpf-hero__claims li::after {
  content: "";
  position: absolute;
  left: 0.32em;
  top: 0.44em;
  width: 0.34em;
  height: 0.17em;
  border-left: 2px solid var(--gpf-grass-dark);
  border-bottom: 2px solid var(--gpf-grass-dark);
  transform: rotate(-45deg);
}

/* --- From 861px: copy left, photograph right ----------------------------- */

@media (min-width: 861px) {
  .gpf-hero {
    flex-direction: row;
    align-items: stretch;
    margin-block: var(--gpf-space-lg);
  }
  .gpf-hero__media {
    order: 0;            /* back to source order: body, then media */
    width: 36%;
    flex: none;
  }
  /* A row flex container stretches this item to the copy's height, so the
     height is definite here and `height: 100%` resolves — which is what lets
     the ratio go back to `auto` and the photograph fill whatever depth the
     copy turns out to need. */
  /* `object-fit: cover`'s default centering was cropping evenly off both
     edges, which reads as cutting off the left side of the frame against
     the card border. `left center` anchors the image's own left edge to
     the box's left edge instead, so all the crop comes off the right —
     2026-09-12, Simon. */
  .gpf-hero__media img { height: 100%; aspect-ratio: auto; object-position: left center; }
  .gpf-hero__body { flex: 1; padding: var(--gpf-space-lg) var(--gpf-space-xl); }
}


/* ==========================================================================
   POST CARDS — the output of [gpf_posts]
   --------------------------------------------------------------------------
   Replaces Spectra's `uagb/post-grid`, which is on exactly one page: the
   homepage, block_id 4a4d9855, "Read More About Our Organic Grassfed British
   Meat".

   Four things were wrong with it, all verified on the clone rather than
   asserted:

     1. Card titles rendered UNDERLINED. Its anchors carry no class, so
        theme.css's body-copy rule reached them. Fixed at the root — see the
        `:not([class*="gpf-"])` note in theme.css.
     2. Cards sat on `#f6f6f6`. Grey reads as disabled next to the white ground
        step 3 established, and archive.css already moved product cards off it.
     3. The subhead rendered BURGUNDY, the colour direction C retires.
     4. The byline's `<span class="dashicons dashicons-calendar">` icons render
        as nothing at all: dashicons is not enqueued on the front end. Two
        empty spans and a comment count on a store's article teasers.

   And the reason it is worth doing beyond appearance: its four "Read More"
   buttons are four of the five generic links §4j names as the cheapest
   remaining SEO point on the homepage. The shortcode gives each card exactly
   one link whose text is the post title, so the audit finding goes with it.
   ========================================================================== */

.gpf-posts {
  display: grid;
  gap: var(--gpf-space-md);
  grid-template-columns: 1fr;
  margin-block: var(--gpf-space-md);
}

@media (min-width: 601px) {
  .gpf-posts--cols-2 { grid-template-columns: repeat(2, 1fr); }
  .gpf-posts--cols-3 { grid-template-columns: repeat(2, 1fr); }
  .gpf-posts--cols-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 861px) {
  .gpf-posts--cols-3 { grid-template-columns: repeat(3, 1fr); }
  .gpf-posts--cols-4 { grid-template-columns: repeat(4, 1fr); }
}

/* The same card as the product card in archive.css: white, hairline border,
   no fill. One object on this site, two contents. */
.gpf-postcard {
  position: relative;
  display: flex;
  flex-direction: column;
  background-color: var(--gpf-surface);
  border: 1px solid var(--gpf-rule-cool);
  border-radius: var(--gpf-radius-md);
  overflow: hidden;
  transition: box-shadow var(--gpf-transition), border-color var(--gpf-transition);
}
.gpf-postcard:hover {
  border-color: var(--gpf-accent);
  box-shadow: var(--gpf-shadow-md);
}

/* A fixed box, so a mixed bag of 4:3 and 3:2 featured images does not give
   every card a different height and leave the titles on different lines. */
.gpf-postcard__media { aspect-ratio: 3 / 2; background-color: var(--gpf-surface-sunk); }
.gpf-postcard__media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.gpf-postcard__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--gpf-space-md);
  gap: var(--gpf-space-2xs);
}

.gpf-postcard__meta {
  margin: 0;
  font-size: var(--gpf-text-sm);
  font-weight: 600;
  color: var(--gpf-text-muted);
}

.gpf-postcard__title {
  margin: 0;
  font-size: var(--gpf-h4);
  font-weight: 600;
  line-height: var(--gpf-leading-snug);
  letter-spacing: -0.01em;
  text-wrap: balance;
}

/* ONE link per card, and its text is the post title.
   `::after` stretches it over the whole card, so the image, the excerpt and
   the "read the full article" line are all clickable without any of them being
   a second anchor to the same URL. That is what closes §4j: the audit counts
   links whose text is not descriptive, and there is now no such link here.

   The trade is that dragging to select text inside a card selects awkwardly,
   because the overlay sits above the copy. Accepted: these are teasers, and
   the alternative is three anchors per card announcing the same destination
   three times to a screen reader. */
.gpf-postcard__link {
  color: inherit;
  text-decoration: none;
}
.gpf-postcard__link::after {
  content: "";
  position: absolute;
  inset: 0;
}
.gpf-postcard:hover .gpf-postcard__link { color: var(--gpf-link); }

/* The focus ring belongs to the card, not to the invisible overlay — the
   overlay has no box of its own to draw around. */
.gpf-postcard:focus-within { outline: 3px solid var(--gpf-focus); outline-offset: 2px; }
.gpf-postcard__link:focus-visible { outline: none; }

.gpf-postcard__excerpt {
  margin: var(--gpf-space-2xs) 0 0;
  font-size: 0.95rem;
  line-height: var(--gpf-leading-body);
  color: var(--gpf-text-muted);
}

/* Not a link and not a button: the card is already the link, and this is the
   affordance saying so. It carries `aria-hidden` in the markup for the same
   reason — a screen reader has had the title already. */
.gpf-postcard__more {
  margin: var(--gpf-space-sm) 0 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--gpf-link);
}
.gpf-postcard:hover .gpf-postcard__more { color: var(--gpf-link-hover); }

/* Pushes the read-more line to the bottom so it aligns across a row whose
   excerpts differ in length — what Spectra's "equal height inline read more
   buttons" was doing with absolute positioning and two inline offsets. */
.gpf-postcard__body > .gpf-postcard__more { margin-top: auto; padding-top: var(--gpf-space-sm); }


/* ==========================================================================
   SECTION HEADINGS
   --------------------------------------------------------------------------
   The homepage's "Best Sellers" / "Latest Arrivals" / "Read More About…"
   headings, once they are core heading blocks rather than Spectra advanced
   headings. Nothing here is required — a plain `<h2>` already inherits the
   design system — but the eyebrow-and-rule treatment is what the mockup shows
   above each band, and it is one class on the heading block.
   ========================================================================== */

.gpf-section-head {
  margin-block: var(--gpf-space-xl) var(--gpf-space-md);
  padding-bottom: var(--gpf-space-xs);
  border-bottom: 1px solid var(--gpf-rule-cool);
}


/* ==========================================================================
   PARAGRAPH MARGINS — and why these selectors are shaped the way they are
   --------------------------------------------------------------------------
   Astra ships `.entry-content p { margin-bottom: 1.6em }` in frontend.min.css.
   That is (0,2,0). Every `<p>` these components emit lives inside
   `.entry-content`, so a single-class rule — (0,1,0) — LOSES to it, whatever
   the source order, and the margins written next to the component above were
   silently not applying. Measured before this block existed: `.gpf-hero__sell`
   resolved to 27.12px where its own rule said 18.75px, and
   `.gpf-hero__promise-head` to 20.88px where its own rule said 7.5px — 29px of
   unwanted space in a hero whose fold budget is counted in single pixels.

   So these are (0,2,1): parent class, then TAG + class. That beats (0,2,0) on
   specificity outright, rather than tying and depending on gpf.css still being
   enqueued last. Step 1's verify note already records what happens when that
   ordering assumption is wrong — the type scale silently reverts — and this is
   the same trap one layer down.

   Worth knowing, and NOT changed here: the same Astra rule beats theme.css's
   `:where(p, ul, ol, blockquote)` paragraph rhythm too, because `:where()`
   contributes zero specificity by design. So body copy across the site uses
   Astra's 1.6em, not the design system's `--gpf-space-md`. At 15px that is
   24px against 18.75px — a difference worth a deliberate decision rather than
   a silent one, and it belongs to a typography pass, not to this file.
   ========================================================================== */

.gpf-hero p.gpf-hero__sell         { margin: var(--gpf-space-md) 0 var(--gpf-space-sm); }
.gpf-hero p.gpf-hero__promise-head { margin: 0 0 var(--gpf-space-xs); }

.gpf-postcard p.gpf-postcard__meta    { margin: 0; }
.gpf-postcard p.gpf-postcard__excerpt { margin: var(--gpf-space-2xs) 0 0; }

@media (min-width: 601px) {
  .gpf-hero p.gpf-hero__sell { margin-bottom: var(--gpf-space-md); }
}


