/* ==========================================================================
   Marble Consulting Group — section layer
   Ground: obsidian. Accent: antique gold. Type: Inter.

   Sections added after editorial.css passed 500KB live here instead. That file
   is large enough that a plain static server truncates it mid-transfer, which
   silently drops whatever sits near its end - these rules were being written
   into a region the browser never received. Keeping new work in a small file
   that always arrives is the difference between shipping and shipping blind.
   ========================================================================== */

/* ==========================================================================
   OUR CLIENTS — "THE ROSTER"   (.roster, index.html)

   Three clients, one static row. The section it replaced travelled a camera
   past five 3D plates over a full screen of scroll; a roster of three does
   not earn that, and a continuously scrolling strip of three reads as a
   carousel that ran out of slides.

   The instrument is the one used everywhere else on this site: a gold
   hairline, and things hung off it. Two rules bracket the row, the items are
   divided by hairlines of the same weight, and nothing else is drawn. The
   marks sit at 58% opacity and come up to full on hover, so the row reads as
   one quiet object at rest and resolves into three when you look at it.

   THE MARK SLOT TAKES AN <img> WITHOUT ANY CSS CHANGE. .roster__mark is a
   fixed-height flex box; an <img> inside it inherits the height cap, the
   greyscale and the hover. Client logos are trademarks and none are in
   assets/ yet, so the slot currently holds a typeset wordmark instead.
   ========================================================================== */
.roster {
  position: relative;
  padding-block: clamp(2.34rem, 1.872rem + 1.872vw, 3.6rem);
}
.roster__rule {
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--border-gold-strong) 22%, var(--border-gold-strong) 78%, transparent);
  opacity: 0.55;
}
.roster__rule--top { top: 0; }
.roster__rule--bot { bottom: 0; }

.roster__head {
  text-align: center;
  margin-bottom: clamp(2.25rem, 1.8rem + 2vw, 3.5rem);
}
.roster__head .eyebrow { justify-content: center; }
.roster__h {
  font-size: clamp(1.35rem, 1.1rem + 1.1vw, 2rem);
  letter-spacing: -0.022em;
  text-wrap: balance;
}
.roster__h em { color: var(--gold-light); font-style: normal; }

.roster__row {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: center;
}
.roster__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  padding-inline: clamp(0.75rem, 2vw, 2rem);
  text-align: center;
}
/* the divider is a hairline between items, never an outline around them */
.roster__item + .roster__item {
  border-left: 1px solid var(--border-gold);
}

.roster__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  height: clamp(1.75rem, 1.4rem + 1.2vw, 2.5rem);
  opacity: 0.6;
  /* see .strip__mark: reversed white, for the same reason */
  filter: brightness(0) invert(1);
  transition: opacity var(--dur) var(--ease-out);
}
.roster__logo {
  max-height: 100%;
  width: auto;
  max-width: clamp(7rem, 12vw, 10.5rem);
  object-fit: contain;
}
/* THE SWAP IS AUTOMATIC AND NEEDS NO CODE CHANGE.
   Each slot ships both a real <img> and a typeset wordmark. If the logo file
   exists the image loads and :has() hides the word; if it is missing the
   inline onerror removes the broken <img>, :has() no longer matches, and the
   wordmark stands in. Dropping three files into assets/logos/ is the entire
   deployment step, and a missing file degrades to type instead of to a broken
   image icon. */
.roster__mark:has(img) .roster__word { display: none; }
.roster__item:hover .roster__mark,
.roster__item:focus-within .roster__mark { opacity: 1; }
/* stand-in until a real mark exists: the name, set as a wordmark */
.roster__word {
  font-size: clamp(1.15rem, 0.95rem + 0.75vw, 1.6rem);
  font-weight: 600;
  letter-spacing: -0.018em;
  color: var(--white);
  white-space: nowrap;
}
.roster__cat {
  font-size: var(--text-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--vein);
  line-height: 1.4;
}

.roster__note {
  margin-top: clamp(2.25rem, 1.8rem + 2vw, 3.5rem);
  text-align: center;
  color: var(--vein);
  font-size: var(--text-sm);
  text-wrap: balance;
}

