/* ============================================================================
   DS-03 PRECISION MODULAR GRID  ·  Meridian Loop Advisory
   Sheet 3 of 3 — the hero, and every page-level composition.

   FINGERPRINT ROWS THIS SHEET CARRIES
     hero: typographic statement plus one rectilinear image ·
     imagery: architectural desk details, documents without private data,
     precise objects

   THE ANTI-TEMPLATE RULE. No page here is a centred headline over two buttons
   over three equal cards. Section 15 also forbids repeating one equal-width
   three-card pattern across Home, Services, Staff and Reviews, so each of
   those four uses a structurally different composition: Home previews
   services as an indexed priced LIST, Services lays six records out as an
   unequal 12-column grid, Staff pairs a portrait column against a record
   column, and Reviews runs a single 52ch measure with a hanging index.
   ========================================================================= */

/* ================================= hero ================================== */

.hero { padding-block: var(--s8) var(--section); }

@media (min-width: 768px) { .hero { padding-block: var(--s9) var(--section); } }

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s6) var(--gut);
  align-items: start;
}

/* Material catalogue correction: Home is one continuous twelve-column
   numbered sheet, not a reset-to-container sequence of rows. */
@media (min-width: 1100px) {
  .home-page main {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: 1px;
    align-items: start;
    padding-inline: max(2rem, calc((100vw - 1440px) / 2));
    background-image: repeating-linear-gradient(90deg, transparent 0, transparent calc(8.333% - 1px), rgba(68,55,108,.12) calc(8.333% - 1px), rgba(68,55,108,.12) 8.333%);
    box-sizing: border-box;
    overflow: clip;
  }
  .home-page main > * { min-width: 0; }
  .home-page main > .hero { grid-column: 1 / -1; }
  .home-page main > .section { width: auto; max-width: none; margin: 0; }
  /* Each section owns a complete twelve-column working area. Its internal
     modules already carry the deliberately unequal spans; narrowing the
     section itself created a second nested 12-column grid whose 48px gutters
     left only 20–40px text tracks. */
  .home-page main > [aria-labelledby] { grid-column: 1 / -1; }
}

@media (min-width: 768px) {
  .hero__grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .hero__statement { grid-column: span 6; }
  .hero__text { grid-column: span 3; }
  .hero__media { grid-column: span 3; }
}

/* From 1024px the statement takes the whole twelve-column measure and the
   supporting block sits in five columns beside a seven-column image.

   It was six-and-six, with the H1 inside the left column, and that was wrong
   on two counts that only a wide viewport shows. The H1 is clamped to 7rem
   and reaches it at 1600px; "Documented" set at 112px is 630px of ink, the
   six-column text slot is 568px, and a single word cannot be broken by
   `text-wrap: balance` -- so the statement overran its column and touched
   the image frame. And a six-column slot is 550px of image, which makes the
   640px rendition the correct pick at 1x and leaves the 960x720 source
   named in section 4 unused at every desktop width.

   Seven columns is 653px of image, so the 960 source is the one that loads
   and the LCP is the asset the specification names. Note what that measures:
   960 CSS pixels of image beside an H1 cannot exist inside the 1280px
   content maximum -- 960 plus a 48px gutter leaves 176px for the statement
   -- so 960x720 is read here as the dimensions of the delivered asset, not
   of the box. That reading is recorded in the DS-03 handoff. */
@media (min-width: 1024px) {
  .hero__grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .hero__statement { grid-column: span 12; }
  .hero__text { grid-column: span 5; }
  .hero__media { grid-column: span 7; }
}

/* The typographic statement is the first thing painted and owes nothing to
   the image: it is live text at a clamped size with no background, no
   overlay and no measurement dependency, so it is fully legible before any
   media arrives and again at 200% zoom, which section 15 asks for by name. */
.hero__statement { margin-bottom: 0; }

/* On one column the statement still needs air under it; from 768px the grid
   row gap does that job and a margin would double it. */
@media (max-width: 767.98px) { .hero__statement { margin-bottom: var(--s2); } }

.hero__statement em {
  font-style: normal;
  display: block;
  color: var(--high);
}

.hero__facts {
  margin-top: var(--s6);
  border-top: 1px solid var(--ink);
  padding-top: var(--s4);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s3);
}

/* 414px allows two compact metadata columns; below that this stays single. */
@media (min-width: 414px) {
  .hero__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3) var(--s4); }
}

.hero__fact dt {
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.hero__fact dd {
  margin: var(--s1) 0 0;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
}

/* THE LCP ELEMENT OF THIS DESIGN IS THE IMAGE BELOW, at its 960x720 source.
   It is a plain img in a frame, never a CSS background, because Chromium
   scores only real content elements. It is also never allowed to fill the
   viewport exactly: an img whose rect matches the viewport rect is treated
   as a background and dropped from LCP candidacy, so the frame always sits
   inside the gutters and under the 72px header. */
.hero__media { margin: 0; }

.hero__frame {
  position: relative;
  border: 1px solid var(--frame);
  border-left-width: 8px;
  border-radius: var(--radius);
  padding: var(--s2);
  background: var(--surface);
}

.hero__frame img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center;
}

/* 390px is where the hero crop is allowed to widen from 4:5 to 3:4. */
@media (min-width: 390px) {
  .hero__frame img { aspect-ratio: 3 / 4; }
}

/* From the desktop identity onward the image sits at its natural 4:3. */
@media (min-width: 768px) {
  .hero__frame img { aspect-ratio: 4 / 3; }
}

/* the registration marks on the hero frame */
.hero__frame::before,
.hero__frame::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 1px;
  background: var(--high);
}
.hero__frame::before { left: -1px; top: -1px; }
.hero__frame::after { right: -1px; bottom: -1px; }

.hero__caption {
  margin-top: var(--s3);
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  color: var(--ink-soft);
}

/* ========================= the section index ============================= */

/* A page-local numbered index. Its live marker is data-active, written by the
   scroll observer. aria-current is never touched here: that attribute belongs
   to the current PAGE link in the header and moving it would tell a screen
   reader the page had changed. */
.sindex { display: none; }

@media (min-width: 1024px) {
  .sindex { display: block; position: sticky; top: calc(var(--header) + var(--s6)); }
  .sindex__item {
    display: flex;
    align-items: center;
    gap: var(--s3);
    min-height: var(--control);
    text-decoration: none;
    color: var(--ink-soft);
    font-family: var(--mono);
    font-size: 0.8125rem;
    letter-spacing: 0.06em;
    border-left: 1px solid var(--second);
    padding-left: var(--s3);
    transition: color var(--t), border-color var(--t);
  }
  .sindex__item:hover { color: var(--high-ink); border-left-color: var(--high); }
  .sindex__item[data-active="true"] {
    color: var(--ink);
    border-left-color: var(--high);
  }
  /* faq.html hangs the run of question numbers a category holds on the far
     edge of its own row, so the rail maps the column beside it instead of
     listing three bare words. privacy.html carries no counts and simply has
     no element with this class. */
  .sindex__count {
    margin-left: auto;
    padding-left: var(--s3);
    font-variant-numeric: tabular-nums;
  }
}

/* ==================== SV3 modular scope matrix (services) ================ */

/* Six service records in an asymmetric twelve-column matrix rather than a card
   deck: one priority service takes eight columns, two concise ones take four,
   and the remainder alternate six and full width. The matrix borrows the page
   grid's column template -- see the .grid, .svm group in grid.css. */
/* records take their own height rather than stretching to the tallest in the
   row: a bordered module with a third of itself empty reads as a gap, and the
   matrix is meant to be read as modules of unequal weight on a shared grid. */
.svm { row-gap: var(--s5); align-items: start; }

