/* ---- tokens ---- */
/* Jenzen Group homepage, redesign option 5a "generous rail" - design tokens.
 *
 * THE SINGLE PLACE EVERY FAMILY, EVERY BRAND COLOUR AND EVERY MEASURE IS DECLARED. Section CSS
 * may only read var(--...); it must never name a font family or a hex directly. That is what
 * makes the licensed-font swap, and a colour decision, a one-file edit.
 *
 * PROVENANCE. Every value below comes from one of exactly three places, and design/geometry.json
 * records which, row by row:
 *   README   the "5a - generous rail" table in
 *            projects/jenzen-group-website-v2-h1/design/redesign-5a-5e/README.md. The handoff says
 *            plainly that every number in that table is the value to hit, so where the table and
 *            the reference prototype disagree, THE TABLE WINS.
 *   PROTOTYPE  design/redesign-5a-5e/jenzen-homepage-5a.html, for the values the table does not
 *            state (gutter widths, the small type sizes, the mobile step-downs it omits).
 *   BUILD    a decision taken here, where neither says anything. Each one is commented.
 *
 * -- FONTS: A SUBSTITUTE, NOT THE DESIGN FACE ------------------------------------------------
 * Playfair Display substitutes Benton Modern D (condensed), which is not licensed to this
 * agency. Three committed woff2 (latin subset), declared "Jenzen Serif" exactly as the reference
 * prototype declares it, loaded with @font-face plus a <link rel="preload"> in src/head.html.
 *
 * NO CSS @import AND NO GOOGLE FONTS / TYPEKIT LINK. An @import is render-blocking and cost this
 * agency 36 Lighthouse points once already. tools/geometry-gate.mjs asserts the built stylesheet
 * contains no @import at all.
 *
 * ⚠ 5a CARRIES NO --k-* CONDENSE FACTORS, and their absence is deliberate rather than an
 * omission. The v2-h1 build squeezes every block horizontally with transform: scaleX() because it
 * reproduces an InDesign comp whose boxes are pinned to the pixel and whose face is condensed.
 * THIS design has no comp: the reference prototype was authored in Playfair itself, at the sizes
 * and measures in the README's table, so the line breaks the table implies are the line breaks
 * Playfair actually produces. Adding a condense factor here would distort type to match a
 * measurement nobody took. If Benton Modern D is ever licensed, every block's line count has to
 * be re-measured, because a condensed face sets the same copy narrower.
 */

