/* ===========================================================================
   compose.css — where things actually sit, and what they are set in.

   WHY THIS FILE EXISTS. leaf.css owns the section spacers, the ink and the
   live/hidden swap. word/band/table/callout/menu each own one component. Nobody
   owned (a) the composition INSIDE each leaf, or (b) the type for the shared
   content classes. Without it every leaf fell into default block flow at
   default 16px Archivo, so the giant word overlapped the lede, the callout
   floated loose, and the corner chrome was pushed out of view.

   Loaded after callout.css and before motion.css: it may position what the
   component sheets define, and motion.css still owns every animation.
   ======================================================================== */


/* === The stage ============================================================ */

.stage{
  position: relative;
  min-width: 0;
  min-height: 0;
}

/* Each relocated wrapper fills the stage and lays its own children out. The
   absolute/inset pair comes from leaf.css; the grid is ours.

   Three deliberate guarantees, each fixing something that actually broke:

   1. ONE EXPLICIT COLUMN + min-content rows. Without a declared track list the
      children auto-placed into shared cells on some leaves and drew on top of
      each other (the product photo sat under its own heading). One column and
      one row per child makes overlap structurally impossible.

   2. `safe center`. Plain `align-content:center` centres overflow in BOTH
      directions, so anything taller than the stage loses its top off the edge
      of the card with no way to reach it — that is why leaf 02's headline and
      the spec index were sliced. `safe` falls back to `start` the moment
      content exceeds the box, so the top is always the part you keep.

   3. `overflow: hidden auto` as the valve. Leaves are designed to fit, but a
      long word, a large font-size preference or a short viewport must degrade
      to a scroll — never to content that cannot be reached at all. */
#stage > .leaf__body{
  display: grid;
  overflow: hidden auto;

  /* NO `overscroll-behavior: contain` HERE — it was the reason the page could
     only be scrolled from the thin strip outside the card. The card covers
     almost the whole viewport, so almost every wheel event lands on a leaf
     body. `contain` stops a scroll container from chaining to its parent even
     when it has nothing left to scroll, so the leaf silently ate the wheel and
     the document never moved. Default (`auto`) chains, which is what we want:
     the leaf scrolls only if it genuinely overflows, otherwise the page does. */
  overscroll-behavior: auto;

  min-width: 0;
  scrollbar-width: thin;
}

/* SPECIFICITY NOTE, load-bearing: `#stage > .leaf__body` is one id + one class,
   which OUTRANKS a bare `#body-04`. Anything a per-leaf rule needs to override
   — alignment, tracks — must therefore be declared at class level or lower, or
   the two-column leaves and leaf 08's bottom-anchored colophon get clobbered.
   Only properties no per-leaf rule sets may live in the block above. */
.leaf__body{ align-content: safe center }


/* === Shared content type ==================================================
   Spec 2.2. Bodoni for anything that speaks, Archivo for anything that labels.
   Never a third family, never a monospace. */

/* Statement — leaf 02 and leaf 06 headlines. */
.statement{
  font-family: var(--f-display);
  font-variation-settings: "opsz" 48;
  font-weight: 400;
  font-size: var(--t-statement);
  letter-spacing: -0.014em;
  line-height: 1.05;
  color: var(--on-plate);
  text-wrap: balance;
  max-width: 26ch;
}
.statement em,
.statement .clause em{ font-style: italic }

/* Leaf 06 sets its three clauses on their own lines. */
.statement .clause{ display: block }

/* Plate name — the cream detail plate. */
.plate-name{
  font-family: var(--f-display);
  font-variation-settings: "opsz" 32;
  font-weight: 500;
  font-size: var(--t-plate);
  letter-spacing: -0.008em;
  line-height: 1.06;
  color: var(--on-plate);
}

/* The single testimonial. */
.quote blockquote{
  font-family: var(--f-display);
  font-variation-settings: "opsz" 32;
  font-style: italic;
  font-weight: 400;
  font-size: var(--t-quote);
  line-height: 1.24;
  color: var(--on-plate);
  max-width: 30ch;
}
.quote figcaption{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 100;
  font-size: var(--t-label);
  color: var(--on-soft);
  margin-top: var(--s-4);
}

/* Body copy — a different voice via the width axis, not a third download. */
.lede,
.body,
address{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 108;
  font-weight: 400;
  font-size: var(--t-body);
  line-height: 1.72;
  color: var(--on-soft);
  max-width: 31ch;
  font-style: normal;
}
.lede{ max-width: 42ch }

/* Parenthetical micro-labels: (2024), (houston, texas), (what fails in transit) */
.above,
.forms,
.sub,
table caption{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 100;
  font-weight: 400;
  font-size: var(--t-label);
  letter-spacing: 0;
  color: var(--on-soft);
}

/* Small caps-style annotation inside the band's type panels. */
.caps{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 88, "wght" 600;
  font-size: var(--t-label);
  letter-spacing: 0.024em;
  text-transform: uppercase;
}

/* Any pill used as a link in a leaf reuses chrome.css's pill; it just needs to
   stop being a grid item that stretches full width. */
a.pill{
  justify-self: start;
  align-self: start;
  width: max-content;
}

