/* ============================================================================
   WOELWATER SPANIELS — the public site. THE GILDED ALBUM.

   GOLDEN, BY THE DIRECTOR'S OWN RULING, 9 August 2026 — which also declared
   the written redesign brief LEGACY ("fuck the redesign brief. be free...
   just keep text and sentences already used"). The visual authority is now the
   chain of the director's rulings recorded in docs/design.md, and the identity
   is the supplied logo turned into a system: golden Cocker Spaniels glowing
   out of warm espresso darkness, honey-gold foil, museum-mounted photography,
   and golden parchment where there is reading to do.

   THERE IS NO GREEN IN THIS FILE. The brief's deep forest was ruled out in
   capitals, twice. If a green-dominant hex ever reappears here, a ruling has
   been undone — design.test.ts scans gradients and shadows for strays.

   What is NOT negotiable, whatever the aesthetics do: the breeder's sentences
   verbatim (content.test.ts), the contrast figures in the token comments, the
   320px floor, the touch minimums, and `style-src 'self'` — every rule lives
   in this file because an inline style is dropped silently.

   docs/design.md is the design record. Read it before changing anything here:
   most of what looks like taste is a decision with a reason, and several of the
   rules exist because the alternative was watched failing in a browser.

   Written by hand. There is no generator, no preview file and no port step: the
   pages are TypeScript templates in site.ts and this is their only stylesheet.

   TWO RULES THIS FILE EXISTS TO OBEY
   ----------------------------------
   1. `style-src 'self'`. A style="" attribute and a <style> block are dropped
      silently by the CSP — the page renders with half its composition missing
      while every test stays green. So EVERY rule lives here. Where a template
      needs a per-image crop it asks for a focal class (.f-*), never an inline
      object-position.
   2. Nothing may be wider than a 320px viewport, and nothing may start
      invisible unless the same rule also carries the animation that reveals it.
   ========================================================================= */

/* The faces are loaded by their own <link> in the document head, not @import-ed
   here: an @import is a chained request that cannot start until this file has
   arrived, and two same-origin links download in parallel. */

/* ── tokens ────────────────────────────────────────────────────────────────
   One neutral ramp of eleven values plus three golds, all OKLCH. No pure white
   and no pure black anywhere, which is most of what makes a screen read as paper
   rather than as a screen. */

:root {
  /* ── the six brand colours, as the brief specifies them ─────────────────
     These are the 9 August brief's own hex values and they are not to be
     "improved":

       deep forest   #17231D   PRIMARY — hero, masthead, major bands, footer
       charcoal      #171714   secondary dark — gallery environments, and INK
       warm stone    #D6D0C4   light editorial sections
       soft taupe    #A9A296   secondary text, metadata, borders
       golden brown  #B88952   the organic accent, used very sparingly
       soft white    #F7F6F2   light text on forest and charcoal

     The ramp below is those six plus the steps between them that reading
     actually needs — a body colour, a small-print colour, a hairline. Nothing
     is invented beyond that, and there is no pure white and no pure black. */
  /* THE GOLDEN THEME — ruled by the director, 9 August 2026: "MAKE WEBSITE FIR
     A GOLDEN THEME. MATCHES GOLDEN COCKER SPANIELS", after ruling the brief's
     deep forest out ("The green is UGLY"). The system is the supplied logo,
     turned into a palette: golden dogs glowing out of warm espresso darkness,
     honey highlights, and golden parchment carrying the reading. There is no
     green anywhere. Every pairing below was contrast-computed; the figures are
     real, not vibes.

     NOTE ON NAMES: the `.forest` class and `--forest` token are HISTORIC names
     kept because markup and tests reach for them — the SURFACE they paint is
     espresso. Renaming them is churn with no pixel; repainting them green again
     would be undoing a ruling. */
  /* GOLDEN HOUR — ruled 9 August 2026: the director asked for the light-led
     version ("creamy and golden... more welcoming?") and the new daylight
     photography settles it. The album's curtains open: golden cream carries
     the site, espresso becomes the frame (masthead, footer, the deep rooms,
     one foil statement at each close) instead of the field. */
  --forest: #1C130A;  /* the dark FRAME: warm espresso-amber black */
  --charcoal: #201709;  /* INK on light — warm black, not grey */
  --stone: #F2E7C9;  /* golden cream — THE page ground */
  --taupe: #B3A176;  /* warm khaki — labels and metadata on dark (7.2:1) */
  --gold: #C9944C;  /* THE coat gold — 6.8:1 on espresso */
  --soft-white: #FAF3E3;  /* candle-lit white for type on dark — 16.6:1 */

  --n-00: #FBF6E8;  /* raised surface — the menu panel on a light band */
  --n-01: var(--stone);  /* PAGE GROUND for the light editorial sections */
  --n-02: #E8D8AC;  /* sunk band — one honeyed step deeper */
  --n-03: #CDB98B;  /* hairline */
  --n-04: #A68F5C;  /* rule */
  --n-05: #7E6B40;  /* strong rule. never small text on light */
  --n-06: #5D512F;  /* small print on light — 6.4:1 on cream, 5.5:1 on sunk */
  --n-07: #413920;  /* BODY PROSE — 9.3:1 on cream */
  --n-08: #2D2614;  /* secondary headings */
  --n-09: var(--charcoal);  /* INK. headings — 14.4:1 on cream */

  /* the espresso steps — raised panels and the deep exhibition rooms */
  --forest-deep: #140D06;
  --forest-lift: #4A3A24;  /* espresso raised into a visible line ON espresso */

  /* ── the golds, which are the identity ──────────────────────────────────
     Three values because gold has three jobs. `--gold` is the coat itself —
     rules, mounts, current-page marks, the filled primary button. `--gold-lift`
     is honey — the bright display accent on dark (9.5:1), the hero's italic
     word. `--gold-ink` is the only gold allowed to be TEXT on the light
     grounds (6.3:1 on parchment); raw gold there is 2.6:1 and banned. */
  --gold-ink: #6B4715;
  --gold-lift: #E3B36B;

  /* semantics. .inverse and .forest re-point these and nothing else changes. */
  --bg: var(--n-01);
  --bg-raised: var(--n-00);
  --ink: var(--n-09);
  --body: var(--n-07);
  --label: var(--n-06);
  --line: var(--n-03);
  --rule: var(--n-04);
  --accent: var(--gold-ink);

  --focus: var(--forest);
  --field: var(--n-00);

  /* A named z-index scale. Never an arbitrary 999. */
  --z-sticky: 30;
  --z-menu: 40;
  --z-float: 45;
  --z-skip: 60;
  --z-grain: 70;
  --z-overlay: 80;

  /* type. Newsreader is a text-first editorial serif with a real optical-size
     axis, so a 6rem display line and a 1rem caption are drawn rather than
     scaled. Schibsted Grotesk carries every label, control and specification. */
  --font-display: "Newsreader", ui-serif, Georgia, serif;
  --font-ui: "Schibsted Grotesk", ui-sans-serif, system-ui, sans-serif;

  --t-label: clamp(0.66rem, 0.62rem + 0.16vw, 0.74rem);
  --t-fine:  clamp(0.8rem, 0.77rem + 0.14vw, 0.88rem);
  --t-body:  clamp(1rem, 0.96rem + 0.2vw, 1.1rem);
  --t-lede:  clamp(1.18rem, 1.06rem + 0.6vw, 1.6rem);
  --t-h4:    clamp(1.1rem, 1.04rem + 0.3vw, 1.3rem);
  --t-h3:    clamp(1.35rem, 1.2rem + 0.75vw, 1.9rem);
  --t-h2:    clamp(2rem, 1.5rem + 2.3vw, 3.8rem);
  /* Ceiling 5.6rem. Above about 6rem a heading is shouting rather than designing,
     and the longest word here — "Spaniels" inside a two-word lockup — starts
     overflowing a 320px viewport long before that. */
  --t-h1:    clamp(2.4rem, 1.7rem + 3.4vw, 4.4rem);
  --t-hero:  clamp(2.6rem, 1.6rem + 5vw, 5.6rem);

  --lh-tight: 1.02;
  --lh-snug: 1.14;
  --lh-body: 1.62;
  --track-label: 0.2em;
  --track-display: -0.018em;

  /* spacing. A single fluid scale; sections use --s-6 and --s-7 so the rhythm
     of the whole site is one decision rather than forty. */
  --s-0: 0.25rem;
  --s-1: 0.5rem;
  --s-2: clamp(0.75rem, 0.7rem + 0.25vw, 0.9rem);
  --s-3: clamp(1rem, 0.9rem + 0.5vw, 1.4rem);
  --s-4: clamp(1.5rem, 1.3rem + 1vw, 2.4rem);
  --s-5: clamp(2.2rem, 1.8rem + 1.8vw, 3.6rem);
  --s-6: clamp(3.2rem, 2.4rem + 3.2vw, 6rem);
  --s-7: clamp(4.5rem, 3.2rem + 5.6vw, 9.5rem);

  /* structure. The radii are the Black Leash ruling made token: soft cards and
     pill controls are the modern language the director pointed at — the square
     2px "print" geometry was the last thing reading as grade-11. */
  --shell: 78rem;
  --shell-narrow: 56rem;
  --gutter: clamp(1.15rem, 4.2vw, 4.5rem);
  --measure: 36rem;
  --radius: 0.75rem;
  --radius-card: 1.25rem;
  --radius-pill: 999px;
  --hair: 1px;

  /* motion. Four transitions and one reveal, all of them slow. */
  --dur-quick: 180ms;
  --dur: 380ms;
  --dur-slow: 900ms;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Two dark surfaces, one set of semantics — both ESPRESSO, never green.
   `.forest` (historic name) is the identity ground: masthead, hero, the major
   bands, the contact close and the footer. `.inverse` is the DEEP ROOM — one
   step darker, where photography hangs (the plate walls, the gallery). One
   dark family is what makes the pages match; the golden hairlines are what
   make the dark rooms glow instead of going flat. */
.forest,
.inverse {
  --ink: var(--soft-white);
  --body: var(--stone);
  --label: var(--taupe);
  --accent: var(--gold);
  --focus: var(--gold);
  --field: rgb(250 243 227 / 6%);
  color: var(--body);
  background: var(--bg);
}

.forest {
  --bg: var(--forest);
  --bg-raised: var(--forest-deep);
  --line: #382814;
  --rule: #5C4526;
}

.inverse {
  --bg: var(--forest-deep);
  --bg-raised: #0D0803;
  --line: #2E2112;
  --rule: #4E3A1F;
}

/* ── reset and base ──────────────────────────────────────────────────────── */

*,
*::before,
*::after { box-sizing: border-box; }

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* A sticky masthead would otherwise sit on top of whatever an anchor lands on. */
  scroll-padding-top: 5rem;
}

