/* Page-specific overrides for /home/ go here.
   Shared styling lives in src/shared/styles/global.css (every page) and
   src/shared/styles/subpages.shared.css (the ten subpages + home's own
   equivalent styling currently still lives inside global.css — see
   docs/STATIC-SOURCE-MIGRATION-AUDIT.md §13 for why that split wasn't
   attempted in this pass). */

/* Section 2's main frame is 3:2 on mobile and 4:5 from 900px, and the
   client's elevation render is a tall 0.88. At 3:2 the cover crop keeps only
   the middle ~59% of the image, which lands on the first floor and loses the
   parapet; nudging the focal point up holds the whole massing in frame. The
   4:5 crop above 900px barely moves, so this is safe at every width. */
.s2-media-main .s2-media-holder img { object-position: 50% 45%; }

/* r13b — Section 2's one-line heading was the page's horizontal scrollbar.
   global.css sizes it at 7.4cqi of its column, which assumed a ~13.5em line;
   in Plus Jakarta Sans "Design and build, planned together." runs ~15.1em,
   so at 7.4cqi it is ~112% of the column: on every screen from 768px up
   the nowrap line ran past its own column, and in the 1100-1400px band
   (a 1920px screen at 150% scaling) 8-9px past the viewport too. 6.4cqi
   fits the measured line inside the column with ~3% to spare. */
@media (min-width: 768px) {
  .s2-body h2 { font-size: calc(min(clamp(2.4rem, 5.2vw, 3.8rem), 7.4cqi) * var(--k-display)); }
}

/* Gateway links added in the asset-sync pass: each homepage section now
   hands off to the page that covers it in full. Client feedback
   (2026-10-01) moved "All services" from under the Services heading to the
   end of the section, after the fourth card. */
.s3-more { margin-top: clamp(2rem, 4vw, 3rem); }

/* Section 3's sticky stack has to RELEASE as cleanly as it builds.
   A sticky card lets go once its margin box reaches the bottom of
   .s3-stack. With the same margin-bottom on cards 01–03 they all let go
   at the same distance from that edge, so as the list scrolled off they
   slid together onto one line: 03 rode up over 02 and the "02" strip
   vanished just above "All services".

   Fix: each card's margin-bottom is one step more than the next card's
   (3, 2, 1, 0 steps), which makes all four release at the moment card 04
   lands, keeping their offsets as the stack leaves. A matching negative
   margin-top on the next card cancels the extra length, so the in-flow gap
   (and the scroll travel between reveals) is the original one. Flex column
   so the two margins add rather than collapse.

   --s3-slack covers phones, where the cards are content-height and one can
   run a line taller than its neighbour (26px at 360–768): each earlier card lets go a
   touch sooner and the strips fan open slightly instead of overlapping.
   On 900px+ the cards share one fixed height, so no slack is needed.
   Motion only: under reduced motion the cards are static and keep the
   plain global.css margins. */
@media (prefers-reduced-motion: no-preference) {
  .s3-stack {
    --s3-gap: clamp(1.5rem, 6vh, 4rem);   /* global.css .s3-card margin */
    --s3-slack: 2rem;      /* one line of copy */
    --s3-release: calc(var(--stack-step) + var(--s3-slack));
    display: flex;
    flex-direction: column;
  }
  .s3-card:nth-child(1) { margin-bottom: calc(var(--s3-release) * 3); }
  .s3-card:nth-child(2) {
    margin-top: calc(var(--s3-gap) - var(--s3-release) * 3);
    margin-bottom: calc(var(--s3-release) * 2);
  }
  .s3-card:nth-child(3) {
    margin-top: calc(var(--s3-gap) - var(--s3-release) * 2);
    margin-bottom: var(--s3-release);
  }
  .s3-card:nth-child(4) {
    margin-top: calc(var(--s3-gap) - var(--s3-release));
    margin-bottom: 0;
  }
}
@media (prefers-reduced-motion: no-preference) and (min-width: 900px) {
  .s3-stack { --s3-slack: 0px; }
}
.s5-more { margin-top: clamp(2rem, 4vw, 3rem); }
.s6-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem 2rem;
  margin-top: clamp(3rem, 6vw, 4.5rem);
}
.s6-actions .s6-cta { margin-top: 0; }

/* ------------------------------------------------------------------
   Sections 7 and 9 share one media width (client feedback, 2026-10-01):
   the project cards are the same width as the design-to-execution image,
   and both heading blocks start on the image's left edge instead of
   floating centred above it.

   --uk-show-w MIRRORS the wipe frame's width in global.css
   (.s7.is-wipe .s7-frame: min(70vw, 1100px, calc(var(--s7-avail) * 16 / 9))
   with the frame's own --s7-avail: 52vh). Keep the two in step: if that
   rule changes, change this one.

   Both rules only apply in the scripted states (.is-wipe / .is-stack). The
   no-JS and reduced-motion fallbacks already line the headings up with the
   media, because both sit in the same max-width container.
   ------------------------------------------------------------------ */