/* The strike through "recordings" — one of the two --film-red uses on the site. */
.struck{
  position: relative;
  white-space: nowrap;
}
.struck::after{
  content: "";
  position: absolute; left: -0.04em; right: -0.04em; top: 55%;
  height: 2px;
  background: var(--film-red);
}


/* === 01 · Hero — PROTECTED ================================================ */

/* THE WORD IS NOT A FRAME FOR A PHOTOGRAPH.
   The photo used to sit over the middle letters, which left PROTECTED reading
   as "PRO   TED". The word now owns its own line, uninterrupted, and the
   photograph sits beneath it as a plate — offset off-centre so the composition
   stays editorial rather than symmetrical, and captioned by the callout. */
#body-01{
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 1fr auto auto auto 1fr;
  justify-items: center;
  align-content: safe center;
  row-gap: clamp(0.6rem, 1.3cqw, 1.4rem);
}
#body-01 .word{ grid-row: 2; justify-self: center }
/* Sized so word + plate + lede clear a 900px viewport without the leaf having
   to fall back to its internal scroll: measured 743px against a 649px stage
   at 30cqw, 649px at 23cqw. */
#body-01 .cut{
  grid-row: 3;
  position: static;
  margin: clamp(0.25rem, 0.8cqw, 0.75rem) 0 0;
  justify-self: center;
  width: clamp(180px, 23cqw, 360px);
  height: calc(clamp(180px, 23cqw, 360px) * 9 / 16);
  transform: none;
  clip-path: none;
  translate: clamp(-2rem, -6cqw, -0.5rem) 0;   /* off the optical centre */
}
#body-01 .lede{
  grid-row: 4;
  text-align: center;
  max-width: 48ch;
  color: var(--on-soft);
}
/* The dimension callout is an annotation, not part of the column.

   CENTRED VERTICALLY, NOT PINNED TO THE BOTTOM, and the reason is the rotation.
   `.callout--v` in css/callout.css is `transform: rotate(-90deg)` — the drafting
   convention for a vertical dimension — and a rotation does NOT change the
   element's layout box. The figure lays out 220x30 and paints 30x220, so its
   visual extent overflows its own box by 95px top and bottom.

   With the original `bottom: 0` that put the drawn line's foot at y 670 against
   a card whose inner edge is 654, and `.card` is `overflow: hidden`, so the
   bottom of the dimension and one of its two ticks were simply clipped off.
   Centring on the body instead puts the 220px drawn extent in the middle of the
   hero's own height, which is also what a vertical dimension of the text block
   should measure. Do not "simplify" this back to a bottom offset without
   accounting for the 95px the rotation adds at each end. */
#body-01 .callout{
  position: absolute;
  left: 0;
  top: 50%;
  /* half the UNROTATED box height (30px), to centre the box on the midline */
  margin: -15px 0 0;
}


/* === 02 · What makes PBE PBE — carbon =====================================

   ONE CLAIM, ONE PHOTOGRAPH, THREE PROOFS. Two columns that share both their top
   and bottom edges: the claim and the plate stack down the left, the ledger
   stretches the full height of the right and distributes its three rows into it.
   Sharing both edges is what makes a two-column leaf read as one object.

   WHAT THIS REPLACED, so nobody rebuilds it: the merged leaf carried a three-line
   display headline, a five-row manifest, a second display claim and a row of three
   thumbnails. Four objects at four scales on a 449px stage — every one of them
   fought the others and the leaf had no subject. The rule that came out of it is
   that a leaf this short can hold ONE dominant element and quiet everything else;
   here the photograph is the dominant element and the type is ranked around it.

   Sizes are capped against the window HEIGHT as well as the card width. A cqw only
   knows how wide the card is, and this leaf's failure mode is being too tall. */
#body-02{
  grid-template-columns: minmax(0, 1.06fr) minmax(0, 0.94fr);
  grid-template-areas:
    "claim reasons"
    "proof reasons";
  align-content: safe center;
  column-gap: clamp(1.5rem, 3.6cqw, 3.75rem);
  row-gap: min(clamp(0.85rem, 2cqw, 1.75rem), 2.8svh);
}
#body-02 .statement{
  grid-area: claim;
  align-self: start;
  font-size: min(var(--t-statement), 6svh);
  max-width: 20ch;
}

/* The plate. Panoramic ON PURPOSE — cut to 1500x560 from the extruder-line
   original rather than cover-cropped from a 4:3, because a 2.7:1 band is the one
   shape that sits under a three-line claim without either crowding the other.
   It is the only photograph on the leaf: "we make it", shown once, large. */
#body-02 .proof{
  grid-area: proof;
  align-self: start;
  margin: 0;
  display: grid;
  gap: var(--s-2);
  min-width: 0;
}
#body-02 .proof img{
  display: block;
  width: 100%;
  height: auto;
  max-height: 33svh;            /* height decides the crop, width never distorts */
  object-fit: cover;
  object-position: 50% 46%;     /* the roll and the machine bed, not the floor */
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px var(--rule-dark);
}
#body-02 .proof :is(figcaption, .proof__cap){
  font-family: var(--f-label);
  font-variation-settings: "wdth" 84;
  font-size: var(--t-label);
  color: var(--on-soft);
}