.svc {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s4);
  align-content: start;
}

/* the 96px number rail arrives with the six-column grid */
@media (min-width: 768px) {
  .svc { grid-template-columns: 96px minmax(0, 1fr); gap: var(--s3) var(--gut); }
}

/* a four-column record is about 372px wide at 1280px, so a rail beside it
   would leave roughly a 33ch measure. The two concise records put the rail
   back on top there rather than squeezing the scope text. */
@media (min-width: 1024px) {
  .svc--tight { grid-template-columns: minmax(0, 1fr); }
}

.svc__id {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
}
.svc__id-t {
  display: inline-block;
  padding: 3px 8px;
  border: 1px solid var(--second);
  color: var(--ink-soft);
  transition: background-color var(--t), border-color var(--t), color var(--t);
}

/* the signature move: focus or hover inverts the ID strip and nothing else */
.svc:hover .svc__id-t,
.svc:focus-within .svc__id-t {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--page);
}

.svc__main { min-width: 0; }
.svc__main > * + *,
.svc__half > * + * { margin-top: var(--s3); }

.svc__name { margin: 0; font-size: 1.1875rem; }
.svc__price {
  margin: 0;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 1.0625rem;
  white-space: nowrap;
}
.svc__desc { margin: 0; max-width: var(--measure); }
.svc__fit,
.svc__time,
.svc__exc {
  margin: 0;
  font-size: 0.9375rem;
  color: var(--ink-soft);
  max-width: var(--measure);
}
.svc__lbl {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.svc__lbl--head { display: block; margin: 0; }
.svc__inc { margin: 0; padding-left: var(--s4); }
.svc__inc li { font-size: 0.9375rem; }
.svc__inc li + li { margin-top: var(--s1); }
.svc__act { margin: 0; }

/* the full-width sixth record reads as two halves so that no line has to run
   the whole twelve columns */
@media (min-width: 1024px) {
  .svc__main--wide {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--gut);
    align-items: start;
  }
  .svc__main--wide > * + * { margin-top: 0; }
}

/* the supporting work-detail figure sits in the matrix as a record-width
   module, borderless so it reads as evidence rather than as a seventh service */
.svm__fig { margin: 0; }
.svm__fig img { display: block; width: 100%; height: auto; }
.svm__fig figcaption {
  margin-top: var(--s3);
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: var(--measure);
}

/* ============================== engagement timeline ========================== */