/* Mobile first: below 1100px the media runs to the normal page gutter, as
   every other section does. 70vw is a desktop proportion; on a phone it left
   the wipe image 273px wide with ~59px either side. The 100% resolves at each
   use site, and every one (.s7-head, .s7-frame, .s9-head, .s9-stack) sits
   directly in its full-width stage. */
.s7, .s9 { --uk-show-w: min(100% - 2 * var(--pad-page), 1100px, calc(52vh * 16 / 9)); }
/* r13b: the wipe adds a fourth term, the height main.js measures as genuinely
   free (--s7-avail, written on the section). 52vh alone ran the stage taller
   than a short laptop screen (1280x632 at 150% scaling): flex centring then
   pushed the heading up under the fixed header and the caption off the
   bottom. The 52vh term still binds on every normal-height screen, so the
   image stays the same width as the Section 9 cards there. Defined on the
   section so the head (which reads --uk-show-w) and the frame stay in step. */
.s7.is-wipe { --uk-show-w: min(100% - 2 * var(--pad-page), 1100px, calc(52vh * 16 / 9), calc(var(--s7-avail, 52vh) * 16 / 9)); }
.s7.is-wipe .s7-frame { width: var(--uk-show-w); }

/* From 1100px, the desktop proportion — this pair must stay identical to the
   global.css frame rule quoted above. */
@media (min-width: 1100px) {
  .s7, .s9 { --uk-show-w: min(70vw, 1100px, calc(52vh * 16 / 9)); }
  .s7.is-wipe { --uk-show-w: min(70vw, 1100px, calc(52vh * 16 / 9), calc(var(--s7-avail, 52vh) * 16 / 9)); }
}

/* r13b — the pinned wipe stage, made to fit the screen it is pinned to.
   1. Clear the header: the stuck bar ends at ~98px (8px float + 88px bar +
      border), past --hdr-safe (92px), so the first heading line sat under
      it even when everything fitted. A little air on top of the token.
   2. Spacing as padding, not margin: main.js (fitStage) measures the head,
      meta row and caption with offsetHeight, which leaves margins out. The
      stacked layout's 2.5-4rem .s7-frame margin-top, the short-screen
      .s7-head margin-bottom and the meta/caption margins were all spent
      but never measured, so the frame was sized for room it did not have.
      The stage gap (the one gap between head and frame) is capped at the
      24px main.js allows for it. */
/* 3. The horizontal scrollbar at 150% Windows scaling. ScrollTrigger gives
      the pin-spacer (in flow) an inline pixel width rounded up from the
      fractional viewport: 1265px in a 1264.67px viewport, so Chrome shows
      a scrollbar for a third of a pixel. Clip, not hidden: no scroll
      container, so the pin and any sticky child are unaffected, and the
      pinned (fixed) stage is never clipped by it. */
.s7 { overflow-x: clip; }
.s7.is-wipe .s7-stage {
  padding-top: calc(var(--hdr-safe) + clamp(.5rem, 2vh, 1.25rem));
  gap: clamp(.75rem, 2vh, 1.5rem);
}
/* The breathing room between the heading and the frame lives here, as
   padding main.js measures, so it only takes height the screen has. */
.s7.is-wipe .s7-head { margin-bottom: 0; padding-bottom: clamp(.25rem, 3vh, 2rem); }
.s7.is-wipe .s7-frame { margin-top: 0; }
.s7.is-wipe .s7-meta { margin-bottom: 0; padding-bottom: .9rem; }
.s7.is-wipe .s7-caption { margin-top: 0; padding-top: .9rem; }

/* The head is a .container inside a flex column, so its auto margins were
   shrink-wrapping and centring it. A fixed width equal to the frame's, with
   the container gutter taken off, puts its left edge on the image's. */
.s7.is-wipe .s7-head,
.s9.is-stack .s9-head {
  width: var(--uk-show-w);
  max-width: 100%;
  padding-inline: 0;
  margin-inline: auto;
}

/* The card sits on the same width at every size. */
.s9.is-stack .s9-stack {
  width: var(--uk-show-w);
  max-width: 100%;
  margin-inline: auto;
}
.s9.is-stack .s9-card { width: 100%; }

/* ---- Section 9: the camera cut (r7, 2026-10-08) -------------------------
   Replaces the GSAP pin + scrub:1 stack, which ran every card's scale, y and
   opacity on the main thread each frame (a style recalc, layout and
   re-layerize per frame on a phone), trailed the finger by a second of scrub
   easing, and lifted the cards behind 14px per level so they showed as thin
   slivers above the front one.

   Now: the stage is position: sticky inside .s9-track, which is one screen
   plus one step per card tall. home.js watches one invisible sentinel per
   step with an IntersectionObserver (no scroll handler at all) and writes a
   state onto each card: active, next (waiting below), prev (just covered),
   past, future. The look of each state lives here and every change is a CSS
   transition of transform or opacity only, so it runs on the compositor at
   a fixed, short duration: scroll position decides WHICH card is up, the
   transition decides HOW it arrives, and nothing is scrubbed.

   The cut: the next card wipes up over the frame while its photograph,
   counter-moving inside it, holds nearly still and eases in from 106% scale;
   the card it covers settles back to 92% and darkens. The stack clips to its
   own edges and the cards behind are always smaller than the one in front,
   so nothing peeks out around the frame. */