/* Under 720px three columns cannot hold a wordmark and a category without
   breaking both, so the row becomes a stack and the dividers turn horizontal. */
@media (max-width: 720px) {
  .roster__row { grid-template-columns: 1fr; }
  .roster__item {
    flex-direction: row;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.1rem 0;
    text-align: left;
  }
  .roster__item + .roster__item {
    border-left: 0;
    border-top: 1px solid var(--border-gold);
  }
  .roster__cat { text-align: right; }
}

/* ==========================================================================
   THE PATH — "THE STEPS"   (.steps, index.html)

   Replaced .rise, which travelled a 3D camera past five photographic plates.
   Two things were wrong with it. The reader had to decode the motion before
   they could read the content, and the order the steps arrived in depended on
   how fast you scrolled - so a section whose entire job is "these happen in
   sequence" delivered them out of sequence.

   No scroll effect here at all. Five steps, numbered, left to right in reading
   order. The only motion is the baseline .reveal: one 28px fade-up per item,
   staggered 60ms so the eye is led along the row once, then nothing moves
   again. IntersectionObserver fires it once and unobserves; prefers-reduced-
   motion skips it entirely. Same hairline instrument as .roster.
   ========================================================================== */
.steps {
  padding-block: clamp(2.34rem, 1.872rem + 1.872vw, 3.6rem);
}
.steps__head {
  max-width: 46rem;
  margin-bottom: clamp(2.25rem, 1.8rem + 2vw, 3.25rem);
}
.steps__h {
  font-size: clamp(1.5rem, 1.2rem + 1.4vw, 2.35rem);
  letter-spacing: -0.026em;
  text-wrap: balance;
}
.steps__h em { color: var(--gold-light); font-style: italic; }
.steps__lead {
  margin-top: 0.85rem;
  color: var(--vein);
  font-size: var(--text-base);
  text-wrap: pretty;
}

.steps__row {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0;
  border-top: 1px solid var(--border-gold);
}
.steps__item {
  padding: clamp(1.25rem, 1rem + 0.9vw, 1.9rem) clamp(0.85rem, 1.4vw, 1.5rem);
  padding-left: 0;
}
.steps__item + .steps__item {
  border-left: 1px solid var(--border-gold);
  padding-left: clamp(0.85rem, 1.4vw, 1.5rem);
}
.steps__n {
  display: block;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--gold);
  margin-bottom: 0.7rem;
}
.steps__t {
  font-size: clamp(0.98rem, 0.92rem + 0.25vw, 1.1rem);
  font-weight: 600;
  color: var(--white);
  letter-spacing: -0.012em;
  margin-bottom: 0.4rem;
}
.steps__p {
  color: var(--vein);
  font-size: 0.9rem;
  line-height: 1.55;
  text-wrap: pretty;
}

/* Five columns stop working long before the phone: the copy is a sentence, not
   a label. Two up on tablet, stacked on mobile, dividers rotate to match. */
@media (max-width: 1020px) {
  .steps__row { grid-template-columns: repeat(2, 1fr); }
  .steps__item, .steps__item + .steps__item {
    padding-left: clamp(0.85rem, 1.4vw, 1.5rem);
    border-left: 1px solid var(--border-gold);
  }
  .steps__item:nth-child(odd) { border-left: 0; padding-left: 0; }
  .steps__item:nth-child(n + 3) { border-top: 1px solid var(--border-gold); }
}
@media (max-width: 620px) {
  .steps__row { grid-template-columns: 1fr; }
  .steps__item, .steps__item + .steps__item {
    border-left: 0;
    padding-left: 0;
  }
  .steps__item + .steps__item { border-top: 1px solid var(--border-gold); }
}