/* ── The synced carousel ─────────────────────────────────────────────────────
   Four frames STACKED IN ONE GRID CELL, all at `grid-area: 1 / 1`, so the figure
   keeps the height of a single plate instead of growing to four. Each frame is
   itself the original img-then-caption grid, which is the point: the caption
   cross-fades WITH its own photograph, so it never describes the wrong picture.

   The index comes from js/reel.js and is shared with the hero's reel — see the
   markup comment in index.html. This file owns only where the frames sit.

   Deliberately no `position: absolute`: absolute frames would collapse the
   figure's height to zero and the leaf's grid row with it. One grid cell gives
   the same stacking and keeps the plate measurable. */
#body-02 .proof--reel{
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

#body-02 .proof--reel .plate__frame{
  grid-area: 1 / 1;
  display: grid;
  gap: var(--s-2);
  min-width: 0;
  opacity: 0;
  /* Matches the hero reel's cross-fade in css/reel.css, so the two plates change
     on the same curve as well as on the same frame. */
  transition: opacity 2.1s var(--e-grade);
}

#body-02 .proof--reel .plate__frame[data-on]{
  opacity: 1;
}

/* Only the pair actually cross-fading holds a layer. */
#body-02 .proof--reel .plate__frame:is([data-on], [data-was]){
  will-change: opacity;
}

@media (prefers-reduced-motion: reduce){
  /* The first frame, held — the same answer .reel--still gives. js/reel.js also
     creates no interval at all under this preference, so nothing advances. */
  #body-02 .proof--reel .plate__frame{ transition: none }
  #body-02 .proof--reel .plate__frame:first-child{ opacity: 1 }
}

/* The three proofs, one per clause of the claim, in the claim's own order. A
   hairline ledger in the same voice as the spec index — deliberately NOT three
   cards: a card grid here would flatten the hierarchy the photograph establishes.
   `space-between` is what lands the last rule level with the foot of the plate. */
#body-02 .reasons{
  grid-area: reasons;
  align-self: stretch;
  list-style: none;
  margin: 0; padding: 0;
  display: grid;
  align-content: space-between;
  border-top: 1px solid var(--on-rule);
}
#body-02 .reasons > li{
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr);
  column-gap: var(--s-3);
  padding-block: min(clamp(0.6rem, 1.5cqw, 1.2rem), 2.2svh);
  border-bottom: 1px solid var(--on-rule);
}
.reasons__num{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 84;
  font-size: var(--t-label);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--on-soft);
  line-height: 1.6;
}
.reasons__body{ display: grid; gap: 3px; min-width: 0 }
.reasons__name{
  font-family: var(--f-display);
  font-variation-settings: "opsz" 24;
  font-weight: 400;
  font-size: min(clamp(1.05rem, 1.7cqw, 1.45rem), 3svh);
  line-height: 1.14;
  letter-spacing: -0.006em;
  color: var(--on-plate);
}
/* READING SIZE, not label size. These three sentences are the argument the leaf
   exists to make; at --t-label (11px, the micro-label step) they read as fine
   print under the names, and the column had 140px of unused height anyway. */
.reasons__desc{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 96;
  font-size: min(var(--t-body), 2.15svh);
  line-height: 1.5;
  max-width: 46ch;
  color: var(--on-soft);
}

/* Narrow card: one column, claim then plate then ledger, and the ledger packs to
   the top instead of spreading — there is no facing column left to align with. */
@container (max-width: 760px){
  #body-02{
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "claim" "proof" "reasons";
  }
  #body-02 .reasons{ align-content: start }
}

/* PHONE: THE THREE THINGS HAVE TO ADD UP, and stacked at their desktop sizes
   they did not. Measured at 375px, where the stage is 324 x 628: the claim came
   to 105, the photograph to 295 and the three reasons to 358, which with the row
   gaps is 786 — 158px past the bottom of the card, and the card does not scroll.
   So the third reason ("We answer the phone", the leaf's own differentiator) was
   simply below the fold with no way to reach it.

   Nothing is dropped to fix it. The photograph is the one element that can give
   up height without giving up meaning — it is proof of the claim, and it proves
   it just as well as a letterbox band as it does as a portrait plate, whereas a
   reason with its text cut in half proves nothing. Held to 21svh it comes to
   about 130, which brings the leaf to roughly 620 and inside the card with the
   type untouched.

   `object-fit: cover` is what makes that safe: the frame gets shorter, the
   photograph inside it does not get squashed. The row gap tightens a little
   alongside, which is the difference between fitting and only just fitting on
   the shortest phones. */
@container (max-width: 560px){
  #body-02{ row-gap: min(0.7rem, 1.6svh) }
  #body-02 .proof{
    height: min(19svh, 138px);
    min-height: 0;
  }
  #body-02 .proof img{ height: 100%; object-fit: cover }
  #body-02 .proof--reel .plate__frame{ height: 100% }
}



/* === 03 · The line-up band ================================================= */

/* `minmax(0, 1fr)`, not `1fr`: a bare 1fr row still grows to its content's
   min-content height, so a band taller than the stage pushed the leaf past the
   card instead of being bounded by it. With a 0 floor the row IS the stage, and
   the strip below can take its height from the row rather than from a viewport
   unit — which is what keeps the line-up inside the card at any window shape. */