body {
  font-family: var(--font-ui);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  font-weight: 400;
  color: var(--body);
  background: var(--bg);
  /* clip, NOT hidden. `overflow-x: hidden` on body makes it a scroll container,
     which silently kills position:sticky on the masthead. */
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img,
picture,
svg { display: block; max-width: 100%; }

img { height: auto; background: var(--n-02); }

a { color: inherit; }

button,
input,
textarea,
select { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}

::selection {
  background: var(--n-09);
  color: var(--n-01);
}

/* ── film grain ─────────────────────────────────────────────────────────────
   The whole site under one breath of grain — what fuses photography, espresso
   and parchment into a single filmic substance instead of assembled rectangles.
   Generated noise as a data URI (img-src allows data:), fixed, faint, and
   incapable of catching a press. It sits under the lightbox, so the full-size
   photographs stay crisp when studied. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E");
}

.skip {
  position: absolute;
  left: var(--s-3);
  top: -6rem;
  z-index: var(--z-skip);
  padding: var(--s-2) var(--s-3);
  background: var(--n-09);
  color: var(--n-01);
  text-decoration: none;
  border-radius: var(--radius);
  transition: top var(--dur-quick) var(--ease);
}

.skip:focus { top: var(--s-2); }

/* Screen-reader only. Clipped to a 1px box, so its nowrap cannot push anything. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── structure ───────────────────────────────────────────────────────────── */

.shell {
  width: min(100% - (var(--gutter) * 2), var(--shell));
  margin-inline: auto;
}

.shell--narrow { width: min(100% - (var(--gutter) * 2), var(--shell-narrow)); }

.band { padding-block: var(--s-7); }
.band--tight { padding-block: var(--s-5); }
.band--sunk { background: var(--n-02); }
.band--flush { padding-block: 0; }

.band + .band--sunk,
.band--sunk + .band { border-top: var(--hair) solid var(--line); }

.stack > * + * { margin-top: var(--s-3); }
.stack--wide > * + * { margin-top: var(--s-4); }
.stack--tight > * + * { margin-top: var(--s-1); }

/* Asymmetric splits that flip side to side. Every track is allowed to shrink,
   which is what keeps 320px honest: minmax(min(100%, X), …), never minmax(X, …). */
.split {
  display: grid;
  gap: var(--s-5) var(--s-6);
  align-items: start;
}

@media (min-width: 52rem) {
  .split--57 { grid-template-columns: minmax(min(100%, 20rem), 5fr) minmax(min(100%, 20rem), 7fr); }
  .split--75 { grid-template-columns: minmax(min(100%, 20rem), 7fr) minmax(min(100%, 20rem), 5fr); }
  /* auto-fit, NOT repeat(2, …). Two fixed tracks with a 16rem floor need 34rem plus the gap, and
     `.split--even` is used inside columns narrower than that — the second track then overflows its
     parent and the whole document scrolls sideways. auto-fit drops to one column instead. */
  .split--even { grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); }
  /* One column offset down against the other. Asymmetry you feel rather than see. */
  .split__drop { margin-top: var(--s-6); }
}