.mod--dead { border-top: 3px solid var(--high); }
.dead__tab { margin: 0 0 var(--s3); }
.dead__tab-t {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--high-ink);
}
.dead { margin: var(--s5) 0 0; padding: 0; list-style: none; }
.dead__row {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  gap: var(--s1) var(--s3);
  padding-block: var(--s3);
  border-top: 1px solid var(--second);
}
.dead__row:last-child { border-bottom: 1px solid var(--second); }
.dead__d {
  font-family: var(--mono);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.dead__w { font-size: 0.9375rem; color: var(--ink-soft); }

/* ============================ comparison band ============================ */

/* The same six compared on four points. Below the six-column grid every cell
   repeats its own label, so the band restacks instead of ever asking for a
   horizontal scroll. */
.cmp { width: 100%; margin-top: var(--s6); border-collapse: collapse; }
.cmp th,
.cmp td { text-align: left; vertical-align: top; }
.cmp thead th {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding: 0 var(--gut) var(--s2) 0;
  border-bottom: 1px solid var(--ink);
}
.cmp tbody tr { transition: background-color var(--t); }
.cmp tbody th,
.cmp tbody td {
  font-size: 0.9375rem;
  font-weight: 400;
  padding: var(--s3) var(--gut) var(--s3) 0;
  border-bottom: 1px solid var(--second);
}
.cmp tbody th { font-family: var(--sans); font-weight: 600; }

/* the matched row lifts when its record is hovered or focused; the link
   between the two is stated in the markup, so this only confirms it */
.cmp tbody tr[data-on="true"] { background: var(--high-fill); color: var(--ink); }

@media (max-width: 767px) {
  .cmp thead {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .cmp tbody tr {
    display: block;
    padding-block: var(--s3);
    border-top: 1px solid var(--second);
  }
  .cmp tbody tr:last-child { border-bottom: 1px solid var(--second); }
  .cmp tbody th,
  .cmp tbody td { display: block; padding: 0; border: 0; }
  .cmp tbody td::before {
    content: attr(data-l);
    display: block;
    margin-top: var(--s3);
    font-family: var(--mono);
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
}

/* the staff page keeps the index number rail */
.rec__no {
  font-family: var(--mono);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}

/* the three-step contact sequence — a numbered run, not a card row */
.steps { counter-reset: step; }
.step {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  gap: var(--s4);
  padding-block: var(--s5);
  border-top: 1px solid var(--second);
}
.step:last-child { border-bottom: 1px solid var(--second); }
.step__no {
  font-family: var(--sans);
  font-weight: 700;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--high);
}

/* ================================ gallery ================================ */

/* Eight plates in four named groups. Each group is its own grid: one column
   at 320, two from 768, and from 1024 a three-up row of four-column modules.
   Two groups hold a single 2:1 panoramic band instead, which spans the whole
   field -- so no group ever leaves part of a row standing empty. */
.galg { margin-bottom: var(--s6); }
.galg__h {
  margin: 0;
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.9375rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.galg__line {
  margin: var(--s2) 0 0;
  font-size: 0.9375rem;
  color: var(--ink-soft);
  max-width: var(--measure);
}

.gal {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s6) var(--gut);
  /* plates take their own height rather than stretching to the tallest in the
     row -- the portrait and landscape sources are deliberately unequal. */
  align-items: start;
}

@media (min-width: 768px) {
  .gal { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .gal__item--pano { grid-column: 1 / -1; }
}

@media (min-width: 1024px) {
  .gal { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .gal__item { grid-column: span 4; }
  .gal__item--pano { grid-column: 1 / -1; }
}

/* the panoramic band is a 2:1 crop of a 3:2 source, taken in CSS rather than
   exported again: the crop is centred, both subjects fill the frame edge to
   edge, and no second derivative of an already-shipped file is created. The
   explicit ratio is what reserves the space, so there is no layout shift. */
.gal__item--pano .gal__frame img {
  aspect-ratio: 2 / 1;
  height: auto;
  object-fit: cover;
  object-position: center;
}

.gal__item figure { margin: 0; }

.gal__frame {
  border: 1px solid var(--frame);
  border-left-width: 8px;
  border-radius: var(--radius);
  padding: var(--s2);
  background: var(--surface);
  /* min-height 0 so the aspect-ratio below is not defeated by the automatic
     minimum size a grid item otherwise takes. */
  min-height: 0;
}

.gal__frame img {
  width: 100%;
  height: auto;
}

.gal__cap {
  margin-top: var(--s3);
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--ink-soft);
  max-width: 46ch;
}
.gal__cap b {
  font-family: var(--mono);
  font-weight: 400;
  color: var(--ink);
  display: block;
}

/* ================================= staff =================================

   S3 Role Architecture Map, specification line 470. The four records are one
   ordered sequence rather than four equal cards: a lead role, two specialist
   roles beside it, and a coordinator closing the run in a full-width strip.

   The <ol> is the real element, not a styling device. Below 768 the
   specification asks for exactly what an ordered list already is -- a
   numbered run with no connectors -- so the markers are shown and the
   connectors are display:none. From 768 the list becomes the grid and the
   markers give way to the index numbers the rest of the unit uses.

   The 6/3/3 arrangement begins at 1024, not at 768. Three columns of a
   twelve-column grid inside a 696px wrap is 150px, which is the "narrow
   underfilled column" line 770 rules out; at 1024 three columns is 196px and
   at 1280 it is 260px. Between 768 and 1023 the lead takes the full row and
   the two specialists sit six and six, which is the same sequence read two
   nodes to the row. */
.map {
  list-style: decimal;
  padding-left: var(--s5);
  margin: 0;
}
.map__node {
  position: relative;
  padding-block: var(--s6);
  border-top: 1px solid var(--second);
}
.map__node:last-child { border-bottom: 1px solid var(--second); }
.map__node::marker {
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

/* Connectors do not exist below 768 -- the list numbers carry the sequence,
   which is what line 470 asks for in as many words. */
.map__link { display: none; }

.map__figure { margin-bottom: var(--s5); }
.map__bio { margin-top: var(--s4); }

/* "best contact reason" (line 470). A ruled aside rather than a second
   paragraph, so a visitor scanning for who to write to can find it without
   reading the biography first. */
.map__reason {
  margin-top: var(--s4);
  padding-left: var(--s4);
  border-left: 2px solid var(--high);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink-soft);
}
.map__reason b {
  display: block;
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: var(--s2);
}

@media (min-width: 768px) {
  .map {
    list-style: none;
    padding-left: 0;
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--s6) var(--gut);
  }
  .map__node { padding-block: var(--s5); }
  .map__node:nth-child(1) { grid-column: 1 / -1; }
  .map__node:nth-child(2) { grid-column: 1 / span 6; }
  .map__node:nth-child(3) { grid-column: 7 / span 6; }
  .map__node:nth-child(4) { grid-column: 1 / -1; }

  /* The lead and the coordinator strip read across; the two specialists
     stack their panel over their record because a six-column node at 768 is
     330px and a side-by-side split of that is two narrow columns. */
  .map__node:nth-child(1),
  .map__node:nth-child(4) {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 5fr);
    gap: var(--s5) var(--gut);
    align-items: start;
  }
  .map__node:nth-child(1) .map__figure,
  .map__node:nth-child(4) .map__figure { margin-bottom: 0; }

  /* Thin connector lines, drawn only in the grid's own gaps. A connector is
     an absolutely positioned child of the node it leaves, pushed out by
     exactly one gap, so it can never cross text: the gap is empty by
     construction and the assertion in stcheck.mjs proves the connector's box
     intersects no node's box at any required width. */
  .map__link {
    display: block;
    position: absolute;
    background: var(--ink-soft);
  }
  /* down into the next row */
  .map__node:nth-child(1) .map__link,
  .map__node:nth-child(3) .map__link {
    left: var(--s6);
    bottom: calc(-1 * var(--s6));
    width: 1px;
    height: var(--s6);
  }
  /* across into the node beside it */
  .map__node:nth-child(2) .map__link {
    top: var(--s7);
    right: calc(-1 * var(--gut));
    width: var(--gut);
    height: 1px;
  }
}

@media (min-width: 1024px) {
  .map__node:nth-child(1) { grid-column: 1 / span 6; }
  .map__node:nth-child(2) { grid-column: 7 / span 3; }
  .map__node:nth-child(3) { grid-column: 10 / span 3; }

  /* The lead is now six columns wide, so its panel and record split evenly.
     A 2fr/3fr split left the lead -- the widest node on the row -- holding the
     narrowest panel on the page (155px at 1024 against a specialist's 194px),
     because a three-column specialist gives its panel the whole node. */
  .map__node:nth-child(1) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* The coordinator strip runs the full twelve. Its panel column is a quarter
     rather than a fifth: at a fifth the drawing stopped 159px above the record
     at 1024 and ~102px above it at 1280 and 1600. A quarter closes that to
     104 / 30 / 32 and still never hands the panel a cell wider than the 320px
     the drawing may fill -- a third would, and would leave 59px of empty cell
     beside it at 1280 instead. */
  .map__node:nth-child(4) { grid-template-columns: minmax(0, 1fr) minmax(0, 3fr); }

  /* the lead now hands across rather than down */
  .map__node:nth-child(1) .map__link {
    left: auto;
    bottom: auto;
    top: var(--s7);
    right: calc(-1 * var(--gut));
    width: var(--gut);
    height: 1px;
  }
}

/* The portraits are authored constructions, not photographs: a stepped
   rectilinear head-and-shoulders drawn entirely on the 8px module, with no
   curve, no diagonal and no face. Each of the four carries a different
   treatment -- reserved, hatched, modular, solid -- so no two people share a
   mark. No identifiable real person appears anywhere on this site.

   The panel is authored at 320x400 and the artwork draws its own 16px inset,
   so there is no padding here: the CSS border IS the panel edge and the SVG
   fills it. */
.portrait {
  border: 1px solid var(--frame);
  border-left-width: 8px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  padding: 0;
  max-width: 320px;
}
.portrait svg { display: block; width: 100%; height: auto; }

.person__role {
  font-family: var(--mono);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: var(--s2);
}
.person__focus {
  margin-top: var(--s4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}
.person__focus a {
  display: inline-flex;
  align-items: center;
  min-height: var(--control);
  padding-inline: var(--s3);
  border: 1px solid var(--second);
  font-family: var(--mono);
  font-size: 0.8125rem;
  text-decoration: none;
  color: var(--ink);
}
.person__focus a:hover { border-color: var(--high); color: var(--high-ink); }

/* ========================= reviews: the evidence matrix ==================

   Two specification passages govern this page and they describe different
   compositions. Line 776 asks for a modular testimony wall -- one lead quote
   with a 96px numeral, four standard quotes, one horizontal closing quote.
   Line 450 asks for an asymmetric 12-column dossier matrix -- a featured
   record spanning 8 columns, smaller records at 4 or 6 in a deliberate
   non-repeating rhythm, numbered margin coordinates, a header strip that
   inverts on hover and focus, and a mobile ordered list with the record
   number held in a narrow left rail. Neither supersedes the other, and the
   silhouette line 776 describes survives intact inside line 450's matrix:
   the same six records, the same three weights, placed on real columns.

   WHERE THE TWO PULL APART, THE ANSWER IS MEASURED, NOT PREFERRED. A
   4-column record is 277px at 1024 and renders a 69-word quote at 24
   characters per line over 16 lines; at 1280 it is 363px and 32 characters
   per line over 12. The narrowest record this page already ships is 324px
   (a pair column at 768). So the rule is: a record may not be narrower than
   the narrowest record the page already ships. 277 < 324 refuses the mix at
   1024; 363 > 324 admits it at 1280. The 4/6 rhythm is therefore gated at
   1280, which is the same shape of resolution staff.html used when it gated
   its 6/3/3 arrangement at 1024 rather than 768.

   The mixed rhythm cannot tile whole rows, and that is deliberate rather
   than sloppy: with a featured record at 8, four smalls drawn from {4,6} fill
   complete 12-column rows only as 4+4+4+4, which is the uniform arrangement
   the direction rules out. Every non-uniform mix leaves margin columns --
   which is exactly what the same sentence asks to have numbered.

   The 96px numeral is where `.numeral` earns its keep -- it was written in
   modules.css and matched nothing, which is a dead selector and a validate.py
   failure. The rule written against it there is that the highlight may be the
   ink only at display size: 6rem measures 3.35:1 on the page ground and
   4.93:1 on the closing panel's ink, and large text needs 3:1. Nothing below
   that size here is allowed the highlight.

   The reviewer marks are drawn in `currentColor`, so the closing record's
   mark inverts with its panel instead of going black on black.             */

.wall { display: grid; margin: 0; padding: 0; list-style: none; }

.rv { border-top: 1px solid var(--second); }

/* Below 768 the matrix is what line 450 asks for on mobile: an ordered
   evidence list with the record number in a narrow left rail. A two-digit
   index number at 13px IBM Plex Mono is 15.6px, so the rail is 1rem and the
   gap var(--s2): 24px in total, which is what it costs the measure at 320
   (264px of content against the 288px the page had before the rail). At
   1.5rem + var(--s3) it cost 36px and took every record below 24 characters
   per line at 320, which the verifier caught. */
.rv__fig {
  display: grid;
  grid-template-columns: 1rem minmax(0, 1fr);
  column-gap: var(--s2);
  row-gap: var(--s4);
  margin: 0;
  padding-block: var(--s7);
  height: 100%;
}

.rv__no { grid-column: 1; grid-row: 1; margin: 0; font-variant-numeric: tabular-nums; white-space: nowrap;
  font-family: var(--mono); font-size: 0.8125rem; letter-spacing: 0.08em; color: var(--ink-soft); }
.rv__head { grid-column: 2; grid-row: 1; }
.rv__quote { grid-column: 2; grid-row: 2; margin: 0; max-width: var(--review); }
.rv__quote p { margin: 0; }
.rv__meta { grid-column: 2; grid-row: 3; margin: 0; }

/* -- the header strip -------------------------------------------------- */
/* Line 450: "Focus and hover invert only the header strip, not the entire
   card." The strip carries the "what mattered" tag and the record's margin
   coordinate; :focus-within is what makes the keyboard behave like the
   pointer, since the only focusable thing in a record is its service link. */
.rv__head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: var(--s2) var(--s4);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--second);
  transition: background-color var(--t), border-color var(--t), color var(--t);
}
.rv__tag { margin: 0; font-family: var(--mono); font-size: 0.8125rem; line-height: 1.45; }
.rv__tag-k { display: block; font-size: 0.6875rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-soft); transition: color var(--t); }
.rv__coord { display: none; font-family: var(--mono); font-size: 0.6875rem;
  letter-spacing: 0.08em; color: var(--ink-soft); white-space: nowrap; transition: color var(--t); }