#body-03{
  grid-template-rows: minmax(0, 1fr);
  align-content: stretch;
}
#body-03 .band{
  align-self: stretch;
  height: 100%;
  max-height: 100%;
}
#body-03 .band .panel{ height: 100%; min-height: 0 }
#body-03 .band .panel img{ width: 100%; height: 100%; object-fit: cover; display: block }


/* === 04 · Specification index — seal ====================================== */

#body-04{
  grid-template-columns: minmax(0, 1fr) auto;
  align-content: safe center;
  column-gap: clamp(1rem, 3cqw, 3rem);
}
#body-04 .index{ align-self: center }

/* The silhouette is the leaf's quiet illustration, not a product photo. */
/* Definite height, not aspect-ratio — same grid row-sizing trap as the
   product photo above. */
#body-04 .silhouette{
  align-self: center;
  width: clamp(90px, 14cqw, 220px);
  height: calc(clamp(90px, 14cqw, 220px) * 4 / 3);
  -webkit-mask-image: var(--mask);
  mask-image: var(--mask);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}
#body-04 .silhouette img{
  width: 100%; height: 100%; object-fit: cover; display: block;
}
@container (max-width: 700px){
  #body-04{ grid-template-columns: minmax(0, 1fr) }
  #body-04 .silhouette{ display: none }
}


/* === Product pages — the description beside the pictures ==================
   Two honest columns: the left column is everything you read, the right is
   everything you look at. Earlier versions overlaid the name on the photo and
   tried to do it with grid areas across five rows; it fought itself and the
   pieces drifted apart. Explicit structure (.line__text / .line__media) is
   what actually holds together.

   The pictures take the larger track. Source order matches: the h1 is read
   before the gallery it names.
   ======================================================================== */

#body-line{
  grid-template-columns: minmax(0, 1fr);
  align-content: safe center;
  justify-items: stretch;
}

/* Both tracks are sized to their content, not to a fraction of the card, and the
   pair is then centred. Fractional tracks gave the words far more width than the
   40ch measure ever uses and the pictures far more than their 534px ceiling, so
   the two columns ended up at opposite edges with a 240px hole down the middle
   of the card. Centring the pair puts that slack in the outer margins, where it
   reads as margin rather than as a gap. */
.line{
  display: grid;
  grid-template-columns: minmax(0, max-content) minmax(0, 534px);
  justify-content: center;
  column-gap: clamp(1.5rem, 4cqw, 4rem);
  row-gap: clamp(1rem, 2.4cqw, 2rem);
  align-items: center;
  width: 100%;
}

/* --- right: the pictures ------------------------------------------------- */

/* The gallery is capped at its sources' true width (534px, below), so on a wide
   card its track is always wider than it is. Centred, not flush right: pushed to
   the end it left a hole in the middle of the card between the words and the
   pictures. The words stay pinned to the card's left edge, under the PBE mark. */
.line__media{
  display: grid;
  gap: clamp(0.5rem, 1.2cqw, 1rem);
  min-width: 0;
  justify-items: center;
}

/* The variants, as pictures. This grid IS the variant list: every variant of the
   line appears here exactly once, captioned, with its own tile. There is no hero
   above it (the hero was a second copy of one of these tiles, blown up past its
   source width) and no text list beside it repeating these captions.

   534px = two 260px columns plus the gap, and 260px is the whole of every
   source. That is the ceiling: the band.css hard rule holds here too — bigger
   product pictures need new photography, not a wider grid. The 58svh term keeps
   two rows of them inside the card on a short window. */
.line__gallery{
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.4rem, 1cqw, 0.85rem);
  width: min(100%, 534px, 58svh);
  justify-self: center;
}
.line__gallery figure{ margin: 0; display: grid; gap: 0.35rem }
.line__gallery img{
  width: 100%;
  aspect-ratio: 1 / 1;   /* the sources are square: this crops none of them */
  height: auto;
  object-fit: cover;
  display: block;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px var(--panel-edge);
}
.line__gallery figcaption{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 84;
  font-size: var(--t-label);
  color: var(--on-soft);
  line-height: 1.25;
}

.line__watch{ margin: 0; justify-self: center }
.line__media .machines{
  margin: 0;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: min(100%, 534px, 58svh);
  justify-self: center;
}
.line__media .machine__still{ height: clamp(70px, 12cqw, 168px) }
.line__media .machine__play{ width: 32px; height: 32px }
.line__media .machine__play::before{
  border-left-width: 9px; border-top-width: 6px; border-bottom-width: 6px;
}

/* --- right: the words ---------------------------------------------------- */

.line__text{
  display: grid;
  gap: clamp(0.3rem, 0.8cqw, 0.6rem);
  align-content: center;
  min-width: 0;
}
.line__text .plate-name{
  font-size: clamp(1.7rem, 4.2cqw, 3rem);
  margin: 0;
}
.line__text .body{ max-width: 40ch; margin-top: var(--s-2) }
.line__text a.pill{ justify-self: start; margin-top: var(--s-4) }

/* Stacked: the words sit above the pictures, and both align to the same left
   edge — nothing is flush right when there is no second column to be right of. */
@container (max-width: 760px){
  .line{ grid-template-columns: minmax(0, 1fr) }
  .line__media{ justify-items: start }
  .line__gallery,
  .line__watch,
  .line__media .machines{ justify-self: start }
}