.grid {
  display: grid;
  gap: var(--s-4) var(--s-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

/* ── type ────────────────────────────────────────────────────────────────── */

.label {
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--label);
  line-height: 1.4;
}

.display,
.h1,
.h2,
.h3,
.h4 {
  font-family: var(--font-display);
  font-weight: 300;
  color: var(--ink);
  letter-spacing: var(--track-display);
  font-optical-sizing: auto;
  text-wrap: balance;
}

/* A tonal page has no colour contrast to spend, so it spends weight: display
   type is LIGHTER than body type. That inversion is what reads as confidence. */
.display {
  font-size: var(--t-h1);
  line-height: var(--lh-tight);
  font-weight: 250;
}

/* The closing statements on dark bands are set in gold foil — the site's own
   line says "Heart of Gold", and the type keeps the promise. Guarded: engines
   without clip support keep candle-white. Applied per WORD, because the kinetic
   spans each need their own clip box. */
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .forest .display,
  .forest .display span,
  .inverse .display,
  .inverse .display span {
    background: linear-gradient(100deg, #E8C078, #C9944C 45%, #E3B36B 75%, #FAF3E3);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
  }
}

/* The living close: the espresso statement band carries the golden-dust
   atmosphere (public/hero.js) — the one dark, glowing moment on each light
   page. Position anchors the canvases; clip keeps the dust inside the band. */
.band--live {
  position: relative;
  overflow: clip;
}

.band--live > .shell { position: relative; z-index: 1; }

/* THE EPIC REGISTER — the closing statement owns the whole viewport width, the
   way the reference bar's type owns its screen. One register above everything;
   used only on the closes, so it stays an event. */
.display--epic {
  font-size: clamp(2.6rem, 1.2rem + 6.8vw, 7.75rem);
  line-height: 1.04;
  letter-spacing: -0.022em;
  max-width: none;
}

/* The kinetic assembly: each word arrives as the statement scrolls into view —
   scroll-driven CSS, no JavaScript, double-guarded like every reveal so no
   engine and no motion preference ever sees a blank line. */
.kinetic span { display: inline-block; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .kinetic span {
      opacity: 0;
      transform: translateY(0.4em);
      animation: rise linear both;
      animation-timeline: view();
    }

    .kinetic span:nth-child(1) { animation-range: entry 0% cover 18%; }
    .kinetic span:nth-child(2) { animation-range: entry 3% cover 21%; }
    .kinetic span:nth-child(3) { animation-range: entry 6% cover 24%; }
    .kinetic span:nth-child(4) { animation-range: entry 9% cover 27%; }
    .kinetic span:nth-child(5) { animation-range: entry 12% cover 30%; }
    .kinetic span:nth-child(6) { animation-range: entry 15% cover 33%; }
    .kinetic span:nth-child(7) { animation-range: entry 18% cover 36%; }
    .kinetic span:nth-child(8) { animation-range: entry 21% cover 39%; }
    .kinetic span:nth-child(9) { animation-range: entry 24% cover 42%; }
    .kinetic span:nth-child(10) { animation-range: entry 27% cover 45%; }
    .kinetic span:nth-child(n + 11) { animation-range: entry 30% cover 48%; }
  }
}

.h1 { font-size: var(--t-h1); line-height: var(--lh-tight); font-weight: 250; }
.h2 { font-size: var(--t-h2); line-height: var(--lh-snug); }

/* Every section opener carries a short gold rule above it — the same mark the hero
   introduces, recurring like a bookplate. It is the one place the accent repeats,
   and it is structure (a new movement begins) rather than decoration. */
.h2::before {
  content: "";
  display: block;
  width: 2.6rem;
  margin-bottom: var(--s-3);
  border-top: var(--hair) solid var(--accent);
}
.h3 { font-size: var(--t-h3); line-height: 1.2; }
.h4 {
  font-family: var(--font-ui);
  font-size: var(--t-h4);
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.35;
}

.lede {
  font-family: var(--font-display);
  font-size: var(--t-lede);
  font-weight: 300;
  line-height: 1.38;
  color: var(--n-08);
  max-width: 30ch;
  text-wrap: pretty;
}

.forest .lede,
.inverse .lede { color: var(--body); }

.prose {
  max-width: var(--measure);
  text-wrap: pretty;
}

.prose > * + * { margin-top: var(--s-3); }

.fine {
  font-size: var(--t-fine);
  color: var(--label);
  line-height: 1.5;
}

.serif-em {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 300;
}

.centred {
  text-align: center;
  margin-inline: auto;
}

.centred .lede { margin-inline: auto; }

.rule {
  border: 0;
  border-top: var(--hair) solid var(--rule);
  margin-block: var(--s-4);
}

/* ── links and controls ──────────────────────────────────────────────────── */

.link {
  color: var(--ink);
  text-decoration: none;
  border-bottom: var(--hair) solid var(--rule);
  padding-bottom: 0.12em;
  transition: border-color var(--dur-quick) var(--ease), color var(--dur-quick) var(--ease);
}

.link:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* ── controls ───────────────────────────────────────────────────────────────

   TEXT-LED, not filled slabs. The brief asks for understated actions — "DISCOVER
   OUR DOGS →" — and rules out pill buttons, oversized rounded rectangles and drop
   shadows. So `.more` is the primary pattern across the whole site and `.btn` is a
   quiet bordered rectangle kept for the few places a real button belongs: the form
   submit, and the one action that closes a page.

   The previous version filled every call to action with solid ink, which is what
   made the pages read as a product site rather than a breeder's. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  min-height: 3.25rem;
  padding: 0.8rem 1.8rem;
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  color: var(--ink);
  background: transparent;
  border: var(--hair) solid var(--rule);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
    border-color var(--dur) var(--ease), translate var(--dur) var(--ease);
}

.btn:hover {
  color: var(--bg);
  background: var(--ink);
  border-color: var(--ink);
  translate: 0 -2px;
}

/* The filled primary: poured gold with ink lettering (6.6:1) — the identity as
   a control. The hero's MEET OUR DOGS, the form's submit, the contact enquiry. */
.btn--send {
  color: var(--charcoal);
  background: var(--gold);
  border-color: var(--gold);
  font-weight: 600;
}

.btn--send:hover {
  color: var(--charcoal);
  background: var(--gold-lift);
  border-color: var(--gold-lift);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  align-items: center;
}

/* text-align cannot centre flex ITEMS: without this, an action row inside a centred
   closing band sits flush left under centred type, and it was watched doing it. */
.actions.centred { justify-content: center; }

/* A text link that carries a moving arrow. The arrow is the only motion in the
   type, and it moves 0.3em — enough to notice, not enough to look playful. */
.more {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  min-height: 2.9rem;
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
}

.more::after {
  content: "\2192";
  transition: transform var(--dur) var(--ease);
}

/* The arrow carries the coat gold at rest — the accent working as wayfinding,
   which is the one job the brief gives it beyond the rules. */
.more::after { color: var(--accent); }

.more:hover { color: var(--accent); }
.more:hover::after { transform: translateX(0.3em); }

/* ── masthead ────────────────────────────────────────────────────────────── */

/* The masthead is espresso GLASS — the Black Leash move: it blurs whatever
   scrolls beneath it, light or dark, and always reads as the same warm bar.
   It keeps the .forest class so its children read dark-surface semantics. */