.s9.is-cut {
  --s9-h: 100vh;
  --s9-step: calc(var(--s9-h) * .62);
  --s9-cut: .72s;
  --s9-ease: cubic-bezier(.77, 0, .18, 1);
}
@supports (height: 100svh) {
  /* svh, so the track and the sentinels do not move when a phone's address
     bar shows or hides. */
  .s9.is-cut { --s9-h: 100svh; }
}
.s9.is-cut .s9-track {
  position: relative;
  height: calc(var(--s9-h) + var(--s9-n, 5) * var(--s9-step));
}
.s9.is-cut .s9-stage {
  position: sticky;
  top: 0;
  height: var(--s9-h);
  min-height: 0;
}

/* One sentinel per card. The switch from card i-1 to card i happens when the
   middle of the viewport crosses sentinel i's top, i.e. i steps into the pin. */
.s9-steps { position: absolute; inset: 0; pointer-events: none; }
.s9-step {
  position: absolute;
  left: 0;
  width: 1px;
  top: calc(var(--s9-h) / 2 + var(--i) * var(--s9-step));
  height: var(--s9-step);
}
.s9-step:first-child { top: 0; height: calc(var(--s9-h) / 2 + var(--s9-step)); }
.s9-step:last-child { height: auto; bottom: 0; }

/* The frame. Painted once and never moves, so it carries the shadow the cards
   used to carry each, and its dark ground is what shows around a card as it
   settles back. */
.s9.is-cut .s9-stack {
  overflow: hidden;
  overflow: clip;
  isolation: isolate;
  border-radius: var(--radius);
  background: rgb(30 18 14);
  box-shadow: 0 18px 40px rgba(63, 36, 34, .18);
}
.s9.is-cut .s9-card {
  box-shadow: none;
  transform: translateY(100%);
  transition: transform var(--s9-cut) var(--s9-ease);
}
.s9.is-cut .s9-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgb(24 14 11);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--s9-cut) var(--s9-ease);
}
.s9.is-cut .s9-card[data-s9="active"] { transform: none; }
.s9.is-cut .s9-card[data-s9="prev"],
.s9.is-cut .s9-card[data-s9="past"] { transform: scale(.92); }
.s9.is-cut .s9-card[data-s9="prev"]::after,
.s9.is-cut .s9-card[data-s9="past"]::after { opacity: .62; }

/* Layers only where motion can happen next: the card in front, the one
   waiting below it and the one just covered. */
.s9.is-cut .s9-card[data-s9="active"],
.s9.is-cut .s9-card[data-s9="next"],
.s9.is-cut .s9-card[data-s9="prev"] { will-change: transform; }

/* The photograph's counter-move, on a fine pointer only. Moving with the
   card's top edge at 45% of its speed keeps it nearly still on screen, so the
   arrival reads as a wipe across one shot rather than a panel sliding. Phones
   keep the plain card move: one layer per card, nothing else to composite. */
@media (hover: hover) and (pointer: fine) {
  .s9.is-cut .s9-media {
    transform: translateY(-45%) scale(1.06);
    transition: transform var(--s9-cut) var(--s9-ease);
  }
  .s9.is-cut .s9-card[data-s9="active"] .s9-media,
  .s9.is-cut .s9-card[data-s9="prev"] .s9-media,
  .s9.is-cut .s9-card[data-s9="past"] .s9-media { transform: none; }
  .s9.is-cut .s9-card[data-s9="active"] .s9-media,
  .s9.is-cut .s9-card[data-s9="next"] .s9-media { will-change: transform; }
}
@media (hover: none), (pointer: coarse) {
  .s9.is-cut { --s9-step: calc(var(--s9-h) * .56); --s9-cut: .6s; }
}

/* No transitions until the first state is in (a reload mid-section lands on
   the right card instead of animating to it), and none for a card that jumps
   from below the frame to behind it in one update (a fast fling), which would
   otherwise travel visibly under the card arriving. */
.s9.is-cut:not(.is-live) .s9-card,
.s9.is-cut:not(.is-live) .s9-card::after,
.s9.is-cut:not(.is-live) .s9-media,
.s9.is-cut .s9-card.is-snap,
.s9.is-cut .s9-card.is-snap::after,
.s9.is-cut .s9-card.is-snap .s9-media { transition: none; }

/* From 768px the card takes the wipe image's 16:9 shape as well, so the two
   read as the same frame. Below that a 16:9 card at phone width is too short
   to hold the category, name and link over the photograph, so it keeps the
   taller stack height global.css gives it and only matches the width. */
@media (min-width: 768px) {
  /* aspect-ratio, not calc(var(--uk-show-w) * 9 / 16): below 1100px that
     width carries a 100%, which inside height would resolve against the
     stage height instead of the width. */
  .s9.is-stack .s9-stack { height: auto; aspect-ratio: 16 / 9; }
}

