/* ==========================================================================
   Plumbline — sections
   Deliberately unequal: a service card and a testimonial are not the same
   object, so they do not get the same padding, radius or weight.
   ========================================================================== */

/* --- 1. Hero --------------------------------------------------------------
   Full-bleed photograph, chrome pushed into the corners, one oversized block
   of type sitting on the floor of the frame. Everything else gets out of the
   way of the picture.
   -------------------------------------------------------------------------- */
/* Sticky, not static. The hero stays pinned while the next section climbs
   over it, and hero-depart.js slides it up at a fraction of scroll speed so
   it leaves slowly instead of sitting still. */
.hero {
  position: sticky; top: 0; z-index: 0;
  height: 100svh;
  display: flex;
  isolation: isolate;
  overflow: hidden;
  background: var(--ink);
}
/* The entrance pushes the photograph in from 1.1, so this has to clip. */
.hero__media { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
.hero__media img {
  /* Weighted to the top of the frame: the band is short now, and the bottom
     of this photograph is the part worth losing. */
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 26%;
  /* One grade for the whole page starts here: pull the highlights down so a
     bright sky can never win an argument with the headline. */
  filter: saturate(0.86) contrast(1.06) brightness(0.78);
}
.hero__scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(180deg, rgba(8,6,5,.62) 0%, rgba(8,6,5,.20) 18%, rgba(8,6,5,0) 38%),
    linear-gradient(2deg, rgba(8,6,5,.92) 4%, rgba(8,6,5,.74) 26%, rgba(8,6,5,.34) 50%, rgba(8,6,5,0) 76%);
}

.hero__inner {
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--s-12);
  padding-block: 160px clamp(2.5rem, 1rem + 4vw, 4.5rem);
}

/* Live dot — reused by the "now building" strip further down the page. */
.tag__dot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--action-hi);
  box-shadow: 0 0 0 0 rgba(224,90,38,.75);
  animation: pulse 2.6s var(--ease-out) infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(224,90,38,.6); }
  70%  { box-shadow: 0 0 0 10px rgba(224,90,38,0); }
  100% { box-shadow: 0 0 0 0 rgba(224,90,38,0); }
}

.hero h1 {
  color: var(--text-inv);
  font-size: var(--fs-hero);
  /* Below 1 on purpose: at 120px the three lines have to read as one block,
     and Inter's default leading would open a gap between each. */
  line-height: 0.95;
  text-transform: uppercase;
  /* Looser than the -0.07em the rest of the display scale runs at. Capitals
     have no descenders to fall into each other's gaps, so the same tracking
     that closes lowercase up neatly collides them. */
  letter-spacing: -0.045em;
  /* The one place on the page heavier than Medium. At this size the weight
     is doing structural work, not emphasis. */
  font-weight: var(--wt-hero);
  text-wrap: nowrap;
}
.hero__line { display: block; overflow: hidden; padding-bottom: 0.06em; margin-bottom: -0.06em; }
.hero__line > span { display: block; will-change: transform; }

/* Bottom-right: the only action above the fold. */
.hero__aside { display: flex; justify-content: flex-end; padding-bottom: 8px; }

.pill {
  display: inline-flex; align-items: center; gap: var(--s-4);
  padding: 9px 9px 9px var(--s-6);
  border-radius: 999px;
  background: #fff; color: var(--ink);
  font-size: 1.0625rem;
  font-weight: var(--wt-display);
  letter-spacing: var(--ls-body);
  box-shadow: var(--shadow-overlay);
  transition: background var(--t-fast) var(--ease-out), transform var(--t-mid) var(--ease-out);
}
.pill__icon {
  display: grid; place-items: center; flex: none;
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--action-fill); color: #fff;
  transition: transform var(--t-mid) var(--ease-out), background var(--t-fast) var(--ease-out);
}
/* The hero's button with its two surfaces swapped: accent carries the label,
   white carries the arrow. Same silhouette, opposite emphasis. */