.masthead {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: rgb(28 19 10 / 78%);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: var(--hair) solid rgb(250 243 227 / 12%);
}

.masthead__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  min-height: 4.25rem;
  padding-block: var(--s-1);
}

/* The lockup: a drawn W, a hairline, and the name. One mark, shared with the favicon. */
.wordmark {
  display: inline-flex;
  align-items: center;
  /* It is the link back to the home page on every screen. It is a touch target too. */
  min-height: 2.75rem;
  gap: 0.6em;
  font-family: var(--font-display);
  font-size: clamp(1.02rem, 0.94rem + 0.4vw, 1.3rem);
  font-weight: 400;
  line-height: 1.06;
  letter-spacing: 0.02em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

/* The golden artwork, hairline-mounted in the coat gold. 2.2em of the wordmark is
   ~44px — large enough that the two spaniels in it actually read as spaniels. */
.wordmark__mark {
  width: 2.2em;
  height: 2.2em;
  flex: none;
  margin-right: 0.55em;
  border: var(--hair) solid var(--accent);
  border-radius: var(--radius);
}

.wordmark__text {
  display: inline-flex;
  flex-direction: column;
}

.wordmark__first { display: block; }

.wordmark__second {
  font-family: var(--font-ui);
  /* A floor, not just a ratio. At 0.56em of the wordmark this fell to 10px on a 320px phone —
     the business's own name, set too small to read on the screen most visitors use. */
  font-size: max(0.56em, 0.7rem);
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--label);
}

.nav {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 1.4vw, 1.6rem);
  /* Seven labels and a control. Without this the row simply exceeds the masthead and the whole
     document scrolls sideways on a 1024px laptop — which is what it did. */
  min-width: 0;
}

.nav__link {
  display: inline-flex;
  align-items: center;
  /* A navigation label is a touch target before it is type. Without this the desktop row measures
     29px tall — comfortably under the 44px minimum, on the one control every visitor uses. */
  min-height: 2.75rem;
  font-size: var(--t-label);
  font-weight: 500;
  /* Tighter than a standalone label: seven of these have to share one row with the wordmark. */
  letter-spacing: 0.07em;
  white-space: nowrap;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--body);
  transition: color var(--dur-quick) var(--ease);
}

.nav__link:hover { color: var(--ink); }

/* The current page: a restrained colour shift to the coat gold, and a small
   weight change. Not a heavy underline — the brief rules that out by name. */
.nav__link[aria-current="page"] {
  color: var(--accent);
  font-weight: 600;
}

/* The mobile menu is a native <details>. No JavaScript, so it cannot fail to
   open — and above 62rem it is forced open and flattened into the row. */
.menu { position: relative; }

.menu__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  min-height: 2.9rem;
  padding-inline: 0.2rem;
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}

.menu__toggle::-webkit-details-marker { display: none; }

.menu__bars {
  position: relative;
  width: 1.15rem;
  height: 0.55rem;
  border-top: var(--hair) solid currentColor;
  border-bottom: var(--hair) solid currentColor;
}

.menu[open] .menu__bars { border-bottom-color: transparent; }

.menu__panel {
  position: absolute;
  right: 0;
  top: calc(100% + var(--s-2));
  width: min(19rem, calc(100vw - (var(--gutter) * 2)));
  padding: var(--s-3);
  background: var(--bg-raised);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: 0 1rem 2.6rem #00000059;
  display: grid;
  gap: 0.15rem;
}

.menu__panel .nav__link {
  display: flex;
  align-items: center;
  min-height: 3rem;
  padding-inline: var(--s-2);
  border-bottom: 0;
}

/* A tint and the ink colour mark the current page. Not a coloured side-stripe. */
.menu__panel .nav__link:hover { background: var(--field); }

.menu__panel .nav__link[aria-current="page"] {
  background: var(--field);
  color: var(--ink);
}

@media (min-width: 62rem) {
  /* THREE UA behaviours of a CLOSED <details> have to be overridden here, or the desktop navigation
     silently collapses. This is not defensive tidying — each one was watched failing:
       1. older engines set `display: none` on the content slot;
       2. newer ones use `content-visibility: hidden`;
       3. and Chrome ≥131 moves that onto `::details-content`, which also SIZE-CONTAINS the slot.
     Number 3 is the nasty one. The links still paint, so the nav looks present in the DOM and in a
     screenshot of the markup — but the box measures zero, so all seven sit outside the masthead and
     are clipped away by `overflow-x: clip`. The header renders with no navigation at all.
     `block-size: auto` is what releases the containment. */
  .menu::details-content {
    content-visibility: visible;
    block-size: auto;
  }

  .menu__panel {
    position: static;
    display: flex;
    align-items: center;
    width: auto;
    padding: 0;
    background: none;
    border: 0;
    content-visibility: visible;
  }

  .menu__toggle { display: none; }

  .menu__panel .nav__link {
    /* NOT min-height: 0. This override used to reset it, which quietly put every desktop navigation
       link at 30px — under the touch minimum on exactly the control everybody uses. */
    padding-inline: 0;
  }

  .menu__panel {
    gap: clamp(0.7rem, 1.3vw, 1.5rem);
  }

  .menu__panel .nav__link:hover { background: none; color: var(--ink); }

  .menu__panel .nav__link[aria-current="page"] {
    background: none;
    color: var(--accent);
    font-weight: 600;
  }
}

/* ── the hero ──────────────────────────────────────────────────────────────

   THE BLACK LEASH RECIPE, in Woelwater's gold — ruled 9 August 2026 by
   pointing at that build's source: one full-viewport photograph, the type
   seated at its base over two scrims (one vertical for the type, one from the
   left so the headline holds on wide screens), pill actions, and a glass
   proof strip pinned to the hero's foot carrying only facts the business has
   actually published. The photograph is the moody stone-wall frame — chosen
   by eye, dark where the type sits. */

.hero {
  position: relative;
  z-index: 1;
}

/* The atmosphere canvases (the golden dust in the espresso close) can never
   catch a press. */
.hero__atmosphere {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.hero__atmosphere--front { z-index: 3; }

.hero__side {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--label);
}

.hero__eyebrow {
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold-lift);
}

.hero__word {
  font-size: clamp(2.6rem, 1.3rem + 5.6vw, 6rem);
  line-height: 1.02;
  letter-spacing: -0.03em;
}

.hero__word--soft {
  font-style: italic;
  font-weight: 250;
  color: var(--gold-lift);
}

/* Gold foil: over the dark hero photograph the italic word pours in BRIGHT
   honey golds. Guarded so an engine without clip support keeps flat honey. */
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .hero__word--soft {
    background: linear-gradient(100deg, #E8C078, #C9944C 45%, #E3B36B 75%, #FAF3E3);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
  }

  /* ON A PHONE THE FOIL COMES OFF, ruled 9 August 2026 ("text whites out with
     background"). The gradient's last stop is near-white, and at phone size
     that stop lands across whole letters instead of an edge — over a pale
     patch of the photograph the word simply vanished. Solid honey cannot. */
  @media (max-width: 48rem) {
    .hero__word--soft {
      background: none;
      -webkit-text-fill-color: var(--gold-lift);
      color: var(--gold-lift);
    }
  }
}