/* ==========================================================================
   WHAT MARBLE DOES — "IN PLAIN ENGLISH"   (.plain, index.html)

   The plainest section on the page by intent - the test is whether someone
   who reads only this can explain the business to somebody else. But plain
   copy still needs a composition, and set as one 40rem column it left half
   the viewport empty and gave the eye nowhere to land.

   TWO COLUMNS, UNEVEN, AND THEY FINISH TOGETHER. The claim sits left over a
   framed photograph, the argument runs right, and a rule down the gutter
   says they are two different jobs. The plate is not given a height: it
   takes whatever the prose leaves, so both columns land on the same line at
   every width. See the block comment above .plain__plate.

   THE WHOLE SECTION IS SIZED TO ONE SCREEN. That is the constraint the
   composition is built around, and it is why the photograph sits inside the
   left column rather than spanning the page, and why the three statements
   close on a drawn rule instead of in a second framed box.
   ========================================================================== */
.plain {
  position: relative;
  padding-block: clamp(2.4rem, 1.92rem + 2.048vw, 4.16rem);
  overflow: hidden;
}
/* one soft light source behind the claim, the same instrument the hero uses */
.plain__glow {
  position: absolute;
  top: -12%;
  left: -6%;
  width: min(46rem, 70%);
  aspect-ratio: 1;
  background: radial-gradient(circle, rgba(156, 120, 72, 0.11), transparent 62%);
  pointer-events: none;
}

/* THE ASIDE IS NO LONGER STICKY. Sticky is for a short claim that holds
   while a long argument scrolls past it; this column took the photograph
   and is now the taller of the two, so there is nothing to hold. */
.plain__grid {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: clamp(1.75rem, 1.35rem + 2.1vw, 3.75rem);
  align-items: start;
}
.plain__h {
  font-size: clamp(1.6rem, 1.25rem + 1.6vw, 2.6rem);
  letter-spacing: -0.03em;
  text-wrap: balance;
}
.plain__h em { color: var(--gold-light); font-style: italic; }

/* a small typographic footing under the claim so the column does not end in air */
.plain__kicker {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: clamp(1rem, 0.85rem + 0.6vw, 1.5rem);
  font-size: var(--text-xs);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--vein);
}
/* motion.js grows this from nothing when the claim lands. It is one pixel of
   detail, and it is the difference between a typographic footing that was
   set and one that was drawn. */
.plain__kicker i {
  width: 1px;
  height: 0.8em;
  transform-origin: center;
  background: currentColor;
  opacity: 0.45;
}

/* One rule down the gutter. The left column asserts and the right column
   argues; the line is there to say they are two different jobs. It runs the
   length of the argument only, which is why it sits on the body rather than
   being drawn down the grid.

   A PSEUDO-ELEMENT RATHER THAN A border-left, because a border cannot be
   scaled and this one has to draw itself down as the argument arrives. Every
   other hairline in the section already does (the seam, the cell dividers,
   and .ledger below): a rule that simply appears was the one piece of the
   section still behaving like a static stylesheet. --rule defaults to 1, so
   with no JS it is just a line. */
.plain__body {
  position: relative;
  padding-left: clamp(1.5rem, 1.15rem + 1.5vw, 2.75rem);
}
.plain__body::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--border-gold);
  transform: scaleY(var(--rule, 1));
  transform-origin: top center;
}
.plain__body p {
  color: var(--ink);
  font-size: var(--text-base);
  line-height: 1.78;
  text-wrap: pretty;
}
.plain__body p + p { margin-top: 1.15rem; }
/* the opening line carries the whole claim, so it is set larger and lit */
.plain__lede {
  font-size: clamp(1.1rem, 1rem + 0.45vw, 1.32rem) !important;
  line-height: 1.6 !important;
  color: var(--white) !important;
  padding-bottom: 1.15rem;
  border-bottom: 1px solid var(--border-gold);
}
.plain__lede b { color: var(--gold-light); font-weight: 600; }

/* --------------------------------------------------------------------------
   THE PLATE - the photograph, framed, in the left column.

   2.1:1 AND THE SUBJECT CHOSE IT, NOT THE LAYOUT. The three figures occupy
   source rows 110 to 620 of an 848-row frame, so 510 rows have to survive the
   crop or somebody loses their head or their feet. 2.1:1 leaves 602 visible,
   which is 537 once motion.js applies its 1.12 over-scale: 27 rows of margin
   around the figures, half of it spent by the drift at full travel.

   THIS IS WHY THE RATIO IS LOCKED RATHER THAN FILLED. The first pass had the
   plate flex-grow into whatever height the prose left it, which read as
   elegant and produced a 528x176 letterbox showing 421 of the 510 rows it
   needed. A photograph's crop is a fact about the photograph. It cannot be a
   remainder, and it must not change with the copy or the viewport.

   Sized medium on purpose: it is an aside to the claim above it, not the
   subject of the section. The frame never moves. The photograph drifts
   inside it, which is the whole point of framing something: an aperture,
   not a card.
   -------------------------------------------------------------------------- */