/* === Product pages, contact and privacy ===================================
   The generated pages reuse the leaf-05 detail-plate composition, plus these. */

/* Request-a-spec form. Fields are hairlines, not boxes — the card is the box. */
.spec-form{
  display: grid;
  gap: clamp(0.5rem, 1.4cqw, 1.1rem);
  width: min(100%, 46ch);
  text-align: left;
  margin: 0;
}
.spec-form .field{ display: grid; gap: 0.35rem; margin: 0 }
.spec-form label{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 100;
  font-size: var(--t-label);
  color: var(--on-soft);
}
.spec-form input,
.spec-form textarea{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 108;
  font-size: var(--t-body);
  color: var(--on-plate);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--on-rule);
  padding: 0.45rem 0;
  border-radius: 0;
  transition: border-color var(--d-chrome) var(--e-lay);
}
.spec-form textarea{ resize: vertical; min-height: 4.5rem }
.spec-form input:focus-visible,
.spec-form textarea:focus-visible{
  outline: none;
  border-bottom-color: var(--seal-blue);
}
.spec-form .actions{ margin: var(--s-3) 0 0 }
.spec-form button.pill{ cursor: pointer }

/* 16px MINIMUM ON A PHONE, and it is not a taste decision — it is the one
   number iOS Safari watches. Focus an input whose computed font-size is under
   16px and Safari zooms the whole page in to make it legible, which on this site
   means the card jumps, the layout is left mid-scale, and the reader has to
   pinch back out to reach the next field. --t-body resolves to 14px at this
   width, so every field on the request-a-spec form did exactly that.

   16px on the CONTROLS only. The labels above them are Archivo small caps and
   stay where they are; nothing else on the page changes size. */
@media (max-width: 560px){
  .spec-form input,
  .spec-form textarea{ font-size: 16px }
}

/* The modal that opens after Send. See contact.html for why it is a <dialog>.

   SIZED IN rem AND vw, NOT cqw. Everything else on this page is measured
   against the card's container, but this element renders in the TOP LAYER over
   the whole viewport — a width in container units would size it to a box it is
   no longer visually inside, and on a narrow card it came out as a sliver in
   the middle of a full-screen backdrop. */
.spec-modal{
  width: min(30rem, calc(100vw - 2.5rem));
  margin: auto;                        /* centres in the top layer, both axes */
  padding: clamp(1.4rem, 4vw, 2.25rem);
  border: 0;
  border-radius: 14px;
  background: var(--plate-foam);
  color: var(--stencil);
  box-shadow: 0 40px 90px -30px rgba(20,22,26,.55), 0 0 0 1px var(--rule);
}
/* The dim behind it. Warm rather than neutral black — the card sits on kraft
   photography and a cold grey wash over it reads as a browser chrome, not as
   part of the site. */
.spec-modal::backdrop{
  background: rgba(32,26,20,0.52);
  backdrop-filter: blur(3px);
}

.spec-modal__head{
  font-family: var(--f-display);
  font-variation-settings: "opsz" 40;
  font-weight: 400;
  font-size: clamp(1.35rem, 4.4vw, 1.75rem);
  line-height: 1.12;
  letter-spacing: -0.014em;
  color: var(--stencil);
  margin: 0 0 var(--s-3);
}
.spec-modal__body{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 100;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--stencil-2);
  margin: 0 0 var(--s-4);
}
.spec-modal__ways{
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  margin: 0 0 var(--s-3);
}
/* Sized as targets, not as text, and stacked rather than inline: this is the
   path for someone whose mail app has just failed them, on a phone, and it
   should be tappable without aiming. */
.spec-modal__way{
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: 0 var(--s-3);
  font-family: var(--f-display);
  font-variation-settings: "opsz" 32;
  font-size: clamp(1.05rem, 4.6vw, 1.4rem);
  letter-spacing: -0.01em;
  color: var(--stencil);
  text-decoration: none;
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
}
.spec-modal__way:hover,
.spec-modal__way:focus-visible{
  color: var(--seal-blue);
  border-color: var(--seal-blue);
}
/* The details, quoted back. A <pre> because these are lines the reader is about
   to copy verbatim: line breaks are the content, and any wrapping that is not
   theirs would be pasted into the email as well. `pre-wrap` keeps a long
   description on screen without collapsing the breaks. */
.spec-modal__quote{
  margin: 0 0 var(--s-3);
  padding: var(--s-3);
  max-height: 34vh;
  overflow: auto;
  background: var(--plate-cream);
  border: 1px solid var(--rule);
  border-radius: 8px;
  font-family: var(--f-label);
  font-variation-settings: "wdth" 100;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--stencil);
  white-space: pre-wrap;
  word-break: break-word;
}
.spec-modal__quote:focus-visible{ outline: 2px solid var(--seal-blue); outline-offset: 2px }
.spec-modal__actions{ margin: 0 0 var(--s-4) }
.spec-modal .sub{ margin: 0 0 var(--s-4) }
.spec-modal form{ margin: 0 }
.spec-modal button{ cursor: pointer }

/* Line videos. preload="none" is set in the markup: these clips are 23-90 MB
   straight off a phone, so nothing is fetched until the visitor presses play. */