/* The full-viewport composition: image fills the section; the type block sits
   at its base; the glass strip pins to the very bottom. */
.hero--home {
  display: flex;
  align-items: flex-end;
  /* Viewport-filling, but CAPPED: without the ceiling, a very tall viewport
     stretches the absolute-fill photograph into a wall of blur. */
  min-height: clamp(38rem, 96svh, 66rem);
  overflow: hidden;
}

.hero__media {
  position: absolute;
  inset: 0;
}

.hero__media img,
.hero__media picture {
  width: 100%;
  height: 100%;
}

.hero__media img {
  object-fit: cover;
  object-position: 50% 42%;
}

/* Two scrims, straight from the reference: a vertical one so the type holds,
   and one from the left so the headline reads on wide screens without dimming
   the whole photograph. Espresso-toned, never grey. */
.hero__media::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, #140D06EB, #140D068C 38%, #140D0640);
}

/* A phone shows a narrow slice of a wide photograph, so the bright middle of
   the frame ends up directly behind the words. The scrim goes heavier and
   reaches higher there: legibility beats a clean photograph, every time. */
@media (max-width: 48rem) {
  .hero__media::before {
    background: linear-gradient(to top, #140D06F7 0%, #140D06E0 45%, #140D0680 100%);
  }
}

.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, #140D06B3, #140D0600 60%);
}

.hero__panel {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: start;
  gap: var(--s-3);
  padding-block: var(--s-7) 12rem;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
  margin-top: var(--s-2);
}

/* The glass strip: published facts only, pinned to the hero's foot. */
.hero__strip {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  border-top: var(--hair) solid rgb(250 243 227 / 16%);
  background: rgb(20 13 6 / 42%);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

.hero__facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 0;
  padding-block: var(--s-3);
}

.hero__facts > div {
  display: grid;
  gap: 0.3rem;
  padding-inline: var(--s-4);
  border-left: var(--hair) solid rgb(250 243 227 / 12%);
}

.hero__facts > div:first-child {
  padding-left: 0;
  border-left: 0;
}

/* Value above, label below — the reference's proof-strip order. The markup
   stays dt-then-dd for validity; the grid reorders visually. */
.hero__facts dd {
  order: -1;
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(0.95rem, 0.8rem + 0.7vw, 1.35rem);
  line-height: 1.25;
  color: var(--soft-white);
}

.hero__facts dd a {
  color: inherit;
  text-decoration: none;
}

.hero__facts dd a:hover { color: var(--gold-lift); }

.hero__facts dt {
  font-size: max(0.6875rem, 0.6875rem);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgb(250 243 227 / 55%);
}

/* The opening choreography: once, on load, and only for people who have not
   asked for reduced motion. Every rule that starts something at opacity 0 is
   the same rule that animates it back, so no path ships a blank hero. */
@media (prefers-reduced-motion: no-preference) {
  .hero--home .hero__eyebrow,
  .hero--home .hero__title,
  .hero--home .hero__lede,
  .hero--home .hero__actions,
  .hero--home .hero__strip { animation: hero-rise 800ms var(--ease) both; }

  /* The interior spine label FADES only: an animation that declares `transform`
     would override its writing-mode rotation and snap it at the end. */
  .hero-band .hero__side { animation: hero-fade 900ms var(--ease) both; }

  .hero--home .hero__title { animation-delay: 90ms; }
  .hero--home .hero__lede { animation-delay: 200ms; }
  .hero--home .hero__actions { animation-delay: 320ms; }
  .hero--home .hero__strip { animation-delay: 450ms; }
}

@keyframes hero-rise {
  from {
    opacity: 0;
    transform: translateY(0.7rem);
  }
}

@keyframes rule-draw {
  from { transform: scaleX(0); }
}

@keyframes hero-fade {
  from { opacity: 0; }
}

/* ── the museum mount ──────────────────────────────────────────────────────
   An espresso mat and a coat-gold keyline around a photograph at its own
   ratio. The hero portrait and the plate-wall exhibits wear it; the gallery
   grids keep quiet room-coloured mounts, or gold would stop meaning anything. */
.mount {
  background: var(--bg-raised);
  padding: clamp(0.55rem, 1.2vw, 1rem);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-card);
  /* Depth and a breath of golden light — the plate glows in the dark room. */
  box-shadow: 0 1.4rem 3.4rem #00000059, 0 0 3.6rem #C9944C17;
}

.mount img {
  display: block;
  width: 100%;
  height: auto;
  border: var(--hair) solid var(--accent);
  border-radius: var(--radius);
}


/* ── the gilded drop cap ───────────────────────────────────────────────────
   The reading columns open the way an album's foreword does. Lead paragraphs
   only — a drop cap on every paragraph is a ransom note. */
.prose--lead > p:first-of-type::first-letter {
  float: left;
  font-family: var(--font-display);
  font-size: 3.4em;
  line-height: 0.8;
  padding: 0.06em 0.14em 0 0;
  font-weight: 400;
  color: var(--gold-ink);
}

.forest .prose--lead > p:first-of-type::first-letter,
.inverse .prose--lead > p:first-of-type::first-letter { color: var(--gold-lift); }

/* ── the interior page opening ─────────────────────────────────────────────

   NOT a letterboxed photograph. A fixed-height cover crop of a portrait frame
   shows 16–35% of it depending on the visitor's screen — that is the arithmetic
   that was cutting dogs' heads in half. An interior page opens on a forest band:
   the title on the left, the photograph as a 4:5 plate on the right, which shows
   75–100% of every frame in this library on every screen there is. */

.hero-band {
  position: relative;
  border-bottom: var(--hair) solid var(--line);
  padding-block: var(--s-6);
}

/* The same candle-glow the home spread has, so every page opens in the same
   light. */
.hero-band::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(46rem 28rem at 72% 50%, #C9944C1F, #C9944C00 70%);
}

.hero-band__grid {
  position: relative;
  display: grid;
  gap: var(--s-5) var(--s-6);
  align-items: center;
}

@media (min-width: 52rem) {
  .hero-band__grid {
    grid-template-columns: auto minmax(min(100%, 18rem), 7fr) minmax(min(100%, 16rem), 5fr);
  }

  .hero-band__grid > .hero__side {
    align-self: start;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
  }
}

.hero-band__text {
  display: grid;
  justify-items: start;
  gap: var(--s-3);
}

.hero-band__media { max-width: 28rem; justify-self: end; }

/* Interior pages compose themselves the way the home spread does — once. The
   spine label is covered by the fade rule beside the home choreography. */
@media (prefers-reduced-motion: no-preference) {
  .hero-band__text,
  .hero-band__media { animation: hero-rise 800ms var(--ease) both; }

  .hero-band__media { animation-delay: 180ms; }

  .hero-band .hero__rule {
    transform-origin: left center;
    animation: rule-draw 640ms var(--ease) 220ms both;
  }
}