.plain__plate {
  position: relative;
  aspect-ratio: 2.1 / 1;
  margin: clamp(1.25rem, 1rem + 1.1vw, 2rem) 0 0;
  border: 1px solid var(--border-gold);
  border-radius: var(--radius-md);
  background: var(--obsidian);
  /* clips the photograph to the frame's radius AND gives the drift its walls */
  overflow: hidden;
  /* lit object, not a box drawn on the page: one hairline of gold catching the
     top edge, and a shadow tinted to the ground rather than to black */
  box-shadow:
    inset 0 1px 0 rgba(184, 145, 88, 0.10),
    0 24px 56px -42px rgba(9, 7, 4, 0.95);
}
/* NO TRANSFORM AND NO TRANSITION HERE. motion.js owns transform on this
   element for both the settle and the scroll drift, and a CSS transition
   sharing a property with a tween makes the image judder. Without JS it
   simply sits at scale 1 and fills the frame exactly. */
.plain__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* centres the three figures in the visible band rather than the frame:
     13 rows of clearance above the heads and below the feet, which is what
     lets the drift travel without cropping either */
  object-position: 50% 31%;
  filter: saturate(0.92) contrast(1.04);
}
/* Resting scrim. It seats the photograph into the frame's dark ground so the
   edges do not glare against the page, and it keeps the top of the image from
   competing with the prose that ends just above it. */
.plain__veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 1;
  background:
    linear-gradient(180deg, rgb(var(--scrim-rgb) / 0.38) 0%, transparent 30%, transparent 58%, rgb(var(--scrim-rgb) / 0.5) 100%),
    radial-gradient(118% 78% at 50% 44%, transparent 42%, rgb(var(--scrim-rgb) / 0.42) 100%);
}
/* The layer the entry tween lifts, and the only reason it is separate from
   the veil: opacity on a flat overlay costs one composite, where animating a
   filter on the image itself repaints every frame. At rest it is invisible,
   so with no JS the photograph is simply already developed. */
.plain__burn {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background: rgb(var(--scrim-rgb) / 0.92);
}

/* THE PLATE ANSWERS THE POINTER. It was the only framed photograph on the
   site that did not: .shot lifts its image, .crew__card resolves to full
   colour, the client marks come up from 58%. A frame that stays inert while
   every other one responds reads as a printed block dropped on the page.

   IT LIGHTS, IT DOES NOT MOVE. The house gesture for a photograph is
   .shot's scale(1.05), and it is unavailable here: motion.js owns transform
   on this image for the settle and the drift, and a CSS transition sharing
   a property with a tween makes the image judder. So the veil thins, the
   photograph warms, and the gold hairline on the top edge comes up - which
   is the site's own language anyway, since everything else here moves like
   light. filter and opacity are untouched by GSAP, so nothing is contested. */
@media (hover: hover) {
  .plain__plate,
  .plain__veil,
  .plain__img {
    transition: opacity var(--dur) var(--ease-out), filter var(--dur) var(--ease-out),
      box-shadow var(--dur) var(--ease-out);
  }
  .plain__plate:hover {
    box-shadow:
      inset 0 1px 0 rgba(184, 145, 88, 0.24),
      0 24px 56px -42px rgba(9, 7, 4, 0.95);
  }
  .plain__plate:hover .plain__veil { opacity: 0.68; }
  .plain__plate:hover .plain__img { filter: saturate(1) contrast(1.06); }
}

/* The three statements close the section on a drawn rule, not in a second
   box. One framed object per section is the whole discipline: a bordered
   strip sitting under a bordered photograph reads as packaging, and it would
   have cost the height this rewrite was meant to give back. */