.pill--accent {
  flex-direction: row;
  padding: 9px var(--s-6) 9px 9px;
  background: var(--action-fill); color: #fff;
  box-shadow: none;
}
.pill--accent .pill__icon { background: #fff; color: var(--action-fill); }
/* The disc carries the hover, not the field. Darkening the orange as well
   would drop the ink disc to 2.9:1 against it — under the 3:1 a non-text
   control boundary needs — so the field holds and only the disc flips.
   Against the untouched accent the disc clears 3.4:1. */
.pill--accent:hover { box-shadow: var(--shadow-card); }
.pill--accent:hover .pill__icon { background: var(--ink); color: #fff; transform: rotate(45deg); }
.pill--accent:active { background: var(--action-fill-hi); }

.pill:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(22,18,15,.12), 0 28px 64px -18px rgba(22,18,15,.40); }
.pill:hover .pill__icon { background: var(--action-fill-hi); transform: rotate(-45deg); }
.pill:active { transform: translateY(0); }

@media (max-width: 1080px) {
  .hero h1 { text-wrap: balance; }
}
/* The headline and the button keep sharing a row all the way down to the
   narrowest phone — the button is the only action above the fold, and pushing
   it under the type on the screens that need it most is the wrong trade. From
   here down neither fits at full size, so both are sized off the row itself:
   container units, so the gutter's own clamp can't throw the sum off. */
@media (max-width: 900px) {
  .hero__inner {
    container-type: inline-size;
    gap: max(0.5rem, min(var(--s-8), 3cqw));
    padding-block: 140px var(--s-12);
    align-content: end;
  }
  /* The longest line is 5.9em wide, so the headline's share of the row would
     be a straight fraction of it — except the button stops shrinking at its
     floor and keeps 133px whatever happens. The subtracted constant is that
     residual. The min hands back to the desktop clamp at the width where
     that becomes the smaller of the two. */
  .hero h1 {
    text-wrap: nowrap;
    font-size: max(1.25rem, min(var(--fs-hero), calc(11.5cqw - 10px)));
  }
  /* Vertical padding is left alone as the icon shrinks: 18px + a 28px disc is
     still a 46px target. */
  .hero__aside .pill {
    font-size: max(0.6875rem, min(1.0625rem, 2.85cqw));
    gap: max(0.375rem, min(var(--s-4), 2.4cqw));
    padding-left: max(0.625rem, min(var(--s-6), 3.6cqw));
  }
  .hero__aside .pill__icon {
    width: max(28px, min(44px, 7.6cqw));
    height: max(28px, min(44px, 7.6cqw));
  }
  .hero__aside .pill__icon svg { width: 62%; height: 62%; }
}
@media (max-width: 560px) {
  .pill { font-size: var(--fs-sm); }
}

/* --- Now building strip (Work section) ---------------------------------- */
/* --- 2. By the numbers ------------------------------------------------------
   Two figures that matter get cards; two supporting ones get a footnote row.
   Unequal content, unequal weight.
   -------------------------------------------------------------------------- */
.numbers-top { background: var(--paper); }

.stats__head {
  display: grid; grid-template-columns: minmax(0,1fr) auto;
  gap: var(--s-8) var(--s-12); align-items: end;
  margin-bottom: var(--s-12);
}
.stats__head h2 { max-width: 16ch; }

/* Near-equal thirds: the card column runs a touch narrower than the two
   photographs, which is what stops it reading as a three-up grid. */
.stats__grid {
  display: grid; grid-template-columns: repeat(12, minmax(0,1fr));
  grid-template-rows: clamp(22rem, 34vw, 34rem);
  gap: var(--s-4);
}
.stats__col { grid-column: span 4; display: grid; grid-template-rows: 1fr 1fr; gap: var(--s-4); }
.shot--a { grid-column: span 4; }
.shot--b { grid-column: span 4; }

.figcard {
  background: var(--paper-sunk); border-radius: var(--r-lg);
  padding: clamp(1.25rem, 0.6rem + 1.6vw, 2rem);
  display: flex; flex-direction: column; justify-content: space-between; gap: var(--s-6);
  min-width: 0;
}
.figcard__fig {
  font-size: clamp(2.25rem, 1.2rem + 3.1vw, 3.75rem); line-height: 0.94;
  font-weight: var(--wt-display); letter-spacing: var(--ls-h1);
  font-variant-numeric: tabular-nums;
}
.figcard__label { font-size: var(--fs-sm); color: var(--text-sub); max-width: 22ch; }

.shot { margin: 0; border-radius: var(--r-lg); overflow: hidden; background: var(--paper-sunk); }
.shot img { width: 100%; height: 100%; object-fit: cover; will-change: transform; }


@media (max-width: 1024px) {
  .stats__grid { grid-template-rows: auto; }
  .stats__col { grid-column: span 12; grid-template-rows: auto; grid-template-columns: 1fr 1fr; }
  .figcard { min-height: 13rem; }
  .shot--a { grid-column: span 6; }
  .shot--b { grid-column: span 6; }
  .shot img { aspect-ratio: 3/4; height: auto; }
}
@media (max-width: 860px) {
  /* One column, so the button is no longer beside the heading but under it —
     and a grid item with nothing next to it stretches to the full column
     unless it is told where to sit. A button as wide as the page reads as a
     banner, not something to press. */
  .stats__head { grid-template-columns: 1fr; justify-items: start; }
}
@media (max-width: 560px) {
  .stats__col { grid-template-columns: 1fr; }
  .shot--a, .shot--b { grid-column: span 12; }
  .shot img { aspect-ratio: 4/3; }
}

/* --- 3. About ------------------------------------------------------------
   Hairlines instead of cards. A statement across the full measure, a rule
   under it, then the picture and the proof either side of a single vertical
   line. Nothing here is a box.
   -------------------------------------------------------------------------- */
.about__frame { border-top: 1px solid var(--line); }

.about__head { padding-block: var(--s-8) var(--s-12); border-bottom: 1px solid var(--line); }
.about__head h2 { max-width: 15ch; }

.about__split {
  display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr);
  align-items: start;
}
.about__media { padding-block: var(--s-12); padding-right: var(--s-12); }
.about__media img { width: 100%; aspect-ratio: 5/4; object-fit: cover; }

.about__aside {
  border-left: 1px solid var(--line);
  padding: var(--s-12) 0 var(--s-12) var(--s-12);
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--s-8);
  min-height: 100%;
}

/* Four jobs, overlapped. The same four buildings are further down the page,
   so this is a preview of the evidence rather than a stock-photo garnish. */