/* The small tracked label above the hero title — the brief's own micro-typography:
   `WOOLWATER SPANIELS` in small caps with generous letter spacing, above the large
   serif. On forest it is soft taupe; the title under it carries the weight. */
.hero__eyebrow {
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--label);
  line-height: 1.4;
}

.hero__title {
  font-family: var(--font-display);
  /* The full-bleed hero can carry the full display size — but the ceiling stays
     5.6rem: above about 6rem a heading is shouting rather than designing. */
  font-size: var(--t-hero);
  font-weight: 300;
  line-height: 1.04;
  letter-spacing: -0.014em;
  color: var(--ink);
  max-width: 14ch;
  text-wrap: balance;
}

/* The short golden-brown hairline between the label and the title — the mark the
   .h2::before rule echoes down the rest of the page. */
.hero__rule {
  border: 0;
  width: 3rem;
  margin: 0;
  border-top: var(--hair) solid var(--accent);
}

.hero__lede {
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1rem + 0.4vw, 1.3rem);
  font-weight: 300;
  font-style: italic;
  line-height: 1.42;
  color: var(--body);
  max-width: 30ch;
}

.hero__actions { margin-top: var(--s-2); }

/* ── figures ───────────────────────────────────────────────────────────────

   EVERY placed photograph is mounted — the album's one law of photography.
   A mat in the raised surface colour, a hairline, a keyline in the coat gold
   around the print itself, and depth. On parchment the shadow is a breath; in
   the dark rooms it is deeper and carries a trace of golden light. Only the
   gallery grids opt out into their quieter room-coloured mounts. */

.figure {
  margin: 0;
  background: var(--bg-raised);
  padding: clamp(0.55rem, 1.2vw, 1rem);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: 0 0.8rem 2rem #00000021;
}

.forest .figure,
.inverse .figure {
  box-shadow: 0 1.4rem 3.4rem #00000059, 0 0 3.6rem #C9944C17;
}

.figure img {
  width: 100%;
  border: var(--hair) solid var(--accent);
  border-radius: var(--radius);
}

/* A frame shown at its own aspect ratio, uncropped. For anything a crop would destroy — the brand
   plate has the business's name typeset along its bottom edge. */
.figure--native img { border-radius: var(--radius); }

.figure__caption {
  margin-top: var(--s-2);
  font-size: var(--t-fine);
  color: var(--label);
  max-width: 34ch;
}

/* The frontispiece: the B&W brand plate as a small mounted object beside the
   foreword — never a half-page grayscale slab on a golden page. */
.frontispiece { max-width: 16rem; }

/* A cropped plate. The ratio is set by a class, never inline, and every crop
   has a focal point chosen for the frame — a centre crop takes the head off a
   phone photograph of a dog, because the head is in the upper third. */
.plate-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ar-portrait { aspect-ratio: 4 / 5; }
.ar-tall { aspect-ratio: 3 / 4; }
.ar-square { aspect-ratio: 1 / 1; }
.ar-wide { aspect-ratio: 16 / 10; }
.ar-cinema { aspect-ratio: 21 / 9; }

.plate-frame { overflow: hidden; border-radius: var(--radius); }


/* focal points. One class per crop the site actually uses, each aimed by LOOKING
   at the frames it crops — the library's heads sit between ~12% and ~50% of frame
   height, so a letterboxing crop has very little room to be wrong in. */
.f-top img { object-position: 50% 12%; }
.f-upper img { object-position: 50% 26%; }
.f-face img { object-position: 50% 32%; }
.f-centre img { object-position: 50% 50%; }
.f-left img { object-position: 34% 28%; }
.f-right img { object-position: 66% 28%; }

/* Full-bleed. The section it sits in carries no .shell, so this is already the full width of the
   viewport — and it must NOT reach for 100vw to say so. 100vw includes the scrollbar, so on a
   desktop browser it is about 15px wider than the page and the whole document scrolls sideways by
   exactly that much. `overflow-x: clip` on the body does not save you: the scroll happens on the
   root element. */
.bleed { width: 100%; }

/* ── the plate wall ─────────────────────────────────────────────────────────

   The signature device, and the answer to "half the dog's head is cut off": a
   photograph HUNG rather than cropped. A deep-room band, one uncropped frame at
   its own aspect ratio, capped so it sits inside a viewport, a hairline mount,
   and ink around it. Nothing on a wall band is ever cropped — if a frame does
   not suit the wall, choose a different frame, never a crop. */

.band--wall { padding-block: var(--s-6); }

.wall {
  display: grid;
  justify-items: center;
}

.wall .figure {
  background: var(--bg-raised);
  padding: clamp(0.55rem, 1.2vw, 1rem);
  border: var(--hair) solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: 0 1.4rem 3.4rem #00000059, 0 0 3.6rem #C9944C17;
}

.wall .figure img {
  width: auto;
  max-width: 100%;
  max-height: min(80svh, 42rem);
  margin-inline: auto;
  border: var(--hair) solid var(--accent);
}

/* ── specifications, indexes, colour names ───────────────────────────────── */

/* A labelled specification. Tabular figures, so the heights and weights line up
   without being asked. */
.spec {
  display: grid;
  gap: 0;
  border-top: var(--hair) solid var(--rule);
  font-variant-numeric: tabular-nums lining-nums;
}

.spec__row {
  display: grid;
  gap: 0.15rem var(--s-4);
  padding-block: var(--s-3);
  border-bottom: var(--hair) solid var(--line);
}

@media (min-width: 40rem) {
  .spec__row { grid-template-columns: minmax(min(100%, 9rem), 4fr) minmax(min(100%, 12rem), 9fr); }
}

.spec__label {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--label);
  line-height: 1.5;
}

.spec__value { color: var(--n-08); }

.forest .spec__value,
.inverse .spec__value { color: var(--body); }

.spec__sex {
  display: flex;
  align-items: baseline;
  gap: 0.5em;
}

/* The source's own ♂ / ♀ notation. Hidden from screen readers — the sex is named in words beside it —
   but a sighted reader still has to see it, so it clears 4.5:1 rather than sitting at rule grey. */
.spec__symbol {
  font-size: 1.1em;
  color: var(--n-06);
}

.forest .spec__symbol,
.inverse .spec__symbol { color: var(--label); }

/* The four qualities.
   NOT numbered. 01 / 02 / 03 markers say "this is a sequence and the order carries
   information", and these four are a set of characteristics in no order at all —
   numbering them would be scaffolding borrowed from other people's landing pages.
   They are a description list: the term in the display serif, the definition beside
   it, hairline-ruled. The term does the work the number was pretending to do. */
.qualities {
  margin: 0;
  border-top: var(--hair) solid var(--rule);
}

.qualities__item {
  display: grid;
  gap: 0.35rem var(--s-5);
  padding-block: var(--s-4);
  border-bottom: var(--hair) solid var(--line);
}

@media (min-width: 52rem) {
  .qualities__item { grid-template-columns: minmax(min(100%, 11rem), 5fr) minmax(min(100%, 14rem), 8fr); }
}