.rv:hover .rv__head, .rv:focus-within .rv__head { background: var(--ink); border-color: var(--ink); color: var(--page); }
.rv:hover .rv__tag-k, .rv:focus-within .rv__tag-k,
.rv:hover .rv__coord, .rv:focus-within .rv__coord { color: var(--second); }

/* -- attribution ------------------------------------------------------- */
.rv__mark { display: block; width: 72px; }
.rv__mark svg { display: block; width: 100%; height: auto; }
.rv__name { margin: var(--s3) 0 0; font-family: var(--sans); font-weight: 600; font-size: 1rem; }
.rv__facts { margin: var(--s4) 0 0; }
.rv__fact { margin-top: var(--s3); }
.rv__fact:first-child { margin-top: 0; }
.rv__fact dt { font-family: var(--mono); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); }
.rv__fact dd { margin: var(--s1) 0 0; font-family: var(--mono); font-size: 0.8125rem; }
.rv__fact a { color: var(--action); }
.rv__fact a:hover { color: var(--ink); }

/* -- the featured record ----------------------------------------------- */
.rv--lead { border-top: 0; }
.rv--lead .rv__fig { padding-top: 0; }
.rv--lead .rv__quote { max-width: 40ch; font-size: clamp(1.1875rem, 2.2vw, 1.5rem); line-height: 1.4; letter-spacing: -0.01em; }

/* -- the four standard records ----------------------------------------- */
.rv--std .rv__quote { font-size: 1.0625rem; line-height: 1.6; }

/* -- the closing record ------------------------------------------------ */
/* Below 768 the ink panel breaks out to the full viewport and carries the
   wrap's own gutter, so the closing record reads at exactly the measure every
   other record on the page reads at. Inset inside an already padded wrap it
   was 224px against their 288px at 320 -- the narrowest column on the page,
   and the only one whose narrowness was chrome rather than content. */