.about__thumbs { display: flex; }
.about__thumbs li { margin-left: -12px; }
.about__thumbs li:first-child { margin-left: 0; }
.about__thumbs img {
  width: 48px; height: 48px; object-fit: cover;
  border-radius: 50%; border: 2px solid var(--paper);
}
.about__proof { display: flex; align-items: center; gap: var(--s-4); }
.about__proof-text b { font-size: 1.75rem; line-height: 1.1; display: block; }
.about__proof-text span { font-size: var(--fs-sm); color: var(--text-sub); }

.about__note { font-size: var(--fs-lead); color: var(--text-sub); max-width: 42ch; }

/* Sits on the floor of its column, level with the bottom of the photograph,
   however much or little copy lands above it. */
.about__aside .pill { margin-top: auto; }

@media (max-width: 900px) {
  .about__split { grid-template-columns: 1fr; }
  .about__media { padding: var(--s-8) 0 0; }
  /* The 5:4 frame suits a half-column; across the full width of a phone it
     runs a quarter of the screen tall for one photograph. Shorter band, held
     above centre — the drawing and the hand reading it are the picture, and
     the jacket underneath is what goes. */
  .about__media img { aspect-ratio: 16/10; object-position: 50% 35%; }
  .about__aside {
    border-left: 0; border-top: 1px solid var(--line);
    padding: var(--s-8) 0;
    margin-top: var(--s-8);
  }
}

/* --- 4. Services ---------------------------------------------------------
   A deck rather than a grid. Each card sticks a few pixels below the one
   before it, so scrolling deals them onto the pile and every card that has
   already landed keeps a sliver of its top edge showing.

   No JS: sticky siblings in normal flow do the whole thing. That also means
   it cannot half-play — a card is either pinned or it isn't.
   -------------------------------------------------------------------------- */
.svc-stack {
  /* A fixed card height is what lets the deck hold still: a sticky card can
     only be centred in the viewport if we know how tall it is. */
  --card-h: clamp(400px, 62vh, 620px);
  --step: 16px;
  /* Centre of the frame, nudged down by half the stuck header and up by one
     step, so the finished pile lands on the middle rather than the first card
     landing there and the stack growing past it. */
  --stack-top: calc((100vh - var(--card-h)) / 2 + 18px);
  /* Tail: the run of scroll where the finished pile holds still before the
     whole thing lets go. It has to be real in-flow content. A sticky child is
     bounded by its parent's CONTENT box, inset by its own margins — so
     padding under the deck buys no travel, and neither does a margin on the
     last card, which cancels itself out. An empty block after the cards is
     the one thing that does. Kept short: every pixel of it is empty paper
     once the pile releases. */
  --deck-tail: 18vh;
  display: flow-root;
  padding-bottom: 0;
}
.svc-stack::after { content: ""; display: block; height: var(--deck-tail); }
/* The centred deck already leaves half a viewport of paper under the last
   card, so this section closes tighter than the rest of the page. */
#services { padding-bottom: var(--s-12); }

.svc-card {
  position: sticky;
  /* Each card parks 16px lower than its predecessor: that offset is the
     stack. --i comes from the markup, counting from zero. */
  top: calc(var(--stack-top) + var(--i) * var(--step));
  height: var(--card-h);
  margin-bottom: var(--s-8);

  display: grid; grid-template-columns: minmax(0, 0.86fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 1rem + 2.4vw, 3.5rem);
  align-items: stretch;

  background: var(--ink);
  border-radius: clamp(16px, 1.4vw, 24px);
  padding: clamp(1rem, 0.6rem + 1.6vw, 1.5rem);
  box-shadow: var(--shadow-overlay);
}
.svc-card:last-child { margin-bottom: 0; }

/* The card's height is fixed, so the photograph takes whatever is left and
   crops rather than setting the height itself. */
.svc-card__media { border-radius: clamp(10px, 1vw, 16px); overflow: hidden; height: 100%; }
.svc-card__media img {
  width: 100%; height: 100%; object-fit: cover;
  /* Same grade as every other photograph on the page. */
  filter: saturate(0.86) contrast(1.04);
}

.svc-card__body {
  display: flex; flex-direction: column; justify-content: center;
  gap: var(--s-6);
  padding: clamp(0.5rem, 1.6vw, 2rem) clamp(0.5rem, 2vw, 2.5rem) clamp(0.5rem, 1.6vw, 2rem) 0;
  height: 100%;
}
/* Number over trade, stacked. The count can run this large without competing
   with the heading because it carries no words — it reads as a mark, and the
   name of the trade reads as the label directly under it. */