/* Tight against the row above on purpose. The section is budgeted to one
   screen and this gap is the cheapest height on the page: the drawn rule
   already separates the statements from the argument, so spending 3rem here
   as well only buys the reader a scroll. */
.plain__close {
  margin-top: clamp(1.6rem, 1.3rem + 1.4vw, 2.6rem);
}
/* A short gold lead-in at the rule's left end so it reads as drawn from
   somewhere rather than switched on. */
.plain__seam {
  position: relative;
  display: block;
  height: 1px;
  background: var(--border-gold);
  transform-origin: left center;
}
.plain__seam i {
  position: absolute;
  inset: 0;
  display: block;
  background: linear-gradient(90deg, var(--gold) 0%, transparent 52%);
  opacity: 0.55;
}

.plain__three {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
/* No left pad on the first cell: its number has to sit on the container edge
   or the row reads as inset from a frame that is no longer there. */
.plain__three li {
  position: relative;
  padding: clamp(1.1rem, 0.9rem + 0.85vw, 1.7rem) clamp(1.15rem, 0.9rem + 1.1vw, 2rem);
}
.plain__three li:first-child { padding-left: 0; }
/* Divider as a pseudo-element so motion.js can draw it down by tweening
   --seam, with no extra markup. The 1 default is what no-JS gets. */
.plain__three li + li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--border-gold);
  transform: scaleY(var(--seam, 1));
  transform-origin: top center;
}
.plain__n {
  display: block;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--gold);
  margin-bottom: 0.8rem;
}
.plain__three h3 {
  font-size: clamp(0.98rem, 0.93rem + 0.25vw, 1.12rem);
  font-weight: 600;
  color: var(--white);
  letter-spacing: -0.014em;
  margin-bottom: 0.5rem;
  text-wrap: balance;
}
.plain__three p {
  color: var(--vein);
  font-size: 0.92rem;
  line-height: 1.62;
  text-wrap: pretty;
}
/* The only hover in the row: the number lifts to the light gold. With the
   frame gone there is nothing for a background wash to sit inside, so a
   warmed rectangle would just be a floating shape. Pointer devices only. */
@media (hover: hover) {
  .plain__n { transition: color var(--dur) var(--ease-out); }
  .plain__three li:hover .plain__n { color: var(--gold-light); }
}

/* Three columns of sentence-length copy stop working well before the phone,
   and once the grid is a single column the plate has no row to fill, so it
   takes a ratio instead of a flex share. */
@media (max-width: 940px) {
  .plain__grid { grid-template-columns: 1fr; gap: 1.75rem; }

  /* THE RULE TURNS WITH THE STACK; IT DOES NOT SWITCH OFF. It used to, on
     the reasoning that there is nothing to divide once the columns are
     stacked — but the rule was never dividing the two columns from each
     other. It marks the ARGUMENT, which is why it sits on .plain__body and
     runs that block's length rather than the grid's. The argument is still
     here in one column, so the rule still has its job: it runs down the side
     of the prose instead of up its gutter, and the same --rule tween draws
     it. Without this the stacked section is the only rendering that loses
     its own central instrument. */
  .plain__body { padding-left: clamp(0.95rem, 4vw, 1.4rem); }

  /* PARAGRAPHS THAT READ AS PARAGRAPHS. The gap between them was 18.4px
     against a 30.3px line — the space BETWEEN paragraphs was smaller than
     the space between two lines of the same one, which is the exact
     arrangement that sets 641px of prose as a single grey block. */
  .plain__body p + p { margin-top: 1.8rem; }

  /* full column width on the phone, so it can afford a squarer crop and
     more of the figures than the desktop band shows */
  .plain__plate { aspect-ratio: 16 / 9; }
  .plain__three { grid-template-columns: 1fr; }
  .plain__three li,
  .plain__three li:first-child { padding-inline: 0; }

  /* THE STATEMENTS BECOME A NUMBERED LEDGER. Stacked, each number was a
     full-width block line above its heading: 33px of column spent on two
     characters, three times over, and the row read as a generic list. Set in
     a gutter on the heading's own baseline it reads as a numbered clause,
     which is what it is — and the three cells hand back 87px. */
  .plain__three li {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    column-gap: 0.85rem;
    row-gap: 0.45rem;
    align-items: baseline;
  }
  .plain__n {
    grid-area: 1 / 1;
    margin-bottom: 0;
  }
  .plain__three h3 {
    grid-area: 1 / 2;
    margin-bottom: 0;
    /* 15.8px against a 14.7px paragraph: a heading a hair larger than its
       own body copy is not a heading. And the display leading it inherits
       (1.06) is set for a line that never wraps — it does wrap at 320. */
    font-size: 1.0625rem;
    line-height: 1.28;
  }
  .plain__three p { grid-area: 2 / 2; }

  /* the divider turns with the stack: a rule between rows, drawn left to
     right off the same --seam the columns use */
  .plain__three li + li::before {
    left: 0;
    right: 0;
    top: 0;
    bottom: auto;
    width: auto;
    height: 1px;
    transform: scaleX(var(--seam, 1));
    transform-origin: left center;
  }
}
@media (max-width: 560px) {
  .plain__plate { aspect-ratio: 3 / 2; }
}