.rv--close { border-top: 0; margin-top: var(--s7); margin-inline: calc(var(--gut) * -1); }
.rv--close .rv__fig { padding: var(--s7) var(--gut); background: var(--high); color: #ffffff; }
.rv--close .rv__quote { font-size: 1.0625rem; line-height: 1.55; }
.rv--close .rv__fact dt { color: var(--second); }
.rv--close .rv__fact a { color: #ffffff; text-decoration: underline; text-underline-offset: 3px; }
.rv--close .rv__fact a:hover { color: var(--second); }
/* the closing panel is already inverted, so its strip inverts the other way */
.rv--close .rv__head { border-color: var(--ink-soft); }
.rv--close .rv__tag-k, .rv--close .rv__coord { color: var(--second); }
/* The record index is the largest mark on this panel and it is on ink,
   so it takes the same inverted quiet colour as the labels above rather
   than .rv__no's page-ground --ink-soft, which measures 2.36:1 here. */
.rv--close .rv__no { color: var(--second); }
.rv--close:hover .rv__head, .rv--close:focus-within .rv__head { background: var(--page); border-color: var(--page); color: var(--ink); }
.rv--close:hover .rv__tag-k, .rv--close:focus-within .rv__tag-k,
.rv--close:hover .rv__coord, .rv--close:focus-within .rv__coord { color: var(--ink-soft); }

/* -- the matrix -------------------------------------------------------- */
/* Records are placed by their own ids. They are unique, they are already the
   page's anchor targets, and naming them here makes the matrix legible in the
   stylesheet instead of hiding it behind positional classes. */
@media (min-width: 768px) {
  .wall { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gut); row-gap: 0; }
  .rv__fig { grid-template-columns: minmax(0, 1fr); column-gap: var(--gut); }
  .rv__no, .rv__head, .rv__quote, .rv__meta { grid-column: 1; grid-row: auto; }
  .rv__no { font-family: var(--mono); }

  .rv--lead { grid-column: 1 / span 12; }
  #review-02 { grid-column: 1 / span 6; }
  #review-03 { grid-column: 7 / span 6; }
  #review-04 { grid-column: 1 / span 6; }
  #review-05 { grid-column: 7 / span 6; }
  .rv--close { grid-column: 1 / span 12; }

  .rv--lead .rv__fig { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .rv--lead .rv__no { grid-column: 1 / span 6; grid-row: 1; font-size: clamp(3rem, 9vw, 6rem); line-height: 1; }
  .rv--lead .rv__head { grid-column: 1 / span 4; grid-row: 2; }
  .rv--lead .rv__quote { grid-column: 1 / span 4; grid-row: 3; }
  .rv--lead .rv__meta { grid-column: 5 / span 2; grid-row: 2 / span 2; }

  .rv--close { margin-inline: 0; }
  .rv--close .rv__fig { grid-template-columns: repeat(6, minmax(0, 1fr)); padding: var(--s7) var(--s6); }
  .rv--close .rv__quote { font-size: 1.125rem; }
  .rv--close .rv__no { grid-column: 1 / span 1; grid-row: 1 / span 2; font-size: clamp(3rem, 9vw, 6rem); line-height: 1; }
  .rv--close .rv__head { grid-column: 2 / span 3; grid-row: 1; }
  .rv--close .rv__quote { grid-column: 2 / span 3; grid-row: 2; }
  .rv--close .rv__meta { grid-column: 5 / span 2; grid-row: 1 / span 2; }
}

@media (min-width: 1024px) {
  /* the featured record takes 8 of 12; C09-C12 become the numbered margin */
  .rv--lead { grid-column: 1 / span 8; }

  /* At 8 columns the featured record is 603px, and its quote is capped at
     40ch regardless, so stacking the attribution beneath costs the quote
     nothing and gives it the whole span instead of a squeezed 4/6 share. */
  .rv--lead .rv__fig { grid-template-columns: minmax(0, 1fr); }
  .rv--lead .rv__no, .rv--lead .rv__head,
  .rv--lead .rv__quote, .rv--lead .rv__meta { grid-column: 1; grid-row: auto; }
  .rv--lead .rv__meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s5) var(--s7); }
  .rv--lead .rv__name { margin: 0; }
  .rv--lead .rv__facts { margin: 0; display: flex; flex-wrap: wrap; gap: var(--s5) var(--s7); }
  .rv--lead .rv__fact { margin-top: 0; }

  .rv--close .rv__fig { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .rv--close .rv__no { grid-column: 1 / span 2; grid-row: 1 / span 2; }
  .rv--close .rv__head { grid-column: 3 / span 6; grid-row: 1; }
  .rv--close .rv__quote { grid-column: 3 / span 6; grid-row: 2; }
  .rv--close .rv__meta { grid-column: 10 / span 3; grid-row: 1 / span 2; }
}

@media (min-width: 1280px) {
  /* The non-repeating rhythm, admitted only here because only here is a
     4-column record (363px) wider than the narrowest record this page already
     ships (324px at 768). Row one runs 4 | 6 with C05-C06 in the margin; row
     two mirrors it, 6 | 4 with C07-C08 in the margin. The two shortest quotes
     take the two narrow columns. */
  #review-02 { grid-column: 1 / span 4; }
  #review-03 { grid-column: 7 / span 6; }
  #review-04 { grid-column: 1 / span 6; }
  #review-05 { grid-column: 9 / span 4; }

  /* the margin coordinates are only true where the asymmetric matrix exists */
  .rv__coord { display: inline; }
}

/* ================================== faq ================================== */

/* Specification line 779, verbatim: "Add a left category index and right
   native-details column. Closed questions use 56px rows; selected/open state
   uses a 4px blue rule and pale-blue surface. Keep answers within 68ch."

   The index is the .sindex component the privacy page already uses, unchanged
   and with no new script: it is a page-local numbered index whose live marker
   is written by the same scroll observer. Reusing it is not laziness, it is
   the thing the rule asks for, and the alternative -- a second index component
   doing the same job -- is how a design system stops being one. */

/* The eyebrow titles the rail, so it cannot outlive it. .sindex hides itself
   below 1024 and the label was being left behind, printing "Categories" over
   nothing at every handset width -- a defect no gate reads, because the label
   is a correct element in a correct grid cell that simply has nothing left to
   name. privacy.html's own .d3 is NOT this case: it carries the effective
   date, which is content and stays at every width. So this is hidden here, on
   the page, and not pushed down into the shared component. */
.faq__rail { display: none; }
@media (min-width: 1024px) { .faq__rail { display: block; } }

.faq__group { margin-bottom: var(--s8); }
.faq__group:last-child { margin-bottom: 0; }
.faq__group > h2 { margin-bottom: var(--s2); }
.faq__group > .meta { margin: 0 0 var(--s5); max-width: var(--measure); }

.qa { border-top: 1px solid var(--second); }
.qa:last-of-type { border-bottom: 1px solid var(--second); }

/* 56px closed rows. --control is 44px and is the TAP-TARGET floor, a different
   quantity that happens to be a length, so this is written as the number the
   direction gives rather than dressed up as a token it is not. The padding is
   what makes a wrapped two-line question taller than 56px; the min-height is
   what makes a short one exactly 56px. */
.qa > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  gap: var(--s4);
  align-items: center;
  min-height: 56px;
  padding: var(--s3) var(--s4);
  font-family: var(--sans);
  font-weight: 600;
  font-size: 1.0625rem;
  line-height: 1.35;
  transition: background-color var(--t);
}
.qa > summary::-webkit-details-marker { display: none; }
.qa > summary::marker { content: ""; }
.qa > summary:hover { color: var(--high-ink); }
.qa__no {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.8125rem;
  color: var(--ink-soft);
  flex: 0 0 auto;
}

/* The open state. The 4px rule is an inset shadow rather than a border so the
   question does not shift sideways by 4px at the moment it is opened -- the
   row the visitor just clicked has to stay where their eye left it. Both the
   summary and the answer take the surface, because the open THING is the
   question and its answer together, not the answer alone. */
.qa[open] {
  background: var(--pale);
  box-shadow: inset 4px 0 0 0 var(--action);
}
.qa[open] > summary { color: var(--action); }
.qa[open] .qa__no { color: var(--action); }

.qa__a { padding: 0 var(--s4) var(--s5) calc(var(--s4) + var(--s4) + 2.4rem); }
.qa__a p { max-width: var(--measure); margin: 0; }

/* The escape hatch after the fourth answer. */
.faq__escape {
  border-left: 3px solid var(--high);
  padding: var(--s4) var(--s5);
  margin-block: var(--s6);
  background: var(--surface);
}

/* ================================ contact ================================ */

/* Specification line 782: "Use a 5/7 desktop split with a 'what happens next'
   process rail beside the form." Five and seven are the whole twelve, so the
   two columns sit edge to edge with nothing but the gutter between them. What
   this replaced ran the form at span 7 and the aside at 9 / span 4 -- a 7/4
   split with a dead column between them and, because the aside held four short
   paragraphs against a nine-field form, around 500px of empty column under it
   at 1280.

   Below 1024 both columns stack, which is the F3 family contract in its own
   words: "At tablet and mobile widths, preserve DOM order and stack to one
   column." The rail is therefore read before the form at 768 and below, which
   is also the order specification line 158 fixes -- address and hours block,
   then form. */
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s7) var(--gut);
}

@media (min-width: 768px) {
  .contact__grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .contact__rail { grid-column: span 6; }
  .contact__form { grid-column: span 6; }
}

@media (min-width: 1024px) {
  .contact__grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .contact__rail { grid-column: span 5; }
  .contact__form { grid-column: 6 / span 7; }
}

.contact__rail > * + * { margin-top: var(--s6); }

/* --- the "what happens next" process rail --- */

/* The vertical counterpart of .pline on about.html, and deliberately not
   .pline itself: that component is a horizontal LINE of four stations and goes
   four-across at 1024, which inside a five-column rail would be four 65px
   cells with a two-word heading in each. The visual language is the same one
   turned through ninety degrees -- a single unbroken 1px ink rule, a 28px
   --high tick hung on it at each station, alternating white and --alt cells.

   The numerals are --high-ink for the reason already recorded on .pstep__no:
   --high measures 2.92:1 on --alt, under the 3:1 large-text floor. */
.nextrail {
  margin: var(--s5) 0 0;
  padding: 0;
  list-style: none;
  border-left: 1px solid var(--ink);
}
.nextrail__step {
  position: relative;
  padding: var(--s4) var(--s5);
  background: var(--surface);
}
.nextrail__step:nth-child(even) { background: var(--alt); }
.nextrail__step + .nextrail__step { border-top: 1px solid var(--second); }
.nextrail__step::before {
  content: "";
  position: absolute;
  top: 0;
  left: -1px;
  width: 3px;
  height: 28px;
  background: var(--high);
}
.nextrail__no {
  margin: 0;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--high-ink);
}
.nextrail__step h3 { font-size: 1rem; margin-top: var(--s2); }
.nextrail__step p { margin: var(--s2) 0 0; font-size: 0.9375rem; color: var(--ink-soft); }

/* --- the compact document icon panel (line 782) --- */

/* Compact is the whole instruction, so a cell is a 24px linework glyph and a
   two-word label and nothing else. What a visitor actually needs to know about
   documents -- that none of them are attached here -- is the note under the
   list, where it can be a sentence. Two up from 414, one up below it: at 320
   a two-column cell would be 96px and the labels would wrap to three lines. */