.svc-card__top {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--s-4);
}
.svc-card__num {
  font-size: clamp(3.5rem, 1.9rem + 5vw, 6.5rem);
  line-height: 1;
  font-weight: var(--wt-hero);
  letter-spacing: var(--ls-hero);
  color: var(--action);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.svc-card__top h3 { font-size: clamp(1.375rem, 1.05rem + 1.3vw, 2rem); }
/* Direct child only: the count is a <p> too, and a descendant selector here
   outranks .svc-card__num and quietly greys it back down to body size. */
.svc-card__body > p { color: var(--text-inv-sub); font-size: var(--fs-body); max-width: 44ch; }

/* Narrow. The picture keeps its place beside the words all the way down — a
   full-width photograph stacked over the text makes a card taller than the
   frame it's pinned in, and the bottom of it never gets seen. The column
   narrows instead of moving, so the card reads the same at every width.
   Height comes from the content now, so the deck can't be centred by
   arithmetic — park it under the header instead. */
@media (max-width: 860px) {
  .svc-stack { --stack-top: clamp(88px, 7vw, 116px); --deck-tail: 20vh; }
  /* js/svc-deck.js decides whether the heading can hold the top of the frame
     while the pile builds under it, and parks the cards at its measured bottom
     edge. Position and paint order only — nothing here changes the heading's
     height, which is the number that file is measuring. The paper ground is
     insurance: if the heading grows between a font swap and the next
     measurement, a card passes behind it rather than through it. */
  #services.is-deck-pinned .head {
    position: sticky;
    top: clamp(84px, 6vw, 112px);
    z-index: 3;
    background: var(--paper);
  }
  .svc-card {
    grid-template-columns: clamp(96px, 28vw, 300px) minmax(0, 1fr);
    gap: var(--s-4);
    height: auto;
    align-items: stretch;
  }
  /* Out of flow: the picture takes its height from the words beside it, and a
     percentage height on an in-flow image has nothing to resolve against. */
  .svc-card__media { position: relative; height: auto; }
  .svc-card__media img { position: absolute; inset: 0; width: 100%; height: 100%; aspect-ratio: auto; }
  .svc-card__body {
    padding: var(--s-3) var(--s-3) var(--s-3) 0;
    justify-content: center; gap: var(--s-4);
  }
}
/* Short viewports can't hold a pinned card and still show the next one
   arriving, so the deck becomes a plain list. */
@media (max-height: 640px) {
  .svc-stack { --deck-tail: 0; }
  .svc-card { position: static; height: auto; }
}
/* Only where the card is still one column wide: under 861px the picture is a
   positioned strip, and handing it back its own aspect ratio would collapse
   the column it lives in. */
@media (max-height: 640px) and (min-width: 861px) {
  .svc-card__media { height: auto; }
  .svc-card__media img { height: auto; aspect-ratio: 16/10; }
}

/* Phones. Same card, tighter: the count and the body step down so the whole
   thing still fits the frame it's pinned in. */
@media (max-width: 640px) {
  .svc-card__body {
    padding: var(--s-2) var(--s-2) var(--s-2) 0;
    gap: var(--s-3);
  }
  .svc-card__top { gap: var(--s-2); }
  .svc-card__num { font-size: clamp(1.75rem, 8vw, 2.5rem); }
  .svc-card__body > p { font-size: var(--fs-sm); }
}

/* --- 5. Projects ---------------------------------------------------------
   A row that runs off the right edge of the page, pinned while you scroll
   through it: the section holds still and the projects travel sideways.
   js/workscroll.js does the pinning. Everything here works without it — the
   track is a plain horizontal scroller until that file says otherwise.
   -------------------------------------------------------------------------- */
/* The row overruns the text column on purpose; the page must not grow a
   horizontal scrollbar because of it. clip, not hidden: hidden would make this
   a scroll container and break the sticky pin inside it. */
#work { overflow-x: clip; }

.wscroll { position: relative; }
.wscroll__pin { display: flex; flex-direction: column; gap: var(--s-12); }
.wscroll__pin .head { margin-bottom: 0; }
.wscroll.is-pinned .wscroll__pin { position: sticky; top: clamp(84px, 6vw, 112px); }

.wscroll__track {
  display: flex; gap: var(--s-6);
  /* Swipeable by default. The pin turns this off and drives the row itself. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-left: var(--gutter);
  padding-bottom: var(--s-4);
}
.wscroll.is-pinned .wscroll__track {
  overflow: visible;
  scroll-snap-type: none;
  padding-bottom: 0;
  will-change: transform;
}

/* Cards, not posters. One object per project: photograph on top, a sunk grey
   foot under it carrying the words, so nothing has to be legible over an
   image. Every card is the same width now that they sit in a row. */
.project {
  flex: 0 0 clamp(260px, 34vw, 510px);
  scroll-snap-align: start;
  display: flex; flex-direction: column;
  overflow: hidden;
  border-radius: clamp(16px, 1.4vw, 24px);
  border: 1px solid var(--line);
  background: var(--paper-sunk);
}

.project__media {
  position: relative; margin: 0;
  aspect-ratio: 16/10;
  flex: none;
  overflow: hidden;
}

/* Pinned, a card is cut to whatever the viewport has left under the heading —
   js/workscroll.js measures that and sets --card-h — so the foot of every card
   is on screen the whole time it's travelling. The photograph gives up its
   fixed proportion and takes the slack; the foot keeps its own height. */
.wscroll.is-pinned .project { height: var(--card-h, auto); }
.wscroll.is-pinned .project__media { aspect-ratio: auto; flex: 1 1 auto; min-height: 0; }
.wscroll.is-pinned .project__text { flex: 0 0 auto; }
.project img {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%; object-fit: cover;
  transition: transform 900ms var(--ease-out);
}
.project:hover img, .project:focus-visible img { transform: scale(1.05); }

.project__text {
  display: flex; flex-direction: column; gap: var(--s-4);
  /* Grows to the tallest card in the row, so every foot lines up. */
  flex: 1 1 auto; justify-content: flex-end;
  padding: clamp(1rem, 0.7rem + 1vw, 1.5rem);
}
.project__meta {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4);
  font-size: var(--fs-cap); color: var(--text-sub);
  font-variant-numeric: tabular-nums;
}
.project__meta span + span::before { content: "·"; margin-right: var(--s-4); color: var(--action); }
.project h3 {
  font-size: clamp(1.25rem, 1rem + 1vw, 1.75rem);
  transition: color var(--t-fast) var(--ease-out);
}
/* The corner marker is gone with the overlay, so the title carries the
   affordance instead. */