/* --------------------------------------------------------------------------
   THE PHONE. Everything structural is already handled by the stack above;
   what is left is type that never scaled. The claim sat within 1.3px of its
   clamp floor across the entire phone range — 25.6px at 320 in a 280px
   measure, 26.9px at 430 — so it was simultaneously too big for the smallest
   phone and too small for the largest. One width-driven ramp instead.
   -------------------------------------------------------------------------- */
@media (max-width: 760px) {
  .plain__h { font-size: clamp(1.5rem, 7vw, 2.05rem); }

  /* 1.78 is a long-measure leading. At 320px of column it is loose enough
     that the paragraph gap has to grow with it to stay visible; bringing it
     in a step lets the 1.8rem gap above read cleanly and hands back a line
     of height on the longest paragraph. */
  .plain__body p { line-height: 1.72; }
  .plain__lede { padding-bottom: 1.35rem; }
}

/* ==========================================================================
   02 · CLIENT STRIP   (.strip, index.html)

   MOVED OUT 2026-08-16. The band that lived here was a centred row - label,
   three marks at 52%, category line - and it was replaced by "the register"
   in css/register.css, which owns every .strip rule now. Nothing is left
   here on purpose: a stale half-definition of a section that lives in
   another file is worse than no definition at all.
   ========================================================================== */

/* ==========================================================================
   08 THE TEAM   (.crew, index.html)

   Three cards, one row. Two carry real portraits; the founder's carries an
   initials plate until a photograph exists. The plate is the same box at the
   same aspect ratio, so the row reads as deliberate rather than as a grid
   with a hole in it - and swapping it for an <img> later needs no CSS change.

   THE PORTRAITS ARE NORMALISED, NOT JUST DROPPED IN. Both were re-cropped to
   one 4:5 at 800x1000 and the lighter studio backdrop pulled down 28%, since
   the two originals were shot against a dark charcoal and a pale grey. On an
   obsidian page an unmatched pale plate glares and reads as a stock photo.
   The section grade below finishes the job: every plate is desaturated and
   dimmed at rest, and resolves to full colour on hover, so mismatched
   lighting never shows at the same moment across two cards.
   ========================================================================== */
.crew { padding-block: clamp(2.5rem, 2rem + 2.4vw, 4.5rem); }
.crew__head { max-width: 46rem; margin-bottom: clamp(1.75rem, 1.4rem + 1.8vw, 3rem); }
.crew__h {
  font-size: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem);
  letter-spacing: -0.028em;
  text-wrap: balance;
}
.crew__h em { color: var(--gold-light); font-style: italic; }
.crew__lead {
  margin-top: 0.9rem;
  color: var(--vein);
  font-size: var(--text-base);
  line-height: 1.7;
  text-wrap: pretty;
}
/* the homepage's § 07 has no visible title (its h2 is .sr-only), so the lead
   sits straight under the eyebrow at the house eyebrow-to-heading gap */
.crew__head .sr-only + .crew__lead { margin-top: 0; }