.docpanel__list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s3);
  margin: var(--s5) 0 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 414px) {
  .docpanel__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.docpanel__item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--second);
  font-family: var(--mono);
  font-size: 0.875rem;
  line-height: 1.35;
}
.docpanel__icon {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  color: var(--action);
}

/* --- the enquiry form matrix (book-to-contact family F3) --- */

/* F3: "separate modules for contact details, service choice,
   profession-specific qualifiers and the open message. Each module has a
   number, one-sentence purpose and precise field limits." The numbers are
   05.1 to 05.4 because the panel holding them is the page's fifth module and
   index.html already numbers sub-modules that way (01.1, 02.1, 02.2, 04.1).

   A fieldset defaults to min-inline-size: min-content, which in a grid cell
   lets the widest unbreakable child set the column width; min-width: 0 is what
   keeps the form inside its seven columns. */
.fmod {
  border: 0;
  padding: 0;
  margin: 0 0 var(--s7);
  min-width: 0;
}
.fmod:last-of-type { margin-bottom: 0; }
.fmod__lg {
  display: block;
  padding: 0;
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}
.fmod__lg b { font-family: var(--mono); font-weight: 400; color: var(--high-ink); }
.fmod__why {
  margin: var(--s2) 0 var(--s5);
  font-size: 0.9375rem;
  color: var(--ink-soft);
  max-width: var(--measure);
}

/* The form's own twelve-column matrix, live only at 1024 and above because
   the family contract stacks tablet as well as mobile. F3 asks required fields
   to span at least six columns and permits three-column modules for optional
   context; the optional fields here take six as well, because the same
   paragraph ends "Inputs must never be squeezed into unreadable grid cells"
   and a three-column cell of this form is 150px at 1280. */
.fgrid { display: grid; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 1024px) {
  .fgrid { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gut); }
}

/* Specification line 782 asks for 50px fields. --control is 44px and is the
   TAP-TARGET FLOOR -- a different quantity that merely happens to be a length
   -- so 50 is written as a literal rather than dressed up as a token it is
   not, exactly as the FAQ's 56px row was. Scoped to this form: the subscribe
   band's input carries its own sizing and is not what line 782 is about. */
.cform .input,
.cform .select { min-height: 50px; }

/* --- the permanently reserved response area (line 782) --- */

/* Reserved means the region is in the page at full height from first paint, so
   that announcing a result moves nothing under it -- not the alternative
   action, not the footer. The idle sentence is deliberately written to be the
   LONGEST of the five things this region can ever say, so its natural height is
   already the worst case and no min-height floor is needed to hold it. That is
   asserted rather than asserted-at: contactcheck.mjs drives all five states at
   all nine required widths and requires the box height to be identical in every
   one of them.

   overflow-wrap is not cosmetic here: the failure message carries a 33-
   character email address, and at 320 this box is 23 mono characters wide. */
.respond {
  margin-top: var(--s6);
  border: 1px solid var(--second);
  padding: var(--s4);
}
.respond__label {
  margin: 0 0 var(--s3);
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}
/* .form__status is shared with the subscribe band on all ten pages, where it
   is empty until something happens and where a 3px rule appearing with the
   text is exactly right. Here it is the opposite: this region is reserved, so
   nothing about it may change size when the state changes. The rule is
   therefore present in EVERY state and only its colour moves -- --second while
   idle or pending, --action on success, --high on failure. Reserving the rule
   is what reserves the text width, and the text width is what fixes the number
   of lines. */
.respond .form__status {
  margin-top: 0;
  border-left: 3px solid var(--second);
  padding-left: var(--s4);
  overflow-wrap: anywhere;
}
.respond .form__status[data-state="ok"] { border-left-color: var(--action); }
.respond .form__status[data-state="error"] { border-left-color: var(--high); }

/* One grid cell holding two paragraphs: a hidden sizer permanently carrying the
   longest sentence this region can ever show, and the live status laid over it.
   The cell is therefore as tall as the worst case AT THE CURRENT WIDTH,
   computed by the browser from the real string in the real font.

   The alternative was a min-height per breakpoint band, and it is worse in both
   directions: the worst case at 320 is 376px and at 414 is 256px, so one number
   for the whole band either fails the narrow width or leaves 120px of empty box
   at the wide one -- and every number would go stale the first time the copy is
   edited. Here nothing can drift, because the reservation IS the copy.

   The sizer carries .form__status as well, so it inherits the same font, the
   same 3px rule and the same padding without any of it being restated; a sizer
   whose box differs from the thing it is sizing reserves the wrong height. */
.respond__area { display: grid; }
.respond__area > * { grid-area: 1 / 1; }
.respond__sizer { visibility: hidden; }

/* The summary strip F3 requires above Submit. It repeats the chosen service
   and nothing else, it is written only after the visitor has chosen one, and
   it carries the hidden attribute in the markup so that without script it is
   absent rather than empty. --pale is the surface the FAQ's open question
   already uses: the same --action at 12%, for the same reason -- a state
   worth sustaining without a second accent colour. */
.summary {
  margin: 0 0 var(--s5);
  border-left: 3px solid var(--action);
  padding: var(--s2) var(--s4);
  background: var(--pale);
  font-family: var(--mono);
  font-size: 0.9375rem;
  line-height: 1.5;
}

.hours dt {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.9375rem;
}
.hours dd {
  margin: 0 0 var(--s3);
  font-family: var(--mono);
  font-size: 0.9375rem;
  color: var(--ink-soft);
}

/* ============================ privacy and 404 ============================ */

/* Both pages are built from the same modules as everything else, which is
   what section 15 means by requiring them to visibly belong to the design. */

/* Specification line 785: "Use a formal-document treatment: document ID,
   revision date placeholder from the launch checklist, numbered clauses and a
   240px sticky contents rail. Keep it quiet and readable."

   240 is written as a literal for the same reason contact.html's 50px field
   and faq.html's 56px row are: it is a design-specified width rather than one
   of the spacing tokens, and dressing it up as a token it is not would only
   hide it. What it replaces was a .d3 column, measured at 196px at 1024 and
   260px at 1280 -- so never 240 at any width, and at 1024 narrow enough that
   the longest contents entry wrapped. */
.privacy__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s7) var(--gut);
}
@media (min-width: 1024px) {
  .privacy__grid { grid-template-columns: 240px minmax(0, 1fr); }
}

/* The document head. A notice carrying a reference, an effective date and a
   revision date is a controlled document, and this is how one is stamped:
   quiet mono rows above the contents, not a banner. It is visible at every
   width -- the reference and the two dates are the substance of line 785, so
   they cannot be desktop-only; the contents index under it stays desktop-only
   exactly as it already was. */
.docid {
  margin: var(--s5) 0 0;
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--second);
}
.docid__row {
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) 0;
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.4;
}
.docid__row + .docid__row { border-top: 1px solid var(--second); }
.docid dt { color: var(--ink-soft); letter-spacing: 0.06em; }
.docid dd { margin: 0; text-align: right; color: var(--ink); }

/* Numbered clauses.

   The rule above each clause is also the repair of a measured defect. .legal
   h2 carried margin-top: var(--s7), but every clause heading is the first h2
   inside its own <section>, so .legal h2:first-of-type reset every one of
   them to 0. Measured, the gap between two clauses was 0px and a clause
   boundary read exactly like a paragraph boundary -- in a document whose
   whole structure is its clauses.

   From 1280 the numeral hangs in a 96px rail, the same 96px the services rows
   and the reviews lead quote use, so at the widest band the numbers sit where
   a printed notice puts them. Below 1280 it sits above the heading instead,
   because 96px of rail plus the 68ch measure does not fit the clause column
   at 1024 and squeezing the measure to buy a rail is the wrong trade on the
   one page that is nothing but reading. */