.videos{
  display: grid;
  gap: clamp(0.75rem, 1.6cqw, 1.5rem);
  width: min(100%, 56ch);
  justify-self: center;
  margin-top: clamp(0.75rem, 2cqw, 1.75rem);
}
.videos[data-count="3"]{
  width: min(100%, 92cqw);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}
.vid{ margin: 0; display: grid; gap: 0.5rem }
.vid video{
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--plate-carbon);
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px var(--panel-edge);
  display: block;
}
.vid figcaption{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 100;
  font-size: var(--t-label);
  color: var(--on-soft);
  text-align: left;
}
.vid p{
  font-family: var(--f-label);
  font-size: var(--t-body);
  color: var(--on-soft);
  padding: var(--s-4);
}

/* Static prose (privacy). */
.prose{ width: min(100%, 62ch); text-align: left; display: grid; gap: var(--s-4) }
.prose .body{ max-width: none }

/* Their content is a reading column, left-aligned inside a centred block.
   Not id-scoped: contact composes as #body-01 and privacy as its own body. */
.spec-form,
.prose{ justify-self: center }

/* The contact page's colophon sits in flow under the form. */
#body-01 .colophon{
  width: 100%;
  margin-top: clamp(1.25rem, 3cqw, 2.5rem);
  padding-top: var(--s-4);
  border-top: 1px solid var(--on-rule);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2.5cqw, 2.5rem);
  font-family: var(--f-label);
  font-size: var(--t-label);
  color: var(--on-soft);
  text-align: left;
}
#body-01 .colophon ul{ list-style: none; margin: 0; padding: 0 }
#body-01 .colophon a{ color: var(--on-plate) }
@container (max-width: 640px){
  #body-01 .colophon{ grid-template-columns: minmax(0, 1fr) }
}


/* === 05 · The card inverts — carbon =======================================
   RETIRED. This was the service leaf ("we make it, we cut it, we answer the
   phone") on its own carbon slide. It is now the right-hand column of leaf 02,
   which took the carbon ground with it — see §02 above. The inversion still
   happens exactly once on the page; it just happens one slide earlier, on the
   leaf whose subject is what goes wrong. Nothing else claimed #body-05, so the
   rules are gone rather than left pointing at markup that no longer exists. */


/* === 07 · The mark, alone ==================================================
   One object on an empty ground. Nothing shares this slide — the client asked
   for the logo on its own, and a solid needs room to read as a solid. It turns
   as you arrive and fades out as the About slide arrives underneath it. */

/* One row, floored at 0, stretched: the leaf's single child is the mark-and-
   reviews composition, and its own rows are sized against the stage. A bare
   `1fr` grows to its content's min-content height, which left the review ring
   a 73px band to be absolutely positioned inside — the quotes drew over the
   cube and off the bottom of the card. */
#body-07{
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  justify-items: stretch;
  align-content: stretch;
}


/* === The review card, shared by leaf 10 ================================== */

/* The card itself. Leaf 10 overrides the flow and the columns below. */
.reviews{
  list-style: none;
  margin: 0; padding: 0 0 var(--s-3);
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(min(78vw, 22rem), 22rem);
  gap: clamp(0.75rem, 1.8cqw, 1.5rem);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}
.review{
  scroll-snap-align: start;
  display: grid;
  align-content: start;
  gap: var(--s-3);
  padding: clamp(0.75rem, 1.6cqw, 1.25rem);
  border: 1px solid var(--on-rule);
  border-radius: 2px;
  min-width: 0;
}
.review blockquote{ margin: 0 }
.review blockquote p{
  margin: 0;
  font-family: var(--f-display);
  font-variation-settings: "opsz" 24;
  font-style: italic;
  font-size: clamp(0.95rem, 1.15cqw, 1.1rem);
  line-height: 1.35;
  color: var(--on-plate);
}
.review figcaption{ display: grid; gap: 2px }
.review .who{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 88, "wght" 600;
  font-size: var(--t-label);
  color: var(--on-plate);
}
.review .what{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 76;
  font-size: var(--t-label);
  color: var(--on-soft);
}



/* === 08 · About — the company, as a place on the home page ============================
   The whole company story, on the home page. There is no separate About page:
   the menu jumps here. The numbered facts sit under the lede, the team photo
   beside both. */

#body-08{
  grid-template-columns: minmax(0, 1fr);
  align-content: safe center;
}
#body-08 .about{
  display: grid;
  /* The words take the larger share: narrowing them to feed a wide photo added
     more lines than the photo was worth and pushed the leaf past the card. */
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 0.65fr);
  grid-template-areas:
    "lede  photo"
    "facts facts"
    "more  more";
  column-gap: clamp(1.1rem, 2.6cqw, 2.6rem);
  row-gap: clamp(0.4rem, 0.8cqw, 0.75rem);
  align-items: start;
  width: 100%;
}
#body-08 .about__lede { grid-area: lede }
/* The photo stands beside the PROSE only, and the two rows under it run the full
   width of the card. It used to span all three rows, which cost the leaf twice:
   the portrait had to stretch to whatever the text came to, and — because a
   spanning item forces its rows to add up to its own height — shortening the
   facts underneath bought nothing, the photo just pushed the space back. Beside
   the prose alone, the photo is cropped by one row it cannot fight, and the facts
   get the whole width to spread across. */