.crew__row {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 0.85rem + 1vw, 1.75rem);
}

.crew__card {
  position: relative;
  display: flex;
  flex-direction: column;
}

/* the plate: one aspect ratio for every card, portrait or placeholder */
.crew__shot {
  position: relative;
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border: 1px solid var(--border-gold);
  border-radius: var(--radius-md);
  background: var(--surface);
  transition: border-color var(--dur) var(--ease-out);
}
.crew__card:hover .crew__shot,
.crew__card:focus-within .crew__shot { border-color: var(--border-gold-strong); }

.crew__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 22%;
  /* Full colour at rest. An earlier version desaturated these to make two
     differently-lit studio backdrops agree, which worked and cost the actual
     photographs - the wrong trade on the one section that exists to show
     people's faces. The backdrops are matched in the FILES instead, so the
     colour can stay on. */
  filter: saturate(1.04) contrast(1.03);
  transform: scale(1.02);
  transition: filter 0.85s var(--ease-out), transform 1s var(--ease-out);
}
.crew__card:hover .crew__shot img,
.crew__card:focus-within .crew__shot img {
  filter: saturate(1.12) contrast(1.05) brightness(1.05);
  transform: scale(1.05);
}
/* the ground pulled up the bottom of the plate so the card sits in the page */
.crew__shot::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, transparent 45%, rgba(6, 6, 8, 0.55) 100%);
}

/* the founder's plate until a portrait exists */
.crew__shot--empty {
  display: grid;
  place-items: center;
  background:
    radial-gradient(120% 90% at 50% 20%, rgba(156, 120, 72, 0.1), transparent 60%),
    var(--surface);
}
.crew__initials {
  font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.6rem);
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--gold-light);
  opacity: 0.55;
}

.crew__meta { padding-top: clamp(0.8rem, 0.7rem + 0.4vw, 1.1rem); }
.crew__name {
  font-size: clamp(1rem, 0.95rem + 0.3vw, 1.18rem);
  font-weight: 600;
  color: var(--white);
  letter-spacing: -0.018em;
}
.crew__role {
  margin-top: 0.2rem;
  font-size: var(--text-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
}
.crew__bio {
  margin-top: 0.6rem;
  color: var(--vein);
  font-size: 0.9rem;
  line-height: 1.6;
  text-wrap: pretty;
}
.crew__more { margin-top: clamp(1.5rem, 1.2rem + 1vw, 2.25rem); }

@media (max-width: 760px) {
  .crew__row { grid-template-columns: repeat(2, 1fr); }
}
/* Two to a row down to the smallest common phones. One full-width portrait
   per row made four people 2.3 screens of scrolling at 375px, each face the
   size of the screen. The narrower column takes a tighter gutter and a
   little less tracking on the title, which wraps rather than clips. Under
   340px a name and a title no longer fit side by side: one column again. */
@media (max-width: 460px) {
  .crew__row { gap: 1.5rem 0.75rem; }
  .crew__role { letter-spacing: 0.12em; }
}
@media (max-width: 339px) {
  .crew__row { grid-template-columns: 1fr; max-width: 22rem; }
}

/* ===================================================================
   02 SERVICES - moved out of this file.

   This slot held .svcs, "the drawer": six service files racked in
   perspective, one pulled open at a time. It was replaced by THE INDEX,
   which keeps the drawer's one real finding - six things of equal rank,
   all six names and summaries permanently legible - and drops the
   accordion shape it wore. That section owns its own sheet, loaded by
   index.html alone:

       css/bill.css      (.bill, and js/motion.js -> initBill)

   Nothing on any page still references .svcs. If you are here from a git
   blame looking for it, it is in the history at this line.
   =================================================================== */
/* Logos vary wildly in aspect: AT&T is 2.5:1, Primo is 4.5:1. Cap the width so
   one wide wordmark cannot dominate a row sized by height. */
.strip__logo { max-height: 100%; width: auto; max-width: clamp(6rem, 11vw, 9rem); object-fit: contain; }
.roster__logo { max-height: 100%; width: auto; max-width: clamp(7rem, 13vw, 11rem); object-fit: contain; }