.clause { border-top: 1px solid var(--second); margin-top: var(--s6); padding-top: var(--s5); }
.clause:first-of-type { border-top: 0; margin-top: 0; padding-top: 0; }
.clause__no {
  margin: 0 0 var(--s2);
  font-family: var(--mono);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  color: var(--high-ink);
}
.clause h2 { font-size: clamp(1.25rem, 2vw, 1.75rem); margin: 0; }
.clause p { margin: var(--s4) 0 0; }

@media (min-width: 1280px) {
  .clause { display: grid; grid-template-columns: 96px minmax(0, 1fr); column-gap: var(--s5); }
  .clause__no { grid-column: 1; grid-row: 1; }
  .clause > :not(.clause__no) { grid-column: 2; }
}

.legal p, .legal li { max-width: var(--measure); }
.legal ul { margin: var(--s3) 0 0; }
.legal li {
  padding-left: var(--s5);
  position: relative;
  margin-bottom: var(--s2);
}
.legal li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.7em;
  width: 12px;
  height: 1px;
  background: var(--high);
}

/* Specification line 788: "Use a reconciliation-error sheet: large 404, one
   short explanation and Home/Services/Contact actions inside a bordered 720px
   module. Do not mimic a software error console."

   What that replaced was a bare twelve-column split: a .d7 column carrying the
   numeral, the heading and the actions with no border on any side at any
   width, beside a .d4 module holding a note that only repeated the footer.
   Measured, the left column ran 288 / 324 / 335 / 350 / 366 / 696 / 521 / 671
   / 671 across the nine required widths -- so the one width the specification
   names was the one width it never had -- and the module beside it stretched
   to the full row height at every desktop width, 727px of border around three
   short lines. */
.notfound { padding-block: var(--s8) var(--s9); }
@media (min-width: 768px) { .notfound { padding-block: var(--s9) var(--s10); } }

/* The sheet itself. 720 is written as a literal on the same standing as the
   privacy rail's 240 and the contact field's 50: it is a design-specified
   width, not one of the spacing tokens, and calling it a token it is not
   would hide it rather than explain it. It is a max-width because 720 is
   wider than six of the nine required viewports; centred, because a
   single-sheet document has nothing to sit beside. Every span class was
   removed with the grid -- they are global at their breakpoints and a sheet
   is not a twelve-column row. */
.sheet {
  max-width: 720px;
  margin-inline: auto;
}

.notfound__motif {
  font-family: var(--sans);
  font-weight: 700;
  font-size: clamp(4rem, 18vw, 9rem);
  line-height: 0.9;
  letter-spacing: -0.05em;
  color: var(--second);
}

/* This carried the last inline style left on the website,
   style="margin:24px 0", which is exactly var(--s5). A one-off declaration
   that happens to equal a token is the hardest kind to keep in step, because
   nothing connects the two. */
.notfound__reg { margin-block: var(--s5); }

/* The stamp that makes this a reconciliation sheet rather than a bordered
   404: the same .docid component the privacy notice is stamped with, so the
   two document pages speak one language. Below 768 it stays as the stacked
   label/value rows that component draws. From 768 the sheet is wide enough
   that space-between would strand a three-word label against a two-word value
   across most of 672px, so the rows become three cells of one strip -- which
   is where a printed sheet puts its reference line anyway. */
@media (min-width: 768px) {
  .sheet .docid { display: flex; }
  .sheet .docid__row {
    flex: 1;
    display: block;
    padding-inline: var(--s4);
  }
  .sheet .docid__row:first-child { padding-left: 0; }
  .sheet .docid__row + .docid__row {
    border-top: 0;
    border-left: 1px solid var(--second);
  }
  .sheet .docid dd { text-align: left; margin-top: var(--s1); }
}

.notfound__links {
  margin-top: var(--s7);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
}

/* ============================== place image ============================== */

.place {
  border: 1px solid var(--frame);
  border-left-width: 8px;
  border-radius: var(--radius);
  padding: var(--s2);
  background: var(--surface);
  /* A grid item stretches to the row height by default, and this figure has a
     white ground, so beside a taller prose column it rendered as the image
     followed by 200px of empty white inside its own frame -- the same
     underdeveloped-area defect the gallery panorama had, and equally invisible
     to every mechanical check. A figure hugs its content. */
  align-self: start;
}
.place img { width: 100%; height: auto; }
.place figcaption {
  margin-top: var(--s3);
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--ink-soft);
}

/* The 5:4 the about page direction asks for is taken in CSS rather than by
   exporting a fourth derivative of an already-shipped negative. about-desk is
   1400x933 and centre-weighted, so a centred 5:4 crop is 1166x933 and loses
   nothing at either side; the rendered box measures 447px at 1280, so the 700px
   variant is still ample. No new file means no new ASSET_SOURCES.md record and
   no second copy of the same image in the tree. The explicit aspect-ratio is
   what reserves the space, so the crop costs no layout shift. This is the same
   decision, for the same reason, as the two gallery panoramas. */
.place--54 img {
  aspect-ratio: 5 / 4;
  height: auto;
  object-fit: cover;
  object-position: center;
}

/* ======================= the about page: three shapes ==================== */

/* Specification line 773, verbatim: "Use the place image at 5:4, a visible
   operating-principles matrix and a four-step process line. Alternate white
   and #E2E8F0 modules to create structure without losing restraint."
   Those are three distinct silhouettes, and none of them is the rule-separated
   stack the page used to carry for both the principles and the steps. The old
   .principle block is gone rather than left behind: the moment about.html
   stopped using it, it became a dead selector and validate.py would have
   failed the sheet on it. */

/* --- the operating-principles matrix --- */

/* One bordered field divided into cells, not three free-standing cards: the
   outer border is on the container and every division is a single shared
   edge, so the three principles read as one instrument with three readings.
   It stays one column until 1024 because three columns of a 678px tablet
   field leave about 160px of text each, which is below anything readable. */
.pmx {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border: 1px solid var(--second);
}
.pcell {
  padding: var(--s5);
  background: var(--surface);
  border-top: 1px solid var(--second);
}
.pcell:first-child { border-top: 0; }
.pcell:nth-child(even) { background: var(--alt); }
.pcell__no {
  margin: 0 0 var(--s3);
  font-family: var(--mono);
  font-size: 0.8125rem;
  letter-spacing: 0.14em;
  color: var(--high-ink);
}
.pcell h3 { font-size: 1.25rem; }
.pcell p { margin: var(--s3) 0 0; font-size: 0.9375rem; color: var(--ink-soft); }

@media (min-width: 1024px) {
  .pmx { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .pcell { border-top: 0; border-left: 1px solid var(--second); }
  .pcell:first-child { border-left: 0; }
}

/* --- the four-step process line --- */

/* A LINE, so the cells sit edge to edge with no column gap and the single
   1px ink rule runs unbroken across all four of them. Each cell carries a
   28x3px highlight tick hung on that rule at its own left edge, which is
   what makes four cells read as four stations on one line rather than as a
   row of boxes.

   The numerals are --high-ink and not --high. The even cells sit on --alt,
   where --high measures 2.92:1 -- under the 3:1 large-text floor, never mind
   4.5:1 -- so the .step__no pattern used elsewhere on the site (1.5rem in
   --high on the page ground) cannot be reused here. --high-ink measures
   5.27:1 on --alt and is the same hue. The tick keeps --high because it is
   decorative: it carries no information the numeral beside it does not. */
.pline {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border-top: 1px solid var(--ink);
}
.pstep {
  position: relative;
  padding: var(--s5);
  background: var(--surface);
}
.pstep:nth-child(even) { background: var(--alt); }
.pstep::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 28px;
  height: 3px;
  background: var(--high);
}
.pstep__no {
  margin: 0 0 var(--s3);
  font-family: var(--sans);
  font-weight: 700;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--high-ink);
}
.pstep h3 { font-size: 1.125rem; }
.pstep p { margin: var(--s2) 0 0; font-size: 0.9375rem; color: var(--ink-soft); }