.qualities__title {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: 300;
  line-height: 1.14;
  letter-spacing: var(--track-display);
  color: var(--ink);
  text-wrap: balance;
}

.qualities__body { color: var(--body); max-width: 46ch; }

/* The gilded index: the home page sets the four qualities full-width, terms
   enormous — a chapter list, not a feature grid. Inside narrow columns the
   base sizes above still apply. */
.qualities--index .qualities__item { padding-block: var(--s-5); }

.qualities--index .qualities__title {
  font-size: var(--t-h2);
  font-weight: 250;
}

.qualities--index .qualities__body {
  align-self: center;
  font-size: var(--t-lede);
  font-family: var(--font-display);
  font-weight: 300;
  line-height: 1.4;
}

/* Coat colours are set as a typographic index. content.ts carries hex swatches
   and they are an editorial addition rather than the breeder's — rendering them
   would publish a colour standard the business has never claimed. The names
   carry the information; the photograph beside them carries the colour. */
.colours { display: grid; gap: var(--s-5); }

@media (min-width: 40rem) {
  .colours { grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }
}

.colours__group { border-top: var(--hair) solid var(--rule); padding-top: var(--s-2); }

.colours__label {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--label);
}

.colours__list { list-style: none; padding: 0; margin-top: var(--s-2); }

.colours__name {
  font-family: var(--font-display);
  font-size: var(--t-h4);
  font-weight: 300;
  line-height: 1.55;
  color: var(--ink);
}

/* A plain marked list, for the philosophy's focus points and what a puppy
   leaves with. The mark is a hairline rule, not a bullet. */
.marks { list-style: none; padding: 0; }

.marks > li {
  position: relative;
  padding-left: 2rem;
  padding-block: var(--s-2);
  border-bottom: var(--hair) solid var(--line);
  color: var(--n-08);
}

.forest .marks > li,
.inverse .marks > li { color: var(--body); }

.marks > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(var(--s-2) + 0.75em);
  width: 1.1rem;
  border-top: var(--hair) solid var(--accent);
}

.marks > li:last-child { border-bottom: 0; }

/* A pull quote — the breeder's opening line, and the closing statement. Hanging
   punctuation where the engine offers it: the opening mark sits in the margin
   and the letterforms stay on the grid, the way a book sets it. */
.quote {
  font-family: var(--font-display);
  font-size: var(--t-h2);
  font-weight: 250;
  font-style: italic;
  line-height: 1.14;
  letter-spacing: var(--track-display);
  color: var(--ink);
  max-width: 26ch;
  text-wrap: balance;
  hanging-punctuation: first;
}

/* Her name, set as the letter's signature — large italic serif in the coat
   gold, under the message she wrote. Typography as a signature, not a font
   pretending to be handwriting. */
.signature {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.2rem + 1.4vw, 2.2rem);
  font-style: italic;
  font-weight: 300;
  line-height: 1.2;
  color: var(--gold-ink);
}

/* The emphasised phrase inside a kinetic statement — italic foil mid-assembly. */
.kinetic__em { font-style: italic; }

.attrib {
  display: block;
  margin-top: var(--s-3);
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-style: normal;
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--label);
}

.ready {
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ── contact ─────────────────────────────────────────────────────────────── */

.contact-card {
  border-top: var(--hair) solid var(--rule);
  padding-top: var(--s-3);
}

.contact-card__name {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: 300;
  line-height: 1.2;
  color: var(--ink);
}

.contact-card__number {
  display: inline-flex;
  align-items: center;
  min-height: 2.9rem;
  font-size: var(--t-lede);
  font-family: var(--font-display);
  font-weight: 300;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--ink);
  text-decoration: none;
  border-bottom: var(--hair) solid var(--rule);
  white-space: nowrap;
  transition: border-color var(--dur-quick) var(--ease), color var(--dur-quick) var(--ease);
}

.contact-card__number:hover { color: var(--accent); border-bottom-color: var(--accent); }

.address { font-style: normal; color: var(--body); }
.address > span { display: block; }

/* ── WhatsApp ───────────────────────────────────────────────────────────────

   Two treatments, both understated.

   `.wa-link` is the in-page call: text-led, the mark beside the words, sitting in
   the same line as every other action on the page.

   `.wa-float` is the persistent one. It is the size of a single touch target, in
   the identity colour, with no shadow and no round shape — the brief rules out "a
   large generic floating black rectangle", which is what the previous version was.
   Its label appears from 26rem, where there is room for it; below that the mark
   carries it and the accessible name is on the link. */

.wa-link {
  display: inline-flex;
  align-items: center;
  gap: 0.55em;
  min-height: 2.9rem;
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  transition: color var(--dur-quick) var(--ease);
}

.wa-link:hover { color: var(--accent); }

.wa-link svg,
.wa-float__btn svg {
  width: 1.05em;
  height: 1.05em;
  flex: none;
  fill: currentColor;
}

.wa-float {
  position: fixed;
  right: max(var(--s-3), env(safe-area-inset-right));
  bottom: max(var(--s-3), env(safe-area-inset-bottom));
  z-index: var(--z-float);
}

.wa-float__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-width: 3.25rem;
  min-height: 3.25rem;
  padding: 0 1.1rem;
  font-family: var(--font-ui);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--soft-white);
  background: var(--forest);
  border: var(--hair) solid var(--forest-lift);
  border-radius: var(--radius-pill);
  box-shadow: 0 0.6rem 1.6rem #00000047;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
    translate var(--dur) var(--ease);
}

.wa-float__btn:hover { translate: 0 -2px; }

.wa-float__btn:hover { background: var(--forest-deep); border-color: var(--gold-lift); }

.wa-float__label { display: none; }

@media (min-width: 26rem) {
  .wa-float__label { display: inline; }
}

/* The overlay covers the viewport at a higher layer, but a fixed control under a
   94%-opaque backdrop still ghosts through on some compositors. */
body.lightbox-open .wa-float { display: none; }

/* Room for it, so it never sits on top of the last line of the footer. */
.foot__base { padding-bottom: 3.5rem; }

@media (min-width: 40rem) {
  .foot__base { padding-bottom: 0; }
}

/* ── the gallery ────────────────────────────────────────────────────────────

   A TWELVE-COLUMN EDITORIAL GRID, not a uniform thumbnail grid and not a sideways
   scroller. gallery.ts assigns each successive plate a column span from a repeating
   rhythm — one large, two smaller, one wide, a pair — and every plate keeps its
   NATIVE aspect ratio, so rows genuinely differ in height instead of lining up like
   a contact sheet. Nothing is cropped anywhere in here.

   The library is roughly three quarters portrait, which is why the rhythm mixes wide
   and narrow spans rather than assuming landscape. */

.gallery {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

@media (min-width: 52rem) {
  .gallery { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--s-4); }

  .plate--4 { grid-column: span 4; }
  .plate--5 { grid-column: span 5; }
  .plate--6 { grid-column: span 6; }
  .plate--7 { grid-column: span 7; }
  .plate--12 { grid-column: span 12; }
}

.plate { margin: 0; }

