/* =====================================================================
   Schreiber Foods International — main stylesheet
   Montserrat headlines · Arial body · brand palette · ovalesque dividers
   Built to WCAG 2.2 AA (focus, contrast, reduced-motion, reflow).
   ===================================================================== */

:root {
	--red:      #C52435;
	--red-dark: #a81d2b;
	--green:    #4C9B44;
	--beige:    #CEB099;
	--charcoal: #63696D;
	--ink:      #1A1A1A;
	--paper:    #FFFFFF;
	--mist:     #F5F2EE;

	--font-head: 'Montserrat', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
	--font-body: Arial, 'Helvetica Neue', Helvetica, sans-serif;

	--gold:      #E8C87E;   /* warm accent for the dark variant */
	--ink-2:     #121417;   /* near-black panel */
	--footer-bg: #141416;   /* deep neutral footer (no blue cast) */
	--red-deep:  #9d1b28;   /* rich red for the utility/CTA bar */

	/* Concept B (Showcase) — warm, bright neutrals (client: no dark backgrounds) */
	--cream:     #F7F1E8;   /* warm panel / hero side */
	--sand:      #EFE6D8;   /* slightly deeper warm block */
	--linen:     #FBF8F3;   /* near-white warm section */

	--wrap: 1200px;
	--wrap-wide: 1360px;
	--gutter: clamp(1.15rem, 4vw, 2.75rem);
	--radius: 14px;
	--radius-lg: 22px;
	--section-y: clamp(2.5rem, 6vw, 5.5rem);

	--shadow-sm: 0 2px 8px rgba(28,28,28,.06);
	--shadow-md: 0 18px 40px -18px rgba(28,28,28,.28);
	--shadow-lg: 0 40px 80px -30px rgba(28,28,28,.45);

	--ease: cubic-bezier(.22,.61,.36,1);
	--curve-fill: var(--paper);
}

/* ---------- reset-ish ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
	margin: 0;
	font-family: var(--font-body);
	font-size: 1rem;
	line-height: 1.6;
	color: var(--ink);
	background: var(--paper);
}
img, svg, video { max-width: 100%; height: auto; display: block; }
a { color: var(--red); text-underline-offset: 2px; }
a:hover { color: var(--red-dark); }

h1, h2, h3, h4, h5, h6 {
	font-family: var(--font-head);
	font-weight: 700;
	line-height: 1.08;
	letter-spacing: -0.02em;
	color: var(--ink);
	margin: 0 0 .5em;
	text-wrap: balance;
}
h1 { font-size: clamp(2.4rem, 5.2vw, 4.5rem); font-weight: 800; }
h2 { font-size: clamp(1.9rem, 3.6vw, 2.9rem); }
h3 { font-size: clamp(1.3rem, 2vw, 1.6rem); letter-spacing: -0.01em; }

p { margin: 0 0 1rem; }

.wrap { width: min(100% - 2 * var(--gutter), var(--wrap)); margin-inline: auto; }
.wrap--wide { width: min(100% - 2 * var(--gutter), var(--wrap-wide)); }
.prose { max-width: 62ch; }
.prose p { color: color-mix(in srgb, var(--ink) 82%, transparent); }

/* ---------- eyebrow / kicker ---------- */
.eyebrow {
	font-family: var(--font-head);
	font-weight: 700;
	font-size: .78rem;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--red);
	margin: 0 0 1rem;
	display: inline-flex; align-items: center; gap: .6rem;
}
.eyebrow::before {
	content: ""; width: 28px; height: 2px; background: currentColor; display: inline-block;
}
.eyebrow--light { color: var(--gold); }

/* ---------- animated link with arrow ----------
   THE ARROW LIVES IN THE TEXT, NOT BESIDE IT (fixed 2026-09-23).

   This was `display: inline-flex; align-items: center; gap: .5rem` for a year,
   which is correct for every label that fits on one line and quietly broken for
   every label that does not. Two flex items — the label and the arrow — mean the
   label shrinks to the full width available, wraps INSIDE its own box, and the
   arrow is then placed after that box: pinned to the right edge of the card,
   vertically centred against two lines, ~90px of white between it and the word
   it belongs to. Colton, on the Distributors support cards: "these red text
   links — we need to think about improving them visually, they just look
   awkward." The links were not badly styled. The arrow was in the wrong place,
   and only on the cards whose label ran long, which is why it read as a
   mysterious wobble rather than as a bug.

   `display: inline-block` puts the arrow back in the text flow, where it trails
   the last word on whatever line the last word lands on. INLINE-BLOCK RATHER
   THAN INLINE, and that is not a detail: plain `inline` would drop every
   vertical margin on this component — .job__link, .news-card__cta and
   .mu-card__cta all set `margin-top` and several of their parents are ordinary
   blocks, not flex containers, so the margins would silently stop applying.
   Inline-block keeps the OUTER behaviour inline-flex already had (inline-level,
   shrink-to-fit, margins honoured, `margin-top: auto` still works when it is a
   flex item) and changes only the INSIDE, from a flex formatting context to a
   normal one. That inside is the whole bug.

   The one thing normal flow costs is that the arrow can now wrap to a line of
   its own; sfi_link_arrow_label() ties it to the last word so the pair moves
   down together. Any new .link-arrow must emit the arrow through that helper. */
.link-arrow {
	font-family: var(--font-head); font-weight: 700; font-size: .95rem;
	text-decoration: none; color: var(--red);
	display: inline-block;
	letter-spacing: .01em;
	/* Body leading (1.6) is set for paragraphs; on a two-line bold label at
	   .95rem it reads as two separate links rather than one wrapped one. */
	line-height: 1.35;
	/* A label that wraps breaks evenly instead of dropping one orphan word
	   ("Contact your Regional Sales / Manager" → "Contact your Regional / Sales
	   Manager"). Works because inline-block IS a block container; a one-line
	   label is unaffected, and a browser without it wraps correctly anyway, just
	   less prettily. */
	text-wrap: balance;
}
/* Scoped to .link-arrow: .product-label__cta also uses this icon and lays it out
   with its own flex `gap`, so a bare margin here would double the space. */
.link-arrow .link-arrow__icon { display: inline-block; margin-left: .5rem; }
/* The last word and the arrow, glued (see sfi_link_arrow_label). */
.link-arrow__tail { white-space: nowrap; }
.link-arrow__icon { transition: transform .3s var(--ease); }
.link-arrow:hover { color: var(--red-dark); }
.link-arrow:hover .link-arrow__icon { transform: translateX(6px); }

/* ---------- scroll reveal (only hides when JS is present → no-JS shows all) ---------- */
.sfi-js .reveal { opacity: 0; transform: translateY(26px); transition: opacity .8s var(--ease), transform .8s var(--ease); transition-delay: calc(var(--i, 0) * 90ms); will-change: opacity, transform; }
.sfi-js .reveal.is-visible { opacity: 1; transform: none; }