.project:hover h3, .project:focus-visible h3 { color: var(--action); }

@media (max-width: 860px) {
  .wscroll__pin { gap: var(--s-8); }
  .project { flex-basis: min(78vw, 420px); }
}

/* --- 6. Process ----------------------------------------------------------
   A timeline down the middle of the page: one hairline in the centre, filling
   with the accent as you scroll, and the steps taking turns either side of it.
   js/process-rail.js drives the fill and marks the step you're level with —
   without it the line is a plain hairline and the steps are a numbered list,
   which is what they are underneath anyway.
   ------------------------------------------------------------------------ */
.hist {
  --hist-gap: clamp(1.75rem, 1.2rem + 2.6vw, 4rem);
  --dot: clamp(40px, 2rem + 1vw, 48px);
  position: relative;
  max-width: 62rem;
  margin-inline: auto;
}

/* Two pseudo-elements rather than two divs, because the list underneath has
   to stay an <ol> and nothing but an <li> belongs inside one. The fill is
   driven by a custom property, which inherits into a pseudo-element the way a
   transform set from script never could. */
.hist::before,
.hist::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 50%;
  width: 1px; margin-left: -0.5px;
  /* The line arrives and leaves rather than starting and stopping. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 10%, #000 90%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 10%, #000 90%, transparent);
}
.hist::before { background: var(--line); }
.hist::after {
  background: var(--action);
  transform: scaleY(var(--hist-p, 0)); transform-origin: 50% 0;
}

.hist__steps {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--hist-gap);
}

.step {
  /* Its own row, or the odd and even columns would pair up side by side. */
  grid-row: var(--i);
  position: relative;
  display: grid; gap: var(--s-3);
  padding-block: clamp(1.5rem, 1.1rem + 1.8vw, 2.5rem);
}
/* The bead is wider than half the gutter, so each column is held off the
   line by the difference — text never runs under it. */
.step:nth-child(odd)  { grid-column: 2; padding-left: var(--s-3); }
.step:nth-child(even) {
  grid-column: 1; padding-right: var(--s-3);
  text-align: right; justify-items: end;
}

/* A bead on the line for each step, carrying that step's icon, so an entry
   off to one side still reads as belonging to a point on the line. */
.step__bead {
  position: absolute; top: 50%; margin-top: calc(var(--dot) / -2);
  /* Above the rail: ::after paints after its element's children, so without
     this the line would be drawn straight across the icon. */
  z-index: 1;
  display: grid; place-items: center;
  width: var(--dot); height: var(--dot); border-radius: 50%;
  background: var(--paper); border: 1.5px solid var(--line);
  color: var(--text-mute);
  transition: background var(--t-mid) var(--ease-out),
              border-color var(--t-mid) var(--ease-out),
              color var(--t-mid) var(--ease-out);
}
.step__bead svg { width: 50%; height: 50%; }
/* Offsets are measured from the step's border box, which the inner padding
   above does not move — so it plays no part here. */
.step:nth-child(odd) .step__bead  { left:  calc(-1 * (var(--hist-gap) / 2) - var(--dot) / 2); }
.step:nth-child(even) .step__bead { right: calc(-1 * (var(--hist-gap) / 2) - var(--dot) / 2); }

.step__num {
  font-size: var(--fs-cap); letter-spacing: var(--ls-body);
  font-weight: var(--wt-display);
  font-variant-numeric: tabular-nums;
  color: var(--text-mute);
  transition: color var(--t-mid) var(--ease-out);
}
.step h3 { font-size: clamp(1.25rem, 1.05rem + .7vw, 1.625rem); }
.step p { font-size: var(--fs-sm); color: var(--text-sub); max-width: 46ch; }

/* The step you're level with takes the accent — number and bead. White on
   the fill is 5.3:1, so the icon holds up once the bead inverts. */
.step.is-active .step__num { color: var(--action); }
.step.is-active .step__bead {
  background: var(--action); border-color: var(--action); color: #fff;
}

/* Too narrow to read a column either side of the line, so the line moves to
   the edge and every step lines up to the right of it. The padding is half a
   bead, which is exactly the room the bead needs to hang to the left. */
@media (max-width: 700px) {
  /* Gap is half a bead plus breathing room, so the copy clears the icon. */
  .hist {
    --hist-gap: calc(var(--dot) / 2 + var(--s-3));
    --dot: 40px;
    padding-left: calc(var(--dot) / 2);
  }
  .hist::before, .hist::after { left: calc(var(--dot) / 2); }
  .hist__steps { grid-template-columns: 1fr; margin-left: var(--hist-gap); }
  .step:nth-child(odd), .step:nth-child(even) {
    grid-column: 1; padding-inline: 0;
    text-align: left; justify-items: start;
  }
  .step:nth-child(odd) .step__bead, .step:nth-child(even) .step__bead {
    left: calc(-1 * var(--hist-gap) - var(--dot) / 2); right: auto;
  }
  .step { padding-block: var(--s-6); }
}