/* ------------------------------------------------------------------
   Hero film on phones and portrait tablets (mobile pass, 2026-10-01).

   The client's film carries its own titles ("Spaces Designed Well. Built
   Right", "We Design", "One team, from first sketch to final handover.")
   across 15-85% of the frame's width. object-fit: cover on a portrait
   screen keeps only the middle quarter of a 16:9 frame (26% at 390x844), so
   every title was cut at both ends.

   Below a 4:3 aspect the whole frame is shown instead, as a band a little
   wider than the screen (122%: the edges lose 9% each side, the titles keep
   a clear margin even at 320px), centred in the space between the header
   and the two buttons, its top and bottom edges fading into the umber
   ground. Landscape phones and desktops are wider than 4:3 and
   keep the full-bleed cover crop, which already holds the titles there.
   ------------------------------------------------------------------ */
#film-ambient { display: none; }

@media (max-aspect-ratio: 4/3) {
  /* Phones (client, 2026-10-02): the band sits on a clean umber ground
     with a soft warm glow behind it, not on a blurred copy of the film
     (that read as smeared). The band is as large as the widest burned-in
     title allows: "One team, from first sketch to final handover." spans
     about 15-85% of the frame, so at 132% of the screen width (74% of the
     frame visible) every title keeps a margin. */
  #track {
    background:
      radial-gradient(ellipse 120% 42% at 50% var(--film-band-y, 46%), rgba(120, 78, 52, .55), transparent 70%),
      var(--uk-umber);
  }
  #film {
    inset: auto;
    top: var(--film-band-y, 46%);
    left: 50%;
    width: var(--film-band-w, 132%);
    max-width: none;
    height: auto;
    aspect-ratio: 16 / 9;
    transform: translate(-50%, -50%);
    background: transparent;
    -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 10%, #000 90%, transparent 100%);
            mask-image: linear-gradient(180deg, transparent 0, #000 10%, #000 90%, transparent 100%);
  }
}
/* Portrait tablets have far more height than the band needs; a slightly
   larger band reads as the film rather than a strip. */
@media (max-aspect-ratio: 4/3) and (min-width: 700px) {
  #film { --film-band-w: 132%; }
}

/* Landscape phones (844x390 and the like): the stage's 34rem min-height was
   taller than the screen, which pushed both buttons below the fold. The
   hero is one screen there too. */
@media (orientation: landscape) and (max-height: 560px) {
  #stage {
    min-height: 0;
    padding-top: clamp(4.5rem, 22vh, 6rem);
    padding-bottom: clamp(1.25rem, 7vh, 2.5rem);
  }
}

/* ==================================================================
   Mobile pass (2026-10-01): every section on every phone size.

   "LS" below means a phone held sideways:
     (orientation: landscape) and (max-height: 560px) and (max-width: 1099px)
   About 300px of a 390px-tall screen is left under the fixed header there,
   so the pinned and sticky sections put their heading BESIDE their media
   instead of above it, the way the desktop layout already does for
   Sections 3 and 4. Nothing is removed; every effect still runs.
   ================================================================== */

/* ---- Section 3: the sticky card stack ----------------------------------
   Phones. The card is capped to the height left under the stacked strips
   (global.css), and the photograph had a fixed 16:10 height, so on any
   screen under ~740px tall the COPY was what got cut: the paragraph and
   the "Explore" link sat below the card's clipped edge (320x568, 360x740,
   375x667). Now the photograph is the part that gives: it starts at its
   16:10 height and shrinks to whatever the copy leaves, so the active card
   always shows its title, paragraph and link in full. */
@media (max-width: 899px) {
  .s3-card-head { flex: 0 0 auto; }
  .s3-card-media {
    aspect-ratio: auto;
    flex: 0 1 auto;
    height: calc((100vw - 2 * var(--pad-page)) * 10 / 16);
    min-height: 4.5rem;
  }
  .s3-card-body { flex: 0 0 auto; overflow: visible; }
  /* Long markers ("Construction and Execution") wrap to two lines at
     320-360px; a tighter line keeps both inside the strip. */
  .s3-card-marker { line-height: 1.25; }
}
/* Short phones: slimmer strips and a tighter card body, so the photograph
   keeps a useful height as well. */
@media (max-width: 899px) and (max-height: 760px) {
  .s3-stack { --head-h: 2.375rem; }
  .s3-card-body { padding: 1rem 1.25rem .75rem; }
  .s3-card-body p { margin-top: .5rem; }
  .s3-card-body .link-arrow { padding-top: .25rem; }
}
/* 320x568 and the like: one more notch, or the longest card (03) still
   ran 6px past its own edge. */
@media (max-width: 899px) and (max-height: 620px) and (orientation: portrait) {
  .s3-card-body h3 { font-size: 1.4rem; }
  .s3-card-media { min-height: 4rem; }
}
@media (max-width: 359px) {
  .s3-card-head { padding-inline: 1rem; }
  .s3-card-marker { letter-spacing: 0; }
}