#body-08 .about__photo{
  grid-area: photo;
  margin: 0;
  align-self: stretch;
  justify-self: stretch;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  min-height: 0;
}
#body-08 .facts      { grid-area: facts; min-width: 0 }
#body-08 .about__more{ grid-area: more; margin: 0 }

/* Capped against the WINDOW HEIGHT as well as the card width. Every other size on
   this leaf is a cqw, which only knows how wide the card is — and this leaf fails
   by being too TALL. On a short window the heading is the first thing that should
   give. */
#body-08 .statement{
  font-size: min(clamp(1.35rem, 2.9cqw, 2.2rem), 5svh);
  max-width: 20ch;
}
/* Wider measure and a tighter leading than the shared .body: the two paragraphs
   are the longest prose on the site and have to sit inside one card. */
#body-08 .body{ margin-top: var(--s-2); max-width: 62ch; line-height: 1.45 }

/* `height: 0` with `min-height: 100%` is the whole trick, and it is what finally
   made this leaf fit. A 652x869 portrait in a 343px column asks for 457px of row
   whether the words need it or not, so the PHOTOGRAPH was setting the height of
   the leaf and every line trimmed out of the text was handed straight back. At
   height 0 it asks for nothing during intrinsic sizing; min-height 100% then
   fills whatever the prose beside it turned out to be. The text decides, the crop
   follows — which is what the note above always claimed was happening. */
#body-08 .about__photo img{
  width: 100%;
  height: 0;
  min-height: 100%;
  object-fit: cover;
  object-position: 50% 30%;      /* the faces, not the ceiling */
  display: block;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px var(--panel-edge);
}
#body-08 .about__photo figcaption{
  font-family: var(--f-label);
  font-size: var(--t-label);
  color: var(--on-soft);
  margin-top: var(--s-2);
}

/* The three facts: a numbered ledger, same hairline rhythm as the spec index —
   but set ACROSS, not down.

   THE WHOLE LEAF HAS TO FIT THE CARD, and this is the row that decided whether it
   did. Stacked, the three facts were 215px under the longest prose on the site:
   on a 684px window that pushed the third fact and the call to action past the
   foot of the card and the leaf grew its own scrollbar. Side by side they cost
   ONE row of about 95px instead of three, and not a word is dropped. `auto-fit`
   rather than a hard `repeat(3, …)` so they fold back to two and then to one on a
   narrow card without a second breakpoint to maintain. */
.facts{
  list-style: none;
  margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11.5rem), 1fr));
  gap: 0 clamp(0.9rem, 2cqw, 1.75rem);
  border-top: 1px solid var(--on-rule);
}
/* The numeral takes its own line above the name. In the stacked ledger it sat in
   a 2.5rem column beside the words; in a column a third the width, that gutter
   was the difference between a two-line and a four-line description. */
.facts li{
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1px;
  align-content: start;
  padding-block: clamp(0.3rem, 0.7cqw, 0.5rem);
  border-bottom: 1px solid var(--on-rule);
}
.facts__num{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 84;
  font-size: var(--t-label);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--on-soft);
}
.facts__body{ display: grid; gap: 2px; min-width: 0 }
.facts__name{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 88, "wght" 600;
  font-size: var(--t-body);
  color: var(--on-plate);
}
.facts__desc{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 82;
  font-size: var(--t-label);
  color: var(--on-soft);
  line-height: 1.4;
}
#body-08 .about__more{ display: flex; align-items: center; gap: clamp(1rem, 2cqw, 2rem); flex-wrap: wrap }
.about__link{
  font-family: var(--f-label);
  font-variation-settings: "wdth" 84;
  font-size: var(--t-label);
  color: var(--on-plate);
}

/* 620px, not 900px: at 900 the photograph was dropped on any card narrower than
   ~1000px — which is most windows — leaving the whole right half of the leaf
   empty. The photo now survives down to a genuinely narrow card, and only then
   does the leaf stack and give it up. */
@container (max-width: 620px){
  #body-08 .about{
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "lede" "facts" "more";
  }
  #body-08 .about__photo{ display: none }
}

/* PHONE: seven pixels, and they were the bottom of the "Read what customers say"
   line. Stacked at 375px the lede, the facts and the two links come to 634 in a
   627 stage. The photo is already gone at this width, so the row gap is the only
   thing left that can give — and it is the right thing to give: white space
   between three stacked blocks costs less than any of the three.

   HAS TO SIT AFTER the 620px block above, not with the other phone rules further
   up the file. `#body-08 .about` has the same specificity in both places, so
   this is settled purely by source order, and written earlier it lost silently —
   the rule was there, the gap stayed at 6.4px, and the leaf still clipped. */
@container (max-width: 560px){
  #body-08 .about{ row-gap: 2px }
}


/* === reviews.html — every review, listed once =============================
   Not the homepage carousel: carousel.js only claims `.about__reviews .reviews`,
   so this list keeps its own layout. The global `.reviews` is a horizontal
   snap-scroller for the leaf-08 strip; here it becomes a real column set that
   scrolls vertically inside the card with the rest of the page. */