/* --- 7. The record (ink) -------------------------------------------------
   Four tiles, unequal. The photograph is the biggest object here — it takes
   the widest column and the full height of the three tiles beside it, with
   the one figure that isn't financial sitting on its corner in orange. The
   tiles carry figures and short labels only; the sentences that explain them
   live in the sections either side.
   ------------------------------------------------------------------------ */
.bento {
  display: grid;
  /* The photograph's column is worth nearly both tile columns together. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.7fr);
  /* The tiles set the height of the section, not the photograph — see the
     absolute positioning below for why that's true. */
  grid-auto-rows: minmax(clamp(9.5rem, 6.5rem + 9vw, 15rem), auto);
  gap: var(--s-4);
}
.bento__card {
  display: flex; flex-direction: column; justify-content: space-between;
  gap: var(--s-8); min-width: 0;
  padding: clamp(1.25rem, 0.6rem + 1.6vw, 2rem);
  border-radius: var(--r-lg);
  border: 1px solid var(--line-inv);
}
.bento__card--fill { background: var(--ink-raised); border-color: transparent; }
.bento__card--wide { grid-column: span 2; }

.bento__fig {
  font-size: clamp(2.25rem, 1.2rem + 3.1vw, 3.75rem);
  line-height: 0.94; letter-spacing: var(--ls-h1);
  font-weight: var(--wt-display);
  font-variant-numeric: tabular-nums;
  color: var(--text-inv);
}
/* The currency sign is a marker, not a character in the number. */
.bento__fig sup { font-size: 0.46em; vertical-align: 0.62em; margin-right: 0.04em; color: var(--action-hi); }
.bento__label { font-size: var(--fs-sm); color: var(--text-inv-sub); max-width: 34ch; }

/* Third column, both rows: the photograph stands as tall as everything beside
   it. The rounding lives on the image rather than on the figure, so the plate
   can hang off the edge instead of being clipped by it. */
.bento__media {
  position: relative; z-index: 2;
  grid-column: 3; grid-row: 1 / span 2;
  margin: 0; min-height: 0;
}
/* Out of flow on purpose. Left in flow, this portrait's own proportion would
   size the two rows it spans and make the section as tall as the photograph
   is — 4:5 of picture where a third of it is ceiling and floor. Out of flow it
   takes the height the tiles set and crops to it. */
.bento__media img {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  /* Below the midline: the ceiling above the hard hat goes first, then the
     bottom of the jacket. The hat and the face stay in frame at every width. */
  object-fit: cover; object-position: 50% 52%;
  border-radius: var(--r-lg); background: var(--ink-raised);
}

/* The one number that isn't money, on the only orange object in the section. */
.bento__plate {
  position: absolute; left: calc(-1 * var(--s-8)); bottom: var(--s-8);
  width: min(15rem, 74%);
  padding: var(--s-6);
  border-radius: var(--r-md);
  background: var(--action-fill); color: #fff;   /* 5.3:1 */
  box-shadow: var(--shadow-overlay);
}
.bento__plate b {
  display: block;
  font-size: clamp(2rem, 1.4rem + 1.4vw, 2.5rem); line-height: 1;
  font-weight: var(--wt-display); letter-spacing: var(--ls-h1);
  font-variant-numeric: tabular-nums;
}
.bento__plate span { display: block; margin-top: var(--s-2); font-size: var(--fs-cap); }

@media (max-width: 940px) {
  .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The photograph gives up its column and becomes a band under the tiles —
     still the biggest thing in the section, just lying down. */
  .bento__media { grid-column: 1 / -1; grid-row: auto; aspect-ratio: 6/5; }
  /* Nothing to overhang into out here, so the plate comes inside the frame. */
  .bento__plate { left: var(--s-4); bottom: var(--s-4); }
}
@media (max-width: 560px) {
  .bento { grid-template-columns: 1fr; }
  .bento__card--wide { grid-column: span 1; }
  /* Upright on a phone, where the screen is tall and the photograph is still
     meant to be the thing you look at. */
  .bento__media { aspect-ratio: 4/5; }
}

/* --- 8. Testimonials -----------------------------------------------------
   One quote at a time on a sunk panel, with the portrait standing on the
   panel's floor and its head clearing the top edge. Three of them share a
   single grid cell and cross-fade; js/testimonials.js turns the stack on and
   hangs the two buttons in the bottom corner. Without it they are three
   cards in a column, which is the whole point of building it that way.
   -------------------------------------------------------------------------- */
.says {
  --say-lift: clamp(28px, 4.4vw, 72px);
  position: relative;
  /* Room for the overhang. Padding, not margin: a margin here would collapse
     into the section head's own margin-bottom and buy nothing, leaving the
     portrait's head in the lead copy. */
  padding-top: var(--say-lift);
}
.says > .say + .say { margin-top: var(--s-6); }

.say {
  margin: 0;
  display: grid; grid-template-columns: minmax(0, 0.42fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 1rem + 2vw, 3rem);
  align-items: end;
  background: var(--paper-sunk);
  border: 1px solid var(--line);
  border-radius: clamp(16px, 1.4vw, 24px);
  /* The portrait breaks the top edge, so nothing here may clip. */
  overflow: visible;
  min-height: clamp(260px, 24vw, 340px);
  padding-right: clamp(1.25rem, 0.8rem + 1.6vw, 2rem);
}