/* LS: the desktop card (title strip across, photograph left, copy right),
   sized to the height that is actually left. */
@media (orientation: landscape) and (max-height: 560px) and (max-width: 1099px) {
  .s3-stack { --head-h: 2.125rem; }
  .s3-card {
    display: grid;
    grid-template-columns: 5fr 7fr;
    grid-template-rows: auto minmax(0, 1fr);
    height: calc(100vh - var(--stack-top) - var(--stack-step) * 3 - .75rem);
    height: calc(100svh - var(--stack-top) - var(--stack-step) * 3 - .75rem);
    max-height: none;
    min-height: 0;
  }
  .s3-card-head { grid-column: 1 / -1; }
  .s3-card-media { aspect-ratio: auto; height: 100%; min-height: 0; }
  .s3-card-body {
    padding: .6rem 1.25rem .25rem;
    justify-content: center;
    min-height: 0;
    overflow: hidden;
  }
  .s3-card-body h3 { font-size: 1.4rem; }
  .s3-card-body p { margin-top: .35rem; font-size: .8125rem; line-height: 1.45; }
  .s3-card-body .link-arrow { margin-top: 0; padding-top: 0; }
}
/* Shorter still (740x360 and the like). */
@media (orientation: landscape) and (max-height: 400px) and (max-width: 1099px) {
  .s3-stack { --head-h: 1.875rem; }
  .s3-card { grid-template-columns: 4fr 8fr; }
  .s3-card-body h3 { max-width: none; }
  .s3-card-body { padding: .35rem 1rem 0; }
  .s3-card-body h3 { font-size: 1.2rem; }
  .s3-card-body p { margin-top: .25rem; }
}

/* ---- Section 4: the problem and the vendor tree -----------------------
   The stage is sticky and one screen tall. On short phones its contents
   ran taller than the screen and, centred, spilled UP under the fixed
   header ("The problem" was hidden at 320x568). safe centre keeps them
   below it, and the short-screen sizes make them fit in the first place. */
@media (max-width: 899px) {
  .s4-stage { align-items: safe center; }
  /* 220vh left only 120vh of travel for the whole sequence, and the last
     labels finished on the very frame the stage let go. Longer runway plus
     the hold in main.js (initProblem) gives the finished tree a beat. */
  /* The six outcomes were 10px; 11px still fits each label inside its row
     at 320px. */
  .tree-label { font-size: .6875rem; line-height: 1.22; }
}
/* Motion only: under reduced motion global.css collapses the runway. */
@media (max-width: 899px) and (prefers-reduced-motion: no-preference) {
  .s4-runway { height: 260vh; }
}
@media (orientation: landscape) and (max-height: 560px) and (max-width: 1099px) and (prefers-reduced-motion: no-preference) {
  .s4-runway { height: 300vh; }
}
@media (max-width: 899px) and (max-height: 760px) and (orientation: portrait) {
  .s4-copy h2 { font-size: clamp(1.45rem, 6.6vw, 1.8rem); }
  .s4-copy > p:not(.eyebrow) { margin-top: .45rem; line-height: 1.45; }
  .s4-inner { gap: .5rem; }
  .s4-tree { margin-top: .5rem; }
}

/* LS: copy beside the figure, as on desktop; the figure takes its size
   from the height that is left. */
@media (orientation: landscape) and (max-height: 560px) and (max-width: 1099px) {
  .s4-stage {
    padding-top: var(--hdr-safe);
    padding-bottom: .75rem;
  }
  .s4-inner {
    grid-template-columns: minmax(0, 32fr) minmax(0, 68fr);
    gap: 1.5rem;
  }
  .s4-copy h2 { font-size: 1.45rem; margin-top: 0; max-width: 18ch; }
  .s4-copy > p:not(.eyebrow) { margin-top: .45rem; font-size: .8125rem; line-height: 1.45; }
  .s4-tree {
    width: min(100%, calc((100vh - var(--hdr-safe) - 1.5rem) * 1200 / 660));
    width: min(100%, calc((100svh - var(--hdr-safe) - 1.5rem) * 1200 / 660));
    margin: 0 auto;
    border: 1px solid rgba(239, 231, 218, .14);
    border-radius: var(--radius);
  }
  .tree-label { width: 22%; }
}

/* ---- Section 6: the method ---------------------------------------------
   On desktop the rail (heading, track, counter) is sticky beside the
   steps. A phone had no sticky element at all: the track and its "02 / 07"
   counter scrolled away with the heading before the first step was read,
   so the progress it reports was never on screen. Here the track alone
   becomes a slim bar that sticks under the header for the length of the
   steps: the desktop rail, adapted. display: contents lets it stick to
   the whole grid rather than to its own short column. */