@media (min-width: 768px) {
  .pline { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pstep:nth-child(even) { border-left: 1px solid var(--second); }
  .pstep:nth-child(n + 3) { border-top: 1px solid var(--second); }
}

@media (min-width: 1024px) {
  .pline { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .pstep:nth-child(n + 2) { border-left: 1px solid var(--second); }
  .pstep:nth-child(n + 3) { border-top: 0; }
}

.limitation {
  border: 1px solid var(--ink);
  border-left-width: 3px;
  border-left-color: var(--high);
  padding: var(--s5);
  margin-top: var(--s7);
}

/* ===================== the priced index on the home page ================= */

/* The home page previews services as a numbered priced index rather than as
   three equal cards. It is one of the four different silhouettes the site
   uses for a repeating list, and it is deliberately not the shape the
   Services page uses for the same data. */
.plist { list-style: none; margin: 0; padding: 0; }

.plist li {
  display: grid;
  /* minmax(0, 1fr), not 1fr: an fr track keeps an automatic min-content
     floor, so a long service word pushed this row 6px past the 320px viewport. */
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--s3);
  min-height: var(--control);
  padding-block: var(--s3);
  border-top: 1px solid var(--second);
}
.plist li:first-child { border-top: 0; padding-top: 0; }

.plist__no {
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.35;
  color: var(--ink-soft);
}

.plist__name {
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--second);
}
.plist__name:hover { border-bottom-color: var(--high); color: var(--high-ink); }

.plist__price {
  font-family: var(--mono);
  font-size: 0.9375rem;
  color: var(--ink);
  white-space: nowrap;
}

/* ========================== home: the process act ========================

   Spec line 761 asks the home page for three things the Approach section did
   not have: a 720x480 document photograph beside the process, a three-step
   flow diagram on 48px numbered nodes, and a four-column proof band.

   The node is a 48px SQUARE, never a circle -- every corner on this design is
   rectilinear, and a round node would be the one exception on ten pages. The
   connector is a 1px hairline of the same `--second` that draws every module
   border, so the sequence is held together by the page's own rule language.
   The LAST node inverts to solid ink, which is the same device the reviews
   wall uses to close: a sequence that simply stops reads as truncated, one
   that resolves reads as finished.

   The proof band is a <dl>, because "scope / inputs / handoff / next step" is
   four keys and four values and nothing else. Each cell is a `dl > div`
   wrapper, which is the only grouping the element permits -- the record
   number therefore lives INSIDE its <dt> as a <span>, never as a sibling <p>,
   which `dl > div` forbids. At desktop the four cells are divided by hairlines
   rather than by whitespace, so the band reads as one instrument with four
   scales instead of four loose paragraphs.                                 */

.intro > * + * { margin-top: var(--s5); }

.doc { margin: 0; }

/* The photograph is shorter than the flow module beside it, so the row leaves
   roughly 390px under the caption. Spec line 761 allows a gap above 112px
   only where a grid line visibly explains it, so the line is drawn: a
   hairline down the middle of the gutter, running the full height of the row
   because the figure is a stretched grid item. It sits in the gutter rather
   than on the figure's own edge, so the image box -- and therefore every
   `sizes` clause written against it -- is unchanged. */
@media (min-width: 768px) {
  .doc { position: relative; }
  .doc::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(var(--gut) / -2);
    width: 1px;
    background: var(--second);
  }
}

/* -- the flow diagram --------------------------------------------------- */

.flow { list-style: none; margin: 0; padding: 0; }

.flow__step {
  position: relative;
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  column-gap: var(--s4);
  padding-bottom: var(--s6);
}
.flow__step:last-child { padding-bottom: 0; }

.flow__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 23.5px;
  top: 48px;
  bottom: 0;
  width: 1px;
  background: var(--second);
}

.flow__node {
  margin: 0;
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  border: 1px solid var(--ink);
  background: var(--page);
  font-family: var(--mono);
  font-size: 0.9375rem;
  color: var(--ink);
}
.flow__step:last-child .flow__node { background: var(--action); color: #ffffff; }

.flow__text { min-width: 0; }

.flow__name {
  margin: 0;
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.3;
}

.flow__note {
  margin: var(--s2) 0 0;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ink-soft);
}

.flow__when {
  margin: var(--s3) 0 0;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* -- the proof band ----------------------------------------------------- */

.proof__band {
  margin: 0;
  display: grid;
  gap: var(--s6) var(--gut);
  padding-top: var(--s5);
  border-top: 1px solid var(--ink);
}
.proof__cell { min-width: 0; }

.proof__k {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
}

.proof__no {
  font-family: var(--mono);
  font-size: 0.8125rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  color: var(--high-ink);
}

.proof__v {
  margin: var(--s3) 0 0;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ink-soft);
  max-width: 34ch;
}

@media (min-width: 768px) {
  .proof__band { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .proof__band { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; }
  .proof__cell { padding-inline: var(--s5); border-left: 1px solid var(--second); }
  .proof__cell:first-child { padding-left: 0; border-left: 0; }
  .proof__cell:last-child { padding-right: 0; }
}

/* -- the three-record home preview of the wall -------------------------- */

/* These records are <figure class="rv rv--std"> with no .rv__fig wrapper, so
   they get none of the record box the evidence matrix moved onto that
   wrapper. It is restored here: the same grid, the same var(--s4) internal
   gap and the same var(--s7) block padding the records carried before the
   matrix was built. The column and row assignments the matrix makes are
   reset to auto flow, because this band has one column and no header strip. */
.wall--home .rv {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s4) var(--gut);
  padding-block: var(--s7);
}
.wall--home .rv__no,
.wall--home .rv__quote,
.wall--home .rv__meta { grid-column: 1; grid-row: auto; }

@media (min-width: 768px) {
  .wall--home { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s7); }
  /* Three excerpts of different lengths left the marks, names and fact lists
     on three different baselines, which reads as three unrelated blocks
     rather than one set of records. The quote takes the slack instead, so
     every record's caption starts on the same line. */
  .wall--home .rv { grid-template-rows: auto 1fr auto; }
}
@media (min-width: 768px) and (max-width: 1023px) {
  .wall--home .rv:last-child { grid-column: 1 / -1; }
}
@media (min-width: 1024px) {
  .wall--home { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--gut); }
}

/* ===== Recommendation #3 (Business / AI consulting) media and panels =====
   Colour-only: the media filter on every photograph, and the review and staff
   panel grounds on the sections that carry them. Nothing here moves a box. */
.hero__frame img, .gal__frame img, .place img, .svm__fig img, .doc img { filter: var(--media-filter); }
.svm__fig img, .doc img { border: 1px solid var(--frame); border-left-width: 8px; border-radius: var(--radius); }
.section:has(.wall), .section:has(.map), .section:has(.portrait) { background-color: var(--panel-review); }
.section:has(.map), .section:has(.portrait) { background-color: var(--panel-staff); }

/* aajwebz-mailto-nowrap — an address is one token, never split across lines */
a[href^="mailto:"] {
  overflow-wrap: normal;
  word-break: normal;
  white-space: nowrap;
  display: inline-block;
  max-width: 100%;
}
@media (max-width: 380px) {
  a[href^="mailto:"] { font-size: 0.88em; }
}