/* Stretched to the panel, then pulled up by the lift: the box ends up taller
   than its row by exactly that much, standing on the floor and overhanging
   the top. */
.say__portrait {
  position: relative;
  align-self: stretch;
  margin-top: calc(-1 * var(--say-lift));
  overflow: hidden;
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: clamp(10px, 1vw, 16px);
  background: var(--paper-sunk);
}
.say__portrait img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 16%;
  filter: saturate(0.86) contrast(1.04);
}
/* One blurred band instead of the reference's eight stacked masks: the
   photograph loses its detail into the panel it stands on rather than ending
   on a hard line. */
.say__portrait::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 30%;
  backdrop-filter: blur(9px);
  -webkit-backdrop-filter: blur(9px);
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 66%);
  mask-image: linear-gradient(to bottom, transparent, #000 66%);
  pointer-events: none;
}

.say__body {
  display: flex; flex-direction: column; justify-content: space-between;
  gap: var(--s-8);
  padding-block: clamp(1.5rem, 1rem + 1.6vw, 2.25rem);
  /* Clear of the buttons in the corner. */
  padding-bottom: clamp(4.5rem, 3.5rem + 2vw, 5.5rem);
}
.say blockquote {
  margin: 0;
  font-size: clamp(1.125rem, 0.95rem + 1vw, 1.75rem);
  line-height: 1.3; letter-spacing: var(--ls-h3);
  font-weight: var(--wt-display);
  max-width: 34ch;
}
.say__who { font-size: var(--fs-cap); color: var(--text-sub); }
.say__who b {
  display: block; color: var(--text);
  font-weight: var(--wt-display); font-size: var(--fs-sm);
}

/* --- The deck, once JS has taken over ----------------------------------- */
.says.is-deck { display: grid; }
.says.is-deck > .say {
  grid-area: 1 / 1;
  margin-top: 0;
  opacity: 0; pointer-events: none;
  transform: translateX(calc(var(--dir, 1) * 2rem));
  transition: opacity 420ms var(--ease-out), transform 620ms var(--ease-out);
}
.says.is-deck > .say.is-active {
  opacity: 1; pointer-events: auto;
  transform: none;
}

.says__nav {
  position: absolute; z-index: 2;
  right: clamp(1.25rem, 0.8rem + 1.6vw, 2rem);
  bottom: clamp(1.25rem, 0.8rem + 1.6vw, 2rem);
  display: flex; gap: var(--s-2);
}
.says__btn {
  width: 46px; height: 46px; padding: 0;
  display: grid; place-items: center;
  border-radius: 50%; cursor: pointer;
  border: 1px solid var(--line-strong);
  background: var(--paper); color: var(--ink);
  transition: background var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out);
}
.says__btn:hover {
  background: var(--action-fill); color: #fff;
  border-color: var(--action-fill);
}
.says__btn:active { background: var(--action-fill-hi); border-color: var(--action-fill-hi); }

@media (max-width: 760px) {
  .says { --say-lift: 0px; padding-top: 0; }
  .say {
    grid-template-columns: 1fr;
    align-items: start;
    gap: var(--s-6);
    padding: 0 var(--s-6);
    min-height: 0;
  }
  /* Stacked, the portrait has no panel floor to stand on — it becomes a plain
     picture at the top of the card and the lift goes away with it. */
  .say__portrait {
    margin-top: 0; align-self: start;
    aspect-ratio: 4/3;
    margin-inline: calc(-1 * var(--s-6));
    border-radius: 0;
    border-top-left-radius: inherit; border-top-right-radius: inherit;
  }
  .say__portrait img { object-position: 50% 24%; }
  .say__portrait::after { height: 22%; }
  .say__body { padding-bottom: var(--s-16); }
  .say blockquote { max-width: none; }
}

/* --- 9. FAQ ------------------------------------------------------------
   Centred head over one narrow column of filled cards. Each card holds its
   own answer, so the list never shifts sideways as things open — it only
   grows downward, and only one of them at a time.
   -------------------------------------------------------------------------- */
.faq__head {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  margin-bottom: var(--s-12);
}
.faq__head h2 { max-width: 18ch; }
.faq__head .lead { margin-top: var(--s-4); max-width: 46ch; }

/* Two columns. align-items:start lets each card size to its own answer
   rather than every card in a row matching the tallest one. */
.faq {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
  align-items: start;
  width: 100%; max-width: 1100px; margin-inline: auto;
}
@media (max-width: 760px) { .faq { grid-template-columns: 1fr; } }

.acc {
  background: var(--paper-sunk);
  border-radius: var(--r-lg);
  padding: var(--s-6);
  transition: background var(--t-fast) var(--ease-out);
}
.acc:hover { background: #E3DCD0; }

.acc__btn {
  width: 100%; display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-4);
  background: none; border: 0; cursor: pointer; text-align: left;
  padding: 0; min-height: 24px;
  font: inherit; font-size: var(--fs-lead);
  font-weight: var(--wt-display); letter-spacing: var(--ls-h3);
  line-height: var(--lh-body);
  color: var(--text);
}