@media (max-width: 899px) and (prefers-reduced-motion: no-preference) {
  .s6-rail { display: contents; }
  .s6-progress {
    position: sticky;
    top: var(--hdr-safe);
    z-index: 2;
    margin-top: 1.5rem;
    margin-inline: calc(-1 * var(--pad-page));
    padding: .7rem var(--pad-page);
    background: linear-gradient(180deg, var(--uk-sandstone) 70%, rgba(229, 217, 195, 0));
  }
  /* The rail's three children are grid items now; the column gap was
     sized for two blocks, not for the gaps inside one. */
  .s6-grid { row-gap: 0; }
  .s6-right { margin-top: 1.25rem; }
}

/* ---- Section 7: the design-to-execution wipe ---------------------------
   Portrait phones: the frame was 16:9 at the full content width, so the
   two photographs were a 190px strip in an 844px screen with the stage
   mostly empty around them. 4:3 is the site photographs' own shape and
   gives the wipe a third more height; the render loses a little at each
   side, centred. main.js still sizes the frame from the height left, so
   the 4:3 term below is that same calculation for the taller shape. */
@media (max-width: 767px) and (orientation: portrait) {
  .s7.is-wipe .s7-media { aspect-ratio: 4 / 3; }
  /* --s7-avail: inherit. global.css declares a 52vh fallback for it ON the
     frame, which shadows the measured value main.js writes on the section,
     so the frame was sized off 52vh, not off the room actually left. On a
     phone that difference is the caption falling past the fold; reading
     the section's value fixes it (desktop keeps its tuned behaviour). */
  .s7.is-wipe .s7-frame {
    --s7-avail: inherit;
    width: min(100% - 2 * var(--pad-page), calc(var(--s7-avail) * 4 / 3));
  }
  /* Two captions share one row; at body size each wrapped onto two or
     three lines ("Bedroom arch wall — / render"). */
  .s7.is-wipe .s7-caption { font-size: .875rem; line-height: 1.4; }
  .s7.is-wipe .s7-caption span { text-wrap: balance; }
}

/* LS: heading beside the frame. main.js (fitStage) sees the heading is no
   longer stacked above the frame and stops taking its height off the
   frame's allowance. */
@media (orientation: landscape) and (max-height: 560px) and (max-width: 1099px) {
  .s7.is-wipe .s7-stage {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    align-content: center;
    column-gap: 1.5rem;
    padding-inline: var(--pad-page);
    padding-top: var(--hdr-safe);
    padding-bottom: .5rem;
  }
  .s7.is-wipe .s7-head { width: auto; max-width: none; margin: 0; padding: 0; }
  .s7.is-wipe .s7-head h2 { font-size: clamp(1.25rem, 3.4vw, 1.75rem); margin-top: .75rem; }
  /* min-width: 0, or the grid item never narrows below the meta row's
     one-line width and the image comes out taller than the room left. */
  .s7.is-wipe .s7-frame {
    --s7-avail: inherit;   /* the measured room, see the portrait rule */
    width: min(62vw, calc(var(--s7-avail) * 16 / 9));
    min-width: 0;
    margin: 0;
  }
  /* Margins, not the measured padding the r13b base rule uses: this layout
     already fitted, and the extra measured height narrowed the frame until
     two captions wrapped and the next fit shrank it again. */
  .s7.is-wipe .s7-meta { margin-bottom: .4rem; padding-bottom: 0; letter-spacing: 0; gap: .5rem; }
  .s7.is-wipe .s7-meta-right { gap: .6rem; }
  .s7.is-wipe .s7-caption { font-size: .8125rem; margin-top: .4rem; padding-top: 0; }
}

/* ---- Section 9: featured projects --------------------------------------
   LS: the 16:9 card plus the heading above it needed ~420px; the screen
   has 390. Heading beside the stack, card as tall as the screen allows. */
@media (orientation: landscape) and (max-height: 560px) and (max-width: 1099px) {
  .s9.is-stack .s9-stage {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 1.5rem;
    height: 100vh;
    height: 100svh;
    min-height: 0;
    padding-inline: var(--pad-page);
    padding-bottom: .75rem;
  }
  .s9.is-stack .s9-head { width: auto; margin: 0; padding: 0; }
  .s9.is-stack .s9-head h2 { font-size: clamp(1.25rem, 3.4vw, 1.75rem); margin-top: .75rem; }
  .s9.is-stack .s9-stack {
    margin: 0;
    height: calc(100vh - var(--hdr-safe) - 1.75rem);
    height: calc(100svh - var(--hdr-safe) - 1.75rem);
    width: auto;
    aspect-ratio: 16 / 9;
    max-width: 62vw;
  }
  .s9-card-body { padding: 2.5rem 1rem .75rem; }
  .s9-link { margin-top: .25rem; }
}

/* ------------------------------------------------------------------
   Phone hero (client, 2026-10-02): a composed first screen, drawn rather
   than written. Portrait screens only; desktop and landscape keep the
   full-screen cover film unchanged.

   One screen tall. On the umber ground, a faint drawing-sheet grid; the
   film as a framed 4:3 card (rounded, copper hairline, soft shadow, and
   copper crop marks off its four corners; 4:3 keeps every burned-in
   title inside the frame); the two buttons at the card's width; and along
   the foot of the screen a street of buildings in fine line work that
   draws itself in on load. No text beyond the film and the buttons.
   ------------------------------------------------------------------ */