@font-face {
  font-family: 'Jenzen Serif';
  src: url('/fonts/playfair-display.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Jenzen Serif';
  src: url('/fonts/playfair-display-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ⚠ THE 400 ITALIC FACE IS GONE, 24/08/26. Its ONLY user on this page was the about pull-quote,
   which Keith removed. A declared @font-face with nothing to render is a face the gate reports as
   "unloaded" forever and a file shipped for no one, so the declaration and the .woff2 both go
   rather than the check being relaxed. If italic copy ever returns, restore this block AND
   playfair-display-italic.woff2 in build.mjs FONTS and geometry.json fonts[] together. */

:root {
  /* ---- Colour. README token table. ----
     THE GOLD SPLIT IS THE WHOLE CONTRAST STORY OF THIS BUILD, so it is spelled out here rather
     than left to the register to explain.

       --c-gold      #ba9765  brand gold. 2.73:1 on white. RULES AND FIELDS ONLY. Never text.
       --c-gold-text #8a6a3c  4.99:1 on white and 4.75:1 on this page's paper, both MEASURED, so it
                              CLEARS the WCAG 2.2 AA 4.5:1 floor with room. This is
                              the only addition to the brand palette and it exists for TEXT.

     v2-h1 carried three registered KNOWN-FAIL pairs, every one of them brand gold used as text
     (a pull-quote, a footer address, two footer legal labels). The pull-quote's failure is gone
     here by using --c-gold-text, which is what the README's "gold text" row is for. What 5a
     cannot escape is the other direction: its footer field IS the brand gold, so white 14px text
     on it measures the same 2.73:1 from the other side. Those pairs are registered in
     design/contrast-register.json, still measured, and surfaced for Keith - they are inherent to
     a gold footer, not a build defect. tools/contrast.mjs additionally sweeps for ANY element
     whose own text computes to #ba9765 and fails the build if one appears. */
  --c-navy: #00406e; /* text, submit fill, 98 Churchill plate */
  --c-gold: #ba9765; /* rules, hero base rule, footer field, credit field. NEVER text. */
  --c-gold-text: #8a6a3c; /* pull-quote, link hover. 4.75:1 on the paper, measured. */
  --c-olive: #9c9666; /* Southwark Square plate only */
  /* 98 Churchill identity, sampled from the supplied brand pack (2649_Churchill Road Marketing.pdf,
     24/08/26): sage ground #7c9082 with the peach #e9c3a0 lockup. Like --c-olive, this colour
     exists for ONE element - that project's plate - and is never used as type. */
  --c-churchill-sage: #7c9082;
  --c-white: #ffffff; /* the form card, and the reversed WORDMARK on the gold field */

  /* Type on the gold field. Keith's production sign-off, 24/08/26, taken at the launch of 5a on
     jenzengroup.com.au: the brand gold FIELD is kept and the TYPE on it darkens, which is the
     option tools/contrast.mjs costed. White on #ba9765 measures 2.73:1 and fails AA on the legal
     row and the credit label; #3d2f18 on #ba9765 measures 4.76:1 and clears the 4.5 floor while
     leaving the field itself on the brand value. Retires KNOWN-FAIL JZ5A-CF-01 and JZ5A-CF-02. */
  --c-on-gold: #3d2f18;

  /* Neither option uses pure white for the page. 5a's paper is #fbf9f5. */
  --c-paper: #fbf9f5;

  /* Input borders. A 1px line on white, never a fill: the 4d treatment puts the label INSIDE the
     field, so a tinted fill would compete with placeholder text that is doing a label's job. */
  --c-fieldline: #dde4ea;

  /* PROTOTYPE. The placeholder colour, and in this treatment the placeholder IS the label.
     ⚠ #7c8894 on white measures 3.62:1, under the 4.5:1 normal-text floor. It is registered as a
     KNOWN-FAIL rather than silently darkened, because darkening it is a visible design change to
     the one element Keith is being asked to compare. See design/DESIGN-SPEC.md, open question 2. */
  --c-placeholder: #7c8894;

  /* Hairline above the portfolio band. PROTOTYPE: rgba(0,64,110,.35). Decorative, no text. */
  --c-hair: rgba(0, 64, 110, 0.35);

  /* BUILD. Error red. No error state was drawn in either reference file. Chosen to clear AA on
     the only two surfaces an error can appear on here: 10.4:1 on the white form card, 9.6:1 on
     the #fbf9f5 paper. */
  --c-error: #7a1c14;

  /* ---- Type ---- */
  --font-body: 'Jenzen Serif', 'Playfair Display', Georgia, 'Times New Roman', serif;

  /* README, 5a table. Sizes and line pitches together, because the table states them as pairs
     and the pairs are what the copy was written to. */
  --fs-nav: 12.5px;
  --k-nav: 0.16em;

  --fs-headline: 38px;
  --lh-headline: 1.2;

  --fs-body: 18px;
  --lh-body: 1.75;

  --fs-quote: 25px;
  --lh-quote: 1.5;

  --fs-pf-title: 64px;
  --lh-pf-title: 1.04;

  --fs-pf-name: 26px;
  --lh-pf-name: 1.25;

  --fs-pf-body: 17px;
  --lh-pf-body: 1.7;

  /* PROTOTYPE. The form, the legal row and the credit label - the table is silent on all three. */
  --fs-field: 15px;
  --fs-submit: 15px;
  --k-submit: 0.14em;
  --fs-legal: 14px;
  --fs-credit: 14px;
  --k-credit: 0.06em;

  /* ---- Layout. README, 5a table. ---- */
  --rail: 1120px; /* the rail's own width, centred */
  --gutter: 24px; /* PROTOTYPE: .rail{padding:0 24px}. Content measure is 1120 - 48 = 1072. */

  /* THE VERTICAL RHYTHM, and the one token that CHANGES WITH THE BREAKPOINT. The README states
     both values - 160px per band, 72px at 767 and below - and this file is written mobile-first
     like every stylesheet in the build, so --band holds the mobile value and base.css swaps in
     --band-wide from 768 up. Declaring 160 here instead would silently give a phone the desktop
     rhythm, which is how this build first measured 160px bands at 375. */
  --band: 72px;
  --band-wide: 160px;

  --h-header: 176px;
  --w-header-logo: 222px;
  --nav-gap: 64px;

  /* THE HERO IS 719 OF PHOTOGRAPH PLUS A 3px GOLD RULE, so the band is 722.
     719 is not a chosen number: hero-jenzen-j06.jpg is 2732 x 1438, and 1366 / (2732/1438) =
     719.0. That is what the README means by "the photograph's own ratio at 1366, so nothing is
     cropped". box-sizing is border-box everywhere, so the band height has to carry the rule as
     well or every section below it rides 3px high. */
  --h-hero: 719px;
  --hero-rule: 3px;
  --w-monogram: 104px;
  --monogram-base: 64px; /* off the base of the photograph, not the base of the band */

  --about-col: 470px; /* the photograph's track */
  --about-gap: 96px;
  --about-stack-gap: 44px;
  --about-photo-h: 620px;
  --about-quote-indent: 32px;

  --pf-title-gap: 96px; /* below the Portfolio heading */
  --pf-row-gap: 96px; /* between rows */
  --pf-pic: 620px; /* the picture's track; every frame is 3:2, so 413.33 tall */
  --pf-gap: 96px;
  --pf-copy-gap: 18px;
  --pf-rule-w: 60px; /* PROTOTYPE: the 60 x 1 gold rule above each project name */

  --footer-pad-top: 112px;
  --footer-pad-bottom: 72px;
  --footer-form-col: 560px;
  --footer-gap: 96px;
  --w-footer-logo: 238px;
  --form-card-pad: 40px;
  --legal-pad-top: 16px; /* PROTOTYPE */

  --credit-pad-top: 44px;
  --credit-pad-bottom: 52px;
  --credit-gap: 16px; /* PROTOTYPE: the gap between "Marketing by" and the mark it sits above */
  --w-credit-logo: 158px; /* PROTOTYPE */

  /* ---- The form. README: treatment 4d. ---- */
  --field-h: 52px; /* clears the 44px touch minimum, so the same size is used at mobile */
  --field-pad-x: 18px;
  --textarea-min-h: 110px;
  --field-gap: 14px; /* PROTOTYPE */
  --radius: 0; /* radius 0 everywhere in both options */

  /* ---- Focus ring. In neither reference file; required by WCAG 2.2 (2.4.7, 2.4.11).
     Navy at a 2px offset clears 3:1 against every surface this page has: 10.7:1 on the paper,
     4.0:1 on the gold field, and it sits OUTSIDE the navy submit button so it lands on the white
     card rather than disappearing into the fill. ---- */
  --focus-ring: 3px solid #00406e;
  --focus-offset: 2px;
}

/* ---- base ---- */
/* Jenzen Group 5a - reset, page shell, the shared rail, the brand plate, and the two behaviours
 * that are in neither reference file: the visibly-non-interactive state and the mobile nav
 * disclosure.
 *
 * ONE DOM AT EVERY WIDTH. Nothing is reordered, duplicated or hidden between the modes, with one
 * declared exception: below 768px the inline nav is replaced by a menu button that discloses the
 * same three anchors. The README requires that button ("under 768px the nav collapses to a menu
 * button, three 1.5px rules, 24px wide") and the reference prototype simply sets the nav to
 * display:none, so the button and its open state are the one piece of visible UI in this build
 * with no reference drawing behind them.
 *
 *   >= 1024px   two-column: About beside its photograph, portfolio rows alternating
 *   768-1023    About and the rows stack; the footer stacks and centres
 *   320-767     one column, 72px bands, the nav behind a menu button
 *
 * The CSS is written MOBILE-FIRST, so every rule below is a progressive widening and the desktop
 * spec lives in min-width blocks. No max-width queries and no fractional breakpoints: a pass at
 * 1024 says nothing about 1023, and tools/responsive-gate.mjs renders both sides of every one.
 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  /* The nav anchors are the only navigation this page has. Smooth is the designed behaviour; the
     reduced-motion block at the foot of this file puts it back to auto. */
  scroll-behavior: smooth;
}

body {
  margin: 0;
  /* NEITHER OPTION USES PURE WHITE. Painted explicitly rather than left to the UA. */
  background: var(--c-paper);
  color: var(--c-navy);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

img,
svg {
  display: block;
  max-width: 100%;
}

h1,
h2,
h3,
p,
figure {
  margin: 0;
}

a {
  color: inherit;
}

button,
input,
textarea {
  font: inherit;
  color: inherit;
}

/* THE `hidden` ATTRIBUTE MUST ALWAYS WIN.
 *
 * The UA implements it as `[hidden] { display: none }` at specificity (0,1,0), so ANY author
 * `display` on the same element silently overrides it. The form card's own `display:flex` is
 * exactly that, so on a successful submit `form.hidden = true` would change nothing: the five
 * filled fields would stay on screen with the confirmation printed across them.
 *
 * What makes this worth a comment is HOW it was caught on the sibling build. The end-to-end test
 * asserted `form.hidden === true` and passed, because the property really was true. It was the
 * committed screenshot that showed the visitor seeing something else. Assert on rendered output,
 * never on the property you just set - tools/e2e-form.mjs measures the form's painted box. */
[hidden] {
  display: none !important;
}

/* Visible keyboard focus on every operable control. README: "Visible focus ring on every
   control." */
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* Screen-reader-only. Carries the page h1, the section headings this design draws as layout
   rather than as type, the "not yet available" notes and the link qualifiers. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Skip link. Parked ABOVE the viewport rather than off to the left: a negative left offset is the
   classic way a page acquires a phantom horizontal scrollbar, and the responsive gate is
   deliberately not allowed to be silenced with overflow-x: hidden. */
.skip-link {
  position: absolute;
  left: 0;
  top: -64px;
  z-index: 99;
  padding: 12px 20px;
  background: var(--c-navy);
  color: var(--c-white);
  font-size: var(--fs-legal);
  text-decoration: underline;
}

.skip-link:focus {
  top: 0;
}

/* ---------------------------------------------------------------------------
   THE ONE TOKEN THAT MOVES WITH THE BREAKPOINT. The README gives the vertical rhythm twice: 160px
   per band, and 72px at 767 and below. Every stylesheet here is mobile-first, so tokens.css holds
   the 72 and this is where the 160 arrives. It is in base.css rather than in each section's own
   file because all four bands share the value: splitting it four ways is how three of them stay in
   step and the fourth quietly does not.
   --------------------------------------------------------------------------- */
@media (min-width: 768px) {
  :root {
    --band: var(--band-wide);
  }
}

/* ---------------------------------------------------------------------------
   THE RAIL. README: 1120px, centred, 24px gutters. So the rail BOX is 1120 and the content
   measure inside it is 1072. Below 1120 + nothing the box simply narrows and the gutters hold,
   which is why the padding is stated in px rather than as a percentage.
   --------------------------------------------------------------------------- */
.rail {
  width: 100%;
  max-width: var(--rail);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ---------------------------------------------------------------------------
   THE 3:2 FRAME. EVERY frame in the portfolio band is 3:2, with no exceptions, plates included.

   ⛔ THERE IS EXACTLY ONE ASPECT-RATIO DECLARATION IN THIS BUILD AND IT IS THIS ONE. Where a
   source photograph is not 3:2 it is re-cropped AT EXPORT and never given its own ratio in CSS:
   ellson-3x2.jpg is 1800 x 1200 with the sky trimmed so the house fills the frame, noble-3x2.jpg
   is 1500 x 1000 centre-cropped. The uncropped originals are NOT shipped and are not even copied
   into src/. tools/geometry-gate.mjs measures the rendered width/height of every frame against
   1.5 and its sabotage run injects a 4/3 override to prove the check can fail.
   --------------------------------------------------------------------------- */
.pf-frame {
  width: 100%;
  /* ⚠ `height: auto` IS LOAD-BEARING ON THE THREE <img> FRAMES AND IS NOT TIDY-UP TO DELETE.
     Those images carry width= and height= attributes so the browser can reserve the box before the
     bytes arrive. HTML maps those attributes to CSS `width` and `height` as PRESENTATIONAL HINTS,
     which beat nothing in the author origin - but they DO win where the author sets neither. With
     `height` left unstated the hint made the height definite at the file's own 1200px, which made
     `aspect-ratio` inert, and all three photographs rendered 620 x 1200 instead of 620 x 413.33.
     Nothing looked broken in a thumbnail; the band was simply 2,700px taller than the design.
     Stating `height: auto` puts the height back under aspect-ratio's control. The plates are
     <div>s and were never affected, which is exactly why the ratio has to be MEASURED on every
     frame rather than assumed from one. */
  height: auto;
  aspect-ratio: 3 / 2;
}

.pf-pic {
  object-fit: cover;
  /* What shows while a lazy image is still in flight, so a loading row reads as a reserved frame
     rather than a hole in the page. Decorative, carries no text. */
  background: #eef1f4;
}

/* ---------------------------------------------------------------------------
   THE BRAND PLATE. Two of the five entries have no photograph and are composed plates instead.

   THE 64% OCCUPANCY IS A REQUIREMENT, NOT A POSITION. The README is explicit about why: "a plate
   that is mostly empty colour at photograph size reads as an image that failed to load." So the
   wordmark occupies 64% of the plate width, top left, 16px in, with a 1px white rule across the
   base and the rest deliberately empty. tools/geometry-gate.mjs measures that occupancy to within
   1% on both plates, at 1366 and at 375.

   SOUTHWARK uses its supplied reversed wordmark and STAYS A PLATE - never a photograph, never a
   render, never a tinted stock image. There is no building yet, and a render on a page carrying
   three real buildings would be the one false note in the section.

   98 CHURCHILL uses LIVE TEXT as a placeholder, per the README, because no 98 Churchill identity
   asset has been supplied. When the reversed asset arrives it drops in exactly like the Southwark
   mark: swap the <p> for an <img class="pf-plate__mark">, and switch this plate's row in
   design/geometry.json plates[] from the text block to the image. Nothing else moves.

   container-type: inline-size is what lets the live text scale with the PLATE rather than with
   the viewport, so the placeholder holds its 64% at every width including the stacked ones.
   --------------------------------------------------------------------------- */
.pf-plate {
  position: relative;
  container-type: inline-size;
}

.pf-plate--southwark {
  background: var(--c-olive);
}

.pf-plate--churchill {
  background: var(--c-churchill-sage);
}

.pf-plate__mark,
.pf-plate__word {
  position: absolute;
  left: 16px;
  top: 16px;
  width: 64%;
  height: auto;
}

/* ⚠ A PORTRAIT MARK CANNOT TAKE THE LANDSCAPE MARK'S 64%.
   Southwark's wordmark is 1400x723 and lies down; the supplied Ninety Eight Churchill lockup is
   1400x1468 and stands up. At 64% of a 3:2 plate's WIDTH this mark computes 0.671w tall against a
   plate only 0.667w tall, so it would overflow its own plate before the 16px inset is counted.
   The README's requirement is an OCCUPANCY one - "a plate that is mostly empty colour reads as an
   image that failed to load" - not a literal width, so it is honoured on AREA instead: 45% width
   gives this mark 0.212w^2 of ink, which is what Southwark's 64% gives its own. Recorded as a
   per-plate widthPct in design/geometry.json rather than a magic number here, so the gate still
   holds it to a stated figure. */
.pf-plate__mark--portrait {
  width: 45%;
}

.pf-plate__word {
  color: var(--c-white);
  font-size: clamp(30px, 7.6cqw, 86px);
  line-height: 0.96;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-transform: uppercase;
}

.pf-plate__rule {
  position: absolute;
  left: 16px;
  right: 16px;
  bottom: 16px;
  height: 1px;
  background: rgba(255, 255, 255, 0.55);
}

/* ---------------------------------------------------------------------------
   VISIBLY NON-INTERACTIVE. Terms & Conditions and Privacy Policy have no destination.

   Not an <a>, not a <button>, no href, no tabindex, out of the tab order, cursor default, and
   each one carries a visually-hidden "not yet available" note plus a title. role="link" with
   aria-disabled="true" is what tells assistive technology the affordance exists but cannot be
   used - a bare aria-disabled on a role-less <span> is not a valid pairing and axe-core is right
   to reject it.

   It must not look broken and it must not lie about being clickable. When a destination arrives,
   each one becomes a real anchor: one line each, plus its row moving from disabled[] to
   focusable[] in design/geometry.json.

   THE ADDRESS BESIDE THEM IS THE OPPOSITE CASE and is a real link, to Google Maps, opening in a
   new tab. tools/noninteractive-gate.mjs asserts it as a POSITIVE control in the same tab-walk
   that asserts these two are absent from it: an absence-only suite proves nothing, because four
   of five such checks once passed on a form that was completely dead.
   --------------------------------------------------------------------------- */
.is-unavailable {
  cursor: default;
  -webkit-user-select: none;
  user-select: none;
}

/* ---------------------------------------------------------------------------
   Reduced motion. Smooth scrolling and the nav disclosure are the only motion on this page.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- section: header ---- */
/* HEADER. Mobile first: the lockup and the menu button on one row, the nav disclosed beneath.
   At 768 and up the button goes and the three anchors sit inline.

   README, 5a: 176px tall, lockup 222px, nav 12.5px / 0.16em / 64px gap. At 767 and below the band
   is 84px and the lockup steps down (PROTOTYPE: 140px).

   ⚠ THE DISCLOSURE IS DRIVEN BY A CLASS, NOT BY THE `hidden` ATTRIBUTE, and that is deliberate.
   Using `hidden` would put this rule in a specificity fight with `[hidden]{display:none!important}`
   in base.css - both are (0,1,0), so the winner would be decided by which stylesheet build.mjs
   concatenated last. A layout that depends on section order inside an assembled stylesheet is a
   layout that breaks the first time somebody reorders SECTIONS. `.is-open` has no such ambiguity,
   and aria-expanded on the button is what carries the state to assistive technology either way. */

.header {
  background: var(--c-paper);
}

.header__rail {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0 24px;
  min-height: 84px;
}

.header__logo {
  width: 140px;
  height: auto;
}

/* THE MENU BUTTON. README: three 1.5px rules, 24px wide. Nothing else about it is specified, so
   the rest is a build decision: a 44px square hit area around the 24px mark, which is the touch
   minimum, and no fill and no border, so it reads as a mark rather than as button chrome this
   design does not use anywhere else. */
.header__toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 0 10px;
  border: 0;
  background: none;
  color: var(--c-navy);
  cursor: pointer;
}

.header__bar {
  display: block;
  width: 24px;
  height: 1.5px;
  background: currentColor;
}

.header__toggle:hover {
  color: var(--c-gold-text);
}

/* Closed by default at mobile widths, so the three anchors are genuinely out of the tab order
   rather than invisible-but-focusable. src/nav.js adds .is-open. */
.header__nav {
  display: none;
  flex-direction: column;
  gap: 18px;
  width: 100%;
  padding-block: 18px 22px;
}

.header__nav.is-open {
  display: flex;
}

.header__nav-item {
  font-size: var(--fs-nav);
  font-weight: 400;
  line-height: 1;
  color: var(--c-navy);
  letter-spacing: var(--k-nav);
  text-decoration: none;
  white-space: nowrap;
}

/* README: hover navy -> #8a6a3c. Colour is not the only cue for a nav item - position and the
   pointer both are - so a hue shift on its own is correct here. */
.header__nav-item:hover {
  color: var(--c-gold-text);
}

/* ---- 768 and up: the button goes, the nav is inline, and the band takes its full height ---- */
@media (min-width: 768px) {
  .header {
    height: var(--h-header);
  }

  .header__rail {
    flex-wrap: nowrap;
    height: 100%;
    min-height: 0;
    gap: 32px;
  }

  .header__logo {
    width: var(--w-header-logo);
  }

  /* Not merely invisible: removed from layout AND from the tab order at every width where the
     inline nav is present. A hidden-but-focusable button would put a phantom stop at the top of
     the tab order, which tools/noninteractive-gate.mjs fails on. */
  .header__toggle {
    display: none;
  }

  /* Shown regardless of the disclosure state, so a visitor who opened the menu on a phone and then
     rotated to a tablet width does not find the navigation missing. */
  .header__nav,
  .header__nav.is-open {
    display: flex;
    flex-direction: row;
    gap: var(--nav-gap);
    width: auto;
    padding: 0;
  }
}

/* ---- section: hero ---- */
/* HERO. Full bleed at every width; the band height, the monogram size and its offsets are what
   change.

   README, 5a: 719px, grayscale + contrast(1.05), navy wash
   linear-gradient(180deg, rgba(0,64,110,.6), rgba(0,64,110,.85)) on multiply, monogram 104px at
   the rail's left edge 64px off the base, 3px gold rule at the bottom. 420px at 767 and below.

   THE BAND CARRIES THE GOLD RULE IN ITS OWN HEIGHT. box-sizing is border-box, so a bare
   height:719px would take the 3px out of the photograph and every section below would ride 3px
   high. calc(719 + 3) keeps the photograph at exactly its own un-cropped height. */

.hero {
  position: relative;
  height: calc(420px + var(--hero-rule));
  border-bottom: var(--hero-rule) solid var(--c-gold);
  /* The ground the photograph paints over, so a slow image reads as the designed navy band rather
     than as a hole. Also what the wash multiplies against if the picture ever fails outright. */
  background: var(--c-navy);
  overflow: hidden;
}

.hero__photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* ⛔ DO NOT WIDEN THE RIGHT CROP: the framing keeps a third-party pylon sign out of shot. At the
     stage nothing is cropped at all (the band is the photograph's own ratio at 1366), so this
     only bites at the narrower widths, where the trim is taken off the TOP and BOTTOM. */
  object-position: 50% 44%;
  filter: grayscale(1) contrast(1.05);
}

/* The navy wash. A separate layer rather than a second filter, because multiply needs something to
   multiply INTO: mix-blend-mode composites this gradient against the filtered photograph beneath
   it. Multiplying navy (0, 64, 110) drives red towards zero fastest, which is what makes the
   rendered band cool - blue above green above red - and what darkens the base enough for the white
   monogram to hold 3:1 against it. tools/hero-treatment-gate.mjs measures exactly that. */
.hero__wash {
  position: absolute;
  inset: 0;
  mix-blend-mode: multiply;
  background: linear-gradient(180deg, rgba(0, 64, 110, 0.6), rgba(0, 64, 110, 0.85));
}

/* THE MONOGRAM SITS OVER BOTH LAYERS, outside the wash, so it stays pure white rather than being
   multiplied down to a navy tint.

   `bottom` is measured from the padding box, which excludes the 3px border - so 64px off the base
   is 64px off the base of the PHOTOGRAPH, which is what the README means. */
.hero__monogram {
  position: absolute;
  left: 22px;
  bottom: 32px;
  width: 68px;
  height: auto;
}

@media (min-width: 768px) {
  .hero {
    height: calc(var(--h-hero) + var(--hero-rule));
  }

  /* THE RAIL'S LEFT EDGE, expressed for an element that is not inside a rail.
     The rail is `max-width:1120px` centred with a 24px gutter, so its content edge is
     50% - 1120/2 + 24 once the viewport is wide enough for the rail to cap, and the bare 24px
     gutter below that. max() picks whichever is correct at the current width, which is the same
     expression the reference prototype uses. */
  .hero__monogram {
    left: max(var(--gutter), calc(50% - var(--rail) / 2 + var(--gutter)));
    bottom: var(--monogram-base);
    width: var(--w-monogram);
  }
}

/* ---- section: about ---- */
/* ABOUT. README, 5a: 1fr + 470px at a 96px gap, 44px stack gap. Headline 38 / 1.2 at 600
   (max 640px), body 18 / 1.75 (max 600px), pull-quote 25 / 1.5 italic #8a6a3c with a 32px indent
   off a 1px gold rule. Photograph 470 x 620.

   At 1023 and below the two columns stack and the photograph goes full width at 380px tall
   (PROTOTYPE). At 767 and below the type steps down: headline 26, body 16.5, quote 21 with a 20px
   indent, and the stack gap closes to 32px.

   THE MAX-WIDTHS ARE CEILINGS, NOT WIDTHS. At the stage the copy column is 506px wide - 1072 of
   rail measure, less the 96px gap, less the photograph's 470px track - so the 640px headline
   ceiling and the 600px body ceiling never bind there. They bind between 1024 and 1365, where the
   column grows, and they are what stops the body running to a 90-character measure on a wide
   tablet. Stated because a reader of this file will otherwise try to reconcile 640 with 506. */

.about {
  padding-block: var(--band);
}

.about__rail {
  display: grid;
  grid-template-columns: 1fr;
  gap: 48px;
  align-items: start;
}

.about__copy {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.about__headline {
  font-size: 26px;
  line-height: 1.24;
  font-weight: 600;
  max-width: 640px;
  color: var(--c-navy);
  text-wrap: pretty;
}

.about__body {
  font-size: 16.5px;
  line-height: var(--lh-body);
  max-width: 600px;
  color: var(--c-navy);
  text-wrap: pretty;
}

.about__photo {
  width: 100%;
  height: 380px;
  object-fit: cover;
}

@media (min-width: 768px) {
  .about__copy {
    gap: var(--about-stack-gap);
  }

  .about__headline {
    font-size: var(--fs-headline);
    line-height: var(--lh-headline);
  }

  .about__body {
    font-size: var(--fs-body);
  }

  }

/* ---- 1024 and up: the two columns, at the README's own tracks ---- */
@media (min-width: 1024px) {
  .about__rail {
    grid-template-columns: 1fr var(--about-col);
    gap: var(--about-gap);
  }

  .about__photo {
    width: var(--about-col);
    height: var(--about-photo-h);
  }
}

/* ---- section: portfolio ---- */
/* THE PORTFOLIO. README, 5a: heading Portfolio 64 / 1.04 at WEIGHT 400, 96px below it, 96px
   between rows. Rows 620px + 96px + 1fr, alternating, copy vertically centred, 18px stack gap,
   h3 26 / 1.25, body 17 / 1.7. At 767 and below: heading 42, h3 21, body 16 / 1.68, 56px between
   rows, 44px under the heading (PROTOTYPE).

   ⛔ WEIGHT 400 ON THE HEADING IS NOT AN OVERSIGHT. The README states it and states why: semibold
   read too heavy at 64px. Every other heading on the page is 600. design/geometry.json asserts the
   computed weight, so "tidying" it to match the others fails the build.

   THE ALTERNATION SWAPS THE TRACKS, NOT JUST THE CONTENTS. `--reverse` re-declares
   grid-template-columns as 1fr 620px and moves the copy to the first column with `order`, so the
   PICTURE always holds the 620px track. Swapping only the contents would hand the picture the 1fr
   track on every second row and quietly widen those bodies to about 95 characters while the
   pictures kept alternating and everything still looked deliberate.
   tools/responsive-gate.mjs compares picture and copy WIDTHS on every row for exactly that reason. */

.portfolio {
  border-top: 1px solid var(--c-hair);
  padding-block: var(--band);
}

.portfolio__title {
  margin-bottom: 44px;
  font-size: 42px;
  line-height: var(--lh-pf-title);
  font-weight: 400;
  color: var(--c-navy);
}

.pf-rows {
  display: flex;
  flex-direction: column;
  gap: 56px;
}

.pf-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
  align-items: center;
}

.pf-copy {
  display: flex;
  flex-direction: column;
  gap: var(--pf-copy-gap);
}

/* The 60 x 1 gold rule above each project name. A rule, not text, so it is brand gold. */
.pf-rule {
  display: block;
  width: var(--pf-rule-w);
  height: 1px;
  background: var(--c-gold);
}

.pf-name {
  font-size: 21px;
  line-height: var(--lh-pf-name);
  font-weight: 600;
  color: var(--c-navy);
}

.pf-body {
  font-size: 16px;
  line-height: 1.68;
  color: var(--c-navy);
  text-wrap: pretty;
}

/* THE CUE IS NEVER COLOUR ALONE. Navy text, gold UNDERLINE, and an arrow. Gold text on the paper
   measures 2.73:1 and was a registered failure on the sibling build; this retires the pair by
   design rather than by deletion, and the brand gold survives as the underline. */
.pf-more {
  align-self: flex-start;
  margin-top: 6px;
  font-size: var(--fs-pf-body);
  color: var(--c-navy);
  text-decoration: underline;
  text-decoration-color: var(--c-gold);
  text-underline-offset: 5px;
}

.pf-more:hover {
  color: var(--c-gold-text);
  text-decoration-color: var(--c-gold-text);
}

.pf-more__arrow {
  margin-left: 0.35em;
}

@media (min-width: 768px) {
  /* THE TWO 96px GAPS STEP UP AT 768, NOT AT 1024. The README puts 44px under the heading and 56px
     between rows in its "767 and below" row and nowhere else, so a tablet gets the design's own
     96px rhythm even while the rows are still stacked. Putting them in the 1024 block instead left
     a 768-to-1023 band running the phone's spacing under desktop type, which reads as cramped
     without looking obviously wrong. */
  .portfolio__title {
    margin-bottom: var(--pf-title-gap);
    font-size: var(--fs-pf-title);
  }

  .pf-rows {
    gap: var(--pf-row-gap);
  }

  .pf-name {
    font-size: var(--fs-pf-name);
  }

  .pf-body {
    font-size: var(--fs-pf-body);
    line-height: var(--lh-pf-body);
  }
}

/* ---- 1024 and up: two columns, alternating, at the README's own tracks ---- */
@media (min-width: 1024px) {
  .pf-row {
    grid-template-columns: var(--pf-pic) 1fr;
    gap: var(--pf-gap);
  }

  .pf-row--reverse {
    grid-template-columns: 1fr var(--pf-pic);
  }

  .pf-row--reverse .pf-copy {
    order: -1;
  }
}

/* ---- section: footer ---- */
/* FOOTER. README, 5a: gold field, 112px / 72px padding. 1fr + 560px at a 96px gap - reversed
   lockup 238px left, white form card (40px padding) right, legal row spanning underneath.

   At 1023 and below the two columns stack and centre. ⚠ THE LEGAL ROW MUST KEEP width:100% THERE
   OR ITS TRACKS COLLAPSE: once the parent grid is `justify-items:center`, a grid item shrinks to
   its content, and a `1fr auto 1fr` row inside a shrunk box puts all three labels in a huddle in
   the middle of the field with the address no longer on the left. The README calls this out
   explicitly and it is the one non-obvious rule in this file.

   At 767 and below the legal row stacks and centres, and the form card's padding closes to 24px. */

.footer {
  background: var(--c-gold);
  padding-block: var(--footer-pad-top) var(--footer-pad-bottom);
  color: var(--c-white);
}

.footer__rail {
  display: grid;
  grid-template-columns: 1fr;
  justify-items: center;
  gap: 40px;
  align-items: center;
}

.footer__logo {
  width: var(--w-footer-logo);
  height: auto;
}

/* THE FORM CARD. White on the gold field, radius 0, no shadow. The 4d fields sit directly on it
   with no label row above them. */
.form-card {
  width: 100%;
  background: var(--c-white);
  padding: 24px;
  color: var(--c-navy);
}

.form-card__form {
  display: flex;
  flex-direction: column;
  gap: var(--field-gap);
}

.form-card__input {
  height: var(--field-h);
  padding: 0 var(--field-pad-x);
  border: 1px solid var(--c-fieldline);
  border-radius: var(--radius);
  background: var(--c-white);
  font-size: var(--fs-field);
  color: var(--c-navy);
}

.form-card__textarea {
  min-height: var(--textarea-min-h);
  padding: 14px var(--field-pad-x);
  border: 1px solid var(--c-fieldline);
  border-radius: var(--radius);
  background: var(--c-white);
  font-size: var(--fs-field);
  line-height: 1.5;
  color: var(--c-navy);
  resize: vertical;
}

/* THE PLACEHOLDER IS DOING A LABEL'S JOB, which is what treatment 4d means, and it is registered
   in design/contrast-register.json at 3.64:1 rather than quietly darkened. See DESIGN-SPEC.md. */
.form-card__input::placeholder,
.form-card__textarea::placeholder {
  color: var(--c-placeholder);
  opacity: 1; /* Firefox applies a UA opacity to placeholders; without this the measured pair lies */
}

.form-card__input[aria-invalid='true'],
.form-card__textarea[aria-invalid='true'] {
  border-color: var(--c-error);
}

/* Error text. No error state was drawn in either reference file. It collapses to nothing while
   `hidden`, so it cannot disturb the layout at rest. */
.form-card__error {
  margin-top: -6px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--c-error);
}

.form-card__submit {
  margin-top: 4px;
  width: 100%;
  height: var(--field-h);
  border: 0;
  border-radius: var(--radius);
  background: var(--c-navy);
  color: var(--c-white);
  font-size: var(--fs-submit);
  font-weight: 600;
  letter-spacing: var(--k-submit);
  cursor: pointer;
}

.form-card__submit[disabled] {
  cursor: progress;
}

/* Off the visual layer and out of the accessibility tree. NOT display:none - some bots read the
   computed style and skip anything invisible, which would defeat the trap. */
.form-card__gotcha {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The polite live region. Present from first paint and never display:none, so it is registered
   with assistive technology before the first message arrives; empty it collapses to nothing. */
.form-card__status:not(:empty) {
  margin-top: 14px;
  font-size: 15px;
  line-height: 1.5;
  color: var(--c-navy);
}

.form-card__status--error {
  color: var(--c-error);
}

/* THE LEGAL ROW. 1fr auto 1fr, so Terms is centred on the RAIL rather than centred in whatever
   space the address leaves. Stacked and centred below 768. */
.footer__legal {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1fr;
  justify-items: center;
  gap: 12px;
  width: 100%;
  padding-top: var(--legal-pad-top);
  font-size: var(--fs-legal);
  text-align: center;
}

.footer__address {
  color: var(--c-on-gold);
  text-decoration: underline;
  text-decoration-color: rgba(61, 47, 24, 0.55);
  text-underline-offset: 4px;
}

.footer__legal-link {
  color: var(--c-on-gold);
}

@media (min-width: 768px) {
  /* README puts the 24px card padding at 767 and BELOW, so 40px is the value from 768 up - not
     from 1024 up. The card is still full width here; only its inset opens out. */
  .form-card {
    padding: var(--form-card-pad);
  }

  .footer__legal {
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 24px;
    text-align: left;
  }

  .footer__legal > :nth-child(1) {
    justify-self: start;
  }

  .footer__legal > :nth-child(2) {
    justify-self: center;
  }

  .footer__legal > :nth-child(3) {
    justify-self: end;
  }
}

/* ---- 1024 and up: the lockup beside the card, at the README's own tracks ---- */
@media (min-width: 1024px) {
  .footer__rail {
    grid-template-columns: 1fr var(--footer-form-col);
    justify-items: stretch;
    gap: var(--footer-gap);
  }
}

/* ---- section: credit ---- */
/* MARKETING BY CLUSE & CO. README, 5a: gold, 44px / 52px.

   ⛔ SAME FIELD AS THE FOOTER, NO DIVIDER RULE. The background is the same --c-gold and there is
   deliberately no border-top, no hairline and no tone change, so the footer and this band read as
   one continuous field. That is the instruction, and it is the reason this file has no border
   declaration to find and delete later: there was never one to remove.

   THE LABEL IS STACKED ABOVE THE MARK, both centred. Not beside it: a flex column, not a row.
   tools/responsive-gate.mjs asserts the label's box sits entirely above the mark's at every width
   and that both are centred on the page's own centre line. */

.credit {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--credit-gap);
  background: var(--c-gold);
  padding-block: var(--credit-pad-top) var(--credit-pad-bottom);
  color: var(--c-white);
}

.credit__label {
  font-size: var(--fs-credit);
  line-height: 1.4;
  letter-spacing: var(--k-credit);
  color: var(--c-on-gold);
}

.credit__link {
  display: inline-flex;
}

/* ⚠ AN APPROXIMATION, NOT THE REVERSED ARTWORK. cluseco-logo.png is a black wordmark on
   transparency and this ground is the brand gold, so it is inverted here: brightness(0) crushes
   every pixel to black and invert(1) flips it to white, which paints a flat white silhouette of
   the mark. It is legible and correctly proportioned; it is not the true reversed logo, and the
   difference is in the detail of the ampersand. When the reversed asset is supplied, replace the
   file and DELETE the filter line. */
.credit__logo {
  width: var(--w-credit-logo);
  height: auto;
  filter: brightness(0) invert(1);
}