/* ---------- accessibility ---------- */
.screen-reader-text {
	position: absolute !important;
	width: 1px; height: 1px;
	padding: 0; margin: -1px; overflow: hidden;
	clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.skip-link {
	position: absolute; left: -9999px; top: 0; z-index: 1000;
	background: var(--ink); color: #fff; padding: .75rem 1rem;
}
.skip-link:focus {
	left: 8px; top: 8px; width: auto; height: auto; clip: auto;
	border-radius: 4px; color: #fff;
}
:focus-visible {
	outline: 3px solid var(--green);
	outline-offset: 2px;
}

/* ---------- buttons ---------- */
.btn {
	display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
	font-family: var(--font-head);
	font-weight: 700;
	font-size: .98rem;
	letter-spacing: .01em;
	text-decoration: none;
	padding: .95rem 1.9rem;
	border-radius: 999px;
	border: 2px solid transparent;
	cursor: pointer;
	min-height: 48px;
	line-height: 1.2;
	transition: transform .25s var(--ease), box-shadow .25s var(--ease), background .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn--primary { background: var(--red); color: #fff; box-shadow: 0 14px 26px -12px rgba(197,36,53,.7); }
.btn--primary:hover { background: var(--red-dark); color: #fff; box-shadow: 0 20px 34px -12px rgba(197,36,53,.85); }
/* NOT CURRENTLY USED IN ANY TEMPLATE. White on transparent — it only works over
   photography. It was on both "See All …" links until 2026-08-20, where it
   painted white on cream and the button simply vanished; they use --outline
   below now. Keep it for a future CTA over a hero image, but check the band
   colour before reaching for it. */
.btn--ghost { background: rgba(255,255,255,.06); color: #fff; border-color: rgba(255,255,255,.55); backdrop-filter: blur(4px); }
.btn--ghost:hover { background: #fff; color: var(--ink); border-color: #fff; }
/* The ghost above is for photography; this is its light-band counterpart —
   used by the "See All Olives" step at the foot of a product list, which is
   navigation and must not shout as loudly as the red spec-sheet CTA beside it
   (the restraint the client asked for on 2026-08-10 still holds). */
.btn--outline { background: transparent; color: var(--red); border-color: color-mix(in srgb, var(--red) 45%, transparent); }
.btn--outline:hover { background: var(--red); color: #fff; border-color: var(--red); }
.btn--lg { padding: 1.1rem 2.3rem; font-size: 1.05rem; min-height: 56px; }
.btn--small { padding: .4rem 1rem; min-height: 0; }

/* ---------- header / nav ----------
   Menu items flank a LARGE centred red brand mark (client: "the red logo is what
   chefs recognise"). `.site-nav` is display:contents on desktop so the two halves
   become flex children either side of the logo; below the nav breakpoint it
   becomes a real dropdown panel. */
/* Parent-company mark inside the nav row (client, 2026-08-04). It used to live in
   its own cream strip above the nav with the licensing sentence beside it; both
   the strip and the sentence are gone from the header — the full attribution
   still runs in the footer. */
.parent-lockup {
	display: flex; align-items: center; gap: clamp(.5rem, 1.4vw, .9rem);
	text-decoration: none; color: var(--charcoal);
}
/* Auto width off the natural ratio — the global img reset would otherwise squash it. */
.parent-lockup__logo { width: auto; max-width: none; height: clamp(23px, 2.3vw, 30px); object-fit: contain; display: block; flex: 0 0 auto; }
.parent-lockup__text {
	font-size: clamp(.68rem, .85vw, .78rem); line-height: 1.35; letter-spacing: .01em;
}
a.parent-lockup:hover .parent-lockup__text { color: var(--red); text-decoration: underline; text-underline-offset: 2px; }

/* Sized up ~1.5x on 2026-08-05 at the client's request. It cannot match the
   Ambrosia mark's HEIGHT — the SFI lockup is 4:1, so 68px tall would be 272px
   wide and there is no bar that holds that plus six labels plus a centred mark.
   Matched toward its WIDTH instead: 124px vs Ambrosia's 183px, ~70%. Every pixel
   here comes out of the left-hand nav cluster, which is what runs out first. */
.parent-lockup--nav .parent-lockup__logo { height: clamp(24px, 2.05vw, 31px); transition: height .3s var(--ease); }
.parent-lockup--nav { opacity: .92; transition: opacity .2s var(--ease); }
a.parent-lockup--nav:hover { opacity: 1; }
/* Steps down with the Ambrosia mark once the sticky bar compacts. */
.site-header.is-scrolled .parent-lockup--nav .parent-lockup__logo { height: clamp(20px, 1.7vw, 26px); }

.site-header {
	position: sticky; top: 0; z-index: 100;
	background: rgba(255,255,255,.9);
	backdrop-filter: saturate(150%) blur(12px);
	-webkit-backdrop-filter: saturate(150%) blur(12px);
	border-bottom: 1px solid rgba(28,28,28,.07);
}
/* Five columns: SFI mark · left nav · BRAND · right nav · icons.
   The two end columns are the SAME fixed width and the two nav columns are equal
   fractions, so the brand track lands on the exact centre line of the bar — the
   client's requirement — no matter what the labels say. Flex with equal-basis
   halves could only approximate that, because the SFI mark (~88px) and the search
   icon (44px) don't weigh the same.
   --nav-edge is sized to always exceed the SFI mark's own width: the mark is
   clamp(18px,1.7vw,22px) tall × 4.005 ratio = clamp(72px, 6.8vw, 88px). */
.site-header__inner {
	display: grid; align-items: center;
	--nav-edge: clamp(100px, 8.6vw, 132px);
	grid-template-columns: var(--nav-edge) minmax(0, 1fr) auto minmax(0, 1fr) var(--nav-edge);
	gap: clamp(.6rem, 1.6vw, 1rem); padding: .5rem 0;
}
/* No sibling mark at all (a single-site install, or the sibling URL cleared):
   the end columns collapse to just what the search button needs — 44px. */
.site-header__inner--no-parent { --nav-edge: clamp(48px, 4vw, 60px); }
/* THE SFI SITE, which carries the AMBROSIA mark (client, 2026-09-03 — the
   far-left mark is a two-way toggle now, see header.php).

   THIS BAR IS THE TIGHT ONE OF THE PAIR and the numbers below are not free
   parameters. Its own brand mark is the WIDE Schreiber wordmark (4.22:1, ~220px
   at the 1141px nav breakpoint, against Ambrosia's 2.69:1), and it was the only
   bar in the network with no end mark at all until today — so restoring that
   column spends slack the left-hand nav cluster does not have much of.

   Worked at 1141px, one pixel above the breakpoint, which is where it is
   tightest: wrap 1053 − ends 148 − brand 220 − gaps 55 = 630, i.e. 315 per nav
   half, against a left cluster ("Products · Distributors · Chefs & Kitchens")
   that measures ~285 at that size. ~30px of headroom. Re-derive all three
   values below if the breakpoint, the brand height or the labels move.

   Three things buy that back, and each is also right on its own terms:
   · The mark is set a notch SMALLER than the SFI mark is on Ambrosia. That one
     was sized up 1.5x at the client's request in August; this one carries no
     such history, and the Ambrosia logotype is optically heavier per pixel of
     height than the SFI lockup, so matching heights would not match weights.
   · The end columns are ~10px narrower per side than Ambrosia's, because the
     mark in them is narrower. Both ends still move TOGETHER — equal ends are
     what centres the brand on the bar, which is a client requirement.
   · The bar's own column gap tightens a little sooner. */
.site-header__inner--sibling {
	--nav-edge: clamp(74px, 6.4vw, 96px);
	gap: clamp(.5rem, 1.2vw, 1rem);
}
.site-header__inner--sibling .parent-lockup--nav .parent-lockup__logo { height: clamp(22px, 1.9vw, 28px); }
.site-header.is-scrolled .site-header__inner--sibling .parent-lockup--nav .parent-lockup__logo { height: clamp(19px, 1.6vw, 24px); }
.site-nav { display: contents; }
.nav-half { min-width: 0; }
/* EVERY CHILD IS PINNED TO ITS COLUMN, and that is not belt-and-braces.
   Auto-placement fills the five tracks with whatever children exist, so it only
   lands correctly when all five are present — and the SFI site has no parent
   mark, which used to shift the whole bar one column left: the nav crammed into
   the 130px end track and overran the logo, while the last track sat empty.
   `order` stays because the mobile bar below the breakpoint is a FLEX row,
   where order is what sequences it; grid-column is ignored there. */
.site-header__parent  { grid-column: 1; order: 0; justify-self: start; display: flex; align-items: center; }
.nav-half--left       { grid-column: 2; order: 1; }
.site-brand           { grid-column: 3; order: 2; justify-self: center; }
.nav-half--right      { grid-column: 4; order: 3; }
.site-header__actions { grid-column: 5; order: 4; justify-self: end; display: flex; align-items: center; gap: .25rem; }

/* The mark itself — the anchor of the whole bar. Shrinks once the page scrolls so
   the sticky bar doesn't follow the visitor down at full height.
   `max-width: none` is REQUIRED: the global img reset caps images at 100% of their
   container, which — against a fixed height — squashes the logo horizontally as soon
   as the brand cell gets tight. With it off, width always resolves from the image's
   natural ratio. object-fit stays as a guard in case both axes ever get constrained. */
.site-brand__logo {
	width: auto; max-width: none;
	height: clamp(52px, 4.4vw, 68px);
	object-fit: contain; display: block;
	transition: height .3s var(--ease);
}
.site-header.is-scrolled .site-brand__logo { height: clamp(42px, 3.6vw, 56px); }
.site-header { transition: box-shadow .3s var(--ease); }
.site-header.is-scrolled { box-shadow: 0 6px 22px -14px rgba(28,28,28,.35); }

.primary-nav__list {
	list-style: none; margin: 0; padding: 0;
	display: flex; align-items: center;
	/* The two nav columns are equal fractions, so the longer LEFT cluster is what
	   runs out of room first, just above the nav breakpoint — that's what these
	   clamps are tuned against. */
	gap: clamp(.65rem, 1.9vw, 1.25rem);
}
.nav-half--left  .primary-nav__list { justify-content: flex-start; }
.nav-half--right .primary-nav__list { justify-content: flex-end; }
/* Labels run RED across the bar, as in the client's own product-page concept
   (5.5:1 on white — clears AA). Since red no longer distinguishes the active
   item, the current page is marked by weight + an underline rule instead.

   TITLE CASE, not uppercase (2026-08-04). Six labels either side of a large
   centred mark plus the SFI mark is more than an uppercase bar can hold: measured
   against the real Montserrat 700 metrics, "Products · Distributors · Chefs &
   Kitchens" is 19.66em in title case at .02em versus 26.78em in caps at .085em —
   27% narrower, which is where the room for the SFI mark came from. Tightening
   tracking alone only bought 3%. The type is a notch LARGER as a result. */
.primary-nav__list a {
	font-family: var(--font-head); font-weight: 700;
	font-size: clamp(.78rem, .95vw, .84rem);
	letter-spacing: .02em;
	color: var(--red); text-decoration: none;
	padding: .65rem 0; display: inline-block; white-space: nowrap;
	position: relative;
	transition: color .2s var(--ease);
}
.primary-nav__list a:hover { color: var(--red-dark); }
.primary-nav__list a[aria-current="page"],
.primary-nav__item.is-current > a { color: var(--red-dark); font-weight: 800; }
.primary-nav__list a[aria-current="page"]::after,
.primary-nav__item.is-current > a::after {
	content: ""; position: absolute; left: 0; right: 0; bottom: .3rem;
	height: 2px; background: currentColor;
}
/* DROPDOWNS. The selector here is `.primary-nav__list`, NOT `.primary-nav`:
   the split renderer (inc/nav.php) emits <nav class="nav-half"> + <ul
   class="primary-nav__list">, and no element on the page has ever carried a
   bare `primary-nav` class. Written that way, these three rules matched
   nothing — which went unnoticed until 2026-09-01, when About Us became the
   first menu item with children and the sub-menu rendered as a static, always-
   open list that shoved the whole header apart. Keep them anchored to a class
   that is actually rendered.

   The right-hand half opens its panels right-aligned, so a dropdown on the
   last item cannot run off the edge of the bar. */
.primary-nav__list .sub-menu {
	list-style: none; margin: 0; padding: .5rem 0;
	position: absolute; left: 0; top: 100%; background: var(--paper); min-width: 240px;
	box-shadow: 0 8px 24px rgba(0,0,0,.12); border-radius: var(--radius);
	display: none; z-index: 10;
}
.nav-half--right .primary-nav__list .sub-menu { left: auto; right: 0; }
.primary-nav__item { position: relative; }
.primary-nav__item:hover > .sub-menu,
.primary-nav__item:focus-within > .sub-menu { display: block; }
/* ONE LINE PER ITEM (Colton, 2026-09-04: "no menu items line-break the
   submenu"). This said `white-space: normal`, which overrode the `nowrap` on
   the base link rule and let "Schreiber Foods International" wrap to two lines
   inside a 240px panel. The panel has a min-width and no max, so nowrap just
   widens it to the longest label — and the right-hand half opens right-aligned,
   so a wider panel still cannot run off the edge of the bar.

   MEASURED, not estimated, against the real Montserrat 700 metrics at the
   desktop size (.84rem, .05em tracking): the longest label in either menu,
   "Schreiber Foods International", is 229px of text, so the About Us panel grows
   to 264px and the Resources panel stays on its 240px min-width. At the 1141px
   nav breakpoint that panel still starts ~544px in. Nothing overflows; a label
   materially longer than ~29 characters is what would change that.

   It goes back to `normal` at the nav breakpoint, where the panel stops being a
   panel and becomes a static indented list on a phone screen. */
.primary-nav__list .sub-menu a { display: block; padding: .55rem 1.1rem; letter-spacing: .05em; white-space: nowrap; }

/* THE ACTIVE ITEM IN A DROPDOWN IS HIGHLIGHTED, NOT UNDERLINED (same note).
   The bar's active rule is `.primary-nav__list a[aria-current]`, which caught
   submenu links too — and there the ::after is a full-bleed 2px rule spanning
   the whole panel width, padding included, because the link is `display: block`.
   In the bar that underline is the approved treatment and it stays; in a panel
   it reads as a stray divider. So: suppress it below the top level and mark the
   current item the way a menu row usually is — darker red, one weight up, and a
   tint on the row.

   The hover tint is here for the tint above: without it the active row is the
   only thing in the panel with a background, which reads as a hover state stuck
   on. Colour alone still carries the state, so this is decoration, not meaning.

   Keyed on `aria-current` alone: `.is-current` is only ever set on a top-level
   <li> (see sfi_render_nav_item()), so a `.sub-menu .is-current` selector would
   be a rule that can never match. */
.primary-nav__list .sub-menu a[aria-current="page"]::after { content: none; }
.primary-nav__list .sub-menu a[aria-current="page"] {
	color: var(--red-dark); font-weight: 800;
	background: color-mix(in srgb, var(--red) 8%, transparent);
}
/* Hover is DEEPER than the current row's tint, not lighter, so hovering the
   current item still reads as a hover. It can safely be stronger than the
   resting active state because the active row is also darker red and a weight
   up — the tint is never the only thing carrying it. */
.primary-nav__list .sub-menu a:hover { background: color-mix(in srgb, var(--red) 14%, transparent); }
/* A parent's own underline would sit under the panel it opens. */
.primary-nav__item.menu-item-has-children > a { padding-right: .8rem; }
.primary-nav__item.menu-item-has-children > a::before {
	content: ""; position: absolute; right: 0; top: 50%; margin-top: -.15rem;
	width: .32rem; height: .32rem; border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translateY(-.05rem);
}
.primary-nav__placeholder { flex: 1; margin: 0; font-size: .85rem; color: var(--charcoal); }

/* icon buttons in the bar (search, hamburger) */
.search-toggle, .nav-toggle {
	background: none; border: 0; cursor: pointer; color: var(--ink);
	width: 44px; height: 44px; position: relative;
	display: inline-flex; align-items: center; justify-content: center;
	border-radius: 999px; transition: background .2s var(--ease), color .2s var(--ease);
}
.search-toggle:hover, .nav-toggle:hover { background: var(--mist); color: var(--red); }
.search-toggle .sfi-icon { width: 21px; height: 21px; }
.search-toggle[aria-expanded="true"] { background: var(--red); color: #fff; }

.nav-toggle { display: none; }
.nav-toggle__bar,
.nav-toggle__bar::before,
.nav-toggle__bar::after {
	content: ""; position: absolute; left: 10px; width: 24px; height: 2px;
	background: currentColor; transition: transform .2s ease, opacity .2s ease;
}
.nav-toggle__bar { top: 21px; }
.nav-toggle__bar::before { top: -7px; }
.nav-toggle__bar::after  { top: 7px; }

/* ---------- search (header drawer + inline forms) ---------- */
.header-search {
	background: var(--mist); border-bottom: 1px solid rgba(28,28,28,.07);
	overflow: hidden;
}
/* Collapsed only when JS can open it — no-JS visitors keep a usable field. */
.sfi-js .header-search { max-height: 0; border-bottom-color: transparent; transition: max-height .3s var(--ease); }
.sfi-js .header-search.is-open { max-height: 140px; border-bottom-color: rgba(28,28,28,.07); }
.header-search .wrap { padding: 1rem 0 1.15rem; }

.nav-search { display: flex; align-items: center; gap: .5rem; }
.nav-search__field {
	flex: 1; min-width: 0; font: inherit; font-size: 1rem;
	padding: .7rem 1rem; min-height: 48px;
	border: 2px solid color-mix(in srgb, var(--ink) 14%, transparent);
	border-radius: 999px; background: var(--paper); color: var(--ink);
}
.nav-search__field::placeholder { color: color-mix(in srgb, var(--ink) 45%, transparent); }
/* Focus ring recoloured to brand red (client preference over the site-wide green).
   Recolour the OUTLINE rather than adding a border on top of it — doing both is what
   gave the field a red inner ring inside a green outer one. */
.nav-search__field:focus-visible { outline-color: var(--red); }
.nav-search__submit {
	flex: 0 0 auto; width: 48px; height: 48px; border-radius: 999px;
	border: 0; background: var(--red); color: #fff; cursor: pointer;
	display: inline-flex; align-items: center; justify-content: center;
	transition: background .2s var(--ease), transform .2s var(--ease);
}
.nav-search__submit:hover { background: var(--red-dark); transform: translateY(-1px); }
/* The in-panel form only exists for the mobile menu. */
.nav-search--panel { display: none; }

/* Nav breakpoint. 1140px: the SFI mark grew ~1.5x on 2026-08-05 and took the
   1081–1140 band with it — below ~1140 the left cluster no longer fits its
   equal-width column, and collapsing to the menu button early beats colliding
   with the centred brand. Above it the bar clears by 32–85px. */
@media (max-width: 1140px) {
	.nav-toggle { display: inline-flex; }
	.search-toggle { display: none; }              /* mobile searches from inside the panel */
	.header-search { display: none; }
	/* Back to a plain row: mark · brand · menu button. The three of them must not
	   shrink — the brand mark is a fixed-height image and flex would squash it. */
	.site-header__inner { display: flex; justify-content: space-between; }
	.site-header__parent,
	.site-brand,
	.site-header__actions { flex: 0 0 auto; }
	.site-nav {
		display: block;
		position: absolute; inset: 100% 0 auto 0; background: var(--paper);
		box-shadow: 0 10px 28px rgba(0,0,0,.14);
		max-height: 0; overflow: hidden; transition: max-height .25s ease;
	}
	.site-nav.is-open { max-height: min(80vh, 640px); overflow: auto; }
	.nav-half { flex: none; }
	.nav-half--left .primary-nav__list { padding-top: .75rem; }
	.nav-half--right .primary-nav__list { padding-bottom: .5rem; }
	.primary-nav__list {
		flex-direction: column; align-items: stretch; gap: 0;
		padding-inline: var(--gutter);
	}
	.primary-nav__list a {
		padding: .9rem 0; font-size: 1rem; letter-spacing: .02em;
		border-bottom: 1px solid rgba(28,28,28,.07);
	}
	/* Same class fix as above: on the phone the panel is a static, always-open
	   indent under its parent, because there is no hover to open it with. */
	.primary-nav__list .sub-menu,
	.nav-half--right .primary-nav__list .sub-menu {
		position: static; display: block; box-shadow: none;
		padding: 0 0 0 1rem; min-width: 0; left: auto; right: auto;
	}
	/* Wrapping is right again here, and not optional: the panel is gone, the type
	   is up to 1rem and the list is indented inside the gutter, so "Schreiber
	   Foods International" on one line runs off a 320px phone. */
	.primary-nav__list .sub-menu a { white-space: normal; }
	.primary-nav__item.menu-item-has-children > a::before { display: none; }
	.nav-search--panel { display: flex; padding: 1rem var(--gutter) 1.35rem; }
	.site-brand__logo { height: clamp(46px, 11vw, 62px); }
	/* Three items on one row now (SFI mark · Ambrosia mark · menu button); the mark
	   steps down again so it still fits a 320px phone. */
	.parent-lockup--nav .parent-lockup__logo,
	.site-header.is-scrolled .parent-lockup--nav .parent-lockup__logo { height: clamp(18px, 3.4vw, 30px); }
}

/* ---------- ovalesque curve divider (brand motif) ---------- */
.sfi-curve { line-height: 0; }
.sfi-curve svg { width: 100%; height: clamp(32px, 5vw, 70px); display: block; }
.sfi-curve--paper svg path    { fill: var(--paper); }
.sfi-curve--mist svg path     { fill: var(--mist); }
.sfi-curve--charcoal svg path { fill: var(--charcoal); }
.sfi-curve--beige svg path    { fill: var(--beige); }
.sfi-curve--band svg path     { fill: var(--curve-fill); }

/* ---------- hero ----------
   SPLIT: solid tan panel (copy) + full-height rotating photography. No scrim and
   no gradient — the client asked for the flat tan because type reads far better
   on it. The copy column is padded out to the site wrap so the headline lines up
   with the content below; the photo bleeds to the viewport edge. */
.hero {
	position: relative; color: var(--ink); background: var(--cream);
	--hero-split: 50%;
	/* Left inset for the copy: it has to land on the site's content edge, so the
	   headline lines up with the nav above it and every section below it.
	   It is (heroWidth - wrap) / 2, and with a 50% split the hero is exactly twice
	   this column — so that reduces to 100% - wrap/2, written longhand. (It used to
	   read (100vw - wrap)/2, but 100vw INCLUDES the scrollbar while the page lays
	   out without it, which pushed the copy 7.5px right of everything else.)
	   Tied to the 50% split — re-derive if --hero-split moves. */
	--hero-inset: max(var(--gutter), calc(100% - var(--wrap) / 2));
	display: grid; grid-template-columns: var(--hero-split) calc(100% - var(--hero-split));
	align-items: stretch; isolation: isolate; overflow: hidden;
}
/* Longhands, not the `padding` shorthand: one bad value in a shorthand takes the
   whole declaration down — and the left inset is the fussy one to lose. */
.hero__inner {
	display: flex; align-items: center; min-width: 0;
	padding-top: clamp(2.75rem, 6vw, 5rem);
	padding-right: clamp(2rem, 3.5vw, 3.5rem);
	padding-bottom: clamp(4.5rem, 8vw, 7rem);
	padding-left: var(--hero-inset);
}
.hero__content { max-width: 46rem; }
/* An optional mark above the hero eyebrow. It exists for one situation: a page
   that sits on ONE domain and speaks in the other's voice — the Market Update
   page lives on Ambrosia and its copy opens "At Schreiber Foods International,
   we're committed to…". The mark is what makes that attribution visible in the
   half-second before anyone reads the sentence. */
.hero__logo { margin: 0 0 clamp(.9rem, 2vw, 1.35rem); }
.hero__logo img {
	width: auto; max-width: min(100%, 13rem);
	height: clamp(30px, 3.2vw, 42px);
	object-fit: contain; display: block;
}
.hero__title {
	color: var(--ink); margin-bottom: 1.1rem;
	font-size: clamp(2rem, 5.6vw, 2.7rem); font-weight: 800; letter-spacing: -.03em;
}
/* The registered mark, wherever it appears in display type (sfi_reg()).
   At 40–60px a full-height ® is a glyph the size of a capital and reads as a
   mistake; superscripting it is what every brand guide asks for and, usefully,
   makes a one-line headline NARROWER — the homepage H1's measured 13.52em was
   taken with the mark at full size, so this only buys slack. `top` rather than
   `vertical-align` so the mark cannot inflate the line box. */
.reg {
	font-size: .45em; font-weight: 600; letter-spacing: 0;
	position: relative; top: -.62em; line-height: 0;
}
/* The "s" of a decade inside a line set in capitals — "1980s", not "1980S"
   (client, 2026-09-23). Printed by sfi_caps_decades(). */
.nocaps { text-transform: none; }
/* IN BODY COPY THE HEADLINE RATIO IS WRONG. .45em of a 40–60px headline is a
   legible 20px mark; .45em of 15px running text is under 7px — it stops reading
   as a ® and starts reading as dirt on the screen. Howard's approved brand
   statement (2026-09-01) put "Ambrosia®" into two runs of small text — the
   footer lockup and the Contact sidebar — so those get a bigger mark and a
   shallower rise. Add any future body-text context to this list. */
.parent-lockup__text .reg,
.contact-side__text .reg,
/* The SFI company page (2026-09-02) added two more runs of small text carrying
   the mark: the Ambrosia "crown jewel" paragraph, and eleven brand wordmarks
   nine of which are registered. Same problem, same fix. */
.brands__primary-text .reg,
.brand__name .reg { font-size: .62em; top: -.42em; }
/* Client: the headline must stay on ONE line — so it is capped to whatever the
   copy panel can actually hold. Measured against the real font, "Ambrosia® Makes
   the Meal" is 13.52em of Montserrat 800 at -.03em tracking (confirmed against a
   rendered screenshot: 596px at 43.2px type).
   Re-fitted TWICE on 2026-08-04: first when the split went 54% → 50% (2.7rem/3.3vw
   → 2.45rem/3.0vw), then again when the copy panel's right inset was opened up
   from ~2rem to 3.5rem because the type was running into the photo edge
   (→ 2.3rem/2.8vw). Holds the line with 41–60px to spare from 1024px up.
   These are NOT free parameters — they are derived from the panel width, so
   re-derive them if either the split or the panel's insets move. */
@media (min-width: 1024px) {
	.hero__title { white-space: nowrap; font-size: min(2.3rem, 2.8vw); }
}
/* 901–1023px: still split, but the panel is too narrow for the big size. */
@media (min-width: 901px) and (max-width: 1023px) {
	.hero__title { font-size: 2.2rem; }
}
.hero__sub {
	font-size: clamp(1.1rem, 1.45vw, 1.32rem); line-height: 1.6;
	max-width: 46ch; color: color-mix(in srgb, var(--ink) 84%, transparent);
	margin-bottom: 2.15rem;
}
.hero__actions { display: flex; flex-wrap: wrap; gap: .85rem; }

/* ---- hero CTA ----
   History, because this went back and forth: the button was stripped to a plain
   ink outline on 2026-08-10 so it could not out-shout the hero copy (the client
   wants visitors to READ the page rather than be fired out of the hero — their
   reference is rolandfoods.com). Seeing it live, the client asked for the RED
   button back (2026-08-11) and judged that the enlarged hero copy plus the
   breathing room around the button already do the work of holding the eye on
   the words. So: brand red is back, and the restraint now lives in the button's
   SIZE and in the space above it, not in its colour —
     · a step below .btn--lg (which would be 1.05rem/56px),
     · a flatter shadow than the site default, so it sits on the cream panel
       instead of floating over it,
     · .hero__sub margin-bottom above, 1.85rem → 2.15rem.
   Overrides .btn--primary/.btn--lg by specificity, so the markup is untouched. */
.hero__actions .btn--primary {
	box-shadow: 0 10px 20px -14px rgba(197,36,53,.75);
}
.hero__actions .btn--lg {
	padding: .88rem 1.85rem; font-size: .98rem; min-height: 52px;
}
.hero__actions .btn--primary:hover {
	box-shadow: 0 16px 26px -14px rgba(197,36,53,.9);
}

/* ---- scroll cue ----
   A small arrow under the CTA nudging the visitor into the page. In the copy
   panel rather than centred on the hero's foot, because the curve divider is
   overlaid there and bulges ~70px up at mid-span — right where a centred cue
   would sit. A real in-page link, so it works without JS. */
.hero__scroll {
	display: inline-flex; align-items: center; gap: .6rem;
	margin-top: clamp(1.75rem, 3.5vw, 2.75rem);
	text-decoration: none; color: var(--charcoal);
	font-family: var(--font-head); font-weight: 700;
	font-size: .72rem; letter-spacing: .14em; text-transform: uppercase;
	transition: color .2s var(--ease);
}
.hero__scroll-icon {
	width: 40px; height: 40px; flex: 0 0 auto; border-radius: 999px;
	display: inline-flex; align-items: center; justify-content: center;
	border: 2px solid color-mix(in srgb, var(--red) 35%, transparent);
	color: var(--red); background: rgba(255,255,255,.5);
	transition: background .25s var(--ease), border-color .25s var(--ease), color .25s var(--ease);
}
.hero__scroll-icon .sfi-icon { width: 19px; height: 19px; animation: sfi-nudge 2.4s var(--ease) infinite; }
.hero__scroll:hover { color: var(--red); }
.hero__scroll:hover .hero__scroll-icon { background: var(--red); border-color: var(--red); color: #fff; }
@keyframes sfi-nudge {
	0%, 55%, 100% { transform: translateY(0); }
	72%           { transform: translateY(4px); }
}
/* Zero-height landing point dropped straight after each hero. The offset keeps
   the sticky header from covering the top of the section it lands on. */
.hero-anchor { display: block; height: 0; scroll-margin-top: 5.5rem; }
.hero__media { position: relative; min-width: 0; min-height: min(74vh, 680px); background: var(--sand); }
.hero__video { width: 100%; height: 100%; object-fit: cover; }
.hero .sfi-curve { position: absolute; bottom: -1px; left: 0; right: 0; z-index: 3; }

/* ---- the photo caption (The Schreiber Legacy) ----
   Sits ABOVE the curve, not under it. The curve is absolutely positioned across
   the whole hero at z-index 3 and stands up to 70px tall at mid-span, so a
   caption pinned to bottom:0 was half-swallowed by a white dome and had to be
   lifted clear of it, over a 82% scrim so white type cleared 4.5:1 against a
   blown-out sky.

   ALL OF THAT IS GONE, 2026-09-08. The caption now sits in the COPY panel,
   under the body text, as ordinary small print on the cream — client: "the
   caption is covering Emily's face, can we move it under the copy block
   instead?" Nothing overlays the photograph any more, so there is no scrim,
   no z-index and no curve to clear, and the type inherits the panel's own
   contrast. See sfi_render_split_hero(). */
.hero__caption {
	margin: clamp(1.5rem, 3vw, 2.25rem) 0 0; padding-top: clamp(.9rem, 2vw, 1.25rem);
	border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
	max-width: 48ch;
	color: color-mix(in srgb, var(--ink) 72%, transparent);
	font-size: clamp(.78rem, 1.1vw, .86rem); line-height: 1.6;
	text-wrap: pretty;
}
/* This hero carries a caption AND a wide group photograph, so it takes the same
   shorter frame the Our People hero used to: height off the box buys width back
   at the same aspect, and nobody at either end of the row gets cropped out. */
/* Our People took the split hero back on 2026-09-10 (see sfi_render_people_hero)
   and takes the pinned box with it, for the reason spelled out below: a
   thirty-five-person group photograph is the worst possible thing to hand a box
   whose aspect follows the window, and the people at each end are the first to
   go. 1.66 is the crop's own ratio. */
.hero--people .hero__media { aspect-ratio: 1.66; min-height: 0; align-self: center; }

/* PIN THE BOX'S SHAPE, DO NOT CHASE IT WITH THE CROP (2026-09-10).
   This is the second crop complaint on this one photograph — "too much ceiling,
   can't see the feet" on 09-09, then "Emily's mom is cut in half" on 09-10 —
   and re-cropping was the wrong answer both times, because the thing doing the
   cutting is not the file. `.hero` is `align-items: stretch`, so the media box
   is as tall as whichever COLUMN is taller, and on this page that is the copy:
   eyebrow, H1, a 267-character intro and a 210-character caption, roughly
   650px. So the box is ~720x650 at 1440 (aspect 1.11) and ~960x650 at 1920
   (aspect 1.48) — it is not one shape, it is a shape that changes with the
   window, and object-fit: cover was throwing away up to 23% of the WIDTH to
   fill it. Tricia stands 7% from the left edge of the frame. That is what was
   cutting her in half, and no crop of the source can fix a box that moves.

   So the box gets a fixed aspect instead, matched to the photograph, and stops
   stretching. `min-height: 0` retires the old floor and `align-self: center`
   is what actually stops the stretch — without it `aspect-ratio` is simply
   overridden by the row. The crop is now ~0.5% at every viewport instead of
   0–23%, and it is the same 0.5% on every screen, which is the part that
   matters: what the client approves is what everyone sees.

   The photograph stays full-bleed to the right edge, which is the whole point
   of this hero. When the copy runs taller than the picture the difference shows
   as cream above and below it, on the cream panel — a mounted print, which on
   the legacy page is if anything more right than a bled one. */
.hero--legacy .hero__media { aspect-ratio: 1.4; min-height: 0; align-self: center; }
/* The photograph was re-cropped on 2026-09-09 — client: "crop the hero photo
   just a bit differently so you can see the feet and less of the ceiling" — and
   the new file is WIDER than the old one (1.44 against 1.356) because that is
   what taking the ceiling off does to an aspect ratio.

   That matters here because this box is not one shape. It is 1.33 at 1440px
   wide, 1.78 at 1920 (the min-height stops growing at 540px while the column
   keeps widening), and 1.5 on a phone under the 560px rule — so the same
   picture is cropped at the SIDES on one screen and at the TOP AND BOTTOM on
   another. 1.44 sits between the extremes on purpose: the worst side crop is
   3.7% per edge and the worst vertical crop is 6%.

   The bias is the insurance on the second case. The feet are the point of the
   re-crop and they are at the bottom edge, so when the box does crop
   vertically, it should take more from the ceiling than from the floor. 58% is
   as far as it can go before a 1920px screen starts cutting into Howard's
   head — the ceiling above him is only about a tenth of the frame now. */
/* Centred, because there is essentially nothing left to bias. The 58% bottom
   bias this replaces existed to protect the feet from a vertical crop that the
   fixed aspect above has removed. */
.hero--legacy .hero__img { object-position: center; }

/* ---- hero carousel ---- */
.hero-carousel { position: absolute; inset: 0; }
.hero-carousel__track { list-style: none; margin: 0; padding: 0; position: absolute; inset: 0; }
.hero-carousel__slide {
	position: absolute; inset: 0; opacity: 0;
	transition: opacity .9s var(--ease);
}
.hero-carousel__slide.is-active { opacity: 1; }
.hero__img { width: 100%; height: 100%; object-fit: cover; object-position: center; }

.hero-carousel__controls {
	position: absolute; z-index: 4;
	right: clamp(1rem, 2.5vw, 2rem); bottom: clamp(3.25rem, 6vw, 4.75rem);
	display: flex; align-items: center; gap: .5rem;
	padding: .35rem .5rem; border-radius: 999px;
	background: rgba(255,255,255,.82);
	backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
	box-shadow: 0 8px 22px -10px rgba(28,28,28,.45);
}
html:not(.sfi-js) .hero-carousel__controls { display: none; }
.hero-carousel__arrow {
	width: 34px; height: 34px; border-radius: 999px; border: 0; cursor: pointer;
	background: none; color: var(--ink);
	display: inline-flex; align-items: center; justify-content: center;
	transition: background .2s var(--ease), color .2s var(--ease);
}
.hero-carousel__arrow:hover { background: var(--red); color: #fff; }
.hero-carousel__arrow .sfi-icon { width: 17px; height: 17px; }
.hero-carousel__dots { list-style: none; margin: 0; padding: 0; display: flex; align-items: center; gap: .3rem; }
.hero-carousel__dot {
	width: 24px; height: 24px; padding: 0; border: 0; background: none; cursor: pointer;
	display: inline-flex; align-items: center; justify-content: center;
}
.hero-carousel__dot::before {
	content: ""; width: 8px; height: 8px; border-radius: 999px;
	background: color-mix(in srgb, var(--ink) 26%, transparent);
	transition: background .25s var(--ease), transform .25s var(--ease);
}
.hero-carousel__dot:hover::before { background: color-mix(in srgb, var(--ink) 45%, transparent); }
.hero-carousel__dot.is-active::before { background: var(--red); transform: scale(1.35); }

/* Stack on small screens: photo on top, tan copy panel beneath (keeps the type
   on the flat tan rather than over the photo). */
@media (max-width: 900px) {
	.hero { grid-template-columns: 1fr; }
	.hero__media { order: -1; min-height: 0; aspect-ratio: 4 / 3; }
	.hero__inner { padding: clamp(2rem, 7vw, 3rem) var(--gutter) clamp(3.75rem, 11vw, 5rem); }
	.hero__content { max-width: 40rem; }
	.hero-carousel__controls { bottom: clamp(.75rem, 3vw, 1.25rem); }
}
@media (max-width: 560px) {
	.hero__media { aspect-ratio: 3 / 2; }
}

/* ---------- generic bands ---------- */
.intro { padding: clamp(2.5rem, 6vw, 5rem) 0; background: var(--mist); }
/* THE COMPACT SIZE IS THE SLIM HERO NOW, not a per-page exception (2026-09-09).
   Contact took this trim on 09-01 and Market Updates copied it on 09-04, both to
   buy back roughly 180px of fold; Our People and Food Sourcing took half of it on
   09-08. Then the client asked for the fourth copy — "the hero can be literally as
   small as the Contact / Market Update page so there is less blank space and it
   goes right into the content" — at which point four pages wanting the same three
   rules is the default with three exceptions written down, which is the pattern
   the cadence block above exists to stop. Folded in here; the per-page copies are
   deleted. A page that wants the tall version overrides these three. */
.inner-hero { background: var(--mist); padding: clamp(1.1rem, 2.2vw, 1.35rem) 0 0; position: relative; }
.inner-hero h1 { font-size: clamp(1.9rem, 3vw, 2.5rem); margin-bottom: .35em; }
.inner-hero .sfi-curve svg { height: clamp(22px, 2.2vw, 32px); }
.inner-hero--media { color: #fff; }
.inner-hero__intro { max-width: 60ch; font-size: 1.125rem; }
/* Optional brand mark above the H1 (Market Updates carries the SFI one while
   sitting under the Ambrosia header). Capped by HEIGHT, not width: the marks in
   this network run from a 1:1 roundel to a 7:1 wordmark, and only a height cap
   gives them the same optical weight. */
.inner-hero__logo { margin: 0 0 .5rem; line-height: 0; }
.inner-hero__logo img { height: clamp(28px, 2.6vw, 38px); width: auto; display: block; }
.page-body { padding: clamp(2rem, 5vw, 4rem) 0; }

/* ---------- product catalog ---------- */
.catalog__header { background: var(--mist); padding: clamp(2rem,5vw,4rem) 0 0; position: relative; }
.catalog__eyebrow a { font-family: var(--font-head); font-weight: 600; text-decoration: none; }
.catalog__body { padding: 2rem 0 4rem; }

/* filter bar + its selects are styled with the rest of the catalogue below */

.card-grid {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: 1.5rem;
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.card { background: var(--paper); border: 1px solid #e6e0d8; border-radius: var(--radius); padding: 1rem; display: flex; flex-direction: column; }
.card__link { text-decoration: none; color: var(--ink); }
.card__media { aspect-ratio: 4/3; overflow: hidden; border-radius: 8px; background: var(--mist); margin-bottom: .75rem; }
.card__img { width: 100%; height: 100%; object-fit: cover; }
.card__placeholder { display: block; width: 100%; height: 100%; background: repeating-linear-gradient(45deg,#efe9e1,#efe9e1 10px,#e7ded2 10px,#e7ded2 20px); }
.card__title { font-size: 1.1rem; margin: 0 0 .25rem; }
.card__meta { font-size: .85rem; color: var(--charcoal); display: flex; gap: .75rem; flex-wrap: wrap; margin: 0 0 .5rem; }
.card__spec { margin-top: auto; font-family: var(--font-head); font-weight: 600; font-size: .85rem; }
.catalog__empty { padding: 2rem 0; color: var(--charcoal); }

/* ---------- single product ---------- */
.product-single__grid { display: grid; gap: 2.5rem; grid-template-columns: 1fr 1fr; padding: clamp(2rem,5vw,4rem) 0; }
.product-single__img { width: 100%; border-radius: var(--radius); }
.product-single__cats a { font-family: var(--font-head); font-weight: 600; font-size: .85rem; text-transform: uppercase; letter-spacing: .04em; }
.product-single__specs { display: grid; grid-template-columns: auto 1fr; gap: .25rem 1rem; margin: 1rem 0; }
.product-single__specs dt { font-family: var(--font-head); font-weight: 600; color: var(--charcoal); }
.product-single__specs dd { margin: 0; }
.product-single__uses ul { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: .5rem; }
.product-single__uses li a { display: inline-block; padding: .35rem .8rem; background: var(--mist); border-radius: 999px; text-decoration: none; font-size: .85rem; }
/* Pack shots are cut-outs or shot on white — contain them in a white panel
   rather than letting a 3:4 jar dictate the height of the row. */
.product-single__media {
	display: grid; place-items: center; overflow: hidden; padding: clamp(1rem, 3vw, 2.5rem);
	background: var(--paper); border: 1px solid rgba(28,28,28,.08); border-radius: var(--radius-lg);
	aspect-ratio: 1; align-self: start;
}
/* Same clamp as the card stage — see the note there for why it is height:100%.
   The max-* pair is the sharpness cap (2026-08-24): --img-w/--img-h are the
   attachment's real pixel dimensions, printed on the wrapper by
   single-product.php, so a 250px pack shot scraped off the old site is not
   simply blown up to fill a 500px panel. Both default to `none`, so an image
   whose metadata is missing behaves exactly as before.

   --img-scale IS THE DIAL, and it is the only number to touch. Those 250px
   files are all that exists — the old site's originals are 250px and the
   spec-sheet PDFs carry no product photo, so there is no sharper source to
   fetch and every value here is a trade:
     1.0  native, sharpest, and the client said too small
     1.5  ~375px in the panel — the product reads at about half again its
          native size, softened but not mushy            <- shipped
     2.0  fills the panel, and is what the client called blurry
   Real photography lands above the cap and it stops applying by itself. */
.product-single__img {
	--img-scale: 1.5;
	width: 100%; height: 100%; min-width: 0; min-height: 0;
	max-width: calc(var(--img-w, 100%) * var(--img-scale));
	max-height: calc(var(--img-h, 100%) * var(--img-scale));
	object-fit: contain; border-radius: 0;
}
.product-single__title { margin: .25rem 0 .5rem; font-size: clamp(1.6rem, 3.2vw, 2.35rem); }
.product-single__id { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1.25rem; font-family: var(--font-head); font-weight: 700; color: var(--red); }
.product-single__id span[aria-hidden] { color: var(--beige); }
.product-single__pack { color: var(--charcoal); }
.product-single__specs {
	margin: 1.5rem 0; padding: 1.1rem 1.25rem; gap: .5rem 1.5rem;
	background: var(--mist); border-radius: var(--radius);
}
.product-single__specs dt { font-size: .82rem; letter-spacing: .02em; }
.product-single__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.5rem; margin-top: 1.5rem; }
.product-single .cat-crumbs { padding-top: 1.5rem; }
.product-single .cat-crumbs a { color: var(--charcoal); }
.product-single .cat-crumbs span { color: var(--beige); }
@media (max-width: 720px) { .product-single__grid { grid-template-columns: 1fr; } }

/* ---------- footer ---------- */
.site-footer { background: var(--footer-bg); color: rgba(255,255,255,.72); margin-top: clamp(3rem, 6vw, 5rem); position: relative; }
/* The footer's default top margin shows the white page background above it.
   That is only invisible on templates that END on white; anything ending on a
   tinted section gets a white bar between the section and the footer curve.
   Every full-bleed template is therefore flush.

   THE LIST BELOW IS NOT THE RULE, IT IS A SHORTCUT, AND IT FAILED THREE TIMES.
   Legacy on 09-09 ("a weird white thing at the bottom"), the orphaned SFI
   /contact/ stub the same day, and single market updates on 09-10 — all three
   ended on something tinted, none of the three was named here, and each one was
   found by a person looking at a page rather than by anything in this file.

   THE ACTUAL INVARIANT IS: **a tinted valley implies a flush footer.** If a
   rule sets `--footer-curve-fill` to anything but `--paper`, it is by definition
   describing a page that does NOT end white, and that page needs
   `margin-top: 0` in the same breath. So every one of those rules now carries
   both declarations — including the handful where the template is already in
   the list below and it is redundant, because the redundancy is the point: the
   next rule anyone writes will be copied from one of these, and it will bring
   the margin with it.

   To audit: `grep footer-curve-fill main.css | grep -v 'var(--paper)'` and check
   every hit also says `margin-top: 0`. */
.front + .site-footer,
.catalog + .site-footer,
.category-page + .site-footer,
/* THE SCHREIBER LEGACY, ADDED 2026-09-09, AND IT IS WORTH SAYING WHY THE RULE
   ABOVE DID NOT ALREADY COVER IT. The test that list encodes is "does the page
   end on a tinted section" — and the timeline is painted `--paper`, white, so
   it needs no fill (in 2026-09-09 terms because it was the first band after the
   anchor; since 2026-09-11 because the Legacy block pins it there). But
   `.timeline::before` lays the scrapbook wash over that background: a warm
   radial from the top and a beige diagonal weave over the whole band. So the
   cadence is telling the truth about the `background` property and the wrong
   thing about the page, and the footer's default top margin showed as a white
   strip 48–80px tall between the paper and the curve. Client: "the legacy page
   has a weird white thing at the bottom".

   GENERALISE IT: a section that paints itself with a pseudo-element is invisible
   to every rule that reasons about band colour, and this file reasons about band
   colour in about a dozen places. The timeline is the only one today. */
.page-legacy + .site-footer,
.product-single + .site-footer { margin-top: 0; }
/* The footer's valley curve is filled with whatever section sits above it. */
.front + .site-footer { margin-top: 0; --footer-curve-fill: var(--mist); }
.site-footer a { color: #fff; text-decoration: none; }
.site-footer a:hover { color: var(--gold); }
.site-footer__inner { display: grid; gap: clamp(2rem, 5vw, 4rem); grid-template-columns: 1.7fr 1fr 1.3fr; padding: clamp(2.75rem, 5vw, 4.25rem) 0 2.5rem; }

.site-footer__brand { max-width: 40ch; }
/* Red mark on white, sized up to match its new prominence in the header. */
.site-footer__logo { background: #fff; padding: .7rem 1rem; border-radius: 14px; width: clamp(200px, 20vw, 248px); height: auto; }
.site-footer__tagline {
	margin: 1.35rem 0 0; line-height: 1.4; color: #fff;
	font-family: var(--font-head); font-weight: 700; font-size: clamp(1.05rem, 1.5vw, 1.3rem);
	letter-spacing: -.01em;
}
/* Parent-company lockup (replaces the old "…family." sentence). Stacked here —
   the brand column is only ~40ch wide — and it uses the white-knockout wordmark,
   since the supplied art is maroon + black type and black type vanishes on the
   near-black footer. A hairline rule above sets it apart from the tagline. */
.parent-lockup--footer {
	display: block; margin: 1.5rem 0 0; padding: 1.35rem 0 0;
	border-top: 1px solid rgba(255,255,255,.12);
	color: rgba(255,255,255,.68);
}
.parent-lockup--footer .parent-lockup__logo { width: clamp(180px, 17vw, 210px); height: auto; }
.parent-lockup--footer .parent-lockup__text {
	display: block; margin-top: .8rem;
	font-size: .92rem; line-height: 1.6; max-width: 34ch;
	/* Explicit — `.site-footer a` (higher specificity) would otherwise pull it to full white. */
	color: rgba(255,255,255,.68);
}
a.parent-lockup--footer:hover .parent-lockup__text { color: var(--gold); text-decoration: underline; text-underline-offset: 2px; }

/* social icon row */
.social-links { list-style: none; margin: 1.6rem 0 0; padding: 0; display: flex; gap: .6rem; }
.social-links__link {
	width: 42px; height: 42px; border-radius: 999px;
	display: inline-flex; align-items: center; justify-content: center;
	border: 1px solid rgba(255,255,255,.22); color: #fff;
	transition: background .2s var(--ease), border-color .2s var(--ease), color .2s var(--ease), transform .2s var(--ease);
}
.social-links__link:hover { background: var(--red); border-color: var(--red); color: #fff; transform: translateY(-2px); }
.social-links .sfi-icon { width: 19px; height: 19px; }

.site-footer__h { font-family: var(--font-head); font-weight: 700; font-size: .78rem; letter-spacing: .15em; text-transform: uppercase; color: rgba(255,255,255,.5); margin: 0 0 1.2rem; }
.site-footer__nav { min-width: 0; }
.site-footer__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .8rem; }
.site-footer__list a { font-family: var(--font-head); font-weight: 600; }

.site-footer__contact address { font-style: normal; line-height: 1.7; margin: 0 0 1.4rem; color: rgba(255,255,255,.72); }
.site-footer__cs-label { display: block; font-family: var(--font-head); font-size: .75rem; text-transform: uppercase; letter-spacing: .13em; color: rgba(255,255,255,.5); margin-bottom: .25rem; }
.site-footer__phone { font-family: var(--font-head); font-weight: 800; font-size: 1.35rem; color: #fff; letter-spacing: .01em; }
.site-footer__phone:hover { color: var(--gold); }
.site-footer__h--search { margin-top: 2rem; }

/* search in the bottom nav — dark-surface treatment of the shared form */
.nav-search--footer .nav-search__field {
	background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.2); color: #fff;
	min-height: 44px; padding: .55rem .95rem; font-size: .95rem;
}
.nav-search--footer .nav-search__field::placeholder { color: rgba(255,255,255,.45); }
/* Red only clears ~2.2:1 against the near-black footer, below the 3:1 a focus ring
   needs to be visible, so the footer keeps a white ring. */
.nav-search--footer .nav-search__field:focus-visible { outline-color: #fff; }
.nav-search--footer .nav-search__submit { width: 44px; height: 44px; }

.site-footer__legal { border-top: 1px solid rgba(255,255,255,.1); font-size: .82rem; color: rgba(255,255,255,.55); padding: 1.35rem 0; }
.site-footer__legal .wrap { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 1.5rem; }
.site-footer__legal p { margin: 0; }
.site-footer__legal a { color: rgba(255,255,255,.75); }

@media (max-width: 860px) { .site-footer__inner { grid-template-columns: 1fr 1fr; } .site-footer__brand { grid-column: 1 / -1; } }
@media (max-width: 560px) { .site-footer__inner { grid-template-columns: 1fr; } }

/* Footer curve = the INVERSE of the hero's (client request).
   The hero fills the arc with the colour of the section BELOW it and pins it to
   the hero's bottom, so that colour arcs UP → the boundary reads as a dome.
   The footer mirrors it: same arc ROTATED 180° (so the fill hugs the top edge and
   bulges downward) and filled with the colour of the section ABOVE, sitting at
   the footer's top → the boundary reads as a valley.
   BOTH halves matter. Rotated + footer-coloured = invisible (colour on colour).
   Un-rotated + section-coloured = a crescent that floats free of the section
   above it and reads as a blade. */
.sfi-curve--footer { margin-top: -1px; }   /* kill any sub-pixel seam at the join */
.sfi-curve--footer svg path { fill: var(--footer-curve-fill, var(--paper)); }

/* ---------- misc ---------- */
.front__placeholder { padding: 4rem 0; text-align: center; }
.pagination, .nav-links { display: flex; gap: .5rem; flex-wrap: wrap; padding: 2rem 0; }
.pagination a, .pagination span { padding: .5rem .85rem; border-radius: 6px; background: var(--mist); text-decoration: none; }

/* =====================================================================
   HOMEPAGE — one design (Concept 1, approved 2026-07-29). The A/B concept
   switch and the Concept 2 skin were removed on 2026-07-30.
   ===================================================================== */

.front { --curve-fill: var(--paper); }

/* ---- parent-company band: editorial split ---- */
.band { padding: var(--section-y) 0; background: var(--paper); }
.band__grid { display: grid; gap: clamp(2rem, 5vw, 4.5rem); align-items: center; }
.band__grid--split { grid-template-columns: 1.05fr .95fr; }
.band__title { margin-bottom: 1.25rem; }
.band__body { font-size: 1.08rem; line-height: 1.72; }
.band__content .btn { margin-top: .5rem; }
.band__figure { margin: 0; position: relative; }
.band__img {
	width: 100%; aspect-ratio: 3 / 2; object-fit: cover;
	border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
}
/* A MARK, NOT A PHOTOGRAPH — no crop, no radius, no shadow, and no oval behind
   it. Sized well under the column so a wordmark reads as a signature on the
   band rather than as a picture of a logo. See sfi_image_has_alpha(). */
.band__figure--mark .band__img {
	aspect-ratio: auto; object-fit: contain;
	width: min(100%, 22rem); height: auto;
	border-radius: 0; box-shadow: none;
}
.band__figure--mark { display: flex; justify-content: center; }
.band__figure--mark::after { display: none; }

/* THE MARK OVER A PHOTOGRAPH — the "olive overlay" (client, 2026-09-09).
   The picture is the figure's ground and the logo sits on it, so this is one
   composed object rather than two stacked ones. The scrim is a warm, mostly
   opaque wash rather than a fade: the wordmark is thin and dark-red, and the
   harvest photograph has bright sky in the top corners, so a gradient would put
   the thinnest strokes over the brightest pixels. At 88% cream over the image
   the olives still read as olives and the mark keeps its full contrast. */
.band__figure--over {
	position: relative; isolation: isolate;
	aspect-ratio: 3 / 2;
	align-items: center;
	border-radius: var(--radius-lg); overflow: hidden;
	box-shadow: var(--shadow-md);
	padding: clamp(1.5rem, 4vw, 2.75rem);
}
.band__figure--over .band__backdrop {
	position: absolute; inset: 0; z-index: -2;
	width: 100%; height: 100%; object-fit: cover;
}
.band__figure--over::before {
	content: ""; position: absolute; inset: 0; z-index: -1;
	background: color-mix(in srgb, var(--cream) 88%, transparent);
}
/* The mark sits smaller here than it does on the bare band: it is now inside a
   frame rather than filling a column, and a wordmark that runs to the edges of
   its own panel reads as a header, not a signature. */
.band__figure--over .band__img { width: min(100%, 17rem); }

/* brand curve/oval accent tucked behind the image */
.band__figure::after {
	content: ""; position: absolute; z-index: -1;
	inset: auto -20px -20px auto; width: 62%; height: 66%;
	border-radius: var(--radius-lg); background: var(--beige); opacity: .55;
}
/* NO PHOTOGRAPH → CENTRED. The site's standing rule, and the client's own
   correction on the company page: copy that is constrained and has nothing
   beside it centres, rather than sitting in a 62ch column against the left edge
   of a 1200px band with the right half empty. The Ambrosia homepage's band
   always has a picture, so this only reaches the SFI one. */
.band__grid:not(.band__grid--split) .band__content { max-width: 68ch; margin-inline: auto; text-align: center; }
.band__grid:not(.band__grid--split) .band__body { margin-inline: auto; }

@media (max-width: 860px) {
	.band__grid--split { grid-template-columns: 1fr; }
	.band__figure { order: -1; }
}

/* stat highlights — 3-up row with divider rules */
.stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(1rem, 2.5vw, 2rem); margin: 2.25rem 0; padding: 0; }
.stats__item { margin: 0; padding-left: clamp(1rem, 2vw, 1.5rem); border-left: 2px solid color-mix(in srgb, var(--red) 55%, transparent); }
.stats__value { font-family: var(--font-head); font-weight: 800; font-size: clamp(1.9rem, 3.4vw, 3rem); line-height: 1; color: var(--red); letter-spacing: -.03em; }
.stats__label { margin: .5rem 0 0; font-family: var(--font-head); font-weight: 600; font-size: .76rem; text-transform: uppercase; letter-spacing: .1em; color: var(--charcoal); line-height: 1.35; }
@media (max-width: 460px) { .stats { grid-template-columns: 1fr 1fr; } }

/* ---- audience benefit cards ---- */
.benefits { padding: var(--section-y) 0; background: var(--mist); }
.benefits__grid { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); grid-template-columns: repeat(2, 1fr); }
.benefits__grid[data-count="1"] { grid-template-columns: 1fr; max-width: 680px; margin-inline: auto; }
.benefits__grid[data-count="3"] { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 820px) { .benefits__grid { grid-template-columns: 1fr !important; } }
.benefit-card {
	background: var(--paper); border-radius: var(--radius-lg); overflow: hidden;
	box-shadow: var(--shadow-md); display: flex; flex-direction: column;
	transition: transform .45s var(--ease), box-shadow .45s var(--ease);
}
.benefit-card:hover { transform: translateY(-8px); box-shadow: var(--shadow-lg); }
.benefit-card__media { aspect-ratio: 16 / 10; overflow: hidden; }
.benefit-card__img { width: 100%; height: 100%; object-fit: cover; transition: transform .7s var(--ease); }
.benefit-card:hover .benefit-card__img { transform: scale(1.06); }
.benefit-card__inner { padding: clamp(1.7rem, 3vw, 2.5rem); display: flex; flex-direction: column; align-items: flex-start; flex: 1; }
.benefit-card__title { margin: 0 0 .6rem; }
.benefit-card__text { margin: 0; color: color-mix(in srgb, var(--ink) 78%, transparent); line-height: 1.65; }
.benefit-card__cta { margin-top: auto; padding-top: 1.5rem; }

/* ---- utility strip: a rich red accent bar (breaks up the dark content ↔ dark footer) ---- */
.utility-strip { background: var(--red-deep); color: #fff; }
.utility-strip a { color: #fff; }
.utility-strip__inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem 2rem; padding: 1.5rem 0; }
.utility-strip .link-arrow { color: #fff; }
.utility-strip .link-arrow:hover { color: #fff; opacity: .82; }
.utility-strip__cs { margin: 0; font-family: var(--font-head); display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.utility-strip__cs-label { font-weight: 400; opacity: .85; font-size: .95rem; }
.utility-strip__phone { font-weight: 800; font-size: 1.2rem; letter-spacing: .01em; text-decoration: none; }
.utility-strip__phone:hover { color: #fff; opacity: .85; }

/* ---- "What's New": 3 tiles (Market Update / Newsroom / Instagram).
        Client asked for this to sit back — it's a utility footer-of-page block,
        not a feature. Tighter padding, smaller heading, shorter images, flatter
        cards than the audience section above it. ---- */
.whats-new { padding: clamp(2rem, 4vw, 3.25rem) 0 clamp(2.25rem, 4.5vw, 3.75rem); background: var(--paper); }
.whats-new__head { margin-bottom: clamp(1.1rem, 2.2vw, 1.65rem); }
.whats-new__title { margin: 0; font-size: clamp(1.25rem, 1.9vw, 1.6rem); }
.whats-new .eyebrow { font-size: .7rem; margin-bottom: .55rem; }
.whats-new__grid {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: clamp(.9rem, 1.8vw, 1.35rem);
	grid-template-columns: repeat(3, 1fr);
}
/* A SHORT ROW CENTRES RATHER THAN HANGING LEFT. Three tiles is the designed
   case, but a tile with no destination is dropped (see sfi_render_whats_new),
   and while the WIP gate is up BOTH the Market Update and Resources tiles go —
   leaving one card in the left third of a three-column grid with two empty
   thirds beside it. Which is precisely the "pushed to the left and blank on the
   right" the client raised about the SFI page. Same data-count pattern as
   .benefits__grid. */
.whats-new__grid[data-count="1"] { grid-template-columns: 1fr; max-width: 420px; margin-inline: auto; }
.whats-new__grid[data-count="2"] { grid-template-columns: repeat(2, 1fr); max-width: 820px; margin-inline: auto; }
@media (max-width: 900px) { .whats-new__grid { grid-template-columns: 1fr; max-width: 520px; margin-inline: auto; } }
.news-card {
	background: var(--paper); border-radius: var(--radius); overflow: hidden;
	box-shadow: var(--shadow-sm); border: 1px solid rgba(28,28,28,.07);
	transition: transform .4s var(--ease), box-shadow .4s var(--ease);
}
.news-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.news-card__link { display: flex; flex-direction: column; height: 100%; text-decoration: none; color: var(--ink); }
.news-card__media { aspect-ratio: 16 / 8; overflow: hidden; }
.news-card__img { width: 100%; height: 100%; object-fit: cover; transition: transform .6s var(--ease); }
.news-card:hover .news-card__img { transform: scale(1.05); }
.news-card__body { padding: clamp(1rem, 1.8vw, 1.35rem); display: flex; flex-direction: column; align-items: flex-start; gap: .35rem; flex: 1; }
.news-card__eyebrow { margin: 0 0 .2rem; font-size: .66rem; }
.news-card__eyebrow::before { width: 18px; }
.news-card__title { font-family: var(--font-head); font-weight: 700; font-size: 1.02rem; letter-spacing: -.01em; line-height: 1.25; }
.news-card__text { margin: 0; color: color-mix(in srgb, var(--ink) 72%, transparent); line-height: 1.5; font-size: .88rem; }
.news-card__cta { margin-top: auto; padding-top: .6rem; font-size: .85rem; }


/* ---- category browse: "Browse the Catalog" ---- */
.categories { padding: var(--section-y) 0; background: var(--paper); }
.categories__head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem 2rem; flex-wrap: wrap; margin-bottom: clamp(1.75rem, 4vw, 3rem); }
.categories__title { margin: 0; }
.categories__intro { margin: .85rem 0 0; color: color-mix(in srgb, var(--ink) 78%, transparent); }
.categories__all { white-space: nowrap; }
.categories__grid {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: clamp(1rem, 2vw, 1.5rem);
	grid-template-columns: repeat(5, 1fr);
}
@media (max-width: 1040px) { .categories__grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 640px)  { .categories__grid { grid-template-columns: repeat(2, 1fr); } }
.cat-card__link { display: flex; flex-direction: column; gap: .85rem; text-decoration: none; color: var(--ink); }
.cat-card__media {
	position: relative; display: block; aspect-ratio: 1 / 1; overflow: hidden;
	border-radius: var(--radius-lg); background: var(--mist); box-shadow: var(--shadow-sm);
}
.cat-card__img { width: 100%; height: 100%; object-fit: cover; transition: transform .6s var(--ease); }
.cat-card__placeholder { position: absolute; inset: 0; background: linear-gradient(135deg, var(--sand), var(--mist)); }
/* These tiles are shot on white marble, so on the white catalog band they need a
   hairline to hold their edge. */
.cat-card__media::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius-lg); box-shadow: inset 0 0 0 1px rgba(28,28,28,.1); pointer-events: none; }
.cat-card__link:hover .cat-card__img { transform: scale(1.07); }
.cat-card__label { font-family: var(--font-head); font-weight: 700; font-size: 1.02rem; letter-spacing: -.01em; text-align: center; }
.cat-card__link:hover .cat-card__label { color: var(--red); }

/* ===================== BASE SECTION CADENCE =====================
   THE TWO RULES, AS DEFAULTS RATHER THAN AS A THING TO REMEMBER (2026-09-03).

   Client, for at least the fourth time: "you keep doing this thing where you
   don't make the 2nd section white, it should always be white no matter what.
   The last section also needs to be white or at least match its divider. This
   consistently happens on almost every page — I keep fixing it with you and
   then you do it again on a new page."

   They are right, and the cause was structural, not carelessness on any one
   page: FOURTEEN components each hard-coded their own warm tint (.mu, .faqs,
   .support, .pillars, .catalogue-list …), so every page was born tinted and
   only became correct if someone remembered to write a per-page cadence block.
   Five templates had one. The audience template never did, which is why
   /distributors/ was live with mist → linen → cream, three warm tints running
   into each other.

   THE FIX IS A SPECIFICITY SANDWICH, and it is why these selectors look odd.
   Component defaults are one class, (0,1,0). Per-page cadences are two,
   (0,2,0). Keying on the ELEMENT lands these at (0,1,N) — one class, N
   elements — which beats every component default and loses to every per-page
   block. So the cadence becomes the default for any page that does not declare
   one, and not one existing approved rule had to change.

   DO NOT "tidy" these into .site-main. That would make them (0,2,N) and they
   would start overriding the approved per-page cadences below.

   Positional off .hero-anchor, like the category pages, so it holds however
   many bands a page turns out to have: first band after the hero WHITE, then
   alternating through white so two tints can never touch. */
main .hero-anchor + section { background: var(--paper); }
main .hero-anchor + section + section { background: var(--mist); }
main .hero-anchor + section + section + section { background: var(--paper); }
main .hero-anchor + section + section + section + section { background: var(--linen); }
main .hero-anchor + section + section + section + section + section { background: var(--paper); }

/* FIXED FURNITURE opts out. These are not bands in the alternation — they are
   pinned to one end of the page and their colour is part of the design: the
   hero is always first and always cream, the closing CTA always last and always
   cream. Two classes, so they sit above the cadence above. */
.site-main .hero,
.site-main .catalogue-hero,
.site-main .cta-band { background: var(--cream); }
.site-main .inner-hero { background: var(--mist); }

/* …and the second rule: the valley curve is filled with whatever band actually
   ENDS the page, so the divider always matches the colour above it. The
   inherited default is white, which is right whenever the page ends white; this
   covers the case the client caught on the two SFI stub pages, which end on the
   mist hero because they have no content band yet. */
.site-main:has(> .inner-hero:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--mist); }
/* A closing CTA is fixed furniture and always cream, so any page that ends on
   one takes a cream valley. Written once, here, rather than per template: the
   audience pages, the SFI homepage and now a single market update all end this
   way, and the market update is why it is stated — its band had been silently
   missing since 2026-09-02 (see single.php) and restoring it moved that page
   from ending white to ending cream. */
.site-main:has(> .cta-band:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--cream); }

/* THE SAME CASE FOR THE SPLIT HERO, which reaches it differently: it drops a
   .hero-anchor after itself, so "nothing follows the hero" is the anchor being
   main's last child. The Schreiber Legacy is that page until its timeline is
   built.

   TWO declarations because this hero carries a curve of its own. Left alone the
   page ends with the hero's white dome stacked directly on the footer's white
   valley — two curve shapes touching, with no band between them for either to
   be the edge of. Filling the hero's curve with the band's own cream makes it
   disappear, and the footer's valley then rises out of the cream hero as the
   one divider, which is exactly the rule. */
.site-main:has(> .hero-anchor:last-child) { --curve-fill: var(--cream); }
.site-main:has(> .hero-anchor:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--cream); }
/* …and the hero's "See more" cue goes with them. It points at that anchor, and
   with nothing after it the arrow is an invitation into the footer. It comes
   back on its own the day the page gains a second band. */
.site-main:has(> .hero-anchor:last-child) .hero__scroll { display: none; }

/* ===================== SECTION RHYTHM =====================
   Client: "the next section can be a different background colour so it's not tan
   followed by tan":
     hero TAN → catalog WHITE → parent-company MIST → audience WHITE →
     what's-new MIST → footer (dark, valley curve filled with MIST). */
.front .categories { background: var(--paper); }
.front .band       { background: var(--mist); }
.front .benefits   { background: var(--paper); }
.front .whats-new  { background: var(--mist); }
/* A homepage carrying the company FAQ (the SFI landing page) runs one band
   longer, so the alternation shifts from the FAQ down rather than putting a
   near-white FAQ against the white audience cards above it:
     … audience WHITE → FAQs MIST → what's-new WHITE → footer (curve WHITE). */
.front--faq .faqs      { background: var(--mist); }
.front--faq .whats-new { background: var(--paper); }
/* …and the footer curve follows whichever of the two actually ends the page.
   What's New drops out entirely when none of its tiles has a destination, so
   this cannot be hard-coded: with it, the page ends white; without it, the page
   ends on the FAQ's mist and the base .front rule above is already right. */
.front--faq:has(.whats-new) + .site-footer { --footer-curve-fill: var(--paper); }

/* ---- the SFI corporate homepage (.front--company) ----
   A different set of sections entirely — pillars, brands, FAQs, CTA — so none
   of the alternation above applies to it and it was falling through to each
   component's own default: cream hero → LINEN pillars → white brands → MIST
   FAQs → CREAM CTA, with the footer's valley curve still filled MIST from the
   base .front rule. That broke the palette rule twice (see PALETTE RULE, the
   category-page block): cream against linen at the top, where the hero's WHITE
   dome then had nothing white to empty into and read as a stray crescent; and
   grey mist against beige cream at the foot, where the mist-filled footer
   curve sat on the cream band as a grey shape.

   Its own cadence, alternating THROUGH WHITE so two warm tints never touch:
     hero CREAM → pillars WHITE → brands MIST → FAQs WHITE → CTA CREAM →
     footer (valley curve filled CREAM, matching the band above it).
   Must stay after the .front--faq rules — it overrides the FAQ band's tint at
   equal specificity.

   RESTRUCTURED 2026-09-04. The page gained two bands — "The Company" (the
   editorial .band, which on Ambrosia is mist and here has to be the white band
   two) and the story blocks — and the pillars moved from second to sixth. Seven
   bands now, still alternating through white so no two warm tints touch:
     hero CREAM → The Company WHITE → brands MIST → stories WHITE →
     pillars LINEN → FAQs WHITE → CTA CREAM → footer (curve CREAM). */
.front--company .band    { background: var(--paper); }
.front--company .brands  { background: var(--mist); }
.front--company .stories { background: var(--paper); }
.front--company .pillars { background: var(--linen); }
.front--company .faqs    { background: var(--paper); }
/* The featured Ambrosia panel is cream, and cream on mist is 6/255 apart — it
   would dissolve into the band. White, like every other card on a tinted band
   in this theme. */
.front--company .brands__primary { background: var(--paper); box-shadow: var(--shadow-sm); }
/* …and the accordion rows are white on a now-white band, so they carry their
   resting edge as a shadow rather than relying on a 9%-alpha hairline. */
.front--company .faq { box-shadow: var(--shadow-sm); }
.front--company + .site-footer { margin-top: 0; --footer-curve-fill: var(--cream); }
/* The FAQ head centres with the two above it (2026-09-03). Pillars and Brands
   take .section-head--center in their own markup; the FAQ renderer is SHARED
   with the Distributors page, whose flush-left head is approved, so it is
   centred from here and only here. Leaving one band flush left between two
   centred ones re-creates exactly the "pushed to the left and blank on the
   right" the client was complaining about. */
.front--company .faqs .section-head { margin-inline: auto; text-align: center; }
.front--company .faqs .section-head__intro { margin-inline: auto; }
.front .band__figure::after { background: var(--beige); opacity: .5; }
/* Cards need to lift off the tinted bands they sit on. */
.front .benefit-card { background: var(--paper); }
.front .news-card    { background: var(--paper); }


/* =====================================================================
   AUDIENCE LANDING PAGES — "For Chefs & Kitchens", "For Distributors".
   Built out of the homepage's approved components (split cream hero, benefit
   cards, tile grid) so an inner page reads as the same site. Only the pieces
   this page adds are defined here.

   Section rhythm, same alternation as the homepage. Each page shows only the
   sections it has copy for, so the two run:
     Chefs:        hero CREAM → cards WHITE → inspiration MIST → steps WHITE →
                   CTA CREAM → footer (valley curve filled with cream).
     Distributors: hero CREAM → cards WHITE → support MIST → FAQs LINEN →
                   CTA CREAM → footer.
   ===================================================================== */

.audience { --curve-fill: var(--paper); }
.audience + .site-footer { margin-top: 0; --footer-curve-fill: var(--cream); }
/* FAQ answers jump to sections on this same page ("your Regional Sales
   Manager" → the map). Keep the sticky header off whatever they land on. */
.audience :is(h2, h3)[id] { scroll-margin-top: 6rem; }
/* The forms box is a jump target too (the map pins' "Send a message", and the
   Contact page's links in). Without this the sticky header sat over the RSM
   button and the first thing in view was the non-commercial one (2026-09-18). */
.rsm-contact { scroll-margin-top: 6rem; }

/* ---- page hero ----
   Same hero as the homepage, on purpose — the client asked why it wasn't, and the
   answer was that nothing forced it apart. Every height/padding override this
   block used to carry has been deleted so the page inherits the homepage values.

   Exactly two things still differ, and both are structural rather than taste:

   1. The headline WRAPS. The homepage H1 is a four-word brand line pinned to one
      line (13.52em); these are sentences — "Ambrosia Imported Foods for Chefs and
      Kitchens" is 24.13em, which cannot fit one line in a half-width panel at any
      readable size. It takes the homepage's own type size and runs to two lines.
   2. The photo is taken out of flow. On the homepage the img sits inside
      .hero-carousel (position:absolute; inset:0) so its height:100% resolves
      against a filled box. Here it is a direct child of the grid item, where
      height:100% computes to auto — the image then sized the row off its own
      intrinsic ratio (a 896x1200 portrait rendered ~954px tall in a ~712px
      column, past 100vh). This is the carousel's positioning, without a carousel. */
.hero--page .hero__title { white-space: normal; }
.hero--page .hero__media > .hero__img { position: absolute; inset: 0; }

/* NO SIZE OF ITS OWN — THE HEADLINE TAKES THE HOMEPAGE'S, WHICH IS WHAT POINT 1
   ABOVE SAYS AND WHAT THE CLIENT ASKED FOR ON 2026-09-22: "the Products, Chefs,
   and Distributors pages seem to have a larger hero font than the rest of the
   pages like Ambrosia main page and SFI main page … the Ambrosia main page size
   looks like the right one."

   THIS REVERSES AN EARLIER INSTRUCTION, DELIBERATELY. There used to be a
   `font-size: clamp(2rem, 3.2vw, 2.75rem)` here — ~20% above the homepage —
   added because the client had asked to "make the hero copy bigger" and the
   homepage H1 could not grow (it is pinned to the largest size that holds ONE
   line). That bump is what she is now looking at across six pages at once, and
   matching the two front pages wins.

   SO DO NOT RE-DECLARE THE VALUE HERE, even to write the same numbers down. The
   homepage size is DERIVED from the copy panel's width (see the nowrap block up
   at .hero__title), and a second copy of it is a second thing to re-derive the
   day the split or the panel insets move. Inheriting is the fix. Only
   white-space differs, above.

   MORE THAN THE THREE PAGES SHE NAMED CHANGE, because every .hero--page shares
   this rule: Products (the catalogue overview), For Chefs & Kitchens, For
   Distributors, the category archives, The Schreiber Legacy and Our People.
   That is the point — they were the odd ones out, and shrinking only three
   would leave three new ones. */

/* ---- shared section header ---- */
.section-head { max-width: 62ch; margin-bottom: clamp(1.75rem, 4vw, 2.75rem); }
.section-head h2 { margin: 0; }
/* Centred variant. The site already had this rule, written out twice in the
   category stylesheet — copy that is CONSTRAINED AND ALONE gets centred, and
   only flips back to flush left when something sits beside it. This is that
   rule as a class, added 2026-09-03 when the client read the SFI company page
   and said the copy "was pushed to the left and blank on the right … how will
   it look to center the main copy the way you did on the product pages". */
.section-head--center { margin-inline: auto; text-align: center; }
.section-head--center .section-head__intro { margin-inline: auto; }
.section-head__intro { margin: .85rem 0 0; font-size: 1.06rem; line-height: 1.65; color: color-mix(in srgb, var(--ink) 80%, transparent); }
/* Client (2026-08-10): the copy directly under "How We Support Our Distributor
   Partners" is the section's argument, not a caption — it gets deck-sized type
   and a wider measure than the other section intros. */
.support .section-head__intro {
	font-size: clamp(1.12rem, 1.5vw, 1.3rem); line-height: 1.6; max-width: 72ch;
	color: color-mix(in srgb, var(--ink) 88%, transparent);
}

/* ---- audience cards ---- */
.audience-blocks { background: var(--paper); }

/* ---- recipe inspiration ----
   Same tile component as the homepage's "What's New", but this is a feature on
   this page rather than a utility footer block, so the cards get their height
   and type back. */
.inspiration { padding: var(--section-y) 0; background: var(--mist); }
.inspiration .news-card__media { aspect-ratio: 16 / 10; }
.inspiration .news-card__body { padding: clamp(1.15rem, 2.2vw, 1.6rem); gap: .45rem; }
.inspiration .news-card__eyebrow { font-size: .7rem; }
.inspiration .news-card__title { font-size: clamp(1.08rem, 1.5vw, 1.22rem); }
.inspiration .news-card__text { font-size: .95rem; }
.inspiration .eyebrow { font-size: .78rem; margin-bottom: 1rem; }
.inspiration .cat-card__placeholder { position: static; display: block; height: 100%; }

/* ---- inspiration panels ----
   The client asked for these to open in place, not as separate pages. Each tile
   links to its panel by id: WITHOUT JS the link is an ordinary jump and the
   panels read as content at the foot of the section; WITH JS the same panel
   opens as a modal (js sets role/aria-modal and moves focus). Nothing is ever
   hidden from a no-JS visitor. */
.inspo-panel { padding-top: clamp(1.5rem, 3vw, 2.5rem); }
.inspo-panel__dialog {
	position: relative; background: var(--paper);
	width: min(100% - 2 * var(--gutter), 780px); margin-inline: auto;
	border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
}
.inspo-panel__inner { padding: clamp(1.5rem, 3.5vw, 2.75rem); }
.inspo-panel__title { margin: 0 0 .9rem; font-size: clamp(1.4rem, 2.4vw, 1.9rem); }
.inspo-panel__body { max-width: none; }
.inspo-panel__body > *:last-child { margin-bottom: 0; }
.inspo-panel__body + .btn { margin-top: 1.5rem; }
/* A photo inside a panel's copy (Bar & Menu Pairings, 2026-09-16). The panel
   body is WYSIWYG, so this is written for any <figure>/<img> the client drops in. */
.inspo-panel__body figure { margin: 1.5rem 0; }
.inspo-panel__body img { display: block; width: 100%; height: auto; max-height: 22rem; object-fit: cover; border-radius: var(--radius, 12px); }
.inspo-panel__body h4 { margin: 1.5rem 0 .5rem; font-size: 1.15rem; }
.inspo-panel__close {
	position: absolute; top: .65rem; right: .65rem; z-index: 1;
	width: 44px; height: 44px; border: 0; border-radius: 999px; cursor: pointer;
	background: var(--mist); color: var(--ink); font-size: 1.6rem; line-height: 1;
	display: inline-flex; align-items: center; justify-content: center;
	transition: background .2s var(--ease), color .2s var(--ease);
}
.inspo-panel__close:hover { background: var(--red); color: #fff; }
/* The close button only means anything once the panel is a modal. */
html:not(.sfi-js) .inspo-panel__close { display: none; }

.sfi-js .inspo-panel { display: none; padding: 0; }
.sfi-js .inspo-panel.is-open {
	display: grid; place-items: center;
	position: fixed; inset: 0; z-index: 200;
	padding: clamp(1rem, 4vw, 3rem);
	background: rgba(20, 20, 22, .55);
	backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.sfi-js .inspo-panel.is-open .inspo-panel__dialog {
	width: min(100%, 780px); max-height: min(86vh, 900px); overflow: auto;
	box-shadow: var(--shadow-lg);
}
/* A flipbook inside the panel (Featured Recipes, 2026-09-18). A book spread is
   two portrait pages side by side, so the dialog widens to give it room and the
   frame holds a 3:2 spread — capped by height so the close button and the
   "open in a new tab" fallback below it stay on screen. */
.inspo-panel__embed { margin-top: 1.25rem; aspect-ratio: 3 / 2; max-height: 62vh; width: 100%; border-radius: var(--radius, 12px); overflow: hidden; background: var(--mist); }
.inspo-panel__embed iframe { display: block; width: 100%; height: 100%; border: 0; }
.inspo-panel__embed + .btn { margin-top: 1.25rem; }
.sfi-js .inspo-panel--embed.is-open .inspo-panel__dialog { width: min(100%, 1100px); max-height: 94vh; }
@media (max-width: 700px) { .inspo-panel__embed { aspect-ratio: 3 / 4; max-height: 64vh; } }
/* Body scroll lock while a panel is open. */
body.inspo-open { overflow: hidden; }

/* ---- "How to request…" steps ---- */
.request { padding: var(--section-y) 0; background: var(--paper); }
.request__grid { display: grid; gap: clamp(2rem, 4.5vw, 4rem); grid-template-columns: 1fr 1.05fr; align-items: start; }
.request__title { margin-bottom: 1.15rem; }
.request__body { font-size: 1.06rem; line-height: 1.7; }
.request__body p:last-child { margin-bottom: 1.5rem; }
.request__steps { list-style: none; counter-reset: sfi-step; margin: 0; padding: 0; display: grid; gap: clamp(.7rem, 1.4vw, 1rem); }
.request__step {
	counter-increment: sfi-step; position: relative;
	padding: 1.05rem 1.35rem 1.05rem 4rem;
	background: var(--linen); border: 1px solid rgba(28,28,28,.07);
	border-radius: var(--radius); line-height: 1.6;
}
.request__step::before {
	content: counter(sfi-step);
	position: absolute; left: 1.1rem; top: 1rem;
	width: 2.1rem; height: 2.1rem; border-radius: 999px;
	background: var(--red); color: #fff;
	font-family: var(--font-head); font-weight: 800; font-size: .95rem;
	display: grid; place-items: center;
}
.request__step p { margin: 0; }
.request__step p + p { margin-top: .5rem; }
@media (max-width: 860px) { .request__grid { grid-template-columns: 1fr; } }

/* ---- partner support: sales map + contact cards ----
   The three support blocks are NOT three equal things — the Regional Sales
   Manager map is the section's object and its task ("find your RSM"), the other
   two are short notes. So: one wide lead block with the map beside it, then a
   two-up card row underneath.

   The map is client-supplied. Until it arrives the frame holds its own height
   with a labelled placeholder, so the section reviews at full size and the
   hand-off is one image upload — no layout change. */
.support { padding: var(--section-y) 0; background: var(--mist); }
/* Guard against tan-on-tan if a page ever runs inspiration straight into this. */
.inspiration + .support { background: var(--paper); }
/* Centred heading → the three support blocks → the full-width map and the form
   under it. The blocks moved ABOVE the map on 2026-08-12 (client): they say who
   looks after a distributor, which reads better before the map that shows which
   of them covers your states. (2026-08-10 history: the sales-team copy that used
   to sit beside the map is the first of those three blocks.) */
.support .section-head { max-width: 68ch; margin-inline: auto; text-align: center; }
.support .section-head__intro { margin-inline: auto; }
.support__grid + .support__lead { margin-top: clamp(1.75rem, 4vw, 3rem); }
/* Title + how-to-use line over the map (client, 2026-08-12) — centred with the
   section head above it, since the map itself is full width. */
.support__map-head { text-align: center; margin-bottom: clamp(1rem, 2.5vw, 1.75rem); }
.support__map-title { margin: 0; font-size: clamp(1.35rem, 2.4vw, 1.85rem); letter-spacing: -.02em; }
.support__map-instruction {
	margin: .5rem auto 0; max-width: 52ch;
	color: color-mix(in srgb, var(--ink) 78%, transparent);
	font-size: clamp(1rem, 1.25vw, 1.1rem);
}
.support__map { margin: 0; }
.support__map-img {
	width: 100%; border-radius: var(--radius-lg);
	background: var(--paper); box-shadow: var(--shadow-sm);
}
.support__map-placeholder {
	display: grid; place-content: center; justify-items: center; gap: .75rem;
	aspect-ratio: 16 / 9; max-height: 420px; padding: 1.5rem; text-align: center;
	border: 2px dashed color-mix(in srgb, var(--charcoal) 45%, transparent);
	border-radius: var(--radius-lg); background: var(--linen); color: var(--charcoal);
}
.support__map-pin { width: 34px; height: 34px; color: var(--red); }
.support__map-placeholder-text {
	margin: 0; max-width: 24ch;
	font-family: var(--font-head); font-weight: 700; font-size: .82rem;
	letter-spacing: .12em; text-transform: uppercase; line-height: 1.5;
}
.support__map-note { margin: .9rem 0 0; font-size: .85rem; color: var(--charcoal); text-align: center; }
.support__grid { display: grid; gap: clamp(1rem, 2.2vw, 1.75rem); grid-template-columns: repeat(2, 1fr); }
.support__grid[data-count="1"] { grid-template-columns: 1fr; max-width: 640px; }
.support__grid[data-count="3"] { grid-template-columns: repeat(3, 1fr); }
.support-card {
	background: var(--paper); border: 1px solid rgba(28,28,28,.08);
	border-radius: var(--radius); box-shadow: var(--shadow-sm);
	padding: clamp(1.35rem, 2.6vw, 2rem);
	display: flex; flex-direction: column; align-items: flex-start;
}
.support-card__title { margin: 0 0 .6rem; font-size: clamp(1.15rem, 1.7vw, 1.35rem); }
.support-card__text { margin: 0; color: color-mix(in srgb, var(--ink) 78%, transparent); line-height: 1.65; }
.support-card__contact { list-style: none; margin: 0; padding: 1.15rem 0 0; display: grid; gap: .5rem; }
.support-card__contact-link {
	display: inline-flex; align-items: center; gap: .6rem;
	font-family: var(--font-head); font-weight: 700; font-size: .98rem;
	text-decoration: none; color: var(--ink); overflow-wrap: anywhere;
}
.support-card__contact-link .sfi-icon { flex: 0 0 auto; color: var(--red); }
.support-card__contact-link:hover { color: var(--red); }
.support-card__cta { margin-top: 0; padding-top: 1.15rem; }
/* WHATEVER ENDS THE CARD IS PUSHED TO THE CARD'S FOOT, so the three cards agree
   at the bottom instead of each stopping wherever its paragraph happened to run
   out. Same fix, same reason, as .news-card__cta and .mu-card__cta, which have
   had `margin-top: auto` since they were written — the two card types that grew
   later (support, benefit) simply never picked it up, and a row of three ends
   at three different heights with dead space beneath two of them.
   The gap is PADDING, not margin: `margin-top: auto` absorbs free space, so it
   collapses to nothing on the tallest card in the row — which is the one that
   needs the gap most. */
.support-card__contact:last-child,
.support-card__cta:last-child { margin-top: auto; }
@media (max-width: 900px) {
	.support__lead { grid-template-columns: 1fr; }
	.support__grid { grid-template-columns: 1fr !important; }
}

/* ---- RSM territory map ----
   Built from real state geometry rather than the client's exported slide, so it
   stays sharp at any width, reads on a phone, and every rep is real text.

   Selecting a territory HIGHLIGHTS — it never hides. Non-selected states dim,
   the matching rep card lifts, and the whole rep list stays on the page for
   search engines, assistants and anyone without JavaScript. */
.rsm { --rsm-blank: #DCD7CF; }
.rsm__stage {
	background: var(--paper); border-radius: var(--radius-lg);
	box-shadow: var(--shadow-sm); border: 1px solid rgba(28,28,28,.07);
	padding: clamp(.75rem, 2vw, 1.5rem);
}
.rsm__canvas { position: relative; }
.rsm__svg { width: 100%; height: auto; display: block; }
/* --terr-tint is set inline, per state, by render-rsm-map.php. It has to be a
   custom property rather than a fill=" attribute: a presentation attribute is
   the weakest thing in the cascade and this very rule used to beat it, which
   painted all 51 states the same grey and left the resting map with no
   territory colours at all. --rsm-blank is the fallback, so a state nobody
   covers still reads as unassigned. */
.rsm__state {
	fill: var(--terr-tint, var(--rsm-blank)); stroke: var(--paper); stroke-width: 1;
	stroke-linejoin: round;
	transition: opacity .25s var(--ease), filter .25s var(--ease);
}
.rsm__state[data-territory] { cursor: pointer; }
.rsm__label, .rsm__leader-label {
	font-family: var(--font-head); font-weight: 700; font-size: 11px;
	letter-spacing: .02em; text-anchor: middle; pointer-events: none;
	paint-order: stroke;
}
.rsm__leader-label { text-anchor: start; fill: var(--ink); font-size: 10.5px; }
.rsm__leader { stroke: color-mix(in srgb, var(--charcoal) 60%, transparent); stroke-width: .8; }
.rsm__chip { stroke: var(--paper); stroke-width: 1; }

/* Hover reads as "this whole territory", not "this one state". */
.rsm:not([data-active]) .rsm__state[data-territory]:hover { filter: brightness(1.08); }
.rsm[data-active] .rsm__state:not([data-territory]),
.rsm[data-active] .rsm__state[data-territory] { opacity: .28; }
.rsm[data-active] .rsm__state[data-territory].is-active {
	opacity: 1; fill: var(--terr, currentColor); stroke: var(--charcoal); stroke-width: .7;
	filter: drop-shadow(0 2px 6px rgba(28,28,28,.28));
}
/* The selected territory fills with the rep's OWN colour (client, 2026-08-28:
   "didn't the map light up in the RSM's colour?"). The resting map keeps the
   legible tint; only the chosen region goes full strength, so its label flips
   to whichever of ink/white reads on that colour. */
.rsm[data-active] .rsm__label.is-active { fill: var(--terr-ink, #1A1A1A); }
.rsm[data-active] .rsm__label:not(.is-active),
.rsm[data-active] .rsm__leaders :not(.is-active) { opacity: .3; }

/* ---- pins ----
   Client (2026-08-10) asked for the names on the map as tooltips rather than a
   list underneath, with a placeholder person image until headshots arrive.

   The card is hidden, not absent: it is real markup in the HTML, so the rep's
   name, contact details and full state list are still there for search engines
   and assistants. It opens on hover AND on focus-within, which means it also
   works with no JavaScript at all — including on touch, where tapping the
   button focuses it. */
.rsm__pin {
	position: absolute; left: var(--pin-x); top: var(--pin-y);
	transform: translate(-50%, -50%); z-index: 2;
}
/* An open card lifts its whole pin above the neighbours, so the card box paints
   over any avatar it overlaps rather than under it (client, 2026-08-28: clicking
   Kevin put Tina's face inside his box — her pin sits where his card opens). */
.rsm__pin:hover, .rsm__pin:focus-within, .rsm__pin.is-active { z-index: 10; }
.rsm__pin-btn {
	display: block; padding: 0; cursor: pointer; border: 0; background: none;
	border-radius: 999px; line-height: 0;
	transition: transform .25s var(--ease);
}
.rsm__pin-avatar {
	position: relative; display: grid; place-items: center;
	width: clamp(34px, 4.2vw, 54px); aspect-ratio: 1; border-radius: 999px;
	background: var(--paper); border: 3px solid var(--pin-color, var(--red));
	box-shadow: 0 6px 16px -6px rgba(28,28,28,.6);
	color: color-mix(in srgb, var(--charcoal) 85%, transparent);
}
.rsm__pin-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 999px; }
/* No headshot on file: initials on the territory's own colour, so the pin still
   reads as that person's pin without putting a stranger's face under their name. */
.rsm__pin-avatar--initials { background: var(--pin-color, var(--red)); }
.rsm__pin-initials {
	font-family: var(--font-head); font-weight: 800; letter-spacing: .01em;
	font-size: clamp(.72rem, 1.5vw, .95rem); line-height: 1;
	color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.rsm__pin-avatar > .sfi-icon, .rsm__pin-avatar > img { border-radius: 999px; }

.rsm__pin-btn:hover, .rsm__pin-btn:focus-visible { transform: scale(1.08); }

.rsm__card {
	position: absolute; left: 50%; bottom: calc(100% + 14px);
	transform: translateX(-50%) translateY(6px);
	width: max-content; max-width: 270px; z-index: 3;
	padding: .85rem 1rem; text-align: left;
	background: var(--paper); border-radius: var(--radius);
	border: 1px solid rgba(28,28,28,.1); box-shadow: var(--shadow-md);
	opacity: 0; visibility: hidden; pointer-events: none;
	transition: opacity .2s var(--ease), transform .2s var(--ease), visibility .2s;
}
.rsm__pin:hover .rsm__card,
.rsm__pin:focus-within .rsm__card,
.rsm__pin.is-active .rsm__card {
	opacity: 1; visibility: visible; pointer-events: auto; transform: translateX(-50%) translateY(0);
}
.rsm__card::after {
	content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
	border: 8px solid transparent; border-top-color: var(--paper);
}
.rsm__card-name { margin: 0; font-family: var(--font-head); font-weight: 800; font-size: 1rem; }
.rsm__card-title { margin: .1rem 0 0; font-size: .78rem; color: var(--charcoal); }
.rsm__card-contact { list-style: none; margin: .6rem 0 0; padding: 0; display: grid; gap: .3rem; }
.rsm__card-contact a {
	display: inline-flex; align-items: center; gap: .45rem;
	font-family: var(--font-head); font-weight: 700; font-size: .82rem;
	color: var(--ink); text-decoration: none; overflow-wrap: anywhere;
}
.rsm__card-contact a:hover { color: var(--red); }
.rsm__card-contact .sfi-icon { width: 15px; height: 15px; flex: 0 0 auto; color: var(--red); }
.rsm__card-states { margin: .55rem 0 0; font-size: .76rem; line-height: 1.5; color: color-mix(in srgb, var(--ink) 70%, transparent); }

/* Below ~860px the map is narrower than ~800px, so an 11px label in a 1000-unit
   viewBox paints at 8px or less — past legible however it is scaled. The labels
   come off and the map becomes a coloured overview. The pins stay: they are the
   interface, and their cards drop to the foot of the map where there is room. */
@media (max-width: 860px) {
	.rsm__stage { padding: .5rem; }
	.rsm__labels, .rsm__leaders { display: none; }
	.rsm__card {
		position: fixed; left: 50%; bottom: 1rem; top: auto;
		transform: translateX(-50%) translateY(6px);
		width: min(92vw, 340px); max-width: none; z-index: 60;
	}
	.rsm__pin:hover .rsm__card,
	.rsm__pin:focus-within .rsm__card,
	.rsm__pin.is-active .rsm__card { transform: translateX(-50%) translateY(0); }
	.rsm__card::after { display: none; }
}

/* ---- "Contact your Regional Sales Manager" ----
   Sits directly under the map. Collapsed behind its button until asked for, so
   a long page doesn't end up with a full form nailed to the middle of it — the
   client's words were "if they want to reach out, it opens a form". A
   <details>, which means it opens with JS off and the state is announced for
   free, exactly like the FAQs. */
.rsm-contact { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
/* Two disclosure cards, stacked (client, 2026-08-20: a second button beneath the
   routed one, for national non-commercial enquiries). Separate cards rather than
   two buttons in one, because each opens its own form and the open panel has to
   sit directly under the button that opened it. */
.rsm-contact__box + .rsm-contact__box { margin-top: .85rem; }
/* The national desk is the secondary route — same card, quieter button, so the
   distributor's own RSM stays the obvious first choice. */
.rsm-contact__box--natl .rsm-contact__summary-btn {
	background: transparent; color: var(--red);
	border: 1.5px solid color-mix(in srgb, var(--red) 45%, transparent); box-shadow: none;
}
.rsm-contact__box--natl .rsm-contact__summary:hover .rsm-contact__summary-btn {
	background: var(--red); border-color: var(--red); color: #fff;
}
.rsm-contact__box {
	background: var(--paper); border: 1px solid rgba(28,28,28,.1);
	border-radius: var(--radius-lg); overflow: hidden;
}
.rsm-contact__box[open] { box-shadow: var(--shadow-sm); }
.rsm-contact__summary {
	display: flex; flex-wrap: wrap; align-items: center; gap: .85rem 1.25rem;
	padding: clamp(1rem, 2.2vw, 1.5rem); cursor: pointer; list-style: none;
}
.rsm-contact__summary::-webkit-details-marker { display: none; }
.rsm-contact__summary::marker { content: ""; }
/* The button is a <span>: a real <button> inside <summary> swallows the click
   that is supposed to toggle the details. */
.rsm-contact__summary-btn { pointer-events: none; }
.rsm-contact__summary-hint { font-size: .86rem; color: var(--charcoal); }
.rsm-contact__box[open] .rsm-contact__summary-hint { display: none; }
.rsm-contact__summary:focus-visible { outline: 3px solid var(--red); outline-offset: -3px; }
/* Full width inside the card (client, 2026-08-11) — the panel used to cap at
   62rem, which left the fields stopping short of the card's own edge and read
   as a mistake. The intro keeps a readable measure of its own below. */
.rsm-contact__panel {
	padding: 0 clamp(1rem, 2.2vw, 1.5rem) clamp(1.25rem, 2.5vw, 1.75rem);
}
.rsm-contact__intro { margin: 0 0 1.25rem; max-width: 62ch; color: color-mix(in srgb, var(--ink) 82%, transparent); }
.rsm-contact__notice {
	margin: 0 0 1.25rem; padding: .9rem 1.1rem; border-radius: var(--radius);
	font-family: var(--font-head); font-weight: 600; font-size: .95rem; line-height: 1.5;
	border-left: 4px solid;
}
.rsm-contact__notice--success { background: color-mix(in srgb, var(--green) 12%, #fff); border-color: var(--green); }
.rsm-contact__notice--error { background: color-mix(in srgb, var(--red) 9%, #fff); border-color: var(--red); }

/* Two columns by default; three once the card is wide enough, so a full-width
   form doesn't turn "Phone" into a 580px slot. Message always spans the row. */
.rsm-form__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem 1.25rem; }
@media (min-width: 1080px) {
	.rsm-form__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.rsm-form__field { margin: 0; display: flex; flex-direction: column; gap: .35rem; }
.rsm-form__field--wide { grid-column: 1 / -1; }
.rsm-form__field label {
	font-family: var(--font-head); font-weight: 700; font-size: .82rem;
	letter-spacing: .02em; color: var(--ink);
}
.rsm-form__req { color: var(--red); }
.rsm-form__opt { font-weight: 500; color: var(--charcoal); }
.rsm-form__field input, .rsm-form__field select, .rsm-form__field textarea {
	font-family: var(--font-body); font-size: 1rem; color: var(--ink);
	padding: .7rem .85rem; border-radius: 10px; background: var(--linen);
	border: 1.5px solid rgba(28,28,28,.16); width: 100%;
	transition: border-color .2s var(--ease), background .2s var(--ease);
}
.rsm-form__field textarea { resize: vertical; min-height: 7.5rem; }
.rsm-form__field :is(input, select, textarea):focus-visible {
	outline: none; background: var(--paper);
	border-color: var(--red); box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 22%, transparent);
}
/* Off-screen rather than display:none — enough bots skip hidden inputs that a
   honeypot they can't see but can find is worth the two lines. */
.rsm-form__hp {
	position: absolute !important; width: 1px; height: 1px;
	overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.rsm-form__route {
	margin: 1rem 0 0; min-height: 1.5rem;
	font-family: var(--font-head); font-weight: 700; font-size: .9rem; color: var(--ink);
}
.rsm-form__route:empty { margin: 0; min-height: 0; }
.rsm-form__actions { display: flex; flex-wrap: wrap; align-items: center; gap: .85rem 1.25rem; margin-top: 1.25rem; }
.rsm-form__privacy { font-size: .82rem; color: var(--charcoal); }
.rsm-form .cf-turnstile { margin-top: 1.25rem; }
.rsm-form__help { font-size: .8rem; color: var(--charcoal); }
.rsm-form__field input[type="file"] { padding: .55rem .6rem; font-size: .9rem; cursor: pointer; }

/* ---- Pop-up contact forms (2026-09-16) ----
   Every published e-mail address became a form that sends to it, opened from
   any ?rsmf=<mode> link as a modal <dialog> (see sfi_render_form_dialogs()).
   Same card language as the recipe panels. Without JS the dialog is rendered
   OPEN in the page flow at the foot of the page, so it is laid out as a plain
   card there rather than floating over the footer. */
.form-dialog {
	width: min(100% - 2 * var(--gutter), 760px); max-width: none; max-height: min(90vh, 960px);
	padding: 0; border: 0; border-radius: var(--radius-lg);
	background: var(--paper); color: var(--ink); box-shadow: var(--shadow-lg); overflow: auto;
}
/* [open] is load-bearing: a CLOSED dialog is :not(:modal) too, and author CSS
   beats the UA's dialog:not([open]) { display: none } — without it every desk's
   form sat as a card under the footer on every page that links one (2026-09-18). */
.form-dialog[open]:not(:modal) { position: static; display: block; margin: clamp(2rem, 5vw, 4rem) auto; box-shadow: var(--shadow-md); }
.form-dialog::backdrop { background: rgba(20, 20, 22, .55); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
.form-dialog__inner { position: relative; padding: clamp(1.5rem, 3.5vw, 2.5rem); }
.form-dialog__title { margin: 0 3rem 1rem 0; font-size: clamp(1.4rem, 2.4vw, 1.9rem); }
.form-dialog__closer { margin: 0; }
.form-dialog__close {
	position: absolute; top: .65rem; right: .65rem; z-index: 1;
	width: 44px; height: 44px; border: 0; border-radius: 999px; cursor: pointer;
	background: var(--mist); color: var(--ink); font-size: 1.6rem; line-height: 1;
	display: inline-flex; align-items: center; justify-content: center;
	transition: background .2s var(--ease), color .2s var(--ease);
}
.form-dialog__close:hover { background: var(--red); color: #fff; }
/* A 760px card fits two columns, not the three a wide inline panel gets. */
.form-dialog .rsm-form__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 700px) {
	.form-dialog .rsm-form__grid { grid-template-columns: 1fr; }
}
html:has(.form-dialog:modal) { overflow: hidden; }
@media (max-width: 700px) {
	.rsm-form__grid { grid-template-columns: 1fr; }
}

/* ---- FAQs ----
   Native <details>/<summary>, so questions open with JavaScript off and the
   expanded state is announced without any ARIA of our own. Grouped because the
   client's copy arrives in named sets; the set heading sticks alongside its
   questions on wide screens.

   Linen rather than mist or white: this section can follow either, and a third
   near-neutral keeps it separate from both. */
.faqs { padding: var(--section-y) 0; background: var(--linen); }
.faqs__group { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 2fr); gap: clamp(1.25rem, 3vw, 3rem); }
.faqs__group + .faqs__group { margin-top: clamp(2rem, 4vw, 3.25rem); }
.faqs__group-title {
	margin: 0; align-self: start; position: sticky; top: 6.5rem;
	font-size: clamp(1.15rem, 1.8vw, 1.45rem);
}
.faqs__list { display: grid; gap: .75rem; }
.faq {
	background: var(--paper); border: 1px solid rgba(28,28,28,.09);
	border-radius: var(--radius); overflow: hidden;
	transition: box-shadow .3s var(--ease), border-color .3s var(--ease);
}
.faq[open], .faq:hover { box-shadow: var(--shadow-sm); border-color: rgba(28,28,28,.16); }
.faq__q {
	display: flex; align-items: center; justify-content: space-between; gap: 1rem;
	padding: 1.05rem clamp(1rem, 2vw, 1.5rem); cursor: pointer;
	font-family: var(--font-head); font-weight: 700; font-size: 1rem;
	line-height: 1.4; letter-spacing: -.01em; list-style: none;
}
/* Safari/Chrome still paint their own marker without these. */
.faq__q::-webkit-details-marker { display: none; }
.faq__q::marker { content: ""; }
.faq__q:hover { color: var(--red); }
.faq[open] .faq__q { color: var(--red); }
.faq__icon { flex: 0 0 auto; transition: transform .3s var(--ease); }
.faq[open] .faq__icon { transform: rotate(180deg); }
/* No measure cap: the answer fills the card its question spans. A 68ch cap
   inside a wider card left a band of empty card down the right of every open
   answer — the "wide right margin" (2026-09-18). The card's column is already
   the readable width. */
.faq__a {
	padding: 0 clamp(1rem, 2vw, 1.5rem) 1.25rem;
	max-width: none; line-height: 1.7;
}
.faq__a > *:last-child { margin-bottom: 0; }
/* Some answers list facilities (warehouses, addresses) — the global reset strips
   list styling, so give it back here rather than sitewide. */
.faq__a ul { margin: .35rem 0 1rem; padding-left: 1.1rem; list-style: disc; }
.faq__a li { margin-bottom: .35rem; }
.faq__a li::marker { color: color-mix(in srgb, var(--red) 60%, transparent); }
.faq__a p + p { margin-top: .85rem; }
@media (max-width: 860px) {
	.faqs__group { grid-template-columns: 1fr; }
	.faqs__group-title { position: static; }
}

/* ---- closing CTA band ---- */
.cta-band { background: var(--cream); padding: clamp(2.25rem, 5vw, 3.75rem) 0; }
.cta-band__inner { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.25rem 2.5rem; }
.cta-band__title { margin: 0; max-width: 26ch; font-size: clamp(1.35rem, 2.5vw, 2rem); }

/* =====================================================================
   PRODUCT CATEGORY PAGES
   The layout from the client's marketing-manager concepts: photo banner with
   the category name over it → "About our…" copy beside photography → the item
   grid with pack specs and spec-sheet links → a full-bleed strip of the product
   in use → "you might also like". One template serves every category.
   ===================================================================== */
.cat-banner {
	position: relative; isolation: isolate;
	min-height: clamp(190px, 26vw, 320px);
	display: flex; align-items: center;
	background: var(--sand); overflow: hidden;
}
.cat-banner__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: -2; }
/* The category name sits on photography that we don't control the tone of, so
   the scrim is not optional — it is what guarantees the contrast ratio. */
.cat-banner::after {
	content: ""; position: absolute; inset: 0; z-index: -1;
	background: linear-gradient(180deg, rgba(20,20,20,.15) 0%, rgba(20,20,20,.55) 100%);
}
.cat-banner--plain { background: var(--cream); }
.cat-banner--plain::after { display: none; }
/* DESIGN NOTE: the banner name is CENTRED over the photo, as in the client's
   concepts (Black (Ripe) Olives, Specialty Olives, Quinoa are all centred on
   their banner). Same rule as the catalogue header above: a band with nothing
   beside it centres; a band paired with an image or a map keeps its copy on the
   content edge.
   `padding-block`, NOT the padding shorthand, and no `width` — both would beat
   `.wrap` on specificity order and cost the gutter, which is what put this text
   hard against the left edge of the screen (fixed 2026-08-11). Anything that
   also carries `.wrap` must leave width and inline padding alone. */
.cat-banner__inner { position: relative; padding-block: clamp(2rem, 5vw, 3.5rem); text-align: center; }
.cat-banner .cat-crumbs { justify-content: center; }
.cat-banner__title {
	margin: .35rem 0 0; color: #fff;
	font-size: clamp(1.9rem, 4.4vw, 3rem); letter-spacing: -.03em;
	text-shadow: 0 2px 20px rgba(0,0,0,.35);
}
.cat-banner--plain .cat-banner__title { color: var(--ink); text-shadow: none; }
/* Breadcrumbs default to ink-on-light with the brand red on hover, like every
   other link on the site. They only go white where they sit ON a photo — the
   white was previously the default, which made the hover state invisible
   wherever the crumbs run on a white page (client spotted it on a product
   page, 2026-08-11). Red on the photo scrim is not readable enough, so that
   one case keeps white. */
.cat-crumbs { display: flex; flex-wrap: wrap; gap: .5rem; font-family: var(--font-head); font-size: .78rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
.cat-crumbs a { color: var(--charcoal); text-decoration: none; }
.cat-crumbs a:hover { color: var(--red); text-decoration: underline; }
.cat-crumbs span { color: var(--beige); }
.cat-banner:not(.cat-banner--plain) .cat-crumbs a { color: rgba(255,255,255,.9); }
.cat-banner:not(.cat-banner--plain) .cat-crumbs a:hover { color: #fff; }
.cat-banner:not(.cat-banner--plain) .cat-crumbs span { color: rgba(255,255,255,.55); }

.cat-about { padding: var(--section-y) 0; background: var(--paper); }
.cat-about__grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, .85fr); gap: clamp(1.5rem, 4vw, 3.5rem); align-items: start; }
/* No photos on this category yet — so the copy has no partner column and the
   rule applies again: constrained + alone → centre it. Left in a 1fr column
   next to an empty one it reads as a layout that failed to load. */
.cat-about--solo .cat-about__grid { grid-template-columns: 1fr; max-width: 56rem; margin-inline: auto; text-align: center; }
.cat-about--solo .cat-about__body { margin-inline: auto; }
.cat-about__copy h2 { margin-top: 0; font-size: clamp(1.5rem, 2.6vw, 2.1rem); }
.cat-about__body { max-width: 60ch; }
.cat-about__body p { margin-bottom: 1rem; }
.cat-about__media { display: grid; gap: 1rem; }
.cat-about__media[data-count="3"] { grid-template-columns: 1fr 1fr; }
.cat-about__media[data-count="3"] .cat-about__img:first-child { grid-column: 1 / -1; }
.cat-about__img { width: 100%; border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); aspect-ratio: 3 / 2; object-fit: cover; }

.cat-items, .cat-children { padding: var(--section-y) 0; background: var(--mist); }
.cat-items__heading { margin: 0 0 clamp(1.25rem, 3vw, 2rem); font-size: clamp(1.5rem, 2.8vw, 2.15rem); }
.cat-items__grid, .cat-children__grid {
	display: grid; gap: clamp(1rem, 2.2vw, 1.75rem);
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
	list-style: none; margin: 0; padding: 0;
}

/* One SKU. The pack shot sits on white and is CONTAINED, never cropped — some
   of these are transparent cut-outs and some are shot on white, and a cover
   crop would slice the top off a gallon jar. */
.spec-card {
	display: flex; flex-direction: column;
	background: var(--paper); border: 1px solid rgba(28,28,28,.09);
	border-radius: var(--radius); overflow: hidden;
	transition: box-shadow .3s var(--ease), transform .3s var(--ease);
}
.spec-card:hover { box-shadow: var(--shadow-md); transform: translateY(-3px); }
/* DESIGN NOTE (client, 2026-08-11): every pack shot gets the SAME fixed-height
   stage and is contained inside it, so a 4/1 gallon jar and a squat 14oz can
   line up across a row instead of each card sizing itself to its own photo.
   Two things had to change to get there:
     · an `aspect-ratio` box does NOT hold inside a flex column — the flex item's
       default `min-height: auto` lets a tall portrait image push straight past
       it, which is what made the jars twice the height of the cans;
     · `max-width/max-height` rather than `width/height: 100%`, so a small source
       image is never upscaled into a blurry mess to fill the stage.
   The stage is deliberately shallower than the tallest jar: contained shots read
   as a set, and the dead space above a short can is the price of that. */
.spec-card__media {
	display: grid; place-items: center; overflow: hidden;
	height: clamp(170px, 17vw, 220px); flex: 0 0 auto; min-height: 0;
	padding: .9rem; background: var(--paper); border-bottom: 1px solid rgba(28,28,28,.07);
}
/* `height: 100%` + `object-fit: contain`, NOT `max-height: 100%`. A grid or
   flex item's automatic minimum size is its intrinsic height, so max-height
   cannot shrink a 1500px-tall jar — it just overflowed the stage and ran over
   the card text. Resolving against the stage's now-definite height is what
   actually clamps it; `min-height: 0` disables that automatic minimum, and the
   overflow rule is the backstop. */
.spec-card__img { width: 100%; height: 100%; min-width: 0; min-height: 0; object-fit: contain; }
.spec-card__placeholder { display: block; width: 100%; height: 100%; background: var(--mist); border-radius: var(--radius); }
.spec-card__body { display: flex; flex-direction: column; flex: 1; padding: 1rem clamp(1rem, 1.6vw, 1.25rem) 1.15rem; }
.spec-card__title { margin: 0; font-size: .98rem; line-height: 1.35; letter-spacing: -.01em; }
.spec-card__title a { color: var(--ink); text-decoration: none; }
.spec-card__title a:hover { color: var(--red); }
.spec-card__id {
	margin: .4rem 0 .7rem; display: flex; flex-wrap: wrap; gap: .4rem;
	font-family: var(--font-head); font-weight: 700; font-size: .82rem; color: var(--red);
}
.spec-card__id span[aria-hidden] { color: var(--beige); }
.spec-card__pack { color: var(--charcoal); }
.spec-card__specs { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: .28rem; font-size: .82rem; color: color-mix(in srgb, var(--ink) 74%, transparent); }
.spec-card__specs li { padding-left: .85rem; position: relative; }
.spec-card__specs li::before { content: ""; position: absolute; left: 0; top: .55em; width: 4px; height: 4px; border-radius: 50%; background: var(--beige); }
/* Brand is identity, not a spec — same bullet, but it reads at full ink so the
   eye lands on it first in a mixed category (Mandarins and Pears carry both
   Ambrosia and Horn of Plenty; 14 of the 48 sub-categories are mixed). */
.spec-card__brand { color: var(--ink); font-weight: 600; }
.spec-card__brand::before { background: var(--red); }
.spec-card__spec {
	margin-top: auto; display: inline-flex; align-items: center; gap: .45rem;
	font-family: var(--font-head); font-weight: 700; font-size: .78rem;
	letter-spacing: .07em; text-transform: uppercase; color: var(--red); text-decoration: none;
}
.spec-card__spec .sfi-icon { width: 16px; height: 16px; }
.spec-card__spec:hover { color: var(--red-dark); text-decoration: underline; }
.spec-card__spec--soon { color: var(--charcoal); text-transform: none; letter-spacing: .02em; font-weight: 500; }

.cat-child__link { display: grid; gap: .6rem; text-decoration: none; color: var(--ink); }
/* position:relative is LOAD-BEARING. The placeholder inside is .cat-card__placeholder,
   which is position:absolute;inset:0 — written for .cat-card__media, which is
   positioned. Without it the placeholder resolved against .cat-child instead
   (`.reveal` sets `will-change: transform`, and that alone makes an element a
   containing block for absolutely positioned descendants, permanently — even
   once the transform is `none`), so the tile art painted over the label and the
   item count on every sub-category without a photo. Found 2026-08-24 on the
   Fruits page: eight tiles, no captions. */
.cat-child__media { position: relative; display: block; aspect-ratio: 1; border-radius: var(--radius-lg); overflow: hidden; background: var(--paper); }
.cat-child__img { width: 100%; height: 100%; object-fit: cover; transition: transform .5s var(--ease); }
.cat-child__link:hover .cat-child__img { transform: scale(1.05); }
.cat-child__label { font-family: var(--font-head); font-weight: 800; font-size: 1.05rem; }
.cat-child__link:hover .cat-child__label { color: var(--red); }
.cat-child__count { font-size: .8rem; color: var(--charcoal); }

/* ---- parent category browse: copy + tiles as ONE section ----
   Client, 2026-08-20: the standalone copy band below the olives hero "looked by
   itself" — make it read like the home page's "Our Products / Browse the
   Product Catalogue" block. So it reuses .categories wholesale and only
   re-states what differs: the head carries a paragraph of real copy rather than
   home's single line, so it needs a reading measure and a little more air under
   it, and the grid is auto-fill (three sub-categories, not ten categories) so a
   short row does not stretch into three enormous tiles. */
.cat-browse .categories__head { align-items: flex-end; }
.cat-browse__intro {
	max-width: 68ch; margin: 1rem 0 0;
	font-size: clamp(1rem, 1.2vw, 1.08rem); line-height: 1.7;
	color: color-mix(in srgb, var(--ink) 80%, transparent);
}
.cat-browse__intro p { margin: 0 0 .9rem; }
.cat-browse__intro p:last-child { margin-bottom: 0; }
.cat-browse__grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
/* ---- the lonely last card, fixed by counting (2026-09-09) ----
   At a 1200px wrap this grid is FOUR across, so a total of 5, 9 or 13 cards
   leaves exactly one card alone on the last row with three empty columns beside
   it — the "pushed to the left and blank on the right" the client raised on
   09-04 about a different grid. The client did this arithmetic themselves back
   in September and it was answered by choosing how many lifestyle photos each
   category carried: a photo card padded the row out.

   Those photos have now moved to the lifestyle bar at the FOOT of the page
   (client, 2026-09-09), so the padding went with them and two categories are
   left on a bad number: Condiments with 5 sub-categories and Vegetables with 9.
   Five across fixes both — 5 becomes one full row, 9 becomes 5 + 4 — and it is
   the width the ten category tiles on /products/ already use, so it is not a new
   look. Keyed on the count rather than the category, because the next
   sub-category anyone adds moves both of them. */
.cat-browse__grid[data-count="5"],
.cat-browse__grid[data-count="9"] { grid-template-columns: repeat(5, 1fr); }
@media (max-width: 1040px) {
	.cat-browse__grid[data-count="5"],
	.cat-browse__grid[data-count="9"] { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 640px) {
	.cat-browse__grid[data-count="5"],
	.cat-browse__grid[data-count="9"] { grid-template-columns: repeat(2, 1fr); }
}
/* A lifestyle photo in the tile row is a picture, not a destination: same square
   media box so the row stays level, no label weight, no hover lift. */
.cat-child__figure { margin: 0; display: grid; gap: .6rem; }
.cat-child--photo .cat-child__media { box-shadow: var(--shadow-sm); }
/* The caption is no longer printed (client, 2026-09-02) — see the note in
   sfi_render_category_browse(). The rule stays so that putting it back is one
   line in the template and nothing else. */
.cat-child__caption { font-size: .82rem; line-height: 1.5; color: var(--charcoal); }

/* Edge to edge, exactly as drawn: the dishes are the payoff, not a gallery. */
.cat-uses__strip { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; list-style: none; margin: 0; padding: 0; }
.cat-uses__item { margin: 0; }
.cat-uses__img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
@media (max-width: 700px) {
	.cat-uses__strip { grid-auto-columns: 62%; overflow-x: auto; scroll-snap-type: x mandatory; }
	.cat-uses__item { scroll-snap-align: start; }
}

/* ---- ONE OR TWO PHOTOGRAPHS IS A BAND, NOT A STRIP (2026-09-09) ----
   The rules above were written for Olives, which has three. The client has now
   asked for this bar at the foot of all ten category pages, and six of the other
   nine have exactly one lifestyle photograph to put in it.

   `aspect-ratio: 1` on a full-bleed 1fr column means one photo is a SQUARE THE
   WIDTH OF THE WINDOW — 1440px tall on a laptop, several screens on a phone.
   Two is barely better. So below three the square is dropped for a fixed band
   height, which is the treatment a single wide photograph wants anyway: the
   `clamp` lands within ~30px of the height a three-up square resolves to at the
   same viewport, so the ten pages end at close to the same weight whatever their
   photo count.

   Three or more is untouched. That case is the one the client has approved and
   the one this bar is being copied FROM. */
.cat-uses__strip[data-count="1"] .cat-uses__img,
.cat-uses__strip[data-count="2"] .cat-uses__img {
	aspect-ratio: auto;
	height: clamp(220px, 27vw, 420px);
}
@media (max-width: 700px) {
	/* No scroll-snap carousel for one or two: they fit. A 62% column would
	   otherwise hide 38% of a single photograph behind a swipe nobody is
	   prompted to make. */
	.cat-uses__strip[data-count="1"],
	.cat-uses__strip[data-count="2"] { grid-auto-columns: 1fr; overflow-x: visible; scroll-snap-type: none; }
	.cat-uses__strip[data-count="1"] .cat-uses__img,
	.cat-uses__strip[data-count="2"] .cat-uses__img { height: clamp(180px, 46vw, 260px); }
}

/* DESIGN NOTE (client, 2026-08-11): the footer curve is filled with the colour
   of the section ABOVE it — that is what makes it read as a valley cut out of
   that section rather than a white shape floating on top of it. The default
   fill is paper, which is only right when the page ends on white; these pages
   end on linen or mist depending on which sections rendered. Rather than
   hard-code a fill per template, match on whichever section actually ends the
   page, so a category with no related terms or a product with no siblings still
   lines up. Add a rule here whenever a new section can end a page.
   The existing per-template cases are `.front` (mist) and `.audience` (cream). */
/* ---- section rhythm on a category page ----
   The warm tints are deliberately close together — cream #F7F1E8 and mist
   #F5F2EE differ by SIX of 255, linen sits between them — so any two of them
   touching reads as a smudge rather than as two sections, and the curve
   divider between them disappears. White is the only real step in the palette.
   Hence the rule the approved pages already follow: NEVER let two tints touch,
   put white between them.

   Positional rather than per-component, because the same sections appear in
   different slots — .cat-items is the first band on a leaf category and the
   second on a parent. First band under the cream hero is always white, the
   second tinted, the third white again. */
.category-page .hero-anchor + section { background: var(--paper); }
.category-page .hero-anchor + section + section { background: var(--mist); }
.category-page .hero-anchor + section + section + section { background: var(--paper); }
/* Extended 2026-08-19: the category's copy moved out of the hero into a section
   of its own, so a parent page can now run to five bands — copy, tiles, items,
   also-bought, photography. Alternating white/tint by POSITION is what keeps
   two tints from ever touching whatever shape the page takes, and a category
   with no copy simply starts the chain one step earlier. */
.category-page .hero-anchor + section + section + section + section { background: var(--linen); }
.category-page .hero-anchor + section + section + section + section + section { background: var(--paper); }
/* First band is white, so the hero's curve keeps the paper default. */

/* A page that ENDS on the full-bleed dish photos gets NO footer curve. The
   valley is filled with whatever sits above it, and above it is photography —
   there is no flat colour to match, so any fill reads as a stray white shape
   floating over the pictures. A full-bleed image meeting the dark footer wants
   a hard edge, not a divider. */
.category-page:has(> .cat-uses:last-child) + .site-footer .sfi-curve--footer { display: none; }

/* The footer valley is filled with the last band, so it follows the same
   positional rhythm rather than naming components that move between slots. */
.category-page:has(> .hero-anchor + section:last-child) + .site-footer,
.category-page:has(> .hero-anchor + section + section + section:last-child) + .site-footer,
.category-page:has(> .hero-anchor + section + section + section + section + section:last-child) + .site-footer { --footer-curve-fill: var(--paper); }
.category-page:has(> .hero-anchor + section + section:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--mist); }
.category-page:has(> .hero-anchor + section + section + section + section:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--linen); }
.product-single:has(> .cat-items:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--mist); }

.cat-empty { padding: var(--section-y) 0; background: var(--mist); }
/* Same rule as the banner above it — constrained and alone, so centred. */
.cat-empty__inner { max-width: 46rem; margin-inline: auto; text-align: center; }
.cat-empty h2 { margin-top: 0; font-size: clamp(1.4rem, 2.6vw, 2rem); }
.cat-empty p { color: color-mix(in srgb, var(--ink) 80%, transparent); }
.cat-empty__actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 1rem 1.75rem; margin-top: 1.5rem; }

/* "Customers Also Bought" (client, 2026-08-19 — was "You might also like").
   It points at single PRODUCTS now, and the client asked for the images to be
   "a bit bigger". Bigger meant a change of shape as well as size: a 140px
   roundel is a category token, and cropping a gallon jar into a circle is not
   a product photograph. So these are square cards at roughly the item grid's
   own tile size, with the pack shot CONTAINED on white — the same treatment,
   and the same reasoning, as .spec-card__media. */
.cat-related { padding: var(--section-y) 0; background: var(--linen); }
.cat-related__heading { margin: 0 0 clamp(1.25rem, 3vw, 2rem); text-align: center; font-size: clamp(1.1rem, 2vw, 1.4rem); letter-spacing: .04em; text-transform: uppercase; }
.cat-related__grid { --related-gap: clamp(1.25rem, 3vw, 2.25rem); display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--related-gap); list-style: none; margin: 0; padding: 0; }
/* A SHORT ROW IS A SHORT ROW, NOT A SPARSE ONE (client, 2026-09-21, on Tuna:
   "Customers Also Bought looks like it has a middle item missing?").

   `auto-fit` collapses the tracks it cannot fill and hands the space to the
   survivors, so two cards in a 1200px wrap become two cards pinned to opposite
   edges with ~450px of nothing between them. Nothing is missing; the row is
   built for four and only two of Seafood's sub-categories are not Tuna.
   Fourteen of the 48 sub-category pages are in this state — twelve at two
   cards, Oil and Vinegar at one.

   CAPPING THE GRID'S WIDTH rather than its column count, because a cap is
   inert below the width it names: `auto-fit` inside still counts tracks the
   way it always did, so the narrow-screen behaviour of every one of these rows
   is byte-identical to before, and a four-card row is untouched at any width.
   The widths are what the same number of cards occupies in a four-across row,
   so a two-card row and the first two cards of a four-card row are the same
   size. */
.cat-related__grid[data-count="1"] { max-width: 285px; margin-inline: auto; }
.cat-related__grid[data-count="2"] { max-width: calc(285px * 2 + var(--related-gap)); margin-inline: auto; }
.cat-related__grid[data-count="3"] { max-width: calc(285px * 3 + var(--related-gap) * 2); margin-inline: auto; }
.cat-related__item a { display: grid; justify-items: center; gap: .7rem; text-decoration: none; color: var(--ink); text-align: center; }
.cat-related__media {
	display: grid; place-items: center; width: 100%; max-width: 260px; aspect-ratio: 1;
	padding: 1rem; border-radius: var(--radius-lg); overflow: hidden;
	background: var(--paper); box-shadow: var(--shadow-sm);
	transition: box-shadow .3s var(--ease), transform .3s var(--ease);
}
.cat-related__item a:hover .cat-related__media { box-shadow: var(--shadow-md); transform: translateY(-3px); }
/* contain, not cover: these are cut-outs and shots-on-white of wildly different
   proportions, and a cover crop slices the lid off the tall ones. */
.cat-related__media img { width: 100%; height: 100%; min-width: 0; min-height: 0; object-fit: contain; }
.cat-related__media .spec-card__placeholder { border-radius: var(--radius); }

/* ---------- photography placeholder ----------
   Sits on TOP of each component's own placeholder box (it keeps that class, so
   the box keeps its size, radius and — for .cat-card__placeholder — its
   absolute positioning). All this adds is what goes inside: the Ambrosia mark
   for Ambrosia's own items, the brand's name for the fifty-one items that are
   somebody else's. Muted rather than full strength: at full red, a grid of
   twelve of these reads as twelve copies of one product. */
.spec-card__placeholder.sfi-ph,
.card__placeholder.sfi-ph,
.cat-card__placeholder.sfi-ph {
	display: grid; place-items: center; padding: 14%;
	background: linear-gradient(135deg, var(--sand), var(--mist));
}
.sfi-ph__mark {
	width: 100%; max-width: 210px; height: auto;
	opacity: .34; mix-blend-mode: multiply;
}
.sfi-ph__brand {
	font-family: var(--font-head); font-weight: 700;
	font-size: clamp(.78rem, 1.5vw, .98rem); letter-spacing: .08em;
	text-transform: uppercase; text-align: center; text-wrap: balance;
	color: color-mix(in srgb, var(--charcoal) 70%, transparent);
}
/* The single-product stage is the one place a placeholder gets a lot of room. */
.product-single__media .sfi-ph__mark { max-width: 260px; }
.cat-related__label { font-family: var(--font-head); font-weight: 700; font-size: .9rem; line-height: 1.35; letter-spacing: .02em; }
.cat-related__item a:hover .cat-related__label { color: var(--red); }
.cat-related__sku { font-family: var(--font-head); font-weight: 700; font-size: .78rem; color: var(--red); }

@media (max-width: 900px) {
	.cat-about__grid { grid-template-columns: 1fr; }
	.cat-about__media { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
	.cat-about__media[data-count="3"] .cat-about__img:first-child { grid-column: auto; }
}

/* ---- catalogue overview + search ----
   The client asked for search to be "really sophisticated and prominent", so on
   the catalogue it is the first thing under the headline rather than an icon in
   the corner. */
/* DESIGN NOTE (client, 2026-08-11): this header is CENTRED, not left-aligned.
   The rest of the site aligns copy to the content edge because it always has a
   second element to sit against — a hero photo, a map, an image column. This
   band has nothing beside it: it is a headline, a line of intro and a search
   field, capped at 54rem inside a 1200px wrap. Left-aligned, that cap reads as
   a column that lost its partner, with dead space to the right of everything.
   Centred, the cap reads as deliberate. So the rule is: constrained + alone →
   centre it; constrained + paired with something → keep it on the content edge.
   If this band ever gains a right-hand element, take the centring back off. */
.catalogue-hero { position: relative; background: var(--cream); padding: clamp(2.25rem, 5vw, 4rem) 0 clamp(3rem, 6vw, 4.5rem); }
.catalogue-hero__inner { max-width: 54rem; margin-inline: auto; text-align: center; }
.catalogue-hero h1 { margin: .35rem 0 .75rem; font-size: clamp(1.9rem, 4vw, 2.9rem); letter-spacing: -.03em; }
.catalogue-hero__intro { font-size: clamp(1.02rem, 1.35vw, 1.2rem); line-height: 1.6; max-width: 60ch; margin-inline: auto; color: color-mix(in srgb, var(--ink) 82%, transparent); }
.catalogue-hero__count { margin: 0 0 1.25rem; color: var(--charcoal); font-family: var(--font-head); font-weight: 600; }

/* The field itself keeps left-aligned input text — centred text in a search box
   fights the cursor — but the box and its hint centre with the rest. */
.cat-search { position: relative; margin: 1.5rem auto 0; max-width: 44rem; text-align: left; }
.cat-search__row {
	display: flex; align-items: center; gap: .5rem;
	background: var(--paper); border: 2px solid rgba(28,28,28,.14);
	border-radius: 999px; padding: .35rem .35rem .35rem 1rem;
	transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.cat-search__row:focus-within { border-color: var(--red); box-shadow: 0 0 0 4px color-mix(in srgb, var(--red) 16%, transparent); }
.cat-search__icon { flex: 0 0 auto; color: var(--charcoal); }
.cat-search__row input[type="search"] {
	flex: 1; min-width: 0; border: 0; background: none; outline: none;
	font-family: var(--font-body); font-size: 1.02rem; padding: .65rem .25rem; color: var(--ink);
}
.cat-search__row input[type="search"]::-webkit-search-cancel-button { cursor: pointer; }
.cat-search__submit { flex: 0 0 auto; padding: .7rem 1.5rem; min-height: 44px; font-size: .9rem; }
.cat-search__hint { margin: .55rem 0 0 1rem; font-size: .82rem; color: var(--charcoal); }

/* Suggestions. Positioned over the page, so the list can be long without the
   layout jumping every keystroke. */
.cat-search__results {
	position: absolute; top: calc(100% + .4rem); left: 0; right: 0; z-index: 20;
	list-style: none; margin: 0; padding: .35rem;
	background: var(--paper); border: 1px solid rgba(28,28,28,.12);
	border-radius: var(--radius); box-shadow: var(--shadow-md);
	max-height: 22rem; overflow-y: auto;
}
.cat-search__result a { display: grid; grid-template-columns: 40px 1fr auto; align-items: center; gap: .75rem; padding: .5rem .6rem; border-radius: 10px; text-decoration: none; color: var(--ink); }
.cat-search__result a:hover, .cat-search__result[aria-selected="true"] a { background: var(--mist); }
.cat-search__result img, .cat-search__blank { width: 40px; height: 40px; object-fit: contain; border-radius: 6px; background: var(--mist); }
.cat-search__name { font-family: var(--font-head); font-weight: 700; font-size: .88rem; line-height: 1.3; }
.cat-search__meta { font-size: .78rem; color: var(--charcoal); white-space: nowrap; }

/* ---- dropdowns ----
   One look for every <select> on the site — the catalogue facets, the contact
   form, anything added later. Native selects are styled inconsistently across
   platforms and the default chevron is the giveaway, so `appearance: none` plus
   our own chevron (inline SVG data URI — no extra request, and it inherits
   nothing so the colour is baked in). Padding-right leaves room for it. The
   OPTION list is still drawn by the OS; that part cannot be styled, which is
   the tradeoff for keeping a real, accessible, keyboard-native control. */
.sfi-select,
.rsm-form__field select {
	appearance: none; -webkit-appearance: none;
	font-family: var(--font-head); font-weight: 600; font-size: .9rem; color: var(--ink);
	padding: .7rem 2.6rem .7rem 1rem;
	border: 1.5px solid rgba(28,28,28,.16); border-radius: 999px;
	background-color: var(--paper);
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2363696D' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 9l7 7 7-7'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right .85rem center;
	background-size: 16px 16px;
	cursor: pointer; max-width: 100%;
	transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.sfi-select:hover { border-color: rgba(28,28,28,.3); }
.sfi-select:focus-visible,
.rsm-form__field select:focus-visible {
	outline: none; border-color: var(--red);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 20%, transparent);
}
/* The form's selects sit in a column of text inputs, so they take that shape
   rather than the pill .sfi-select uses. */
.rsm-form__field select { border-radius: 10px; background-color: var(--linen); font-family: var(--font-body); font-weight: 400; font-size: 1rem; padding: .7rem 2.6rem .7rem .85rem; }


/* ---- results + pagination ---- */
.catalogue-results { transition: opacity .18s var(--ease); }
.catalogue-results.is-loading { opacity: .45; pointer-events: none; }
.cat-pager {
	display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
	gap: .75rem 1.5rem;
}
.cat-pager--top { margin-bottom: 1.25rem; padding-bottom: .85rem; border-bottom: 1px solid rgba(28,28,28,.1); }
.cat-pager--bottom { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.cat-pager--bottom .cat-pager__count { order: 2; }
.cat-pager__count { margin: 0; font-size: .84rem; color: var(--charcoal); font-family: var(--font-head); font-weight: 600; }
.cat-pager__list { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; list-style: none; margin: 0 0 0 auto; padding: 0; }
.cat-pager__list .page-numbers {
	display: inline-flex; align-items: center; justify-content: center;
	min-width: 38px; height: 38px; padding: 0 .6rem; border-radius: 999px;
	font-family: var(--font-head); font-weight: 700; font-size: .86rem;
	color: var(--ink); text-decoration: none; border: 1.5px solid transparent;
	transition: background .2s var(--ease), border-color .2s var(--ease), color .2s var(--ease);
}
.cat-pager__list a.page-numbers:hover { background: var(--paper); border-color: rgba(28,28,28,.16); color: var(--red); }
.cat-pager__list .page-numbers.current { background: var(--red); color: #fff; }
.cat-pager__list .page-numbers.dots { min-width: 1.5rem; color: var(--charcoal); }
.cat-pager__list .sfi-icon { width: 17px; height: 17px; }

/* ---------- category pages (2026-08-12 consolidation) ----------
   The category hero is the same component as everywhere else; only the copy
   block differs — it holds the category's marketing paragraph rather than a
   one-line sub, so it needs prose spacing and a cap on how far it can run
   before the panel stops being a hero. */
.hero--category .cat-hero__body { max-width: 52ch; margin-bottom: 1.75rem; }
.hero--category .cat-hero__body p { margin: 0 0 .7rem; }
.hero--category .cat-hero__body p:last-child { margin-bottom: 0; }
.hero--category .hero__title { margin-bottom: .85rem; }
/* Breadcrumbs sit above the H1 inside the copy panel now that the full-bleed
   banner is gone. */
.hero--category .cat-crumbs { margin: 0 0 .85rem; color: var(--charcoal); font-size: .82rem; }
.hero--category .cat-crumbs a { color: inherit; }
.hero--category .cat-crumbs a:hover { color: var(--red); }
.hero--category .cat-crumbs span { margin: 0 .4rem; opacity: .5; }

/* Grouped item listing: every SKU in the parent category, under its
   sub-category. */
.cat-items__head {
	display: flex; align-items: baseline; justify-content: space-between;
	gap: .5rem 1.5rem; flex-wrap: wrap; margin-bottom: clamp(1.25rem, 3vw, 2rem);
}
.cat-items--grouped .cat-items__heading { margin: 0; }
.cat-items__count { margin: 0; color: var(--charcoal); font-family: var(--font-head); font-weight: 600; font-size: .88rem; }
.cat-group + .cat-group { margin-top: clamp(2rem, 4.5vw, 3.25rem); }
.cat-group__head {
	display: flex; align-items: baseline; justify-content: space-between;
	gap: .5rem 1.5rem; flex-wrap: wrap;
	margin-bottom: clamp(.85rem, 2vw, 1.25rem);
	padding-bottom: .6rem; border-bottom: 2px solid color-mix(in srgb, var(--red) 22%, transparent);
}
.cat-group__title { margin: 0; font-size: clamp(1.15rem, 2vw, 1.45rem); }
.cat-group__all { white-space: nowrap; font-size: .86rem; }

/* ---------- one block per brand inside a sub-category ----------
   Client, 2026-08-27: Horn of Plenty must not sit shoulder to shoulder with
   Ambrosia — HoP is the cheaper line and the adjacency invites a price
   comparison. Sorting cannot do it: the grid is auto-fill, so the brand
   boundary lands wherever the column count puts it. Each brand therefore gets
   its own grid, and the gap between them is the separation.

   The label is quiet on purpose ("not too prominent"): a small caps line, not a
   second heading competing with the sub-category above it. Only sub-categories
   carrying more than one brand render this at all — the other 34 are a single
   unlabelled grid, exactly as before. */
.cat-brand + .cat-brand { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.cat-brand__title {
	margin: 0 0 clamp(.65rem, 1.4vw, .9rem);
	font-family: var(--font-body, inherit);
	font-size: .78rem; font-weight: 700;
	letter-spacing: .09em; text-transform: uppercase;
	color: var(--charcoal, #63696D);
}
.cat-brand__title::after {
	content: ""; display: block;
	width: 2.25rem; height: 2px; margin-top: .45rem;
	background: color-mix(in srgb, var(--red) 45%, transparent);
}

/* The spec-sheet disclaimer. "Not too prominent but enough for CYA" (client,
   2026-08-27) — so: small, muted, left where the eye lands after the listing,
   and deliberately not boxed. It sits below a grid of white cards on --mist,
   which is enough separation without a rule above it. */
.spec-disclaimer {
	margin: clamp(1.5rem, 3vw, 2.25rem) 0 0;
	font-size: .8rem; line-height: 1.5;
	color: color-mix(in srgb, var(--charcoal) 88%, transparent);
}
.product-single__actions + .spec-disclaimer { margin-top: 1.25rem; }

.catalogue-all { padding: var(--section-y) 0; background: var(--paper); }

/* ---- the search results' category filter (2026-09-09) ----
   Client's standing meeting: "they also suggested a filter categories option."

   A chip ROW, not a select: every state is a real URL, so the row doubles as a
   summary of where the search landed — "kosher" is 211 items and the row says
   which shelves they are on before anyone clicks. That is most of the value; the
   filtering is the rest.

   It wraps rather than scrolling. A horizontal scroller hides options behind an
   affordance people miss, and ten short chips at this size are two calm rows on
   a phone. The count is the reason a chip is worth clicking, so it is inside the
   link and inherits its colour rather than sitting beside it in grey. */
.search-filters {
	display: flex; flex-wrap: wrap; align-items: baseline;
	gap: .6rem clamp(.75rem, 2vw, 1.15rem);
	margin: 0 0 clamp(1.75rem, 3.5vw, 2.5rem);
}
.search-filters__label {
	font-family: var(--font-head); font-weight: 700; font-size: .74rem;
	letter-spacing: .08em; text-transform: uppercase; color: var(--charcoal);
}
.search-filters__list { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; margin: 0; padding: 0; }
.search-filter {
	display: inline-flex; align-items: baseline; gap: .45rem;
	padding: .42rem .85rem; border-radius: 999px;
	border: 1px solid rgba(28,28,28,.14); background: var(--paper);
	font-size: .9rem; line-height: 1.3; color: var(--ink); text-decoration: none;
	transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.search-filter:hover { border-color: rgba(28,28,28,.34); background: var(--mist); }
.search-filter:focus-visible { outline: 3px solid var(--red); outline-offset: 2px; }
/* The current chip is the brand red rather than a tint: it is the one piece of
   state on the page, and it has to read as "on" from the corner of the eye. */
.search-filter.is-current {
	background: var(--red); border-color: var(--red); color: #fff; font-weight: 600;
}
.search-filter.is-current:hover { background: #a81d2b; border-color: #a81d2b; }
.search-filter__n {
	font-family: var(--font-head); font-weight: 700; font-size: .74rem;
	font-variant-numeric: tabular-nums; opacity: .65;
}
.search-filter.is-current .search-filter__n { opacity: .8; }
@media (max-width: 560px) {
	/* The label stops sharing a line with the chips before it starts hyphenating. */
	.search-filters { display: block; }
	.search-filters__label { display: block; margin-bottom: .6rem; }
}

/* ---- the page results, and the other site's (2026-09-11) ----
   THESE HAD NO STYLES AT ALL. `.search-results` has been in search.php since the
   theme was scaffolded and never had a rule written for it, because until
   2026-09-09 the site-wide search could not return a page: every widening was
   gated on the catalogue's hidden `post_type` input, so the branch that renders
   this list only ever ran empty. It runs for real now — "emily" answers Our
   People — so the bare <ul> of blue links is finally visible, and this is it
   dressed to match the spec cards beside it.

   RULES BETWEEN ROWS RATHER THAN CARDS. A card says "this is a thing you can
   have"; these are doorways to a page, and the snippet under each one is the
   half that does the persuading (see sfi_search_snippet()). Hairlines keep the
   snippet next to its own title and let the list stay as long as it needs to. */
.search-results { list-style: none; margin: 0; padding: 0; }
.search-result { padding: clamp(1rem, 2.2vw, 1.4rem) 0; border-top: 1px solid rgba(28,28,28,.1); }
.search-result:first-child { border-top: 0; padding-top: 0; }
.search-result h3 { margin: 0; font-size: clamp(1.08rem, 1.9vw, 1.3rem); line-height: 1.3; }
.search-result h3 a { color: var(--ink); text-decoration: none; }
.search-result h3 a:hover { color: var(--red); text-decoration: underline; }
.search-result h3 a:focus-visible { outline: 3px solid var(--red); outline-offset: 3px; }
.search-result p {
	margin: .45rem 0 0; max-width: 78ch;
	font-size: .95rem; line-height: 1.65; text-wrap: pretty;
	color: color-mix(in srgb, var(--ink) 78%, transparent);
}

/* THE OTHER SITE'S RESULTS ARE SET BACK FROM THE LOCAL ONES, deliberately and
   visibly. They are not more of the same list — they are a capped, unpaged peek
   at the half of the site that lives on the other domain (see
   inc/cross-site-search.php), and presenting them as equals would make the match
   count above them wrong. A rule across the top and a tint is the whole
   treatment: enough to say "different place", not enough to say "less good". */
.cross-site {
	margin-top: clamp(2.5rem, 5vw, 4rem);
	padding: clamp(1.5rem, 3.5vw, 2.5rem) clamp(1.25rem, 3vw, 2rem);
	border-top: 3px solid var(--red);
	border-radius: 0 0 var(--radius-lg) var(--radius-lg);
	background: var(--cream);
}
.cross-site .cat-items__heading { margin: 0 0 clamp(.75rem, 2vw, 1.1rem); font-size: clamp(1.25rem, 2.2vw, 1.6rem); }
.cross-site .search-result { border-top-color: rgba(28,28,28,.14); }
.cross-site__more { margin: clamp(1rem, 2.2vw, 1.4rem) 0 0; font-size: .95rem; }
.cross-site__more a { color: var(--red); font-weight: 600; }

/* ---------- catalogue overview (the /products/ page) ----------
   Rebuilt 2026-08-12: the site's split hero, then the categories, then a
   smaller repeat of the search. Rhythm is the homepage's — hero CREAM → cards
   WHITE → strip MIST — so the two pages read as the same site. */
.catalogue-cats { background: var(--paper); }

/* The search field standing in for the hero's CTA button. Left-aligned on the
   copy panel's own edge, and narrower than the standalone version (the panel is
   half the page) — .cat-search's own max-width would otherwise centre it. */
/* The desktop slot is close to square while the photo is 4:3, so ~20% of the
   width is cropped. Biased slightly right: it keeps both cans clear of the edge
   and trims the dead window frame on the left. Re-derive if the photo changes. */
.hero--catalogue .hero__img { object-position: 55% center; }

.hero__search { margin-top: .35rem; }
.hero__search .cat-search { margin: 0; max-width: 34rem; }
.hero__search .cat-search__hint { margin-left: 1rem; }
/* The type-ahead panel drops well past the foot of the hero, and .hero clips
   its overflow (it is what keeps the photography inside the split). Opened up
   here only — nothing in this hero can spill: the photo is absolutely
   positioned inside .hero__media and the curve divider is meant to overlap the
   join. z-index keeps the panel over the categories section it lands on, and
   stays under the sticky header (100). */
.hero--catalogue { overflow: visible; z-index: 5; }
.hero__search .cat-search__results { max-height: min(56vh, 380px); overflow-y: auto; }

/* The hero copy has to paint ABOVE the curve divider, or the arc covers the
   search field and its suggestions. The z-index on .cat-search__results alone
   could not do it: .hero__content carries .reveal, whose `will-change:
   opacity, transform` makes it a STACKING CONTEXT — so every z-index inside it
   is sealed in, and the whole block sat below the curve's z-index 3. Raising
   the context itself is the fix. Applies to all heroes: the arc should never
   cover hero copy, it just never reached far enough down to show before. */
.hero__content { position: relative; z-index: 4; }

/* Same trap at the other end of the page: the repeat search sits in the last
   band, its .reveal wrapper seals the suggestion panel's z-index in, and the
   footer — position: relative, later in the DOM — would paint over it. */
.cat-search-band { position: relative; z-index: 2; }

/* Two rows of five, matching the homepage grid exactly (client, 2026-08-12).
   Scoped to the overview: the same card grid renders sub-categories on a
   category page, where auto-fill is the right behaviour for 3–4 of them. */
.catalogue-cats .cat-children__grid { grid-template-columns: repeat(5, 1fr); }
@media (max-width: 1040px) { .catalogue-cats .cat-children__grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 640px)  { .catalogue-cats .cat-children__grid { grid-template-columns: repeat(2, 1fr); } }

/* The repeat search band. Quieter than the hero field by design — same
   component, smaller type and a tighter row, on the mist strip. */
.cat-search-band { padding: clamp(2.25rem, 5vw, 3.5rem) 0; background: var(--paper); }
.cat-search-band__inner { max-width: 46rem; text-align: center; }
.cat-search-band__title {
	margin: 0 0 1rem; font-size: clamp(1.15rem, 2vw, 1.5rem); letter-spacing: -.02em;
}
.cat-search-band .cat-search { margin-top: 0; max-width: 38rem; }
.cat-search--band .cat-search__row { border-width: 1.5px; padding-left: .85rem; }
.cat-search--band .cat-search__row input[type="search"] { font-size: .95rem; padding-top: .5rem; padding-bottom: .5rem; }
.cat-search--band .cat-search__submit { padding: .55rem 1.2rem; min-height: 40px; font-size: .84rem; }
.cat-search--band .cat-search__hint { text-align: center; margin-left: 0; }

/* The last section on the page is the search band either way, so the footer's
   curve fills with whatever the cadence has painted it — and since 2026-09-21
   that depends on how many bands are above it. The family-of-brands section
   reads its content off the SFI company page across the network and bails when
   there is nothing to show, so this page is three bands after the hero or four,
   and the search band is BAND 4 (paper) or BAND 5 (linen) accordingly.

   Both cases are written out, positionally, the way .category-page does it.
   Keying this on `.cat-search-band:last-child` instead — which is what was here
   before, and was true — describes the element and not its colour, and would
   have gone on confidently answering "white" the moment a section was inserted
   above it. */
/* The brand tiles are `--paper` on a band the cadence also paints `--paper`
   here, where on the company page they sit on mist. So they carry their resting
   edge as a shadow instead of relying on the 9%-alpha hairline — the same
   answer `.front--company .faq` got on 2026-09-03, for the same reason. The
   Ambrosia panel needs nothing: it is cream, which is a real step off white. */
.catalogue-overview .brand { box-shadow: var(--shadow-sm); }

.catalogue-overview:has(> .hero-anchor + section + section + section:last-child) + .site-footer { --footer-curve-fill: var(--paper); }
.catalogue-overview:has(> .hero-anchor + section + section + section + section:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--linen); }

@media (max-width: 600px) {
	.cat-search__row { flex-wrap: wrap; border-radius: var(--radius); padding: .5rem; }
	.cat-search__submit { width: 100%; }
	.cat-search__hint { margin-left: .25rem; }
}

/* ---------- category copy section (2026-08-19) ----------
   The paragraph the hero used to carry, now a band of its own between the hero
   and the products. No photo column beside it and a hard cap on measure, so by
   the site's own rule — constrained and alone → centre it. */
.cat-intro { padding: clamp(2.5rem, 5.5vw, 4rem) 0; }
.cat-intro__inner { max-width: 58rem; margin-inline: auto; text-align: center; }
.cat-intro__heading { margin: 0 0 .9rem; font-size: clamp(1.35rem, 2.4vw, 1.9rem); }
.cat-intro__body { max-width: 68ch; margin-inline: auto; font-size: clamp(1rem, 1.25vw, 1.1rem); line-height: 1.7; color: color-mix(in srgb, var(--ink) 84%, transparent); }
.cat-intro__body p { margin: 0 0 1rem; }
.cat-intro__body p:last-child { margin-bottom: 0; }

/* …and the same band WITH photography (2026-08-20). Two or three shots of the
   product itself beside the copy, so the screen below the fold is not a wall of
   text on white. Photos left / copy right, against the hero's copy-left order
   directly above. The copy stops being centred here — it is no longer alone, so
   the site's rule flips it back to flush left. */
.cat-intro--media .cat-intro__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
	gap: clamp(1.5rem, 4vw, 3.25rem);
	align-items: center;
}
.cat-intro--media .cat-intro__inner { max-width: none; margin-inline: 0; text-align: left; }
.cat-intro--media .cat-intro__body { margin-inline: 0; max-width: 62ch; }
.cat-intro__media { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(.7rem, 1.5vw, 1.1rem); order: -1; }
.cat-intro__img {
	width: 100%;
	aspect-ratio: 4 / 5;
	object-fit: cover;
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-sm);
	display: block;
}
/* A pair sits staggered rather than square-on — the second shot is the detail
   (sliced rings, a single variety) and reads as a note against the first. */
.cat-intro__media[data-count="2"] .cat-intro__img:first-child { margin-top: clamp(.9rem, 2.6vw, 2.25rem); }
.cat-intro__media[data-count="1"] { grid-template-columns: 1fr; }
.cat-intro__media[data-count="1"] .cat-intro__img { aspect-ratio: 3 / 2; }
.cat-intro__media[data-count="3"] .cat-intro__img:first-child { grid-column: 1 / -1; aspect-ratio: 3 / 2; }
@media (max-width: 900px) {
	.cat-intro--media .cat-intro__grid { grid-template-columns: 1fr; gap: clamp(1.5rem, 5vw, 2.25rem); }
	/* Copy first on a phone: the heading names the category the reader just
	   tapped, and the photos read as its illustration, not as a lead-in. */
	.cat-intro__media { order: 0; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
	.cat-intro__media[data-count="2"] .cat-intro__img:first-child { margin-top: 0; }
	.cat-intro__media[data-count="3"] .cat-intro__img:first-child { grid-column: auto; aspect-ratio: 4 / 5; }
}

/* "See All Olives" at the foot of a leaf category's grid, and under the
   siblings on a product page. Centred and on its own line: it is the end of the
   list, not an item in it. */
.cat-items__up { margin: clamp(1.75rem, 3.5vw, 2.75rem) 0 0; text-align: center; }

/* Kosher, on a single product. One badge, so it is a line rather than a row. */
.product-single__badges { margin: 0 0 1.25rem; }
.product-badge {
	display: inline-flex; align-items: center; padding: .3rem .8rem;
	border: 1px solid color-mix(in srgb, var(--green) 45%, transparent);
	border-radius: 999px; background: color-mix(in srgb, var(--green) 8%, transparent);
	font-family: var(--font-head); font-weight: 700; font-size: .76rem;
	letter-spacing: .06em; text-transform: uppercase;
	/* The brand green is a 3:1 colour on white — fine for the map's large fills,
	   short of AA for .76rem uppercase. Darkened to ~5.9:1 for the text only;
	   the badge still reads as brand green. */
	color: color-mix(in srgb, var(--green) 70%, #000);
}

/* ---------- the complete product list (2026-08-19) ----------
   Client: "add the full product list to the main product page under the
   categories… they like it as PDF download and regular web page." So: a real
   TABLE, one row per SKU, every column off their own sheet — not more cards.
   Cards are a browse device and there are ten category tiles above doing that
   job; this is the reference document, and a distributor reads it the way they
   read the printed list, or hits ⌘F. */
/* BAND COLOUR — the /products/ rhythm is now hero CREAM → categories WHITE →
   full list MIST → search band WHITE. The list took the tint because it is the
   page's longest section and needed to read as its own thing rather than as
   more of the categories above it; the search band gave its tint up in the same
   move, since two tints touching is the one thing the palette cannot do (cream
   #F7F1E8 and mist #F5F2EE are six of 255 apart). */
.catalogue-list { padding: var(--section-y) 0; background: var(--mist); }
.catalogue-list__head {
	display: flex; align-items: flex-end; justify-content: space-between;
	gap: 1.25rem 2.5rem; flex-wrap: wrap;
	padding-bottom: clamp(1rem, 2vw, 1.5rem);
	border-bottom: 2px solid color-mix(in srgb, var(--red) 22%, transparent);
}
.catalogue-list__intro { max-width: 60ch; }
.catalogue-list__head .cat-items__heading { margin: 0; }
.catalogue-list__lede { margin: .6rem 0 0; color: color-mix(in srgb, var(--ink) 80%, transparent); }
.catalogue-list__count { margin: .5rem 0 0; color: var(--charcoal); font-family: var(--font-head); font-weight: 600; font-size: .88rem; }
.catalogue-list__pdf { flex: 0 0 auto; }
.catalogue-list__pdf .sfi-icon { width: 18px; height: 18px; }

/* Jump list. 265 rows is a long scroll and the alternative is finding "Peppers"
   by eye. Chips rather than a bulleted column: ten of them fit on one or two
   lines and read as a control strip, which is what they are. */
/* ---- the full list is collapsed until asked for ----
   Client, 2026-08-20. A native <details>, so the marker has to be removed by
   hand (::-webkit-details-marker for older Safari, list-style for everyone
   else) before <summary> can be styled as the button it reads as. */
.catalogue-list__disclosure { margin: clamp(1.5rem, 3.5vw, 2.25rem) 0 0; }
.catalogue-list__toggle {
	display: inline-flex; align-items: center; gap: .55rem;
	cursor: pointer; list-style: none;
}
.catalogue-list__toggle::-webkit-details-marker { display: none; }
.catalogue-list__toggle-icon { display: inline-flex; transition: transform .25s var(--ease); }
.catalogue-list__toggle-icon .sfi-icon { width: 18px; height: 18px; }
.catalogue-list__toggle-close { display: none; }
[open] > .catalogue-list__toggle .catalogue-list__toggle-open { display: none; }
[open] > .catalogue-list__toggle .catalogue-list__toggle-close { display: inline; }
[open] > .catalogue-list__toggle .catalogue-list__toggle-icon { transform: rotate(180deg); }
/* The first category block sets its own top margin; with the list open the
   toggle needs the same air beneath it as the head above has. */
.catalogue-list__disclosure > .catalogue-list__jump { margin-top: clamp(1.5rem, 3.5vw, 2.25rem); }

/* THE SALES TEAM'S BROCHURE, KEPT QUIET (client, 2026-09-14: "not a red button
   and you have to click to expand… too many CTAs and buttons in one place").
   A line of text inside the disclosure, at the size of the spec-sheet
   disclaimer rather than the size of the two buttons above it — the one thing
   it must not look like is a third call to action. It still underlines and
   still takes the red on hover, because it is a link and nothing is served by
   hiding that. */
.catalogue-list__brochure {
	margin: .9rem 0 0;
	font-size: .85rem; line-height: 1.5; color: var(--charcoal);
}
.catalogue-list__brochure a {
	display: inline-flex; align-items: center; gap: .4rem;
	color: var(--charcoal); text-decoration: underline;
	text-underline-offset: .18em;
}
.catalogue-list__brochure a:hover { color: var(--red); }
.catalogue-list__brochure .sfi-icon { width: 15px; height: 15px; flex: none; }
.catalogue-list__brochure-note::before { content: " — "; }

.catalogue-list__jump { margin: clamp(1.25rem, 3vw, 1.75rem) 0 0; }
.catalogue-list__jump ul { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; margin: 0; padding: 0; }
.catalogue-list__jump a {
	display: inline-block; padding: .4rem .9rem; border-radius: 999px;
	border: 1px solid rgba(28,28,28,.14); background: var(--mist);
	font-family: var(--font-head); font-weight: 700; font-size: .8rem;
	color: var(--ink); text-decoration: none;
}
.catalogue-list__jump a:hover { background: var(--red); border-color: var(--red); color: #fff; }

.catalogue-list__cat { margin-top: clamp(2.5rem, 5vw, 4rem); scroll-margin-top: 120px; }
.catalogue-list__cathead {
	display: flex; align-items: baseline; justify-content: space-between;
	gap: .5rem 1.5rem; flex-wrap: wrap; margin-bottom: 1rem;
}
.catalogue-list__catname { margin: 0; font-size: clamp(1.3rem, 2.4vw, 1.8rem); }
.catalogue-list__subname {
	margin: clamp(1.5rem, 3vw, 2.25rem) 0 .6rem;
	font-size: clamp(.95rem, 1.5vw, 1.08rem); letter-spacing: .06em; text-transform: uppercase;
	color: var(--red);
}
.catalogue-list__subname a { color: inherit; text-decoration: none; }
.catalogue-list__subname a:hover { text-decoration: underline; }

/* The table is wider than a phone. It scrolls INSIDE this box — the page body
   must never scroll sideways — and the box is focusable so that scroll is
   reachable without a pointer. */
.catalogue-table__scroll {
	overflow-x: auto; -webkit-overflow-scrolling: touch;
	background: var(--paper); border: 1px solid rgba(28,28,28,.09);
	border-radius: var(--radius);
}
.catalogue-table__scroll:focus-visible { outline: 3px solid var(--red); outline-offset: 2px; }
.catalogue-table { width: 100%; border-collapse: collapse; font-size: .88rem; }
.catalogue-table th, .catalogue-table td { padding: .55rem .75rem; text-align: left; vertical-align: top; }
/* No sticky header: `overflow-x: auto` on the wrapper computes overflow-y to
   auto as well, so `position: sticky` would resolve against a box with no
   vertical scroll and do nothing but risk clipping. It is not needed anyway —
   the list is 48 small tables, each with its own header a few rows away. */
.catalogue-table thead th {
	background: var(--mist); border-bottom: 1px solid rgba(28,28,28,.16);
	font-family: var(--font-head); font-weight: 700; font-size: .74rem;
	letter-spacing: .06em; text-transform: uppercase; color: var(--charcoal);
	white-space: nowrap;
}
.catalogue-table tbody tr { border-bottom: 1px solid rgba(28,28,28,.08); }
.catalogue-table tbody tr:last-child { border-bottom: 0; }
.catalogue-table tbody tr:hover { background: color-mix(in srgb, var(--cream) 75%, var(--paper)); }
.catalogue-table .is-num { white-space: nowrap; }
.catalogue-table__sku { font-family: var(--font-head); font-weight: 700; color: var(--red); }
/* The description is the row's header cell — it is what identifies the row to a
   screen reader reading the pallet figure three columns over. Normal weight,
   because a table of 265 bold rows is a table with no emphasis left. */
.catalogue-table__desc { font-weight: 500; min-width: 18rem; }
.catalogue-table__desc a { color: var(--ink); text-decoration: none; }
.catalogue-table__desc a:hover { color: var(--red); text-decoration: underline; }
.catalogue-table__kosher { font-family: var(--font-head); font-weight: 700; color: color-mix(in srgb, var(--green) 70%, #000); }

@media (max-width: 700px) {
	.catalogue-list__head { align-items: flex-start; }
	.catalogue-list__pdf { width: 100%; }
	.catalogue-table { font-size: .82rem; }
	.catalogue-table__desc { min-width: 14rem; }
}

/* ---------- motion ---------- */
@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
	.hero__video { display: none; }
	.sfi-js .reveal { opacity: 1 !important; transform: none !important; }
	.benefit-card:hover { transform: none; }
	.benefit-card:hover .benefit-card__img { transform: none; }
}

/* ---------- product label (ingredients & nutrition) ----------
   A second picture in the same column as the pack shot, and deliberately NOT
   the same kind of object: the pack shot is the product, the label is a
   document about it. So it reads as a document — squared corners inside a
   hairline frame, a caption under it, and a link affordance rather than a
   silent image. It is only ever on the single-product page; the browse grids
   keep showing the pack shot, which is what a label at card size cannot do.

   The client's scans run from about 850px to 1500px on the long edge, which is
   readable when opened but not at panel size, so the thumbnail is explicitly a
   way IN to the full image, never the place anyone reads an ingredient list. */
.product-single__gallery { align-self: start; display: grid; gap: 1rem; }
.product-label { margin: 0; }
.product-label__link {
	display: block; position: relative; text-decoration: none;
	background: var(--paper); border: 1px solid rgba(28,28,28,.12); border-radius: var(--radius);
	overflow: hidden; transition: border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.product-label__link:hover,
.product-label__link:focus-visible { border-color: var(--red); box-shadow: var(--shadow-sm, 0 4px 14px rgba(0,0,0,.08)); }
.product-label__img { display: block; width: 100%; height: auto; }
.product-label__cta {
	display: flex; align-items: center; gap: .4rem;
	padding: .6rem .9rem; border-top: 1px solid rgba(28,28,28,.08);
	font-family: var(--font-head); font-weight: 600; font-size: .85rem;
	text-transform: uppercase; letter-spacing: .04em; color: var(--red);
}
.product-label__link:hover .link-arrow__icon { transform: translateX(3px); }
.product-label__note { margin: .5rem 0 0; font-size: .8rem; color: var(--charcoal); }

/* The dialog is an enhancement — the <a> underneath it opens the same file on
   its own if the script never runs. */
.label-dialog {
	width: min(92vw, 1100px); max-width: none; max-height: 92vh; padding: 0;
	border: 0; border-radius: var(--radius-lg); background: var(--paper);
	overflow: auto;
}
.label-dialog::backdrop { background: rgba(21,24,27,.72); }
.label-dialog__img { display: block; width: 100%; height: auto; }
.label-dialog__close-form { position: sticky; top: 0; z-index: 2; display: flex; justify-content: flex-end; }
.label-dialog__close {
	margin: .5rem; width: 2.25rem; height: 2.25rem; line-height: 1; font-size: 1.4rem;
	border: 0; border-radius: 999px; cursor: pointer;
	background: rgba(21,24,27,.82); color: #fff;
}
.label-dialog__close:hover { background: var(--red); }
.label-dialog__cap {
	display: flex; flex-wrap: wrap; gap: .25rem .75rem;
	margin: 0; padding: .75rem 1rem 1rem; font-size: .85rem; color: var(--charcoal);
}

@media (max-width: 760px) {
	.label-dialog { width: 96vw; }
}

/* ---------- browse card falling back to the label ----------
   A label is a flat scan with white margins and a different shape to a pack
   shot, so left alone it would sit awkwardly small inside a card stage built
   for a jar. It gets the stage's full width instead and a hairline, which
   reads as "this is a document standing in", not as a badly cropped photo. */
.spec-card__img.is-label,
.card__img.is-label {
	object-fit: contain; background: #fff;
	border: 1px solid rgba(28,28,28,.07); border-radius: calc(var(--radius) / 2);
}

/* ---------- the serving photograph, below the specs ----------
   The only picture on a product page that is of food rather than of packaging,
   and the widest thing on the page. Aspect ratios in the client's set run from
   3:2 landscape through square to 3:4 portrait, so the frame is capped by
   HEIGHT and the image is contained inside it — a portrait shot sits centred
   rather than being cropped through the middle of the dish. */
.product-food { padding: clamp(1rem, 3vw, 2rem) 0 clamp(2rem, 5vw, 3.5rem); }
.product-food__heading {
	margin: 0 0 1rem; font-size: clamp(1.15rem, 2.2vw, 1.5rem);
}
.product-food__figure {
	margin: 0; padding: clamp(.75rem, 2vw, 1.25rem);
	background: var(--paper); border: 1px solid rgba(28,28,28,.08);
	border-radius: var(--radius-lg); overflow: hidden;
}
.product-food__img {
	display: block; width: 100%; height: auto;
	max-height: min(62vh, 560px); object-fit: contain;
	border-radius: var(--radius);
}

/* ================================================================== contact
   Client copy, 2026-09-01: a prominent left column (general assistance →
   sales → warehouses) and a deliberately quieter right sidebar (who we are →
   who we are not).

   THE SIDEBAR IS SECOND IN THE SOURCE AND MUST STAY THERE. The client's note
   is explicit that the main contact information comes first, and on a phone
   "first" means first in the document. Grid places it to the right on desktop
   without touching the reading order — do not swap it with `order`. */
/* Client review, 2026-09-01: "Sales" and "Who We Are Not" should PEEK above
   the fold, so a visitor does not open the message form without seeing that
   there are two better-aimed routes and a notice about who we are not. The
   band is trimmed at the TOP only — the foot keeps its full breathing room —
   and the hero above it is trimmed to match in the .page-contact rules below. */
.contact { padding: clamp(1.5rem, 2.6vw, 2rem) 0 clamp(2.5rem, 6vw, 4.5rem); }

/* ---- the short hero: THIS PAGE ASKED FOR IT FIRST, and it is global now ----
   Second review on 2026-09-01: the client put "Who We Are" back on top of the
   sidebar ("they want to lead with their brand") but still wanted the "Who We
   Are Not" copy readable without scrolling. Those two pull against each other —
   the notice sits BELOW a box roughly 380px tall — so the fold had to be bought
   back from somewhere, and the client said where: "it's ok if the hero section
   is different for this one page, since there's a lot of blank space ATF."

   They were right, and three more pages have since said the same thing, so the
   trim moved into `.inner-hero` itself (see the note there, 2026-09-09) and the
   three rules that lived here are gone. This page is no longer the exception —
   it is the page that discovered the default. The ~130px it buys is still
   load-bearing for the notice, so if `.inner-hero` is ever restored to the tall
   sizes, this page needs its own copy back. */

/* ---- the sidebar, tightened, this page only ----
   The rest of that budget. Same reason, same review: shorter boxes are what
   keeps the notice on screen once it sits second. Nothing is removed — the
   padding, the gaps and the leading each give back a little. */
.page-contact .contact__aside { gap: 1.15rem; }
.page-contact .contact-side { padding: clamp(1rem, 1.9vw, 1.25rem); }
.page-contact .contact-side__text { line-height: 1.55; }
.page-contact .contact-side__subtitle { margin-top: .8rem; }
.page-contact .contact-offices { gap: .7rem; }
.contact__wrap {
	display: grid; gap: clamp(2rem, 4vw, 3.5rem);
	grid-template-columns: minmax(0, 1fr);
	align-items: start;
}
@media (min-width: 900px) {
	/* The sidebar is "less prominent", so it is narrower — not equal columns. */
	.contact__wrap { grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr); }
}
.contact__aside { display: grid; gap: 1.5rem; }

.contact-block + .contact-block { margin-top: clamp(1.6rem, 3vw, 2.25rem); }
.contact-block__title {
	font-family: var(--font-head); font-weight: 700;
	font-size: clamp(1.25rem, 2.4vw, 1.6rem); margin: 0 0 .5rem;
}
.contact-block__intro { margin: 0 0 1.1rem; max-width: 62ch; }

/* --- the direct routes: phone + the forwarding inbox --- */
.contact-direct { list-style: none; margin: 0 0 1.1rem; padding: 0; display: grid; gap: .5rem; }
.contact-direct__item { display: flex; align-items: center; gap: .55rem; flex-wrap: wrap; }
.contact-direct__icon { width: 18px; height: 18px; flex: 0 0 auto; color: var(--red); }
.contact-direct__label {
	font-family: var(--font-head); font-weight: 700; font-size: .74rem;
	letter-spacing: .06em; text-transform: uppercase; color: var(--charcoal);
	min-width: 4.5rem;
}
.contact-direct__value { font-weight: 600; color: var(--ink); text-decoration: none; }
.contact-direct__value:hover { color: var(--red); text-decoration: underline; }

/* --- sales: two routes out to the forms on the distributors page --- */
.contact-sales { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }
.contact-sales__item {
	background: var(--paper); border: 1px solid rgba(28,28,28,.09);
	border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
	padding: clamp(1rem, 2.2vw, 1.4rem);
}
.contact-sales__title { font-family: var(--font-head); font-size: 1.02rem; margin: 0 0 .35rem; }
.contact-sales__text { margin: 0 0 .7rem; font-size: .95rem; }

/* --- warehouses ---
   Click to view (client, 2026-09-01: "I said dropdown but actually meant click
   for more"). A <details>, so it opens with no JavaScript and its state is
   announced — and every address stays in the markup whether or not it is ever
   opened, which is what keeps six real local-business records findable.
   There is no location picker inside it: the client's own call, six being few
   enough to read. */
.contact-wh {
	background: var(--paper); border: 1px solid rgba(28,28,28,.12);
	border-radius: var(--radius-lg);
}
.contact-wh[open] { box-shadow: var(--shadow-sm); }
.contact-wh__summary {
	display: flex; align-items: center; gap: .7rem; flex-wrap: wrap;
	padding: .8rem clamp(.9rem, 2vw, 1.2rem); cursor: pointer; list-style: none;
	border-radius: var(--radius-lg);
}
.contact-wh__summary::-webkit-details-marker { display: none; }
.contact-wh__summary::marker { content: ""; }
.contact-wh__summary:hover .contact-wh__summary-btn { color: var(--red); }
.contact-wh__summary:focus-visible { outline: 3px solid var(--red); outline-offset: -3px; }
.contact-wh__summary-btn {
	display: inline-flex; align-items: center; gap: .55rem;
	font-family: var(--font-head); font-weight: 700; color: var(--ink);
}
/* Empty content, so the chevron is decoration — <details> already announces
   expanded/collapsed, and a "+" here would be read out on top of it. */
.contact-wh__summary-btn::after {
	content: ""; width: .48rem; height: .48rem; margin-bottom: .18rem;
	border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
	transform: rotate(45deg); transition: transform .25s var(--ease);
}
.contact-wh[open] .contact-wh__summary-btn::after { transform: rotate(-135deg) translate(-.12rem, -.12rem); }
.contact-wh__summary-count { font-size: .86rem; color: var(--charcoal); }
.contact-wh__results { padding: 0 clamp(.9rem, 2vw, 1.2rem) clamp(1rem, 2vw, 1.3rem); }
.contact-wh__group + .contact-wh__group { margin-top: 1.4rem; }
.contact-wh__group-title {
	font-family: var(--font-head); font-weight: 700; font-size: .78rem;
	letter-spacing: .06em; text-transform: uppercase; color: var(--charcoal);
	margin: 0 0 .6rem;
}
.contact-wh__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .8rem; }
.contact-wh__item {
	border-left: 3px solid var(--beige); padding: .15rem 0 .15rem .85rem;
}
.contact-wh__name { font-family: var(--font-head); font-weight: 700; margin: 0 0 .2rem; }
.contact-wh__address { font-style: normal; margin: 0; font-size: .93rem; color: var(--charcoal); }
.contact-wh__phone { margin: .3rem 0 0; display: flex; align-items: center; gap: .4rem; font-size: .93rem; }
.contact-wh__phone a { color: var(--ink); text-decoration: none; font-weight: 600; }
.contact-wh__phone a:hover { color: var(--red); text-decoration: underline; }
.contact-wh__icon { width: 15px; height: 15px; flex: 0 0 auto; color: var(--red); }
.contact-wh__note { margin: .25rem 0 0; font-size: .85rem; color: var(--charcoal); }

/* --- the sidebar --- */
.contact-side {
	background: color-mix(in srgb, var(--beige) 16%, var(--paper));
	border-radius: var(--radius-lg); padding: clamp(1.1rem, 2.4vw, 1.6rem);
}
.contact-side__title { font-family: var(--font-head); font-size: 1.05rem; margin: 0 0 .5rem; }
.contact-side__subtitle {
	font-family: var(--font-head); font-weight: 700; font-size: .76rem;
	letter-spacing: .06em; text-transform: uppercase; color: var(--charcoal);
	margin: 1.1rem 0 .5rem;
}
.contact-side__text { margin: 0; font-size: .93rem; line-height: 1.65; }
.contact-side__text p { margin: 0 0 .7rem; }
.contact-side__text p:last-child { margin-bottom: 0; }
.contact-offices { list-style: none; margin: 0; padding: 0; display: grid; gap: .9rem; }
.contact-offices__label {
	font-family: var(--font-head); font-weight: 700; font-size: .93rem; margin: 0 0 .15rem;
}
.contact-offices__address { font-style: normal; margin: 0; font-size: .9rem; color: var(--charcoal); }
.contact-offices__phone { margin: .2rem 0 0; font-size: .9rem; }
.contact-offices__phone a { color: var(--ink); text-decoration: none; font-weight: 600; }
.contact-offices__phone a:hover { color: var(--red); text-decoration: underline; }

/* Client review, 2026-09-01: white with black text, NOT the red wash it had.
   "It's important language but they don't want to overly draw attention there"
   — so it keeps a hairline frame to stay a distinct box against the tinted
   panel above it, and drops every red cue. Colour does not carry it; being on
   screen does — see the fold notes above and in render-contact.php. */
.contact-side--notice {
	background: var(--paper); color: var(--ink);
	border: 1px solid rgba(28,28,28,.14);
}

/* One form rather than the distributor pair — no stacking gap to correct. */
.rsm-contact--single { margin-top: .25rem; }

/* =============================================================== resources
   Client brief, 2026-09-02: the split hero every other inner page runs, then
   "a market update drop down", then whatever else they file under Resources.

   THE MARKET UPDATES ARE CARDS, NOT AN ACCORDION (client, 2026-09-02: "can it
   look more like the current page with image at the top, date, and preview,
   rather than the current one open and then plain rows for the rest"). The
   newest is a wide lead card, picture beside copy; the rest sit in a three-up
   grid, picture over copy. Both use the news-card shape from the homepage, so
   Resources and the home page read as one site.

   Every card is one anchor — the headline — stretched over the whole surface by
   the ::after below. That keeps the tab order at one stop per update while the
   picture and the preview stay clickable. */
/* No background: the BASE SECTION CADENCE decides this band's colour by its
   position on the page, which is the only thing that can know it. */
.mu { padding: var(--section-y) 0; }

/* ---- the hero is the COMPACT one, and the trim is global now ----
   Colton, 2026-09-04: "just market update at the top, the quick intro in the
   spot where 'general assistance' is and then straight into the updates, and
   the most recent one visible above the fold rather than the random apples."

   The component swap (split hero → .inner-hero, in render-resources.php) does
   most of that on its own; the rest was three rules copied from the Contact
   page, and they now live in `.inner-hero` itself (2026-09-09). What is left
   here is the one rule that is genuinely about THIS page's first band.

   The fold budget is ~180px in total, which is what puts the lead update's
   picture, date, headline and most of its preview above an 800px fold at 1440
   wide. Do not restore the global hero sizes without checking that card. */
/* The updates start closer to the curve than a normal band would. The section
   head under it is an intro paragraph with no heading above it, so its own
   top margin is dead space at the top of the band — collapsed here rather than
   in .section-head, which every other page uses under an <h2>. */
.page-resources .mu { padding-top: clamp(1.5rem, 3vw, 2.5rem); }
.page-resources .mu .section-head__intro:first-child { margin-top: 0; }

/* No per-page cadence block here any more, and that is the point. This page had
   one for about an hour; it did nothing the BASE SECTION CADENCE block does not
   already do, and a per-page copy of the default is exactly the pattern that
   let the tint come back on the next new page. Only the footer valley needs
   naming, because it depends on how many bands this page ends up with. */
.page-resources:has(> .hero-anchor + section + section:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--mist); }

.mu-card {
	position: relative;
	background: var(--paper); border-radius: var(--radius); overflow: hidden;
	box-shadow: var(--shadow-sm); border: 1px solid rgba(28,28,28,.07);
	transition: transform .4s var(--ease), box-shadow .4s var(--ease);
	display: flex; flex-direction: column; height: 100%;
}
.mu-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); }
.mu-card__media { overflow: hidden; aspect-ratio: 16 / 9; background: var(--mist); }
.mu-card__img { width: 100%; height: 100%; object-fit: cover; transition: transform .6s var(--ease); }
.mu-card:hover .mu-card__img { transform: scale(1.04); }
.mu-card__blank { display: block; width: 100%; height: 100%; background: var(--mist); }
.mu-card__body {
	padding: clamp(1rem, 1.8vw, 1.4rem);
	display: flex; flex-direction: column; align-items: flex-start; gap: .4rem; flex: 1;
}
.mu-card__date {
	font-family: var(--font-head); font-weight: 700; font-size: .7rem;
	letter-spacing: .08em; text-transform: uppercase; color: var(--charcoal);
}
.mu-card__title {
	margin: 0; font-family: var(--font-head); font-weight: 700;
	font-size: 1.05rem; line-height: 1.3; letter-spacing: -.01em;
}
.mu-card__link { color: inherit; text-decoration: none; }
/* The stretched link. `position: relative` on the card is what scopes it. */
.mu-card__link::after { content: ""; position: absolute; inset: 0; }
.mu-card:hover .mu-card__title, .mu-card__link:focus-visible { color: var(--red); }
.mu-card__excerpt {
	margin: 0; font-size: .9rem; line-height: 1.6;
	color: color-mix(in srgb, var(--ink) 72%, transparent);
}
.mu-card__cta { margin-top: auto; padding-top: .7rem; font-size: .85rem; }

/* The lead card: picture left, copy right, and enough of both to be the thing
   you land on. Stacks under 780px, where a 45% column is a postage stamp. */
.mu-card--lead { flex-direction: row; align-items: stretch; }
.mu-card--lead .mu-card__media { flex: 0 0 45%; aspect-ratio: auto; }
.mu-card--lead .mu-card__body { padding: clamp(1.25rem, 3vw, 2.5rem); justify-content: center; gap: .55rem; }
.mu-card--lead .mu-card__title { font-size: clamp(1.3rem, 2.4vw, 1.9rem); }
.mu-card--lead .mu-card__excerpt { font-size: .97rem; }
@media (max-width: 780px) {
	.mu-card--lead { flex-direction: column; }
	.mu-card--lead .mu-card__media { flex: none; aspect-ratio: 16 / 9; }
}

.mu__archive-h {
	margin: clamp(2rem, 4vw, 3rem) 0 clamp(.9rem, 2vw, 1.4rem);
	font-family: var(--font-head); font-weight: 700; font-size: .74rem;
	letter-spacing: .1em; text-transform: uppercase; color: var(--charcoal);
}
.mu__grid {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: clamp(.9rem, 1.8vw, 1.35rem);
	grid-template-columns: repeat(3, 1fr);
}
.mu__cell { display: flex; }
@media (max-width: 980px) { .mu__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .mu__grid { grid-template-columns: 1fr; max-width: 520px; margin-inline: auto; } }

/* ===================== NEWS & UPDATES (Ambrosia) =====================
   Client, 2026-09-13: "three 'recent news' tiles, then it will be the older
   news archive like the market update archives, and then a recipes section."

   THERE IS ALMOST NO CSS HERE, WHICH IS THE POINT. The tiles are `.mu-card`s —
   the same component, not a copy of it — so this block only has to say how many
   of them stand in a row. `.news-archive` below it reuses `.mu__grid` outright
   and needs no rule at all. What is new on this page is the shelf of cookbook
   covers, and that is the only thing with a component of its own.

   THREE ACROSS AND NO LEAD CARD, unlike Market Updates. A leadership
   announcement, an award and a new hire are three unrelated pieces of company
   news; making whichever is newest twice the size of the other two would be a
   claim about importance that the dates do not support. See
   sfi_render_news_recent(). */
.news,
.news-archive,
.recipes { padding: var(--section-y) 0; }

.news__grid {
	list-style: none; margin: clamp(1.5rem, 3vw, 2.25rem) 0 0; padding: 0;
	display: grid; gap: clamp(.9rem, 1.8vw, 1.35rem);
	grid-template-columns: repeat(3, 1fr);
}
.news__cell { display: flex; }
.news-archive .mu__grid { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
@media (max-width: 980px) { .news__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .news__grid { grid-template-columns: 1fr; max-width: 520px; margin-inline: auto; } }

/* ---- the recipe booklets ----
   A SHELF, NOT A LIST OF LINKS. Each booklet is a designed object with its own
   typography and photography on the front, which is the whole reason the covers
   are worth the bytes; a row of titles would throw that away and leave six
   identical grey links to six different books.

   SIX ACROSS ON A WIDE SCREEN, which is more than this theme puts in a row
   anywhere else. A book cover is tall and narrow, so six of them at ~170px wide
   are still 220px high and legible — the same six at three across would be a
   screen and a half of scrolling for a section the client called a footnote to
   the page. It steps to four, three and two on the way down.

   NO CARD AROUND THEM. The cover already has a border, a colour and a shape; a
   white panel behind it would be a frame around a frame. The lift on hover is
   the whole tile, cover and caption together, so the object that moves is the
   book. */
.recipes__grid {
	list-style: none; margin: clamp(1.5rem, 3vw, 2.25rem) 0 0; padding: 0;
	display: grid; gap: clamp(1rem, 2.2vw, 1.75rem);
	grid-template-columns: repeat(6, 1fr);
}
.recipe { display: flex; }
.recipe__card {
	position: relative; display: flex; flex-direction: column; gap: .5rem;
	width: 100%;
	transition: transform .4s var(--ease);
}
.recipe:hover .recipe__card { transform: translateY(-4px); }
/* The box is the shape of the covers that are in it (roughly 1:1.3) rather than
   a ratio of its own, and `contain` rather than `cover`: these are not
   photographs, they are artwork with type on it, and cropping a millimetre off
   a cover cuts the title. The mist behind shows only where a cover is a
   slightly different shape from its neighbours. */
.recipe__cover {
	background: var(--mist); border-radius: var(--radius);
	overflow: hidden; box-shadow: var(--shadow-sm);
	aspect-ratio: 1 / 1.3;
	transition: box-shadow .4s var(--ease);
}
.recipe:hover .recipe__cover { box-shadow: var(--shadow-md); }
.recipe__img { display: block; width: 100%; height: 100%; object-fit: contain; }
.recipe__blank { display: block; width: 100%; height: 100%; background: var(--mist); }
.recipe__title {
	margin: 0; font-family: var(--font-head); font-weight: 700;
	font-size: .92rem; line-height: 1.35; letter-spacing: -.01em; text-wrap: pretty;
}
.recipe__link { color: inherit; text-decoration: none; }
/* Stretched link, scoped by `position: relative` on the card — one anchor for
   the whole tile, the same pattern as `.mu-card`. */
.recipe__link::after { content: ""; position: absolute; inset: 0; }
.recipe:hover .recipe__title, .recipe__link:focus-visible { color: var(--red); }
.recipe__meta, .recipe__ext {
	margin: 0; font-size: .78rem; line-height: 1.4;
	color: color-mix(in srgb, var(--ink) 65%, transparent);
}
.recipe__ext { margin-top: auto; color: var(--charcoal); }

@media (max-width: 1100px) { .recipes__grid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width:  780px) { .recipes__grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width:  520px) { .recipes__grid { grid-template-columns: repeat(2, 1fr); } }

@media (prefers-reduced-motion: reduce) {
	.recipe:hover .recipe__card { transform: none; }
}

/* No per-page cadence block, for the same reason Market Updates no longer has
   one: the BASE SECTION CADENCE already paints white → mist → white → linen off
   the hero anchor, and a per-page copy of the default is the pattern that let
   the tint creep back onto every new page. Only the footer valley needs naming,
   because it depends on how many bands this page turns out to have — and today
   that is TWO, the archive band being absent until there is a fourth story.

   Every line here carries `margin-top: 0` with its fill, which is the invariant
   stated at the top of this file: a tinted valley implies a flush footer. The
   odd-numbered endings (1, 3, 5 bands) are white and need no rule. */
.page-news:has(> .hero-anchor + section + section:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--mist); }
.page-news:has(> .hero-anchor + section + section + section + section:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--linen); }

/* The closing band on an update carries TWO things now: the sales line the
   client wrote for it and the route back to the rest of them. One band, not two
   — `.cta-band` is fixed furniture in the cadence (always last, always cream),
   so a second one would put two cream bands back to back at the foot of the
   page. The buttons wrap under the copy on a phone. */
.cta-band__body { margin: .55rem 0 0; max-width: 46ch; font-size: 1rem; line-height: 1.6; color: color-mix(in srgb, var(--ink) 82%, transparent); }
.cta-band__actions { display: flex; flex-wrap: wrap; gap: .75rem; }

/* ---- a single update's own page ---- */
/* SIZED TO THE COPY, NOT TO THE PAGE (2026-09-09). It ran the full 1200px wrap
   at 21:9 above an 86ch column of text, so the picture was half as wide again as
   anything under it — client: "the headline and peach image are super large".
   Matched to the body measure instead, so the update reads as one column; 16:9
   rather than 21:9 because at this width the letterbox had started to crop the
   fruit out of its own photograph. */
.single-post__lead {
	margin: 0 auto clamp(1.5rem, 4vw, 2.75rem);
	width: min(100% - 2 * var(--gutter), 52rem);
	border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-sm);
}
.single-post__lead-img { width: 100%; height: auto; display: block; aspect-ratio: 16 / 9; object-fit: cover; }

.mu__body {
	padding: 0 clamp(1rem, 2vw, 1.5rem) 1.35rem;
	/* Wider than .prose's 62ch: these run to 2,000 words with figures floated
	   into them, and a 62ch column leaves a floated 336px image nowhere to sit. */
	max-width: 78ch; line-height: 1.7;
}
/* On the update's OWN page there is no accordion frame holding this against the
   left edge, and a 78ch column pinned to the left of a 74rem wrap is what the
   client was looking at on 2026-09-02: "the text [is] all on the left side of
   the page — so can that be spread out." Centred, and given a little more room,
   since the floats want it. */
.mu__body--single { max-width: 86ch; margin-inline: auto; padding-inline: 0; }
.mu__body > *:last-child { margin-bottom: 0; }
.mu__body ul { margin: .35rem 0 1rem; padding-left: 1.1rem; list-style: disc; }
.mu__body li { margin-bottom: .35rem; }
.mu__body li::marker { color: color-mix(in srgb, var(--red) 60%, transparent); }
.mu__body p + p { margin-top: .85rem; }
.mu__body strong { color: var(--ink); }

/* THE BOILERPLATE AT THE FOOT OF A PRESS RELEASE (client, 2026-09-14: "for the
   full press release, can you change the 'About' sections to be smaller font —
   just regular bold").

   Every release the client issues ends the same way: a horizontal rule, then one
   "About <company>" block per organisation named in it. Those headings were
   arriving as <h2>, which on this site is clamp(1.9rem, 3.6vw, 2.9rem) — set at
   the size of a section headline, three times over, under the story. Boilerplate
   is the smallest print on a press release and it was the biggest type on the
   page.

   FOUND BY THE SEPARATOR, NOT BY A CLASS, and that is what makes it work on the
   next release nobody has written yet: the rule IS the divider between the story
   and the boilerplate, so `hr ~ h2` is "every heading after the story ended".
   The client pastes these in from Word and will not add a class.

   STILL AN <h2>. It is styled down to body-size bold, not demoted to a bold
   paragraph — these really are sections, and an outline that goes h1, h2, h2 is
   right for a reader moving by headings even when the type says fine print. The
   reference the client sent does use bold paragraphs; this looks identical and
   reads better.

   Matched on the bare element rather than on .wp-block-separator: the releases
   are seeded with the block class, but the next one may be pasted in as a plain
   <hr>, and a rule in a press release is this rule wherever it came from. */
.mu__body--release hr {
	border: 0; border-top: 1px solid rgba(28,28,28,.14);
	margin: 2.2rem 0 1.5rem;
}
.mu__body--release hr ~ h2 {
	font-family: var(--font-body);
	font-size: 1rem; font-weight: 700; line-height: 1.5;
	letter-spacing: 0; text-wrap: initial;
	margin: 1.5rem 0 .3rem;
}
/* The paragraphs under them come down with the headings — the whole block is
   the fine print, not just its labels. */
.mu__body--release hr ~ p {
	font-size: .92rem; line-height: 1.65;
	color: color-mix(in srgb, var(--ink) 72%, transparent);
}

/* The client's own editor floats figures left and right inside this copy, and
   the global reset sets `img { display:block }` — which kills the float's text
   wrap. Restore the three WordPress alignment classes HERE rather than
   sitewide, since this is the only place the site runs editor-authored HTML
   with images in it. Floats are dropped below 700px: a 336px image floated
   beside a 320px column is not a layout. */
.mu__body img { border-radius: var(--radius); margin: .35rem 0 1rem; }
.mu__body .alignleft  { float: left;  margin: .35rem 1.5rem 1rem 0; }
.mu__body .alignright { float: right; margin: .35rem 0 1rem 1.5rem; }
.mu__body .aligncenter { margin-inline: auto; }
.mu__body::after { content: ""; display: block; clear: both; }
@media (max-width: 700px) {
	.mu__body .alignleft, .mu__body .alignright {
		float: none; margin-inline: 0; width: 100%;
	}
}

/* ---- downloads & links ---- */
.res-links { padding: var(--section-y) 0; }
.res-links__grid {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: 1rem;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
}
.res-link__a {
	display: flex; align-items: flex-start; gap: .9rem; height: 100%;
	padding: clamp(1rem, 2.2vw, 1.35rem);
	background: var(--paper); border: 1px solid rgba(28,28,28,.09);
	border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
	text-decoration: none; color: var(--ink);
	transition: box-shadow .3s var(--ease), transform .3s var(--ease);
}
.res-link__a:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); color: var(--ink); }
.res-link__icon { width: 20px; height: 20px; flex: 0 0 auto; color: var(--red); margin-top: .15rem; }
.res-link__body { display: grid; gap: .2rem; }
.res-link__title { font-family: var(--font-head); font-weight: 700; font-size: 1rem; }
.res-link__a:hover .res-link__title { color: var(--red); }
.res-link__text { font-size: .92rem; color: var(--charcoal); }
.res-link__meta {
	font-family: var(--font-head); font-weight: 700; font-size: .7rem;
	letter-spacing: .08em; text-transform: uppercase; color: var(--charcoal);
	margin-top: .15rem;
}

/* ---- a single market update on its own page ---- */
.single-post .page-body { padding: var(--section-y) 0; }
.single-post .inner-hero__intro { font-family: var(--font-head); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; font-size: .8rem; }

/* ========================================================= company page (SFI)
   The Schreiber Foods International corporate homepage — client draft,
   2026-09-02. Three sections that exist only on that domain; the Ambrosia
   homepage never renders them (their fields are empty there). */

/* ---- the five operational pillars ----
   Two-up, so the fifth sits alone on the last row. That is the shape the copy
   asks for: these are 250–400 character paragraphs, and five columns of a
   1200px wrap is 220px each — about thirty lines apiece. The numeral is set
   large and pale so it reads as structure rather than as a competing headline,
   and it is aria-hidden: <ol> already tells a screen reader the order. */
.pillars { padding: var(--section-y) 0; background: var(--linen); }
.pillars__list {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: clamp(1.5rem, 3vw, 2.5rem);
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
}
.pillar { display: flex; align-items: flex-start; gap: clamp(.9rem, 2vw, 1.4rem); }
.pillar__num {
	font-family: var(--font-head); font-weight: 800;
	font-size: clamp(1.6rem, 3vw, 2.3rem); line-height: 1;
	color: color-mix(in srgb, var(--red) 28%, transparent);
	flex: 0 0 auto; letter-spacing: -.03em;
}
.pillar__body { min-width: 0; }
.pillar__title { font-size: clamp(1.15rem, 1.9vw, 1.45rem); margin: 0 0 .4rem; }
.pillar__text { margin: 0; color: color-mix(in srgb, var(--ink) 82%, transparent); }

/* ---- …and the same five as PICTURE TILES (client, 2026-09-03: "can the tiles
   be laid out with photos like the chef/distributor pages").
   The tile itself is a .benefit-card — literally the class those pages use, so
   the radius, shadow, hover lift and image zoom cannot drift from them. Only
   two things are new: the grid (five tiles, not two or three) and the numeral.

   THE GRID IS SIX COLUMNS, tiles spanning two, so three sit on the first row
   and the remaining two centre themselves under them. A five-item row in a
   three-column grid otherwise leaves a hole at the end, and a five-item row in
   the .benefits two-column grid leaves the fifth stranded — the same problem
   the numbered list lived with, which the pictures make impossible to ignore.
   The two centring rules are guarded with :nth-last-child so they fire ONLY on
   a set of exactly five; a sixth pillar added in the admin falls back to plain
   three-up rather than to a broken row. */
.pillars__list--cards {
	display: grid; gap: clamp(1.5rem, 3vw, 2.5rem);
	grid-template-columns: repeat(6, 1fr);
}
.pillars__list--cards > .pillar-card { grid-column: span 2; }
.pillars__list--cards > .pillar-card:nth-child(4):nth-last-child(2) { grid-column: 2 / span 2; }
.pillars__list--cards > .pillar-card:nth-child(5):nth-last-child(1) { grid-column: 4 / span 2; }
/* The photo box is 16/10 and clipped, so the badge sits inside it and the image
   zoom on hover runs underneath. */
.pillar-card__media { position: relative; }
/* The ordinal, kept from the numbered list it replaced: the copy above says
   "five important pillars", and this is the only thing on a picture tile that
   says which one this is. aria-hidden — the <ol> already carries the order. */
.pillar-card__num {
	position: absolute; left: 0; bottom: 0;
	font-family: var(--font-head); font-weight: 800; letter-spacing: -.02em;
	font-size: clamp(1.25rem, 2.1vw, 1.7rem); line-height: 1;
	color: #fff; background: var(--red);
	padding: .5rem .75rem .45rem;
	border-top-right-radius: var(--radius);
}
@media (max-width: 1000px) {
	.pillars__list--cards { grid-template-columns: repeat(4, 1fr); }
	.pillars__list--cards > .pillar-card,
	.pillars__list--cards > .pillar-card:nth-child(4):nth-last-child(2) { grid-column: auto / span 2; }
	.pillars__list--cards > .pillar-card:nth-child(5):nth-last-child(1) { grid-column: 2 / span 2; }
}
@media (max-width: 700px) {
	.pillars__list--cards { grid-template-columns: 1fr; }
	.pillars__list--cards > .pillar-card,
	.pillars__list--cards > .pillar-card:nth-child(4):nth-last-child(2),
	.pillars__list--cards > .pillar-card:nth-child(5):nth-last-child(1) { grid-column: 1 / -1; }
}

/* ---- brands ----
   The doc sketches this as "(Diagram with logos)". It is built as structure,
   not as a picture: eleven brand names inside an image would be invisible to a
   search engine, an assistant and a screen reader, and those names are exactly
   what a distributor types into a search box. */
.brands { padding: var(--section-y) 0; }
/* THE PYRAMID (client, 2026-09-03: "you can add the SFI logo there again with
   the main copy, so it sort of mimics a pyramid"). Three tiers, narrowest
   first: the company's mark, then the primary brand on a panel capped well
   inside the wrap, then the eleven distributed brands filling it. The centring
   here is structural — an apex only reads as an apex over the middle of what it
   opens — which is why the section head takes .section-head--center and the
   list label follows it. */
.brands__crest { display: flex; justify-content: center; margin: 0 0 clamp(1rem, 2.4vw, 1.6rem); }
.brands__crest-img {
	width: auto; max-width: min(100%, 16rem);
	height: clamp(34px, 3.6vw, 46px);
	object-fit: contain; display: block;
}
.brands__label--list { text-align: center; }
.brands__label {
	font-family: var(--font-head); font-weight: 700; font-size: .74rem;
	letter-spacing: .16em; text-transform: uppercase; color: var(--charcoal);
	margin: 0 0 .5rem;
}
.brands__label--list { margin: clamp(2rem, 4vw, 3rem) 0 1rem; }
.brands__primary {
	display: grid; gap: clamp(1.25rem, 3vw, 2.5rem); align-items: center;
	grid-template-columns: minmax(0, 1fr);
	/* Middle tier: narrower than the brand grid below it, wider than the mark
	   above. Without the cap all three tiers are 1200px and there is no pyramid. */
	max-width: 60rem; margin-inline: auto;
	background: var(--cream); border-radius: var(--radius-lg);
	padding: clamp(1.5rem, 3.5vw, 2.5rem);
}
@media (min-width: 800px) {
	.brands__primary { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); }
}
.brands__primary-media { display: flex; justify-content: center; }
.brands__primary-logo { width: min(100%, 15rem); height: auto; }
.brands__primary-text { margin: 0; font-size: clamp(1rem, 1.5vw, 1.12rem); line-height: 1.6; }

.brands__grid {
	list-style: none; margin: 0; padding: 0;
	display: grid; gap: .75rem;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
}
.brand {
	display: flex; flex-direction: column; align-items: center; justify-content: center;
	gap: .7rem; min-height: 5.5rem; padding: 1rem;
	background: var(--paper); border: 1px solid rgba(28,28,28,.09);
	border-radius: var(--radius); text-align: center;
}
/* The name is ALWAYS printed, logo or no logo — it is the searchable half of
   the tile. Alone it stands in for missing artwork, set in the brand face so a
   grid of names still reads as a line-up of brands; under a logo it steps back
   to a caption so the mark leads and the two do not compete. */
.brand__name {
	font-family: var(--font-head); font-weight: 700; font-size: .98rem;
	letter-spacing: -.01em; line-height: 1.3; color: var(--ink);
}
.brand--has-logo .brand__name {
	font-size: .78rem; font-weight: 600; letter-spacing: .02em;
	color: color-mix(in srgb, var(--ink) 62%, transparent);
}
/* Fixed mark box, so a 1:1 roundel and a 7:1 wordmark occupy the same optical
   space and the row of tiles keeps one baseline. The supplied artwork is a
   mixed bag — transparent PNGs alongside JPEGs with white and black grounds —
   so nothing here assumes transparency. */
.brand__mark {
	display: flex; align-items: center; justify-content: center;
	width: 100%; max-width: 11rem; height: 3.5rem;
}
.brand__logo { max-height: 100%; max-width: 100%; width: auto; height: auto; object-fit: contain; }

/* ========================================================= our people (SFI)
   Client draft, 2026-09-04 ("About Us SFI.docx"). Split hero → the CEO Council
   → the co-founder → the team photograph.

   NO CADENCE BLOCK HERE ON PURPOSE. The hero is fixed furniture (cream) and the
   BASE SECTION CADENCE block then runs the rest positionally: council WHITE →
   founder mist → team WHITE → footer curve white, which is exactly the two
   rules the client has asked for four times. Declaring the same sequence again
   under `.page-people` is what re-creates the drift. Nothing in this block sets
   a section background — see the note at the top of BASE SECTION CADENCE.
   ===================================================================== */
.council,
.founder,
.team { padding: var(--section-y) 0; }

/* ---- the hero ----
   THE SPLIT HERO IS GONE FROM THIS PAGE and the rule that used to sit here went
   with it (it bought the banner photograph width by taking height off the
   frame). Client, 2026-09-04: "Our People can follow the SFI hero format, just
   the copy intro that dives into the rest, no photo of them with the banner."
   Nothing to style — .inner-hero is shared and this page wants it at its normal
   size, because unlike Market Updates it has a real paragraph in the hero. */

/* ---- the three council cards ----
   Three-up on desktop, two-up in the middle, one on a phone. minmax(0,1fr)
   rather than auto: a long unbroken title in one card would otherwise widen its
   track and knock the other two out of step. */
.council__list {
	list-style: none; margin: 2.5rem 0 0; padding: 0;
	display: grid; gap: clamp(1.25rem, 2.5vw, 2rem);
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
	align-items: start;
}
.bio-card {
	background: var(--paper); border: 1px solid rgba(28,28,28,.08);
	border-radius: var(--radius-lg); overflow: hidden;
	box-shadow: var(--shadow-sm);
	display: flex; flex-direction: column;
}
/* ---- the framed portrait ----
   Client, 2026-09-04: "I did promise him we could keep the photos on the smaller
   side. I love the way it looks as is, but he may balk at the size. Maybe one
   idea is to put a title frame around them to shrink the size."

   That is exactly the right instinct and it is why this is a HEADER ROW, not a
   smaller version of the old block. A full-bleed square at the top of the card
   was the photograph, and the name was its caption; framed beside the name the
   photograph becomes an identifier — a quarter of the visual weight for the same
   information, and it reads as a decision rather than as a picture someone
   shrank. Whoever is sensitive about their photograph is much less exposed here,
   and the page did not lose anything.

   Fixed rem rather than a fraction of the card: three cards of different widths
   must not produce three different portrait sizes down the row. */
.person__head {
	display: flex; align-items: center; gap: clamp(.85rem, 1.8vw, 1.15rem);
	padding: clamp(1.1rem, 2vw, 1.4rem);
	background: var(--cream);
	border-bottom: 1px solid rgba(28,28,28,.07);
}
.person__media {
	flex: 0 0 auto; width: clamp(4.6rem, 8vw, 5.6rem); aspect-ratio: 1 / 1;
	background: var(--paper); overflow: hidden;
	/* The frame itself: a white mat, a hairline and a little lift. */
	padding: .28rem; border: 1px solid rgba(28,28,28,.12); border-radius: 10px;
	box-shadow: var(--shadow-sm);
	display: flex; align-items: center; justify-content: center;
}
.person__img {
	width: 100%; height: 100%; object-fit: cover; object-position: center 22%;
	border-radius: 7px;
}
.person__id { min-width: 0; }
/* A MONOGRAM, NOT A MISSING PHOTOGRAPH — and that intent flipped on 2026-09-09.
   This tile used to be deliberately quiet, greyed and low-contrast, so it read
   as "no photo yet" beside two real headshots. That was right while a photograph
   was still expected. It stopped being right the day the CFO decided there would
   never be one and the rest of the council agreed to it: a tile that announces an
   absence is exactly what made the agency call it "sketchy", because it looks
   like the site failed rather than like a person chose.

   So it is now a monogram someone would have commissioned: the brand red at full
   strength on cream, wide-tracked, in the same white mat and hairline as the two
   photographs beside it. Same frame, same weight in the row, different medium —
   which is a design decision, and reads as one. Nothing else about the card
   changes, so a photograph dropped into the field later still wins outright. */
.person__initials {
	font-family: var(--font-head); font-weight: 700;
	font-size: clamp(1.3rem, 2.7vw, 1.7rem); letter-spacing: .09em; text-indent: .09em;
	color: var(--red);
	background: var(--cream);
	box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 18%, transparent);
	width: 100%; height: 100%; border-radius: 7px;
	display: flex; align-items: center; justify-content: center;
}
.person__body {
	padding: clamp(1.1rem, 2vw, 1.5rem);
	display: flex; flex-direction: column; align-items: flex-start; flex: 1;
}
.person__name {
	margin: 0; font-family: var(--font-head); font-weight: 700;
	font-size: 1.12rem; line-height: 1.25; letter-spacing: -.01em;
	overflow-wrap: anywhere;
}
.person__title {
	margin: 0; font-family: var(--font-head); font-weight: 600;
	font-size: .78rem; letter-spacing: .08em; text-transform: uppercase;
	color: var(--red);
}
.person__title + .person__lede,
.person__lede { margin: 0; font-size: .95rem; line-height: 1.65; color: color-mix(in srgb, var(--ink) 82%, transparent); }
.person__title { margin-top: .2rem; }

/* ---- "click for more" ----
   A native <details>, so a bio opens with JavaScript off and its state is
   announced. Same component for the three council bios and for the Founder's
   Statement; the marker has to be removed by hand before <summary> can be
   styled as the control it reads as. */
.bio-more { margin-top: auto; padding-top: .9rem; width: 100%; }
.bio-more__summary {
	display: inline-flex; align-items: center; gap: .45rem; cursor: pointer;
	list-style: none;
	font-family: var(--font-head); font-weight: 700; font-size: .82rem;
	letter-spacing: .06em; text-transform: uppercase; color: var(--red);
}
.bio-more__summary::-webkit-details-marker { display: none; }
.bio-more__summary::marker { content: ""; }
.bio-more__summary:hover .bio-more__label { text-decoration: underline; }
.bio-more__summary:focus-visible { outline: 3px solid var(--red); outline-offset: 3px; border-radius: 4px; }
/* Empty content — <details> already announces open/closed, so the chevron is
   decoration and never the thing carrying the state. */
.bio-more__chevron {
	width: .42rem; height: .42rem; flex: 0 0 auto;
	border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
	transform: rotate(45deg) translate(-.06rem, -.06rem);
	transition: transform .25s var(--ease);
}
.bio-more[open] .bio-more__chevron { transform: rotate(-135deg) translate(-.06rem, -.06rem); }
.bio-more__body { margin-top: .9rem; max-width: none; }
.bio-more__body > :first-child { margin-top: 0; }
.bio-more__body > :last-child { margin-bottom: 0; }
.bio-more__body p { font-size: .95rem; line-height: 1.65; }

/* ---- the co-founder, as an HONOUR SECTION ----
   Client, 2026-09-04: "let's make Howard's photo at least as big as everyone
   else's (this will be a win...). Can we jazz up that section a bit so it looks
   like an honor section in some way. Minor, can you add quotes around his
   statement, and since we are changing the hero maybe the whole statement is
   visible?"

   Four asks, one change. The council portraits became framed thumbnails in the
   same pass, so this one is now roughly three times the size of any other
   photograph of a person on the page without a single number changing here.
   The rest is a plaque: a white panel lifted off the mist band with a red rule
   across the top, the portrait framed the same way as the council's but large,
   and the name and title engraved under it behind a hairline. Nothing here is
   ornament for its own sake — every part of it is the card treatment the page
   already uses, at the one size that says "this person is the exception".

   The panel is WHITE, not cream. Cream and mist are six values apart; the
   plaque has to lift off the band it sits on or it is not a plaque. */
.founder__head { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }
.founder__panel {
	background: var(--paper); border-radius: var(--radius-lg);
	border-top: 4px solid var(--red);
	box-shadow: var(--shadow-md);
	padding: clamp(1.6rem, 3.5vw, 3rem);
}
.founder__wrap {
	display: grid; gap: clamp(1.5rem, 4vw, 3rem);
	grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
	align-items: start;
}
.founder__media { margin: 0; }
/* The same frame as .person__media, three times the size. */
.founder__frame {
	aspect-ratio: 1 / 1; overflow: hidden; background: var(--paper);
	padding: .45rem; border: 1px solid rgba(28,28,28,.12);
	border-radius: var(--radius); box-shadow: var(--shadow-sm);
	display: flex; align-items: center; justify-content: center;
}
.founder__frame .person__img,
.founder__frame .person__initials { border-radius: calc(var(--radius) - 6px); }
.founder__frame .person__initials { font-size: clamp(2.4rem, 5vw, 3.4rem); }
/* The engraving under the portrait. */
.founder__plate {
	margin-top: .9rem; padding-top: .8rem;
	border-top: 1px solid rgba(28,28,28,.14);
	display: flex; flex-direction: column; gap: .25rem; text-align: center;
}
.founder__name {
	font-family: var(--font-head); font-weight: 700;
	font-size: 1.18rem; line-height: 1.25; letter-spacing: -.01em;
}
.founder__role { display: block; }
.founder__lede { margin: 0 0 1.4rem; font-size: 1.05rem; line-height: 1.7; max-width: 62ch; color: color-mix(in srgb, var(--ink) 82%, transparent); }

/* The statement is OPEN ON THE PAGE now — no disclosure — and in real quotation
   marks. The marks are generated content on the first and last paragraph rather
   than characters typed into the field: an editor cannot then end up with one of
   them, or three, and the field stays plain prose to edit. */
.founder__statement {
	margin: 0; padding: .1rem 0 0 clamp(1.1rem, 2.5vw, 1.8rem);
	border-left: 3px solid var(--red);
	max-width: 66ch;
}
.founder__statement p { font-size: 1.02rem; line-height: 1.78; }
.founder__statement p:last-child { margin-bottom: 0; }
.founder__statement > p:first-of-type::before { content: "\201C"; }
.founder__statement > p:last-of-type::after { content: "\201D"; }
.founder__attrib {
	margin: 1rem 0 0; padding-left: clamp(1.1rem, 2.5vw, 1.8rem);
	font-family: var(--font-head); font-weight: 700;
	font-size: .82rem; letter-spacing: .06em; text-transform: uppercase; color: var(--charcoal);
}

/* ---- our team ----
   The group photograph is not full-bleed and it is no longer full CONTENT width
   either. Client, 2026-09-04: "the team photo can be smaller too." Capped and
   centred rather than cropped — it is a forty-person shot and a banner crop is
   what cuts the people off each end of the row, which on this page of all pages
   is the wrong trade. Narrower frame, same whole picture. */
/* The team photograph is a `.story__media` now (2026-09-08) and takes that
   component's sizing, shadow and radius. What survives here is the ONE rule
   that was specific to it: no forced aspect ratio was already true of
   `.story__img`, so the picture still gets shorter rather than cropped. */
.team__caption { margin-top: .7rem; font-size: .84rem; color: var(--charcoal); }
.team__actions-intro { margin: 0 0 1.1rem; font-size: 1.02rem; color: color-mix(in srgb, var(--ink) 82%, transparent); }
/* Centred only while the block has no photograph and `.story--solo` centres
   the column around it. */
.story--solo .team__caption { text-align: center; }

/* ---- Our People: the lede moved out of the hero (2026-09-08) ----
   The hero now carries only an eyebrow and an H1, so it takes the same trim
   Contact and Market Updates took for the same reason — otherwise .inner-hero
   spends its full padding rendering two words over a decorative curve. */
/* Left-aligned at a reading measure — a page lede, not a centred introduction
   to what follows, which is the section head's own job.

   RENAMED FROM `.people__lede` AND GIVEN A SECOND COLUMN, 2026-09-09. It was
   always shared with Food Sourcing & Safety and the old note here said to rename
   it if a third page took it; the rename came early because the component
   changed shape. Client: "on our people and food sourcing … it seems like a lot
   of empty plain space in the hero. It seems to be the intro copy that is
   floating in some blank space." Both ledes were one short paragraph alone
   across a 1200px band — 204 characters on Our People, EIGHTY-SIX on Food
   Sourcing — so the emptiness was not a spacing value anywhere, it was a column
   with nothing in the other one. See sfi_render_page_lede() for what went in it
   and why it had to be the page's own content. */
.page-lede {
	margin: 0 0 clamp(2rem, 4vw, 3rem);
}
.page-lede__text {
	margin: 0;
	max-width: 62ch;
	font-size: clamp(1.05rem, 1.7vw, 1.2rem); line-height: 1.7;
	color: color-mix(in srgb, var(--ink) 86%, transparent);
	text-wrap: pretty;
}
/* The council's head follows a lede now, so it does not also need the top
   margin it carries when it is the first thing in a band. */
.page-lede + .section-head { margin-top: 0; }

@media (min-width: 900px) {
	/* The copy column is the NARROWER one, which is the opposite of the usual
	   split and is the point: the aside is a photograph or a row of marks and
	   wants room, while the lede is two or three lines and reading past 62ch is
	   worse, not better.

	   `center`, which is what `.story` and `.sourcing__grid` already do for the
	   same pairing. The two columns here are very different heights — a
	   sentence and a half against two rows of certification marks, three lines
	   against a 290px photograph — and top-aligning them puts the whole
	   difference in one hole under the text, which is the shape of the problem
	   this band was built to fix. Centred, it splits above and below and reads
	   as a margin. */
	.page-lede--split {
		display: grid;
		grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
		gap: clamp(2rem, 4.5vw, 3.5rem);
		align-items: center;
	}
	.page-lede--split .page-lede__text { max-width: 46ch; }
}
/* Stacked, the aside stays SECOND — the sentence is still the introduction to
   the page, and a photograph above it would make the lede a caption. */
.page-lede__aside { margin-top: clamp(1.5rem, 4vw, 2.25rem); }
@media (min-width: 900px) { .page-lede__aside { margin-top: 0; } }

/* The two asides in use, each shedding the spacing it carried where it used to
   live. The cert marks were the last thing in a section and had a rule above
   them separating them from the copy; here they ARE the column, so the rule and
   its padding go. The team photograph was a grid cell in `.story`, which gave it
   its own margins. */
.page-lede__aside .cert-marks {
	margin: 0; padding: 0; border-top: 0;
	justify-content: flex-start; gap: clamp(.9rem, 2vw, 1.4rem);
}
.page-lede__aside .cert-marks__item { width: clamp(6rem, 10vw, 7.5rem); }
.page-lede__aside .story__media { margin: 0; }

/* =====================================================================
   THE SCHREIBER LEGACY TIMELINE
   Client's brief, 2026-09-08: "SFI logo anchored in the center, with the
   pre-1980 on one side and the post-1980 on the other… like a sort of vintage
   scrapbook feel (see example) maybe as a subtle background. We can have some
   fun with this page!"

   The reference was an album page — white-bordered prints laid down at angles
   over patterned paper. Taken literally on a company history that reads as a
   craft project, and she asked for it subtle, so the scrapbook lives in three
   places only: the paper ground, the tilted mats, and photo corners on the
   entries she chooses to feature. Type, palette and spacing stay the site's.
   ===================================================================== */

/* ---- the generations band, above the timeline (client, 2026-09-11) ----
   "the first generation on the left, the SFI logo and same 'founded' text in
   the middle, and the next generation on the right."

   THREE EQUAL COLUMNS SINCE 2026-09-13, and that is a reversal. The middle
   column used to be the narrow one, on the argument that two photographs the
   same size with a small mark between them reads as a sentence — "these two
   people, and the company they made" — where three equal columns read as "here
   are three things". The client looked at it and asked for the mark "the same
   or close to the same size" as the photographs, with a caption under it like
   theirs; the sentence is now carried by the three captions instead (THE FIRST
   GENERATION / FOUNDED / THE NEXT TWO GENERATIONS), which says it in words
   rather than in column widths. minmax(0,…) so a wide replacement photograph
   still cannot widen its own column.

   THIS BAND TAKES THE CADENCE'S WHITE and declares no background of its own —
   it is the first band after the hero anchor, which is the one the client has
   asked to be white on every page. The pin below is for the band UNDER it. */
.generations { padding: var(--section-y) 0; }
.generations__row {
	margin-top: clamp(1.75rem, 4vw, 2.75rem);
	display: grid; align-items: center;
	gap: clamp(1.5rem, 4vw, 3rem);
	grid-template-columns: repeat(3, minmax(0, 1fr));
}
.generations__panel { margin: 0; }
/* 4:5, AND A FIXED RATIO HERE IS RIGHT WHERE IT IS WRONG ON THE TIMELINE BELOW.
   Two panels facing each other across a mark have to match each other or the row
   reads as broken, so one of them has to give up its own shape.

   IT WAS SQUARE UNTIL THE ANUGA CROP (2026-09-13), which is what settled it.
   Square cost nothing while both files were square; cropping the trade-fair
   branding out of the Howard and Emily photograph left 564px of usable width
   against 600px of people — they are standing full length, and no square that
   clears the backdrop can hold both his shoes and the top of his head. So the
   pair goes portrait and Norman and Helen's square is the one that gives: a
   tenth off each side of a picture that is two faces in the middle of the
   frame. The band is taller for it, which is the price of the crop. */
.generations__img {
	display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
	border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
}
/* Caption as two stacked lines rather than a sentence: the kicker names the
   generation, the line under it names the people. Centred under the picture,
   because the picture above it is centred in its own column. */
.generations__cap {
	display: grid; gap: .3rem; justify-items: center; text-align: center;
	margin-top: clamp(.8rem, 1.8vw, 1.1rem);
}
.generations__kicker {
	font-family: var(--font-head); font-weight: 700;
	font-size: .78rem; letter-spacing: .12em; text-transform: uppercase;
	color: var(--red);
}
.generations__name {
	font-family: var(--font-head); font-weight: 700;
	font-size: clamp(1rem, 1.6vw, 1.12rem); line-height: 1.35; color: var(--ink);
	text-wrap: balance;
}
/* The centre. STILL NO CARD AND NO BORDER, even though it is now a full column:
   the mark is the third object in the row, not a third tile, and giving it the
   photographs' rounded corners and shadow would make it one. What it shares with
   them instead is the caption — `.generations__cap` and its two lines are the
   same component here as under the photographs, which is what the client asked
   for ("a caption like the photo captions") and is why there is no third rule
   for it below. */
.generations__centre { margin: 0; display: grid; justify-items: center; text-align: center; }
/* AS WIDE AS ITS COLUMN, which is what "the same size as the photos" means for a
   4:1 horizontal lockup: the same footprint across, not the same area. The cap
   is above the 368px a third of the wrap comes to, so in practice the mark fills
   its column; it is there so a wider wrap could never stretch a letterhead scan
   into a banner. The `sizes` in the renderer is written against this number. */
.generations__mark { display: block; width: 100%; max-width: 24rem; height: auto; }

@media (max-width: 860px) {
	/* Stacked, the centre goes FIRST. The markup order is left → centre → right
	   so this is the reading order already; the point of saying it here is that
	   the founding is the thing the two portraits are either side of, and a
	   single column has no "either side" left to express it. */
	.generations__row { grid-template-columns: 1fr; gap: clamp(1.75rem, 6vw, 2.5rem); }
	.generations__centre { order: -1; }
}

.timeline { padding: var(--section-y) 0; position: relative; overflow: hidden; }
/* THE TIMELINE KEEPS ITS OWN PAPER WHATEVER POSITION IT IS IN (2026-09-11).
   Before the generations band went in, the timeline was the first section after
   the hero anchor and the BASE SECTION CADENCE painted it `--paper` — which is
   what `.timeline::before` has always laid its scrapbook wash over. Adding a
   band above it moved it to second place, where the cadence paints `--mist`,
   and the warm wash over mist is a muddier and quite different page.

   So this is a per-page deviation and NOT the anti-pattern the cadence block
   warns about: it is not a copy of the default, it is the one band on this site
   whose ground is part of its design rather than a position in an alternation.
   Two classes, (0,2,0), which is where the sandwich puts an approved override.
   The footer valley is filled to match, further up this file. */
.page-legacy .timeline { background: var(--paper); }
/* The paper. Two very low-contrast layers — a warm wash and a fine diagonal
   weave — so it reads as stock rather than as a pattern. No image: at this
   contrast a texture file would be 200kb of noise nobody can see. */
.timeline::before {
	content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
	background-image:
		repeating-linear-gradient(45deg,
			color-mix(in srgb, var(--beige) 13%, transparent) 0 1px,
			transparent 1px 7px),
		radial-gradient(120% 80% at 50% 0%,
			color-mix(in srgb, var(--cream) 70%, transparent) 0%, transparent 70%);
}
.timeline > .wrap { position: relative; z-index: 1; }

/* ---- the spine ---- */
.tl {
	list-style: none; margin: clamp(2rem, 5vw, 3.5rem) 0 0; padding: 0;
	position: relative;
	display: grid; gap: var(--tl-gap);
	/* Client, 2026-09-21: "there is a lot of empty space and a lot of
	   scrolling… can you also tighten the space in between events — the left
	   side doesn't have to be totally out of view before the right side event
	   appears, they can be just slightly staggered and that way you see more on
	   the page at a time."

	   Two numbers do that: a smaller gap, and a NEGATIVE top margin that pulls
	   each card up into the previous one's row. Consecutive entries alternate
	   sides, so a card and the one it overlaps are never in the same column and
	   cannot collide — they simply interleave, which is what "staggered" means
	   here.

	   THE ONE PAIR THAT CAN COLLIDE IS N AND N+2, which share a column, and the
	   bound is arithmetic rather than taste. Their tops are h(n+1) + 2·gap −
	   2·stagger apart, so nothing overlaps as long as

	       stagger ≤ (shortest card + 2 × gap) ÷ 2

	   PUSHED TO THE CEILING ON 2026-09-21, second pass: "does look tighter, I
	   would suggest even less space in between, so the right side comes up
	   halfway to the left side (or so)."

	   HALFWAY IS THE LIMIT ITSELF, not a setting short of it. If every entry
	   overlaps the one before it by half a card, then a column's two neighbours
	   are exactly one card apart and they touch — and they only touch rather
	   than collide if every card is the same height. These run 434px to 738px,
	   so the bound above, evaluated against the real cards, puts the ceiling at
	   237px. 13rem is 208px, about 12% under it: the next entry now starts
	   roughly two thirds of the way down the one before it (was ~86%), and the
	   shortest pair on the page still clears by 29px.

	   MEASURE BEFORE MOVING IT. The ceiling is a property of the CONTENT, not of
	   this file — a row losing its photograph, or a card being cut, lowers it.
	   There is an estimator for exactly this in the session notes; what it needs
	   is every card's height, and the rule is stagger ≤ (shortest + 2 × gap) ÷ 2. */
	--tl-gap: clamp(.9rem, 2vw, 1.25rem);
	--tl-stagger: clamp(3rem, 11vw, 13rem);
}
/* Adjacent side-entries only. The medallion and the closing block are not
   `.tl__item`, so the `+` breaks the chain at both of them by itself — neither
   is ever pulled into a card, and no card is ever pulled into them. */
.tl__item + .tl__item { margin-top: calc(-1 * var(--tl-stagger)); }
/* The line itself, stopping short at both ends so it reads as a spine rather
   than as a border on the section. */
.tl::before {
	content: ""; position: absolute; top: .5rem; bottom: .5rem; left: 50%;
	width: 2px; margin-left: -1px; border-radius: 2px;
	background: linear-gradient(to bottom,
		transparent 0,
		color-mix(in srgb, var(--beige) 85%, transparent) 4rem,
		color-mix(in srgb, var(--beige) 85%, transparent) calc(100% - 4rem),
		transparent 100%);
}
/* AND IT STOPS ABOVE THE CLOSING BLOCK (client, 2026-09-21: "In the 2027 text,
   the brown line is going into the text so they asked to have that shortened
   out of the copy").

   `.tl__coda` is centred and full width, so the spine ran straight down the
   middle of it — between the words, which is worse than through them. The
   reserve below is generous rather than exact: the closing block is an era
   line and one short paragraph, about 160px, and 11rem leaves room for it to
   grow. Err on the high side, because too much reserve only ends the line a
   little early and the gradient's own 4rem fade hides where. */
.tl:has(> .tl__coda)::before { bottom: clamp(8rem, 14vw, 11rem); }

.tl__item { display: grid; grid-template-columns: 1fr 1fr; }
.tl__item--left  > .tl-card { grid-column: 1; margin-right: clamp(1.75rem, 4vw, 3rem); }
.tl__item--right > .tl-card { grid-column: 2; margin-left: clamp(1.75rem, 4vw, 3rem); }

/* The dot on the spine, on the card's inner edge. */
.tl-card { position: relative; }
.tl-card::after {
	content: ""; position: absolute; top: 1.9rem;
	width: 11px; height: 11px; border-radius: 50%;
	background: var(--paper);
	box-shadow: 0 0 0 2px var(--red);
}
.tl__item--left  > .tl-card::after { right: calc(-1 * clamp(1.75rem, 4vw, 3rem) - 5.5px); }
.tl__item--right > .tl-card::after { left:  calc(-1 * clamp(1.75rem, 4vw, 3rem) - 5.5px); }

/* ---- an entry ---- */
.tl-card {
	background: var(--paper);
	border: 1px solid color-mix(in srgb, var(--beige) 55%, transparent);
	border-radius: var(--radius);
	padding: clamp(1.15rem, 2.4vw, 1.6rem);
	box-shadow: var(--shadow-sm);
}
/* THE YEAR IS THE THING PEOPLE SCAN FOR, so it is bigger than a kicker of this
   kind would normally be (client, 2026-09-22: "she asked if the red font for the
   years can be somewhat bigger"). .8rem → .95rem, which is up 19% and still
   under the card's own body copy (.97rem) — a timeline entry is dated, not
   titled by its date, and a year that outweighs the heading beside it inverts
   the card. The coda's era line moves with it: they are the same thing read
   twice and a page where only one of them grew looks like a mistake. */
.tl-card__era {
	margin: 0 0 .35rem;
	font-family: var(--font-head); font-weight: 700;
	font-size: .95rem; letter-spacing: .12em; text-transform: uppercase;
	color: var(--red);
	font-variant-numeric: tabular-nums;
}
/* `text-wrap: pretty`, NOT THE GLOBAL `balance` (client, 2026-09-23: "since they
   are paying attention to headlines, it looks like some have a wide right margin
   and others don't").

   Measured off their own screenshot before changing anything, on the 2004
   warehouse card: the body copy reaches x=1036 of a content box that ends at
   ~1040, and the heading's longest line stops at x=917. **119px — 11% of the
   measure — of right margin that the paragraph directly beneath it does not
   have.** That is the "wide right margin", and the cards that do not have it are
   the ones whose heading fits on one line.

   IT IS `h1, h2, h3… { text-wrap: balance }` DOING EXACTLY ITS JOB. Balance
   minimises the LONGEST line, so it pulls a two-line heading in from the right
   on purpose. That is right for a display heading standing alone — a hero H1, a
   centred section head — and wrong here, where a 1.1rem heading sits directly on
   top of body copy at the same measure and the mismatch is what you see.

   `pretty` fills the measure the way the body does and still protects the last
   line from an orphan, so the heading and the paragraph now rag the same way.
   Scoped to the timeline card title deliberately: the global default stays, and
   the pivot and coda titles keep `balance` because they are CENTRED, where
   balance is the right answer. */
.tl-card__title { margin: 0 0 .55rem; font-size: clamp(1.02rem, 1.7vw, 1.18rem); line-height: 1.3; text-wrap: pretty; }
/* A row's copy can be more than one paragraph — see sfi_tl_paragraphs(), and the
   1985 fire, which is the second paragraph of the Distributor Partners row. */
.tl-card__body { margin: 0 0 .8rem; font-size: .97rem; line-height: 1.7; color: color-mix(in srgb, var(--ink) 86%, transparent); text-wrap: pretty; }
.tl-card__body:last-of-type { margin-bottom: 0; }

/* ---- the print in its mat ---- */
.tl-card__figure {
	/* SMALLER PRINTS (client, 2026-09-21: "a lot of the images are really large
	   and can be trimmed down in size so they are more like the 2015 sizes").

	   IT IS A HEIGHT CAP, EXPRESSED AS A WIDTH. The pictures on this page are
	   documents as often as they are photographs — a 1953 moving announcement, a
	   1997 office-warming invitation, two label scans — so `max-height` plus
	   `object-fit: cover` is not available here: on a document, cropping means
	   deleting the words. But `--tl-ratio` is a plain number printed on the
	   figure from the picture's own dimensions, so `ratio × height` IS the width
	   at which that picture is exactly that tall. Nothing is cropped and
	   nothing is taller than --tl-mat-h.

	   FIRST TRY WAS A FLAT `min(100%, 21rem)` AND THE CLIENT CAUGHT WHAT IS
	   WRONG WITH IT (2026-09-21): "some of the photos that were already small
	   are super tiny, like the 2004 and 2015 warehouse photos, and the
	   2009-2015 booth. Their owned warehouses are a big differentiator from
	   competitors so if anything, those two events should be on the bigger side
	   comparatively." A flat cap taxes every picture the same, but the page's
	   height problem was never the wide ones — a 2.6:1 warehouse frontage was
	   already only 185px tall and the cap took another 57px off it, while the
	   0.65:1 invitation beside it was still 494. Keyed on the ratio, the tall
	   ones give up half their height and the wide ones give up nothing: the
	   warehouse shots run the full width of the card and are now the biggest
	   pictures on the page, which is also what the client is asking for.

	   The two named as croppable were cropped in the SOURCE files instead, where
	   the crop can be judged against the subject: the NIFDA tins (0.75 → 0.99,
	   most of it empty backdrop) and the 1980s boxes, "crop out the left side
	   with the crushed boxes and the top box which is upside down" (0.75 →
	   0.78). Originals are in _private/client-assets/. */
	--tl-mat-h: 19rem;
	max-width: min(100%, calc(var(--tl-ratio, 1.3333) * var(--tl-mat-h)));
	margin-inline: auto;
	margin-block: clamp(1rem, 2vw, 1.35rem) .35rem;
	background: var(--paper);
	padding: .5rem .5rem 1.4rem;
	border: 1px solid rgba(28,28,28,.10);
	box-shadow: 0 6px 18px -8px rgba(28,28,28,.4);
	transform: rotate(-1.4deg);
	transform-origin: 50% 100%;
}
/* Alternating tilt, so a column of prints does not lean the same way twice. */
.tl__item--right .tl-card__figure { transform: rotate(1.4deg); }

/* THE MAT TAKES THE SHAPE OF THE PICTURE (2026-09-11). `--tl-ratio` is printed
   onto the figure from the attachment's own dimensions — see
   sfi_attachment_ratio(), which clamps it, and the note on
   sfi_render_legacy_timeline() for why this stopped being a fixed 4:3. The
   fallback in the var() is the ratio it used to be, so a figure that somehow
   arrives without the property looks exactly like the old one. */
.tl-card__photos { display: grid; gap: .5rem; }
.tl-card__img {
	display: block; width: 100%; height: auto;
	aspect-ratio: var(--tl-ratio, 4 / 3); object-fit: cover;
}
/* Two prints in one mat, same constraint as the diptych on Food Sourcing and
   Safety: both together take the space one would.

   BOTH HALVES SHARE ONE RATIO and `--tl-ratio` carries it, exactly as it does
   for a single picture — but computed by sfi_tl_pair_ratio() rather than read
   off one file, because two photographs can only share a shape if they nearly
   agree on one. Where they do (1.33 and 1.53 for the two 2004 warehouse group
   photographs) it is their average and neither is meaningfully cropped; where
   they do not, that function returns 0 — "no shared shape" — and the pair
   stacks with a ratio each. See `--mixed` below. */
.tl-card__figure--pair .tl-card__photos { grid-template-columns: 1fr 1fr; }
.tl-card__figure--pair .tl-card__img { aspect-ratio: var(--tl-ratio, 1 / 1); }
/* Two pictures side by side each get half the mat, so the mat needs twice the
   width to paint either of them --tl-mat-h tall. Without this the 2004
   warehouse pair is capped as though it held one picture and each half comes
   out at 213px — which is the "super tiny" the client reported. */
/* A COLLAGE GETS THE ROOM A PAIR GETS, and for the same reason: more than one
   picture is sharing the mat. The cap is a HEIGHT for one photograph, so a file
   that is itself three photographs spends that height three ways — the 2026 CEO
   Council graphic came out 316px wide with three portraits in it. 26rem rather
   than the pair's doubling, because this one is a single frame and doubling it
   would make it the tallest thing on the page by half: it lands at 433×416,
   next to a 304px ceiling everywhere else. "Slightly bigger", which is what was
   asked for, not the uncapped size it had before 2026-09-21.

   The row opts in (`composite`) — two attachments are visible to the renderer,
   three photographs inside one JPEG are not. */
.tl-card__figure--composite { --tl-mat-h: 26rem; }
/* A DOCUMENT GETS THE HEIGHT ITS WORDS NEED (client, 2026-09-23: "can the 1990s
   office invitation be bigger as now the text isn't as readable"). At the 19rem
   cap the 0.65:1 invitation was 304×198 and its body copy about 5px tall; at
   30rem it is 480×311, where "Please join us for hors d'oeuvres…" reads. Only
   the tall documents need it — a landscape page already hits the card's width
   long before its height cap — so it is opt-in per row (`document`), like the
   collage above. A taller card only ever raises the stagger ceiling. */
.tl-card__figure--document { --tl-mat-h: 30rem; }

.tl-card__figure--pair:not(.tl-card__figure--stack) {
	max-width: min(100%, calc(var(--tl-ratio, 1) * var(--tl-mat-h) * 2 + .5rem));
}
/* …EXCEPT WHEN THE PAIR IS TWO PANORAMAS, which halving would flatten into two
   unreadable strips — a 3:1 label scan at half a card's width is about 60px
   tall. One column instead: full width each, and the two of them together are
   still shallower than one square picture would have been. */
.tl-card__figure--stack .tl-card__photos { grid-template-columns: 1fr; }
/* …AND WHEN THE TWO HAVE NO SHARED SHAPE AT ALL, each half keeps its own. The
   renderer prints `--tl-half-ratio` on each <img> from that picture's own
   dimensions — see sfi_tl_pair_ratio(), which now says "no shared shape" rather
   than squaring both. Squaring is what this rule exists to avoid: the 1950s row
   pairs a near-square advertisement with a 3.23:1 label scan, and a squared
   label is the ingredients panel with the brand name cropped off both ends. */
.tl-card__figure--mixed .tl-card__img { aspect-ratio: var(--tl-half-ratio, var(--tl-ratio, 1 / 1)); }
.tl-card__caption {
	margin-top: .5rem; text-align: center;
	font-size: .82rem; line-height: 1.4; color: var(--charcoal);
}
/* A CAPTION UNDER EACH HALF (client, 2026-09-13, on the 2004 warehouse pair:
   "the caption is for the right photo, and for the left photo you can put…").
   Each half becomes its own <figure> inside the mat — see the note on
   sfi_render_legacy_timeline() for why nesting rather than two figcaptions —
   so the caption travels with its picture and the two columns stay level
   however long either line runs. */
.tl-card__half { margin: 0; display: grid; align-content: start; }
/* The mat's 1.4rem of bottom padding is the room a caption under the WHOLE mat
   needs. A split pair has already spent that space inside the grid, so the mat
   closes up — and the feature corner, which is pinned above that reserve, comes
   down with it. */
.tl-card__figure--split { padding-bottom: .5rem; }
.tl__item--feature .tl-card__figure--split::after { bottom: .5rem; }
/* The placeholder the client asked for: an empty mat with the same weave as the
   page, so the layout can be judged before the photographs are chosen. */
.tl-card__figure--empty {
	aspect-ratio: 4 / 3; padding: .5rem;
	background-image: repeating-linear-gradient(45deg,
		color-mix(in srgb, var(--beige) 26%, transparent) 0 1px,
		transparent 1px 9px);
	box-shadow: none;
}

/* ---- a featured entry ---- */
.tl-card--wide,
.tl__item--feature > .tl-card { box-shadow: var(--shadow-md); }
/* A featured entry USED to also re-crop its photograph to 3:2. That rule is gone
   (2026-09-11): now that the mat takes the shape of the picture, this one was
   the only thing left cropping anything, and it was doing it to the two rows
   most likely to carry a real archive photograph. Featuring is now the heavier
   shadow and the photo corners below — a frame, not a crop. */
/* Photo corners — the one unmistakably-album detail, spent on the handful of
   entries the client picks rather than on all twenty. */
.tl__item--feature .tl-card__figure { position: relative; }
.tl__item--feature .tl-card__figure::before,
.tl__item--feature .tl-card__figure::after {
	content: ""; position: absolute; width: 18px; height: 18px;
	border: 0 solid color-mix(in srgb, var(--beige) 95%, transparent);
	pointer-events: none;
}
.tl__item--feature .tl-card__figure::before {
	top: .5rem; left: .5rem; border-top-width: 12px; border-left-width: 12px;
	border-top-color: color-mix(in srgb, var(--beige) 80%, transparent);
	border-left-color: color-mix(in srgb, var(--beige) 80%, transparent);
	clip-path: polygon(0 0, 100% 0, 0 100%);
}
.tl__item--feature .tl-card__figure::after {
	bottom: 1.4rem; right: .5rem; border-bottom-width: 12px; border-right-width: 12px;
	border-bottom-color: color-mix(in srgb, var(--beige) 80%, transparent);
	border-right-color: color-mix(in srgb, var(--beige) 80%, transparent);
	clip-path: polygon(100% 0, 100% 100%, 0 100%);
}

/* ---- the founding ---- */
.tl__pivot {
	display: grid; justify-items: center; text-align: center;
	gap: 1rem; padding: clamp(1.5rem, 3.5vw, 2.5rem) 0;
	/* Lifts BOTH the medallion and the copy tile over the spine — see the note
	   on .tl__pivot-copy for why an opaque background is not enough on its own.
	   On the li rather than on each child so the medallion is covered too: it has
	   been relying on the stacking context that `.reveal`'s `will-change` creates,
	   which exists only when JavaScript has run. */
	position: relative;
}
.tl__medallion {
	display: grid; justify-items: center; gap: .4rem;
	width: min(100%, 17rem); padding: 1.15rem 1.5rem;
	background: var(--paper);
	border: 1px solid color-mix(in srgb, var(--red) 22%, transparent);
	border-radius: var(--radius-lg);
	box-shadow: 0 10px 30px -14px rgba(28,28,28,.55);
}
.tl__mark { display: block; width: min(100%, 11rem); height: auto; }
.tl__pivot-year {
	margin: 0;
	font-family: var(--font-head); font-weight: 700;
	font-size: clamp(1.5rem, 3vw, 2rem); letter-spacing: .04em;
	font-variant-numeric: tabular-nums;
	color: var(--red);
}
/* THE PIVOT'S COPY IS A TILE, BECAUSE THE SPINE WAS RUNNING THROUGH IT (client,
   2026-09-23: "on the 1980 I'm still seeing the brown line there — can we put
   that copy into a tile like the others?").

   Same defect the coda had on 09-21 and the opposite fix. Both blocks are
   CENTRED ON the spine rather than beside it, so the line runs down the middle
   of the words. The coda is the last thing on the page, so the line could simply
   be stopped short above it; the pivot has fourteen entries under it and the
   line has to continue, so the copy has to cover it instead.

   `.tl-card`'s own box, value for value, which is what "like the others" means —
   and it makes the pivot structurally identical to every other entry: a marker
   on the spine (the medallion, where a card has its dot) plus a card.

   AN OPAQUE BACKGROUND IS NOT ENOUGH ON ITS OWN, and that is the part worth
   writing down. `.tl::before` is absolutely positioned, so it paints with the
   positioned descendants — ABOVE the background of any in-flow block, however
   opaque. The line has to be got UNDER the tile, which is what the
   `position: relative` on `.tl__pivot` above does. `.tl-card` has carried the
   same declaration since it was written, for its dot, which is why the ordinary
   entries have never shown this. */
.tl__pivot-copy {
	max-width: 56ch;
	background: var(--paper);
	border: 1px solid color-mix(in srgb, var(--beige) 55%, transparent);
	border-radius: var(--radius);
	padding: clamp(1.15rem, 2.4vw, 1.6rem);
	box-shadow: var(--shadow-sm);
}
.tl__pivot-title { margin: 0 0 .6rem; font-size: clamp(1.2rem, 2.4vw, 1.5rem); text-wrap: balance; }
.tl__pivot-copy p { margin: 0 0 .85rem; font-size: 1rem; line-height: 1.72; color: color-mix(in srgb, var(--ink) 86%, transparent); text-wrap: pretty; }
.tl__pivot-copy p:last-of-type { margin-bottom: 0; }
/* THE FOUNDING ROW CARRIES A PICTURE NOW (client, 2026-09-23: "is it possible to
   add the new company announcement to the middle tile that says SFI is
   founded"). The same mat as every card — see sfi_tl_render_figure() — inside
   the copy tile, under the words. The figure is now the tile's last child,
   which is why the paragraph rule above is :last-of-type. */
.tl__pivot-copy .tl-card__figure { margin-bottom: 0; }

/* ---- the coda: the last row, and the end of the line ----
   Client, 2026-09-13: a "2027 and beyond" entry "(no photo, this can be at the
   end to round it off)". Centred on the spine and matless, so it is not read as
   the one row whose photograph nobody found — the empty placeholder mat means
   "a picture is coming", which is a promise you cannot leave at the bottom of
   the page. It answers the medallion: the mark opens the company's half of the
   timeline, this closes it, and everything with a dot on the spine is history
   between the two. */
.tl__coda {
	display: grid; justify-items: center; text-align: center;
	gap: .5rem; max-width: 52ch; margin: 0 auto;
	padding: clamp(1.75rem, 3.5vw, 2.5rem) 0 .5rem;
}
.tl__coda-era {
	margin: 0; font-family: var(--font-head); font-weight: 700;
	font-size: .95rem; letter-spacing: .12em; text-transform: uppercase;
	color: var(--red);
}
.tl__coda-title { margin: 0; font-size: clamp(1.1rem, 2vw, 1.3rem); text-wrap: balance; }
.tl__coda-body {
	margin: 0; font-size: 1rem; line-height: 1.72; text-wrap: pretty;
	color: color-mix(in srgb, var(--ink) 86%, transparent);
}

/* ---- one column on a phone: the spine moves to the left edge ---- */
@media (max-width: 860px) {
	.tl::before { left: 9px; }
	.tl__item { grid-template-columns: 1fr; }
	.tl__item--left > .tl-card,
	.tl__item--right > .tl-card { grid-column: 1; margin: 0 0 0 2.25rem; }
	.tl__item--left  > .tl-card::after,
	.tl__item--right > .tl-card::after { left: -2.25rem; right: auto; }
	.tl__pivot { padding-left: 2.25rem; justify-items: start; text-align: left; }
	.tl__medallion { justify-items: start; }
	.tl__coda { padding-left: 2.25rem; justify-items: start; text-align: left; margin-left: 0; }
	/* The spine is at the left EDGE here and the closing block is indented past
	   it, so the line runs beside that copy rather than through it. The desktop
	   reserve would just leave a gap. */
	.tl:has(> .tl__coda)::before { bottom: .5rem; }
	/* One column, so nothing alternates and every card would overlap the card
	   directly above it. */
	.tl__item + .tl__item { margin-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
	.tl-card__figure { transform: none; }
}

/* ---- careers ----
   A BLOCK INSIDE OUR TEAM, not a band of its own (client: "this should fit in
   well with the 'team' section"), so it is separated by a rule rather than by a
   colour. That is also what keeps this page at three bands and ending white.

   It is a `.story` since 2026-09-08, so the width and the centring it used to
   set here are gone — all that is left is the separating rule and the space
   above it. `.careers` and `.story` are on the same element; this block loads
   after `.story`, which is why the margin below wins. */
.careers {
	margin-top: clamp(2.5rem, 5vw, 4rem);
	padding-top: clamp(2rem, 4vw, 3rem);
	border-top: 1px solid rgba(28,28,28,.12);
}
.careers__title { margin: 0 0 1rem; font-size: clamp(1.4rem, 2.6vw, 1.8rem); }
.careers__body { max-width: none; }
.careers__body p { font-size: 1.02rem; line-height: 1.75; }
.careers__body p:last-child { margin-bottom: 0; }

.careers__jobs-title {
	margin: clamp(1.75rem, 3vw, 2.5rem) 0 1rem;
	font-family: var(--font-head); font-weight: 700; font-size: .82rem;
	letter-spacing: .1em; text-transform: uppercase; color: var(--charcoal);
}
.careers__jobs { list-style: none; margin: 0; padding: 0; display: grid; gap: .85rem; }
/* A role is a card — a job title, its location and a line of detail are a
   record, and a centred record is unreadable the moment it runs to two lines,
   which is why `text-align: left` is still stated even now that the block
   around it is left-aligned by default. */
.job {
	background: var(--paper); border: 1px solid rgba(28,28,28,.1);
	border-left: 3px solid var(--red);
	border-radius: var(--radius); padding: clamp(.9rem, 2vw, 1.25rem);
	text-align: left;
}
.job__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .75rem; }
.job__title { font-family: var(--font-head); font-weight: 700; font-size: 1.02rem; line-height: 1.3; }
.job__place {
	font-family: var(--font-head); font-weight: 600; font-size: .72rem;
	letter-spacing: .08em; text-transform: uppercase; color: var(--charcoal);
}
.job__note { margin: .45rem 0 0; font-size: .95rem; line-height: 1.6; color: color-mix(in srgb, var(--ink) 80%, transparent); }
.job__link { margin-top: .7rem; }

.careers__foot { margin-top: clamp(1.5rem, 3vw, 2rem); }
.careers__line { margin: 0 0 1.1rem; font-size: 1.02rem; line-height: 1.7; color: color-mix(in srgb, var(--ink) 82%, transparent); }

@media (max-width: 760px) {
	/* The portrait stops being a column and becomes a lead-in — capped, so a
	   square headshot does not fill the phone screen before the copy starts. */
	.founder__wrap { grid-template-columns: 1fr; }
	.founder__media { max-width: 13rem; margin-inline: auto; }
	.founder__plate { text-align: center; }
}

/* ==================================================== SFI homepage story blocks
   Client, 2026-09-04, with Roland's "our company" page open beside it: "a little
   snippet that gives a cohesive overview but then directs them elsewhere to read
   more … also like Roland's has lots of photos and color."

   Alternating copy-and-photograph rows in ONE band. One band because seven is
   already a long page and three more would leave the cadence with nothing left
   to alternate through; the variety comes from the photographs, which is what
   "photos and color" actually meant.
   ===================================================================== */
.stories { padding: var(--section-y) 0; }
.stories > .wrap { display: grid; gap: clamp(2.75rem, 6vw, 5.5rem); }
.story {
	display: grid; gap: clamp(1.5rem, 4vw, 3.5rem);
	grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr);
	align-items: center;
}
/* The photograph is first in the markup, so it is on the left by default and
   the flip moves it right. Source order stays picture-then-copy either way —
   a screen reader should not hear the blocks in a different order depending on
   which side a picture happens to be on. */
.story--flip .story__media { order: 2; }
.story__media { margin: 0; }
/* NO FORCED ASPECT RATIO. These are the client's own photographs and they are
   not one shape — a landscape family photo and a square phone shot — and
   cropping both to a house ratio is what loses the people at the edges of the
   first one. align-items:center on the row absorbs the difference. */
.story__img {
	display: block; width: 100%; height: auto;
	border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
}
.story__title { margin: .35rem 0 1rem; }
.story__body p { font-size: 1.03rem; line-height: 1.75; }
.story__body p:last-child { margin-bottom: 0; }
.story__actions { margin-top: clamp(1.25rem, 2.5vw, 1.75rem); display: flex; flex-wrap: wrap; gap: .75rem; }
.story__actions:empty { display: none; }

/* A block with no photograph is a centred column, not a half-empty row — the
   same rule the editorial band above follows. */
.story--solo { grid-template-columns: 1fr; }
.story--solo .story__content { max-width: 68ch; margin-inline: auto; text-align: center; }
.story--solo .story__body { margin-inline: auto; }
.story--solo .story__actions { justify-content: center; }

/* The "groups we support" list inside the pop-up. Two columns where there is
   room — ten non-profit names in one column is a very tall dialog. */
.story-list { list-style: none; margin: 1.25rem 0 0; padding: 0; columns: 2; column-gap: 2rem; }
/* ---- the grouped list (2026-09-10) ----
   SFI's 2025–2026 giving is 29 organisations against the 10 this panel was built
   for, and they asked for it split: a primary recipient, then Food Organizations,
   then Community Groups.

   THE LEAD IS NOT A ONE-ITEM LIST, it is the answer to "who do you support?" —
   so it loses the column box and the small type and reads at body size. It keeps
   the red dot: it is still a member of the list that follows, just the first
   among them.

   THE COLUMN COUNT FOLLOWS `data-count`. Two columns is right for five names and
   wrong for twenty-three — that is twelve rows deep in a dialog capped at 86vh,
   which turns the panel into a scroller for no reason when the names are short
   enough to sit three across. Only on a wide dialog; the panel is 780px, so this
   is a container question rather than a viewport one and the breakpoint is set
   where the dialog stops being narrow. */
.story-list__group {
	margin: 1.75rem 0 0;
	font-family: var(--font-head); font-weight: 700; font-size: .74rem;
	letter-spacing: .1em; text-transform: uppercase; color: var(--charcoal);
}
.story-list__group + .story-list { margin-top: .7rem; }
.story-list--lead { columns: 1; margin-top: 1.1rem; }
.story-list--lead li { font-size: 1.02rem; font-weight: 600; line-height: 1.5; margin-bottom: 0; }
@media (min-width: 620px) {
	.story-list[data-count="16"], .story-list[data-count="17"], .story-list[data-count="18"],
	.story-list[data-count="19"], .story-list[data-count="20"], .story-list[data-count="21"],
	.story-list[data-count="22"], .story-list[data-count="23"], .story-list[data-count="24"],
	.story-list[data-count="25"], .story-list[data-count="26"], .story-list[data-count="27"],
	.story-list[data-count="28"], .story-list[data-count="29"], .story-list[data-count="30"] { columns: 3; column-gap: 1.5rem; }
}
.story-list li {
	break-inside: avoid; margin-bottom: .6rem; padding-left: .9rem; position: relative;
	font-size: .95rem; line-height: 1.5;
}
.story-list li::before {
	content: ""; position: absolute; left: 0; top: .55em;
	width: .34rem; height: .34rem; border-radius: 50%; background: var(--red);
}
.inspo-panel__lede { margin: .5rem 0 0; font-size: .98rem; line-height: 1.65; color: color-mix(in srgb, var(--ink) 78%, transparent); }

/* The brands section's own button (client: "CTA: Explore our Product Catalogue"). */
.brands__actions { margin-top: clamp(1.75rem, 4vw, 2.75rem); text-align: center; }

@media (max-width: 900px) {
	.story { grid-template-columns: 1fr; }
	/* Picture first on a phone whichever side it takes on desktop. */
	.story--flip .story__media { order: 0; }
}
@media (max-width: 560px) {
	.story-list { columns: 1; }
}


/* ================================================ food sourcing & safety (SFI)
   Client draft, 2026-09-04 ("Sourcing and safety .docx"). Split hero → Safety
   (copy + highlights sidebar + certification marks) → Sourcing → the
   Procurement Footprint map.

   NO CADENCE BLOCK HERE ON PURPOSE, same as Our People above. The hero is fixed
   furniture (cream) and drops a .hero-anchor, so BASE SECTION CADENCE runs the
   rest positionally — safety WHITE → sourcing mist → footprint WHITE, footer
   curve white. Nothing below sets a section background, which is the rule that
   stops the warm tints creeping back. The ONE deviation is the curve fill at
   the end of this block, for the case where an editor adds a fourth band.
   ===================================================================== */
.safety,
.sourcing,
.footprint { padding: var(--section-y) 0; }

/* A TIGHTER RHYTHM ON THIS PAGE ONLY (client, 2026-09-11: "is there room to
   still shrink the white space on the sourcing safety page a tad?"). One
   variable rather than three padding overrides, so the three bands stay in step
   with each other and with anything added later.

   ~22% off the top of the clamp and ~20% off the bottom. It is a page of three
   dense bands — a copy column with a sticky credentials panel, a diptych, and a
   world map that is 560px tall on its own — so the gaps between them were doing
   less work here than on a page of short sections, which is why this page and
   not the others. The eighth of an inch the client can see, without the bands
   starting to touch. */
.page-sourcing { --section-y: clamp(2rem, 4.7vw, 4.3rem); }

/* ---- safety: copy + the highlights sidebar ----
   The copy column is the wider one. The sidebar is a credentials list, not the
   argument, and a 50/50 split makes it look like the argument. minmax(0,…) on
   both tracks: a long unbroken highlight ("Certified Superior Audit, 97%" is
   fine, a pasted URL would not be) must not widen its column. */
.safety__grid {
	display: grid; gap: clamp(1.75rem, 4vw, 3.25rem);
	grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
	align-items: start;
}
.safety__figure { margin: 0 0 clamp(1.25rem, 3vw, 1.9rem); }
.safety__img {
	display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover;
	border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
}
.safety__body p { font-size: 1.03rem; line-height: 1.75; }
.safety__body p:last-child { margin-bottom: 0; }

/* The panel is CREAM, not charcoal. The dark treatment on the client's own
   slide is a slide convention; the standing instruction on this build is no
   dark section backgrounds, and cream is the same panel colour the hero above
   it already uses — so the sidebar reads as part of the page rather than as a
   widget dropped onto it. The red rule at the top is the only ornament. */
.safety__aside {
	background: var(--cream); border-radius: var(--radius-lg);
	border-top: 4px solid var(--red);
	padding: clamp(1.5rem, 3vw, 2.25rem);
	box-shadow: var(--shadow-sm);
	position: sticky; top: clamp(1rem, 8vh, 6rem);
}
.safety__aside-title {
	margin: 0 0 1.1rem; font-size: 1.12rem; line-height: 1.3;
}
/* The thesis sentence, at the top of the highlights box (client, 2026-09-10:
   "move that single intro sentence into safety highlights box above 'key safety
   highlights' — both in bold"). Same weight as the heading under it and a rule
   between them, so the box reads as claim-then-evidence rather than as two
   headings that lost their sections. */
.safety__aside-thesis {
	margin: 0 0 1rem; padding-bottom: 1rem;
	border-bottom: 1px solid rgba(28,28,28,.12);
	font-family: var(--font-head); font-weight: 700;
	font-size: 1.02rem; line-height: 1.5; color: var(--ink);
	text-wrap: pretty;
}
.safety__list { list-style: none; margin: 0; padding: 0; }
.safety__list li {
	position: relative; padding-left: 1.4rem; margin-bottom: .8rem;
	font-size: .97rem; line-height: 1.55;
	color: color-mix(in srgb, var(--ink) 86%, transparent);
}
.safety__list li:last-child { margin-bottom: 0; }
/* A disc rather than a tick glyph: the check characters that read well here are
   not in every system font, and a bullet that falls back to a box is worse than
   one that was never a glyph. */
.safety__list li::before {
	content: ""; position: absolute; left: .25rem; top: .55em;
	width: .45rem; height: .45rem; border-radius: 50%;
	background: var(--red);
}

/* ---- certification marks ----
   EVERY TILE IS WHITE. It used to be a repair: four of the six marks were cut
   out of the client's PowerPoint carrying their own white box and two were not,
   so on any tinted band the row would have read as two floating marks beside
   four white rectangles, and giving all six the same tile hid the difference.

   The client sent the real artwork on 2026-09-09 and all six are now cut out to
   a real alpha channel, so that repair is not needed any more — which makes the
   white tile a CHOICE, and it is still the right one. A certification mark is a
   third party's property reproduced as evidence; standing each one on its own
   white card says so, keeps six different lock-ups optically level, and holds
   if this section ever moves off white. Nothing here changed. The reason did. */
.cert-marks {
	list-style: none; margin: clamp(2rem, 4vw, 3rem) 0 0; padding: clamp(1.5rem, 3vw, 2rem) 0 0;
	border-top: 1px solid rgba(28,28,28,.1);
	display: flex; flex-wrap: wrap; justify-content: center;
	gap: clamp(1rem, 2.5vw, 2rem);
}
.cert-marks__item {
	display: flex; flex-direction: column; align-items: center; gap: .7rem;
	width: clamp(7rem, 14vw, 9.5rem);
}
.cert-marks__img {
	width: 100%; height: clamp(3.2rem, 6vw, 4.4rem); object-fit: contain;
	background: var(--paper); border-radius: var(--radius);
	padding: .5rem .75rem; box-shadow: var(--shadow-sm);
}
.cert-marks__label {
	font-family: var(--font-head); font-weight: 700; font-size: .72rem;
	letter-spacing: .06em; text-transform: uppercase; text-align: center;
	line-height: 1.35; color: var(--charcoal);
}

/* ---- sourcing: photography left, copy right ----
   Mirrored against the safety section above so two long copy blocks do not run
   down the same edge of the page. */
.sourcing__grid {
	display: grid; gap: clamp(1.75rem, 4vw, 3.25rem);
	grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
	align-items: center;
}
.sourcing__figure { margin: 0; }
.sourcing__photos { display: grid; gap: clamp(.5rem, 1.2vw, .8rem); }
.sourcing__img {
	display: block; width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
	border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
}

/* TWO PHOTOGRAPHS IN THE SPACE OF ONE — the client's constraint, word for word:
   "can you add both of them (without enlarging the total space)".

   A DIPTYCH, NOT A STACK, and the arithmetic is the argument. Call the column
   W. One 3:4 portrait is 1.33W tall. Two of these pictures stacked at full
   width would be ~2W before the caption, and squeezing them back down to 1.33W
   means cropping each to a ~2:1 letterbox — on a standing figure and a harvest
   basket, that is the one crop that throws away the subject. Side by side each
   photograph is (W − gap)/2 wide, and at 4:5 the pair is ~0.62W tall: with the
   caption under it the whole figure lands well INSIDE the footprint the single
   portrait had, which is what was asked for.

   4:5 rather than square because both files are within a whisker of square
   (0.99 and 1.13) — a 4:5 box takes a few percent off the sides of each and
   leaves every subject where the photographer put it, no object-position
   needed. Anything squarer stops reading as a deliberate crop; anything taller
   starts cutting into her.

   THEY STAY SIDE BY SIDE AT EVERY WIDTH, including the phone. Letting them
   stack below some breakpoint would double the height of exactly the thing
   that was asked not to grow, and on a stacked layout the section is already
   the tallest on the page. Two ~160px photographs on a small phone are small,
   but they are a pair — which is what these two are. */
.sourcing__figure--pair .sourcing__photos { grid-template-columns: 1fr 1fr; }
.sourcing__figure--pair .sourcing__img { aspect-ratio: 4 / 5; }

/* TWO GENERATIONS, ONE COLUMN (client, 2026-09-23): Norman Schreiber in Spain in
   the 1980s above Emily in Spain in the 2020s, "side by side like Emily's
   instead of stacked", a caption each. See sfi_render_sourcing_figure().
   The top pair is LANDSCAPE and keeps its own shared ratio (`--src-ratio`,
   ~1.5) — 4:5 would keep half of each print. Same specificity as the 4:5 rule
   above and later in the file, so it wins; the phone's 16:10 rule below is
   (0,1,0) and never reaches either pair. */
.sourcing__media { display: grid; gap: clamp(1.4rem, 3vw, 2rem); }
.sourcing__figure--own .sourcing__img { aspect-ratio: var(--src-ratio, 3 / 2); }

/* Small print under the pair, same recipe as .hero__caption on the Legacy hero
   and printed for the same reason: it names a person and her title. Rule above
   it rather than a box, so it reads as a caption belonging to the pictures and
   not as a second copy block competing with the real one alongside. */
.sourcing__caption {
	margin: clamp(.9rem, 2vw, 1.25rem) 0 0; padding-top: clamp(.7rem, 1.5vw, .95rem);
	border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
	color: color-mix(in srgb, var(--ink) 72%, transparent);
	font-size: clamp(.76rem, 1vw, .84rem); line-height: 1.6;
	text-wrap: pretty;
}
.sourcing__body p { font-size: 1.03rem; line-height: 1.75; }
.sourcing__body p:last-child { margin-bottom: 0; }
.sourcing__actions { margin-top: clamp(1.4rem, 3vw, 2rem); }

/* ---- the procurement footprint map ----
   Geometry is inc/world-map-data.php (Natural Earth, public domain, Robinson).
   Fills are set HERE, by class, and not with a fill= attribute on the path:
   that attribute is the weakest thing in the cascade and any class rule beats
   it — the lesson the RSM map above learned the hard way. */
.wmap { --map-blank: #DCD7CF; margin: clamp(1.5rem, 4vw, 2.5rem) 0 0; }
/* Below ~700px the map is wider than the screen. It scrolls inside this box —
   the page body must never scroll sideways — and the box is focusable so that
   scroll is reachable without a pointer. */
.wmap__frame { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.wmap__frame:focus-visible { outline: 3px solid var(--red); outline-offset: 4px; }
.wmap__svg { display: block; width: 100%; height: auto; }
.wmap__c { fill: var(--map-blank); stroke: var(--paper); stroke-width: .7; }
.wmap__c--on { fill: var(--red); }
/* The ring is for the countries a fill cannot carry — Belgium is 28 square
   pixels on this map and Israel 18. Not a pin: it is centred on the country and
   sized so the shape inside it is still what you look at. */
.wmap__ring { fill: none; stroke: var(--red); stroke-width: 2.4; }

/* The list under the map is the map's content, not its caption — it is the only
   version of "Portugal" a search engine, an assistant or a screen reader can
   read. Set as a credential row: small, uppercase, centred, red-dotted. */
.wmap__caption { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.wmap__list {
	list-style: none; margin: 0; padding: 0;
	display: flex; flex-wrap: wrap; justify-content: center;
	gap: .55rem 1.35rem;
}
.wmap__list li {
	position: relative; padding-left: .85rem;
	font-family: var(--font-head); font-weight: 600; font-size: .78rem;
	letter-spacing: .07em; text-transform: uppercase; color: var(--charcoal);
}
.wmap__list li::before {
	content: ""; position: absolute; left: 0; top: .45em;
	width: .34rem; height: .34rem; border-radius: 50%; background: var(--red);
}

@media (max-width: 900px) {
	.safety__grid,
	.sourcing__grid { grid-template-columns: 1fr; }
	/* Sticky is meaningless once it is a stacked block, and it would pin the
	   panel over the copy that follows it. */
	.safety__aside { position: static; }
	/* A 3:4 portrait is half a phone screen tall before the copy starts. The
	   PAIR is exempt by specificity (.sourcing__figure--pair .sourcing__img
	   beats this) and that is deliberate — see the diptych note above. */
	.sourcing__img { aspect-ratio: 16 / 10; }
}
@media (max-width: 700px) {
	/* Wide enough that Greece is still a shape rather than a pixel; the frame
	   above scrolls the rest. */
	.wmap__svg { min-width: 620px; }
}

/* THE ONE DEVIATION. With the three sections this page ships with, the base
   cadence ends it WHITE and the footer curve's inherited white is already
   right. An editor typing into the page's own editor adds .page-body as a
   fourth band, which the cadence paints linen — and then the curve below it has
   to match, or the page ends on the client's other standing rule. */
.page-sourcing:has(> .page-body:last-child) + .site-footer { margin-top: 0; --footer-curve-fill: var(--linen); }