.hero-m-card { display: contents; }
.hero-m-mark,
.hero-m-skyline { display: none; }
@media (max-aspect-ratio: 4/3) {
  #track {
    background:
      radial-gradient(ellipse 90% 40% at 50% 48%, rgba(150, 96, 60, .30), transparent 72%),
      var(--uk-umber);
  }
  #stage {
    --card-w: min(100vw - 2.5rem, 34rem);
    height: 100vh;
    height: 100svh;
    min-height: 34rem;
    max-height: none;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 0;
    padding: calc(var(--hdr-h) - .5rem) 0 calc((100vw * 150 / 400) + 1rem);
    background:
      linear-gradient(rgba(239, 231, 218, .045) 1px, transparent 1px) 0 0 / 28px 28px,
      linear-gradient(90deg, rgba(239, 231, 218, .045) 1px, transparent 1px) 0 0 / 28px 28px;
    -webkit-mask-image: radial-gradient(ellipse 120% 85% at 50% 45%, #000 55%, transparent 100%);
            mask-image: radial-gradient(ellipse 120% 85% at 50% 45%, #000 55%, transparent 100%);
  }
  .hero-m-card {
    order: 1;
    position: relative;
    display: block;
    width: var(--card-w);
    flex: none;
  }
  #film {
    position: relative;
    inset: auto;
    top: auto;
    left: auto;
    transform: none;
    display: block;
    width: 100%;
    max-width: none;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    object-position: 50% 50%;
    border-radius: 14px;
    box-shadow:
      0 0 0 1px rgba(200, 129, 28, .38),
      0 24px 48px -18px rgba(0, 0, 0, .65);
    -webkit-mask-image: none;
            mask-image: none;
    background: #2a1a17;
  }
  /* crop marks: two copper strokes off each corner, like a drawing sheet */
  .hero-m-mark {
    display: block;
    position: absolute;
    width: 22px;
    height: 22px;
    border-color: color-mix(in srgb, var(--uk-ochre) 80%, transparent);
    border-style: solid;
    border-width: 0;
    pointer-events: none;
  }
  .hero-m-mark--tl { top: -9px; left: -9px; border-top-width: 1px; border-left-width: 1px; }
  .hero-m-mark--tr { top: -9px; right: -9px; border-top-width: 1px; border-right-width: 1px; }
  .hero-m-mark--bl { bottom: -9px; left: -9px; border-bottom-width: 1px; border-left-width: 1px; }
  .hero-m-mark--br { bottom: -9px; right: -9px; border-bottom-width: 1px; border-right-width: 1px; }
  #film-vignette { display: none; }
  .hero-static {
    order: 2;
    width: var(--card-w);
    padding: 0;
    margin-top: clamp(1.75rem, 4.5svh, 2.5rem);
  }
  .hero-static__actions {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: .7rem;
  }
  .hero-static__actions .btn {
    width: 100%;
    padding: .95rem 1rem;
    border-radius: 999px;
    font-size: .9rem;
    letter-spacing: .01em;
    text-align: center;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  /* the street drawing along the foot of the screen */
  .hero-m-skyline {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: auto;
    aspect-ratio: 400 / 150;
    color: var(--uk-ochre);
    pointer-events: none;
  }
  .hm-draw--ground { stroke: color-mix(in srgb, var(--uk-ochre) 70%, transparent); }
  .hm-draw--ticks { stroke: color-mix(in srgb, var(--uk-ochre) 55%, transparent); }
  .hm-draw--left { stroke: color-mix(in srgb, var(--text-on-dark) 42%, transparent); }
  .hm-draw--detail { stroke: color-mix(in srgb, var(--text-on-dark) 22%, transparent); }
}
@media (max-aspect-ratio: 4/3) and (prefers-reduced-motion: no-preference) {
  .hm-draw[pathLength] {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: hm-draw 1.6s cubic-bezier(.2, .7, .2, 1) .3s forwards;
  }
  .hm-draw--left  { animation-delay: .75s; animation-duration: 2.4s; }
  .hm-draw--right { animation-delay: .9s; animation-duration: 2.4s; }
  .hm-draw--ticks { opacity: 0; animation: hm-fade .8s ease 1.2s forwards; }
  .hm-draw--detail { opacity: 0; animation: hm-fade 1.2s ease 2.2s forwards; }
  .hero-m-mark { opacity: 0; animation: hm-fade .7s ease .5s forwards; }
}
@keyframes hm-draw { to { stroke-dashoffset: 0; } }
@keyframes hm-fade { to { opacity: 1; } }
/* Short phones: the card gives way first, so everything fits. */
@media (max-aspect-ratio: 4/3) and (max-height: 700px) {
  #stage { --card-w: min(100vw - 2.5rem, calc((100svh - var(--hdr-h) - 100vw * 150 / 400 - 10.5rem) * 4 / 3)); }
}