/* Two 19px rules crossed into a plus. Opening rotates one onto the other,
   which lands as a minus without either bar ever leaving the box. */
.acc__icon { flex: none; width: 24px; height: 24px; position: relative; margin-top: 2px; }
.acc__icon::before, .acc__icon::after {
  content: ""; position: absolute;
  left: calc(50% - 9.5px); top: calc(50% - 1px);
  width: 19px; height: 2px; border-radius: 2px;
  background: var(--text);
  transition: transform var(--t-mid) var(--ease-out), background var(--t-fast) var(--ease-out);
}
.acc__icon::after { transform: rotate(-90deg); }
.acc__btn[aria-expanded="true"] .acc__icon::after { transform: rotate(0deg); }
.acc__btn[aria-expanded="true"] .acc__icon::before,
.acc__btn[aria-expanded="true"] .acc__icon::after { background: var(--action); }

/* Answers are open until the script closes them — no JS, no trapped content. */
.acc__panel { overflow: hidden; }
.acc__panel p {
  font-size: var(--fs-body); color: var(--text-sub);
  padding-top: var(--s-4); max-width: 52ch;
}

@media (max-width: 560px) {
  .acc { padding: var(--s-4); }
  .acc__btn { font-size: var(--fs-body); }
}

/* --- 10. CTA band ------------------------------------------------------- */
/* Full-bleed photograph with the words standing on it — no card, no column.
   The page has spent nine sections on paper; the last thing before the footer
   is the site itself at the end of a shift. */
.cta {
  position: relative; isolation: isolate; overflow: hidden;
  min-height: min(58svh, 520px);
  display: flex; align-items: center;
  padding-block: clamp(3.5rem, 2.25rem + 4.5vw, 6rem);
  background: var(--ink);
}
/* Overscanned top and bottom so the parallax-free crop never shows an edge. */
.cta__media { position: absolute; inset: -6% 0; z-index: 0; }
.cta__media img {
  /* Weighted to the top of the frame: the band is short now, and the bottom
     of this photograph is the part worth losing. */
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 26%;
  filter: saturate(0.86) contrast(1.06) brightness(0.82);
}
/* Dark where the words are, clear where the sun is. */
.cta__scrim {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(to right, rgba(8,6,5,.86) 0%, rgba(8,6,5,.62) 38%, rgba(8,6,5,.18) 78%);
}
.cta__body { position: relative; z-index: 2; }
/* Bone, not the muted grey it gets on flat ink: over a photograph the muted
   tone drops to 3.5:1 against the brightest pixel the scrim lets through. */
.cta__body h2 { max-width: 15ch; }
.cta .pill { margin-top: clamp(1.5rem, 1rem + 1.6vw, 2.25rem); }

@media (max-width: 860px) {
  .cta { min-height: min(54svh, 440px); }
  /* Nothing is to the right of the type any more, so the scrim runs bottom-up
     instead of left-to-right. */
  .cta__scrim {
    background: linear-gradient(to top, rgba(8,6,5,.88) 0%, rgba(8,6,5,.62) 46%, rgba(8,6,5,.30) 100%);
  }
}

/* --- 11. Newsletter ----------------------------------------------------- */
.news { max-width: 42ch; }
/* The signup is headed by the lockup itself now, so the mark is sized to the
   name rather than to the nav's smaller one. */
.news__brand { font-size: var(--fs-h3); gap: 12px; }
.news__brand .brand__mark { width: 26px; height: 31px; }
.news__form { margin-top: var(--s-6); }
.news__row { display: flex; gap: var(--s-3); flex-wrap: wrap; align-items: flex-end; }
.news__field { flex: 1 1 220px; }
.news label { display: block; font-size: var(--fs-cap); color: var(--text-inv-sub); margin-bottom: 6px; }
.news input {
  width: 100%; min-height: 48px; padding: 0 var(--s-4);
  background: var(--paper-sunk); color: var(--text-inv);
  border: 1px solid #6E655C; border-radius: var(--r-md);
  font: inherit; font-size: var(--fs-sm);
  transition: border-color var(--t-fast) var(--ease-out);
}
.news input::placeholder { color: #8A8079; }
.news input:hover { border-color: #A29689; }
.news input[aria-invalid="true"] { border-color: #FF8D7A; }
.news__msg { display: none; align-items: flex-start; gap: var(--s-2); margin-top: var(--s-3); font-size: var(--fs-cap); }
.news__msg.is-shown { display: flex; }
.news__msg--err { color: #FF8D7A; }
.news__msg--ok { color: #8FD8A4; }
.news__msg svg { flex: none; margin-top: 2px; }
@media (max-width: 900px) { .news { max-width: none; } }

/* --- Contact block in footer -------------------------------------------- */
.fcontact { margin-top: var(--s-8); font-size: var(--fs-sm); color: var(--text-inv-sub); display: grid; gap: 2px; justify-items: start; }
.fcontact a { padding-block: 4px; }
.fcontact a:hover { color: var(--action-hi); }

/* --- Reveal primitive -----------------------------------------------------
   .reveal is only a hook. The hidden state is applied by reveal.js, which
   means a blocked CDN or a thrown error leaves a fully readable page rather
   than a column of invisible sections. Section leads only, never everything. */
@media (prefers-reduced-motion: reduce) {
  .hero__line > span { transform: none !important; }
  .tag__dot { animation: none; }
}