#body-reviews{
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
}
.ledger{ display: grid; gap: clamp(0.6rem, 1.4cqw, 1.15rem); width: 100% }
/* Capped against svh and given room to stay on one line: at 24ch it wrapped to
   two, and the ten cards below it then ran past the foot of the card. */
.ledger__head .statement{ font-size: min(clamp(1.3rem, 2.6cqw, 2rem), 5svh); max-width: 44ch }
.ledger__head .sub{ margin-top: var(--s-2) }
.ledger__foot{ margin: 0 }

.reviews--page{
  grid-auto-flow: row;
  grid-auto-columns: auto;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
  overflow-x: visible;
  scroll-snap-type: none;
  padding-bottom: 0;
  align-items: start;
}
/* Tighter than the leaf-08 strip was: ten cards have to sit inside one card. */
.reviews--page .review{
  scroll-snap-align: none;
  gap: var(--s-2);
  padding: clamp(0.6rem, 1.2cqw, 0.95rem);
}
.reviews--page .review blockquote p{ font-size: clamp(0.875rem, 1cqw, 1rem) }


/* === 06 · Five Texas doors ================================================= */

#body-06{
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-content: safe center;
  align-items: center;
  column-gap: clamp(1.5rem, 4cqw, 4.5rem);
}
#body-06 .map{
  width: clamp(180px, 30cqw, 420px);
  height: auto;
  overflow: visible;
}
#body-06 .map .outline{
  fill: none;
  stroke: var(--on-rule);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
#body-06 .map .pin{ fill: none; stroke: var(--on-plate); stroke-width: 1.5; vector-effect: non-scaling-stroke }
#body-06 .map .pin--hq{ fill: var(--seal-blue); stroke: none }
@container (max-width: 700px){
  #body-06{ grid-template-columns: minmax(0, 1fr) }
  #body-06 .map{ display: none }
}


/* === 09 · Close — YOURS + colophon ======================================== */

/* Two flexible spacers centre the closing stack while the colophon stays in
   flow as the final row. It was absolutely positioned at first, which let the
   giant word and the colophon occupy the same 34px of the card. */
#body-09{
  grid-template-rows: 1fr auto auto auto auto 1fr auto;
  justify-items: center;
  align-content: stretch;
  row-gap: clamp(0.5rem, 1.2cqw, 1.25rem);
}
#body-09 .cut{
  grid-row: 3;
  position: static;
  margin: clamp(0.4rem, 1cqw, 0.9rem) 0 0;
  width: clamp(200px, 26cqw, 400px);
  height: calc(clamp(200px, 26cqw, 400px) * 9 / 16);
  transform: none;
  clip-path: none;
  justify-self: center;
}
#body-09 .word { grid-row: 2 }
#body-09 .body { grid-row: 4 }
#body-09 a.pill{ grid-row: 5 }
/* Larger than the site's other parentheticals: this one is the setup line for
   the closing word, so it reads as a spoken aside rather than a micro-label. */
#body-09 .above  {
  grid-row: 1;
  align-self: end;
  font-size: min(clamp(1rem, 1.6cqw, 1.5rem), 2.6svh);
  color: var(--on-plate);
}
#body-09 .word   { grid-row: 3; position: relative }
/* --t-giant is capped at 26svh for a leaf where the word is nearly the only
   thing on it. This one also carries the aside, the closing line, the call to
   action and the whole colophon — some 250px of other content — so the word
   takes a tighter share of the height here and the leaf stops overrunning the
   card on a short window. It is still the largest thing on the page. */
#body-09 .glyphs { font-size: min(var(--t-giant), 21svh) }
#body-09 .body   { grid-row: 4; text-align: center; max-width: 40ch }
#body-09 a.pill  { grid-row: 5; justify-self: center }

/* The colophon is the page's last quiet line, sitting at the leaf's foot. */
#body-09 .colophon{
  grid-row: 7;
  width: 100%;
  align-self: end;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2.5cqw, 2.5rem);
  padding-top: var(--s-4);
  border-top: 1px solid var(--on-rule);
  font-family: var(--f-label);
  font-size: var(--t-label);
  color: var(--on-soft);
  text-align: left;
}
/* Ten lines in one column made the colophon 225px tall on an 821px card. Two
   columns halved it — and on a wide card three halves it again, which is the
   20px this leaf was over by at 1512x684. `columns: 8rem 3` is a ceiling and a
   floor rather than a count: three while each column can hold 8rem, two when it
   cannot, one on a phone, with no breakpoint to keep in step. */
#body-09 .colophon ul{
  list-style: none; margin: 0; padding: 0;
  columns: 7rem 3;
  column-gap: var(--s-4);
}
#body-09 .colophon li{ break-inside: avoid }
#body-09 .colophon a{ color: var(--on-plate) }
#body-09 .colophon a:hover{ color: var(--seal-blue) }
#body-09 .colophon .copy{ margin-top: var(--s-3) }
#body-09 .colophon .tagline{ display: block; color: var(--on-soft) }

/* Below ~640px the three columns and the giant word cannot share the leaf. */
@container (max-width: 640px){
  #body-09 .colophon{ grid-template-columns: minmax(0, 1fr) }
  #body-09 .colophon [data-col="lines"]{ display: none }
}