/* Phone hero, round 3 (client, 2026-10-02): a bigger film and a livelier
   drawing. The film now runs the full screen width and shows its WHOLE
   frame (no side crop), set a touch taller than its own 16:9 (16:10, an
   ~11% stretch) so it fills more of the screen. Copper dimension strings
   (end ticks, a centre tick, drawn out from the middle) sit above and below
   it, filling the space under the menu the way a drawing sheet would. The
   street gains a faint taller back row, a tower crane whose jib sways, a few
   trees, a low sun, and windows that light up one by one. Overrides the
   round-2 card rules above; reduced motion keeps everything still. */
.hero-m-dim { display: none; }
@media (max-aspect-ratio: 4/3) {
  #stage {
    --card-w: 100vw;
    padding-bottom: calc(100vw * 200 / 400 + .75rem);
  }
  .hero-m-card { width: 100vw; }
  #film {
    aspect-ratio: 16 / 10;
    object-fit: fill;
    border-radius: 0;
    box-shadow: 0 22px 44px -20px rgba(0, 0, 0, .7);
  }
  .hero-m-mark { display: none; }
  .hero-m-dim {
    display: block;
    width: calc(100% - 2.5rem);
    height: 12px;
    margin: 0 auto;
    color: color-mix(in srgb, var(--uk-ochre) 75%, transparent);
  }
  .hero-m-dim--top { margin-bottom: .9rem; }
  .hero-m-dim--bot { margin-top: .9rem; }
  .hero-static { width: min(100vw - 2.5rem, 34rem); margin-top: clamp(1.5rem, 4svh, 2.25rem); }
  .hero-m-skyline { aspect-ratio: 400 / 200; }
  .hm-draw--back { stroke: color-mix(in srgb, var(--text-on-dark) 14%, transparent); }
  .hm-draw--crane { stroke: color-mix(in srgb, var(--uk-ochre) 60%, transparent); }
  .hm-hook { stroke: color-mix(in srgb, var(--uk-ochre) 55%, transparent); }
  .hm-draw--trees { stroke: color-mix(in srgb, var(--text-on-dark) 30%, transparent); }
  .hm-sun { stroke: color-mix(in srgb, var(--uk-ochre) 55%, transparent); fill: none; }
  .hm-win { stroke: none; fill: color-mix(in srgb, var(--uk-ochre) 70%, transparent); opacity: .12; }
  .hm-jib { transform-box: view-box; transform-origin: 352px 18px; }
}
@media (max-aspect-ratio: 4/3) and (prefers-reduced-motion: no-preference) {
  .hm-dim-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: hm-draw 1.1s cubic-bezier(.2,.7,.2,1) .2s forwards; }
  .hm-dim-ends { opacity: 0; animation: hm-fade .6s ease 1s forwards; }
  .hero-m-dim--bot .hm-dim-line { animation-delay: .45s; }
  .hm-draw--back { stroke-dasharray: 1; stroke-dashoffset: 1; animation: hm-draw 2.6s cubic-bezier(.2,.7,.2,1) .4s forwards; }
  .hm-draw--crane { stroke-dasharray: 1; stroke-dashoffset: 1; animation: hm-draw 1.8s cubic-bezier(.2,.7,.2,1) 1.1s forwards; }
  .hm-draw--trees { stroke-dasharray: 1; stroke-dashoffset: 1; animation: hm-draw 1.4s ease 1.9s forwards; }
  .hm-sun { opacity: 0; animation: hm-fade 1.6s ease .3s forwards; }
  .hm-hook { opacity: 0; animation: hm-fade .6s ease 2.6s forwards, hm-hook 4.8s ease-in-out 3s infinite; }
  .hm-jib { animation: hm-sway 9s ease-in-out 3s infinite; }
  .hm-win { animation: hm-light .9s ease var(--d, 3s) forwards; }
}
@keyframes hm-sway { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(-4deg); } }
@keyframes hm-hook { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(5px); } }
@keyframes hm-light { to { opacity: .85; } }
@media (max-aspect-ratio: 4/3) and (prefers-reduced-motion: reduce) {
  .hm-win { opacity: .6; }
}
@media (max-aspect-ratio: 4/3) and (max-height: 700px) {
  /* Short phones: the film keeps its own 16:9 and the dimension strings
     step aside, so film, buttons and street all fit with clear space. */
  .hero-m-skyline { aspect-ratio: auto; height: calc(100vw * 200 / 400 * .62); }
  #stage { padding-top: calc(var(--hdr-h) + .5rem); padding-bottom: calc(100vw * 200 / 400 * .62 + 1rem); }
  #film { aspect-ratio: 16 / 9; }
  .hero-m-dim { display: none; }
}


/* Paused while off screen (home.js). */
.hero-m-skyline.is-offscreen,
.hero-m-skyline.is-offscreen * { animation-play-state: paused !important; }

/* Owner, 2026-10-09: the turn line is the key point of section 4, so it
   carries the keyword accent. */
.s4-copy > p.s4-turn { color: var(--keyword-on-dark); }