/* Every gallery plate is mounted: a hairline in the room's own line colour, the way
   a frame sits on an exhibition wall. The mount is the room's, not the plate's, so
   it recolours with the environment. */
.plate__link {
  display: block;
  overflow: hidden;
  border: var(--hair) solid var(--line);
  border-radius: var(--radius);
  background: var(--n-02);
}

.plate__link img { width: 100%; transition: opacity var(--dur) var(--ease); }

/* No zoom on hover. The photograph does not need to move to be worth looking at, and
   a scaling image is the effect the brief rules out by name. It dims very slightly. */
.plate__link:hover img { opacity: 0.88; }

/* Three frames, laid out. The wide one leads. */
.gallery--moment { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (min-width: 52rem) {
  .gallery--moment { grid-template-columns: repeat(12, minmax(0, 1fr)); }
}

/* The lightbox. public/gallery.js upgrades the plate links into this overlay;
   with the script blocked or still loading, a click opens the photograph. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: grid;
  place-items: center;
  padding: var(--s-3);
  background: rgb(16 13 9 / 94%);
}

.lightbox[hidden] { display: none; }

.lightbox__figure {
  display: grid;
  gap: var(--s-2);
  justify-items: center;
  margin: 0;
  max-width: min(100%, 68rem);
}

.lightbox img {
  max-width: 100%;
  max-height: 82svh;
  width: auto;
  object-fit: contain;
  border-radius: var(--radius);
  background: var(--charcoal);
}

.lightbox__count {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--gold-lift);
  font-variant-numeric: tabular-nums lining-nums;
}

/* While the overlay is open the page behind it must not scroll. Set on the root as well as the body
   because on iOS the body is not the scroller. */
body.lightbox-open { overflow: hidden; }

.lightbox__close,
.lightbox__nav,
.lightbox__prev,
.lightbox__next {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 3rem;
  min-height: 3rem;
  background: transparent;
  color: var(--soft-white);
  /* Gilded chrome: the overlay's controls carry the coat gold, quietly. */
  border: var(--hair) solid rgb(227 179 107 / 38%);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color var(--dur-quick) var(--ease), background var(--dur-quick) var(--ease);
}

.lightbox__close:hover,
.lightbox__nav:hover { background: rgb(227 179 107 / 14%); border-color: var(--gold-lift); }

.lightbox__close { top: var(--s-3); right: var(--s-3); }
.lightbox__prev { left: var(--s-3); }
.lightbox__next { right: var(--s-3); }

/* Set by gallery.js on the root once the enhancement is running. Nothing depends on it today; it is
   the hook for any rule that must only apply when the lightbox is actually available. */
.js .plate__link { cursor: zoom-in; }

/* ── the enquiry form ────────────────────────────────────────────────────── */

.form { display: grid; gap: var(--s-3); }

.field { display: grid; gap: var(--s-1); }

.field label {
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--label);
}

.field input,
.field textarea {
  width: 100%;
  min-height: 3rem;
  padding: 0.7rem 0.85rem;
  color: var(--n-09);
  background: var(--field);
  border: var(--hair) solid var(--rule);
  border-radius: var(--radius);
  transition: border-color var(--dur-quick) var(--ease);
}

.field textarea { min-height: 7rem; resize: vertical; line-height: 1.55; }

.field input:hover,
.field textarea:hover { border-color: var(--n-05); }

.field__hint { font-size: var(--t-fine); color: var(--label); }

/* "optional" beside a field label. It was 0.85em of an already-small label — about 9px, in rule grey.
   A person filling in a form needs to be able to read which fields they can skip. */
.field__optional {
  font-size: var(--t-fine);
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--n-06);
}

/* A full border and a tint, never a coloured side-stripe. */
.form__error {
  padding: var(--s-2) var(--s-3);
  color: oklch(0.35 0.11 32);
  background: oklch(0.94 0.03 40);
  border: var(--hair) solid oklch(0.72 0.09 36);
  border-radius: var(--radius);
}

/* ── footer ──────────────────────────────────────────────────────────────── */

/* The footer is forest, and so is the contact band that usually precedes it — the
   brief assigns deep forest to both. The hairline is what keeps them two rooms. */
.foot {
  padding-block: var(--s-6) var(--s-5);
  border-top: var(--hair) solid var(--line);
}

/* Three tracks — brand, pages, telephone — in ONE grid rather than a grid inside a grid. The nested
   version overflowed at 1024px: the outer column was 460px and the two inner tracks needed 552px. */
.foot__grid {
  display: grid;
  gap: var(--s-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  align-items: start;
}

.foot .wordmark { color: var(--ink); }
.foot .wordmark__second { color: var(--label); }

.foot__nav {
  display: grid;
  gap: 0.1rem;
  list-style: none;
  padding: 0;
  margin: 0;
}

.foot__nav a {
  display: flex;
  align-items: center;
  min-height: 2.6rem;
  font-size: var(--t-fine);
  color: var(--body);
  text-decoration: none;
  transition: color var(--dur-quick) var(--ease);
}

.foot__nav a:hover { color: var(--ink); }

.foot__base {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  align-items: center;
  justify-content: space-between;
  margin-top: var(--s-5);
  padding-top: var(--s-3);
  border-top: var(--hair) solid var(--line);
  font-size: var(--t-fine);
  color: var(--label);
}

.foot__social { display: flex; flex-wrap: wrap; gap: var(--s-3); }

.foot__social a {
  display: inline-flex;
  align-items: center;
  min-height: 2.6rem;
  color: var(--body);
  text-decoration: none;
  border-bottom: var(--hair) solid transparent;
}

.foot__social a:hover { color: var(--ink); border-bottom-color: var(--rule); }

/* ── motion ──────────────────────────────────────────────────────────────── */

/* Gentle reveals, driven by the scroll position rather than by JavaScript.
   Guarded by @supports, and the rule that sets opacity: 0 is the same rule that
   animates it back — so a browser without scroll-driven animations, and anybody
   who asked for reduced motion, gets the finished page rather than a blank one. */
/* THE WIPE IS DEAD, ruled 9 August 2026: "images begins middle cut and expands
   to full with scroll bug. should be full images instantly." On a phone the
   entry range spans most of the screen, so a photograph sat half-masked for
   the whole time it was being looked at — the effect read as a broken image,
   not as a page turn. A photograph now arrives whole, everywhere, always.
   `.reveal` stays in the markup as a hook with no behaviour behind it. */

@keyframes unveil {
  /* Kept only so nothing references a missing name; no rule uses it since the
     wipe was ruled out. */
  to {
    opacity: 1;
    transform: none;
    clip-path: inset(0 0 0 0);
  }
}

@keyframes rise {
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
}

/* ── print ───────────────────────────────────────────────────────────────── */

@media print {
  .masthead,
  .menu,
  .hero__media,
  .hero-band__media,
  .gallery,
  .lightbox { display: none; }

  /* On paper the photograph is gone, so the panel must flow rather than float. */
  .hero__panel { position: static; }

  body { color: #000; background: #fff; }

  .inverse { color: #000; background: #fff; }

  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.8em; }
}
