/* ==========================================================================
   0. The two typefaces, served from this origin
   Google's stylesheet was one of exactly two render-blocking resources on
   every page, and the slowest: measured 606 to 1491ms of blocking against
   styles.css's 348 to 478. Two extra DNS lookups and TLS handshakes bought
   nothing that 48 KB of woff2 on our own origin does not. Instrument Sans is
   variable, so one file carries 400 to 600. Bitter replaced Tenor Sans as the
   display face on 2026-09-15 (Edgar picked TYPE 3 of three, section 42); it
   is variable too, 400 to 700 in 33 KB.
   ========================================================================== */
@font-face {
  font-family: "Instrument Sans";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("assets/fonts/instrument-sans.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bitter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("assets/fonts/bitter.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   ERFDEVCO
   Farm, smallholding, game farm and vineyard sales across South Africa.

   Look target: altnest.webflow.io measured live at 1440 (Tenor Sans display
   in uppercase at negative tracking over Instrument Sans body, flat 140/140
   section rhythm, 1550 container, 4 to 6px radius, one full bleed dark
   chapter band). Retokenised to ERFDEVCO estate green and gold, with the
   inventory pulled to the top of the page because this is a marketplace.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Brand: four values, everything else is derived with color-mix */
  --ink: #12271d;            /* estate green, the brand dark */
  --ink-deep: #0b1710;       /* the chapter band, deeper than ink */
  --gold: #c8900a;           /* the single accent */
  --paper: #ffffff;

  /* Derived grounds */
  --canvas: #f6f4ef;                 /* the page ground: warm paper, not white */
  --stone: color-mix(in srgb, var(--ink) 5%, var(--canvas));
  --sage: color-mix(in srgb, var(--gold) 7%,
            color-mix(in srgb, var(--ink) 11%, var(--canvas)));
  --gold-dark: color-mix(in srgb, var(--gold) 40%, var(--ink));
  --err: #a3271a;          /* error red, AA on canvas and paper (audit round 2) */
  --err-dark: #ff9b8c;     /* error red for ink grounds */
  --stone-2: color-mix(in srgb, var(--ink) 9%, var(--canvas));
  --line: color-mix(in srgb, var(--ink) 14%, transparent);
  --line-soft: color-mix(in srgb, var(--ink) 8%, transparent);
  --line-dark: color-mix(in srgb, var(--paper) 16%, transparent);

  /* Derived text */
  --text: var(--ink);
  --muted: color-mix(in srgb, var(--ink) 62%, #8d9389);
  --muted-dark: color-mix(in srgb, var(--paper) 62%, transparent);
  /* The light-ground gold. Mixed to 60% so it clears 4.5:1 on BOTH paper and
     the stone band. The bright --gold is only used on ink, where it is 6.5:1. */
  --gold-deep: color-mix(in srgb, var(--gold) 60%, var(--ink));
  /* The gold for text sitting on a PHOTOGRAPH. A photograph is not a ground:
     it is every luminance at once. Measured 2026-09-09 at 3x on the real
     pixels behind each glyph, --gold on the home hero came out at 1.21:1 to
     1.98:1 against a 3:1 bar across all three hero frames, --gold-deep on the
     404 photo at 2.20:1 with every single glyph pixel under the bar, and
     --gold on the about hero at 3.11:1. Solving it with the scrim alone needed
     80% ink, which is no longer a photograph. This mix is light enough to
     clear the bar at a scrim depth the picture survives. */
  --gold-on-photo: color-mix(in srgb, var(--gold) 55%, var(--paper));

  /* Type */
  --display: "Bitter", "Rockwell", "Georgia", serif;  /* section 42 */
  --body: "Instrument Sans", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Fluid display scale. Tracking tightens as size grows, the Altnest move. */
  --fs-hero: clamp(2.7rem, 6.6vw, 5.5rem);
  --fs-h1: clamp(2.4rem, 4.6vw, 4rem);
  --fs-h2: clamp(1.85rem, 3.1vw, 2.85rem);
  --fs-h3: clamp(1.25rem, 1.6vw, 1.6rem);
  --fs-h4: clamp(1.05rem, 1.15vw, 1.2rem);
  --fs-label: 0.8125rem;
  --fs-body: 1rem;
  --fs-lead: clamp(1.0625rem, 1.15vw, 1.1875rem);

  /* Rhythm: Altnest runs a flat 140/140. Ours goes fluid but stays flat. */
  --band: clamp(5.5rem, 8.6vw, 8.75rem);
  --band-tight: clamp(3.5rem, 5.4vw, 5.5rem);
  --container: 1480px;
  /* The floor was 1.25rem until 2026-09-09: at 375 that stood the display caps
     20px off the edge and every heading read as pushed against it. 1.75rem is
     the phone floor now; the vw term still takes over by 875. */
  /* 28px, not 1.75rem: identical at the default root, and a gutter is space,
     not type, so it does not scale with text. (This was first blamed for the
     header running off a phone at 200% text. The third review showed the real
     cause: button labels that could not wrap, section 31.2.) It never drops
     under the safe-area inset either, so a notch in landscape cannot sit on
     the content. */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --gutter: max(clamp(28px, 3.2vw, 2.75rem), var(--safe-left), var(--safe-right));

  /* Shape: a small radius system, never one value everywhere */
  --r-media: 6px;
  --r-card: 6px;
  --r-lift: 10px;    /* the listing card, the one object lifted off the page (37) */
  --r-chip: 4px;
  --r-field: 4px;

  /* Motion */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --t-fast: 180ms;
  --t-mid: 320ms;
  --t-slow: 640ms;

  /* The header carries the status bar's height inside it, so an installed
     iPhone app does not draw the logo under the clock. Everything that offsets
     by the header (sticky tops, scroll padding, the menu sheet) reads this. */
  --header-h: calc(84px + var(--safe-top));
  --header-h-solid: calc(72px + var(--safe-top));

  /* The bottom edge, section 31.3. --dock is whatever is fixed to the bottom
     under the compare tray (the action bar on a listing, otherwise nothing);
     --tray-h is written by script.js while the tray is up. */
  --dock: 0px;
  --tray-h: 0px;
  --tray-gap: clamp(0.75rem, 2vw, 1.5rem);
  --tray-stack: 0px;
  --fab-gap: clamp(1rem, 2.5vw, 1.75rem);
  --fab-size: 60px;
}

/* --------------------------------------------------------------------------
   2. Reset and base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: calc(var(--header-h) + 16px);
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--text);
  font-family: var(--body);
  font-size: var(--fs-body);
  line-height: 1.5;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* The sideways-overflow guard lives on html, not body: on body it either
   creates a scroll container that never scrolls (hidden), which leaves every
   scroll-driven animation timeline inactive, or propagates clip to the
   viewport and kills page scrolling outright. On the root, the page keeps
   scrolling and the timelines attach to it. */
html { overflow-x: hidden; }

img, svg, video { display: block; max-width: 100%; }
img { height: auto; }

h1, h2, h3, h4, h5 {
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  margin: 0;
  color: var(--ink);
  text-wrap: balance;
  /* Farm names carry words no phone can fit on one line: "About
     Baardskeerdersbos Village Stand" ran 13px past a 360 screen and put a
     sideways scroll on the whole page. Breaks only when the word cannot fit. */
  overflow-wrap: break-word;
}

h1 { font-size: var(--fs-h1); line-height: 1.05; letter-spacing: -0.045em; }
h2 { font-size: var(--fs-h2); line-height: 1.12; letter-spacing: -0.035em; }
h3 { font-size: var(--fs-h3); line-height: 1.2; letter-spacing: -0.025em; }
h4 { font-size: var(--fs-h4); line-height: 1.25; letter-spacing: -0.02em; }

p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: inherit; text-decoration: none; }

button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: 2px;
}

::selection { background: var(--gold); color: var(--paper); }

.skip-link {
  position: absolute;
  left: 1rem;
  top: -100px;
  z-index: 999;
  background: var(--ink);
  color: var(--paper);
  padding: 0.75rem 1.25rem;
  border-radius: var(--r-chip);
  font-size: 0.875rem;
  transition: top var(--t-fast) var(--ease);
}
.skip-link:focus { top: 1rem; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */
.shell {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.band { padding-block: var(--band); background: var(--canvas); }
.band--tight { padding-block: var(--band-tight); }
.band--stone { background: var(--stone); }
/* A surface, not a tint step: white panels on the warm canvas are what give
   the page its value change where a dark band would be too much. */
.band--surface { background: var(--paper); }
.band--sage { background: var(--sage); }
/* Two light bands meeting stack their paddings into a void: the value only
   steps subtly (canvas to white), so the eye reads one ground with a hole
   in it. Same-value seams collapse to a quarter step; seams where the
   ground genuinely changes (sage, ink, photo) keep their full air. */
/* One seam for the whole site. Every section boundary shows the same 0.4
   step on each side, whatever the grounds are, photo bands included. No
   tiers, no special cases: measured equal everywhere. */
main > section + :is(.band, .band--surface, .band--sage, .band--tight) {
  padding-top: calc(var(--band) * 0.6);
}
main > :is(.band, .band--surface, .band--sage, .band--tight):has(+ section) {
  padding-bottom: calc(var(--band) * 0.6);
}
.band--sage .rows, .band--sage .cband { border-top-color: color-mix(in srgb, var(--ink) 18%, transparent); }
.band--sage .row-item, .band--sage .cband > div { border-color: color-mix(in srgb, var(--ink) 18%, transparent); }
.band--sage .eyebrow,
.band--sage .cband span { color: var(--gold-dark); }
.band--sage .type-more a {
  border-color: color-mix(in srgb, var(--ink) 22%, transparent);
  color: var(--muted);
}
.band--sage .type-more a:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.band--sage .provlist-note { color: var(--muted); }
.band--sage .mapbox {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  /* Left over from when the map was a boxed panel: it made the band's air
     97.9 above and 145.9 below at 1440. */
  margin-bottom: 0;
}
.band--sage .prov { stroke: var(--sage); fill: color-mix(in srgb, var(--gold) 13%, color-mix(in srgb, var(--ink) 7%, var(--sage))); }
.band--sage .prov.has-farms { fill: var(--ink); }
.band--sage .prov.has-farms:hover { fill: color-mix(in srgb, var(--gold) 62%, var(--ink)); }
/* Must sit after .band--sage .prov.has-farms: same specificity, and the
   selected province has to win. Without it the map gave no feedback at all. */
.band--sage .prov.is-active,
.band--sage .prov.has-farms.is-active { fill: var(--gold); }
.band--sage .pin .dot { stroke: var(--sage); }
.band--sage .provlist { border-top-color: color-mix(in srgb, var(--ink) 18%, transparent); }
.band--sage .provlist button { border-bottom-color: color-mix(in srgb, var(--ink) 18%, transparent); }
/* Two buttons side by side need a column wide enough to hold them. Desktop
   only: below 901 the split collapses like every other one. */
@media (min-width: 901px) {
  .band--sage.cta--flat .split--action { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }
}
.band--sage .btn--outline { border-color: color-mix(in srgb, var(--ink) 42%, transparent); }
/* gold-deep is 3.85:1 on this ground, which passes for large text and fails
   for a 12px count. */
.band--sage .provlist button .n { color: var(--gold-dark); }
.provlist-act { margin-top: 1.25rem; }
/* Stacked link lists (enquiry card, footer, privacy card) meet the 24px
   target floor; measured 19 to 21px before. */
.agent-line a,
.site-footer li a,
.footer-base a { display: inline-flex; align-items: center; min-height: 26px; }
/* An inline display:flex beats the user agent's [hidden] rule, so this lives
   in the stylesheet: with nothing selected the three controls did nothing. */
.cmp-acts { display: flex; gap: 0.6rem; flex-wrap: wrap; }
/* The row keeps its place while the page loads, so the table below it does
   not jump when it appears (compare CLS was 0.305 at 390 and 0.313 at 1280,
   fifth review). Hidden rows are still hidden to assistive technology. */
.cmp-acts[hidden] { display: none; }
.cmp-coming .cmp-controls:has(~ #compare-table > .cmp-loading) .cmp-acts[hidden] { display: flex; visibility: hidden; }
#compare-table:has(> .cmp-loading) { min-height: 150svh; }
/* The row is held open only when a comparison is coming (compare.html sets
   .cmp-coming in its head); the picker never shows it (audit, 2026-09-13). */
/* Only while loading: kept for good, the hidden buttons left a 224px blank
   band under the hero on the empty compare page at 390 (sixth review). */
/* Listings render after a fetch; reserve the grid's ground so the footer
   does not jump up and back (CLS 0.22 measured). */
#all-farms { min-height: 70vh; }
/* The chips and the results line render after the fetch; reserving their
   height stops the whole listings column stepping down (CLS 0.178 measured
   throttled). */
#filters { min-height: 44px; }
/* The map injects after its fetch; reserving its ground stops the cards band
   stepping down under it (the 0.177 CLS source). Until 2026-09-09 the reserve
   was four fixed pixel heights measured against the nine-province demo data.
   The real book is four farms in one province, so the box was holding 316px
   of empty sage under the drawing on a phone and 284px at 768. Reserve the
   drawing's own geometry instead, 877:1000 of the column it fills, and it
   tracks the width instead of a stale measurement. align-content keeps any
   remainder at the foot of the box rather than tearing a hole mid-section. */
#farm-map {
  min-height: calc((min(100vw, var(--container)) - 2 * var(--gutter) - var(--map-gap, 3.5rem)) / 2 * 0.877);
  align-content: start;
}
/* One column below 901: the drawing spans the shell, then the grid gap, then
   the province column stacked under it. The column's height steps with the
   wrap of its own sentence, so it takes two measured values rather than one
   average. Measured every 40px from 320 to 900: 23.47rem while the lede holds
   four lines (to 440), then it settles at 19.8rem from 520 up. */
@media (max-width: 900px) {
  #farm-map {
    min-height: calc((100vw - 2 * var(--gutter)) * 0.877 + var(--map-gap, 1.5rem) + 23.5rem);
  }
}
@media (min-width: 471px) and (max-width: 900px) {
  #farm-map {
    min-height: calc((100vw - 2 * var(--gutter)) * 0.877 + var(--map-gap, 2rem) + 20rem);
  }
}
#results-line { min-height: 1.5em; display: block; }
/* Type chips over a photo scrim (the 404): light borders and paper text. */
.type-more--dark a {
  border-color: color-mix(in srgb, var(--paper) 45%, transparent);
  color: color-mix(in srgb, var(--paper) 88%, transparent);
}
.type-more--dark a:hover { background: var(--paper); border-color: var(--paper); color: var(--ink); }
.band--sage .counter,
.band--sage .counters { border-color: color-mix(in srgb, var(--ink) 18%, transparent) !important; }
.band--sage .counter__n { color: var(--ink) !important; }
.band--top0 { padding-top: 0; }
.band--bottom0 { padding-bottom: 0; }

.band--ink {
  background: var(--ink-deep);
  color: color-mix(in srgb, var(--paper) 92%, transparent);
}
.band--ink h1, .band--ink h2, .band--ink h3, .band--ink h4 { color: var(--paper); }
.band--ink .lede, .band--ink p { color: var(--muted-dark); }

/* Section head. A short gold rule then the label, the reference's own form,
   not a floating pill. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin-bottom: 1.5rem;
}
.eyebrow::before {
  content: "";
  width: 30px;
  height: 1px;
  background: var(--gold);
  flex: none;
}
.band--ink .eyebrow { color: var(--gold); }

.section-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
  margin-bottom: clamp(2.5rem, 4vw, 4rem);
}
.section-head--split {
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: end;
}
.section-head--centre { justify-items: center; text-align: center; max-width: 62ch; margin-inline: auto; }

.lede {
  font-size: var(--fs-lead);
  line-height: 1.62;
  color: var(--muted);
  max-width: 58ch;
}

.measure { max-width: 66ch; }

/* --------------------------------------------------------------------------
   4. Buttons and links
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.95rem 1.65rem;
  border: 1px solid transparent;
  border-radius: var(--r-chip);
  font-family: var(--display);
  font-size: 0.875rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast) var(--ease-soft),
              color var(--t-fast) var(--ease-soft),
              border-color var(--t-fast) var(--ease-soft),
              transform var(--t-fast) var(--ease);
}
.btn:hover { transform: translateY(-2px); }
/* .btn:active is stated once, in section 30.3. */

.btn--gold { background: var(--gold); color: var(--ink); border-color: var(--gold); }
.btn--gold:hover { background: var(--gold-deep); border-color: var(--gold-deep); color: var(--paper); }

.btn--paper { background: var(--paper); color: var(--ink); border-color: var(--paper); }
.btn--paper:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

.btn--outline { background: var(--paper); color: var(--ink); border-color: color-mix(in srgb, var(--ink) 32%, transparent); }
.btn--outline:hover { border-color: var(--ink); background: var(--ink); color: var(--paper); }

.btn--wide { padding-inline: 2.25rem; }
/* nowrap plus a long label was widening the document. */
.btn { max-width: 100%; }

/* Underline link: draws in from the left on hover */
.link-line {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--display);
  font-size: 0.8125rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
  padding-bottom: 4px;
  position: relative;
}
.link-line::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  height: 1px; width: 100%;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-mid) var(--ease);
}
.link-line:hover::after { transform: scaleX(1); }
.link-line .arw { transition: transform var(--t-mid) var(--ease); }
.link-line:hover .arw { transform: translateX(4px); }
.band--ink .link-line { color: var(--paper); }

/* --------------------------------------------------------------------------
   5. Header
   -------------------------------------------------------------------------- */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background var(--t-mid) var(--ease-soft),
              border-color var(--t-mid) var(--ease-soft),
              height var(--t-mid) var(--ease-soft);
}
.site-header.is-solid {
  background: color-mix(in srgb, var(--ink-deep) 94%, transparent);
  backdrop-filter: saturate(140%) blur(14px);
  border-bottom-color: var(--line-dark);
  height: var(--header-h-solid);
}
.site-header { padding-top: var(--safe-top); }
/* Pages that start on paper rather than a photo need the solid bar from the
   first frame, otherwise white marks sit on white. */
.site-header.is-opaque {
  background: var(--ink-deep);
  border-bottom-color: var(--line-dark);
}

.header-inner {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
}

.brand { display: inline-flex; align-items: center; flex: none; }
.brand img { height: 46px; width: auto; }

.nav { margin-left: auto; }
.nav-list { display: flex; align-items: center; gap: clamp(1.1rem, 2.2vw, 2.1rem); }
.nav-list a {
  font-family: var(--display);
  font-size: 0.8125rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--paper) 88%, transparent);
  padding-block: 0.5rem;
  position: relative;
  transition: color var(--t-fast) var(--ease-soft);
}
.nav-list a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-mid) var(--ease);
}
.nav-list a:hover { color: var(--paper); }
.nav-list a:hover::after,
.nav-list a[aria-current="page"]::after { transform: scaleX(1); }
.nav-list a[aria-current="page"] { color: var(--paper); }

.header-cta { flex: none; margin-left: clamp(0.75rem, 2vw, 1.75rem); }
/* The action, the number and the address belong to the phone panel only. */
.nav-extra { display: none; }

.nav-toggle {
  display: none;
  gap: 0.6rem;
  min-width: 44px; height: 44px;
  padding-inline: 0.75rem;
  align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid var(--line-dark);
  border-radius: var(--r-chip);
  color: var(--paper);
  cursor: pointer;
  margin-left: auto;
}
.nav-toggle__label {
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  line-height: 1;
}
.nav-toggle span:not(.nav-toggle__label) {
  display: block; width: 18px; height: 1.5px; flex: none;
  background: currentColor; position: relative;
  transition: background var(--t-fast) var(--ease-soft);
}
.nav-toggle span:not(.nav-toggle__label)::before,
.nav-toggle span:not(.nav-toggle__label)::after {
  content: ""; position: absolute; left: 0;
  width: 18px; height: 1.5px; background: currentColor;
  transition: transform var(--t-mid) var(--ease), top var(--t-mid) var(--ease);
}
.nav-toggle span:not(.nav-toggle__label)::before { top: -6px; }
.nav-toggle span:not(.nav-toggle__label)::after { top: 6px; }
.nav-toggle[aria-expanded="true"] span:not(.nav-toggle__label) { background: transparent; }
.nav-toggle[aria-expanded="true"] span:not(.nav-toggle__label)::before { top: 0; transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:not(.nav-toggle__label)::after { top: 0; transform: rotate(-45deg); }

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: min(100svh, 940px);
  display: flex;
  align-items: center;
  padding-top: calc(var(--header-h) + 3rem);
  padding-bottom: clamp(3.5rem, 7vw, 6rem);
  isolation: isolate;
  overflow: hidden;
}
.hero__media { position: absolute; inset: 0; z-index: -2; background: var(--ink-deep); }
.hero__media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 58%;
}
/* The drift and the three-frame crossfade were taken out on 2026-09-15 with
   the pause button they needed (WCAG 2.2.2). Edgar: the pause "should've
   never been there". One photograph, still. */

.hero__scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--ink-deep) 78%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 68%, transparent) 30%,
      color-mix(in srgb, var(--ink-deep) 68%, transparent) 70%,
      color-mix(in srgb, var(--ink-deep) 80%, transparent) 100%);
}

/* Centred, per Edgar's ruling: the Helix and Tee-to-Trail hero shape. */
.hero__inner { position: relative; width: 100%; text-align: center; }

.hero__title {
  font-size: var(--fs-hero);
  line-height: 1.02;
  letter-spacing: -0.045em;
  color: var(--paper);
  max-width: 34ch;
  margin-inline: auto;
  margin-bottom: 1.5rem;
}
.hero__title .gold { color: var(--gold); } /* the button gold, Edgar 2026-09-15 */

.hero__lede {
  color: color-mix(in srgb, var(--paper) 88%, transparent);
  font-size: var(--fs-lead);
  line-height: 1.7;
  max-width: 46ch;
  margin-inline: auto;
  margin-bottom: 2.25rem;
}

.hero__actions { display: flex; flex-wrap: wrap; gap: 0.85rem; justify-content: center; }

/* Hero word rise. Each line lifts in behind a clip, the reference's entrance. */
.rise { display: block; overflow: hidden; }
.rise > span {
  display: block;
  transform: translateY(105%);
  animation: riseUp 900ms var(--ease) forwards;
}
.rise:nth-of-type(2) > span { animation-delay: 90ms; }
.rise:nth-of-type(3) > span { animation-delay: 180ms; }
@keyframes riseUp { to { transform: translateY(0); } }

/* The lede rises into place, it does not fade in. It used to start at
   opacity: 0 behind a 340ms delay, and it is the largest text on the page, so
   it WAS the Largest Contentful Paint element and the paint could not be
   recorded until the fade had run. Measured on throttled mobile, three runs:
   LCP landed exactly ~1000ms after FCP every time, and under
   prefers-reduced-motion (where line 2104 forces opacity to 1) LCP equalled
   FCP. The entrance is now transform only, so the words are painted on the
   first frame and the movement costs nothing. Never gate the LCP element on
   opacity. */
.hero__fade {
  transform: translateY(14px);
  animation: riseSoft 800ms var(--ease) 340ms forwards;
}
.hero__fade--late { animation-delay: 460ms; }
@keyframes riseSoft { to { transform: none; } }

/* --------------------------------------------------------------------------
   7. Search rail: the ERFDEVCO move. Fused to the hero's bottom edge so the
   inventory is the first thing the page offers, not a decoration.
   -------------------------------------------------------------------------- */
.searchrail { position: relative; z-index: 5; margin-top: -1px; }
.searchrail__inner {
  background: var(--ink-deep);
  border-top: 2px solid var(--gold);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
  align-items: stretch;
}
.searchrail__field {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 1.4rem clamp(1rem, 2vw, 1.9rem);
  border-right: 1px solid var(--line-dark);
  min-width: 0;
}
.searchrail__field label {
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gold);
}
.searchrail__field select {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  color: var(--paper);
  font-family: var(--display);
  font-size: 1rem;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--paper) 50%),
                    linear-gradient(135deg, var(--paper) 50%, transparent 50%);
  background-position: right 6px center, right 1px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 20px;
  text-overflow: ellipsis;
}
.searchrail__field select option { background: var(--ink-deep); color: var(--paper); }
.searchrail__submit {
  border: 0;
  background: var(--gold);
  color: var(--ink);
  font-family: var(--display);
  font-size: 0.8125rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding-inline: clamp(1.5rem, 3vw, 2.75rem);
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 0.6rem;
  transition: background var(--t-fast) var(--ease-soft), color var(--t-fast) var(--ease-soft);
}
.searchrail__submit:hover { background: var(--paper); color: var(--ink); }

/* --------------------------------------------------------------------------
   8. Farm cards
   -------------------------------------------------------------------------- */
.farm-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: clamp(1.25rem, 2.4vw, 2.2rem);
}
/* Every card the same size, everywhere. The home page runs two across, the
   listings page three, and a card is a card either way. */
.farm-grid > * { grid-column: span 3; }

/* The listings page is an even three across: every card the same shape, so
   nine farms read as one inventory rather than a feature plus leftovers. */
.farm-grid--even > * { grid-column: span 2 !important; }

.farm-card {
  display: flex; flex-direction: column; height: 100%;
  background: var(--paper);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  overflow: hidden;
  transition: border-color var(--t-mid) var(--ease-soft),
              box-shadow var(--t-mid) var(--ease-soft),
              transform var(--t-mid) var(--ease);
}
.farm-card:hover {
  border-color: color-mix(in srgb, var(--ink) 22%, transparent);
  box-shadow: 0 18px 40px -24px color-mix(in srgb, var(--ink) 55%, transparent);
  transform: translateY(-3px);
}

.farm-card__media {
  display: block;              /* it is an <a>, and inline boxes ignore aspect-ratio */
  position: relative;
  overflow: hidden;
  border-radius: 0;            /* the card carries the radius now */
  background: var(--stone-2);
  aspect-ratio: 4 / 3;
}
/* The photo link fills the frame; the chips and the save button sit above it. */
.farm-card__media > a { position: absolute; inset: 0; display: block; }
.farm-card__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* filter was in this list and nothing ever changes it; the hover scale that
     sat under here is set back to none at 6558 and 6787, so both were dead
     (audit 12, 2026-09-16). */
  transition: transform 900ms var(--ease);
}
.farm-card__ref, .farm-card__type, .farm-card__status, .farm-card .save-btn { z-index: 2; }

.farm-card__ref {
  position: absolute;
  left: 14px; top: 14px;
  background: color-mix(in srgb, var(--ink-deep) 84%, transparent);
  backdrop-filter: blur(6px);
  color: var(--paper);
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  padding: 0.4rem 0.7rem;
  border-radius: var(--r-chip);
}
.farm-card__type {
  position: absolute;
  right: 14px; top: 14px;
  background: var(--gold);
  color: var(--ink);
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.4rem 0.7rem;
  border-radius: var(--r-chip);
}

.farm-card__body {
  padding: 1.35rem clamp(1rem, 1.6vw, 1.35rem) 0;
  display: flex; flex-direction: column; flex: 1;
}

.farm-card__row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
}
.farm-card__title { margin-bottom: 0.35rem; min-height: 2.4em; }
.farm-card:hover .farm-card__title { color: var(--gold-deep); }
.farm-card__title { transition: color var(--t-fast) var(--ease-soft); }

.farm-card__place {
  font-size: 0.9375rem;
  color: var(--muted);
}
.farm-card__price {
  font-family: var(--display);
  font-size: clamp(1.15rem, 1.5vw, 1.5rem);
  letter-spacing: -0.03em;
  color: var(--gold-deep);
  white-space: nowrap;
}

.farm-card__meta {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
  margin: 1.15rem calc(clamp(1rem, 1.6vw, 1.35rem) * -1) 0;
  padding: 0.9rem clamp(1rem, 1.6vw, 1.35rem);
  border-top: 1px solid var(--line);
  background: var(--stone);
}
.farm-card__meta span {
  display: flex; flex-direction: column; gap: 0.15rem;
  padding-right: 0.6rem;
  min-width: 0;
}
.farm-card__meta span + span {
  padding-left: 0.75rem;
  border-left: 1px solid var(--line);
}
.farm-card__meta b {
  font-weight: 400;
  color: var(--ink);
  font-family: var(--display);
  font-size: clamp(1rem, 1.25vw, 1.15rem);
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.farm-card__meta i {
  font-style: normal;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-family: var(--display);
  color: var(--muted);
  line-height: 1.25;
}
/* One action, one supporting note. The compare tick moved into its own quiet
   row so three controls stop competing at the same weight. */
.farm-card__foot {
  margin-top: auto;
  padding: 1rem clamp(1rem, 1.6vw, 1.35rem) 0;
  margin-inline: calc(clamp(1rem, 1.6vw, 1.35rem) * -1);
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem 1.25rem; flex-wrap: wrap;
}
.farm-card__foot--sub {
  margin-top: 0;
  padding-top: 0.75rem;
  padding-bottom: 1.1rem;
  border-top: 1px solid var(--line-soft);
  margin-top: 1rem;
}

/* Status chip: only a genuinely new listing gets the gold treatment. */
.farm-card__status {
  position: absolute;
  left: 14px; bottom: 14px;
  display: inline-flex; align-items: center;
  background: color-mix(in srgb, var(--ink-deep) 90%, transparent);
  backdrop-filter: blur(6px);
  color: var(--paper);
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.45rem 0.8rem;
  border-radius: var(--r-chip);
}
/* No dot. A new listing earns a gold ground, not a gold speck. */
.farm-card__status[data-new] {
  background: var(--gold);
  color: var(--ink);
}

/* Save to shortlist. Stored per browser, never sent anywhere. */
.save-btn {
  position: absolute;
  right: 14px; bottom: 14px;
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--ink-deep) 78%, transparent);
  backdrop-filter: blur(6px);
  border: 1px solid color-mix(in srgb, var(--paper) 26%, transparent);
  border-radius: 50%;
  color: var(--paper);
  cursor: pointer;
  padding: 0;
  transition: background var(--t-fast) var(--ease-soft),
              border-color var(--t-fast) var(--ease-soft),
              color var(--t-fast) var(--ease-soft),
              transform var(--t-fast) var(--ease);
}
.save-btn:hover { transform: scale(1.08); border-color: var(--gold); color: var(--gold); }
.save-btn svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.6; }
.save-btn[aria-pressed="true"] { background: var(--gold); border-color: var(--gold); color: var(--ink); }
.save-btn[aria-pressed="true"] svg { fill: currentColor; }
.save-btn[aria-pressed="true"]:hover { color: var(--ink); }

/* The same control inline on a detail page, where there is no photo behind it */
.save-btn--inline {
  position: static;
  width: auto; height: auto;
  border-radius: var(--r-chip);
  background: transparent;
  border-color: var(--line);
  color: var(--ink);
  gap: 0.55rem;
  padding: 0.85rem 1.3rem;
  font-family: var(--display);
  font-size: 0.8125rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  backdrop-filter: none;
}
.save-btn--inline:hover { transform: none; border-color: var(--ink); color: var(--ink); }
.save-btn--inline[aria-pressed="true"] { background: var(--gold); border-color: var(--gold); color: var(--ink); }

/* Fact count: the site's whole argument, stated per farm. */
.farm-card__facts {
  display: inline-flex; align-items: center;
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--muted);
  white-space: nowrap;
}

/* Listings toolbar: count left, sort right */
.toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding-bottom: 1.25rem;
  margin-bottom: clamp(1.75rem, 3vw, 2.5rem);
  border-bottom: 1px solid var(--line);
}
.toolbar label {
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: 0.6rem;
}
.toolbar select {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-chip);
  padding: 0.55rem 0.9rem;
  font-family: var(--display);
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  color: var(--ink);
  cursor: pointer;
}
.toolbar select:hover { border-color: var(--ink); }

/* Listings page rows keep the same card, one per row on narrow screens */
.farm-empty {
  border: 1px dashed var(--line);
  border-radius: var(--r-card);
  padding: clamp(2rem, 5vw, 4rem);
  text-align: center;
  color: var(--muted);
  grid-column: 1 / -1;
}
.farm-empty h3 { margin-bottom: 0.75rem; }

/* Filter chips */
/* --------------------------------------------------------------------------
   Farm type menu. Replaces the chip row: one control that names the current
   choice and carries the count behind each option, so you can see what you
   are about to load before you load it. Panel on desktop, sheet on a phone.
   -------------------------------------------------------------------------- */
.typemenu { position: relative; }
.typemenu__btn {
  display: inline-flex; align-items: baseline; gap: 0.75rem;
  min-height: 48px;
  padding: 0.6rem 1rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-chip);
  font-family: var(--display);
  cursor: pointer;
  color: var(--ink);
  transition: border-color var(--t-fast) var(--ease-soft);
}
.typemenu__btn:hover { border-color: var(--ink); }
.typemenu__btn.is-set { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.typemenu__label {
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.typemenu__btn.is-set .typemenu__label { color: color-mix(in srgb, var(--paper) 62%, transparent); }
.typemenu__value { font-size: 0.9375rem; letter-spacing: 0.02em; }
.typemenu__chev {
  width: 9px; height: 9px; align-self: center;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--t-fast) var(--ease-soft);
}
.typemenu__btn[aria-expanded="true"] .typemenu__chev { transform: translateY(1px) rotate(-135deg); }

.typemenu__sheet {
  position: absolute; top: calc(100% + 0.5rem); left: 0; z-index: 30;
  min-width: 17rem;
  padding: 0.4rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: 0 18px 44px -20px color-mix(in srgb, var(--ink) 42%, transparent);
}
.typemenu__title {
  margin: 0.35rem 0.6rem 0.5rem;
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.typemenu__opt {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1.25rem;
  width: 100%;
  min-height: 44px;
  padding: 0.6rem 0.6rem;
  background: transparent; border: 0; border-radius: 3px;
  font-family: var(--display); font-size: 0.9375rem;
  color: var(--ink); text-align: left; cursor: pointer;
  transition: background var(--t-fast) var(--ease-soft);
}
.typemenu__opt:hover { background: color-mix(in srgb, var(--ink) 6%, transparent); }
.typemenu__opt .n { font-size: 0.8125rem; color: var(--gold-deep); font-variant-numeric: tabular-nums; }
.typemenu__opt[aria-selected="true"] { background: var(--ink); color: var(--paper); }
.typemenu__opt[aria-selected="true"] .n { color: var(--gold); }
.typemenu__scrim { display: none; }

@media (max-width: 700px) {
  /* A sheet, not a dropdown: a 10-item panel hanging off a button is unusable
     on a phone, and the list is the whole decision. */
  /* The button is 100% of .typemenu, so .typemenu has to be told to fill the
     row first or 100% is just the content width. */
  .typemenu { width: 100%; }
  .typemenu__btn { width: 100%; justify-content: space-between; align-items: center; }
  .typemenu__sheet {
    position: fixed; inset: auto 0 0 0; top: auto; z-index: 60;
    min-width: 0;
    max-height: 78svh;
    overflow-y: auto;
    padding: 0.5rem 0.5rem calc(0.5rem + var(--safe-bottom));
    border-radius: var(--r-card) var(--r-card) 0 0;
    border-bottom: 0;
    box-shadow: 0 -20px 50px -18px color-mix(in srgb, var(--ink) 55%, transparent);
  }
  .typemenu__title { margin-top: 0.6rem; }
  .typemenu__opt { min-height: 52px; font-size: 1rem; }
  /* :not([hidden]) is not decoration. An author display rule beats the
     browser's own [hidden] rule, so without it the closed scrim covered the
     whole viewport at z-index 55 and nothing on the page could be tapped at
     700px and under. Same failure class as .cmp-acts, .chat-panel,
     .chat-chips and .chat-backdrop, which all restate it. */
  .typemenu__scrim:not([hidden]) {
    display: block;
    position: fixed; inset: 0; z-index: 55;
    background: color-mix(in srgb, var(--ink-deep) 55%, transparent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .typemenu__btn, .typemenu__chev, .typemenu__opt { transition: none; }
}

.filters {
  display: flex; flex-wrap: wrap; gap: 0.5rem;
  margin-bottom: clamp(2rem, 3vw, 3rem);
}
.filter-btn {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-chip);
  padding: 0.6rem 1.05rem;
  font-family: var(--display);
  font-size: 0.875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease-soft),
              border-color var(--t-fast) var(--ease-soft),
              color var(--t-fast) var(--ease-soft),
              transform var(--t-fast) var(--ease-soft);
}
.filter-btn:hover { border-color: var(--ink); color: var(--ink); }
.filter-btn[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }

.results-line {
  font-size: 0.875rem;
  color: var(--muted);
  margin-bottom: 1.5rem;
}

/* --------------------------------------------------------------------------
   9. Split sections
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Requirement recap. The closing band used to be a heading, a paragraph and a
   button floating in an otherwise empty half. The right side now states what
   we would actually watch for, taken live from the filters above, so the ask
   is specific before anyone clicks it.
   -------------------------------------------------------------------------- */
.reqbox__label {
  margin: 0 0 0.9rem;
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.reqbox__rows { margin: 0 0 1.75rem; border-top: 1px solid var(--line); }
.reqbox__rows > div {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1.5rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--line);
}
.reqbox__rows dt {
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.reqbox__rows dd {
  margin: 0;
  font-family: var(--display);
  font-size: 0.9375rem;
  color: var(--ink);
  text-align: right;
}
/* A value the visitor actually set reads as chosen, not as a default. */
.reqbox__rows dd[data-set="true"] { color: var(--gold-deep); }

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 5.5rem);
  align-items: center;
}
.split--wide-left { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); }
.split--wide-right { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
.split--top { align-items: start; }

.split__media { position: relative; }
.split__media img { width: 100%; border-radius: var(--r-media); object-fit: cover; }

/* Offset image pair, the reference's about block */
.media-pair { position: relative; padding-bottom: clamp(2.5rem, 5vw, 4.5rem); }
/* Landscape, not portrait: the 4/5 column ran far past the text beside it
   and left the section mostly open ground. */
.media-pair__main { aspect-ratio: 1 / 0.92; }
.media-pair__main img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--r-media); }
.media-pair__inset {
  position: absolute;
  right: clamp(-1.5rem, -2vw, -0.5rem);
  bottom: 0;
  width: 54%;
  aspect-ratio: 4 / 3;
  border: 8px solid var(--paper);
  border-radius: calc(var(--r-media) + 4px);
  overflow: hidden;
}
.band--stone .media-pair__inset { border-color: var(--stone); }
.media-pair__inset img { width: 100%; height: 100%; object-fit: cover; }

.stat-strip {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem 2rem;
  margin-top: 2.5rem;
  padding-top: 2.25rem;
  border-top: 1px solid var(--line);
}
.stat-strip dt {
  font-family: var(--display);
  font-size: clamp(1.75rem, 2.6vw, 2.5rem);
  letter-spacing: -0.04em;
  color: var(--ink);
  line-height: 1;
}
.stat-strip dd {
  margin: 0.5rem 0 0;
  font-size: 0.8125rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  font-family: var(--display);
}

/* --------------------------------------------------------------------------
   10. Counter band
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   11b. The country. The reach numbers hang off the actual shape of South
   Africa rather than sitting in a row of four, so the scale is seen instead
   of counted. Province geometry is inlined and draws without JavaScript;
   the pins are added from the listings file.
   -------------------------------------------------------------------------- */
.country {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}
.country__map svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 66vh;
}
/* The map is the brand pairing, not a grey diagram: provinces we hold
   mandates in are the estate green, the rest sit back as pale ground, and
   the gold pins read off the dark the way gold on ink does everywhere else. */
.country__map .prov {
  /* Warm sand, not grey: a province we hold nothing in is still farmland.
     Mixed from --sage, the band this map actually sits on, not from --canvas.
     Derived from --canvas it landed 1.04 L* off the sage ground (measured
     2026-09-08), so North West and Gauteng dissolved into the band and the
     country lost its northern edge. This is the SAME value the listings map
     already used (section 3, .band--sage .prov), so one empty-province fill
     exists site-wide; it sits about 7 L* under the ground, which reads as
     land while still reading as a province we hold nothing in. */
  fill: color-mix(in srgb, var(--gold) 13%, color-mix(in srgb, var(--ink) 7%, var(--sage)));
  stroke: var(--canvas);
  stroke-width: 2;
}
.country__map .prov.has-farms { fill: var(--ink); }
/* The home map is a picture, not a control: nothing here filters anything,
   so it must not light up under the cursor the way the listings map does. */
.country__map .prov.has-farms { cursor: default; }
.country__map .prov.has-farms:hover { fill: var(--ink); }
/* Raised off the deep green: full --gold goes muddy at pin size on ink, so
   the disc is lifted towards paper and outlined in the ground it sits on. */
.country__map .halo { fill: var(--gold); opacity: 0.55; }
/* The halo is decoration and it is 36 units across, wider than the gap two
   pins in one district can honestly be given. While it took taps, a pointer on
   one pin resolved to its neighbour and opened the wrong farm. The dot is the
   control. */
.pins .pin .halo { pointer-events: none; }
.country__map .dot {
  /* The map draws at ~0.6 of its viewBox, so the authored r:8 lands under
     5 real pixels. Grown here rather than in the markup, which the listings
     map shares. */
  r: 10;
  fill: color-mix(in srgb, var(--gold) 76%, var(--paper));
  stroke: var(--ink);
  stroke-width: 2.5;
}
/* The map draws itself when the band arrives: the country is stroked in,
   the province fills come up behind the line, then the farm pins drop one
   after another, which is the only part of the move that carries meaning.
   Held only where JS is running and the visitor has not asked for less
   motion, so the map is never blank for anyone. script.js adds is-drawn on
   arrival, without an observer, and again on a safety sweep. */
@media (prefers-reduced-motion: no-preference) {
  .js .country__map .prov {
    fill-opacity: 0;
    stroke: var(--ink);
    stroke-width: 1.4;
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
  }
  .js .country__map .pins .pin { opacity: 0; }
  .js .country__map.is-drawn .prov {
    animation: prov-draw 1000ms var(--ease-soft) both, prov-settle 560ms var(--ease-soft) 620ms both;
  }
  .js .country__map.is-drawn .pins .pin {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    animation: pin-drop 620ms var(--ease) both;
    animation-delay: calc(1000ms + var(--i, 0) * 70ms);
  }
  @keyframes prov-draw { to { stroke-dashoffset: 0; } }
  @keyframes prov-settle { to { fill-opacity: 1; stroke: var(--canvas); stroke-width: 2; } }
  @keyframes pin-drop {
    from { opacity: 0; transform: translateY(-16px) scale(0.55); }
    to { opacity: 1; transform: none; }
  }
}

.country__side .lede { margin-top: 1.25rem; }
.country__facts {
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  display: grid;
  border-top: 1px solid var(--line);
}
.country__facts > div {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: clamp(1rem, 2vw, 1.5rem);
  align-items: baseline;
  padding: 1.1rem 0;
  border-bottom: 1px solid var(--line);
}
.country__facts dt {
  font-family: var(--display);
  /* Sized to fill its column so the number reads as the point of the row,
     not as a marker sitting a long way from its label. */
  font-size: clamp(2.4rem, 3.6vw, 3.4rem);
  letter-spacing: -0.05em;
  line-height: 1;
  color: var(--ink);
}
.country__facts dd { margin: 0; font-size: 0.9375rem; color: var(--muted); }
.country__cta { margin-top: 1.75rem; }

@media (max-width: 900px) {
  .country { grid-template-columns: minmax(0, 1fr); }
  .country__map svg { max-height: 46vh; margin-inline: auto; }
  .country__facts > div { grid-template-columns: 3rem minmax(0, 1fr); gap: 1rem; }
}

/* --------------------------------------------------------------------------
   11c. Contact details as a band, not a card. Four labelled columns on ink,
   read like a letterhead, so the enquiry form underneath gets the page
   instead of being squeezed beside a panel.
   -------------------------------------------------------------------------- */
.cband {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--line);
}
.cband > div {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.5rem 1.25rem 1.5rem 0;
  border-right: 1px solid var(--line);
}
.cband > div:last-child { border-right: 0; }
.cband > div + div { padding-left: 1.25rem; }
.cband span {
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-deep);
}
.cband a,
.cband b {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: -0.02em;
  font-size: clamp(1rem, 1.5vw, 1.3rem);
  color: var(--ink);
  word-break: break-word;
  transition: color var(--t-fast) var(--ease-soft);
}
.cband a:hover { color: var(--gold-deep); }
.cband__foot {
  margin-top: 1.5rem;
  max-width: 78ch;
  font-size: 0.9375rem;
  color: var(--muted);
}
/* Wide, but not so wide the fields become unreadable lines. */
.form-wide { max-width: 62rem; }
/* On the contact page the letterhead is part of the enquiry, not a band of
   its own: same ground, close under the message box, so the two read as one
   block instead of a colour change opening a gap between them. */
.cband--under-form { margin-top: clamp(2.25rem, 3.6vw, 3.25rem); }

@media (max-width: 900px) {
  .cband { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cband > div:nth-child(2n) { border-right: 0; }
}
@media (max-width: 560px) {
  .cband { grid-template-columns: minmax(0, 1fr); }
  /* Media queries add no specificity, so this has to match the weight of
     `.cband > div + div` or the desktop indent survives on a phone. */
  .cband > div,
  .cband > div + div {
    border-right: 0;
    border-bottom: 1px solid var(--line);
    padding-left: 0;
  }
}

.counters {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--line);
}
.counter {
  padding: clamp(2rem, 3.4vw, 3rem) clamp(1rem, 2vw, 2rem);
  border-right: 1px solid var(--line);
}
.counter:last-child { border-right: 0; }
/* A two-up arrangement needs the row divider under BOTH cells of the first
   row, not just the one that happened to carry an inline border. Scoped above
   the 900px breakpoint, where the four-up already collapses to this shape and
   sets the same rules. */
@media (min-width: 901px) {
  .counters--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .counters--2 .counter:nth-child(2n) { border-right: 0; }
  .counters--2 .counter:nth-child(-n+2) { border-bottom: 1px solid var(--line); }
}
.counter__n {
  font-family: var(--display);
  font-size: clamp(2.75rem, 5.4vw, 4.75rem);
  line-height: 0.95;
  letter-spacing: -0.055em;
  color: var(--ink);
  display: flex; align-items: baseline; gap: 0.06em;
}
.counter__n .suffix { font-size: 0.44em; color: var(--gold-deep); letter-spacing: -0.02em; }
.counter__label {
  margin-top: 0.85rem;
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.counter__note { margin-top: 0.5rem; font-size: 0.8125rem; color: var(--muted); }

/* --------------------------------------------------------------------------
   11. Dark chapter: browse by farm type
   -------------------------------------------------------------------------- */
/* A 3 x 3 block with the feature tile taking the top left quarter. Six tiles
   fill nine cells exactly, so the section never ends on an empty row. */
.type-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* A floor, not a fixed height: at 200% text the label is taller than the
     tile and the row grows to hold it instead of cutting it off (31.2). */
  --tile-row: clamp(190px, 15.5vw, 250px);
  grid-auto-rows: minmax(var(--tile-row), auto);
  gap: clamp(0.75rem, 1.4vw, 1.25rem);
}
.type-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: clamp(1rem, 2vw, 1.6rem) clamp(1rem, 2vw, 1.75rem);
  overflow: hidden;
  border-radius: var(--r-media);
  isolation: isolate;
}
.type-tile:nth-child(1) { grid-column: span 2; grid-row: span 2; }
.type-tile img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 1000ms var(--ease);
}
.type-tile::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--ink-deep) 6%, transparent) 30%,
    color-mix(in srgb, var(--ink-deep) 55%, transparent) 72%,
    color-mix(in srgb, var(--ink-deep) 92%, transparent) 100%);
  transition: background var(--t-mid) var(--ease-soft);
}
.type-tile:hover img { transform: scale(1.06); }
.type-tile:hover::after { background: linear-gradient(180deg,
    color-mix(in srgb, var(--ink-deep) 20%, transparent) 20%,
    color-mix(in srgb, var(--ink-deep) 66%, transparent) 72%,
    color-mix(in srgb, var(--ink-deep) 95%, transparent) 100%); }

.type-tile__label {
  position: relative;
  z-index: 2;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem;
}
/* The heading is a flex container, so its text is an anonymous flex item
   whose minimum is the whole word: only anywhere lets "Smallholdings" break
   at 200% text instead of running 92px past the tile. */
.type-tile__label h3 { color: var(--paper); min-width: 0; overflow-wrap: anywhere; }
.type-tile__count {
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  /* pale gold, not the deep --gold: the tiles sit on warm photography
     where #c8900a disappears into the crop. */
  color: color-mix(in srgb, var(--gold) 26%, var(--paper));
  white-space: nowrap;
  padding-bottom: 4px;
}

.type-more {
  display: flex; flex-wrap: wrap; gap: 0.5rem;
  margin-top: clamp(1.25rem, 2vw, 1.75rem);
}
.type-more a {
  border: 1px solid var(--line-dark);
  border-radius: var(--r-chip);
  padding: 0.6rem 1.05rem;
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--paper) 78%, transparent);
  transition: background-color var(--t-fast) var(--ease-soft),
              border-color var(--t-fast) var(--ease-soft),
              color var(--t-fast) var(--ease-soft),
              transform var(--t-fast) var(--ease-soft);
}
.type-more a:hover { background: var(--gold); border-color: var(--gold); color: var(--ink); }

/* --------------------------------------------------------------------------
   12. The five questions, as a plain FAQ. Native details/summary, no JS.
   -------------------------------------------------------------------------- */
.faq-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: stretch;
}
/* The image fills whatever height the FAQ list sets, never the other way
   round: absolutely positioned so its portrait ratio cannot stretch the row. */
.faq-photo { margin: 0; position: relative; min-height: 340px; }
.faq-photo img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: var(--r-media);
  /* Dissolved into the band at the top rather than cut off by an edge.
     Masked, not overlaid, so it works on whatever ground it lands on. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 24%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 24%);
}
@media (max-width: 900px) {
  .faq-wrap { grid-template-columns: minmax(0, 1fr); }
  .faq-photo { min-height: 0; }
  .faq-photo img { position: static; aspect-ratio: 16 / 10; height: auto; }
}
.faq { border-top: 1px solid var(--line); }
.faq-card { border-bottom: 1px solid var(--line); }
.faq-card summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 1.35rem;
  font-family: var(--display);
  font-size: 1.0625rem;
  letter-spacing: 0.01em;
  color: var(--ink);
  transition: color var(--t-fast) var(--ease-soft);
}
.faq-card summary::-webkit-details-marker { display: none; }
.faq-card summary::after {
  content: "+";
  flex: none;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--gold-deep);
  transition: rotate var(--t-fast) var(--ease);
}
.faq-card[open] summary::after { rotate: 45deg; }
.faq-card summary:hover { color: var(--gold-deep); }
.faq-card__a { padding-bottom: 1.6rem; max-width: 62ch; }
.faq-card__a p { color: var(--muted); line-height: 1.72; }
.faq-card__a a {
  color: var(--gold-deep);
  border-bottom: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  transition: border-color var(--t-fast) var(--ease-soft);
}
.faq-card__a a:hover { border-bottom-color: var(--gold-deep); }
@media (prefers-reduced-motion: reduce) {
  .faq-card summary::after { transition: none; }
}

/* --------------------------------------------------------------------------
   13. Credentials list (about page)
   -------------------------------------------------------------------------- */
.credentials { display: flex; flex-wrap: wrap; gap: 0.5rem 2rem; margin-top: 1.75rem; }
.credentials li {
  font-size: 0.8125rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-family: var(--display);
  color: color-mix(in srgb, var(--paper) 72%, transparent);
  display: flex; align-items: center;
}
.credentials li + li { border-left: 1px solid color-mix(in srgb, var(--paper) 28%, transparent); padding-left: 2rem; margin-left: -1rem; }
/* On light grounds (the about page reuses this list) the rule takes the light line. */
.band .credentials li + li { border-left-color: var(--line); }

/* --------------------------------------------------------------------------
   14. Service rows (flat, not a card grid)
   -------------------------------------------------------------------------- */
.rows { border-top: 1px solid var(--line); }
.row-item {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.25fr);
  gap: clamp(1rem, 3vw, 3rem);
  align-items: baseline;
  padding: clamp(1.5rem, 2.6vw, 2.25rem) 0;
  border-bottom: 1px solid var(--line);
}
/* The ground wipes in behind the row instead of the row growing padding, so
   nothing reflows on hover. */
.row-item::before {
  content: "";
  position: absolute;
  inset: 0 -1.25rem;
  background: var(--stone);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-mid) var(--ease);
  z-index: -1;
}
.row-item > * { position: relative; }
.row-item:hover::before { transform: scaleX(1); }
.row-item h3 { transition: color var(--t-fast) var(--ease-soft); }
.row-item:hover h3 { color: var(--gold-deep); }
.row-item__body p { color: var(--muted); font-size: 0.9375rem; line-height: 1.62; }

/* --------------------------------------------------------------------------
   15. CTA band
   -------------------------------------------------------------------------- */
.cta {
  position: relative;
  isolation: isolate;
  overflow: clip;
  padding-block: clamp(6rem, 12vw, 11rem);
}
.cta__media { position: absolute; inset: 0; z-index: -2; }
.cta__media img { width: 100%; height: 100%; object-fit: cover; }
/* Arrival moment. The clip lies exactly over its own still inside the media
   block, both cover-filling it, so the swap is a cross fade and never a jump.
   The still is the honest layer: it is what shows under reduced motion, on a
   metered connection, and whenever the clip cannot play. */
.arrival { position: absolute; inset: 0; margin: 0; overflow: hidden; }
.arrival__poster,
.arrival__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.arrival__video { opacity: 0; transition: opacity var(--t-slow) var(--ease-soft); }
.arrival.is-live .arrival__video { opacity: 1; }
/* Same specificity as the rule above, later in the file, so reduced motion
   wins: the clip is never shown, and script.js never loads it either. */
@media (prefers-reduced-motion: reduce) {
  .arrival__video { display: none; }
}
.cta__scrim {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(96deg,
    color-mix(in srgb, var(--ink-deep) 86%, transparent) 0%,
    color-mix(in srgb, var(--ink-deep) 72%, transparent) 38%,
    color-mix(in srgb, var(--ink-deep) 22%, transparent) 74%,
    color-mix(in srgb, var(--ink-deep) 6%, transparent) 100%);
}
/* Tablet is its own case, and it was the broken one. Below 1024px the copy
   column is most of the band's width, but the wash above was still built for a
   narrow column on a wide band and had faded to 18% ink by the time it reached
   the lede. The lede ran straight onto the lit dust plume: measured 1.68:1 at
   768 and 2.45:1 at 900, against a 4.5:1 floor. This holds the dark side out
   past the copy and lets go only where the tractor is. */
@media (min-width: 641px) and (max-width: 1023px) {
  .cta__scrim {
    background: linear-gradient(100deg,
      color-mix(in srgb, var(--ink-deep) 88%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 80%, transparent) 46%,
      color-mix(in srgb, var(--ink-deep) 60%, transparent) 78%,
      color-mix(in srgb, var(--ink-deep) 26%, transparent) 100%);
  }
}
/* One column held left. The right of the band is the photograph, which is
   why there is no second column asking to be filled.
   The headline and the lede share ONE measure. They used to be set separately,
   in ch of two different font sizes, so the headline came out narrower than its
   own paragraph at every width (400px against 511px at 1440, 265px against
   511px at 768): a hierarchy inversion you can see. Holding both to the column
   also keeps the lede on the scrim's dark side at tablet, where it ran onto lit
   field at 1.68:1 against a 4.5:1 floor. */
.cta__inner { max-width: 34rem; }
.cta h2 { color: var(--paper); max-width: 15ch; }
.cta p { color: color-mix(in srgb, var(--paper) 92%, transparent); }
/* Only the photographic band shares the one measure. .cta--flat is a different
   shape (a two-column split on a light ground, compare.html) and keeps the
   narrow headline cap it was built with. */
.cta:not(.cta--flat) h2 { max-width: 100%; }
.cta:not(.cta--flat) p { margin-top: 1.25rem; }
.cta__actions { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 2.25rem; }

/* The closing photograph pins to the top of the viewport and the section that
   follows slides up over it like a sheet of paper laid down on the table.
   Only where the band actually fits the viewport: on a short screen the
   pinned band would be cropped, so it stays in normal flow instead. */
@media (min-width: 1024px) and (min-height: 660px) {
  /* The hero holds still at the top of the viewport and the rest of the page
     is dragged up over it: the search rail first, then every band behind it.
     .searchrail already sits at z-index 5 on an opaque ink ground, so it
     needs nothing here. */
  .hero { position: sticky; top: 0; z-index: 0; }
  .hero ~ .band { position: relative; z-index: 1; }

  /* Every photograph hero on every page behaves the same way, not just the
     home one. The flat variants carry no picture and stay in flow. */
  .page-hero:not(.page-hero--flat) { position: sticky; top: 0; z-index: 0; }
  .page-hero ~ .band { position: relative; z-index: 1; }


}

@media (min-width: 1024px) and (min-height: 660px) {
  .cta:not(.cta--flat) {
    position: sticky;
    top: 0;
    z-index: 0;
    /* Taller than the viewport while it is held. The extra 50vh is pure dwell:
       the covering section has half a screen to climb before it reaches the
       headline, so the headline holds instead of being eaten the moment the
       band pins.
       The copy is centred in the FIRST screen rather than sitting in the top
       padding. Padding-led, the copy ended 374px above the fold with nothing
       under it, so 42% of the band you could actually see was empty field. */
    min-height: 150vh;
    display: grid;
    grid-template-rows: 100vh 50vh;
    padding-block: 0;
  }
  .cta:not(.cta--flat) > .shell {
    grid-row: 1;
    align-self: center;
  }
  /* The covering section has to be positioned to paint above a sticky
     stacking context, and carries the shadow that sells the overlap. */
  .cta + .band {
    position: relative;
    z-index: 1;
    box-shadow: 0 -28px 64px -28px color-mix(in srgb, var(--ink-deep) 55%, transparent);
  }
}

/* A closing band without a photograph. Same job, light ground, so a short
   page does not end on two dark blocks stacked on the footer. */
.cta--flat { isolation: auto; overflow: visible; }
.cta--flat .cta__inner { max-width: none; }
.cta--flat h2 { color: var(--ink); }
.cta--flat p { color: var(--muted); }
.cta--flat .cta__actions { margin-top: 0; }

/* --------------------------------------------------------------------------
   16. Page hero (inner pages)
   -------------------------------------------------------------------------- */
.page-hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-top: calc(var(--header-h) + clamp(3.5rem, 7vw, 6.5rem));
  padding-bottom: clamp(3rem, 6vw, 5.5rem);
  background: var(--ink-deep);
}
/* A hero carrying a photograph fills the screen, the same as the home page.
   The flat variants have no picture, so they keep their band proportions. */
.page-hero:not(.page-hero--flat) {
  min-height: 100svh;
  display: flex;
  align-items: center;
}
.page-hero:not(.page-hero--flat) > .shell { width: 100%; }
.page-hero__media { position: absolute; inset: 0; z-index: -2; }
/* A <picture> around the image would otherwise be the box the img measures its
   height against, and an inline box is auto-height: the photograph collapses. */
.page-hero__media picture,
.hero__media picture { display: contents; }
.page-hero__media img { width: 100%; height: 100%; object-fit: cover; }
.page-hero__scrim {
  position: absolute; inset: 0; z-index: -1;
  /* 84 at the text end, not 80: the about hero's lede measured 4.48 against a
     4.5 bar on the brightest leaf behind it at 1440. The far end is untouched,
     so the photograph still opens up away from the words. */
  background: linear-gradient(102deg,
    color-mix(in srgb, var(--ink-deep) 84%, transparent) 0%,
    color-mix(in srgb, var(--ink-deep) 62%, transparent) 48%,
    color-mix(in srgb, var(--ink-deep) 24%, transparent) 100%);
}
/* Held to the right where the photograph's subject sits left, one column so
   the block keeps a straight edge rather than three ragged ones. The scrim
   is mirrored with it, or the text would land on the bright side. */
/* Set against the right edge, so that is the edge that lines up. Each block
   keeps its own measure (18ch, 52ch) and hangs off the same right margin,
   ragged on the left. */
.page-hero--right .shell > * { margin-left: auto; margin-right: clamp(1.5rem, 4vw, 4rem); text-align: right; }
.page-hero--right .crumb { justify-content: flex-end; }
.page-hero--right .page-hero__scrim {
  /* Heavier than the left-hand original: this photograph is brightest exactly
     where the text now sits, so the lede was landing on sunlit grass. */
  background: linear-gradient(258deg,
    color-mix(in srgb, var(--ink-deep) 90%, transparent) 0%,
    color-mix(in srgb, var(--ink-deep) 74%, transparent) 46%,
    color-mix(in srgb, var(--ink-deep) 22%, transparent) 100%);
}
@media (max-width: 900px) {
  .page-hero--right .shell > * { margin-left: 0; text-align: left; }
  .page-hero--right .crumb { justify-content: flex-start; }
  .page-hero--right .page-hero__scrim {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--ink-deep) 55%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 82%, transparent) 100%);
  }
}
/* The trail's current page is 13px, which puts it on the 4.5:1 bar rather
   than 3:1. Over the listings photograph on a phone it measured 3.99:1 on the
   brightest pixel behind it, sky showing between the trees. 78% clears it on
   every photo hero without pulling level with the heading. */
.page-hero:not(.page-hero--flat) .crumb,
.page-hero:not(.page-hero--flat) .crumb span {
  color: color-mix(in srgb, var(--paper) 78%, transparent);
}
.page-hero h1 { color: var(--paper); max-width: 18ch; }
/* 88, not 80: at 17px the lede sits on the 4.5:1 bar, and measured on the real
   pixels behind it at 1440 the about hero came out 4.02 and contact 4.46. The
   heading is 64px of pure paper, so the hierarchy survives the lift. */
.page-hero .lede { color: color-mix(in srgb, var(--paper) 88%, transparent); margin-top: 1.25rem; }

/* Wraps since 2026-09-09: a long farm name (Baardskeerdersbos Village Stand)
   ran the trail 8px past a 360 screen and put a sideways scroll on the page. */
.crumb {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; row-gap: 0.35rem;
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--paper) 60%, transparent);
  margin-bottom: 1.5rem;
}
/* Breadcrumb links are a list of links, not links inside a sentence, so
   the inline exception to the target size minimum does not apply. */
.crumb a { display: inline-flex; align-items: center; min-height: 26px; }

.crumb a { color: var(--gold); }
/* On a photograph, not a ground. See --gold-on-photo. */
.page-hero:not(.page-hero--flat) .crumb a,
.page-hero:not(.page-hero--flat) .eyebrow { color: var(--gold-on-photo); }
.page-hero:not(.page-hero--flat) .eyebrow::before { background: var(--gold-on-photo); }
.crumb a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* --------------------------------------------------------------------------
   17. Listing detail
   -------------------------------------------------------------------------- */
.detail-head { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.6fr); gap: clamp(1.5rem, 4vw, 3.5rem); align-items: end; }

/* Place names here are one long word and some are very long. "Baardskeerdersbos"
   set at the h1 size measures 421px inside a 335px column at 375, which widened
   the layout viewport to 441. The header is position:fixed against that
   viewport, so the menu button was laid out at left 377, off the right edge of
   the screen, with horizontal scrolling blocked by html{overflow-x:hidden}. The
   nav was unreachable on a phone on any listing whose title carries the word.
   Breaking the word is the fix; nothing else on the page was at fault. */
.detail-head h1,
.detail-head .lede,
.crumb,
#crumb-now,
.farm-card__title,
.farm-card__place { overflow-wrap: break-word; }
.detail-price {
  font-family: var(--display);
  font-size: clamp(1.85rem, 3vw, 2.6rem);
  letter-spacing: -0.045em;
  color: var(--gold-deep);
  line-height: 1;
}
.detail-perha { font-size: 0.8125rem; color: var(--muted); margin-top: 0.6rem; letter-spacing: 0.06em; text-transform: uppercase; font-family: var(--display); }

/* Three columns: the lead frame spans the row, the three supporting frames
   fill the next one exactly, so the block never ends on an empty cell. */
.gallery {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(0.5rem, 1vw, 0.9rem);
}
.gallery > :first-child { grid-column: span 3; aspect-ratio: 21 / 9; }
.gallery figure { margin: 0; overflow: hidden; border-radius: var(--r-media); background: var(--stone-2); aspect-ratio: 4 / 3; }
.gallery img { width: 100%; height: 100%; object-fit: cover; transition: transform 900ms var(--ease); }
.gallery figure:hover img { transform: scale(1.04); }

.factgrid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
  background: var(--stone);
}
.factgrid div {
  padding: clamp(1.1rem, 2vw, 1.6rem);
  border-right: 1px solid var(--line);
}
.factgrid div:last-child { border-right: 0; }
.factgrid dt {
  font-family: var(--display);
  font-size: clamp(1.1rem, 1.6vw, 1.5rem);
  letter-spacing: -0.035em;
  color: var(--ink);
  line-height: 1.1;
}
.factgrid dd {
  margin: 0.55rem 0 0;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  font-family: var(--display);
}

/* Schedule spine. Ten same-shape blocks stacked is a wall, so the section
   names run down the left and one panel opens beside them. "All sections"
   opens the lot; print forces every one open regardless. */
.schedule-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.36fr) minmax(0, 0.64fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
.spec-tabs {
  display: flex; flex-direction: column;
  border-top: 1px solid var(--line);
}
.spec-tab {
  position: relative;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 0.95rem 0.75rem 0.95rem 1rem;
  font-family: var(--display);
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  text-align: left;
  display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem;
  transition: color var(--t-fast) var(--ease-soft), padding-left var(--t-fast) var(--ease-soft);
}
/* A gold marker slides in on the selected section. */
.spec-tab::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  width: 3px; height: 0;
  background: var(--gold);
  transform: translateY(-50%);
  transition: height var(--t-mid) var(--ease);
}
.spec-tab .c { font-size: 0.75rem; color: var(--gold-deep); flex: none; }
.spec-tab:hover { color: var(--ink); }
.spec-tab[aria-selected="true"] { color: var(--ink); }
.spec-tab[aria-selected="true"]::before { height: 62%; }

.spec-groups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(1.5rem, 3vw, 2.75rem); }
.spec-groups.is-single { grid-template-columns: minmax(0, 1fr); }
.spec-group[hidden] { display: none; }
/* Inside the spine the open panel gets the full right column, so its heading
   sits larger and the rows have room to breathe. */
.schedule-layout .spec-group h3 { font-size: var(--fs-h3); margin-bottom: 1.25rem; }
.schedule-layout .spec-group dt,
.schedule-layout .spec-group dd { padding: 0.75rem 0; font-size: 0.9375rem; }

/* Schedule summary: the site's thesis, measured on this farm. */
.schedule-meter {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.6rem 1.5rem;
  padding: 1.1rem 1.35rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold);
  border-radius: var(--r-chip);
  margin-bottom: clamp(1.75rem, 3vw, 2.5rem);
}
.schedule-meter b {
  font-family: var(--display); font-weight: 400;
  font-size: 1.35rem; letter-spacing: -0.03em; color: var(--ink);
}
.schedule-meter span { font-size: 0.875rem; color: var(--muted); }

/* Detail meta line under the title */
.detail-meta {
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem;
  margin-top: 1rem;
  font-size: 0.8125rem;
  color: var(--muted);
}
.detail-meta b { font-weight: 500; color: var(--ink); }
.detail-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.5rem; }

/* Gallery lightbox */
.lightbox {
  position: fixed; inset: 0; z-index: 500;
  background: color-mix(in srgb, var(--ink-deep) 95%, transparent);
  display: flex; align-items: center; justify-content: center;
  padding: clamp(1rem, 4vw, 3rem);
  opacity: 0;
  transition: opacity var(--t-mid) var(--ease-soft);
}
.lightbox[hidden] { display: none; }
.lightbox.is-open { opacity: 1; }
.lightbox img {
  max-width: 100%; max-height: 100%;
  object-fit: contain;
  border-radius: var(--r-media);
  transform: scale(0.985);
  transition: transform var(--t-mid) var(--ease);
}
.lightbox.is-open img { transform: none; }
.lb-btn {
  position: absolute;
  width: 46px; height: 46px;
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--paper) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--paper) 30%, transparent);
  border-radius: 50%;
  color: var(--paper);
  font-size: 1.1rem;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-soft), border-color var(--t-fast) var(--ease-soft);
}
.lb-btn:hover { background: var(--gold); border-color: var(--gold); color: var(--ink); }
/* Centred with inset and auto margins rather than the translate trick, and
   never under a notch on a landscape phone (fourth review, G1 and G10). */
.lb-close { top: max(clamp(1rem, 3vw, 2rem), var(--safe-top)); right: max(clamp(1rem, 3vw, 2rem), var(--safe-right)); }
.lb-prev { left: max(clamp(0.5rem, 2vw, 1.75rem), var(--safe-left)); top: 0; bottom: 0; margin-block: auto; }
.lb-next { right: max(clamp(0.5rem, 2vw, 1.75rem), var(--safe-right)); top: 0; bottom: 0; margin-block: auto; }
.lb-count {
  position: absolute; bottom: max(clamp(1rem, 3vw, 2rem), var(--safe-bottom)); left: 0; right: 0; margin-inline: auto; width: max-content;
  font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.14em;
  color: color-mix(in srgb, var(--paper) 70%, transparent);
}
.gallery figure { cursor: zoom-in; }
.spec-group { border-top: 2px solid var(--ink); padding-top: 1.25rem; }
.spec-group h3 { margin-bottom: 1rem; }
.spec-group dl { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0; }
.spec-group dt, .spec-group dd {
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 0.875rem;
  min-width: 0;
}
.spec-group dt { color: var(--muted); }
.spec-group dd { margin: 0; text-align: right; color: var(--ink); font-weight: 500; padding-left: 1rem; }

.enquiry-card {
  background: var(--ink-deep);
  color: var(--paper);
  border-radius: var(--r-card);
  padding: clamp(1.5rem, 2.6vw, 2.25rem);
  position: sticky;
  top: calc(var(--header-h) + 20px);
}
.enquiry-card h3 { color: var(--paper); margin-bottom: 0.5rem; }
.enquiry-card p { color: var(--muted-dark); font-size: 0.9375rem; }
.enquiry-card .field input,
.enquiry-card .field textarea,
.enquiry-card .field select {
  background: color-mix(in srgb, var(--paper) 8%, transparent);
  border-color: var(--line-dark);
  color: var(--paper);
}
.enquiry-card .field label { color: var(--gold); }
.enquiry-card .field input::placeholder,
.enquiry-card .field textarea::placeholder { color: color-mix(in srgb, var(--paper) 45%, transparent); }

.agent-line {
  display: flex; flex-direction: column; gap: 0.35rem;
  margin-top: 1.5rem; padding-top: 1.5rem;
  border-top: 1px solid var(--line-dark);
  font-size: 0.875rem;
  color: var(--muted-dark);
}
.agent-line strong { color: var(--paper); font-weight: 500; }
.agent-line a { color: var(--gold); }
.agent-line a:hover { text-decoration: underline; text-underline-offset: 3px; }

/* --------------------------------------------------------------------------
   18. Forms
   -------------------------------------------------------------------------- */
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
.field { display: flex; flex-direction: column; gap: 0.45rem; }
.field--full { grid-column: 1 / -1; }
.field label {
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-deep);
}
.field input, .field textarea, .field select {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-field);
  padding: 0.85rem 1rem;
  /* 1rem, not 0.9375: iOS Safari zooms the whole page in when a field under
     16px takes focus, and these are the site's only conversion. Carried in
     AUDIT.md since the first round, cleared on the 2026-09-09 phone pass. */
  font-size: 1rem;
  transition: border-color var(--t-fast) var(--ease-soft), box-shadow var(--t-fast) var(--ease-soft);
  width: 100%;
}
.field textarea { min-height: 130px; resize: vertical; }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 22%, transparent);
}
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-note { font-size: 0.8125rem; color: var(--muted); margin-top: 0.85rem; }
.form-status { margin-top: 1rem; font-size: 0.9375rem; }
.form-status[data-state="ok"] { color: var(--gold-deep); }
.enquiry-card .form-status[data-state="ok"] { color: var(--gold); }
.form-status[data-state="error"] { color: var(--err); }

/* --------------------------------------------------------------------------
   19. Footer
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--ink-deep);
  color: color-mix(in srgb, var(--paper) 72%, transparent);
  border-top: 3px solid var(--gold);
  padding-block: clamp(3.5rem, 6vw, 5.5rem) calc(max(2rem, var(--fab-size, 60px) + var(--fab-gap, 1.75rem) + 0.75rem) + var(--chrome-b, 0px));
}
.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
  gap: clamp(2rem, 4vw, 3.5rem);
}
/* 78px, not 54: the mark was small for the column it anchors, and the extra
   24px is what brings the paragraph's last line down level with the last item
   in the Site and Farm types columns. */
.site-footer img { height: 78px; width: auto; margin-bottom: 1.5rem; }
/* Same treatment as a header nav link, with the gold marker permanently on:
   uppercase Tenor Sans at 0.8125rem, 0.13em tracking, paper at 88%. One
   heading style for the site, top and bottom. An h3 since the 2026-09-10
   heading-order fix; the line height is pinned to the h4 value it was
   designed at, so the move changed the outline and nothing visible. */
.site-footer h3 {
  display: inline-block;
  font-family: var(--display);
  font-size: 0.8125rem;
  line-height: 1.25;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--paper) 88%, transparent);
  padding-bottom: 0.5rem;
  margin-bottom: 1.35rem;
  border-bottom: 1px solid var(--gold);
}
.site-footer li { margin-bottom: 0.35rem; font-size: 0.9375rem; }
.site-footer li a { display: inline-flex; align-items: center; min-height: 26px; }
.site-footer a { transition: color var(--t-fast) var(--ease-soft); }
.site-footer a:hover { color: var(--gold); }
.site-footer p { font-size: 0.9375rem; line-height: 1.65; max-width: 40ch; }

.footer-base {
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
  padding-top: 1.75rem;
  border-top: 1px solid var(--line-dark);
  display: flex; flex-wrap: wrap; gap: 1rem;
  justify-content: space-between;
  font-size: 0.8125rem;
  color: color-mix(in srgb, var(--paper) 68%, transparent);
}

/* --------------------------------------------------------------------------
   20. Scroll reveal. Content is visible by default. The .js class on <html>
   arms the hidden state, so no script means no invisible page.
   -------------------------------------------------------------------------- */
.js .reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 760ms var(--ease), transform 760ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}
.js .reveal.is-visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .js .reveal { opacity: 1 !important; transform: none !important; }
  .rise > span { transform: none !important; }
  .hero__fade { opacity: 1 !important; transform: none !important; }
}

/* --------------------------------------------------------------------------
   21. Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 1100px) {
  .farm-grid--even > * { grid-column: span 3 !important; }
  /* Two columns: a wide tile top and bottom, four square between. Still no
     empty cell. */
  .type-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); --tile-row: clamp(180px, 20vw, 240px); }
  .type-tile:nth-child(1) { grid-column: span 2; grid-row: span 1; }
  .type-tile:last-child { grid-column: span 2; }
  .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .gallery > :first-child { grid-column: span 2; aspect-ratio: 16 / 9; }
  .factgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .factgrid div:nth-child(3n) { border-right: 0; }
  .factgrid div:nth-child(-n+3) { border-bottom: 1px solid var(--line); }
  .counters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .counter:nth-child(2n) { border-right: 0; }
  .counter:nth-child(-n+2) { border-bottom: 1px solid var(--line); }
  .detail-head { grid-template-columns: minmax(0, 1fr); }
  .enquiry-card { position: static; }
}

@media (max-width: 900px) {
  :root { --header-h: calc(72px + var(--safe-top)); }

  .nav-toggle { display: inline-flex; }

  /* The phone menu is a sheet, not a dropdown. It was a 145px strip on an
     844px screen with the live page under it: a thumb in the lower half hit
     the page, not the menu. It now runs from the header to the bottom edge,
     carries the header's own call to action (which this width hides), the
     number and the address, and script.js locks the document behind it. */
  .nav {
    position: fixed;
    /* The header carries a backdrop-filter, which makes it the containing
       block for anything fixed inside it. A `bottom: 0` therefore resolved to
       the bottom of the 72px bar, not the screen. The height is stated. */
    inset: var(--header-h) 0 auto 0;
    height: calc(100vh - var(--header-h));
    height: calc(100dvh - var(--header-h));
    display: flex;
    flex-direction: column;
    /* Centred, so the sheet rests evenly: measured 84 above the first row, 76
       between the two blocks and 89 under the last at 390x844. Anchoring the
       actions to the bottom edge instead left a 218px hole mid-panel. `safe`
       keeps the top reachable when the content is taller than the screen. */
    justify-content: center;
    justify-content: safe center;
    gap: clamp(1.5rem, 5vw, 2.25rem);
    background: var(--ink-deep);
    padding: clamp(1.25rem, 4vw, 2rem) var(--gutter)
             calc(clamp(1.5rem, 5vw, 2.25rem) + var(--safe-bottom));
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--t-mid) var(--ease), transform var(--t-mid) var(--ease);
    margin-left: 0;
  }
  .nav.is-open { opacity: 1; transform: none; pointer-events: auto; }

  .nav-list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav-list li + li { border-top: 1px solid var(--line-dark); }
  .nav-list a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-block: clamp(0.85rem, 3.2vw, 1.15rem);
    font-size: clamp(1.25rem, 5.6vw, 1.6rem);
    letter-spacing: -0.02em;
    color: color-mix(in srgb, var(--paper) 82%, transparent);
  }
  /* The current page was marked in colour alone once the desktop underline was
     switched off here. It carries a gold rule of its own now. */
  .nav-list a::after {
    position: static;
    width: 26px; height: 2px;
    flex: none;
    transform: scaleX(0);
    transform-origin: right;
  }
  /* The desktop hover rule (.nav-list a:hover::after) outranks the base rule
     here, so every hovered row drew the current-page marker. On a sheet this
     size the mark means one thing only. */
  .nav-list a:hover::after,
  .nav-list a:focus-visible::after { transform: scaleX(0); }
  .nav-list a[aria-current="page"] { color: var(--paper); }
  .nav-list a[aria-current="page"]::after,
  .nav-list a[aria-current="page"]:hover::after { transform: scaleX(1); }
  /* G6: the three action rows gave press feedback and the four destinations
     above them gave none. */
  .nav-list a:active { color: var(--paper); }

  /* Entrance: the rows rise in behind the panel, 40ms apart. */
  .nav.is-open .nav-list li,
  .nav.is-open .nav-extra > * {
    animation: nav-row var(--t-mid) var(--ease) both;
    animation-delay: calc(var(--nav-i, 0) * 40ms + 60ms);
  }

  .nav-extra {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    /* `margin-top: auto` pinned this block to the bottom edge and left 218px
       of empty ink between the destinations and the mandate line, which is the
       same fault this session started with. A stated gap instead; what is left
       over sits under the last row, where it reads as the sheet's own base. */
    margin-top: clamp(1.75rem, 6vh, 3.25rem);
  }
  .nav-extra__note {
    margin: 0 0 0.35rem;
    max-width: 26ch;
    font-size: 0.9375rem;
    line-height: 1.5;
    color: var(--muted-dark);
  }
  /* No font-size of its own: it is a .btn, and a 15px label here was the only
     15px button on the site, which is role drift by another name. */
  .nav-extra__cta { width: 100%; padding-block: 1.05rem; }
  .nav-extra__line {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    min-height: 48px;
    padding-block: 0.55rem;
    border-top: 1px solid var(--line-dark);
    color: color-mix(in srgb, var(--paper) 82%, transparent);
    transition: color var(--t-fast) var(--ease-soft);
  }
  .nav-extra__line:first-of-type { border-top: 0; }
  .nav-extra__line:hover,
  .nav-extra__line:active { color: var(--paper); }
  .nav-extra__k {
    flex: none;
    min-width: 3.4rem;
    font-family: var(--display);
    font-size: var(--fs-label);
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--gold);
  }
  .nav-extra__v { font-size: 1rem; }

  /* A landscape phone leaves 318px under the bar. Two columns rather than a
     scroller: the four destinations on the left, the three actions right. */
  @media (max-height: 560px) and (orientation: landscape) {
    .nav {
      display: grid;
      grid-template-columns: 1fr 1fr;
      align-content: start;
      gap: clamp(1rem, 3vw, 2.5rem);
      padding-block: 0.9rem;
    }
    .nav-list a { font-size: 1.1rem; padding-block: 0.6rem; }
    .nav-extra { margin-top: 0; }
    .nav-extra__cta { padding-block: 0.85rem; }
  }

  .header-cta { display: none; }
  /* One surface: the translucent bar over the ink sheet met it as two darks. */
  .nav-open .site-header { background: var(--ink-deep); }
  /* The chat button is z-index 110 and painted straight over the open panel;
     so does the compare tray on the listing pages. */
  .nav-open .chat-fab,
  .nav-open .cmp-tray { opacity: 0; pointer-events: none; }
  .site-header { background: color-mix(in srgb, var(--ink-deep) 90%, transparent); backdrop-filter: blur(12px); }

  .split, .split--wide-left, .split--wide-right,
  .section-head--split, .cta__inner,
  .spec-groups { grid-template-columns: minmax(0, 1fr); }
  .schedule-layout { grid-template-columns: minmax(0, 1fr); gap: 1.75rem; }
  .section-head--split { gap: 1.25rem; align-items: start; }
  .split__media { order: -1; }
  .split--media-last .split__media { order: 0; }
  .row-item { grid-template-columns: minmax(0, 1fr); gap: 0.6rem; }
  .media-pair { padding-bottom: clamp(2rem, 12vw, 4rem); }
  .media-pair__inset { width: 58%; right: 0; }
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .searchrail__inner { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .searchrail__field:nth-child(2n) { border-right: 0; }
  .searchrail__field:nth-child(-n+2) { border-bottom: 1px solid var(--line-dark); }
  .searchrail__submit { grid-column: 1 / -1; padding-block: 1.1rem; justify-content: center; }
  .hero { min-height: 100svh; padding-top: calc(var(--header-h) + 3rem); padding-bottom: 3.5rem; }
}

@media (max-width: 640px) {
  .farm-grid { grid-template-columns: minmax(0, 1fr); }
  .farm-grid > * { grid-column: 1 / -1 !important; }
  .type-grid { grid-template-columns: minmax(0, 1fr); }
  .gallery, .gallery > :first-child { grid-template-columns: minmax(0, 1fr); grid-column: span 1; }
  .factgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .factgrid div { border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); }
  .factgrid div:nth-child(2n) { border-right: 0; }
  .factgrid div:nth-last-child(-n+2) { border-bottom: 0; }
  .counters { grid-template-columns: minmax(0, 1fr); }
  .counter { border-right: 0; border-bottom: 1px solid var(--line); }
  .counter:last-child { border-bottom: 0; }
  .stat-strip { grid-template-columns: minmax(0, 1fr); }
  .form-grid { grid-template-columns: minmax(0, 1fr); }
  .footer-grid { grid-template-columns: minmax(0, 1fr); }
  .searchrail__inner { grid-template-columns: minmax(0, 1fr); }
  .searchrail__field { border-right: 0; border-bottom: 1px solid var(--line-dark); }
  .spec-group dl { grid-template-columns: minmax(0, 1fr); }
  .spec-group dd { text-align: left; padding-left: 0; padding-top: 0; border-bottom: 1px solid var(--line-soft); }
  .spec-group dt { border-bottom: 0; padding-bottom: 0.2rem; }
  .type-grid { --tile-row: clamp(170px, 46vw, 230px); }
  .type-tile:nth-child(1),
  .type-tile:last-child { grid-column: span 1; grid-row: span 1; }
}

/* --------------------------------------------------------------------------
   22. Print. A listing page prints as a clean farm schedule an agent can hand
   to a buyer or a bank: no chrome, no photo bleed, every section open.
   -------------------------------------------------------------------------- */
@media print {
  @page { margin: 14mm; }

  :root { --band: 0; --band-tight: 0; }

  body { background: #fff; color: #000; font-size: 10.5pt; line-height: 1.42; }

  .site-header, .site-footer, .searchrail, .cta,
  .nav-toggle, .skip-link, .filters, .toolbar, .spec-tabs,
  .enquiry-card, .save-btn, .lightbox, .type-more, .crumb,
  .detail-actions, .link-line, .farm-card__foot { display: none !important; }

  .band, .band--tight { padding-block: 0 !important; }
  .band--stone, .band--sage, .band--ink { background: #fff !important; color: #000 !important; }
  .band--ink h1, .band--ink h2, .band--ink h3 { color: #000 !important; }

  .shell { max-width: none; padding-inline: 0; }

  /* Everything the schedule holds, open, in two columns, unbroken per group */
  .spec-group[hidden] { display: block !important; }
  .schedule-layout { display: block !important; }
  .spec-groups, .spec-groups.is-single { grid-template-columns: repeat(2, 1fr) !important; max-width: none; gap: 6mm 10mm; }
  .spec-group { break-inside: avoid; border-top: 1.5pt solid #000; }
  .spec-group h3 { font-size: 11pt; }
  .spec-group dt, .spec-group dd { padding: 2pt 0; font-size: 9.5pt; border-bottom: 0.4pt solid #ccc; }

  .factgrid { grid-template-columns: repeat(3, 1fr) !important; background: #fff; border-color: #000; }
  .factgrid div { border-color: #ccc; }

  /* One establishing photograph, then the facts */
  .gallery { grid-template-columns: 1fr !important; }
  .gallery figure:not(:first-child) { display: none; }
  .gallery > :first-child { aspect-ratio: 21 / 9; }

  .schedule-meter { border: 1pt solid #000; border-left-width: 3pt; }

  h1 { font-size: 22pt; } h2 { font-size: 14pt; } h3 { font-size: 11pt; }
  .detail-price { font-size: 18pt; color: #000; }
  .eyebrow { color: #000; }
  .reveal { opacity: 1 !important; transform: none !important; }

  a[href]::after { content: ""; }

  /* Contact block for whoever is holding the page */
  #listing-detail::after {
    content: "ERFDEVCO  |  Martiens Du Plessis  |  082 900 5019  |  martiens@erfdevco.com  |  www.erfdevco.com  |  Registered with the PPRA";
    display: block;
    margin-top: 8mm; padding-top: 3mm;
    border-top: 1pt solid #000;
    font-size: 8.5pt; letter-spacing: 0.04em;
  }
}

/* Count badge inside a filter chip (shortlist) */
.filter-btn .chip-n {
  display: inline-block;
  margin-left: 0.45rem;
  min-width: 1.15rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  font-size: 0.75rem;
  text-align: center;
}
.filter-btn[aria-pressed="true"] .chip-n { background: var(--gold); color: var(--ink); }

/* --------------------------------------------------------------------------
   23. Compare. The payoff of a uniform schedule: two or three farms read
   side by side, field for field, with the differences findable.
   -------------------------------------------------------------------------- */
.cmp-toggle {
  display: inline-flex; align-items: center; gap: 0.5rem;
  background: transparent; border: 0;
  /* 18px tall was under the 24px minimum, on every card on the site. */
  min-height: 26px; padding-block: 0.15rem; padding-inline: 0;
  font-family: var(--display);
  font-size: var(--fs-label); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted); cursor: pointer;
  transition: color var(--t-fast) var(--ease-soft);
}
.cmp-toggle:hover { color: var(--ink); }
.cmp-toggle .box {
  width: 15px; height: 15px; flex: none;
  border: 1px solid var(--line);
  border-radius: 2px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background var(--t-fast) var(--ease-soft), border-color var(--t-fast) var(--ease-soft);
}
.cmp-toggle .box::after {
  content: ""; width: 7px; height: 7px; background: var(--ink);
  transform: scale(0); transition: transform var(--t-fast) var(--ease);
}
.cmp-toggle[aria-pressed="true"] { color: var(--ink); }
.cmp-toggle[aria-pressed="true"] .box { border-color: var(--ink); }
.cmp-toggle[aria-pressed="true"] .box::after { transform: scale(1); }
.cmp-toggle[disabled] { opacity: 0.4; cursor: not-allowed; }

/* The tray only exists while something is selected. */
.cmp-tray {
  position: fixed;
  left: 0; right: 0;
  margin-inline: auto;
  bottom: calc(var(--dock) + var(--tray-gap));
  transform: translateY(130%);
  z-index: 300;
  width: min(calc(100vw - 2rem), 68rem);
  background: var(--ink-deep);
  border: 1px solid var(--line-dark);
  border-top: 2px solid var(--gold);
  border-radius: var(--r-card);
  box-shadow: 0 18px 50px -12px color-mix(in srgb, var(--ink-deep) 55%, transparent);
  display: flex; align-items: center; gap: clamp(0.75rem, 2vw, 1.5rem);
  padding: 0.85rem clamp(0.85rem, 2vw, 1.35rem);
  transition: transform var(--t-mid) var(--ease),
              opacity var(--t-fast) var(--ease-soft);
}
.cmp-tray.is-up { transform: none; }
.cmp-tray__label {
  font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--gold); flex: none;
}
.cmp-tray__slots { display: flex; gap: 0.6rem; flex: 1; min-width: 0; }
.cmp-slot {
  position: relative;
  width: 62px; height: 46px; flex: none;
  border-radius: var(--r-chip);
  overflow: hidden;
  background: color-mix(in srgb, var(--paper) 8%, transparent);
  border: 1px dashed color-mix(in srgb, var(--paper) 22%, transparent);
}
.cmp-slot img { width: 100%; height: 100%; object-fit: cover; }
.cmp-slot--filled { border-style: solid; border-color: transparent; }
.cmp-slot__x {
  position: absolute; inset: auto 2px 2px auto;
  width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--ink-deep) 82%, transparent);
  border: 0; border-radius: 50%;
  color: var(--paper); font-size: 0.75rem; line-height: 1; cursor: pointer;
}
.cmp-slot__x:hover { background: var(--gold); color: var(--ink); }
.cmp-tray__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; flex: none; }
.cmp-tray .btn { padding: 0.7rem 1.15rem; font-size: 0.75rem; }
.cmp-clear {
  background: transparent; border: 0; cursor: pointer;
  font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: color-mix(in srgb, var(--paper) 60%, transparent);
}
.cmp-clear:hover { color: var(--paper); }

/* The comparison table itself */
.cmp-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.cmp-table { border-collapse: collapse; width: 100%; min-width: 42rem; }
.cmp-table th, .cmp-table td {
  text-align: left; vertical-align: top;
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--line-soft);
  font-size: 0.9375rem;
}
.cmp-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--paper);
  border-bottom: 2px solid var(--ink);
  padding-top: 1.25rem;
}
.cmp-table tbody th {
  font-weight: 400; color: var(--muted);
  width: 22%; min-width: 11rem;
}
.cmp-table td { color: var(--ink); }
.cmp-table td.is-blank { color: color-mix(in srgb, var(--muted) 60%, transparent); }
.cmp-sec th {
  background: var(--stone);
  font-family: var(--display);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.8125rem;
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  padding-block: 0.7rem;
}
/* A row where the farms actually differ is the row worth reading. */
.cmp-table tr[data-diff="1"] td { background: color-mix(in srgb, var(--gold) 7%, transparent); }
.cmp-table tr[data-diff="1"] th { color: var(--ink); }
.cmp-only-diff tr[data-diff="0"] { display: none; }

/* Two lines reserved for the name so the prices below it line up across
   columns whether a farm's title wraps or not. */
.cmp-head { display: flex; flex-direction: column; gap: 0.4rem; }
/* Pinned state: photos, refs and districts fold away so the stuck strip is
   shallow and the visitor reads it as one page scrolling, not two halves. */
.cmp-head img, .cmp-head .r, .cmp-head .l {
  transition: opacity var(--t-fast) var(--ease-soft);
}
.cmp-table.is-stuck .cmp-head img,
.cmp-table.is-stuck .cmp-head .r,
.cmp-table.is-stuck .cmp-head .l {
  height: 0;
  min-height: 0;
  aspect-ratio: auto;
  opacity: 0;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
}
.cmp-table.is-stuck .cmp-head .t { min-height: 0; }
.cmp-table.is-stuck thead th { padding-top: 0.7rem; padding-bottom: 0.55rem; }
.cmp-head .t { min-height: 2.6em; }
.cmp-head .r { font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.12em; color: var(--gold-deep); }
.cmp-head .t { font-family: var(--display); font-size: 1.0625rem; letter-spacing: -0.02em; text-transform: uppercase; line-height: 1.2; }
.cmp-head .p { color: var(--gold-deep); font-family: var(--display); font-size: 1rem; }
.cmp-head .l { font-size: 0.8125rem; color: var(--muted); }
.cmp-head img { width: 100%; aspect-ratio: 16/10; object-fit: cover; border-radius: var(--r-chip); margin-bottom: 0.35rem; }

.cmp-controls {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap;
  padding-bottom: 1.25rem; margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--line);
}
.cmp-switch {
  display: inline-flex; align-items: center; gap: 0.55rem;
  font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted); cursor: pointer;
  background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-chip); padding: 0.6rem 1rem;
  transition: background-color var(--t-fast) var(--ease-soft),
              border-color var(--t-fast) var(--ease-soft),
              color var(--t-fast) var(--ease-soft),
              transform var(--t-fast) var(--ease-soft);
}
.cmp-switch:hover { border-color: var(--ink); color: var(--ink); }
.cmp-switch[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }

@media (max-width: 640px) {
  .cmp-tray { gap: 0.6rem; padding: 0.7rem; }
  .cmp-tray__label { display: none; }
  .cmp-slot { width: 46px; height: 36px; }
  .cmp-table th, .cmp-table td { padding: 0.65rem 0.7rem; font-size: 0.875rem; }
}

@media print {
  .cmp-tray, .cmp-toggle, .cmp-controls { display: none !important; }
  .cmp-table { min-width: 0; }
  .cmp-table thead th { position: static; }
}

/* Search bar on the listings page */
.findbar {
  display: flex; align-items: center; gap: 0.9rem;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 0.85rem;
  margin-bottom: clamp(1.5rem, 2.5vw, 2rem);
}
.findbar label {
  font-family: var(--display);
  font-size: 0.75rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--gold-deep); flex: none;
}
.findbar input {
  flex: 1; min-width: 0;
  background: transparent; border: 0; padding: 0.2rem 0;
  font-family: var(--display);
  font-size: clamp(1.05rem, 1.6vw, 1.4rem);
  letter-spacing: -0.02em;
  color: var(--ink);
}
.findbar input::placeholder { color: color-mix(in srgb, var(--muted) 72%, transparent); letter-spacing: -0.01em; }
.findbar input:focus { outline: none; }
.findbar:focus-within { border-bottom-color: var(--gold); }
.findbar input::-webkit-search-cancel-button { cursor: pointer; }

@media print { .findbar { display: none; } }

/* --------------------------------------------------------------------------
   24. Map. Province outlines from Natural Earth (public domain), farms pinned
   at their district. Clicking a province filters the list under it.
   -------------------------------------------------------------------------- */
.mapbox {
  --map-gap: clamp(1.5rem, 4vw, 3.5rem);
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: var(--map-gap);
  align-items: center;
  background: var(--stone);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: clamp(1.25rem, 3vw, 2.25rem);
  margin-bottom: clamp(2rem, 3.5vw, 3rem);
}

.mapfig { position: relative; }
.mapfig svg { width: 100%; height: auto; display: block; overflow: visible; }

.prov {
  fill: color-mix(in srgb, var(--gold) 13%, color-mix(in srgb, var(--ink) 7%, var(--canvas)));
  stroke: var(--canvas);
  stroke-width: 2;
  stroke-linejoin: round;
  transition: fill var(--t-fast) var(--ease-soft);
}
.prov.has-farms { fill: var(--ink); cursor: pointer; }
.prov.has-farms:hover { fill: color-mix(in srgb, var(--gold) 62%, var(--ink)); }
.prov.is-active { fill: var(--gold); }

.pin { cursor: pointer; transition: opacity var(--t-mid) var(--ease-soft); }
/* The home map is a picture: nothing there opens, so nothing there may look
   as though it does. Same reason the province hover was killed in round 1. */
.country__map .pin { cursor: default; }
.pin .halo { fill: var(--gold); opacity: 0.55; }
/* Matched to the home map: the disc is lifted towards paper and outlined in
   the estate green it now sits on, and grown past the authored r of 8. */
.pin .dot {
  r: 10;
  fill: color-mix(in srgb, var(--gold) 76%, var(--paper));
  stroke: var(--ink);
  stroke-width: 2.5;
}
/* A cluster the map cannot honestly spread collapses to one pin carrying its
   count. The others stay in the DOM, so the tally, the list and the keyboard
   still reach every listing, but nothing is drawn under anything else. */
.pin.is-merged { display: none; }
.pin--group .dot { r: 13; }
.pin--group .halo { r: 22; }
.pin__n {
  font-family: var(--body);
  font-size: 13px;
  font-weight: 600;
  fill: var(--ink);
  text-anchor: middle;
  dominant-baseline: middle;
  pointer-events: none;
}
.pin:hover .halo, .pin.is-on .halo { opacity: 0.8; }
.pin:hover .dot, .pin.is-on .dot { fill: var(--paper); }
.pin.is-dim { opacity: 0.18; pointer-events: none; }
/* Two rings, because the pin sits on an ink province: paper reads against the
   province, and the dot keeps its own ink outline so the paper ring has an
   edge to contrast with on the inside too. box-shadow does not paint on SVG. */
.pin:focus-visible .halo { opacity: 1; fill: none; stroke: var(--paper); stroke-width: 3; }
.pin:focus-visible .dot { stroke: var(--ink); stroke-width: 2.5; }

/* Tooltip rides above the SVG in normal DOM so type stays crisp. */
.maptip {
  position: absolute;
  left: 50%; top: 0;
  visibility: hidden;          /* never widens the page while it is idle */
  z-index: 5;
  max-width: min(14rem, 78vw);
  min-width: 12rem;
  background: var(--ink-deep);
  color: var(--paper);
  border-radius: var(--r-chip);
  padding: 0.7rem 0.85rem;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -122%) scale(0.97);
  transition: opacity var(--t-fast) var(--ease-soft), transform var(--t-fast) var(--ease);
  box-shadow: 0 12px 30px -10px color-mix(in srgb, var(--ink-deep) 60%, transparent);
}
.maptip.is-on { opacity: 1; visibility: visible; transform: translate(-50%, -130%) scale(1); }
.maptip .r, .maptip .t, .maptip .p { display: block; }
.maptip .r { font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.14em; color: var(--gold); }
.maptip .t { font-family: var(--display); font-size: 0.875rem; text-transform: uppercase; letter-spacing: -0.01em; margin-top: 0.15rem; }
.maptip .p { font-size: 0.8125rem; color: color-mix(in srgb, var(--paper) 70%, transparent); margin-top: 0.2rem; }

.provlist { display: flex; flex-direction: column; border-top: 1px solid var(--line); margin-inline: -0.6rem; }
/* The count used to sit hard right, which left a 430px hole in the middle of
   every row at desktop. It belongs with the name it counts. */
.provlist button {
  display: flex; align-items: baseline; justify-content: flex-start; gap: 0.7rem;
  background: transparent; border: 0; border-bottom: 1px solid var(--line);
  padding: 0.75rem 0.6rem;
  min-height: 44px;
  font-family: var(--display); font-size: 0.875rem; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ink); cursor: pointer; text-align: left;
  transition: background var(--t-fast) var(--ease-soft), color var(--t-fast) var(--ease-soft);
}
.provlist button .n {
  font-size: 0.75rem;
  color: var(--gold-deep);
  font-variant-numeric: tabular-nums;
}
/* A whole-row wash, so a 600px row reads as one target rather than a word
   floating in space. */
.provlist button:hover:not([disabled]) { background: color-mix(in srgb, var(--ink) 6%, transparent); }
/* Not --gold: on the hover wash a 12px gold count measured 1.76:1. The state
   change is carried by a darker value instead, not a lighter one. */
.provlist button:hover:not([disabled]) .n { color: var(--ink); }
/* Selected reads the same way as the filter chips 400px below it: a solid
   ink row, not a 20px chip on an otherwise unchanged line. */
.provlist button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
.provlist button[aria-pressed="true"] .n { color: var(--gold); }
.provlist button[aria-pressed="true"]:hover { background: var(--ink); }
.provlist button[aria-pressed="true"]:hover .n { color: var(--gold); }
.provlist button[disabled] { opacity: 0.42; cursor: default; }
.maphead { margin-bottom: 1.75rem; }
.maphead h2 { margin-bottom: 0.85rem; }
.maphead p { font-size: 0.875rem; color: var(--muted); }

/* The mobile collapse and the print rule for .mapbox live with the grid that
   sets them, further down, so there is one place to change. */

/* --------------------------------------------------------------------------
   25. Bond calculator. Three sliders and the number that matters.
   -------------------------------------------------------------------------- */
.bond {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}
/* Three full-width rows, label | track | value, so the controls fill the
   panel instead of huddling in its middle third. */
.bond-grid {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(1.25rem, 2.4vw, 2rem);
  padding: clamp(1.5rem, 2.8vw, 2.25rem);
  background: var(--paper);
}
.bond-f {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr) auto;
  gap: 1.25rem;
  align-items: center;
}
.bond-f > span {
  font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--gold-deep);
}
.bond-f output {
  font-family: var(--display); font-size: 0.9375rem; letter-spacing: -0.01em;
  color: var(--ink); text-align: right; font-variant-numeric: tabular-nums;
}
.bond-f input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 26px; margin: 0;
  background: transparent; cursor: pointer;
}
.bond-f input[type="range"]::-webkit-slider-runnable-track {
  height: 4px; border-radius: 2px;
  background: color-mix(in srgb, var(--ink) 26%, transparent);
}
.bond-f input[type="range"]::-moz-range-track {
  height: 4px; border-radius: 2px;
  background: color-mix(in srgb, var(--ink) 26%, transparent);
}
.bond-f input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  margin-top: -7px;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--ink); border: 3px solid var(--paper);
  box-shadow: 0 0 0 1px var(--line);
  cursor: grab;
  transition: background var(--t-fast) var(--ease-soft), transform var(--t-fast) var(--ease);
}
.bond-f input[type="range"]::-webkit-slider-thumb:hover { background: var(--gold); transform: scale(1.12); }
.bond-f input[type="range"]::-moz-range-thumb {
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--ink); border: 3px solid var(--paper); cursor: grab;
}

.bond-out {
  background: var(--ink-deep);
  color: var(--paper);
  padding: clamp(1.25rem, 2.4vw, 1.9rem);
  display: flex; flex-direction: column;
}
.bond-lab {
  font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--gold);
}
.bond-big {
  font-family: var(--display);
  font-size: clamp(1.9rem, 3.4vw, 2.9rem);
  letter-spacing: -0.05em; line-height: 1;
  margin-top: 0.5rem;
  font-variant-numeric: tabular-nums;
}
.bond-sum {
  margin-top: auto; padding-top: 1.5rem;
  display: grid; gap: 0.85rem;
  border-top: 1px solid var(--line-dark);
}
.bond-sum > div { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: nowrap; }
.bond-sum dt { white-space: nowrap; }
.bond-sum dt {
  font-family: var(--display); font-size: 1.0625rem; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.bond-sum dd {
  margin: 0; font-size: 0.8125rem; letter-spacing: 0.08em; text-transform: uppercase;
  font-family: var(--display); color: var(--muted-dark);
}
.bond-out .form-note { color: color-mix(in srgb, var(--paper) 52%, transparent); margin-top: 1.25rem; }

@media (max-width: 900px) {
  .bond { grid-template-columns: minmax(0, 1fr); }
  .bond-grid { gap: 1.5rem; }
  .bond-f { grid-template-columns: minmax(0, 1fr) auto; }
  .bond-f input[type="range"] { grid-column: 1 / -1; grid-row: 2; }
}
@media print { .bond, #similar-farms { display: none !important; } }

/* --------------------------------------------------------------------------
   26. The /better pass. Value strip, one value per role, dead halves.
   -------------------------------------------------------------------------- */

/* ---- Map on ink. The panel is the band now, so it loses its own box. ---- */
/* The ink-band map variant was removed on 2026-09-04: the only .mapbox on
   the site sits on the sage band, so all 20 of its overrides matched
   nothing. Recoverable from git history if an ink map ever returns. */
.band--ink .provlist button[disabled] { opacity: 0.55; color: color-mix(in srgb, var(--paper) 42%, transparent); }
.band--ink .maphead p { color: var(--muted-dark); }

/* ---- A masthead with no photograph still has to be a dark band. ---- */
.page-hero--flat { background: var(--sage); }
.page-hero--flat h1 { color: var(--ink); }
.page-hero--flat .lede { color: var(--muted); }
.page-hero--flat .crumb,
.page-hero--flat .crumb span { color: var(--muted); }
.page-hero--flat .crumb a { color: var(--gold-dark); }

/* ---- One value per role. Five body sizes was four too many. ---- */
.counter__note,
.form-note,
.farm-card__place,
.results-line,
.detail-meta,
.spec-group dt, .spec-group dd,
.cmp-table th, .cmp-table td,
.site-footer li, .site-footer p,
.maphead p { font-size: var(--fs-body); line-height: 1.6; }
.spec-tab .c, .cmp-head .r, .farm-card__meta i,
.type-tile__count, .footer-base { font-size: var(--fs-label); }

/* ---- Measure. Nothing runs to 104 characters. ---- */
main p { max-width: 68ch; }
/* One paragraph rhythm site-wide: measured gaps ran 0 to 32px across nine
   ad-hoc inline margins; adjacent paragraphs now breathe 1em, scaled by
   their own type size. Tables and fact grids opt out. */
main p + p { margin-top: 1em; }
main .cmp-table p + p, main .factgrid p + p, main .bond p + p { margin-top: 0; }
main .row-item__body p,
main .counter__note,
main .maphead p, main .provlist-note { max-width: 46ch; }
.cta p, .hero__lede, .page-hero .lede { max-width: 52ch; }
main .cmp-table p, main .factgrid p, main .bond p { max-width: none; }

/* ---- Section rhythm: two paddings, not four. ---- */
.band--flush { padding-block: 0; }

/* ---- Dead halves ---- */
/* A lone button floating in a centred column is not a row. Desktop only:
   below 900 the split collapses like every other one. */
@media (min-width: 901px) {
  .split--action { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  /* Grid items default to min-width:auto, so the nowrap button held the
     0.65fr track open past the container and put 20px of horizontal
     scroll on the page between 901 and about 1050. */
  .split--action > :last-child { justify-self: stretch; min-width: 0; max-width: 100%; }
  /* justify-self:start sizes the item to fit-content, so a nowrap label
     ignored the track. Let this one button wrap instead. */
  .split--action .btn { white-space: normal; text-align: center; }
}
@media (max-width: 900px) { .split--action { grid-template-columns: minmax(0, 1fr); } }

/* The enquiry card is sticky, so its column must not stretch past it. */
.split--top > * { align-self: start; }
/* ...except the column that actually holds the sticky card. A sticky element's
   containing block is its grid item, so an item sized to the card leaves zero
   room to stick in and the card scrolls away instead of following the reader.
   Measured 2026-09-08: room was 0px on privacy and on a farm page, and the
   card had left the viewport by 1600px of scroll, leaving a ~1500px empty
   right column on privacy. This is the Dr Roof ruling in TASTE-PROFILE.md
   (2026-08-12): stretch the grid item and let the block stick inside it.
   :has() is Baseline (Chrome 105, Safari 15.4, Firefox 121); where it is
   missing the card behaves exactly as it does today rather than breaking. */
.split--top > :has(> .enquiry-card) { align-self: stretch; }

/* ---- Mobile: real targets ---- */
@media (max-width: 900px) {
  .searchrail__field select { padding-block: 0.55rem; font-size: 1.0625rem; }
  .findbar input { padding-block: 0.5rem; }
  .link-line { padding-block: 0.5rem; }
  .site-footer li { margin-bottom: 0.95rem; }
}
@media (max-width: 640px) {
  /* The comparison is the best thing on the site and it was a sideways
     scroll on a phone. Two farms at a time, with the field name above. */
  .cmp-table { min-width: 0; }
  .cmp-table thead th:first-child { display: none; }
  .cmp-table tbody th {
    display: block; width: auto; min-width: 0;
    border-bottom: 0; padding-bottom: 0.2rem;
    font-family: var(--display); text-transform: uppercase;
    font-size: 0.75rem; letter-spacing: 0.08em; color: var(--gold-deep);
  }
  .cmp-table tbody tr { display: grid; grid-template-columns: repeat(var(--cmp-cols, 2), minmax(0, 1fr)); gap: 0 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--line-soft); }
  .cmp-table tbody tr th { grid-column: 1 / -1; }
  .cmp-table tbody td { border-bottom: 0; padding: 0.15rem 0; }
  .cmp-table tr.cmp-sec { display: block; }
  .cmp-table tr.cmp-sec th { grid-column: auto; color: var(--ink); }
  .cmp-table thead th { padding: 0.75rem 0.4rem; }
  .cmp-table thead tr { display: grid; grid-template-columns: repeat(var(--cmp-cols, 2), minmax(0, 1fr)); gap: 0 0.75rem; }
  .cmp-table, .cmp-table tbody, .cmp-table thead { display: block; }
  .cmp-head .t { font-size: 0.9375rem; min-height: 0; }
  .cmp-head .p { font-size: 0.875rem; }
  .cmp-head img { aspect-ratio: 4/3; }
}

/* ---- The farm's three headline figures, sized to be read across a room ---- */
.headline-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 2px solid var(--ink);
  border-bottom: 1px solid var(--line);
}
.headline-facts > div {
  padding: clamp(1.1rem, 2vw, 1.6rem) clamp(1rem, 2vw, 1.75rem) clamp(1.1rem, 2vw, 1.6rem) 0;
  border-right: 1px solid var(--line);
}
.headline-facts > div:last-child { border-right: 0; }
.headline-facts > div + div { padding-left: clamp(1rem, 2vw, 1.75rem); }
.headline-facts dt {
  font-family: var(--display);
  font-size: clamp(1.9rem, 3.4vw, 3rem);
  letter-spacing: -0.05em;
  line-height: 1;
  color: var(--ink);
}
.headline-facts dd {
  margin: 0.6rem 0 0;
  font-family: var(--display);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The full six-fact grid below is the supporting detail, so it sits quieter
   than the three headline figures rather than competing with them. */
.factgrid { background: var(--paper); }
.factgrid dt { font-size: clamp(1rem, 1.3vw, 1.2rem); }

/* Price block sits with the title row, not floating above an empty column. */
.detail-price-block { align-self: end; text-align: right; }

/* ---- The schedule band, now on ink ---- */
.band--ink .schedule-meter {
  background: color-mix(in srgb, var(--paper) 7%, transparent);
  border-color: var(--line-dark);
  border-left-color: var(--gold);
}
.band--ink .schedule-meter b { color: var(--paper); }
.band--ink .schedule-meter span { color: var(--muted-dark); }
.band--ink .spec-tabs { border-top-color: var(--line-dark); }
.band--ink .spec-tab {
  color: color-mix(in srgb, var(--paper) 66%, transparent);
  border-bottom-color: var(--line-dark);
}
.band--ink .spec-tab:hover, .band--ink .spec-tab[aria-selected="true"] { color: var(--paper); }
.band--ink .spec-tab .c { color: var(--gold); }
.band--ink .spec-group { border-top-color: var(--gold); }
.band--ink .spec-group h3 { color: var(--paper); }
.band--ink .spec-group dt { color: var(--muted-dark); border-bottom-color: var(--line-dark); }
.band--ink .spec-group dd { color: var(--paper); border-bottom-color: var(--line-dark); }

/* ---- Compare: "Not recorded" was 2.96:1 ---- */
.cmp-table td.is-blank { color: var(--muted); font-style: italic; }

@media (max-width: 900px) {
  .headline-facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .detail-price-block { text-align: left; align-self: start; }
}
@media (max-width: 560px) {
  .headline-facts { grid-template-columns: minmax(0, 1fr); }
  .headline-facts > div { border-right: 0; border-bottom: 1px solid var(--line); padding-left: 0 !important; }
  .headline-facts > div:last-child { border-bottom: 0; }
}

/* ---- Components that now have to work on an ink band ---- */
.band--ink .rows { border-top-color: var(--line-dark); }
.band--ink .row-item { border-bottom-color: var(--line-dark); }
.band--ink .row-item::before { background: color-mix(in srgb, var(--paper) 7%, transparent); }
.band--ink .row-item h3 { color: var(--paper); }
.band--ink .row-item:hover h3 { color: var(--gold); }
.band--ink .row-item__body p { color: var(--muted-dark); }
.band--ink .counters { border-top-color: var(--line-dark); }
.band--ink .counter { border-right-color: var(--line-dark); }
.band--ink .counter__n { color: var(--paper); }
.band--ink .counter__label, .band--ink .counter__note { color: var(--muted-dark); }
.band--ink .filter-btn { border-color: var(--line-dark); color: color-mix(in srgb, var(--paper) 72%, transparent); }
.band--ink .filter-btn:hover { border-color: var(--gold); color: var(--paper); }

/* The map band carries a full-height drawing, so the shared tight rhythm
   leaves it pressed against its neighbours. Desktop only: on a phone the
   map is already stacked and the tight rhythm is right. The seam rule sets
   this band's padding-top, so both sides are restated here to stay equal. */
@media (min-width: 1024px) {
  main > section.band--tight:has(> .shell > .country),
  main > section.band--tight:has(> .shell > .mapbox) {
    padding-top: clamp(5.5rem, 6.8vw, 7rem);
    padding-bottom: clamp(5.5rem, 6.8vw, 7rem);
  }
}

/* ---- Map: fill the band rather than sit small inside a box ---- */
/* Sizing the drawing by width and then capping its height letterboxed it:
   the SVG box stayed 832px wide while the country only drew 707px of that,
   so ~60px of dead ground sat on each side and the map read as floating.
   Drive it off height instead and the element is the drawing, flush left. */
.mapfig svg {
  width: 100%;
  height: auto;
  max-height: none;
  margin-inline: 0;
}
/* This is the section's heading, at aria-level 2, so it takes the section
   heading size. At --fs-h3 it matched the farm-card titles and the whole
   column read as a caption beside the drawing. */
.maphead h2 { font-size: var(--fs-h2); }

/* Below 901 the map is a picture, not a control. The dots were grown here to
   be tappable and it never worked: a 24px target at 375 needs 71.6 viewBox
   units and the whole Overberg cluster spans 28. All it bought was a knot of
   merged gold that painted past the coastline. The province buttons are the
   finger-sized control on a phone, smallest 76x80px, and the farm list below
   is the other. So the dots stay their real size and the pins stop taking
   taps; script.js takes them out of the tab order at the same width, and the
   sentence beside the map stops promising a tap. */
@media (max-width: 900px) {
  .mapfig .pin { pointer-events: none; }
  .maphead__tap { display: none; }
  .mapfig svg { max-height: none; }
}

/* ---- Filter chips: one tidy row of type chips, the shortlist set apart ---- */
.filters { align-items: center; }
.filter-btn[data-saved] {
  margin-left: auto;
  border-color: var(--ink);
  color: var(--ink);
}

/* The sideways-scrolling chip row existed because ten chips wrapped to a
   quarter of the screen. The type menu replaced them, so a phone now has two
   controls and they simply stack: the menu takes the row, the shortlist sits
   under it. No scroller, no pinned chip, no mask. */
@media (max-width: 700px) {
  .filters { flex-wrap: wrap; gap: 0.6rem; }
  .typemenu { flex: 1 1 100%; }
  .filter-btn[data-saved] { flex: 1 1 100%; margin-left: 0; text-align: center; }
}

/* ---- Search bar: the placeholder was doing the work of a label ---- */
.findbar input::placeholder { font-size: 0.9em; }

/* ---- Compare: controls sit on the ink masthead ---- */
.page-hero--flat + .band .cmp-controls { border-bottom-color: var(--line); }

/* ---- Fact trays keep one height whether a label wraps or not ---- */
.farm-card__meta span { justify-content: flex-start; }
.farm-card__meta i { min-height: 2.4em; }

/* ---- Chips flow; the shortlist is set apart by treatment, not position ---- */
.filter-btn[data-saved] { margin-left: 0; border-color: var(--gold-deep); color: var(--gold-deep); }
.filter-btn[data-saved]:hover { border-color: var(--ink); color: var(--ink); }
.filter-btn { padding: 0.55rem 0.9rem; }

/* ---- The map earns more of its band ---- */
/* Two equal halves. The drawing was previously sized from a height cap turned
   into a width, which on a wide screen left the map smaller than the list it
   sat next to and stretched every province row to nearly a metre of rule for
   one word. Half each: the map grows with the band, the rows halve. */
.mapbox {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  /* Centring the two columns left the drawing 21px out from the heading at
     the top and 21px out at the bottom. Both start on the same line instead. */
  align-items: start;
}
.provlist-note {
  margin-top: 1.25rem;
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--muted);
}
@media (max-width: 900px) { .mapbox { grid-template-columns: minmax(0, 1fr); } }
@media print { .mapbox { display: none; } }

/* The supporting facts under the gallery: three columns, quieter than the
   headline band, because they are the detail rather than the argument. */
.factgrid--rest { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 640px) { .factgrid--rest { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* The action row wrapped one button onto a line of its own. Let it breathe
   into an even grid instead of a ragged wrap. */
.detail-actions { display: grid; grid-template-columns: repeat(4, minmax(0, auto)); justify-content: start; gap: 0.6rem; }
.detail-actions .btn, .detail-actions .save-btn--inline { width: auto; }
@media (max-width: 1100px) and (min-width: 901px) {
  .detail-actions { grid-template-columns: repeat(2, minmax(0, 13.5rem)); }
  .detail-actions .btn, .detail-actions .save-btn--inline { width: 100%; }
}
@media (max-width: 560px) { .detail-actions { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* At 375 the tray cell is 78px and the word "Registered" set 16px is 80px:
   one size step down for the narrowest phones keeps every value whole. */
@media (max-width: 420px) {
  .farm-card__meta b { font-size: 0.9375rem; }
}

/* Tile labels sat at different heights where a name wrapped, so the row read
   as broken. Reserve the second line on all of them. */
.type-tile__label h3 { min-height: 2.4em; display: flex; align-items: flex-end; }
.type-tile__label { align-items: flex-end; }
.type-tile__count { padding-bottom: 0.15em; }

/* A white band on the warm canvas has to read as a surface, not as a tint
   step, so it carries an edge the way a card does. */
.band--surface {
  border-block: 1px solid var(--line-soft);
}

/* Toolbar now carries three controls plus a clear, so it needs to wrap well. */
.toolbar__controls { display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap; }
.toolbar__controls > span { display: inline-flex; align-items: center; }
.toolbar__clear {
  background: transparent; border: 0; cursor: pointer;
  font-family: var(--display); font-size: 0.75rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--gold-deep);
  padding: 0.55rem 0; border-bottom: 1px solid currentColor;
}
.toolbar__clear:hover { color: var(--ink); }
@media (max-width: 640px) {
  .toolbar { align-items: flex-start; }
  .toolbar__controls { width: 100%; gap: 0.75rem; }
  .toolbar__controls > span { flex: 1 1 46%; }
  /* 16px for the same reason as the enquiry fields: below it, focusing a
     control zooms the whole page in on iOS. The row is already full width and
     wrapping here, so the larger type costs nothing. */
  .toolbar__controls select { width: 100%; font-size: 1rem; }
}

@media (max-width: 640px) {
  /* Label above the control, so a select is never squeezed to "Any ex". */
  .toolbar__controls > span { flex-direction: column; align-items: stretch; gap: 0.3rem; }
  .toolbar__controls label { margin-right: 0; }
  .toolbar__clear { flex: 1 1 100%; text-align: left; }
  /* The long placeholder was being cut mid-word. */
  .findbar { flex-direction: column; align-items: stretch; gap: 0.35rem; }
  .findbar input { font-size: 1.0625rem; }
}

@media print {
  /* The ink-band component colours were written after the print block and
     outrank it, so a printed schedule came out white on white. Everything on
     a dark band prints as ink on paper. */
  .band--ink, .band--ink * { background: transparent !important; }
  .band--ink,
  .band--ink h1, .band--ink h2, .band--ink h3, .band--ink h4,
  .band--ink p, .band--ink span, .band--ink b,
  .band--ink .spec-group h3,
  .band--ink .spec-group dt,
  .band--ink .spec-group dd,
  .band--ink .schedule-meter b,
  .band--ink .schedule-meter span,
  .band--ink .row-item h3,
  .band--ink .row-item__body p,
  .band--ink .counter__n,
  .band--ink .counter__label,
  .band--ink .counter__note { color: #000 !important; }
  .band--ink .spec-group dt { color: #444 !important; }
  .band--ink .spec-group { border-top-color: #000 !important; }
  .band--ink .spec-group dt,
  .band--ink .spec-group dd { border-bottom-color: #ccc !important; }
  .band--ink .schedule-meter { border: 1pt solid #000 !important; border-left-width: 3pt !important; }
  .headline-facts { break-inside: avoid; }
  .headline-facts dt { color: #000 !important; }
  .mapbox, .page-hero--flat { display: none !important; }
}

/* --------------------------------------------------------------------------
   27. Findings from the independent review and the accessibility audit.
   -------------------------------------------------------------------------- */

/* A6. The global focus ring used bright gold, which the tokens reserve for ink
   grounds. On canvas it measured 2.56:1 against a 3:1 requirement, and on the
   gold button it was the same colour as the button. Two-tone: ink core, light
   halo, so it reads on any ground. */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--paper) 92%, transparent);
}
.band--ink :focus-visible,
/* The header bar and the phone sheet are ink grounds like the rest of this
   list; they were taking the light-ground ring. */
.site-header :focus-visible,
.site-footer :focus-visible,
.enquiry-card :focus-visible,
.hero :focus-visible,
.page-hero :focus-visible,
.cta :focus-visible,
.cmp-tray :focus-visible,
.lightbox :focus-visible {
  outline-color: var(--gold);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--ink-deep) 88%, transparent);
}

/* A7. The gold hero line sat on a pale storm sky: 84% of its area was under
   3:1. The scrim gains a horizontal pass weighted to the copy column, which
   darkens the text side without flattening the photograph. */
/* One flat, even wash over the whole frame (the Helix / Tee-to-Trail scrim),
   with only a gentle floor for the seam into the search rail.
   Deepened 2026-09-09 from 44/40/62. At 44% the hero failed contrast on every
   piece of text it carried, measured at 3x on the real pixels of all three
   frames at 1280: white h1 2.69:1 against a 3:1 bar, accent word 1.56:1, lede
   2.98:1 against 4.5:1, and the header nav 2.61:1. Solved from the naked
   photograph: 65% ink clears every one of them, so the wash holds 68% and is
   still one even field rather than a vignette. This is the rule in force; the
   one in section 6 is the same wash so the two can never disagree. */
.hero__scrim {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--ink-deep) 78%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 68%, transparent) 30%,
      color-mix(in srgb, var(--ink-deep) 68%, transparent) 70%,
      color-mix(in srgb, var(--ink-deep) 80%, transparent) 100%);
}

/* A1. The closed mobile menu kept four focusable links and stayed in the
   accessibility tree while the toggle reported it collapsed. */
@media (max-width: 900px) {
  .nav { visibility: hidden; }
  .nav.is-open { visibility: visible; }
}

/* The open menu holds the document still. script.js sets this on <html> and
   parks the scroll position, so nothing moves behind the sheet and nothing
   jumps when it closes. */
html.nav-open { overflow: hidden; }
html.nav-open body { position: fixed; left: 0; right: 0; width: 100%; }
/* The toggle had one state for rest, hover and press. */
.nav-toggle:hover { border-color: color-mix(in srgb, var(--paper) 34%, transparent); }
.nav-toggle:active { transform: scale(0.94); }
.nav-toggle[aria-expanded="true"] { border-color: var(--gold); color: var(--gold); }
/* Transform only, the site's own lesson: an entrance that also animates
   opacity leaves the row invisible wherever the animation never advances (a
   backgrounded tab throttles it, and `both` holds the from-state). The rows
   paint on the first frame and only the movement is animated. */
@keyframes nav-row {
  from { transform: translateY(10px); }
  to { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .nav { transition: opacity var(--t-fast) linear; transform: none; }
  .nav.is-open .nav-list li,
  .nav.is-open .nav-extra > * { animation: none; }
  .nav-toggle:active { transform: none; }
}

/* A5. The bond sliders had a 173 x 2px hit box. The track is painted by the
   pseudo-elements so the input itself can be a real 24px target. */

/* A-B1. At 375 the compare tray's children overlapped: the gold call to action
   covered the third slot and its remove button. It wraps now. */
.cmp-tray { flex-wrap: wrap; }
@media (max-width: 640px) {
  .cmp-tray__slots { order: 1; flex: 1 1 100%; }
  .cmp-tray__actions { order: 2; flex: 1 1 100%; justify-content: space-between; }
  .cmp-slot { width: 74px; height: 54px; }
  .cmp-slot__x { width: 26px; height: 26px; inset: auto 3px 3px auto; }
}

/* A-B2. thead was sticky against .cmp-wrap, and overflow-y:visible next to
   overflow-x:auto computes back to auto, so the wrapper stayed a scroll
   container and the header never stuck on a 96 row table. Since the phone
   layout stacks the table, nothing needs to scroll sideways at any width, so
   the wrapper stops being a scroll container at all and thead sticks to the
   viewport under the site header. */
.cmp-wrap { overflow: visible; }
.cmp-table thead th { top: calc(var(--header-h) - 1px); }

/* A8. A dismissed tray kept five focusable controls, including remove buttons
   for farms that were no longer selected. */
.cmp-tray:not(.is-up) { visibility: hidden; }

/* A-S2/S3. Form fields had a 1.31:1 boundary and a focus ring under 3:1. */
.field input, .field textarea, .field select { border-color: color-mix(in srgb, var(--ink) 34%, transparent); }
.field input:focus, .field textarea:focus, .field select:focus {
  border-color: var(--ink);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 18%, transparent);
}
.enquiry-card .field input, .enquiry-card .field textarea, .enquiry-card .field select {
  border-color: color-mix(in srgb, var(--paper) 40%, transparent);
}
.enquiry-card .field input:focus, .enquiry-card .field textarea:focus {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 30%, transparent);
}
.btn--outline { border-color: color-mix(in srgb, var(--ink) 32%, transparent); }

/* A-S4/M5. The error red was 3.97:1 on canvas and 4.20:1 on ink. */
.form-status[data-state="error"] { color: var(--err); }
.enquiry-card .form-status[data-state="error"],
.band--ink .form-status[data-state="error"] { color: var(--err-dark); }

/* A-S20. The tile count text was clipped at 320. */
@media (max-width: 380px) {
  .type-tile__label { flex-direction: column; align-items: flex-start; gap: 0.2rem; }
  .type-tile__count { padding-bottom: 0; }
}

/* A-M10. #listing-detail grew from 266px to 9214px once the JSON resolved,
   shoving the CTA band down the page and costing 0.48 CLS. Reserve the space. */
#listing-detail { min-height: 150vh; }
/* The reservation is for a listing that IS coming. When the fetch resolves to
   nothing there is nothing to hold space for, and the reservation left a
   1350px hole under a 268px "not available" card, which is the dead-space rule
   broken by an edge state. :has() is Baseline (Chrome 105, Safari 15.4,
   Firefox 121); where it is missing the page keeps the reserved height, which
   is today's behaviour rather than a broken one. */
#listing-detail:has(.farm-empty) { min-height: 0; }

@media (max-width: 640px) {
  /* The phone head scrolls away with the page and .cmp-mini names the columns
     once it has gone (script.js, miniHead). It used to pin here and fold its
     photos away as it stuck, which fought Chrome's scroll anchoring. */
  .cmp-table thead th { position: static; background: transparent; }
}
.cmp-mini { display: none; }
@media (max-width: 640px) {
  .cmp-mini {
    display: grid;
    grid-template-columns: repeat(var(--cmp-cols, 2), minmax(0, 1fr));
    gap: 0 0.75rem;
    position: fixed;
    top: var(--header-h); left: 0; right: 0;
    z-index: 90;
    padding: 0.55rem max(var(--gutter), var(--safe-left)) 0.6rem max(var(--gutter), var(--safe-right));
    background: var(--canvas);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 8px 16px -12px color-mix(in srgb, var(--ink) 50%, transparent);
    transform: translateY(-110%);
    visibility: hidden;
    transition: transform var(--t-mid) var(--ease), visibility 0s linear var(--t-mid);
  }
  .cmp-mini.is-on { transform: none; visibility: visible; transition: transform var(--t-mid) var(--ease), visibility 0s; }
  .cmp-mini .t {
    display: block;
    font-family: var(--display); font-size: 0.8125rem; text-transform: uppercase;
    letter-spacing: 0.02em; line-height: 1.2; color: var(--ink);
    overflow-wrap: anywhere; -webkit-hyphens: auto; hyphens: auto;
  }
  .cmp-mini .p { display: block; font-size: 0.8125rem; color: var(--gold-deep); margin-top: 0.15rem; font-variant-numeric: tabular-nums; }
}


/* --------------------------------------------------------------------------
   28. Final pass: navigation feel, the jump nav, and touch polish.
   -------------------------------------------------------------------------- */

/* Page-to-page crossfade. The header keeps its own name so it holds still
   while the page changes under it. Off entirely for reduced motion. */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 220ms; }
#site-header { view-transition-name: site-header; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

html { scroll-behavior: smooth; scroll-padding-top: calc(var(--header-h) + 64px); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

::selection { background: var(--gold); color: var(--ink-deep); }

a, button { -webkit-tap-highlight-color: transparent; }
/* .btn:active is stated once, in section 30.3. */

/* The jump nav on a farm page: one line, sticky under the header, the
   current section marked in gold. Scrolls its own overflow on a phone. */
.jumpnav {
  position: sticky;
  top: calc(var(--header-h) - 1px);
  z-index: 40;
  background: color-mix(in srgb, var(--canvas) 92%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
  margin-top: clamp(1.5rem, 2.5vw, 2rem);
}
.jumpnav__inner {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2rem);
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.jumpnav__inner::-webkit-scrollbar { display: none; }
.jumpnav a {
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: 48px;
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom: 2px solid transparent;
  transition: color var(--t-fast) var(--ease-soft),
              border-color var(--t-fast) var(--ease-soft);
}
.jumpnav a:hover { color: var(--ink); }
.jumpnav a[aria-current="true"] { color: var(--ink); border-bottom-color: var(--gold); }
.jumpnav__cta { margin-left: clamp(0.5rem, 2vw, 1.5rem); color: var(--gold-deep); }
#photos, #about-farm, #schedule, #bond, #similar-farms { scroll-margin-top: calc(var(--header-h) + 56px); }

/* Detail actions: 2x2 on a phone instead of four stacked rows of chrome
   between the title and the price. */
@media (max-width: 900px) {
  .detail-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.6rem;
  }
  .detail-actions .btn, .detail-actions .save-btn--inline {
    width: 100%;
    justify-content: center;
    padding-inline: 0.4rem;
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
  }
}

/* The saved-farm control: solid grounds, no glass. */
.save-btn {
  background: var(--paper);
  backdrop-filter: none;
  border: 0;
  color: var(--ink);
  box-shadow: 0 1px 6px color-mix(in srgb, var(--ink-deep) 30%, transparent);
}
.save-btn:hover { color: var(--gold-deep); }


/* Subtle depth on the big photographs. Scroll-driven, so it costs nothing
   on the main thread; off when the visitor asks for reduced motion, and
   absent entirely where the timeline is unsupported. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    /* The home hero's scroll drift was deleted on 2026-09-15: the hero is
       one still photograph (section 41). It only ever sat inert because
       html's overflow-x guard leaves a scroll() timeline inactive. */
    .cta__media > img,
    .cta__media > .arrival {
      animation: band-pan linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    @keyframes band-pan {
      from { transform: translateY(-6%) scale(1.13); }
      to { transform: translateY(6%) scale(1.13); }
    }
  }
}

#about-farm .enquiry-card { position: sticky; top: calc(var(--header-h) + 72px); }

/* Field-level form errors. */
.field-err { font-size: var(--fs-label); color: var(--err); }
.enquiry-card .field-err { color: var(--err-dark); }
.field [aria-invalid="true"] { border-color: var(--err); }
.enquiry-card .field [aria-invalid="true"] { border-color: var(--err-dark); }

@media print {
  .jumpnav { display: none !important; }
}

/* Compare header columns share one geometry: equal photographs, top-aligned
   stacks, the price pinned to a common last line. Lives at the end of the
   cascade so the component's base block cannot override it. */
/* Desktop and tablet only: below 641 the table stacks and these rules
   collapsed the header cells to 1px, spilling their content over the first
   rows (round 7 regression, caught by review). */
@media (min-width: 641px) {
  .cmp-table thead th { vertical-align: top; height: 1px; }
  /* The 1px height is the table-cell trick that makes a child's height:100%
     real, so the flex column can pin the price to one shared last line no
     matter how the names and places wrap. */
  .cmp-head { height: 100%; }
  .cmp-head img { aspect-ratio: 16 / 10; height: auto; }
  .cmp-head .p { margin-top: auto; padding-top: 0.5rem; }
}
@media (max-width: 640px) {
  .cmp-table thead th { height: auto; vertical-align: top; }
  .cmp-head { height: auto; }
}

/* --------------------------------------------------------------------------
   29. /better round 10: line breaks, motion states, touch floors.
   -------------------------------------------------------------------------- */

/* Headings and ledes balance their breaks; body copy avoids lone last
   words. Progressive: browsers without it change nothing. */
@supports (text-wrap: balance) {
  h1, h2, h3, .lede { text-wrap: balance; }
}
@supports (text-wrap: pretty) {
  main p { text-wrap: pretty; }
}

/* The card itself answers a hover, not just its photograph: a small lift
   with a soft ink shadow, in the same motion tokens as everything else. */
.js .farm-card.reveal.is-visible {
  transition: transform var(--t-mid) var(--ease),
              box-shadow var(--t-mid) var(--ease);
}
.farm-card {
  transition: transform var(--t-mid) var(--ease),
              box-shadow var(--t-mid) var(--ease);
}
.farm-card:hover,
.js .farm-card.reveal.is-visible:hover {
  transform: translateY(-4px);
  box-shadow: 0 18px 40px -18px color-mix(in srgb, var(--ink-deep) 38%, transparent);
}

/* The jump nav marker grows in instead of snapping. */
.jumpnav a { position: relative; border-bottom: 0; }
.jumpnav a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--t-mid) var(--ease);
}
.jumpnav a[aria-current="true"]::after { transform: scaleX(1); }

/* Touch floors: on coarse pointers every chip-sized control reaches 44px. */
@media (pointer: coarse) {
  .filter-btn, .type-more a, .cmp-switch,
  .provlist button, .spec-tab {
    min-height: 44px;
  }
  .findbar input, .searchrail__field select { min-height: 44px; }
}

/* The type tiles clip their contents, so their focus ring draws inside. */
.type-tile:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: -6px;
  box-shadow: none;
}

/* Dark-ground chips on the 404 read a step brighter. */
.type-more--dark a { color: color-mix(in srgb, var(--paper) 94%, transparent); }

@media (prefers-reduced-motion: reduce) {
  .farm-card { transition: none; }
  .farm-card:hover { transform: none; }
  .jumpnav a::after { transition: none; }
}

/* --------------------------------------------------------------------------
   31. The receptionist. Launcher pulses until first opened; the panel is a
   small sheet in the site's own voice, full-screen on phones.
   -------------------------------------------------------------------------- */
.chat-fab {
  position: fixed;
  right: clamp(1rem, 2.5vw, 1.75rem);
  bottom: clamp(1rem, 2.5vw, 1.75rem);
  z-index: 110;
  width: var(--fab-size);
  height: var(--fab-size);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ink-deep);
  color: var(--paper);
  border: 2px solid color-mix(in srgb, var(--gold) 65%, transparent);
  box-shadow: 0 12px 34px -12px color-mix(in srgb, var(--ink-deep) 65%, transparent);
  transition: transform var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease-soft),
              opacity var(--t-fast) var(--ease-soft);
}
.chat-fab::before {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 2px solid var(--gold);
  animation: chat-halo 2.6s var(--ease-soft) infinite;
  pointer-events: none;
}
.chat-fab.is-quiet::before { animation: none; opacity: 0; }
@keyframes chat-halo {
  0% { transform: scale(1); opacity: 0.7; }
  70%, 100% { transform: scale(1.5); opacity: 0; }
}
.chat-fab svg { width: 27px; height: 27px; }
.chat-fab:hover { transform: translateY(-3px); background: var(--gold); color: var(--ink); }
/* The label rides beside the launcher until the chat is first opened, then
   only returns on hover or focus. */
.chat-fab__label {
  position: absolute;
  right: calc(100% + 0.8rem);
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.55rem 0.9rem;
  border-radius: var(--r-chip);
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  box-shadow: 0 6px 20px -8px color-mix(in srgb, var(--ink-deep) 40%, transparent);
  transition: opacity var(--t-fast) var(--ease-soft),
              visibility var(--t-fast) var(--ease-soft);
}
.chat-fab.is-quiet .chat-fab__label { opacity: 0; visibility: hidden; }
.chat-fab.is-quiet:hover .chat-fab__label,
.chat-fab.is-quiet:focus-visible .chat-fab__label { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) {
  .chat-fab { transition: none; }
  .chat-fab:hover { transform: none; }
  .chat-fab::before { animation: none; opacity: 0; }
}
@media print {
  .chat-fab, .chat-panel { display: none !important; }
}

.chat-panel {
  position: fixed;
  right: clamp(1rem, 2.5vw, 1.75rem);
  bottom: calc(clamp(1rem, 2.5vw, 1.75rem) + 72px);
  z-index: 130;
  width: min(400px, calc(100vw - 2rem));
  max-height: min(600px, 78vh);
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  box-shadow: 0 24px 60px -24px color-mix(in srgb, var(--ink-deep) 45%, transparent);
  overflow: hidden;
  transform-origin: 92% 100%;
}
.chat-panel[hidden] { display: none; }
.chat-panel.is-open { animation: chat-in 0.38s var(--ease) both; }
@keyframes chat-in {
  from { transform: scale(0.9) translateY(14px); opacity: 0; }
  to { transform: scale(1) translateY(0); opacity: 1; }
}
/* Closing is quieter and quicker than opening. */
.chat-panel.is-closing { animation: chat-out 0.17s var(--ease-soft) both; }
@keyframes chat-out {
  to { transform: scale(0.98) translateY(8px); opacity: 0; }
}
.chat-head {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 0.85rem 1.1rem;
  background: var(--ink-deep);
  color: var(--paper);
  border-bottom: 2px solid var(--gold);
}
.chat-head__mark { width: 34px; height: auto; flex: none; }
.chat-head > div { flex: 1; min-width: 0; }
.chat-head strong {
  display: block;
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-size: var(--fs-label);
  color: var(--gold);
}
.chat-head span {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78125rem;
  color: color-mix(in srgb, var(--paper) 72%, transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chat-dot {
  flex: none;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--gold);
  animation: chat-breathe 2.2s ease-in-out infinite;
}
@keyframes chat-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.chat-close {
  background: transparent;
  border: 0;
  color: var(--paper);
  font-size: 1.5rem;
  line-height: 1;
  width: 44px;
  height: 44px;
  flex: none;
  cursor: pointer;
  border-radius: var(--r-chip);
  transition: color var(--t-fast) var(--ease-soft);
}
.chat-close:hover { color: var(--gold); }
.chat-log {
  flex: 1;
  overflow-y: auto;
  padding: 1rem 1rem 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  background: var(--canvas);
}
.chat-msg {
  max-width: 85%;
  padding: 0.65rem 0.9rem;
  font-size: 0.9375rem;
  line-height: 1.5;
  white-space: pre-wrap;
  animation: chat-msg-in 0.24s var(--ease) both;
}
@keyframes chat-msg-in {
  from { transform: translateY(8px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
/* The receptionist speaks under the brand mark; the visitor in ink. */
.chat-msg--bot {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 4px 14px 14px 14px;
  align-self: flex-start;
  margin-left: 34px;
}
.chat-msg--bot::before {
  content: "";
  position: absolute;
  left: -34px;
  top: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--ink-deep) url("assets/logo-white-160.png") center / 15px auto no-repeat;
}
.chat-msg--me {
  background: var(--ink);
  color: var(--paper);
  align-self: flex-end;
  border-radius: 14px 4px 14px 14px;
}
.chat-lead {
  align-self: stretch;
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  background: color-mix(in srgb, var(--gold) 9%, var(--paper));
  border-radius: var(--r-card);
  padding: 0.75rem 0.95rem;
  font-size: 0.875rem;
  animation: chat-msg-in 0.24s var(--ease) both;
}
.chat-lead span {
  display: block;
  font-family: var(--display);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin-bottom: 0.25rem;
}
/* An arrow, not a tick: nothing has been sent yet, the visitor still acts. */
.chat-lead span::before { content: "\2192\00a0"; }
.chat-lead b { display: block; font-weight: 600; color: var(--ink); }
.chat-lead i { font-style: normal; color: var(--muted); }
/* Tappable contact buttons, shown whenever the receptionist gives out
   Martiens' details. WhatsApp leads, because that is how he actually answers. */
.chat-actions {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  animation: chat-msg-in 0.24s var(--ease) both;
}
.chat-action {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  /* 0.72rem keeps the row on a 44px tap target. */
  padding: 0.72rem 0.85rem;
  border: 1px solid var(--line);
  border-radius: calc(var(--r-card) - 4px);
  background: var(--paper);
  color: var(--ink);
  font-size: 0.875rem;
  font-weight: 500;
  text-decoration: none;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease), transform 0.2s var(--ease);
}
.chat-action svg { width: 18px; height: 18px; flex: none; color: var(--gold-deep); }
.chat-action:hover {
  border-color: color-mix(in srgb, var(--gold) 55%, transparent);
  background: color-mix(in srgb, var(--gold) 8%, var(--paper));
  transform: translateY(-1px);
}
.chat-action:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* WhatsApp is the one he answers on, so it reads as the primary action. */
.chat-action--whatsapp {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.chat-action--whatsapp svg { color: var(--gold); }
.chat-action--whatsapp:hover { background: var(--gold-dark); border-color: var(--gold-dark); }
@media (prefers-reduced-motion: reduce) {
  .chat-actions { animation: none; }
  .chat-action { transition: none; }
  .chat-action:hover { transform: none; }
}
/* The action the visitor takes: their own WhatsApp, message already written. */
.chat-lead-go {
  display: block;
  margin-top: 0.7rem;
  /* Matches the contact rows at a 44px tap target. */
  padding: 0.8rem 1rem;
  border-radius: calc(var(--r-card) - 4px);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--display);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  transition: background 0.2s var(--ease), transform 0.2s var(--ease);
}
.chat-lead-go:hover { background: var(--gold-dark); transform: translateY(-1px); }
.chat-lead-go:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.chat-lead em {
  display: block;
  margin-top: 0.45rem;
  font-style: normal;
  font-size: 0.75rem;
  color: var(--muted);
  text-align: center;
}
@media (prefers-reduced-motion: reduce) {
  .chat-lead-go { transition: none; }
  .chat-lead-go:hover { transform: none; }
}
.chat-typing span {
  display: inline-block;
  width: 6px; height: 6px;
  margin-right: 4px;
  border-radius: 50%;
  background: var(--gold-deep);
  animation: chat-dot-hop 1s infinite ease-in-out;
}
.chat-typing span:nth-child(2) { animation-delay: 0.15s; }
.chat-typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes chat-dot-hop { 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; } 30% { transform: translateY(-4px); opacity: 1; } }
.chat-chips {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.4rem;
  padding: 0.25rem 1rem 0.85rem;
  background: var(--canvas);
}
/* display:grid outguns the UA [hidden] rule; restate it. */
.chat-chips[hidden] { display: none; }
.chat-chips button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  min-height: 42px;
  text-align: left;
  border: 1px solid color-mix(in srgb, var(--gold) 38%, var(--line));
  background: var(--paper);
  border-radius: var(--r-chip);
  padding: 0.5rem 0.85rem;
  font-size: 0.84375rem;
  color: var(--ink);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-soft),
              background var(--t-fast) var(--ease-soft);
}
.chat-chips button::after {
  content: "\2192";
  color: var(--gold-deep);
  flex: none;
}
.chat-chips button:hover {
  border-color: var(--gold-deep);
  background: color-mix(in srgb, var(--gold) 8%, var(--paper));
}
.chat-form {
  display: flex;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border-top: 1px solid var(--line);
  background: var(--paper);
}
/* The composer is one pill: the field and the send button share it, and
   the focus ring lives on the pill (phone ruling of 2026-09-05). */
.chat-pill {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.3rem 0.3rem 1rem;
  border: 1px solid color-mix(in srgb, var(--ink) 34%, transparent);
  border-radius: 999px;
  background: var(--paper);
  transition: border-color var(--t-fast) var(--ease-soft), box-shadow var(--t-fast) var(--ease-soft);
}
.chat-pill:focus-within {
  border-color: var(--gold-deep);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 35%, transparent);
}
.chat-form input {
  flex: 1;
  min-width: 0;
  border: 0;
  border-radius: 0;
  padding: 0.45rem 0;
  font: inherit;
  font-size: 0.9375rem;
  background: transparent;
  color: var(--ink);
}
.chat-form input:focus { outline: none; }
.chat-send {
  width: 44px;
  height: 44px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  /* Grey until there is something to send. */
  background: color-mix(in srgb, var(--ink) 10%, var(--paper));
  color: color-mix(in srgb, var(--ink) 55%, var(--paper));
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease-soft),
              color var(--t-fast) var(--ease-soft),
              transform var(--t-fast) var(--ease);
}
.chat-form.has-text .chat-send { background: var(--gold); color: var(--ink); }
.chat-send svg { width: 20px; height: 20px; }
.chat-form.has-text .chat-send:hover { background: var(--gold-deep); color: var(--paper); transform: translateY(-2px); }
.chat-foot {
  padding: 0 1rem 0.9rem;
  font-size: 0.75rem;
  color: var(--muted);
  background: var(--paper);
}
.chat-foot a { color: var(--gold-deep); text-decoration: underline; }
@media (max-width: 540px) {
  .chat-panel {
    right: 0;
    bottom: 0;
    width: 100vw;
    max-height: 92dvh;
    border-radius: var(--r-card) var(--r-card) 0 0;
    border-inline: 0;
    border-bottom: 0;
    transform-origin: 50% 100%;
  }
}
@media (prefers-reduced-motion: reduce) {
  .chat-typing span { animation: none; opacity: 0.7; }
  .chat-panel.is-open, .chat-panel.is-closing, .chat-msg, .chat-lead { animation: none; }
  .chat-dot { animation: none; }
  .chat-teaser, .chat-teaser.is-out { transition: none; }
  .chat-fab.is-nudged { animation: none; }
  .chat-chips button { animation: none; }
}

/* --------------------------------------------------------------------------
   31b. The receptionist speaks first. One teaser per session, growing out of
   the launcher, then a quiet unread count. Nothing here loops: the halo has
   already had its turn and is switched off the moment the bubble lands.
   -------------------------------------------------------------------------- */
.chat-teaser {
  position: fixed;
  right: clamp(1rem, 2.5vw, 1.75rem);
  bottom: calc(clamp(1rem, 2.5vw, 1.75rem) + 74px);
  z-index: 120;
  width: min(290px, calc(100vw - 2rem));
  background: var(--paper);
  border: 1px solid color-mix(in srgb, var(--gold) 45%, var(--line));
  border-radius: var(--r-card) var(--r-card) 4px var(--r-card);
  box-shadow: 0 18px 44px -18px color-mix(in srgb, var(--ink-deep) 55%, transparent);
  transform-origin: 100% 100%;
  opacity: 0;
  transform: translateY(14px) scale(0.92);
  transition: opacity 420ms var(--ease),
              transform 420ms var(--ease);
}
.chat-teaser.is-in { opacity: 1; transform: none; }
/* Leaving is subtler and quicker than arriving. */
.chat-teaser.is-out {
  opacity: 0;
  transform: translateY(6px);
  transition-duration: 170ms;
  transition-timing-function: var(--ease-soft);
}
.chat-teaser__open {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  width: 100%;
  padding: 0.85rem 1rem 0.85rem 0.85rem;
  text-align: left;
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: inherit;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-soft);
}
.chat-teaser__open:hover { background: color-mix(in srgb, var(--gold) 8%, var(--paper)); }
.chat-teaser__mark {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--ink-deep);
}
.chat-teaser__mark img { width: 20px; height: auto; }
.chat-teaser__x {
  position: absolute;
  top: -10px;
  right: -10px;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--paper);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 6px 20px -8px color-mix(in srgb, var(--ink-deep) 45%, transparent);
  transition: background var(--t-fast) var(--ease-soft),
              color var(--t-fast) var(--ease-soft);
}
.chat-teaser__x:hover { background: var(--ink-deep); color: var(--paper); }
/* Unread count on the launcher. Static on purpose. */
.chat-badge {
  position: absolute;
  top: -3px;
  right: -3px;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--gold);
  color: var(--ink);
  font-family: var(--display);
  font-size: 0.75rem;
  line-height: 22px;
  text-align: center;
  box-shadow: 0 0 0 2px var(--ink-deep);
}
/* One settle when the bubble lands, never a loop. */
.chat-fab.is-nudged { animation: chat-nudge 620ms var(--ease); }
@keyframes chat-nudge {
  0% { transform: translateY(0); }
  34% { transform: translateY(-6px); }
  100% { transform: translateY(0); }
}
/* The opener is typed, so the chips follow it in instead of sitting there
   waiting. Opacity and a short rise, both on the compositor; the buttons own
   their hover colours. */
.chat-chips button {
  animation: chat-chip-in 380ms var(--ease) both;
  animation-delay: calc(var(--i, 0) * 70ms);
}
@keyframes chat-chip-in { from { opacity: 0; transform: translateY(4px); } }

/* --------------------------------------------------------------------------
   30. Mobile rhythm. The band scale was desktop's clamp floor, so colour
   seams stacked ~180px of air on a phone while component insides stayed
   desktop-tight. One scale for small screens, and every seam, full or
   collapsed, shrinks in proportion because they are all calc'd from it.
   -------------------------------------------------------------------------- */
@media (max-width: 640px) {
  :root {
    --band: clamp(3.75rem, 15vw, 5rem);
    --band-tight: clamp(3rem, 12vw, 4rem);
  }
  .section-head { margin-bottom: clamp(1.75rem, 8vw, 2.5rem); }
}

/* The leftover-type chips on a phone: a clean two-column grid, with the
   view-everything route on its own full row instead of dangling. */
@media (max-width: 640px) {
  .type-more {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.5rem;
  }
  .type-more a {
    justify-content: center;
    text-align: center;
    display: inline-flex;
    align-items: center;
  }
  /* The route to everything takes its own full row, and so does a last
     farm type that would otherwise sit alone beside an empty cell. It used
     to be :last-child, which assumed the last chip was always the route to
     everything: on the 404 it is not, and a type was stranded (fourth
     review, G5; phone ruling of 2026-09-05). */
  .type-more .type-more__all { grid-column: 1 / -1; border-color: var(--ink); color: var(--ink); }
  .type-more > a:nth-child(odd):has(+ .type-more__all),
  .type-more > a:not(.type-more__all):last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* Stacked stat rows were touching once the grid collapsed to one column. */
  .stat-strip { row-gap: 1.5rem; }
}

/* ==========================================================================
   32. Round 12: the photographs step forward, mobile as its own design
   ========================================================================== */
/* Light grade so the land reads as photography, not backdrop. Subtle enough
   that the copy zones above keep their measured contrast. */
.hero__media img,
.cta__media img,
.cta__media video,
.page-hero__media img {
  filter: saturate(1.14) contrast(1.06) brightness(1.06);
}
.farm-card__media img { filter: saturate(1.08) contrast(1.03); }

@media (max-width: 640px) {
  /* Credentials: the desktop rule-and-gap pair wrapped into a ragged second
     line with a stray left border. Stacked, flush left, no rules. */
  .credentials { flex-direction: column; gap: 0.7rem; }
  .credentials li + li { border-left: 0; padding-left: 0; margin-left: 0; }

  /* The same flat wash as desktop, one step firmer for full-width copy. */
  /* On a phone the copy is centred and full width, so it crosses the whole
     photograph and there is nowhere for it to hide. This wash was the lightest
     on the site (52% down to 48%) under the text that needed it most. Solved
     from the naked photograph: the lede needs 65% ink to clear 4.5:1 on all
     three hero frames and the accent word needs 65% to clear 3:1, so the copy
     zone holds 70% and eases off at the very top and bottom. */
  .hero__scrim {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--ink-deep) 82%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 70%, transparent) 30%,
      color-mix(in srgb, var(--ink-deep) 70%, transparent) 70%,
      color-mix(in srgb, var(--ink-deep) 80%, transparent) 100%);
  }
  .cta__scrim {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--ink-deep) 84%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 74%, transparent) 55%,
      color-mix(in srgb, var(--ink-deep) 56%, transparent) 85%,
      color-mix(in srgb, var(--ink-deep) 36%, transparent) 100%);
  }
  /* The lede sits low in a page hero, and this wash used to be at its thinnest
     exactly there (44% at the bottom). On the about page's lit vineyard that
     put the lede at 3.57:1 against a 4.5:1 floor at 390px. The bottom now holds
     62%, which every photo hero on the site clears. */
  .page-hero__scrim {
    background: linear-gradient(180deg,
      color-mix(in srgb, var(--ink-deep) 82%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 70%, transparent) 60%,
      color-mix(in srgb, var(--ink-deep) 62%, transparent) 100%);
  }

  .chat-fab { width: var(--fab-size); height: var(--fab-size); }
  .chat-fab svg { width: 24px; height: 24px; }
}

/* ==========================================================================
   33. The receptionist on a phone: sheet manners
   ========================================================================== */
.chat-backdrop {
  position: fixed;
  inset: 0;
  z-index: 120;
  background: color-mix(in srgb, var(--ink-deep) 45%, transparent);
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease-soft);
}
.chat-backdrop.is-open { opacity: 1; }
.chat-backdrop[hidden] { display: none; }
html.chat-lock, html.chat-lock body { overflow: hidden; }

@media (max-width: 640px) {
  /* The desktop attention devices come off: no label pill crowding the
     thumb corner, a calmer halo, safe-area clearance for the home bar. */
  .chat-fab__label { display: none; }
  .chat-panel {
    max-height: 88dvh;
    box-shadow: 0 -18px 50px -20px color-mix(in srgb, var(--ink-deep) 60%, transparent);
  }
  /* Sheet handle: the universal pull-bar affordance. */
  .chat-head { position: relative; padding-top: 1.15rem; }
  .chat-head::before {
    content: "";
    position: absolute;
    top: 6px;
    left: 0; right: 0;
    margin-inline: auto;
    width: 44px;
    height: 4px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--paper) 35%, transparent);
  }
  /* A native slide-up sheet, not a desktop pop. Dragging suspends motion. */
  .chat-panel.is-open { animation: chat-up 0.3s var(--ease) both; }
  .chat-panel.is-dragging { animation: none; transition: none; }
  /* 16px floor stops iOS zooming the whole page when the field focuses. */
  .chat-form input { font-size: 1rem; }
  .chat-form { padding-bottom: calc(0.75rem + var(--safe-bottom)); }
  .chat-foot { padding-bottom: calc(0.9rem + var(--safe-bottom)); }
  .chat-msg { max-width: 88%; }
}
@keyframes chat-up {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

/* The answer to a tapped heart: where the farm went, and the way there. */
.save-toast {
  position: fixed;
  left: 0; right: 0;
  margin-inline: auto;
  width: max-content;
  max-width: calc(100vw - 2rem);
  text-align: center;
  bottom: var(--fab-bottom);
  transform: translateY(12px);
  z-index: 105;
  background: var(--ink-deep);
  color: var(--paper);
  font-size: 0.875rem;
  padding: 0.7rem 1.1rem;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  box-shadow: 0 14px 40px -14px color-mix(in srgb, var(--ink-deep) 70%, transparent);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--t-fast) var(--ease-soft),
              transform var(--t-fast) var(--ease),
              visibility var(--t-fast) var(--ease-soft);
}
.save-toast.is-on { opacity: 1; visibility: visible; transform: none; }
.save-toast a { color: var(--gold); text-decoration: underline; text-underline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .save-toast { transition: opacity var(--t-fast) linear; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .chat-backdrop { transition: none; }
  .chat-panel.is-open { animation: none; }
}

/* ==========================================================================
   30. A phone should feel like an app (/better pass, 2026-09-09)
   Every rule here answers a measurement in QA-REPORT.md, not a preference.
   The numbers quoted are the ones read at 390 before the pass.
   ========================================================================== */

/* ---- 30.1 The bottom bar on a listing ---------------------------------- */
/* The page is 10 942px long at 390 and the only way to act on it was a jump
   nav link that started off screen. Call, WhatsApp and Enquire now live on the
   bottom edge, above the home indicator, and stand down over the form. */
.actionbar { display: none; }
@media (max-width: 900px) {
  :root { --actionbar-h: 62px; }
  .actionbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 320;
    display: grid;
    grid-template-columns: 1fr 1fr 1.15fr;
    gap: 1px;
    background: var(--line-dark);
    border-top: 1px solid color-mix(in srgb, var(--paper) 12%, transparent);
    padding-bottom: var(--safe-bottom);
    padding-inline: var(--safe-left) var(--safe-right);
    transition: transform var(--t-mid) var(--ease), opacity var(--t-fast) var(--ease-soft);
  }
  /* Away means gone: opacity alone left three controls focusable at
     top 851 in an 844px viewport, which is WCAG 2.4.11. */
  .actionbar.is-away {
    transform: translateY(110%);
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
    transition: transform var(--t-mid) var(--ease), opacity var(--t-fast) var(--ease-soft),
                visibility 0s linear var(--t-mid);
  }
  /* The menu is the whole screen while it is open, and the bar was painting
     over it at z-index 320 with every one of its controls dead. */
  .nav-open .actionbar { visibility: hidden; pointer-events: none; }
  .actionbar__btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.2rem;
    min-height: var(--actionbar-h);
    background: var(--ink-deep);
    border: 0;
    color: color-mix(in srgb, var(--paper) 88%, transparent);
    font-family: var(--display);
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background var(--t-fast) var(--ease-soft), color var(--t-fast) var(--ease-soft);
  }
  .actionbar__btn svg { width: 20px; height: 20px; fill: currentColor; }
  .actionbar__btn:active { background: color-mix(in srgb, var(--paper) 12%, var(--ink-deep)); }
  .actionbar__btn--gold { background: var(--gold); color: var(--ink); }
  .actionbar__btn--gold:active { background: color-mix(in srgb, var(--ink) 12%, var(--gold)); }
  /* Nothing may hide behind it: the page, the compare tray and the chat
     button all step up by its height. */
  /* The bar is its buttons plus a 1px top rule; the dock is all of it. */
  .has-actionbar:not(.bar-away) { --dock: calc(var(--actionbar-h) + 1px + var(--safe-bottom)); }
  .has-actionbar .nav { padding-bottom: calc(var(--actionbar-h) + var(--safe-bottom) + 1rem); }
}
@media print { .actionbar { display: none !important; } }

/* ---- 30.2 The whole card is the tap target ----------------------------- */
/* elementFromPoint at the centre of a 660px card returned null: only the
   photo, the title and one link were live. The title's link is stretched over
   the card, and the controls that do something else are raised above it. */
.farm-card { position: relative; }
.farm-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}
.farm-card .cmp-toggle,
.farm-card__foot .link-line { position: relative; z-index: 2; }
/* The photo's own link is already absolute and filling the frame: it needs
   the layer, not a new position. Setting position: relative on it collapsed
   the anchor to content height and left 107px of grey under every picture. */
.farm-card__media > a { z-index: 2; }
/* The heart is already absolute in the photograph's corner: giving it
   position: relative here put it back in the flow, which pushed the media box
   44px taller and left the button sitting under the picture. It only needs
   the layer. */
.farm-card .save-btn { z-index: 2; }
/* A card that does not answer a thumb feels like paper. */
.farm-card { transition: transform var(--t-fast) var(--ease-soft); }
.farm-card:active { transform: scale(0.992); }

/* ---- 30.3 Tap feedback everywhere a thumb lands ------------------------ */
/* The stylesheet carried six :active rules and all of them were on .btn or
   the nav. A press is how a phone confirms it heard you. */
.save-btn:active,
.typemenu__btn:active,
.typemenu__opt:active,
.filter-btn:active,
.provlist__btn:active,
.spec-tab:active { transform: scale(0.96); }
.link-line:active,
.cmp-toggle:active,
.jumpnav a:active { opacity: 0.6; }
/* One press state for buttons, not two rules fighting each other. */
.btn:active { transform: translateY(1px); }

/* ---- 30.4 Targets ------------------------------------------------------ */
/* Measured at 390 before this pass: save 38x38, compare 101x26, the schedule
   link 194x36, all three selects 41, the search field 42. */
.save-btn { width: 44px; height: 44px; }
.save-btn svg { width: 19px; height: 19px; }
.cmp-toggle { min-height: 44px; }
.farm-card__foot .link-line { min-height: 44px; }
.toolbar select,
.searchrail__field select,
.findbar input,
.filter-btn { min-height: 44px; }
/* The compare tray's own controls: the remove cross measured 26x26 and Clear
   60x20. The cross keeps its drawn size and gains the hit area around it. */
.cmp-slot__x { width: 30px; height: 30px; }
.cmp-slot__x::after { content: ""; position: absolute; inset: calc(50% - 22px); }
.cmp-clear { min-height: 44px; padding-inline: 0.5rem; }
/* The mark in the bar measured 44x40. */
.brand { min-height: 44px; }
/* The card title is a stretched link: the text box measures 23px on a
   one-line title, the hit area is the whole 733px card. */

/* ---- 30.5 One control language in the toolbar -------------------------- */
/* A custom menu, a bordered button and three native selects sat in one row.
   The selects take the menu's shape, height and chevron; they stay native
   selects, so the phone still opens its own wheel. */
.toolbar select {
  appearance: none;
  -webkit-appearance: none;
  padding-inline: 0.95rem 2.2rem;
  border-radius: var(--r-chip);
  border: 1px solid var(--line);
  background-color: var(--paper);
  background-image: linear-gradient(45deg, transparent 49%, var(--ink) 50%),
                    linear-gradient(-45deg, transparent 49%, var(--ink) 50%);
  background-size: 6px 6px, 6px 6px;
  background-position: calc(100% - 1.35rem) 55%, calc(100% - 0.95rem) 55%;
  background-repeat: no-repeat;
}
.toolbar select:focus-visible { border-color: var(--gold); }

/* ---- 30.6 The card's fact strip ---------------------------------------- */
/* "600 m2 Workshop" measured scrollWidth over clientWidth: the third cell
   clipped, and three cells of different lengths ran the row 68px ragged. */
.farm-card__meta span { padding-right: 0.5rem; overflow-wrap: break-word; }
/* Two facts and one full width whenever the CARD is narrow, not only the
   screen: three 3-up cards at 1180 and 1280 left "Workshop" 82 to 91px and it
   split as WORKSHO / P (fourth review, G4). The card measures itself. */
.farm-card { container: card / inline-size; }
@container card (max-width: 400px) {
  .farm-card__meta { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 0.75rem; }
  .farm-card__meta span:nth-child(3) {
    grid-column: 1 / -1;
    padding-left: 0;
    border-left: 0;
    padding-top: 0.75rem;
    border-top: 1px solid var(--line);
  }
}
@media (max-width: 420px) {
  .farm-card__meta { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 0.75rem; }
  .farm-card__meta span:nth-child(3) {
    grid-column: 1 / -1;
    padding-left: 0;
    border-left: 0;
    padding-top: 0.75rem;
    border-top: 1px solid var(--line);
  }
}

/* ---- 30.7 Something to look at while the fetch lands ------------------- */
.farm-card--skeleton { pointer-events: none; }
.skel {
  display: block;
  background: color-mix(in srgb, var(--ink) 8%, var(--stone));
  border-radius: var(--r-chip);
}
.farm-card--skeleton .farm-card__media { aspect-ratio: 3 / 2; }
.skel--line { height: 1rem; margin-bottom: 0.7rem; }
.skel--price { height: 1.25rem; margin-bottom: 1.1rem; }
.skel--meta {
  height: 62px;
  margin: 1.15rem calc(clamp(1rem, 1.6vw, 1.35rem) * -1) 0;
  border-radius: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .skel { animation: skel-pulse 1.4s var(--ease-soft) infinite; }
}
@keyframes skel-pulse { 50% { opacity: 0.55; } }

/* ---- 30.8 The gallery swipes on a phone -------------------------------- */
/* It was a grid of 106px thumbnails. On a phone it is a snapping rail, which
   is what a hand expects of a set of photographs. */
@media (max-width: 900px) {
  .gallery {
    display: flex;
    gap: 0.6rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scroll-padding-inline: var(--gutter);
  }
  .gallery::-webkit-scrollbar { display: none; }
  .gallery { align-items: start; }
  /* One ratio for every slide. The thumbnail grid this rail replaced left a
     square rule behind that outranked this one, so nine slides of ten were
     square crops of 16:9 photographs beside a 4:3 lead (third review,
     2026-09-10). The galleries are 16:9 and 4:3 frames; 4:3 shows every 4:3
     frame whole and three quarters of every 16:9 one. */
  .gallery figure,
  .gallery > :first-child {
    flex: 0 0 86%;
    scroll-snap-align: start;
    aspect-ratio: 4 / 3;
  }
}

/* ---- 30.9 The jump nav says it scrolls --------------------------------- */
/* The fade only belongs where the row overflows, and only on the side that
   has something behind it. At 768 the row fits and the mask was fading the
   Enquire link for nothing; at the end of the scroll it was fading the last
   chip while the hard clip sat on the left. script.js sets the two classes. */
@media (max-width: 900px) {
  .jumpnav__inner { scroll-snap-type: x proximity; }
  .jumpnav__inner.can-scroll-right {
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 86%, transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 86%, transparent 100%);
  }
  .jumpnav__inner.can-scroll-left {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14%, #000 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 14%, #000 100%);
  }
  .jumpnav__inner.can-scroll-left.can-scroll-right {
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 12%, #000 88%, transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 12%, #000 88%, transparent 100%);
  }
  .jumpnav a { scroll-snap-align: start; }
}

/* ---- 30.10 Section rhythm on a phone: withdrawn ------------------------ */
/* The audit read every band at 36px and called the rhythm flat. It is not a
   drift: `main > section + :is(.band, ...)` sets 0.6 of the band token on both
   sides of every seam, the one-seam ruling of 2026-09-04, measured equal
   everywhere on purpose. What the reading does show is that `band--tight`
   buys nothing under that rule, both come out 36px at 390. Reinstating a
   second tier would undo a decided system for 8px, so the modifier stays
   redundant and this is recorded rather than fixed. */

/* ---- 30.11 Two light bands are one surface ----------------------------- */
/* Measured L*: canvas 96.2 against paper 100, which is one ground to the eye
   and read as three light sections running together. The pair stops
   pretending to be two: one gap between them, and no step that cannot be
   seen. Where paper follows sage the step is 14 L* and stays. */
/* Reviewer, same day: `transparent` was the wrong way to say "one surface".
   From 1024 up the hero is position: sticky at z-index 0, and the bands only
   cover it because they paint an opaque ground. Three sections printed
   themselves over the photograph. The pair shares the canvas, opaquely. */
main > .band:not(.band--sage):not(.band--ink) + .band--surface {
  background: var(--canvas);
  padding-top: 0;
}

/* ---- 30.12 One radius set ---------------------------------------------- */
/* The set measured {0, 4, 6, 999}: the listings search field was a full pill
   and the search rail's fields were square. */
.findbar input { border-radius: var(--r-field); }
.searchrail__field select { border-radius: var(--r-field); }

/* ---- 30.13 Scrolling behaves ------------------------------------------- */
body { overscroll-behavior-y: contain; }
.typemenu__sheet,
.chat-panel,
.lightbox { overscroll-behavior: contain; }

/* ---- 30.14 One role, one treatment ------------------------------------- */
/* dd rendered at 13px Tenor Sans in one block and 15px Instrument Sans in
   another, on the same page. The figure lists keep the display face on
   purpose; every other definition list takes the body face at the body size. */
dl:not(.headline-facts):not(.country__facts):not(.factgrid):not(.spec-group):not(.bond-sum):not(.stat-strip) dd {
  font-family: var(--body);
  font-size: var(--fs-body);
}

/* ---- 30.15 Page changes ------------------------------------------------ */
/* A cross-document view transition makes a tap on a farm read as one app
   moving rather than two documents swapping. Chrome and Safari take it;
   every other engine simply navigates, which is the same thing without the
   fade. Off entirely under reduced motion. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
::view-transition-old(root) { animation-duration: 140ms; }
::view-transition-new(root) { animation-duration: 180ms; }

/* ---- 30.15b A farm name has to fit on the phone ------------------------ */
/* "Baardskeerdersbos" is 11.5 times its own font size wide in the display
   face, so at 38.4px it needs 441px and the h1 broke mid-word inside a 334px
   column: BAARDSKEERDER / SBOS. Shrinking every listing's heading to suit the
   longest name on the books would cost the other three, so script.js measures
   the longest word against the column and only steps this heading down when
   it has to. The rule below is the floor it may not pass. */
.detail-head h1 { min-height: 0; }

/* ---- 30.15c No button says what the bar already says ------------------- */
/* WhatsApp is one thumb away on the bottom bar; the same button in the
   header row was the third WhatsApp on one screen. */
@media (max-width: 900px) {
  .detail-actions .btn[href*="wa.me"] { display: none; }
}

/* ---- 30.15d One line per button ---------------------------------------- */
.save-label--long { display: none; }
@media (min-width: 601px) {
  .save-btn--inline .save-label { display: none; }
  .save-label--long { display: inline; }
}

/* ---- 30.16 The cards arrive in order ----------------------------------- */
/* Four cards used to land as one block. 60ms apart reads as a list filling.
   The delay rides the site's own --reveal-delay rather than a second
   transition-delay: the first draft put the stagger on the card's whole
   transition, so the fourth card's press feedback started 180ms after the
   thumb landed. Once a card is in, its transform snaps. */
.js .farm-card.reveal.is-visible {
  transition: transform 120ms var(--ease-soft);
  transition-delay: 0s;
}

/* ---- 30.17 The chat button is never buried ----------------------------- */
/* The compare tray is z-index 300 and the button was 110, so with two farms
   selected the launcher was behind the tray on every page: elementsFromPoint
   at its own centre returned the tray. */
.chat-fab { z-index: 310; }

/* ---- 30.18 A table that cannot scroll must not be wider than the page --- */
/* compare.html put 57px of horizontal scroll on the document between 643 and
   699, where .cmp-table's 42rem min-width engaged before the stacked layout
   handed over, and with three farms it ran on to 767.

   The first attempt gave .cmp-wrap overflow-x: auto, which was wrong twice:
   overflow-y computes to auto with it, so the wrapper became a scroll
   container again and the sticky column headers resolved against it instead
   of the viewport, which is the exact defect round 2 removed. The stacked
   layout hands over at 800 instead, where three farms and two gutters fit. */
@media (min-width: 641px) and (max-width: 800px) {
  .cmp-table { min-width: 0; }
  .cmp-table thead th:first-child { display: none; }
  .cmp-table tbody th {
    display: block; width: auto; min-width: 0;
    border-bottom: 0; padding-bottom: 0.2rem;
    font-family: var(--display); text-transform: uppercase;
    font-size: 0.75rem; letter-spacing: 0.08em; color: var(--gold-deep);
  }
  .cmp-table tbody tr {
    display: grid;
    grid-template-columns: repeat(var(--cmp-cols, 2), minmax(0, 1fr));
    gap: 0 0.75rem; padding: 0.6rem 0;
    border-bottom: 1px solid var(--line-soft);
  }
  .cmp-table tbody tr th { grid-column: 1 / -1; }
  .cmp-table tbody td { border-bottom: 0; padding: 0.15rem 0; }
  .cmp-table tr.cmp-sec { display: block; }
  .cmp-table tr.cmp-sec th { grid-column: auto; color: var(--ink); }
  .cmp-table thead th { padding: 0.75rem 0.4rem; height: auto; vertical-align: top; }
  .cmp-table thead tr { display: grid; grid-template-columns: repeat(var(--cmp-cols, 2), minmax(0, 1fr)); gap: 0 0.75rem; }
  .cmp-table, .cmp-table tbody, .cmp-table thead { display: block; }
  .cmp-head { height: auto; }
  .cmp-head img { aspect-ratio: 4/3; height: auto; }
  .cmp-head .t { font-size: 0.9375rem; min-height: 0; }
  .cmp-head .p { font-size: 0.875rem; margin-top: 0; }
}

/* ---- 30.19 A tile label may not be cut mid-word ------------------------ */
/* "Zoning and services" was clipped 34px at 390 and again from 1104 to 1232:
   a nowrap count inside a hidden-overflow tile. */
.type-tile__label { flex-wrap: wrap; }
.type-tile__count { white-space: normal; }

/* ---- 30.20 The first tile's heading sits on sky ------------------------ */
/* Measured on the real pixels at 3x: 21% of the glyph pixels under 4.5:1,
   worst 2.82. The other five tiles pass. The scrim reaches higher and darker
   on the tall tile, which is the only one whose label meets pale sky. */
.type-tile::after { background: linear-gradient(to top,
  color-mix(in srgb, var(--ink-deep) 92%, transparent) 0%,
  color-mix(in srgb, var(--ink-deep) 72%, transparent) 34%,
  color-mix(in srgb, var(--ink-deep) 32%, transparent) 64%,
  transparent 100%); }
/* At 200% text the label climbs most of the tile, and a three-line
   "Vineyards and orchards" ends up on pale sky that no bottom-anchored scrim
   reaches (54.7% of its glyph pixels under 3:1, measured 2026-09-10). The
   tile measures itself in its own text size: when it is narrower than 13em,
   which only happens with enlarged text (the narrowest tile at the normal
   size, on a 320 screen, is 16.5em; 15em fired there, fourth review), the
   photograph steps back. */
.type-tile { container-type: inline-size; }
@container (max-width: 13em) {
  .type-tile img { filter: brightness(0.45); }
}

/* ---- 30.20b The chat label stops sitting on the copy -------------------- */
/* Measured at 1440 on the home page: the label is 185x39 at (1141, 739) and
   covers a block of the lede under it. The button says what it is; the label
   comes back on hover and focus, where it is asked for. */
.chat-fab__label { opacity: 0; visibility: hidden; }
.chat-fab:hover .chat-fab__label,
.chat-fab:focus-visible .chat-fab__label { opacity: 1; visibility: visible; }

/* ---- 30.20c Landscape is mostly chrome --------------------------------- */
/* Measured at 844x390 on a listing: header 72 + jump nav 49 + bar 63 = 184 of
   390, 47% of the screen. The jump nav is the one of the three that repeats
   what the page already shows, so it stands down and the bar gets shorter. */
@media (max-height: 500px) and (orientation: landscape) {
  .jumpnav { display: none; }
  :root { --actionbar-h: 52px; }
  .actionbar__btn { font-size: 0.7rem; }
  .actionbar__btn svg { width: 17px; height: 17px; }
  /* With a shortlist up the chrome came to 51% of a 844x390 screen. The tray
     drops to one line and loses its thumbnails there. */
  .cmp-tray { padding-block: 0.4rem; }
  .cmp-tray__slots { display: none; }
}
/* The tray was covering 19px of the home hero's second button, and the room
   this rule made for it arrived with the tray, after load, moving the hero's
   content (CLS 0.117 at 390). Since 2026-09-11 the tray stands down while the
   home page's first screen is on screen (33.35), so there is nothing to clear. */

/* ---- 30.20d The selects match the menu they sit beside ----------------- */
/* Claimed height and chevron and delivered neither: the menu button is 48px
   with a stroked mark, the selects were 44px with a solid triangle. */
.toolbar select {
  min-height: 48px;
  background-image: none;
  padding-inline: 0.95rem 2.4rem;
}
.toolbar span { position: relative; }
.toolbar span::after {
  content: "";
  position: absolute;
  right: 1.05rem; top: 50%;
  width: 8px; height: 8px;
  border-right: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}
.toolbar label { position: static; }

/* ---- 30.20e Nothing is wider than the screen at 200% text -------------- */
/* break-word lets a long word wrap but still reports the whole word as its
   min-content width, so the card's grid track measured 453px inside a 390px
   screen once the root hit 32px. `anywhere` is the one that shrinks the
   track. Nothing changes at the default root, where the words already fit. */
.farm-card__title,
.farm-card__place,
.nav-extra__v { overflow-wrap: anywhere; }
/* And a flex item keeps min-width: auto, so the address would not shrink to
   its broken width and the closed sheet stayed 435px wide at a 32px root. */
.nav-extra__line > span { min-width: 0; }

/* ---- 30.20f The All sections button, now beside the tablist ------------ */
/* It opens every panel at once, so it was never a tab. Same shape, its own
   control, pressed rather than selected. */
.spec-tab--all { width: 100%; }
.spec-tab--all[aria-pressed="true"] { color: var(--ink); border-color: var(--gold); }

/* ---- 30.20g A slider thumb a thumb can hold --------------------------- */
/* 18px on a 26px track: it worked but it was fiddly to drag on a phone. */
.bond-f input[type="range"]::-webkit-slider-thumb { width: 24px; height: 24px; }
.bond-f input[type="range"]::-moz-range-thumb { width: 24px; height: 24px; }

/* ---- 30.20h The band nobody sees stops setting LCP --------------------- */
/* land-dusk.jpg sits at y=9157 on a listing, out of view, and still
   registered as the largest contentful paint at 1924ms because Chrome
   fetches it and the entry is never retracted. Skipping the band's rendering
   until it is near the viewport keeps the paint honest and off the number. */
.cta { content-visibility: auto; contain-intrinsic-size: auto 720px; }
@media print { .cta { content-visibility: visible; } }

/* ---- 30.20i The mark stops animating its own height -------------------- */
/* height on the logo, transitioned on every scroll-state change, forces the
   header row to lay out again mid-scroll. Same movement, on the compositor. */
.brand img { transition: none; transform-origin: left center; }
.site-header .brand img { transition: transform var(--t-mid) var(--ease-soft); }
.site-header.is-solid .brand img { transform: scale(0.87); }

/* ---- 30.20j The shape of a listing while it loads ---------------------- */
.detail-skel { display: block; max-width: 46rem; }
.skel--title { height: clamp(2.2rem, 8vw, 3.4rem); margin-bottom: 1.1rem; }
.skel--hero { height: clamp(12rem, 46vw, 22rem); margin-top: 1.75rem; border-radius: var(--r-media); }

/* ---- 30.20k No outlined transparent control on a light ground ---------- */
/* taste/TASTE-PROFILE.md line 679. Seven type links and one "everything
   listed" link sat on the sage band as 1px-outlined transparent boxes, which
   is also ban-list item 16. They take the paper ground the cards use. */
.band--sage .type-more a,
.type-more a,
.type-more--dark a {
  background: var(--paper);
  border-color: var(--line);
}
/* Over the 404's photograph too: an outlined transparent chip is a banned
   control on any band (TASTE-PROFILE line 679, bans 10 and 16). The first
   attempt gave these a paper ground under their own paper text and five of
   six went blank; the text is ink now (fourth review, G5). */
.type-more--dark a { border-color: transparent; color: var(--ink); }
.band--sage .type-more a:hover,
.type-more a:hover,
.type-more--dark a:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }

/* ---- 30.21 The header at 200% text ------------------------------------- */
/* With the root at 32px on a 390 screen the header row ran to 459px and the
   Menu button was clipped 13px off the edge with no way to scroll to it. */
@media (max-width: 900px) {
  .header-inner { gap: 0.5rem; }
  .nav-toggle { flex: none; }
  /* max-width only. The first draft added height: auto with it, which threw
     away the 46px the mark is drawn at and let it render 160px wide. */
  .brand img { max-width: 42vw; }
}

/* ==========================================================================
   31. The third review's fixes (2026-09-10)
   Each rule answers an item in QA-REPORT.md, R3-B1 and R3-G1 to R3-G9.
   ========================================================================== */

/* ---- 31.1 Installed on an iPhone --------------------------------------- */
/* Every page declares viewport-fit=cover, so the status bar, the notch and
   the home indicator report real insets. The header carries --safe-top
   (section 1), the gutter never drops under a side inset, and the bottom
   stack below carries --safe-bottom. The chat sheet takes its own share. */
@media (max-width: 640px), (max-height: 500px) and (orientation: landscape) {
  .chat-head { padding-top: calc(1.15rem + var(--safe-top)); }
  .chat-head::before { top: calc(6px + var(--safe-top)); }
  /* Full screen on a landscape phone reaches under the notch: every band
     keeps its ground to the edge and its content inside the insets. */
  .chat-head { padding-left: max(1.1rem, var(--safe-left)); padding-right: max(1.1rem, var(--safe-right)); }
  .chat-log, .chat-chips, .chat-form, .chat-foot { padding-left: max(1rem, var(--safe-left)); padding-right: max(1rem, var(--safe-right)); }
  .chat-foot { padding-bottom: calc(0.9rem + var(--safe-bottom)); }
}
/* Suggestion rows to the 44px floor. */
.chat-chips button { min-height: 44px; }

/* ---- 31.2 A word that cannot fit breaks; it never widens the page ------ */
/* R3-G2. At 200% text on a phone the layout viewport grew to 459px on the
   listings page (435 home, 496 compare, 434 about), and the fixed header grew
   with it, putting the Menu icon off screen. Hiding elements one at a time
   found the cause in the page, not the header: button labels set to
   white-space: nowrap ("Register your requirement" ran from -69 to 459), long
   email addresses and farm names in table cells. html's overflow-x: hidden
   hid the sideways scroll, not the growth. */
.btn { white-space: normal; text-wrap: balance; overflow-wrap: anywhere; }
/* Running text breaks only a word that cannot fit its own line (an email
   address, a farm name). break-word, not anywhere: anywhere also lowers an
   element's minimum width, and on the schedule's term and value grid at 1280
   that squeezed "Total hectares" into one-letter lines. */
p, li, dd, figcaption, address { overflow-wrap: break-word; }
/* The stacked compare table is the one grid whose cells must be allowed to
   narrow below their longest word: three farms at 390 give each 103px, and
   "Baardskeerdersbos" is 128. Scoped to the stacked layout, so the desktop
   table keeps the column widths it had. */
@media (max-width: 800px) {
  .cmp-table th, .cmp-table td, .cmp-head .t, .cmp-head .l { overflow-wrap: anywhere; -webkit-hyphens: auto; hyphens: auto; }
}
/* The bond figures stay whole; their label drops under them instead. */
.bond-sum > div { flex-wrap: wrap; }
.bond-sum dd { flex: 1 1 6rem; text-align: right; }
/* A single long word in a tile heading wraps with a hyphen, not a clip. */
.type-tile__label h3 { -webkit-hyphens: auto; hyphens: auto; }

/* ---- 31.3 The bottom edge is one stack --------------------------------- */
/* R3-G3. The tray, the action bar and the chat launcher each carried their
   own hand-tuned offset: 84px for a tray that wraps to 133px below 640 (the
   launcher sat 45px into it), 0.75rem of gap that showed an 11px slice of
   page between the tray and the bar, and no padding anywhere for the tray,
   so the footer's last lines ended under it. One set of numbers now: --dock
   is the bar (or nothing), --tray-h is measured by script.js while the tray
   is up, and everything else is arithmetic on those two. */
:root {
  --chrome-b: calc(var(--dock) + var(--tray-stack));
  --fab-bottom: calc(max(var(--dock), var(--safe-bottom)) + var(--fab-gap));
}
.tray-up {
  --tray-stack: calc(var(--tray-h) + var(--tray-gap));
  --fab-bottom: calc(var(--dock) + var(--tray-stack) + var(--fab-gap));
}
.chat-fab { bottom: var(--fab-bottom); }
.chat-teaser { bottom: calc(var(--fab-bottom) + 74px); }
@media (max-width: 900px) {
  /* On a phone and a tablet the tray is not a floating card: it docks,
     full width, directly on the bar or on the bottom edge, like a player on
     a tab bar. Nothing of the page can show between them. */
  :root { --tray-gap: 0px; }
  .cmp-tray {
    width: auto;
    border-radius: 0;
    border-inline: 0;
    border-bottom: 0;
    bottom: var(--dock);
    padding-inline: max(0.85rem, var(--safe-left)) max(0.85rem, var(--safe-right));
    padding-bottom: calc(0.7rem + max(0px, var(--safe-bottom) - var(--dock)));
    box-shadow: 0 -16px 36px -24px color-mix(in srgb, var(--ink-deep) 75%, transparent);
  }
}
@media (max-width: 640px) {
  :root { --fab-gap: 1rem; --fab-size: 52px; }
  /* The launcher and the toast share the thumb corner; the toast goes above. */
  .save-toast { bottom: calc(var(--fab-bottom) + var(--fab-size) + 0.75rem); }
}

/* ---- 31.4 Landscape: one strip at the top, one at the bottom ----------- */
/* R3-G4. At 844x390 with a shortlist the chrome was 195 of 390px: a 72px
   header, the tray and the bar stacked. The header drops to 56px on a
   landscape phone, the tray matches the bar's height, and on a listing the
   tray sits beside the bar instead of on top of it. */
@media (max-height: 500px) and (orientation: landscape) {
  :root {
    --header-h: calc(56px + var(--safe-top));
    --header-h-solid: calc(56px + var(--safe-top));
  }
  .brand img { height: 40px; }
  .cmp-tray {
    min-height: calc(var(--actionbar-h, 52px) + 1px);
    padding-block: 0.2rem;
    align-content: center;
  }
  /* The thumbnails are already off here; the label beside a button that
     says Compare was the same word twice. */
  .cmp-tray__label { display: none; }
  .cmp-tray__actions { flex: 1 1 auto; justify-content: space-between; }
}
@media (max-width: 900px) and (max-height: 500px) and (orientation: landscape) {
  .has-actionbar.tray-up:not(.bar-away) { --tray-stack: 0px; }
  .has-actionbar.tray-up:not(.bar-away) .actionbar { left: 50%; padding-left: 0; }
  .has-actionbar.tray-up:not(.bar-away) .cmp-tray {
    right: 50%;
    bottom: 0;
    min-height: calc(var(--actionbar-h) + 1px + var(--safe-bottom));
    padding-block: 0 var(--safe-bottom);
    padding-right: 0.85rem;
    border-right: 1px solid var(--line-dark);
  }
}

/* ---- 31.5 The chat takes the screen on a phone ------------------------- */
/* R3-G6, and the phone ruling of 2026-09-05: "almost becoming the page". It
   opened as a 61% sheet with the site showing above it. It is the full
   viewport now, rising from the bottom edge, the page locked behind it; a
   landscape phone gets the same, since the floating panel ran off its top.
   Above that the floating panel follows the launcher up the stack. */
/* At 1280x680 with a shortlist the message area was 26px and the greeting
   could not be read (fourth review, G7). The panel may overlap the header,
   which is under the backdrop, and on a short screen the four suggestions
   sit two across. The launcher stays where it is: moving it down when the
   chat opened put a second tap, meant to close it, inside the panel
   (fifth review). */
.chat-panel {
  bottom: calc(var(--fab-bottom) + var(--fab-size) + 0.75rem);
  max-height: min(600px, calc(100dvh - var(--fab-bottom) - var(--fab-size) - 1.75rem));
}
@media (min-width: 641px) and (max-height: 800px) {
  .chat-chips { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* On a short screen the panel sits beside the launcher, not above it, so
     it has the full height and the launcher stays where it was tapped. */
  .chat-panel {
    bottom: var(--fab-bottom);
    right: calc(max(var(--fab-gap), var(--safe-right)) + var(--fab-size) + 0.75rem);
    max-height: min(600px, calc(100dvh - var(--fab-bottom) - 1rem));
    transform-origin: 100% 100%;
  }
}
.chat-fab, .chat-teaser { right: max(var(--fab-gap), var(--safe-right)); }
@media (max-width: 640px), (max-height: 500px) and (orientation: landscape) {
  .chat-panel {
    inset: 0;
    width: 100%;
    height: 100vh;
    height: 100dvh;
    max-height: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
}

/* The launcher stands down while the sheet is up: z-index 310 keeps it
   above the compare tray, which also put it on top of the full-screen
   chat's send button. The sheet has its own close control, and html loses
   chat-lock before focus is handed back to the launcher, so it is visible
   again by the time it takes focus. */
html.chat-lock .chat-fab { visibility: hidden; }
/* The tray (300) and the bar (320) sat above the chat (130): over the
   full-screen sheet's input on a landscape phone, and bright above the
   dimmed page on a tablet. While the chat is open they go under its
   backdrop (120) with the rest of the page, which they already are, since
   the chat makes them inert. */
.chat-on .actionbar,
.chat-on .cmp-tray { z-index: 110; }
/* Four suggestion rows left a landscape sheet no room for the conversation
   they suggest; two across gives it back. */
@media (max-height: 500px) and (orientation: landscape) {
  .chat-chips { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The farm-type sheet (z 60) opened under the tray (300) and the launcher
   (310) on a phone, so an option's tap hit Compare, Clear or the chat (fourth
   review, B1). While a sheet is up they stand down, as for the phone menu. */
html.sheet-open .cmp-tray,
html.sheet-open .chat-fab,
html.sheet-open .chat-teaser,
html.sheet-open .actionbar { visibility: hidden; pointer-events: none; }

/* ---- 31.6 The tray's controls are 44px --------------------------------- */
/* The cross carried a 40px hit area that its slot then clipped, and the
   Compare button was 42px. The slot stops clipping (the photograph clips its
   own corners) and the cross's hit area is a centred 44px square. */
.cmp-slot { overflow: visible; }
.cmp-slot img { border-radius: inherit; }
.cmp-tray .btn { min-height: 44px; }

/* ---- 31.7 One theme, declared ------------------------------------------ */
/* Without a declaration, a browser that forces a dark theme can invert the
   grounds under the contrast this site was measured at. */
:root { color-scheme: only light; }

/* ---- 31.8 The anchored tooltip keeps its translate --------------------- */
/* .maptip is placed on a point on the map drawing, not centred in a box, so
   translate(-50%, -130%) is the correct tool there and stays. The tray and
   the save toast, which are centred in the viewport, no longer use it. */


/* ==========================================================================
   32. What a buyer is told on a listing (the /deeper pass, 2026-09-11)
   ========================================================================== */

/* ---- 32.1 Every photograph says what it shows -------------------------- */
/* The captions were written for every photograph and only ever used as alt
   text, so a sighted buyer saw a shed and was not told it is the 600 m²
   workshop. The frame (ratio, crop, radius) moves from the figure onto a
   wrapper so the caption can sit under the picture instead of being cut off
   by the figure's own overflow. Same ratios as before at every width. */
.gallery figure,
.gallery > :first-child {
  aspect-ratio: auto;
  overflow: visible;
  background: none;
  border-radius: 0;
}
.gallery__frame {
  display: block;
  overflow: hidden;
  border-radius: var(--r-media);
  background: var(--stone-2);
  aspect-ratio: 4 / 3;
}
.gallery > :first-child .gallery__frame { aspect-ratio: 21 / 9; }
.gallery figcaption {
  margin-top: 0.6rem;
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--muted);
  max-width: 62ch;
}
@media (max-width: 1100px) {
  .gallery > :first-child .gallery__frame { aspect-ratio: 16 / 9; }
}
@media (max-width: 900px) {
  .gallery__frame,
  .gallery > :first-child .gallery__frame { aspect-ratio: 4 / 3; }
}
@media print {
  .gallery > :first-child .gallery__frame { aspect-ratio: 21 / 9; }
  .gallery figcaption { color: #000; }
}

/* The lightbox gives the caption the room under the photograph that the
   counter already had, so a long caption never sits on the picture. */
.lightbox {
  padding-bottom: calc(max(clamp(1rem, 3vw, 2rem), var(--safe-bottom)) + 5.25rem);
}
.lb-foot {
  position: absolute;
  left: 0; right: 0;
  bottom: max(clamp(1rem, 3vw, 2rem), var(--safe-bottom));
  display: grid;
  justify-items: center;
  gap: 0.5rem;
  padding-inline: max(var(--gutter), 1rem);
  text-align: center;
}
.lb-cap {
  margin: 0;
  max-width: 60ch;
  font-size: var(--fs-body);
  line-height: 1.5;
  color: color-mix(in srgb, var(--paper) 88%, transparent);
}
.lb-foot .lb-count { position: static; margin: 0; width: auto; }
.lb-cap, .gallery figcaption { text-wrap: pretty; }
.lb-cap { text-wrap: balance; }

/* ---- 32.2 Before you make an offer ------------------------------------- */
/* The transfer duty on the asking price and one note a buyer needs, kept out
   of the seller's schedule because it is general law, not the mandate. The
   same 2px ink rule that heads a schedule section, so it reads as part of the
   page rather than a box dropped onto it. */
.offer-notes {
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
  padding-top: 1.25rem;
  border-top: 2px solid var(--ink);
  max-width: 64ch;
}
.offer-notes dl { margin: 0.5rem 0 0; }
.offer-notes dl > div {
  display: grid;
  grid-template-columns: minmax(8.5rem, 0.32fr) minmax(0, 1fr);
  gap: 0.5rem 1.5rem;
  padding-block: 1.15rem;
  border-bottom: 1px solid var(--line);
}
.offer-notes dt {
  padding-top: 0.2rem;
  font-family: var(--display);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.45;
  color: var(--ink);
}
.offer-notes dd { margin: 0; color: var(--muted); line-height: 1.7; }
.offer-notes dd p + p { margin-top: 0.4em; }
.offer-notes .offer-notes__fig {
  font-family: var(--display);
  font-size: clamp(1.4rem, 2.2vw, 1.85rem);
  letter-spacing: -0.035em;
  line-height: 1.15;
  color: var(--ink);
}
.offer-notes .form-note { margin-top: 1rem; }
@media (max-width: 640px) {
  .offer-notes dl > div { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   33. /better pass, 2026-09-11
   Each block carries the number of the fault it answers in docs/QA-REPORT.md
   ("/better pass 2026-09-11"). Written as overrides at the end of the file
   because the rules they correct are spread over sections 3 to 32.
   ========================================================================== */

/* ---- 33.1 The tablet search rail (fault 2) ----------------------------- */
/* Fault 1, the rail on the fold, was built and taken out again in the review
   round: it made the hero shorter than the viewport, against Edgar's
   full-viewport hero ruling of 2026-09-03. It waits for his word.
   Three fields in a two-column grid left an empty ink cell beside Extent. */
@media (min-width: 641px) and (max-width: 900px) {
  .searchrail__inner { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .searchrail__field:nth-child(n) { border-bottom: 0; border-right: 1px solid var(--line-dark); }
  .searchrail__field:nth-child(3) { border-right: 0; }
  .searchrail__field select { font-size: 0.9375rem; }
}

/* ---- 33.2 A card's two chips never share a row they cannot fit (3) ------ */
/* Marietjie's ref and type overlapped by 27px at 390 and 49px at 700; at 320
   all four did. Both chips now sit in one wrapping row across the top of the
   photograph: side by side at either end when they fit, the type on a second
   line under the ref when they do not. */
.farm-card__chips {
  position: absolute; top: 14px; left: 14px; right: 14px; z-index: 2;
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start;
  gap: 6px;
  pointer-events: none;
}
.farm-card__chips > .farm-card__ref,
.farm-card__chips > .farm-card__type { position: static; }

/* ---- 33.3 Same ground, one gap, but never a heading on the line (4) ---- */
/* Section 30.11 collapsed the second band's top padding to zero, which put
   the heading 1px under the hairline. The 2026-09-04 ruling asks for about a
   third of a band, which is what this is. */
main > .band:not(.band--sage):not(.band--ink) + .band--surface { padding-top: calc(var(--band) * 0.4); }

/* ---- 33.4 Text first, and the row starts on one line (5, 6) ------------ */
.split--text-first { align-items: start; }
@media (max-width: 900px) {
  .split--text-first > .split__media { order: 1; }
}

/* ---- 33.5 One label layout for every type tile (9) --------------------- */
/* Some tiles set title and line side by side and others stacked them,
   depending only on how wide the words ran. Every tile stacks now. */
.type-tile__label { flex-direction: column; align-items: flex-start; justify-content: flex-end; gap: 0.35rem; }
.type-tile__count { padding-bottom: 0; }

/* ---- 33.6 The route to everything is a link, not a stranded chip (10) -- */
.type-more__route { margin-top: clamp(1.25rem, 2vw, 1.75rem); }

/* ---- 33.7 An arrow follows its words (11) ------------------------------ */
/* A wrapped line inside an inline-flex link is a flex item as wide as the
   link, so the arrow was pushed 109px clear of the text on a phone. Inline
   text wraps and keeps the arrow on its last word. */
.country__cta .link-line { display: inline; line-height: 1.9; }
.country__cta .link-line::after { display: none; }
.country__cta .link-line .arw { display: inline-block; margin-left: 0.45rem; }
.country__cta .link-line:hover { text-decoration: underline; text-decoration-color: var(--gold); text-underline-offset: 6px; }

/* ---- 33.8 A photograph is a whole object (13) -------------------------- */
.faq-photo img { -webkit-mask-image: none; mask-image: none; }

/* ---- 33.9 A stack of buttons on a phone is one width (15) -------------- */
@media (max-width: 560px) {
  .hero__actions, .cta__actions, .btn-row { flex-direction: column; align-items: stretch; }
  .hero__actions .btn, .cta__actions .btn, .btn-row .btn { width: 100%; }
}

/* ---- 33.10 Four listings are two rows of two (16) ---------------------- */
@media (min-width: 641px) {
  .farm-grid--even:has(> .farm-card:nth-child(4):last-child) > * { grid-column: span 3 !important; }
}

/* ---- 33.11 Province rows stay inside their column (17) ----------------- */
/* The rows ran 10px past the content edge on both sides so the hover wash
   could bleed. The lines now meet the edge and the names sit in from it,
   the way the schedule's section list already does. */
.provlist { margin-inline: 0; }

/* ---- 33.12 The toolbar at tablet width is one row (18) ----------------- */
@media (min-width: 641px) and (max-width: 900px) {
  .toolbar__controls { width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.75rem; }
  .toolbar__controls > span { flex-direction: column; align-items: stretch; gap: 0.3rem; }
  .toolbar__controls label { margin-right: 0; }
  .toolbar__controls select { width: 100%; }
  .toolbar__clear { grid-column: 1 / -1; text-align: left; }
}

/* ---- 33.13 An inset in the right-hand column stays inside the page (19) - */
.split > :last-child .media-pair__inset { right: 0; }

/* ---- 33.14 Full-width rows finish on the content edge (20) ------------- */
/* The body kept its 46ch measure inside a column that ran to the edge, so
   every row stopped about 230px short of it. The body column is the measure
   now, set against the edge; the title takes what is left. */
@media (min-width: 901px) {
  .shell > .rows > .row-item { grid-template-columns: minmax(0, 1fr) minmax(0, 36rem); }
  .shell > .rows > .row-item .row-item__body p { max-width: none; }
}

/* ---- 33.15 Counters two by two on a phone (23) ------------------------- */
@media (max-width: 640px) {
  .counters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .counter { border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); padding-inline: 0 1rem; }
  .counter:nth-child(2n) { border-right: 0; padding-inline: 1rem 0; }
  .counter:nth-last-child(-n+2) { border-bottom: 0; }
}

/* ---- 33.16 Contact: the form and the letterhead side by side (24, 26) -- */
.enquiry-split { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(2.25rem, 3.6vw, 3.25rem); }
.enquiry-split .form-wide { max-width: none; }
.enquiry-split .cband--under-form { margin-top: 0; }
@media (min-width: 901px) {
  .enquiry-split { grid-template-columns: minmax(0, 1fr) minmax(0, 21rem); gap: clamp(2.5rem, 4.5vw, 4.5rem); align-items: start; }
  .cband--stack { grid-template-columns: minmax(0, 1fr); }
  .cband--stack > div,
  .cband--stack > div + div { border-right: 0; border-bottom: 1px solid var(--line); padding: 1.25rem 0; }
  /* The letterhead ends where the form ends: its closing note takes the slack. */
  .enquiry-split > aside { align-self: stretch; display: flex; flex-direction: column; }
  .enquiry-split > aside .cband__foot { margin-top: auto; padding-top: 1.5rem; }
}

/* ---- 33.17 The contact form's selects look like every other select (25) - */
.field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2.6rem;
  cursor: pointer;
}
.field:has(> select) { position: relative; }
.field:has(> select)::after {
  content: "";
  position: absolute;
  right: 1.15rem; bottom: 1.35rem;
  width: 8px; height: 8px;
  border-right: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  transform: rotate(45deg);
  pointer-events: none;
}
.enquiry-card .field:has(> select)::after { border-color: var(--paper); }

/* ---- 33.18 The schedule spine: section list and its All button share
   one column (27) --------------------------------------------------------- */
.spec-nav { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
/* With All sections pressed every panel used to take half the open column,
   339px, and the label column of each collapsed to nothing, so values printed
   over their labels (13 of 37 rows on Franskraal at 1280). The panels stack
   in the open column instead, and the section list pins beside them, so the
   left column is the way through the schedule rather than an empty strip. */
.schedule-layout .spec-groups:not(.is-single) { grid-template-columns: minmax(0, 1fr); }
.schedule-layout .spec-group dl { grid-template-columns: minmax(7rem, 0.9fr) minmax(0, 1.1fr); }
@media (min-width: 901px) {
  .schedule-layout .spec-nav { position: sticky; top: calc(var(--header-h-solid) + 3rem + 2.5rem); }
}
.spec-nav .spec-tab--all { border-top: 1px solid var(--line); }
.band--ink .spec-nav .spec-tab--all { border-top-color: var(--line-dark); }

/* ---- 33.19 A listing's title takes the row; actions and price share the
   last line (28) ----------------------------------------------------------- */
@media (min-width: 901px) {
  .detail-head { grid-template-columns: minmax(0, 1fr) auto; row-gap: 0; }
  .detail-head > :not(.detail-actions):not(.detail-price-block) { grid-column: 1 / -1; }
}
.detail-place { margin-top: 1.25rem; }
/* The head's children are grid items now, so the column gap must not become a
   row gap between every line of it. The price keeps its own space above. */
.detail-head { row-gap: 0; }
@media (max-width: 900px) {
  .detail-head .detail-price-block { margin-top: clamp(1.5rem, 4vw, 3.5rem); }
}

/* ---- 33.20 Save on a listing is a button like the ones beside it (29) -- */
/* Section 30.4's `.save-btn { width: 44px; height: 44px }` is the card's
   round heart and it also caught the inline control, clamping it to 44px
   beside 53px buttons and clipping its label. */
.save-btn.save-btn--inline {
  width: auto; height: auto;
  box-shadow: none;
  background: var(--paper);
  border: 1px solid color-mix(in srgb, var(--ink) 32%, transparent);
}
@media (min-width: 901px) {
  .save-btn.save-btn--inline { font-size: 0.875rem; padding: 0.95rem 1.4rem; }
}
.save-btn.save-btn--inline:hover { border-color: var(--ink); }
.save-btn.save-btn--inline[aria-pressed="true"],
.save-btn.save-btn--inline[data-saved="true"] { background: var(--gold); border-color: var(--gold); color: var(--ink); }
.save-btn--inline[data-saved="true"] svg { fill: currentColor; }
/* 30.15d meant the full sentence above 600px, but its short-label rule also
   matched the long label (both carry .save-label) and hid it at every width. */
@media (min-width: 601px) {
  .save-btn--inline .save-label--long { display: inline; }
}

/* ---- 33.21 No action left alone on a row (30) -------------------------- */
@media (min-width: 561px) and (max-width: 900px) {
  .detail-actions { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .detail-actions > :first-child { grid-column: 1 / -1; }
}

/* ---- 33.22 Pinned strips are solid and meet the header (31) ------------ */
/* Pinned at the full header height, the strips parked 11px under the solid
   header and the page showed through the gap; the jump nav was also glass. */
.jumpnav {
  top: calc(var(--header-h-solid) - 1px);
  background: var(--canvas);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
@media (min-width: 641px) {
  .cmp-table thead th { top: calc(var(--header-h-solid) - 1px); }
}

/* ---- 33.23 Bond sliders end on one line (32, 33) ----------------------- */
.bond-f { grid-template-columns: 8.5rem minmax(0, 1fr) 5.5rem; }
@media (max-width: 900px) {
  .bond-f { grid-template-columns: minmax(0, 1fr) auto; }
}
.bond-sum dd { text-align: right; text-wrap: balance; }

/* ---- 33.24 The closing photograph only dwells where a band follows (34) - */
/* The 150vh hold exists so the next band can slide up over the photograph.
   Last in the page there is nothing to slide, and the listing's band stood
   1350px tall for 157px of copy. */
@media (min-width: 1024px) and (min-height: 660px) {
  main > .cta:not(.cta--flat):last-child {
    position: relative;
    min-height: 0;
    display: block;
    padding-block: clamp(6rem, 12vw, 11rem);
  }
}
.cta p a { color: var(--paper); text-decoration: underline; text-decoration-color: var(--gold-on-photo); text-underline-offset: 4px; }
.cta p a:hover { color: var(--gold-on-photo); }

/* ---- 33.25 The comparison's columns are equal (37, 39) ----------------- */
@media (min-width: 641px) {
  .cmp-table { table-layout: fixed; }
  .cmp-table thead th:first-child { width: 22%; }
}
@media (max-width: 640px) {
  .cmp-table tbody td { padding: 0.35rem 0.5rem; }
}

/* ---- 33.26 The footer (40, 41) ----------------------------------------- */
.footer-base p { max-width: none; }
.footer-base--solo { margin-top: 0; border-top: 0; padding-top: 0; }
@media (max-width: 640px) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 1.25rem; }
  .footer-grid > :first-child,
  .footer-grid > :last-child { grid-column: 1 / -1; }
}

/* ---- 33.27 One body size, one label size (42, 43) ---------------------- */
.row-item__body p,
.enquiry-card p,
.cband__foot,
.form-status,
.country__facts dd,
.schedule-layout .spec-group dt,
.schedule-layout .spec-group dd { font-size: var(--fs-body); }
.bond-lab,
.bond-f > span,
.headline-facts dd,
.field label,
.searchrail__field label,
.factgrid dd,
.cmp-head .r { font-size: var(--fs-label); }

/* ---- 33.28 What used to be inline (14) --------------------------------- */
.shell--bleed { padding-inline: 0; max-width: 100%; }
.hero__title em { font-style: normal; }
.after-head { margin-top: 1.5rem; }
.prose-quiet { color: var(--muted); line-height: 1.72; max-width: 64ch; }
.btn-row { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 2rem; }
.shell--after { margin-top: clamp(1.75rem, 3vw, 2.5rem); }
.shell--gap { margin-top: clamp(2.5rem, 4vw, 3.5rem); }
.band--crumbs { padding-top: calc(var(--header-h) + clamp(2.5rem, 5vw, 4.5rem)); }
.crumb--on-light,
.crumb--on-light span { color: var(--muted); }
.crumb--on-light a { color: var(--gold-deep); }
.page-hero--flat .section-head { margin-bottom: 0; }
#results-line, #compare-line { margin: 0; }
.rows + .form-note { margin-top: 2rem; }
.enquiry-card .form-grid { margin-top: 1.5rem; }
.cta--flat h2 + p { margin-top: 1.25rem; max-width: 46ch; }
.page-hero .type-more--dark { margin-top: 2.5rem; }

/* ---- 33.4b ...and it finishes on one line (5) -------------------------- */
/* With the tops aligned, the photograph pair still ended 45px above the stat
   strip beside it. Above 900 the pair takes the text column's height, so the
   inset's bottom edge and the last line of text share one line. */
@media (min-width: 901px) {
  .split--text-first > .split__media,
  .split--media-last > .split__media { align-self: stretch; }
  .split--text-first .media-pair,
  .split--media-last .media-pair { height: 100%; }
  .split--text-first .media-pair__main,
  .split--media-last .media-pair__main { aspect-ratio: auto; height: 100%; min-height: 16rem; position: relative; }
  /* Out of flow, so the photograph's own height never sets the row: the text
     does, and the photograph fills what the text leaves it. */
  .split--text-first .media-pair__main img,
  .split--media-last .media-pair__main img { position: absolute; inset: 0; }
  .stat-strip { margin-bottom: 0; }
}

/* An empty live region keeps its place in the tree but not its margin, so
   the form ends on its last visible line. */
.form-status:empty { margin-top: 0; }

/* ---- 33.29 No figure label ends on one word (44) ----------------------- */
/* "Homes on the / farm" and "Registered, and the same practitioner from
   valuation / to transfer": the uppercase labels under figures broke wherever
   the width ran out. Balanced, they break into even lines. */
.farm-card__meta i,
.stat-strip dd,
.counter__label,
.headline-facts dd,
.bond-sum dd { text-wrap: balance; }

/* ---- 33.30 The toolbar chevron sits in its select (18) ----------------- */
/* With the label stacked above the select (a phone, and now the tablet row),
   the chevron's top: 50% measured the label and the select together and sat
   12px above the select's middle. Pinned to the select from below instead. */
@media (max-width: 900px) {
  .toolbar__controls > span::after { top: auto; bottom: 21px; transform: rotate(45deg); }
}

/* ---- 33.31 A jump link lands under the jump nav, not a screen down (45) - */
/* The page's scroll-padding already clears the header and the jump nav
   (148px), and each target added its own 140px scroll margin on top, so every
   section landed 168px under the nav with its heading a third of the way down
   the screen. One offset, not two. */
#photos, #about-farm, #schedule, #bond, #similar-farms { scroll-margin-top: 0; }

/* ---- 33.32 Motion audit (46 to 48) ------------------------------------- */
/* The launcher's ring pulsed forever on every page until the chat had been
   opened once, and the "on duty" dot breathed for as long as the panel was
   open: two looped attention pulses, the first tell on the motion ban list.
   The ring now calls three times and rests; the dot is simply there. */
.chat-fab::before { animation-iteration-count: 3; }
.chat-dot { animation: none; }
/* One zoom for a photograph under the pointer: cards 1.045, tiles 1.06 and
   the gallery 1.04 were three answers to one question. */
.farm-card:hover .farm-card__media img,
.type-tile:hover img,
.gallery figure:hover img { transform: scale(1.04); }

/* ---- 33.33 From the accessibility and performance audit ---------------- */
/* A listing's crumb said "Listing" until the data landed, then the farm's
   name wrapped it to a second line and the whole listing dropped 25px (CLS
   0.24 at 390). The name always takes its own line below 900, so "Listing"
   holds the same line the name will. */
@media (max-width: 900px) {
  .band--crumbs #crumb-now { flex-basis: 100%; }
}
/* Above 900 the crumb holds one line whatever the font is doing: the shift
   also happened at 1280 when the name landed before Tenor Sans did. */
@media (min-width: 901px) {
  .band--crumbs .crumb { flex-wrap: nowrap; }
  .band--crumbs #crumb-now { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
/* The listing's bottom bar covered a focused control near the foot of the
   screen (WCAG 2.4.11): nine of them at 390, among them All sections. Focus
   scrolling now keeps clear of the bar. */
html:has(.actionbar) { scroll-padding-bottom: calc(var(--actionbar-h, 0px) + var(--tray-h, 0px) + var(--safe-bottom) + 12px); }
/* content-visibility on the whole closing band kept its photograph off the
   LCP, and also took its heading, copy and links out of the accessibility
   tree while it was off screen. Only the photograph skips rendering now. */
.cta { content-visibility: visible; contain-intrinsic-size: none; }
.cta__media { content-visibility: auto; }
@media print { .cta__media { content-visibility: visible; } }
/* An empty field's border is its only outline, so it needs 3:1 against the
   ground (WCAG 1.4.11); ink at 34% measured 2.06:1. */
.field input,
.field textarea,
.field select { border-color: color-mix(in srgb, var(--ink) 58%, transparent); }

/* ---- 33.34 Review round (site-reviewer, 2026-09-11) -------------------- */
/* R2's hero pause was removed on 2026-09-15 with the hero's movement (6). */

/* R7 was taken out on the second read: pushing the action to the map's
   bottom only moved the empty space into the column (248px between a note
   and its own button at 1440). The column hugs its content; the band's
   shape is listed for Edgar in QA-REPORT.md. */

/* R3. On a phone three farms share 390px. The padding added in 33.25 split
   "R13 000 000" across two lines; the cells take a little less padding and
   the figures a size that fits the column, and a figure is never broken
   inside itself (the script joins its digits with no-break spaces). */
@media (max-width: 640px) {
  .cmp-table tbody td { padding: 0.3rem 0.4rem; font-size: var(--fs-body); }
  .cmp-table tbody tr.is-stacked { grid-template-columns: minmax(0, 1fr); row-gap: 0.35rem; }
  .cmp-table tbody tr.is-stacked td { display: flex; align-items: baseline; gap: 0.75rem; }
  .cmp-table tbody tr.is-stacked td::before {
    content: attr(data-ref);
    flex: none; min-width: 7.5rem;
    font-family: var(--display); font-size: var(--fs-label); letter-spacing: 0.1em;
    color: var(--muted);
  }
}

/* Stated limit: without :has() (Chrome 105, Safari 15.4, Firefox 121 and
   later have it) the contact selects keep the native arrow, since the drawn
   chevron hangs off .field:has(> select). */
@supports not selector(:has(a)) {
  .field select { appearance: auto; -webkit-appearance: menulist; padding-right: 1rem; }
}
/* R3 at 320: three farms in 264px. The column gap and the cell padding give
   up the three pixels "R13 000 000" was short of one line. */
/* (A 360px step that gave up column gap and padding came out with G1.) */
/* Focus scrolling also keeps clear of the compare tray wherever it is up
   (2.4.11), not only of the listing's bottom bar. */
html { scroll-padding-bottom: calc(var(--actionbar-h, 0px) + var(--tray-h, 0px) + var(--safe-bottom) + 12px); }
/* Found in the review round: pressed, "All sections" turned ink on the ink
   schedule band (section 30.20f wrote its colour for a light ground). */
.band--ink .spec-tab--all[aria-pressed="true"] { color: var(--paper); }

/* ---- 33.35 The search rail on the first screen (fault 1, Edgar's yes) ---- */
/* Edgar, 2026-09-11, answering the pass's first decision: yes, the search
   belongs on the first screen. The hero and the rail share the first screen:
   the rail is sized to a height (--rail-h, in rem so it grows with text) and
   the hero gives up exactly that, so the rail's foot is the fold. A rail whose
   content ever outgrows the token only grows, and the page still works.
   Phones keep the full-screen hero: their rail is three stacked selects. */
:root { --rail-h: 0px; }
@media (min-width: 641px) {
  .searchrail__inner { min-height: var(--rail-h); }
}
@media (min-width: 901px) {
  :root { --rail-h: 7.5rem; }
  .hero:has(+ .searchrail) { min-height: calc(min(100svh, 940px) - var(--rail-h)); }
}
@media (min-width: 641px) and (max-width: 900px) {
  :root { --rail-h: 11rem; }
  .hero:has(+ .searchrail) { min-height: calc(100svh - var(--rail-h)); }
}
/* While the rail is at the foot of the screen it owns the bottom edge. The
   compare tray stands down (it returns as soon as the page moves; shrinking
   the hero for it instead shifted the layout by 0.1 to 0.15 after load), and
   the chat launcher, its teaser and its panel stand on the rail rather than
   over the Search button. Everything rides the bottom-edge tokens of 31.3. */
html.rail-at-foot {
  --tray-stack: 0px;
  --fab-bottom: calc(max(var(--dock), var(--safe-bottom)) + var(--fab-gap) + var(--rail-h));
}
html.rail-at-foot .cmp-tray { visibility: hidden; pointer-events: none; }

/* ==========================================================================
   34. /better pass, second read, 2026-09-11 (/better /max)
   Each block names its fault number in docs/QA-REPORT.md under "/better pass
   2026-09-11, second read". Tokens and colour-mixes of them only.
   ========================================================================== */

/* ---- 34.1 One primary colour; a phone's lone action takes the row (25-28) */
/* .btn--ink is gone: every primary is the gold button (ruling 2026-08-11). */
@media (max-width: 560px) {
  .form-grid .btn,
  .provlist-act .btn,
  .reqband__act .btn,
  .farm-empty .btn { width: 100%; }
}

/* ---- 34.2 One section rhythm (32) ---------------------------------------- */
/* Every seam is 0.6 of the band token, the one-seam rule of 2026-09-04; the
   last band before the footer keeps the whole token. The tight band was the
   loose one (88 against 66 at 1280), the map bands restated 88 on purpose and
   the listing's own bands, which are not main > section, ran the full 110. */
.band--tight { padding-block: calc(var(--band) * 0.6); }
@media (min-width: 1024px) {
  main > section.band--tight:has(> .shell > .country),
  main > section.band--tight:has(> .shell > .mapbox) { padding-block: calc(var(--band) * 0.6); }
}
#listing-detail .band { padding-block: calc(var(--band) * 0.6); }
#listing-detail .band.band--top0 { padding-top: 0; }

/* ---- 34.3 One right-hand column line per band (16) ----------------------- */
/* A head's lede starts on the line the block under it starts its right-hand
   column: rows, the FAQ photograph, the letterhead and the requirement box
   all take the head's 1.15 / 0.85 split and the split gap; over a card grid
   or the type tiles the lede starts on a card or tile edge. */
@media (min-width: 901px) {
  .section-head--split { column-gap: clamp(2rem, 5vw, 5.5rem); }
  .shell > .rows > .row-item {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    column-gap: clamp(2rem, 5vw, 5.5rem);
  }
  .faq-wrap { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); gap: clamp(2rem, 5vw, 5.5rem); }
  .enquiry-split { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); column-gap: clamp(2rem, 5vw, 5.5rem); }
  .section-head--split:has(+ .farm-grid) {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    column-gap: clamp(1.25rem, 2.4vw, 2.2rem);
  }
  .section-head--split:has(+ .farm-grid) > * { grid-column: span 3; }
  .section-head--split:has(+ .type-grid) {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: clamp(0.75rem, 1.4vw, 1.25rem);
  }
  .section-head--split:has(+ .type-grid) > :first-child { grid-column: span 2; }
}
@media (min-width: 1101px) {
  .section-head--split:has(+ .farm-grid--even) > :first-child { grid-column: span 4; }
  .section-head--split:has(+ .farm-grid--even) > :last-child { grid-column: span 2; }
}

/* ---- 34.4 The requirement band: statement and action left, rows right,
   both finishing on one line (11, 15, 17) ---------------------------------- */
.reqband { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(1.5rem, 4vw, 2rem); }
.reqband__act { margin: 0; }
.reqband .reqbox__rows { margin-bottom: 0; }
@media (min-width: 901px) {
  .reqband {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    grid-template-areas: "head box" "act box";
    grid-template-rows: auto 1fr;
    column-gap: clamp(2rem, 5vw, 5.5rem);
    row-gap: 1.75rem;
  }
  .reqband__head { grid-area: head; }
  .reqband > .reqbox { grid-area: box; display: flex; flex-direction: column; }
  .reqband__act { grid-area: act; align-self: end; }
  /* The rows share the column's height, so the last hairline lands on the
     button's foot whichever side is taller. */
  .reqband .reqbox > .reqbox__rows { flex: 1; display: flex; flex-direction: column; }
  .reqband .reqbox > .reqbox__rows > * { flex: 1; align-items: flex-end; }
}
.cmp-cta__list > li {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  padding: 0.7rem 0; border-bottom: 1px solid var(--line);
  font-family: var(--display); font-size: 0.9375rem; color: var(--ink); text-align: right;
}
.cmp-cta__list .r { font-family: var(--display); font-size: var(--fs-label); letter-spacing: 0.12em; color: var(--gold-deep); }

/* ---- 34.5 Cards: the trays share a line, a title reserves nothing it does
   not use, and three facts never strand one (18, 19, 20) ------------------- */
.farm-card__title { min-height: 0; }
.farm-card__row { margin-bottom: 1.15rem; }
.farm-card__meta { margin-top: auto; }
/* The foot carried its own auto margin, so the spare height was split between
   the two and the trays still sat 13px apart across a row. */
.farm-card__meta ~ .farm-card__foot { margin-top: 0; }
/* Three facts in a narrow card are three rows, label and figure, the way the
   schedule itself reads, instead of two and a stranded third. */
@container card (max-width: 400px) {
  .farm-card__meta { grid-template-columns: minmax(0, 1fr); row-gap: 0; padding-block: 0.3rem; }
  .farm-card__meta span,
  .farm-card__meta span + span,
  .farm-card__meta span:nth-child(3) {
    grid-column: auto;
    flex-direction: row-reverse; justify-content: space-between; align-items: baseline; gap: 1rem;
    padding: 0.6rem 0;
    border-left: 0; border-top: 0;
    border-bottom: 1px solid var(--line);
  }
  .farm-card__meta span:last-child { border-bottom: 0; }
  .farm-card__meta b { text-align: right; }
}
@media (max-width: 420px) {
  .farm-card__meta { grid-template-columns: minmax(0, 1fr); row-gap: 0; padding-block: 0.3rem; }
  .farm-card__meta span,
  .farm-card__meta span + span,
  .farm-card__meta span:nth-child(3) {
    grid-column: auto;
    flex-direction: row-reverse; justify-content: space-between; align-items: baseline; gap: 1rem;
    padding: 0.6rem 0;
    border-left: 0; border-top: 0;
    border-bottom: 1px solid var(--line);
  }
  .farm-card__meta span:last-child { border-bottom: 0; }
  .farm-card__meta b { text-align: right; }
}

/* ---- 34.6 A listing's three highlights on a phone: three rows (21) ------- */
@media (max-width: 640px) {
  .factgrid.factgrid--rest { grid-template-columns: minmax(0, 1fr); }
  .factgrid.factgrid--rest div {
    display: flex; flex-direction: row-reverse; justify-content: space-between; align-items: baseline; gap: 1rem;
    border-right: 0; border-bottom: 1px solid var(--line);
    padding-block: 0.95rem;
  }
  .factgrid.factgrid--rest div:last-child { border-bottom: 0; }
  .factgrid.factgrid--rest dd { margin: 0; }
  .factgrid.factgrid--rest dt { text-align: right; }
}

/* ---- 34.7 The crumb keeps its links whole (22) --------------------------- */
/* The measure rule held every paragraph in main to its ch count, and the
   crumb is a paragraph: 528px, so "Farms for sale" folded and the farm's
   name was cut off with an ellipsis on a 1198px column. */
.crumb { max-width: none; }
.crumb > a { flex-shrink: 0; white-space: nowrap; }

/* ---- 34.8 Enquire keeps the jump nav's spacing (23) ---------------------- */
.jumpnav .jumpnav__cta { margin-left: 0; }

/* ---- 34.9 A schedule row on a phone is one row (24) ---------------------- */
@media (max-width: 640px) {
  .schedule-layout .spec-group dt,
  .schedule-layout .spec-group dd { padding: 0.75rem 0; border-bottom: 1px solid var(--line-soft); }
  .schedule-layout .spec-group dd { padding-left: 1rem; }
  .band--ink .schedule-layout .spec-group dt,
  .band--ink .schedule-layout .spec-group dd { border-bottom-color: var(--line-dark); }
}

/* ---- 34.10 Controls: one size and one face for what a visitor sets (29, 30) */
/* The label stays the display face in capitals; the value is body type at
   the body size, as on the contact form and the enquiry card. The home rail
   and the listings search line are display pieces and keep their own. */
.toolbar select,
.typemenu__value { font-family: var(--body); font-size: var(--fs-body); letter-spacing: 0; }

/* ---- 34.11 The bond's figures read label, then figure (31) --------------- */
.bond-sum > div { align-items: baseline; flex-wrap: nowrap; }
.bond-sum > div > dt { flex: 1 1 auto; min-width: 0; }
.bond-sum dt {
  font-family: var(--display); font-size: var(--fs-label); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted-dark);
  white-space: normal; font-variant-numeric: normal; text-wrap: balance;
}
.bond-sum dd {
  flex: none; margin: 0;
  font-family: var(--display); font-size: 1.0625rem; letter-spacing: -0.02em;
  text-transform: none; color: var(--paper);
  text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums;
}

/* ---- 34.12 Type: no lone words (34, 35, 37) ------------------------------ */
/* Body copy avoids a last line of one word where the engine can (Chrome 117,
   Safari 26; elsewhere it wraps as before). */
p, li, dd, figcaption { text-wrap: pretty; }
/* 18ch cut three inner titles into a lone last or middle word from 768 up.
   25ch lets each settle into two balanced lines. */
.page-hero h1 { max-width: 25ch; }
@media (max-width: 900px) {
  .page-hero--right .shell > * { margin-right: 0; }
}
/* "South Africa" is 7.5 times its own size wide in the display face, and it
   now stands on its own line, so on a narrow phone the hero steps down just
   enough to keep the two words together. */
.hero__title { font-size: min(var(--fs-hero), calc((100vw - 2 * var(--gutter)) / 7.6)); }
.hero__title .gold { white-space: nowrap; }
.hero__lede .lede-short { display: none; }
/* Phones, Edgar 2026-09-15: "make the text much shorter and the heading
   bigger". "South" and "Africa" take a line each, so the widest line becomes
   "Farms for" (6.06 times its size) instead of "South Africa" (7.8) and the
   title grows by about a quarter. The lede drops to its one-line version. */
@media (max-width: 640px) {
  .hero__title { font-size: min(calc((100vw - 2 * var(--gutter)) / 6.25), 4.25rem); }
  .hero__title .gold > span { display: block; }
  .hero__lede .lede-full { display: none; }
  .hero__lede .lede-short { display: inline; }
}

/* ---- 34.13 Comparison heads on a phone name each listing short (38) ------ */
.cmp-head .t-short { display: none; }
@media (max-width: 640px) {
  .cmp-head .t-full {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  /* A third of a phone is about 97px: even the short name broke in capitals
     (FRANSKRA / AL), so it is set in its own case, and the reference, which
     every stacked row below repeats, steps out of the head. */
  .cmp-head .t-short { display: inline; text-transform: none; letter-spacing: -0.01em; }
  .cmp-head .l,
  .cmp-head .r { display: none; }
}
.cmp-mini .t { text-transform: none; }

/* ---- 34.14 The kinds of land say what is on the books (7, 8, 33) -------- */
.type-tile__stock {
  position: absolute; z-index: 2;
  top: clamp(0.75rem, 1.4vw, 1rem); right: clamp(0.75rem, 1.4vw, 1rem);
  padding: 0.35rem 0.65rem;
  border-radius: var(--r-chip);
  background: var(--gold); color: var(--ink);
  font-family: var(--display); font-size: var(--fs-label); letter-spacing: 0.1em; text-transform: uppercase;
}
.type-tile__label .type-tile__stock.is-none {
  position: static;
  padding: 0; background: none; border-radius: 0;
  color: color-mix(in srgb, var(--paper) 78%, transparent);
  letter-spacing: 0.1em;
}
.type-tile__stock[hidden] { display: none; }
.type-more a .n,
.site-footer .n { margin-left: 0.4rem; font-variant-numeric: tabular-nums; }
.type-more a .n { color: var(--gold-deep); }
.type-more a.is-none .n { color: inherit; }
/* The footer is ink, so its counts take the bright gold (6.5:1); the 404's
   chips are paper, so theirs take the deep one like the home chips. On sage
   the deep gold is 3.85:1, so small gold there is the darker mix. */
.site-footer .n { color: var(--gold); }
.band--sage .type-more a .n,
.band--sage .cmp-cta__list .r,
.band--sage .reqbox__rows dd[data-set="true"] { color: var(--gold-dark); }
/* On a phone the chips are rows with the count beside the name, the way the
   province rows read, rather than a two-column grid of three heights. */
@media (max-width: 640px) {
  .type-more[data-type-list] {
    display: grid; grid-template-columns: minmax(0, 1fr); gap: 0;
    border-top: 1px solid color-mix(in srgb, var(--ink) 18%, transparent);
  }
  .type-more[data-type-list] a {
    display: flex; justify-content: flex-start; align-items: baseline;
    text-align: left;
    padding: 0.85rem 0;
    border: 0; border-bottom: 1px solid color-mix(in srgb, var(--ink) 18%, transparent);
    border-radius: 0;
    background: transparent;
  }
  .type-more[data-type-list] a:hover { background: transparent; color: var(--ink); }
}

/* ---- 34.15 Similar listings under 1100: rows, not a stranded card (44, 48) */
@media (max-width: 1100px) {
  #similar-farms .farm-grid { grid-template-columns: minmax(0, 1fr); gap: 0.75rem; }
  #similar-farms .farm-grid > * { grid-column: 1 / -1 !important; }
  #similar-farms .farm-card { flex-direction: row; }
  #similar-farms .farm-card__media { aspect-ratio: auto; flex: none; width: clamp(7.5rem, 30%, 14rem); min-height: 7.5rem; }
  #similar-farms .farm-card__chips,
  #similar-farms .farm-card .save-btn,
  #similar-farms .farm-card__meta,
  #similar-farms .farm-card__foot:not(.farm-card__foot--sub) { display: none; }
  #similar-farms .farm-card__body { padding: 0.9rem 1rem 0.2rem; justify-content: center; }
  #similar-farms .farm-card__row { flex-direction: column; align-items: flex-start; gap: 0.3rem; margin-bottom: 0.4rem; }
  #similar-farms .farm-card__title { margin-bottom: 0; }
  #similar-farms .farm-card__foot--sub { margin-top: auto; border-top: 0; padding-top: 0.2rem; }
}
/* On a phone a row leaves "Baardskeerdersbos" no room at the card title's
   size, so the photograph goes on top at 16:9 and the card keeps only what
   picks it: name, place, price, compare. */
@media (max-width: 640px) {
  #similar-farms .farm-card { flex-direction: column; }
  #similar-farms .farm-card__media { width: auto; aspect-ratio: 16 / 9; min-height: 0; }
  #similar-farms .farm-card__body { padding: 1rem 1.1rem 0.2rem; }
}

/* ---- 34.16 A listing on a phone: the price before the tools (42) --------- */
@media (max-width: 900px) {
  .detail-head .detail-price-block { order: 1; margin-top: 1.25rem; }
  .detail-head .detail-actions { order: 2; }
}

/* ---- 34.17 Contact on a phone: the one-tap routes first (43) ------------- */
@media (max-width: 900px) {
  .enquiry-split > aside { order: -1; }
  .enquiry-split .cband--stack { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .enquiry-split .cband--stack > div,
  .enquiry-split .cband--stack > div + div {
    padding: 0.9rem 0.75rem 0.9rem 0;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
  .enquiry-split .cband--stack > div:nth-child(n+3) { grid-column: 1 / -1; }
  .enquiry-split .cband__foot { margin-top: 1rem; }
}

/* ---- 34.18 Photographs cropped to what they are (45, 46) ----------------- */
/* The FAQ photograph's top quarter is sky, pure white where it met the white
   band; the vineyard tile showed hills with the vines a strip at its foot. */
.faq-photo img { object-position: 50% 100%; }
/* Cropped to the vines, the portrait frame still opens on white sky at 1280
   (top corners rgb 255 on a rgb 255 band). A hairline gives it the edge the
   photograph cannot. */
.faq-photo::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border-radius: var(--r-media);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 16%, transparent);
}
.type-tile:first-child img { object-position: 50% 85%; }

/* ---- 34.19 Stages say what the seller holds; questions go into the
   message (12) -------------------------------------------------------------- */
/* The outcome and the add button are the row's third piece, under the text
   in the text's own column. (Tried at the foot of the title's column: the
   outcome ran 65px past a four-line body, so the row no longer ended on one
   line.) */
.row-item:has(> .row-item__out, > .row-add) {
  grid-template-areas: "t" "b" "o";
}
/* Areas only on the rows that declare them: a row without a third piece has
   no template, and a named area on it lands the title and body in the same
   implicit cell (the privacy notice printed its headings over its text). */
.row-item:has(> .row-item__out, > .row-add) > h3 { grid-area: t; }
.row-item:has(> .row-item__out, > .row-add) > .row-item__body { grid-area: b; }
.row-item > .row-item__out,
.row-item > .row-add { grid-area: o; justify-self: start; }
.row-item > .row-item__out { justify-self: stretch; }
@media (min-width: 901px) {
  .shell > .rows > .row-item:has(> .row-item__out, > .row-add) {
    grid-template-areas: "t b" ". o";
    row-gap: 0;
    align-items: start;
  }
}
.row-item > p.row-item__out {
  display: flex; flex-direction: column; gap: 0.3rem;
  margin: 1rem 0 0; padding-top: 0.75rem;
  border-top: 1px solid var(--line-soft);
  color: var(--ink); font-size: var(--fs-body); line-height: 1.55;
}
.row-item__out span {
  font-family: var(--display); font-size: var(--fs-label); letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--gold-deep);
}
.row-add {
  display: inline-flex; align-items: center; gap: 0.55rem;
  min-height: 44px; margin-top: 1rem; padding: 0.55rem 1rem;
  border: 1px solid color-mix(in srgb, var(--ink) 42%, transparent);
  border-radius: var(--r-chip);
  background: var(--paper); color: var(--ink);
  font-family: var(--display); font-size: var(--fs-label); letter-spacing: 0.1em; text-transform: uppercase;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease-soft), color var(--t-fast) var(--ease-soft), border-color var(--t-fast) var(--ease-soft);
}
.row-add::before { content: "+"; font-size: 1.05rem; line-height: 1; letter-spacing: 0; }
.row-add:hover { border-color: var(--ink); }
.row-add[data-added="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.row-add[data-added="true"]::before { content: "\2713"; }
.row-add[hidden] { display: none; }
@media print { .row-add { display: none; } }

/* ---- 34.20 An empty comparison is a list to pick from (13, 14) ---------- */
.cmp-pick h3 { margin-bottom: 0.6rem; }
.cmp-pick > p { color: var(--muted); max-width: 58ch; margin-bottom: 1.5rem; }
.cmp-pick__list { border-top: 1px solid var(--line); }
.cmp-pick__row {
  display: grid; grid-template-columns: 96px minmax(0, 1fr) auto;
  gap: 0.75rem 1.25rem; align-items: center;
  padding: 0.9rem 0; border-bottom: 1px solid var(--line);
}
.cmp-pick__row img { width: 96px; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--r-chip); }
.cmp-pick__txt { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.cmp-pick__txt .r { font-family: var(--display); font-size: var(--fs-label); letter-spacing: 0.12em; color: var(--gold-deep); }
.cmp-pick__txt .t {
  font-family: var(--display); font-size: var(--fs-h4); letter-spacing: -0.02em;
  text-transform: uppercase; color: var(--ink); overflow-wrap: break-word;
}
.cmp-pick__txt .t:hover { color: var(--gold-deep); }
.cmp-pick__txt .p { color: var(--muted); }
@media (max-width: 560px) {
  .cmp-pick__row { grid-template-columns: 72px minmax(0, 1fr); }
  .cmp-pick__row img { width: 72px; }
  .cmp-pick__row .cmp-toggle { grid-column: 2; justify-self: start; }
}
.cmp-only-shared tbody tr:not(.cmp-sec)[data-shared="0"],
.cmp-only-shared tbody tr.cmp-sec[data-has-shared="0"],
.cmp-only-diff tbody tr.cmp-sec[data-has-diff="0"] { display: none; }
.cmp-cta__list > li { font-family: var(--body); font-size: var(--fs-body); }

/* ---- 34.21 The roles the census still split (roles.mjs, after the pass) -- */
/* Labels are 13px: the card's chips and fact count, the toolbar's labels and
   the comparison's stacked row names were 12. The home stat strip's labels
   had been caught by 30.14 and set in body type at 16px in capitals. */
.farm-card__ref,
.farm-card__type,
.farm-card__facts,
.toolbar label,
.findbar label { font-size: var(--fs-label); }
@media (max-width: 800px) {
  .cmp-table tbody th { font-size: var(--fs-label); }
}
/* One small-figure size: the card's facts, the listing's highlights, the bond's
   sums and its slider readouts were 15, 17, 18 and 18.7px. */
.farm-card__meta b,
.factgrid dt,
.bond-sum dd,
.bond-f output { font-size: clamp(1rem, 1.25vw, 1.15rem); }
/* A price in the comparison's head is a price like any card's. */
@media (min-width: 801px) {
  .cmp-head .p { font-size: clamp(1.15rem, 1.5vw, 1.5rem); }
}
/* Buttons on a listing phone were 11px so "Print schedule" fitted half the
   row. Buttons are 14px everywhere; Print says "Print" there instead. */
@media (max-width: 900px) {
  .detail-actions .btn,
  .detail-actions .save-btn--inline { font-size: 0.875rem; letter-spacing: 0.09em; }
}
/* ---- 34.22 From the site-auditor's read of the pre-pass build (50 to 60) - */
/* The enquiry card is 885 to 893px tall and pinned 156px down: on a screen
   under about 1060px tall Tab put "Send enquiry" below the fold (2.4.11).
   It only pins where it fits. */
@media (max-height: 1079px) {
  #about-farm .enquiry-card { position: static; }
}
/* At 200% text the footer's email ran past a 1280 screen and "Smallholdings"
   past a 390 one (1.4.4). */
.site-footer a,
.type-more a { overflow-wrap: anywhere; }
.footer-grid > * { min-width: 0; }
/* Focused, the search line went gold at 2.56:1 with a white ring at 1.09:1,
   fainter than at rest (1.4.11, 2.4.7). It darkens instead. */
.findbar:focus-within { border-bottom-color: var(--ink); box-shadow: 0 1px 0 0 var(--ink); }
.findbar input:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
/* What is optional says so before the first failed send (3.3.2). */
.field label .opt { text-transform: none; letter-spacing: 0; color: var(--muted); font-family: var(--body); }
.enquiry-card .field label .opt { color: var(--muted-dark); }
/* Focus scrolling keeps clear of the chat launcher as well as the bars. */
html,
html:has(.actionbar) { scroll-padding-bottom: calc(var(--actionbar-h, 0px) + var(--tray-h, 0px) + var(--safe-bottom) + var(--fab-size) + var(--fab-gap)); }
.typemenu__list { outline: none; }

.btn .lbl-short { display: none; }
@media (max-width: 560px) {
  .btn .lbl-long {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .btn .lbl-short { display: inline; }
}

/* ==========================================================================
   35. The phone card: the photographs swipe, 2026-09-12
   Edgar picked CARDS 1 of three (docs/DESIGN-DIRECTION.md) and asked for it
   more polished. The card is its photographs: five views of the farm and a
   last frame that counts the rest, then the name, the price, three figures
   in one row and the compare tick. No box, nothing on paper. script.js adds
   .farm-card--swipe under 641px only; above that the card is unchanged.
   ========================================================================== */
@media (min-width: 641px) {
  .farm-card__media > a > img + img,
  .farm-card__more,
  .farm-card__dots { display: none; }
}
/* The photograph's link is the first Tab stop on a card, and its ring was
   drawn outside the link where the frame's clip cut all of it away (review,
   2.4.7; the desktop card had the same fault from overflow: hidden). The
   ring is drawn inside the frame instead, over the photograph, in the site's
   two tones: ink at the edge against the page, a paper band inside against
   the picture. The plain outline stays as the fallback without :has(). */
.farm-card__media > a:focus-visible { outline-offset: -4px; box-shadow: none; }
.farm-card__media:has(> a:focus-visible) > a:focus-visible { outline: 0; }
.farm-card__media:has(> a:focus-visible)::after {
  content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 0 0 2px var(--ink),
              inset 0 0 0 5px color-mix(in srgb, var(--paper) 92%, transparent);
}
@media (max-width: 640px) {
  .farm-grid:has(> .farm-card--swipe) { row-gap: 3rem; }
  .farm-card--swipe,
  .farm-card--swipe:hover,
  .js .farm-card--swipe.reveal.is-visible:hover {
    background: transparent; border: 0; border-radius: 0;
    overflow: visible; box-shadow: none; transform: none;
  }

  /* ---- The photographs ---------------------------------------------------- */
  /* clip-path, not only overflow: Safari lets a scrolled child paint past a
     rounded parent's corners. clip-path also makes the frame its own layer,
     which sank the heart under the title's card-wide link (30.2), so the
     frame is raised above that link as a whole. */
  .farm-card--swipe .farm-card__media {
    aspect-ratio: 1 / 1;
    border-radius: var(--r-media);
    clip-path: inset(0 round var(--r-media));
    z-index: 2;
  }
  .farm-card--swipe .farm-card__media > a {
    display: flex; overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory; scrollbar-width: none;
    -webkit-tap-highlight-color: transparent;
  }
  .farm-card--swipe .farm-card__media > a::-webkit-scrollbar { display: none; }
  .farm-card--swipe .farm-card__media > a > img,
  .farm-card--swipe .farm-card__more {
    flex: 0 0 100%; width: 100%; height: 100%;
    scroll-snap-align: start; scroll-snap-stop: always;
  }
  .farm-card--swipe .farm-card__media img,
  .farm-card--swipe:hover .farm-card__media img { transform: none; }

  /* The last frame: the next photograph under one flat scrim, and the count
     of what the schedule holds. A swipe that ends somewhere, not on a wall. */
  .farm-card--swipe .farm-card__more {
    position: relative;
    display: grid; place-items: center;
    background: var(--ink-deep);
  }
  .farm-card--swipe .farm-card__more > img { position: absolute; inset: 0; }
  .farm-card--swipe .farm-card__more::after {
    content: ""; position: absolute; inset: 0;
    background: color-mix(in srgb, var(--ink-deep) 64%, transparent);
  }
  .farm-card--swipe .farm-card__more-txt {
    position: relative; z-index: 1;
    display: flex; flex-direction: column; align-items: center; gap: 0.7rem;
    padding: 1.5rem; text-align: center;
    color: var(--paper);
  }
  .farm-card--swipe .farm-card__more-txt b {
    font-family: var(--display); font-weight: 400;
    font-size: clamp(1.35rem, 7.2vw, 1.75rem); line-height: 1.05;
    letter-spacing: -0.01em; text-transform: uppercase;
  }
  .farm-card--swipe .farm-card__more-txt > span {
    font-family: var(--display); font-size: var(--fs-label);
    letter-spacing: 0.1em; text-transform: uppercase;
    color: color-mix(in srgb, var(--paper) 88%, transparent);
  }

  /* Where you are in the five: plain dots on the photograph, the one in view
     drawn out long. A shadow under each, no capsule behind them. */
  .farm-card--swipe .farm-card__dots {
    position: absolute; left: 0; right: 0; bottom: 14px; z-index: 3;
    display: flex; justify-content: center; align-items: center; gap: 5px;
    pointer-events: none;
  }
  .farm-card--swipe .farm-card__dots i {
    width: 6px; height: 6px; border-radius: 3px;
    background: color-mix(in srgb, var(--paper) 62%, transparent);
    box-shadow: 0 1px 3px color-mix(in srgb, var(--ink-deep) 55%, transparent);
    transition: width var(--t-mid) var(--ease), background var(--t-fast) var(--ease-soft);
  }
  .farm-card--swipe .farm-card__dots i.on { width: 18px; background: var(--paper); }

  /* The type and any status in one row at the top left; the reference number
     is on the schedule, not on the photograph. */
  .farm-card--swipe .farm-card__chips {
    top: 12px; left: 12px; right: 68px;
    min-height: 44px; align-items: center;   /* on the heart's centre line */
    justify-content: flex-start;
  }
  .farm-card--swipe .farm-card__ref { display: none; }
  .farm-card--swipe .farm-card__type,
  .farm-card--swipe .farm-card__status {
    position: static;
    padding: 0.4rem 0.6rem 0.36rem;
    letter-spacing: 0.1em;
    backdrop-filter: none;
    box-shadow: 0 1px 4px -1px color-mix(in srgb, var(--ink-deep) 30%, transparent);
  }
  .farm-card--swipe .farm-card__type { background: var(--paper); color: var(--ink); }
  .farm-card--swipe .save-btn {
    top: 12px; right: 12px; bottom: auto;
    background: var(--paper); border-color: transparent; color: var(--ink);
    backdrop-filter: none;
    box-shadow: 0 2px 8px -2px color-mix(in srgb, var(--ink-deep) 40%, transparent);
  }
  .farm-card--swipe .save-btn:hover { color: var(--ink); }
  .farm-card--swipe .save-btn[aria-pressed="true"] { background: var(--gold); color: var(--ink); }

  /* ---- The words ---------------------------------------------------------- */
  /* Name, place, then the price straight under them in gold at a size that
     is read before anything else on the card. */
  .farm-card--swipe .farm-card__body { display: block; padding: 1.2rem 0 0; }
  .farm-card--swipe .farm-card__row { display: block; margin: 0; }
  .farm-card--swipe .farm-card__title { margin: 0 0 0.4rem; }
  .farm-card--swipe .farm-card__place { margin: 0; }
  .farm-card--swipe .farm-card__price {
    margin: 0.85rem 0 0;
    font-size: 1.5rem; line-height: 1.1;
  }

  /* Three figures in one row, value over label, hairlines between. Every
     rule before this one turned them into rows at this width. Each figure is
     as wide as its own words: equal thirds broke "North facing" and "Stand
     extent" onto two lines at 390 with room to spare beside "AGR1". */
  .farm-card--swipe .farm-card__meta {
    display: flex; flex-wrap: nowrap; align-items: stretch;
    gap: 0; margin: 1.1rem 0 0; padding: 0.95rem 0 0;
    border: 0; border-top: 1px solid var(--line); background: none;
  }
  .farm-card--swipe .farm-card__meta > span:nth-child(n) {
    flex: 0 1 auto; min-width: min-content;   /* a word never splits, a label wraps */
    display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start;
    gap: 0.3rem;
    margin: 0 0 0 0.7rem; padding: 0 0 0 0.7rem;   /* 0.9 left "Boesmans River" 5px short at 375 (look round 19) */
    border: 0; border-left: 1px solid var(--line-soft);
  }
  .farm-card--swipe .farm-card__meta > span:first-child { margin-left: 0; padding-left: 0; border-left: 0; }
  .farm-card--swipe .farm-card__meta b { text-align: left; line-height: 1.15; }
  /* Labels in plain small type, not tracked capitals: capitals ran "Total
     extent" and "Current crop" onto two lines each, and the row stopped
     reading as one line. */
  .farm-card--swipe .farm-card__meta i {
    min-height: 0;
    font-family: var(--body); font-size: var(--fs-label);
    letter-spacing: 0; text-transform: none;
  }

  /* A tap on the heart or Compare leaves a touch screen in :hover, which
     turned that farm's name gold until the next tap elsewhere. */
  @media (hover: none) {
    .farm-card--swipe:hover .farm-card__title { color: var(--ink); }
  }

  /* The tick and the count share the last line, under a softer hairline. */
  .farm-card--swipe .farm-card__foot:not(.farm-card__foot--sub) { display: none !important; }
  .farm-card--swipe .farm-card__foot--sub {
    margin: 1rem 0 0; padding: 0;
    border-top: 1px solid var(--line-soft);
  }
}

/* ==========================================================================
   36. Mobile pass, 2026-09-12 (real iPhone viewport, 390x844, touch)
   ========================================================================== */
/* ---- 36.1 The jump nav keeps its gutter --------------------------------- */
/* The row snapped its first link to the screen edge on load (scrollLeft 28),
   so "Photos" read as "HOTOS" under the fade. Snapping and scrolling into
   view now stop at the gutter. */
.jumpnav__inner { scroll-padding-inline: var(--gutter); }

/* ---- 36.2 A count stays on the line of the word it counts --------------- */
/* In a flex link a two-line name is one anonymous box, so the footer's
   "Commercial property" parked its 1 at the far edge, 48px from the word. */
.site-footer [data-onbooks] a { display: inline-block; }
.site-footer [data-onbooks] .n { display: inline; }

/* ---- 36.3 The 404's type chips are rows on a phone ---------------------- */
/* Two by two they came out at three heights, with each count stranded at the
   right of a wrapped name. The home list already reads as rows. */
@media (max-width: 640px) {
  .type-more--dark[data-onbooks] {
    display: grid; grid-template-columns: minmax(0, 1fr); gap: 0;
    border-top: 1px solid var(--line-dark);
  }
  .type-more--dark[data-onbooks] a {
    display: flex; justify-content: flex-start; align-items: baseline;
    min-height: 48px; padding: 0.85rem 0; text-align: left;
    border: 0; border-bottom: 1px solid var(--line-dark); border-radius: 0;
    background: transparent; color: var(--paper);
  }
  .type-more--dark[data-onbooks] a .n { color: var(--gold); }
  .type-more--dark[data-onbooks] a:hover { background: transparent; color: var(--gold); }
}

/* ---- 36.4 A listing on a phone shows its photographs on the first screen */
/* script.js (seatPhotos) moves the rail into the head under 641px; 36.6
   places it. The jump nav's Photos link would point back up the page, so it
   stands down there. */
@media (max-width: 640px) {
  .jumpnav a[href="#photos"] { display: none; }
}

/* ---- 36.5 What only works beside something stays off a phone ----------- */
/* The FAQ photograph is the column beside the questions from 901 up. Stacked
   under them it was a 210px picture of a vine row between the last answer
   and the footer, saying nothing. The listing's breadcrumb repeated the name
   the headline says 60px lower, over two lines: the phone keeps the way back. */
@media (max-width: 640px) {
  .faq-photo { display: none; }
  .band--crumbs .crumb > span[aria-hidden]:nth-last-child(2),
  .band--crumbs #crumb-now { display: none; }
}

/* ---- 36.6 The first phone screen of a listing: name, price, photograph -- */
/* Look loop round 16 and the reviewer: with the rail above the head the name
   and price fell under the call dock, and hiding the captions to lift them
   took away the caption under every photograph the depth pass put there.
   The rail now sits in the head straight after the price: at 390x664 the
   name, the price and the lead photograph share the first screen and every
   caption stays under its picture. Place, facts count and tools follow. */
@media (max-width: 640px) {
  .detail-head .detail-price-block { order: 1; }
  .detail-head > #photos.is-lead { order: 2; padding-inline: 0; margin: 1.25rem 0 0.25rem; }
  .detail-head .detail-place,
  .detail-head .detail-meta { order: 3; }
  .detail-head .detail-actions { order: 4; }
}

/* ---- 36.7 The footer's two link columns keep one row line -------------- */
/* "Commercial property 1" needs 167px in a 157px column, so it wraps and every
   row under it sat 25px off its Site neighbour. The columns share the grid's
   rows; script.js sets --foot-rows from the longer list (the book list grows
   with stock). Without subgrid the footer is as it was. */
@media (max-width: 640px) {
  @supports (grid-template-rows: subgrid) {
    .footer-grid > :nth-child(2),
    .footer-grid > :nth-child(3) {
      display: grid; grid-row: span var(--foot-span, 6);
      grid-template-rows: subgrid; row-gap: 0; align-content: start;
    }
    .footer-grid > :nth-child(2) > h3,
    .footer-grid > :nth-child(3) > h3 { justify-self: start; }
    .footer-grid > :nth-child(2) > ul,
    .footer-grid > :nth-child(3) > ul {
      display: grid; grid-row: span var(--foot-rows, 5);
      grid-template-rows: subgrid; row-gap: 0;
    }
  }
}

/* ---- 36.8 The person card reads at body size (look loop 16) ------------ */
/* 14px was off the scale, and under the footer's 16px for the same number
   and address on the same screen. */
.agent-line { font-size: var(--fs-body); }

/* ---- 36.9 A phone held sideways (reviewer, 2026-09-12) ------------------ */
/* At 750x342 the header and the call dock leave about 217px, and the listing
   spent it on a two-line crumb and the place line: the price sat under the
   dock. Sideways the rail stays where the tablet has it (a 4:3 slide at that
   width is taller than the screen); the crumb drops the repeated name and the
   price follows the name, as on a phone held upright. */
@media (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
  .band--crumbs .crumb > span[aria-hidden]:nth-last-child(2),
  .band--crumbs #crumb-now { display: none; }
  .band--crumbs { padding-top: calc(var(--header-h) + 1rem); }
  .detail-head .detail-price-block { order: 1; margin-top: 0.5rem; }
  .detail-head .detail-place,
  .detail-head .detail-meta { order: 2; }
  .detail-head .detail-actions { order: 3; }
}

/* ==========================================================================
   37. The listing card is a card, 2026-09-13
   Edgar on the phone card: "it just now needs to stand out and actually
   looking like a card, right now its on top of the background". Section 35
   set it straight on the page with no box, and the desktop card was a white
   box on a ground 3% darker with an 8% line and no shadow until hover, so it
   read the same way. What the portals and the agencies do (Property24,
   Zillow, Rightmove, Sotheby's International Realty, read 2026-09-13): a
   white card lifted off the ground by a line and a soft shadow, the
   photograph edge to edge across its top, the words set in from the card's
   edge, the last row divided off as the card's foot. One surface for the
   listing card at every width. What is on Card 1 does not change.
   ========================================================================== */
.farm-card {
  --card-r: var(--r-lift);
  /* Three layers, the way a real object sits on paper: a tight contact
     shadow, a short one that shades the sides, a long soft one under it.
     One layer at 36% left the sides of a white card on the canvas without
     an edge (trialled side by side, 2026-09-13). */
  --card-lift: 0 1px 2px color-mix(in srgb, var(--ink-deep) 8%, transparent),
               0 4px 12px -6px color-mix(in srgb, var(--ink-deep) 14%, transparent),
               0 18px 40px -20px color-mix(in srgb, var(--ink-deep) 42%, transparent);
  background: var(--paper);
  border: 1px solid color-mix(in srgb, var(--ink) 11%, transparent);
  border-radius: var(--card-r);
  box-shadow: var(--card-lift);
}
/* The hover lifts from a resting shadow now, so it has to go further than
   the old hover did from none. Only where a pointer can hover: a tap left
   a phone's card raised until the next tap elsewhere. */
@media (hover: hover) {
  .farm-card:hover,
  .js .farm-card.reveal.is-visible:hover {
    border-color: color-mix(in srgb, var(--ink) 18%, transparent);
    box-shadow: 0 1px 2px color-mix(in srgb, var(--ink-deep) 8%, transparent),
                0 6px 16px -8px color-mix(in srgb, var(--ink-deep) 18%, transparent),
                0 28px 52px -24px color-mix(in srgb, var(--ink-deep) 52%, transparent);
  }
}
/* On a touch screen the older hover rules (sections 8, 29 and 33.32) still
   answered a tap: the card rose 4px, swapped its three shadows for their
   one, turned its name gold and zoomed its photograph, and stayed that way
   until the next tap elsewhere (look round 21); the heart stayed 8% larger
   (reviewer, 2026-09-13). A touch card stays at rest and answers the press
   instead. */
@media (hover: none) {
  .farm-card:hover,
  .js .farm-card.reveal.is-visible:hover {
    border-color: color-mix(in srgb, var(--ink) 11%, transparent);
    box-shadow: var(--card-lift);
    transform: none;
  }
  .farm-card:hover .farm-card__title { color: var(--ink); }
  .farm-card:hover .farm-card__media img { transform: none; }
  .farm-card .save-btn:hover:not(:active) { transform: none; }
  /* The swipe card never took the press (35): a thumb on its photographs
     is a swipe, and the card shrinking under it would fight the drag. */
  .js .farm-card.reveal.is-visible:not(.farm-card--swipe):active { transform: scale(0.992); }
}
.js .farm-card.reveal.is-visible {
  transition: transform 120ms var(--ease-soft), box-shadow var(--t-mid) var(--ease-soft),
              border-color var(--t-mid) var(--ease-soft);
}

@media (max-width: 640px) {
  /* Cards with edges of their own stand closer: 3rem was the space that
     told one farm from the next when nothing else did. */
  .farm-grid:has(> .farm-card--swipe) { row-gap: 1.5rem; }
  /* The cards run to 16px from the screen's edge, as the portals' do, and
     the words inside them land close to the page's own text line. Inside
     the 28px gutter a padded card left "Boesmans River" and "Total extent"
     on two lines at 375 (281px for a row that needs 305), and the
     photograph 24px narrower. */
  .farm-grid:has(> .farm-card--swipe),
  .farm-grid:has(> .farm-card--skeleton) {
    margin-inline: calc(max(16px, var(--safe-left), var(--safe-right)) - var(--gutter));
  }
  /* The listings page's placeholders are the phone card's shape: a square
     photograph under the card's top corners, the same inset and gap. The
     old 433px placeholder with its 237px 3:2 frame moved every photograph
     when the list landed (look round 21). */
  .farm-grid:has(> .farm-card--skeleton) { row-gap: 1.5rem; }
  .farm-card--skeleton { --card-pad: 1rem; overflow: hidden; }
  .farm-card--skeleton .farm-card__media {
    aspect-ratio: 1 / 1;
    border-radius: calc(var(--card-r) - 1px) calc(var(--card-r) - 1px) 0 0;
  }
  .farm-card--skeleton .farm-card__body { padding: var(--card-pad) var(--card-pad) 0; }
  .farm-card--skeleton .skel--meta { margin-inline: calc(var(--card-pad) * -1); }
  .farm-card--swipe { --card-pad: 1rem; }
  /* 0.6rem either side of each hairline, from 0.7: the last few pixels the
     arable land's row needs at 375. */
  .farm-card--swipe .farm-card__meta > span:nth-child(n + 2) { margin-left: 0.6rem; padding-left: 0.6rem; }
  .farm-card--swipe,
  .farm-card--swipe:hover,
  .js .farm-card--swipe.reveal.is-visible:hover {
    background: var(--paper);
    border: 1px solid color-mix(in srgb, var(--ink) 11%, transparent);
    border-radius: var(--card-r);
    overflow: hidden;
    box-shadow: var(--card-lift);
    transform: none;
  }
  /* The photographs run to the card's edge and take its top corners. The
     clip stays on the frame itself for Safari (see 35). */
  .farm-card--swipe .farm-card__media {
    border-radius: calc(var(--card-r) - 1px) calc(var(--card-r) - 1px) 0 0;
    clip-path: inset(0 round calc(var(--card-r) - 1px) calc(var(--card-r) - 1px) 0 0);
  }
  .farm-card--swipe .farm-card__body { padding: var(--card-pad) var(--card-pad) 0; }
  /* The place wraps at a comma rather than leaving one word under a line. */
  .farm-card--swipe .farm-card__place { text-wrap: balance; }
  /* Compare and the fact count are the card's foot: the hairline runs the
     full width of the card, the way a portal card divides off its last row. */
  /* 0.3 and 0.35rem: at 0.15 the card's clip cut the bottom of Compare's
     focus ring, which needs 4px below the control (look round 21). */
  .farm-card--swipe .farm-card__foot--sub {
    margin: 1.1rem calc(var(--card-pad) * -1) 0;
    padding: 0.3rem var(--card-pad) 0.35rem;
  }

  /* Similar farms under a listing are the same card cut short (34.15): the
     same width, the same inset and the same divided foot, where they had
     an 18px gap under Compare and no line above it. */
  #similar-farms .farm-grid {
    margin-inline: calc(max(16px, var(--safe-left), var(--safe-right)) - var(--gutter));
    gap: 1rem;
  }
  #similar-farms .farm-card { --card-pad: 1rem; }
  #similar-farms .farm-card__body { padding: var(--card-pad) var(--card-pad) 0; }
  #similar-farms .farm-card__place { text-wrap: balance; }
  #similar-farms .farm-card__foot--sub {
    margin: 0.9rem calc(var(--card-pad) * -1) 0;
    padding: 0.3rem var(--card-pad) 0.35rem;
    border-top: 1px solid var(--line-soft);
  }
}

/* ==========================================================================
   38. /better pass, 2026-09-13 (after the card became a card)
   Each block carries the number of the fault it answers in docs/QA-REPORT.md
   ("/better pass 2026-09-13"). The phone card (35, 37) set the grammar; the
   tablet and desktop card still spoke an older one.
   ========================================================================== */

/* ---- 38.1 One set of chips at every width (faults 1 to 4) -------------- */
/* Above 640 the reference sat on the photograph in a dark chip with a 6px
   blur (frosted glass, on the ban list), the type in a gold chip, the heart
   at the photograph's foot; on a phone the type is a paper chip and the
   heart sits beside it. At 768 and in the similar farms at 1280 the two
   chips could not share a row and the type dropped under the reference.
   Every card now carries the phone's corner: the type (and a status, when a
   farm has one) in paper at the top left, the heart on their centre line at
   the top right. The reference is on the schedule. */
.farm-card__ref { display: none; }
.farm-card__type,
.farm-card__status {
  background: var(--paper); color: var(--ink);
  backdrop-filter: none;
  padding: 0.4rem 0.6rem 0.36rem;
  letter-spacing: 0.1em;
  box-shadow: 0 1px 4px -1px color-mix(in srgb, var(--ink-deep) 30%, transparent);
}
.farm-card__status { position: static; }
.farm-card__status[data-new] { background: var(--gold); }
@media (min-width: 641px) {
  .farm-card__chips {
    top: 14px; left: 14px; right: 72px;
    min-height: 44px; align-items: center;
    justify-content: flex-start;
  }
  .farm-card .save-btn {
    top: 14px; right: 14px; bottom: auto;
    box-shadow: 0 2px 8px -2px color-mix(in srgb, var(--ink-deep) 40%, transparent);
  }
}

/* ---- 38.2 One way into a listing, not three (fault 5) ------------------ */
/* Above 640 every card carried "View the schedule" under its figures: a
   third link to the page the photograph and the name already open (the
   name's link covers the whole card, 30.2), a fifth Tab stop per card and a
   60px band. The phone card never had it. */
.farm-card__foot:not(.farm-card__foot--sub) { display: none; }

/* ---- 38.3 Figure labels in one type at every width (faults 6 and 7) ----- */
/* Above 640 the labels were 13px tracked capitals in the display face, each
   holding two lines of height whether it needed them or not (min-height
   2.4em, 29), and "Homes on the farm" still broke in two at 1280. The phone
   card's labels are plain body type on one line (the pick's rule: shorten
   the label type before letting a figure row wrap). */
.farm-card__meta i {
  min-height: 0;
  font-family: var(--body); font-size: var(--fs-label);
  letter-spacing: 0; text-transform: none;
}
/* A card 400px or narrower (two across at 768, the similar farms from 1101,
   where they stop being rows, to 1280) stacked its three figures into three 53px rows, 160px of grey table.
   It takes the phone card's one line instead: value over label, hairlines
   between, each figure as wide as its own words. Up to 470px, not 400: from
   401 the three equal columns came back and "On-site licence" and "Homes on
   the farm" broke in two (the similar farms at 1366 to 1920, listings two
   across at 900 to 960; look round 23). */
@container card (max-width: 470px) {
  .farm-card:not(.farm-card--swipe) .farm-card__meta {
    display: flex; flex-wrap: nowrap; align-items: stretch;
    padding-block: 0.9rem;
  }
  .farm-card:not(.farm-card--swipe) .farm-card__meta > span:nth-child(n) {
    flex: 0 1 auto; min-width: min-content;
    flex-direction: column; justify-content: flex-start; align-items: flex-start;
    gap: 0.3rem;
    margin: 0 0 0 0.6rem; padding: 0 0 0 0.6rem;
    border: 0; border-left: 1px solid var(--line);
  }
  .farm-card:not(.farm-card--swipe) .farm-card__meta > span:first-child { margin-left: 0; padding-left: 0; border-left: 0; }
  .farm-card:not(.farm-card--swipe) .farm-card__meta b { text-align: left; line-height: 1.15; }
}

/* ---- 38.4 One price size (faults 8 and 9) ------------------------------ */
/* The card's price ran 24px on a phone, 18.4 on the similar farms beside it,
   19.2 at 1280 and 21.6 at 1440: smallest on the widest card, and smaller
   than the name it sits beside. 24px everywhere, as the phone polish set it;
   the comparison's head keeps "a price like any card's" (34.21). */
.farm-card__price { font-size: 1.5rem; line-height: 1.1; }
@media (min-width: 801px) {
  .cmp-head .p { font-size: 1.5rem; }
}

/* ---- 38.5 The place line breaks at a comma at every width (fault 10) --- */
/* At 768 three of four cards left "Western Cape" alone on a second line. */
.farm-card__place { text-wrap: balance; }

/* ---- 38.6 The heart keeps no hover colour after a tap (fault 11) ------- */
/* At 768 on touch, a heart tapped on and off again stayed gold (text and
   border) as a sticky hover, reading as saved when it was not. */
@media (hover: none) {
  .farm-card .save-btn:not([aria-pressed="true"]):hover { color: var(--ink); border-color: transparent; }
}

/* ---- 38.7 The placeholders are the card that lands (fault 12) ---------- */
/* Above 640 the three placeholders were three across with a 3:2 frame (379
   by 630 at 1280) and the four farms land two across with a 4:3 frame (584
   by 751): every photograph moved when the list arrived. listings.html now
   holds four, so they sit two across (33.10), and the frame is 4:3. */
/* The bodies hold the height of the words that land in them, measured on
   the real cards: 308px on the phone card, 332 two across at 768, 239 from
   901 up. */
.farm-card--skeleton .farm-card__body { min-height: 15rem; }
@media (max-width: 640px) {
  .farm-card--skeleton .farm-card__body { min-height: 19.25rem; }
}
@media (min-width: 641px) {
  .farm-card--skeleton .farm-card__media { aspect-ratio: 4 / 3; }
}
@media (min-width: 641px) and (max-width: 900px) {
  .farm-card--skeleton .farm-card__body { min-height: 20.75rem; }
}

/* ---- 38.8 An email address is not set in capitals (fault 13) ----------- */
/* About's second button spelt MARTIENS@ERFDEVCO.COM, tracked, which is not
   how anyone types it or reads it back. The rule that fixed it went on
   2026-09-15: section 42 took the capitals off every button, and it lost to
   section 42's label list anyway. */

/* ---- 38.9 A listing's key facts keep their value whole (fault 14) ------- */
/* At 390 "Access off main road" and "Gate in place" both broke in two: the
   two sides shared the row equally. The value keeps its natural width (up to
   60% of the row) and the label takes what is left. */
@media (max-width: 640px) {
  .factgrid.factgrid--rest dd { flex: 1 1 0; min-width: 0; }
  .factgrid.factgrid--rest dt { flex: 0 1 auto; max-width: 60%; }
}

/* ---- 38.10 One hover, in one time (motion audit, faults 15 and 16) ---- */
/* A pointer on a card set off four things at once: the card rose 4px in
   120ms, its shadow deepened over 320ms, the name went gold in 180ms and the
   photograph crept to 104% over 900ms, still moving a second after the
   hand had stopped. The portals answer a hover with the lift alone. The
   card keeps the lift, the shadow and the gold name, all in the site's fast
   step, and the photograph stays still (type tiles and the gallery keep
   their zoom, 33.32: there the photograph is the whole control). */
@media (hover: hover) {
  .farm-card:hover .farm-card__media img { transform: none; }
}
.js .farm-card.reveal.is-visible {
  transition: transform var(--t-fast) var(--ease-soft), box-shadow var(--t-fast) var(--ease-soft),
              border-color var(--t-fast) var(--ease-soft);
}

/* ---- 38.11 A figure row wraps before it is cut (audit 1, WCAG 1.4.4) --- */
/* The one-line figure row (35, 38.3) cannot wrap, and the card clips (37),
   so with a phone's text at 175% "Boesmans River" lost 58px, and 55 to
   139px at 200% on phones and at 768. The query is in em, which follows the
   card's own text size: at the default size every card is wider than 17em
   and the row holds its one line (the arable land's needs about 16.5em at
   its narrowest); enlarged text makes the same card narrower in em, and the
   row wraps instead of losing words. */
@container card (max-width: 17em) {
  .farm-card--swipe .farm-card__meta,
  .farm-card:not(.farm-card--swipe) .farm-card__meta { flex-wrap: wrap; row-gap: 0.75rem; }
}

/* ---- 38.12 An email address may break at 200% text (audit 3) ----------- */
/* "martiens@erfdevco.com" in the agent line could not break, and a listing
   and the privacy page scrolled sideways at 390 (434px). */
.agent-line a { overflow-wrap: anywhere; }

/* ---- 38.13 Reception's bubble stands down while someone types (audit 7) */
/* It sat over the corner of a listing's enquiry message box at 1280x900 and
   1366x768. It also checks textareas now before it shows (chat-widget.js). */
body:has(:is(input, textarea, select):focus) .chat-teaser { visibility: hidden; pointer-events: none; }

/* ---- 38.14 The header fits a phone at 200% text (1.4.4) ---------------- */
/* At 175% text on 320 and 200% on 360 and 375 the mark and the Menu button
   ran 15 to 25px past the screen and every page scrolled sideways. The
   header measures itself in its own em: when enlarged text leaves it under
   15em, the word Menu is kept for a screen reader and the bars carry it on
   screen. At the default size every phone shows the word. */
.header-inner { container: hdr / inline-size; }
@container hdr (max-width: 15em) {
  .nav-toggle__label {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

/* ---- 38.15 The requirement box's rows wrap at 200% text (1.4.4) -------- */
/* "Province / Anywhere in South Africa" could not fit its row at 175% text
   on 320 and 200% on 360 and 375, and the listings page scrolled sideways
   by 15 to 25px. A value that no longer fits beside its label drops under
   it, still set to the right. */
.reqbox__rows > div { flex-wrap: wrap; row-gap: 0.2rem; }
.reqbox__rows dd { margin-left: auto; }

/* ---- 38.16 The idle map tip never widens a phone (1.4.4) --------------- */
/* Its 12rem floor beat its own 78vw ceiling once text was enlarged (384px
   at 200%), and the hidden tip ran the listings page 3 to 27px wide at 320
   and 360. The floor now gives way to the screen too. */
.maptip { min-width: min(12rem, 60vw); }

/* ---- 38.17 The bond's sums stay whole at enlarged text (1.4.4) ------- */
/* The grid's one column took its rows' min-content, so at 375 with text at
   200% "Deposit R2 600 000" ran 50px past the ink card that clips it. And
   each row was nowrap (34.11) with a label allowed to shrink to nothing, so
   at 320 with 150 to 175% text the label printed under its figure (28 to
   91px of overlap, reviewer). The column gives way; a row wraps only when
   the label's longest word and the figure cannot share it, and then the
   figure takes its own line at the right. At the default size nothing
   moves. */
.bond-sum { grid-template-columns: minmax(0, 1fr); }
.bond-sum > div { min-width: 0; flex-wrap: wrap; row-gap: 0.2rem; }
.bond-sum > div > dt { flex: 1 1 0; min-width: min-content; }
.bond-sum dd { margin-left: auto; }

/* ---- 38.18 A text link with a count in it can wrap (1.4.4) ------------- */
/* "See all 4 listings" is three flex items (the count is its own span), so
   the line could not break and ran 22px off a 320 screen at 200% text. */
.link-line { flex-wrap: wrap; }

/* ---- 38.19 The first photograph's focus ring is whole (look round 23) --- */
/* Under 901 the gallery is a scrolling rail, and its clip cut the top 6px
   off the ring on the first photograph at 390 and 768. The rail takes the
   room above the photographs back from its own margin, so nothing moves. */
@media (max-width: 900px) {
  .gallery { padding-top: 0.5rem; margin-top: -0.5rem; }
}

/* 39. The kinds of land band (/bolder, 2026-09-15) was removed the same day
   at Edgar's word: "remove that section dont like it". */

/* ==========================================================================
   40. The card's ground, 2026-09-15
   Edgar: "the farm cards still doesn't have a different background to the
   section it should look like a card". Section 37 gave the card its line
   and its shadow, but it is white on a canvas only 3% darker (246, 244,
   239), so the eye still read one ground. The portals put a white card on
   a grey page; every band that holds farm cards now sits on --stone-2
   (ink 9% into the canvas), the step a white card needs to read as an
   object. Sage was trialled side by side and lost: on the home page it
   runs straight into the kinds band and on listings into the province band
   and the register band, one long sage ground with nothing to step. Covers
   the home page's mandates, the listings grid and its loading
   placeholders, and the similar farms under a listing.
   ========================================================================== */
.band:has(.farm-grid) { background: var(--stone-2); }
/* The similar farms carried no top padding because they ran on from the
   bond band on the same ground. On a ground of their own the heading
   needs the listing's one seam above it (34.2), the same as below. */
#listing-detail #similar-farms.band:has(.farm-grid) { padding-top: calc(var(--band) * 0.6); }
/* --gold-deep clears 4.5:1 on the canvas and on --stone, not on --stone-2:
   4.22:1 on the listings' "Search" and on "Clear all", which only shows
   once a filter is set (reviewer, 2026-09-15). --gold-dark, the sage
   band's answer, gives 6.12:1; Clear all's underline follows its colour. */
.band:has(.farm-grid) .findbar label,
.band:has(.farm-grid) .toolbar__clear { color: var(--gold-dark); }
.band:has(.farm-grid) .toolbar__clear:hover { color: var(--ink); }
/* The placeholder was muted at 72%: 3.84:1 on the canvas, 3.49:1 on the
   grey. At full muted it clears 4.5 and still reads lighter than typed ink. */
.band:has(.farm-grid) .findbar input::placeholder { color: var(--muted); }

/* ==========================================================================
   41. Edgar's screenshot notes on the home page, 2026-09-15
   1. The hero pause is gone, and the hero's movement with it (section 6,
      index.html, script.js).
   2. The search rail is shallower and the hero taller by the same amount.
   3. The kinds of land band is gone (section 39).
   4 and 6. The agency, country and five questions bands take the whole
      band token above and below instead of the 0.6 seam.
   5. The closing band's copy no longer animates on scroll (index.html,
      script.js).
   ========================================================================== */

/* 2. "make the search bar more shallow, less top and bottom spacing, so
   stretch the hero background to make up for that". The rail was 120px at
   1440 around 70px of content, and is 88 now. The hero gives up exactly
   --rail-h (33.35), so a smaller token is also a taller hero. The selects
   keep their 44px. */
.searchrail__field { justify-content: center; row-gap: 0.2rem; padding-block: 0.6rem; }
@media (min-width: 901px) {
  :root { --rail-h: 5.5rem; }
}
/* Tablet: three fields over a full-width Search, 142px of content (was 176). */
@media (min-width: 641px) and (max-width: 900px) {
  :root { --rail-h: 9rem; }
}

/* 4 and 6. The one-seam rule (34.2) gave every seam 0.6 of the band; Edgar
   asked for more air above and below these three bands. Each selector
   restates the rule it has to beat: the light pair (30.11, 33.3), which
   would also have turned the agency band canvas now that it follows the
   farm cards' grey, and the map band (34.2). The five questions band
   already ended on the whole token; its top now matches. */
main > section.band--roomy,
main > .band:not(.band--sage):not(.band--ink) + .band--surface.band--roomy,
main > section.band--tight.band--roomy:has(> .shell > .country) { padding-block: var(--band); }
main > .band:not(.band--sage):not(.band--ink) + .band--surface.band--roomy { background: var(--paper); }

/* ==========================================================================
   42. The display face is Bitter, in sentence case, 2026-09-15.
   Edgar ran /bolder, then: "3 different types of fonts, maybe more bold fonts
   and not all in capitals". Three were built site wide and shown as whole
   pages (docs/DESIGN-DIRECTION.md "Options: the typeface"); he picked TYPE 3,
   the working land: one sturdy slab for every display role, buttons
   included. Fraunces (TYPE 1) and Schibsted Grotesk (TYPE 2) lost.

   The display face had three jobs, each set in Tenor Sans capitals:
   HEAD  the headings and heading-sized words: Bitter 700, sentence case
   FIG   prices, figures and questions: Bitter 700
   LAB   buttons, nav, form labels, chips, footer heads: Bitter 600, sentence
         case, 0.01em tracking (they were capitals tracked 0.09 to 0.16em)
   These rules sit on top of the Tenor-era rules above rather than rewriting
   62 of them; :root in front of each list keeps the specificity the option
   ran at. Tracking and weight ride on custom properties (--trk, --wt-*),
   which only this section sets, so the size steps need no specificity fight.
   ========================================================================== */
:root {
  --wt-head: 700; --wt-fig: 700;
  /* Never tightened. Was -0.025em (h1, h2, hero) and -0.012em, carried over
     from Tenor's capitals; at 700 "ft" and "rs" touched. Edgar, 2026-09-15:
     "more spacing in between the heading letters, it shouldn't touch". At
     0.01em no pair touches at 1280 (measured on the hero, 84.48px), and the
     headings match the labels' 0.01em. */
  --trk-l: 0.01em; --trk-s: 0.01em;
  --label-face: var(--display); --wt-lab: 600; --lab-case: none; --trk-lab: 0.01em;
}
:root :is(h1, h2, h3, h4, h5, .hero__title,
  .searchrail__field select, .cmp-head .t, .maptip .t, .cmp-mini .t,
  .cmp-pick__txt .t, .farm-card--swipe .farm-card__more-txt b) {
  text-transform: none;
  font-weight: var(--wt-head);
  letter-spacing: var(--trk, var(--trk-s));
}
:root :is(h1, h2, .hero__title) { --trk: var(--trk-l); }
:root :is(.farm-card__price, .farm-card__meta b,
  .stat-strip dt, .country__facts dt, .cband a, .cband b, .counter__n,
  .detail-price, .factgrid dt, .schedule-meter b, .bond-big, .bond-sum dd,
  .headline-facts dt, .offer-notes .offer-notes__fig, .findbar input,
  .bond-f output, .faq-card summary, .reqbox__rows dd, .typemenu__opt,
  .cmp-head .p) {
  font-weight: var(--wt-fig);
  /* Figures keep a hair of tightening: at 0 the arable land's phone figure
     row wrapped at 375 (89px against 71). Edgar's "shouldn't touch" was
     about the heading letters. */
  letter-spacing: -0.01em;
}
:root :is(.eyebrow, .btn, .link-line, .nav-list a,
  .nav-toggle__label, .searchrail__field label, .searchrail__submit,
  .farm-card__type, .farm-card__status, .save-btn--inline,
  .toolbar label, .typemenu__label, .typemenu__title, .filter-btn,
  .reqbox__label, .reqbox__rows dt, .stat-strip dd, .cband span,
  .counter__label, .type-more a, .credentials li, .crumb, .detail-perha,
  .factgrid dd, .spec-tab, .field label, .site-footer h3, .nav-extra__k,
  .cmp-toggle, .cmp-tray__label, .cmp-clear, .cmp-sec th, .cmp-switch,
  .findbar label, .provlist button, .bond-f > span, .bond-lab, .bond-sum dt,
  .cmp-table tbody th, .headline-facts dd, .toolbar__clear, .jumpnav a,
  .chat-fab__label, .chat-head strong, .chat-lead span, .chat-lead-go,
  .actionbar__btn, .offer-notes dt, .type-tile__stock, .row-item__out span,
  .row-add, .farm-card__ref, .farm-card__facts,
  .type-tile__count, .lb-count, .cmp-head .r, .maptip .r, .cmp-cta__list .r,
  .cmp-pick__txt .r, .chat-badge,
  .farm-card--swipe .farm-card__more-txt > span) {
  font-family: var(--label-face);
  font-weight: var(--wt-lab);
  text-transform: var(--lab-case);
  letter-spacing: var(--trk-lab);
}
:root .cmp-table tbody tr.is-stacked td::before {
  font-family: var(--label-face); font-weight: var(--wt-lab);
  text-transform: var(--lab-case); letter-spacing: var(--trk-lab);
}
/* Not in the lists on purpose (look round 31): the phone card's figure labels
   (.farm-card__meta i, body type by 38.3 so the row stays one line) and what a
   visitor sets on the listings toolbar (.toolbar select, .typemenu__btn, body
   type by 34.10, one face for every chosen value), and the compare pack list
   (.cmp-cta__list > li, body type by 6148; 700 is not in the Instrument Sans
   file). The email buttons are Bitter like every other button: with no
   capitals left, the old mailto exception (38.8) was deleted. */

/* Capitals were 9px tall at 13px, lowercase is 7px: two labels shrank when
   the capitals went (round 31.6). The footer heads now match the links under
   them, and the desktop nav gets back most of its old width. */
:root .site-footer h3 { font-size: 1rem; }
@media (min-width: 901px) {
  :root .nav-list a { font-size: 0.9375rem; }
}

/* The compare head reserved a second line for a capitals title (2.6em); in
   sentence case every name fits one, which left 31px between name and place.
   The place drops to the foot instead, so prices still line up (round 31.4). */
@media (min-width: 641px) {
  .cmp-head .t { min-height: 0; }
  .cmp-head .l { margin-top: auto; }
  .cmp-head .p { margin-top: 0; }
}
/* A reference code never breaks at its hyphen ("ERF-DLD-" over "102"). */
.cmp-cta__list .r { white-space: nowrap; }

/* The listings empty state (look round 33). Its button filled the row in
   capitals and now sat off centre; and the box itself was one card slot wide
   (its 1 / -1 lost to the even grid's span 2 !important) and 630px tall,
   stretched by the 70vh the grid reserves until the farms arrive. Full width,
   its own height, text column and button centred. */
.farm-grid--even > .farm-empty { grid-column: 1 / -1 !important; }
#all-farms:has(> .farm-empty) { min-height: 0; }
.farm-empty > * { margin-inline: auto; }
.farm-empty .btn-row { justify-content: center; }

/* The phone hero (34.12) divided by 6.25 because "Farms for" in Tenor
   capitals was 6.06 times its size. In Bitter sentence case at 0.01em
   tracking it is about 4.69, so the title takes the 4.25rem cap at 390 (68px)
   and still keeps "Farms for" on one line at 360. index.html ties "Farms&nbsp;for" so the first line
   never breaks as "Farms" alone. */
@media (max-width: 640px) {
  :root .hero__title {
    font-size: min(calc((100vw - 2 * var(--gutter)) / 4.9), 4.25rem);
  }
}

/* ==========================================================================
   43. The card's fact strip in the company gold (Edgar, 2026-09-15)
   ========================================================================== */
/* First "a colour of the company, maybe the header background colour", so the
   strip went --ink-deep. Then, the same evening: "the strip on the property
   cards make it the company orange and keep both text black." It now carries
   the button gold with the button's own pairing, dark type on gold (labels
   and figures both --ink-deep, 6.5:1), and hairlines drawn in ink. The
   hairlines are re-pointed rather than re-declared, so every layout above
   (three columns, rows under 420, the one-line phone row) keeps its geometry
   and only changes colour. */
.farm-card__meta {
  --line: color-mix(in srgb, var(--ink-deep) 22%, transparent);
  --line-soft: color-mix(in srgb, var(--ink-deep) 22%, transparent);
  background: var(--gold);
  border-top-color: transparent;
}
.farm-card__meta b,
.farm-card__meta i { color: var(--ink-deep); }
.skel--meta { background: var(--gold); }
/* The phone card's row sat on the white card with no ground; it now
   runs edge to edge like the wider cards, inset by the card's own padding. */
.farm-card--swipe .farm-card__meta {
  background: var(--gold);
  border-top-color: transparent;
  margin-inline: calc(var(--card-pad, 1rem) * -1);
  padding: 0.95rem var(--card-pad, 1rem);
}
/* The foot's gap and hairline divided the white card from its last row; with
   the strip coloured they left a stray white ledge, so the foot sits straight
   under the strip as it does on the wider cards (5839). */
.farm-card--swipe .farm-card__meta ~ .farm-card__foot--sub {
  margin-top: 0;
  border-top-color: transparent;
}

/* ==========================================================================
   44. The phone hero keeps "South Africa" on one line (Edgar, 2026-09-15)
   ========================================================================== */
/* "Where 'South Africa' is should be in 1 line, currently it's in two. Solve
   with making the text size smaller." This reverses 34.12's line each for
   "South" and "Africa". In Bitter 700 at 0.01em "South Africa" measures 6.05
   times its size, so the title divides the text column by 6.2: 54px at 390,
   49px at 360, still capped at 4.25rem. Desktop and tablet are untouched. */
@media (max-width: 640px) {
  :root .hero__title {
    font-size: min(calc((100vw - 2 * var(--gutter)) / 6.2), 4.25rem);
  }
  :root .hero__title .gold > span { display: inline; }
}

/* ==========================================================================
   45. /better pass, 2026-09-16
   The fault list this answers is at the end of docs/QA-REPORT.md under
   "/better pass 2026-09-16". Every rule restates the rule it has to beat, the
   way sections 41 and 42 do, so the cascade stays readable.
   ========================================================================== */

/* ---- 45.1 One band rhythm, on every page (faults 6 to 10) ---------------- */
/* Edgar, 2026-09-15: a content band takes the whole band token above and below,
   and a band's top matches its bottom. Section 34.2 had put every seam at 0.6
   of the token for the same-ground rule of 2026-09-04, and section 41 lifted
   three home-page bands back to the full token one selector at a time.
   Measured 2026-09-16: the home page's own mandates band was still at 74.3px
   against its neighbours' 123.8, the other five pages were all at 74.3, and
   their closing band ran 74.3 top against 123.8 bottom. No same-ground
   neighbour is left on the site, so the compression applied to nothing it was
   written for. The whole token is the rule; the compression is kept below
   for the day two identical grounds meet again. */
main > section + :is(.band, .band--surface, .band--sage, .band--tight),
main > .band:not(.band--sage):not(.band--ink) + .band--surface,
main > section.band--tight,
main > section.band--tight:has(> .shell > .country),
main > section.band--tight:has(> .shell > .mapbox) {
  padding-top: var(--band);
}
main > :is(.band, .band--surface, .band--sage, .band--tight):has(+ section),
main > section.band--tight,
main > section.band--tight:has(> .shell > .country),
main > section.band--tight:has(> .shell > .mapbox) {
  padding-bottom: var(--band);
}
main > .band--surface + .band--surface,
main > .band--sage + .band--sage { padding-top: calc(var(--band) * 0.4); }

/* A hero is flex-centred inside its min-height, so its padding only shows once
   the content outgrows the box: at 200% text, at 320px, on a long page title.
   There it was lopsided, 48px of air above the title against 96 below on the
   home page and 100.8 against 86.4 on the inner pages, both measured under the
   header's own height. Both read the same above and below now. */
.hero { padding-top: calc(var(--header-h) + clamp(3.5rem, 7vw, 6rem)); }
.page-hero { padding-bottom: clamp(3.5rem, 7vw, 6.5rem); }

/* ---- 45.2 The listings page had one ground under 68% of it (fault 1) ----- */
/* Read off the real painted pixels rather than off background-color: the map
   band L*86.2, the results band L*89.7 and the requirement band L*86.2 ran
   three light grounds in a row, 3.5 L* apart against a floor of 6, across
   3,090px of a 4,512px page. The results band has to stay the darker neutral,
   because Edgar's correction of 2026-09-15 put the white cards on a ground
   that steps. So the two bands around it move to paper, 10.3 clear on both
   sides, and the page reads dark, light, mid, light, dark. Re-pointing the
   token rather than the background carries the map's own province strokes and
   fill with it, which is why every piece of section 3's sage furniture still
   agrees with its ground. */
main > section.band--sage:has(> .shell > .mapbox),
main > section.band--sage:has(> .shell > .reqband) {
  --sage: var(--paper);
}

/* ---- 45.3 The listing page had one ground under 3,954px (faults 2, 3) ---- */
/* The crumb, the title, the price, the figure row, the jump nav, the whole
   photo gallery and the opening of "About this farm" all sat on canvas with no
   change of ground until the ink schedule band: 51% of the page. The gallery
   is the honest place to break it, because a photograph wants a ground behind
   it, and it steps 10 L* from the canvas on both sides. The bond band then
   moves to paper so the similar-farms band, which carries the card ground at
   L*89.7, is not sitting 6.5 from its neighbour. */
/* Only while the gallery is a band of its own. On a phone script.js seats it
   in the title block straight under the price (is-lead, the mobile pass of
   2026-09-12), and there a sage ground with band padding became a boxed panel
   with no edge inside the white head: a stray card. */
#listing-detail .band--gallery:not(.is-lead) {
  background: var(--sage);
  padding-block: calc(var(--band) * 0.8);
  margin-block: 0;
}
#listing-detail .band--gallery.is-lead { background: none; padding-block: 0; }
#listing-detail .band--gallery.is-lead > .shell { padding-inline: 0; }
/* The figure holds the caption as well as the picture, so a ground on the
   figure painted a pale strip under every photograph on the sage band. The
   loading ground belongs to the picture's frame only. */
#listing-detail .band--gallery:not(.is-lead) .gallery figure { background: none; }
#listing-detail .band--gallery:not(.is-lead) .gallery__frame { background: color-mix(in srgb, var(--ink) 8%, var(--sage)); }
#listing-detail .band--gallery:not(.is-lead) .gallery figcaption { color: color-mix(in srgb, var(--ink) 78%, var(--sage)); }
#listing-detail #bond.band { background: var(--paper); }

/* ---- 45.4 Rows start on one line (faults 12 to 16, 18) ------------------- */
/* Edgar, 2026-08-20, after five rounds on one band: "the rows start on the same
   height, and it's finished on the same height." A 44.6px heading on 50px of
   leading beside a 17px lede on 27.5px of leading cannot hold both edges, so
   the edge the eye actually checks wins. `align-items: end` was buying a finish
   within 8.5px and paying 43.6px at the top, which is the drop visible on the
   mandates band. Baseline alignment puts the heading's first line and the
   lede's first line on one line exactly, with no nudge anywhere. */
@media (min-width: 901px) {
  .section-head--split { align-items: baseline; }
}

/* The map and its column were centred against each other, which left the map
   29.2px off the text at its top and 29.2px off at its foot. Starting them
   together is only half an answer while the map is 58px taller than the column
   beside it, so the map's column narrows until the two columns are the same
   height and the row closes at both ends on its own. */
@media (min-width: 901px) {
  .country { grid-template-columns: minmax(0, 0.79fr) minmax(0, 1.21fr); align-items: start; }
}

/* ---- 45.5 No dead half in a row (faults 41, 42) -------------------------- */
/* about.html and contact.html both ran a list of rows where the left 55% of
   every row carried a 23px heading and nothing else: ban 11, and the ruling of
   2026-08-19 that a section of nothing but text rows is not a section. Section
   36's note records that the outcome line was tried under the title before and
   ran 65px past a four-line body. It did, at the old 1.15/0.85 split. At
   0.9/1.1 the title column is a title column rather than half a page, the
   body takes the measure prose actually wants, and the two sides close
   together; the numbers are in QA-REPORT.md. */
@media (min-width: 901px) {
  .shell > .rows > .row-item:has(> .row-item__out) {
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    grid-template-areas: "t b" "o b";
    grid-template-rows: auto 1fr;
    row-gap: 0;
    align-items: start;
  }
  /* The outcome sits under its title at one fixed distance on every row, so
     the four rows read as one system. Bottom-aligning it closed two rows to
     the pixel but gave the four a gap between title and outcome that ran from
     0 to 60px, which read as a mistake on the short ones. The rows start on
     one line; the residual at the foot is recorded per row in QA-REPORT.md.
     The rule that divided the outcome from the body in the old layout was a
     second line inside the row once it moved, so it goes. */
  .shell > .rows > .row-item > p.row-item__out {
    margin-top: 0.9rem; padding-top: 0; border-top: 0;
    align-self: start;
  }
  .shell > .rows > .row-item:has(> .row-item__out) .row-item__body p { max-width: 58ch; }
}

/* ---- 45.6 One value per role (faults 22, 23, 26) ------------------------- */
/* The type menu's button was the only Bitter 400 on a site whose display file
   carries 600 and 700, so the browser was synthesising a weight, at a size no
   other inline control uses. It takes the 13px/600 of the search rail's submit,
   the compare toggle and the schedule tabs. */
:root button.typemenu__btn {
  font-family: var(--display);
  font-size: 0.8125rem;
  font-weight: var(--wt-lab);
  letter-spacing: var(--trk-lab);
}
/* The receptionist's own two controls were the only buttons on the site set in
   the body face. */
:root .chat-teaser__open,
:root .chat-teaser__x {
  font-family: var(--display);
  font-weight: var(--wt-lab);
  letter-spacing: var(--trk-lab);
}
:root .chat-teaser__open { font-size: 0.875rem; }
/* The bond calculator's three slider labels were 16px body face while every
   other form label on the site is 13px display 600. */
/* The words are the span inside the label; the label itself also wraps the
   range input and its output, which must not inherit a label's size. */
:root label.bond-f > span {
  font-family: var(--display);
  font-size: 0.8125rem;
  font-weight: var(--wt-lab);
  letter-spacing: var(--trk-lab);
}
/* A recorded value read four ways: display 700 on a listing card, body face 700
   in the requirement box, body face 500 in the fact grid, body face 400 in the
   stat strip. A figure takes the display face; a sentence takes the body face
   at its one weight. */
:root .reqbox__rows dd { font-family: var(--display); }
:root .factgrid dd { font-weight: 400; }

/* Six line-heights were computing at 16px where two were meant: 1.60 for body
   copy, 1.72 for the quiet prose. 1.62, 1.55 and 1.70 were nobody's decision. */
:root .row-item__body p { line-height: 1.6; }
:root .row-item__out { line-height: 1.6; }
:root #listing-detail dd > p { line-height: 1.72; }

/* ---- 45.7 One shape per composition (fault 29) --------------------------- */
/* The offset pair had a square-cornered photograph with a 10px-cornered
   photograph lying on top of it. Both take the media radius now. */
.media-pair__main { border-radius: var(--r-media); overflow: hidden; }
.media-pair__main img { border-radius: var(--r-media); }

/* Between 641 and 900 the "inset" ran 413px wide against a 712px main and hung
   64px below it: the small photograph was the big one. It keeps its 54% of a
   main that is no longer nearly square. */
@media (min-width: 641px) and (max-width: 900px) {
  .media-pair__main { aspect-ratio: 1 / 0.66; }
  .media-pair__inset { width: 44%; }
}

/* ---- 45.8 The phone card's photograph is sharp at its square (33) ----- */
/* The card's frame is square, Edgar's pick of 2026-09-12, and every master is
   16:9, so a phone file has to be 1.78 times the frame wide to cover it. At
   92vw the browser took the 480w file and blew it up x1.32. The fix is in the
   sizes script.js asks for (STRIP_SIZES and the card's lead photograph, 165vw),
   not in the frame: a 4:3 frame was tried and taken back, because the square
   was part of the pick. */

/* ---- 45.9 Phone line breaks (faults 51, 52) ----------------------------- */
/* Two FAQ questions ended on a one-word line under 15% of the widest line
   ("...is it legal?", "...go on top?") and a listing's "Distance from Cape
   Town" ended on 27%. Balanced wrapping spreads the last line instead. */
.faq-card summary,
.factgrid dt,
.spec-group dt,
.headline-facts dd { text-wrap: pretty; }
@media (max-width: 640px) {
  .faq-card summary { text-wrap: balance; }
}

/* ---- 45.10 One number, one row (fault 47) ------------------------------- */
/* The contact column printed 082 900 5019 twice, once under Call and once
   under WhatsApp, which reads as two numbers until you look. One row now: the
   number is the row's figure, and the WhatsApp route sits under it as a small
   link. The row is a flex column and .cband a is the figure style (sections 3
   and 42), so the link restates every property it must not inherit. */
:root .cband a.contact-alt {
  align-self: flex-start;
  margin: 0;
  font-family: var(--display);
  font-size: var(--fs-label);
  font-weight: var(--wt-lab);
  letter-spacing: var(--trk-lab);
  color: var(--gold-deep);
  border-bottom: 1px solid color-mix(in srgb, var(--gold-deep) 42%, transparent);
  /* 24px of target at least (WCAG 2.5.8): the words alone were 20px tall. */
  display: inline-block;
  padding: 3px 0 2px;
  min-height: 24px;
  line-height: 1.4;
}
:root .cband a.contact-alt:hover { color: var(--ink); border-bottom-color: var(--ink); }

/* ---- 45.5b contact.html: the action stays with its words ---------------- */
/* On contact the third piece is a button that adds that row's line to the
   message, so it belongs under the words it adds, not under the title. The
   void goes the other way there: the title column narrows to a label column
   a third of the row wide, and the body takes a measure that reads. */
@media (min-width: 901px) {
  .shell > .rows > .row-item:has(> .row-add) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    grid-template-areas: "t b" ". o";
    grid-template-rows: auto auto;
  }
  .shell > .rows > .row-item > .row-add { margin-top: 0.9rem; align-self: start; }
  .shell > .rows > .row-item:has(> .row-add) .row-item__body p { max-width: 62ch; }
}

/* ==========================================================================
   46. The independent audit's findings, 2026-09-16
   site-auditor, fresh context, measured on this build. Its report is in
   docs/QA-REPORT.md under "/better pass 2026-09-16".
   ========================================================================== */

/* ---- 46.1 The hero's gold words over the photograph (audit 1) ------------ */
/* Measured on the real pixels behind each glyph at 3x, animations allowed to
   finish: --gold on "South Africa" ran 2.23:1 at its worst against a 3:1 bar,
   with 7.7% of 128,517 glyph pixels below it at 1366 and 3.2% at 390. The
   failing pixels all sit on the horizon, the lightest band in the picture,
   which is exactly where that line falls.
   Edgar asked for the button gold there on 2026-09-15 and it stays the button
   gold: accessibility is a floor, so the ground moves instead of the colour.
   The hero's own wash runs 68 to 80% ink-deep across the whole picture; this
   second layer deepens only the band the title occupies, the shaped form of
   scrim the Purlin ruling approves. Measured after: worst 4.13:1 at 1440 and
   4.21:1 at 390, no glyph pixel under 3:1. */
.hero__scrim {
  background:
    linear-gradient(180deg,
      transparent 4%,
      color-mix(in srgb, var(--ink-deep) 52%, transparent) 24%,
      color-mix(in srgb, var(--ink-deep) 52%, transparent) 64%,
      transparent 86%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--ink-deep) 78%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 68%, transparent) 30%,
      color-mix(in srgb, var(--ink-deep) 68%, transparent) 70%,
      color-mix(in srgb, var(--ink-deep) 80%, transparent) 100%);
}

/* ---- 46.2 The headline is not staked on an animation running (audit 2) --- */
/* .rise clips its line and started it 105% below the clip, so with the
   animation absent the H1 rendered nowhere: zero overlap with its wrapper.
   Reduced motion was already handled, so this was never a WCAG failure, but it
   broke this stylesheet's own rule at section 20: content is visible by
   default. The settled state is now the base style and the hidden state lives
   inside the keyframe, held through the stagger by fill-mode: both. Arming it
   off the .js class instead would work for .reveal but not here: script.js is
   deferred and this is the first thing above the fold, so the title would
   paint in place and then jump. */
.rise > span { transform: none; animation: riseUp 900ms var(--ease) both; }
.rise:nth-of-type(2) > span { animation-delay: 90ms; }
.rise:nth-of-type(3) > span { animation-delay: 180ms; }
@keyframes riseUp {
  from { transform: translateY(105%); }
  to { transform: translateY(0); }
}

/* ---- 46.3 The compare table at 200% text zoom (audit 3) ------------------ */
/* min-width: 42rem is 672px at a 16px root and 1344px at 32px, while .cmp-wrap
   gives its overflow back to visible for the sticky head and html's
   overflow-x guard swallows the page scroll: at 200% text the last column was
   22px off screen with no way to reach it. The floor yields to the space. */
.cmp-table { min-width: min(42rem, 100%); }

/* ---- 46.4 The card's dots (audit 12), left as they were ----------------- */
/* Moving the grow from width to a scaled 18px slot spread six dots from 73px
   to 130px across and squared their ends: a visible change to the "plain dots"
   of Edgar's card pick, to save a layout pass on six 6px elements. Reverted;
   the audit's point is recorded as left in QA-REPORT.md. */

/* ==========================================================================
   47. The phone hero, lighter (Edgar, 2026-09-16)
   "less overlay on home background photo on mobile also its bit dark the
   photo, still make sure text reads properly". The phone wash had become the
   base 70 to 82% plus 46.1's title band, about 86% under the copy: the sky and
   the field read as night. It is now a light wash (26 to 52%) with the depth
   shaped only where the gold line needs it, an ellipse centred on "South
   Africa". Measured on the real glyph pixels at 3x after the change, 390:
   white line worst 8.0:1, gold worst 3.75:1 against its 3:1 bar, lede worst
   9.5:1. Desktop is untouched.
   ========================================================================== */
@media (max-width: 640px) {
  .hero__scrim {
    background:
      radial-gradient(ellipse 110% 20% at 50% 45%,
        color-mix(in srgb, var(--ink-deep) 74%, transparent) 0%,
        color-mix(in srgb, var(--ink-deep) 60%, transparent) 55%,
        transparent 100%),
      radial-gradient(ellipse 100% 34% at 50% 44%,
        color-mix(in srgb, var(--ink-deep) 30%, transparent) 0%,
        transparent 100%),
      linear-gradient(180deg,
        color-mix(in srgb, var(--ink-deep) 52%, transparent) 0%,
        color-mix(in srgb, var(--ink-deep) 30%, transparent) 20%,
        color-mix(in srgb, var(--ink-deep) 34%, transparent) 60%,
        color-mix(in srgb, var(--ink-deep) 26%, transparent) 80%,
        color-mix(in srgb, var(--ink-deep) 45%, transparent) 100%);
  }
}

/* ==========================================================================
   48. Edgar's screenshot notes, 2026-09-16 (afternoon)
   Six notes on the staging build, in his order. Each rule below restates the
   one it beats, so nothing above had to move.
   ========================================================================== */
/* 48.1 The card's price rests in ink like the title and takes the title's
   gold on hover, the two together (was --gold-deep at rest, section 8). */
.farm-card__price { color: var(--ink); transition: color var(--t-fast) var(--ease-soft); }
.farm-card:hover .farm-card__price { color: var(--gold-deep); }

/* 48.2 The card title one step up from --fs-h3 (1.25 to 1.6rem). Two classes,
   so it outranks the :root :is(h3) rule of section 42. */
.farm-card .farm-card__title { font-size: clamp(1.375rem, 1.75vw, 1.75rem); }

/* 48.3 lives in index.html: the five questions band now sits above the
   country band, which is the last band before the footer. */

/* 48.4 The footer's bottom. The launcher no longer earns 100px of padding;
   the legal line just keeps out of its lane. On a phone the legal line reads
   "(c) 2026 ERFDEVCO." on screen and the registration sentence stays for a
   screen reader (the .visually-hidden recipe of section 1). */
.site-footer { padding-block: clamp(3.5rem, 6vw, 5.5rem) calc(clamp(2rem, 3vw, 2.75rem) + var(--chrome-b, 0px)); }
.footer-base p { max-width: calc(100% - var(--fab-size, 60px) - var(--fab-gap, 1.75rem) - 0.75rem); }
@media (max-width: 640px) {
  .footer-base .legal-more {
    position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

/* 48.5 The search rail's label and its value read as one centred pair.
   Desktop drops the 44px box the value sat in (24px is the pointer floor);
   touch keeps its 44px target and the label sits into the box's empty top. */
@media (min-width: 901px) {
  .searchrail__field select { min-height: 24px; height: auto; line-height: 1.25; padding-block: 0.1rem; }
  .searchrail__field { row-gap: 0.3rem; }
}
@media (max-width: 900px) {
  .searchrail__field label { margin-bottom: -0.45rem; position: relative; z-index: 1; }
}

/* 48.6 Four colours on every button: gold, ink, ink-deep and paper. No mixed
   tone on a button's face, edge or label (Edgar: "only 4 colours for
   consideration"). Text accents that are not buttons keep --gold-deep where
   pure gold fails contrast on a light ground. */
.btn--gold:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.btn--outline { border-color: var(--ink); }
.provlist button,
.band--sage .provlist button { border-bottom-color: var(--ink); }
.provlist button .n,
.band--sage .provlist button:not([aria-pressed="true"]) .n { color: var(--ink); }
.provlist button:hover:not([disabled]) { background: var(--paper); }
.band--ink .provlist button[disabled] { color: var(--paper); }
.cmp-toggle { color: var(--ink); }
.cmp-toggle .box { border-color: var(--ink); }
.filter-btn { border-color: var(--ink); color: var(--ink); }
.spec-tab { color: var(--ink); border-bottom-color: var(--ink); }
.spec-tab .c { color: var(--ink); }
.nav-toggle { border-color: var(--paper); }
.nav-toggle:hover { border-color: var(--gold); }
.chat-fab { border-color: var(--gold); }
.save-btn { background: var(--ink-deep); border-color: var(--paper); }
.chat-chips button { border-color: var(--gold); }
.chat-chips button::after { color: var(--ink); }
.chat-chips button:hover { border-color: var(--ink); background: var(--paper); }
.chat-send { background: var(--paper); color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }
.chat-form.has-text .chat-send { box-shadow: none; }
/* 48.6, the four rules the first read missed (each beat 48.6 on specificity):
   the saved-filter chip, the outline button on the sage ground, the inline
   save button on the listing page and the schedule tabs on the ink ground. */
.filter-btn[data-saved] { border-color: var(--gold); color: var(--ink); }
.band--sage .btn--outline { border-color: var(--ink); }
.save-btn.save-btn--inline { border-color: var(--ink); }
.band--ink .spec-tab { color: var(--paper); border-bottom-color: var(--paper); }
/* 48.7 The cards' band sits on the same sage as the country band (Edgar,
   2026-09-16: "make sure these sections background is the same colour"):
   the farm cards' grey of 34.2 (--stone-2) goes, on the home page and on
   the listings page, and the white cards read on the chapter tone. */
.band:has(.farm-grid) { background: var(--sage); }

/* ==========================================================================
   49. Legal pages, 2026-09-16: terms.html (the client's website terms of
   16/09/2026, clause for clause) and the privacy policy rewrite. The clause
   lists inside a row body read like its paragraphs: same tone, size and
   measure, with a gold marker.
   ========================================================================== */
.row-item__body .clauses {
  list-style: disc outside; margin: 0.6rem 0 1rem; padding-left: 1.2rem; max-width: 46ch;
  color: var(--muted); font-size: 1rem; line-height: 1.62;
}
.row-item__body .clauses li { margin-bottom: 0.35rem; padding-left: 0.15rem; }
.row-item__body .clauses li::marker { color: var(--gold-deep); }
.row-item__body p + .clauses { margin-top: 0.5rem; }
.row-item__body p strong { color: var(--ink); font-weight: 600; }

/* ==========================================================================
   50. The portal look, 2026-09-17. Edgar, with five Property24 phone captures:
   "more similar to the way Property24's look ... nice and modern ... the way
   you did it, it's playing a bit too much around, with that strips and stuff
   ... the colouring, you make the colour of their company."
   Taken from the reference: the photograph first with a counter, the price as
   the first words, one line saying what it is and where, figures with small
   pictures, a two-column feature grid, a key-and-value overview, accordion
   sections with a plus, a plain bond card, a white action bar on a phone, and
   a card that leads with its price and carries no strip.
   Kept: Bitter and Instrument Sans, estate green and gold, canvas and paper,
   every fact of the schedule. The new markup carries pd- names, so none of
   the earlier listing rules (17, 26, 32, 36) reach it; those stay for print
   and the compare page until they are swept out.
   ========================================================================== */
.ico { width: 24px; height: 24px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

/* 50.1 The page: paper cards on the canvas, one column beside the enquiry. */
#listing-detail { background: var(--canvas); }
.pd-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(320px, 392px); gap: clamp(1.25rem, 2.4vw, 2.25rem); align-items: start; padding-block: clamp(1rem, 2vw, 1.75rem) clamp(3rem, 6vw, 5rem); }
.pd-main { display: grid; gap: clamp(0.75rem, 1.4vw, 1.25rem); min-width: 0; }
.pd-side { position: sticky; top: calc(var(--header-h) + 1rem); min-width: 0; }
.pd-side .enquiry-card { position: static; }
.pd-card { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lift); padding: clamp(1.25rem, 2.2vw, 2rem); min-width: 0; }
.pd-card > h2, .pd-card__head h2 { font-size: clamp(1.25rem, 1.5vw, 1.45rem); line-height: 1.2; margin: 0 0 0.9rem; }
.pd-card__head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 0.9rem; }
.pd-card__head h2 { margin: 0; }
.pd-note { color: var(--muted); font-size: 0.9375rem; line-height: 1.6; max-width: 62ch; margin: 0 0 1rem; }
.pd-card p { max-width: 68ch; }
.pd-card .lede { font-size: 1.0625rem; line-height: 1.6; color: var(--ink); }

/* 50.2 The photographs lead. Desktop: one large and four small, the rest in
   the lightbox. Phone: one photograph at a time, edge to edge, swiped. */
.pd-photos { padding-top: clamp(0.75rem, 1.6vw, 1.25rem); }
.pd-photos__shell { display: grid; }
.pd-photos__shell > * { grid-area: 1 / 1; min-width: 0; }
.pd-photos .gallery { display: grid; grid-template-columns: 2fr 1fr 1fr; grid-template-rows: repeat(2, minmax(0, 1fr)); gap: 6px; height: clamp(300px, 42vw, 540px); margin: 0; border-radius: var(--r-lift); overflow: hidden; }
.pd-photos .gallery figure { margin: 0; border-radius: 0; aspect-ratio: auto; min-height: 0; position: relative; }
.pd-photos .gallery figure:first-child { grid-row: 1 / 3; }
.pd-photos .gallery figure:nth-child(n+6) { display: none; }
.pd-photos .gallery .gallery__frame { display: block; width: 100%; height: 100%; aspect-ratio: auto; }
.pd-photos .gallery img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pd-photos .gallery figcaption { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.pd-photos__bar { align-self: end; justify-self: end; margin: 0 0.85rem 0.85rem 0; display: flex; align-items: center; gap: 0.5rem; z-index: 2; }
.pd-photos__count { background: var(--ink-deep); color: var(--paper); border-radius: var(--r-chip); padding: 0.3rem 0.6rem; font-size: var(--fs-label); font-variant-numeric: tabular-nums; }
.pd-photos__all { display: inline-flex; align-items: center; gap: 0.45rem; min-height: 40px; padding: 0.4rem 0.8rem; background: var(--paper); color: var(--ink); border: 1px solid var(--ink); border-radius: var(--r-chip); font-family: var(--display); font-size: 0.875rem; cursor: pointer; transition: background var(--t-fast) var(--ease-soft), color var(--t-fast) var(--ease-soft); }
.pd-photos__all:hover { background: var(--ink); color: var(--paper); }
.pd-photos__all .ico { width: 18px; height: 18px; }
@media (min-width: 641px) { .pd-photos__count { display: none; } }

/* 50.3 The head: price, tools, what it is, where, the three figures. */
.pd-head__top { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.pd-price { font-family: var(--display); font-weight: var(--wt-fig, 700); font-size: clamp(1.75rem, 2.6vw, 2.4rem); line-height: 1.1; letter-spacing: -0.01em; color: var(--ink); margin: 0; white-space: nowrap; }
.pd-perha { color: var(--muted); font-size: 0.9375rem; margin: 0.35rem 0 0; }
.pd-perha a { color: var(--gold-deep); text-decoration: underline; text-underline-offset: 3px; }
.pd-perha a:hover { color: var(--ink); }
.pd-title { font-size: clamp(1.4rem, 2vw, 1.8rem); line-height: 1.2; margin: 1rem 0 0.25rem; }
.pd-kind { color: var(--muted); font-size: 1rem; margin: 0; }
.pd-chips { list-style: none; display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.85rem 0 0; padding: 0; }
.pd-chips li { font-size: var(--fs-label); color: var(--ink); border: 1px solid var(--line); border-radius: var(--r-chip); padding: 0.2rem 0.55rem; background: var(--canvas); }
.pd-specs { list-style: none; display: flex; flex-wrap: wrap; gap: 1rem 2rem; margin: 1.1rem 0 0; padding: 1.1rem 0 0; border-top: 1px solid var(--line); }
.pd-specs li { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }
.pd-specs .ico { color: var(--gold-deep); width: 26px; height: 26px; }
.pd-specs b { display: block; font-family: var(--display); font-weight: var(--wt-fig, 700); font-size: 1.0625rem; color: var(--ink); line-height: 1.2; }
.pd-specs i { display: block; font-style: normal; color: var(--muted); font-size: var(--fs-label); }
.pd-head__tools { display: flex; align-items: center; gap: 0.4rem; flex: none; }
.pd-tool, .pd-head__tools .save-btn { width: 44px; height: 44px; min-height: 0; display: inline-flex; align-items: center; justify-content: center; padding: 0; background: var(--paper); color: var(--ink); border: 1px solid var(--ink); border-radius: 50%; cursor: pointer; position: relative; transition: background var(--t-fast) var(--ease-soft), color var(--t-fast) var(--ease-soft); }
.pd-tool:hover, .pd-head__tools .save-btn:hover { background: var(--ink); color: var(--paper); }
.pd-tool .ico, .pd-head__tools .save-btn svg { width: 20px; height: 20px; }
.pd-head__tools .save-btn[data-saved="true"] { background: var(--gold); border-color: var(--gold); color: var(--ink); }
.pd-head__tools .save-label { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.pd-tool[data-tip]::after { content: attr(data-tip); position: absolute; top: calc(100% + 6px); right: 0; background: var(--ink-deep); color: var(--paper); font-size: var(--fs-label); padding: 0.3rem 0.55rem; border-radius: var(--r-chip); white-space: nowrap; z-index: 5; }

/* 50.4 Features: every headline fact with its picture, two columns. */
.pd-feats { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: clamp(1.25rem, 3vw, 2.5rem); }
.pd-feats li { display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; align-items: center; gap: 0.75rem; padding: 0.75rem 0; border-bottom: 1px solid var(--line-soft); }
.pd-feats .ico { color: var(--gold-deep); }
.pd-feat__k { color: var(--muted); font-size: 0.9375rem; }
.pd-feat__v { font-family: var(--display); font-weight: var(--wt-fig, 700); color: var(--ink); text-align: right; font-size: 1rem; }
@media (max-width: 560px) { .pd-feats { grid-template-columns: minmax(0, 1fr); } }

/* 50.5 Key and value rows: the overview and every schedule section. */
.pd-rows { margin: 0; }
.pd-rows > div { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 1rem; padding: 0.7rem 0; border-bottom: 1px solid var(--line-soft); }
.pd-rows > div:last-child { border-bottom: 0; }
.pd-rows dt { color: var(--muted); font-size: 0.9375rem; }
.pd-rows dd { margin: 0; color: var(--ink); font-size: 0.9375rem; font-weight: 600; }

/* 50.6 The schedule as accordions: a plus that becomes a minus. */
.pd-acc { border-top: 1px solid var(--line); }
.pd-acc:last-of-type { border-bottom: 1px solid var(--line); }
.pd-acc summary { list-style: none; display: flex; align-items: center; gap: 0.75rem; min-height: 52px; padding: 0.35rem 0; cursor: pointer; font-family: var(--display); font-weight: var(--wt-head, 700); font-size: 1.0625rem; color: var(--ink); }
.pd-acc summary::-webkit-details-marker { display: none; }
.pd-acc summary:hover .pd-acc__t { color: var(--gold-deep); }
.pd-acc__n { margin-left: auto; color: var(--gold-deep); font-size: var(--fs-label); font-variant-numeric: tabular-nums; }
.pd-acc__i { position: relative; width: 18px; height: 18px; flex: none; }
.pd-acc__i::before, .pd-acc__i::after { content: ""; position: absolute; background: var(--ink); transition: transform var(--t-fast) var(--ease); }
.pd-acc__i::before { left: 2px; right: 2px; top: 8px; height: 2px; }
.pd-acc__i::after { top: 2px; bottom: 2px; left: 8px; width: 2px; }
.pd-acc[open] .pd-acc__i::after { transform: scaleY(0); }
.pd-acc .pd-rows { padding: 0 0 0.9rem; }
.pd-openall { background: var(--paper); color: var(--ink); border: 1px solid var(--ink); border-radius: var(--r-chip); padding: 0.45rem 0.85rem; min-height: 40px; font-family: var(--display); font-size: 0.875rem; cursor: pointer; transition: background var(--t-fast) var(--ease-soft), color var(--t-fast) var(--ease-soft); }
.pd-openall:hover, .pd-openall[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

/* 50.7 The bond card sits on paper like the rest. */
.pd-card .bond { margin-top: 0.5rem; }
.pd-card .bond-out { background: var(--canvas); border: 1px solid var(--line); border-radius: var(--r-card); }
.pd-card .bond-out, .pd-card .bond-big, .pd-card .bond-sum dd { color: var(--ink); }
.pd-card .bond-lab, .pd-card .bond-sum dt, .pd-card .bond-out .form-note { color: var(--muted); }

/* 50.8 The card leads with its price and carries its three figures as a plain
   row with their pictures. The gold strip of section 43 is gone. */
.farm-card__body > .farm-card__price { margin: 0 0 0.2rem; font-size: clamp(1.35rem, 1.7vw, 1.6rem); line-height: 1.15; }
.farm-card .farm-card__title { font-size: clamp(1.125rem, 1.35vw, 1.3rem); line-height: 1.25; min-height: 0; margin: 0 0 0.2rem; }
.farm-card__specs { list-style: none; display: flex; flex-wrap: wrap; gap: 0.6rem 1.35rem; margin: 0.95rem 0 0; padding: 0.95rem 0 0; border-top: 1px solid var(--line-soft); }
.farm-card__specs li { display: flex; align-items: center; gap: 0.5rem; min-width: 0; }
.farm-card__specs .ico { width: 22px; height: 22px; color: var(--gold-deep); }
.farm-card__specs b { display: block; font-family: var(--display); font-weight: var(--wt-fig, 700); font-size: 0.9375rem; color: var(--ink); line-height: 1.2; }
.farm-card__specs i { display: block; font-style: normal; color: var(--muted); font-size: 0.8125rem; line-height: 1.3; }

/* 50.9 Narrow screens: one column, cards edge to edge, a white action bar. */
@media (max-width: 1000px) {
  .pd-grid { grid-template-columns: minmax(0, 1fr); }
  .pd-side { position: static; }
}
@media (max-width: 640px) {
  .pd-photos { padding-top: 0; }
  .pd-photos__shell { padding-inline: 0; }
  .pd-photos .gallery { display: flex; height: auto; gap: 0; border-radius: 0; overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory; scrollbar-width: none; }
  .pd-photos .gallery::-webkit-scrollbar { display: none; }
  .pd-photos .gallery figure, .pd-photos .gallery figure:nth-child(n+6) { display: block; flex: 0 0 100%; aspect-ratio: 4 / 3; scroll-snap-align: start; }
  .pd-grid { padding-inline: 0; padding-top: 0; gap: 0.6rem; }
  .pd-main { gap: 0.6rem; }
  .pd-card { border-inline: 0; border-radius: 0; padding: 1.25rem 1.25rem; }
  .pd-side .enquiry-card { border-radius: 0; }
  .pd-head__top { align-items: flex-start; }
  .pd-tool, .pd-head__tools .save-btn { width: 40px; height: 40px; }
}
@media (max-width: 900px) {
  .actionbar { gap: 0.5rem; background: var(--paper); border-top: 1px solid var(--line); padding: 0.5rem 0.75rem calc(0.5rem + var(--safe-bottom)); grid-template-columns: 1fr 1fr 1.2fr; }
  .actionbar__btn { flex-direction: row; gap: 0.45rem; min-height: calc(var(--actionbar-h) - 1rem); background: var(--paper); color: var(--ink); border: 1px solid var(--ink); border-radius: var(--r-chip); font-size: 0.8125rem; }
  .actionbar__btn svg { width: 18px; height: 18px; }
  .actionbar__btn:active { background: var(--ink); color: var(--paper); }
  .actionbar__btn--gold { background: var(--gold); border-color: var(--gold); color: var(--ink); }
  .actionbar__btn--gold:active { background: var(--ink); border-color: var(--ink); color: var(--paper); }
}
@media print {
  .pd-photos .gallery figure:not(:first-child), .pd-photos__bar, .pd-head__tools, .pd-openall, .pd-side, .actionbar { display: none !important; }
  .pd-photos .gallery { display: block; height: auto; }
  .pd-grid { display: block; }
  .pd-card { border: 0; padding: 0 0 1rem; break-inside: avoid; }
  .pd-acc__i { display: none; }
}

/* 50.10 First read of the portal look (round 41): the earlier gallery rules
   still placed the first photograph across every column and rounded each
   frame, and the card heart's absolute offsets rode into the head's tools. */
.pd-photos .gallery > figure { grid-column: auto; grid-row: auto; border-radius: 0; background: var(--ink-deep); }
.pd-photos .gallery > figure:first-child { grid-column: 1 / 2; grid-row: 1 / 3; }
.pd-photos .gallery > figure .gallery__frame,
.pd-photos .gallery > figure:first-child .gallery__frame { aspect-ratio: auto; height: 100%; border-radius: 0; }
.pd-photos .gallery > figure:hover img { transform: none; }
.pd-head__tools .save-btn { position: relative; inset: auto; right: auto; bottom: auto; margin: 0; backdrop-filter: none; }
@media (max-width: 640px) {
  .shell.pd-photos__shell { padding-inline: 0; }
  .pd-photos .gallery { scroll-padding: 0; padding: 0; }
  .pd-photos .gallery > figure,
  .pd-photos .gallery > figure:first-child,
  .pd-photos .gallery > figure:nth-child(n+6) { display: block; flex: 0 0 100%; width: 100%; max-width: none; aspect-ratio: 4 / 3; scroll-snap-align: start; grid-column: auto; grid-row: auto; margin: 0; }
}

/* 50.11 Tablet read: an earlier tablet rule starts the gallery's items at
   the top of their cells, so between 641 and 1000px the photographs kept
   their own ratio inside the mosaic. Every cell is filled at every width. */
.pd-photos .gallery { align-items: stretch; justify-items: stretch; }
.pd-photos .gallery > figure { width: 100%; height: 100%; }
@media (max-width: 640px) {
  .pd-photos .gallery > figure,
  .pd-photos .gallery > figure:first-child { height: auto; }
}
.pd-photos .gallery { padding: 0; margin: 0; width: 100%; scroll-padding: 0; }
