/* ==========================================================================
   02 THE RECORD — /work/'s spine, and why that page is not a gallery. Five
   jobs, each a frame PAIR beside the record, resolving into what stayed: the
   defect we did NOT remove. Offset alternating — the frames take the start
   edge on odd rows and the end edge on even ones, crossing the container on
   both sides. No hover on a row: a record is an instrument reading, not a
   link, the same decision .refusal__cell and .calendar__row took. Nothing
   invented — tick construction from .ledger, accent grammar from .refusal (a
   numeral and a border, never a word), the site's chamfer, .job--expanded's
   two-frame measurements. 10 images.
   ========================================================================== */

.record { background: var(--surface-sunken); overflow: hidden; }

.record__list { display: grid; gap: var(--space-stack); background: var(--tick-rule); background-position: top; }

.record__row {
  display: grid;
  gap: var(--space-md);
  padding-block: var(--ledger-pad-block);
  background: var(--tick-rule-fine);
  background-position: bottom;
}

.record__idx {
  font-family: var(--font-mono);
  font-size: var(--text-index);
  font-weight: var(--weight-bold);
  line-height: var(--leading-flat);
  letter-spacing: var(--tracking-mono-tight);
  color: var(--text-accent);
}

/* end-aligned: the operation frame is the shorter of the two, and hung from
   the top it left a dead quadrant beside it. On a common bottom line the pair
   reads as two specimens on one shelf. */
.record__frames { display: grid; align-items: end; gap: var(--grid-gap-tight); }

.record__frame {
  aspect-ratio: var(--ratio-tall);
  border-radius: var(--radius-media);
  clip-path: var(--chamfer-clip);
}
.record__frame::after { background: var(--chamfer-edge), var(--overlay-media); }
/* the operation frame is the smaller of the pair: stretched in a two-track
   row it takes the pair's height, stacked it states its own ratio */
.record__frame--sub { --chamfer: var(--chamfer-sm); aspect-ratio: var(--ratio-card); }

.record__body { display: flex; flex-direction: column; gap: var(--space-xs); }

/* identical construction to .job__paint */
.record__paint { font-family: var(--font-mono); font-size: var(--text-micro); line-height: var(--leading-micro); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-secondary); }
.record__title { font-size: var(--text-h3); color: var(--text-primary); }
/* one step above a rail card: these are the page's records, not a scroller */
.record__desc { max-width: var(--width-measure); font-size: var(--text-lead); line-height: var(--leading-body); color: var(--text-muted); }

.record__spec { display: grid; gap: var(--space-3xs); }
.record__spec-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block: var(--space-2xs);
  background: var(--tick-rule-fine);
  background-position: bottom;
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-faint);
}
.record__spec-row b { color: var(--text-secondary); font-weight: var(--weight-medium); }
/* same construction as .refusal__why a */
.record__spec-row a { color: var(--text-secondary); text-decoration: underline; text-decoration-color: var(--border-accent); text-underline-offset: var(--space-3xs); transition: color var(--dur-fast) var(--ease-state); }
.record__spec-row a:hover { color: var(--text-primary); }

/* THE SPINE. Not a footnote at the foot of a card: an inset plate on
   --surface-page inside a --surface-sunken section, carrying the only
   slab-weight accent edge in the row, last and heaviest in every record. The
   eye resolves each reading INTO the thing we could not fix. The accent is a
   FILL here and never the label's ink — "What stayed" is a word. */
.record__stayed {
  --chamfer: var(--chamfer-md);
  margin-block-start: var(--space-2xs);
  padding: var(--card-pad);
  background: var(--chamfer-edge), var(--surface-page);
  border-inline-start: var(--border-width-slab) solid var(--surface-accent);
  border-radius: var(--radius-card);
  clip-path: var(--chamfer-clip);
}
.record__stayed-key { font-family: var(--font-mono); font-size: var(--text-micro); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-faint); }
.record__stayed-val { max-width: var(--width-measure); font-size: var(--text-body-sm); line-height: var(--leading-body); color: var(--text-secondary); }

/* side by side as soon as a track holds a legible frame */
@media (min-width: 768px) {
  .record__frames { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 900px) {
  .record__row {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    grid-template-areas: "idx body" "frames body";
    /* the record is shorter than the frame pair beside it. Centred it floats
       level with the middle of the pair; hung from the top it left ~230px of
       bare ground under the datasheet, and pushed to the foot it opened the
       same gap between the datasheet and the spine plate. Same decision
       .job--expanded took, for the same reason. */
    align-items: center;
    column-gap: var(--space-2xl);
  }
  .record__row:nth-child(even) {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    grid-template-areas: "body idx" "body frames";
  }
  .record__idx { grid-area: idx; align-self: start; }
  .record__frames { grid-area: frames; }
  .record__body { grid-area: body; }
}

@media (min-width: 1024px) {
  /* THE GRID BREAK. The list crosses BOTH container edges, alternating down
     the page. The reach is the gutter plus whatever outer margin the wrap has
     at this width, measured off the layout exactly as measure.css measures it
     — flush with the container is not a crossing above --bp-2xl. .record
     clips, so nothing here can scroll sideways. */
  /* capped at --width-page: <body> stops there, so above 1,920 the raw
     (100vw - --width-wide)/2 is not the wrap's outer margin any more and the
     break overshot the viewport. Half a scrollbar of residue remains on
     Windows and Linux and cannot be removed in CSS — see base.css .bleed. */
  .record__row { --record-break: calc(var(--gutter) * -1 - max(0px, (min(100vw, var(--width-page)) - var(--width-wide)) / 2)); }
  .record__row:nth-child(odd) .record__frames { margin-inline-start: var(--record-break); }
  .record__row:nth-child(even) .record__frames { margin-inline-end: var(--record-break); }
}

/* ==========================================================================
   07 JOBS — horizontal scroll rail of documented jobs on the deepest surface.
   This is a GALLERY, not a comparison: matched before/after pairs do not exist
   yet and cropping two different photographs into a slider would lie about the
   one thing this business sells. See src/assets/photos/README.md. 6 images.
   ========================================================================== */

.jobs { background: var(--surface-sunken); overflow: hidden; }

.jobs__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
  margin-block-end: var(--space-stack);
}

.jobs__hint {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.jobs__rail {
  display: flex;
  gap: var(--grid-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-block-end: var(--space-sm);
  scrollbar-width: thin;
}
.jobs__rail::-webkit-scrollbar { height: var(--space-3xs); }
.jobs__rail::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--radius-pill); }

.job {
  flex: 0 0 auto;
  width: var(--card-min-wide);
  max-width: 82vw;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  background: var(--surface-raised);
  border: var(--border-width-hair) solid var(--border-hairline);
  border-radius: var(--radius-card);
  clip-path: var(--chamfer-clip);
  overflow: hidden;
  transition: border-color var(--dur-fast) var(--ease-state), transform var(--dur-fast) var(--ease-lift);
}
.job:hover { border-color: var(--border-accent-soft); transform: translate3d(0, var(--lift-tile), 0); }
.job:hover .media img { transform: scale3d(var(--scale-media-hover), var(--scale-media-hover), 1); }
.job:hover .media::before { transform: skewX(var(--sheen-skew)) translate3d(1200%, 0, 0); }

.job__media { aspect-ratio: var(--ratio-tall); }
.job__media::after { background: var(--chamfer-edge), var(--overlay-media); }

.job__index {
  position: absolute;
  z-index: var(--z-content);
  /* top-LEFT: the chamfer cuts the top-right corner and would clip it */
  inset-block-start: var(--space-2xs);
  inset-inline-start: var(--space-2xs);
  font-family: var(--font-mono);
  font-size: var(--text-h4);
  font-weight: var(--weight-bold);
  color: var(--text-accent);
}

.job__body { display: flex; flex-direction: column; gap: var(--job-body-gap); padding: var(--card-pad); flex: 1; }
.job__title { font-size: var(--text-h4); color: var(--text-primary); }
.job__paint {
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  line-height: var(--leading-micro);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-secondary);
}
/* Not every frame in the rail is a vehicle, so not every card carries a paint
   name — and the ones that did not sat their title a whole eyebrow line above
   their neighbours. The line is reserved instead of the copy being invented. */
/* Reserved with :first-child rather than :not(:has()) — .job__paint is always
   the record's opening line when it exists, so the title being first IS the
   absence, and a selector every engine has read since 2011 says so. Firefox
   before 121, which includes the 115 ESR still shipping on managed desktops,
   drops any rule containing :has(), and there the reserved line disappeared
   and one card in the rail sat a line proud of its neighbours.
   The offset is the missing line PLUS the flex gap the missing element would
   have brought with it — the gap is read from the same custom property the
   container sets it from, so the two cannot drift. */
.job__body { --job-body-gap: var(--space-2xs); }
.job__body > .job__title:first-child {
  margin-block-start: calc(var(--text-micro) * var(--leading-micro) + var(--job-body-gap));
}
.job__desc { font-size: var(--text-body-sm); line-height: var(--leading-body); color: var(--text-muted); }

.job__foot {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-block-start: auto;
  padding-block-start: var(--space-sm);
  background: var(--tick-rule-fine);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-faint);
}
.job__foot b { color: var(--text-secondary); font-weight: var(--weight-medium); }

/* the honest note card that closes the rail */
.job--note .job__body { justify-content: center; }
.job--note .media::after { background: var(--overlay-media-heavy); }
.job--note .job__note {
  font-size: var(--text-body-sm);
  line-height: var(--leading-body);
  color: var(--text-secondary);
}

/* --- the expanded record -------------------------------------------------
   A city page carries ONE job, not a rail, and a .job outside .jobs__rail
   renders at --card-min-wide and floats as a stray card. Expanded, the frame
   keeps the narrow track and the record takes the rest.
   The row wraps, and the body carries a real basis rather than 0, so that
   between --bp-md and --bp-lg — and on the record that carries two frames —
   the record drops under the frames instead of being crushed to a column of
   single words. specs/021-city-pages.md block 06. */
.job--expanded { width: auto; max-width: none; }

@media (min-width: 900px) {
  .job--expanded { flex-direction: row; flex-wrap: wrap; }
  /* NO ratio override. The two job frames are 4:5 and 3:4 portraits, and
     --ratio-card cut a 380x285 letterbox out of the middle of them: on the
     first record that removed the polisher and left a dark red smear. The
     rail's own --ratio-tall is the ratio the files were shot at. */
  .job--expanded .job__media { flex: 0 0 var(--card-min-wide); }
  /* Centring a full-height body put the closing rule at the foot of a 500px
     track with three lines of prose above it and ~90px of nothing between.
     The body is only as tall as the record, centred against the frame, so the
     DURATION rule closes the prose instead of floating below it. An expanded
     record also sets one step larger than a rail card — it is the page's one
     job, not one of six. */
  .job--expanded .job__body {
    flex: 1 1 var(--width-prose);
    align-self: center;
    padding: var(--card-pad-lg);
    --job-body-gap: var(--space-xs);
  }
  .job--expanded .job__title { font-size: var(--text-h3); }
  .job--expanded .job__desc { font-size: var(--text-lead); max-width: var(--width-measure); }

  /* TWO frames beside one record. At --card-min-wide the pair set the card
     509px tall around a 284px record and opened 225px of it as bare ground.
     The narrower track brings the frames' block size within reach of the
     record's and hands the width back to the record, which is what stops the
     expanded card reading as a 500px box with a paragraph floating in it. */
  /* Stated on the article as .job--pair, not detected with :has(): Firefox
     115 ESR has no :has() and would drop this rule, putting both frames back
     on --card-min-wide and reopening the 225px of bare ground under the
     record that the narrower track exists to close. */
  .job--pair .job__media { flex-basis: var(--card-min); }
}

@media (min-width: 1024px) {
  .jobs__rail { margin-inline-end: calc(50% - 50vw); padding-inline-end: var(--rail-peek); }
}

/* --------------------------------------------------------------------------
   THE PHOTOGRAPHY POLICY STANDFIRST. Every record in this rail names a car and
   a colour over a frame that is neither. The resolution is not faked matching
   photography: it is stating what the frames are, once, at the head of the
   rail, and letting every card inherit it. docs/content.md §Photography policy
   — the jobs rail. Chrome, not page copy: .jobs__policy is the exempt hook for
   the no-shared-sentence check, specs/021-city-pages.md criteria 47-50.
   -------------------------------------------------------------------------- */
.jobs__policy b {
  color: var(--text-secondary);
  font-weight: var(--weight-semibold);
}

/* ==========================================================================
   02 STAT BAND — the one bold move, now a measuring rule rather than a slab.
   Ticks run along the top and bottom edges, every fifth long; the vertical
   dividers are gone because the ticks do that work. The four figures sit on
   the scale as readings. 0 images.
   ========================================================================== */

.statband {
  --chamfer: var(--chamfer-lg);
  position: relative;
  background: var(--surface-accent);
  color: var(--text-on-accent);
  clip-path: var(--chamfer-clip);
}

/* the graduations. The tick rule is composed per-element, so the band sets its
   own inks, pitch and mark lengths and --tick-rule follows. */
.statband {
  --tick-ink: var(--tick-ink-accent);
  --tick-ink-major: var(--tick-ink-accent-major);
  --tick-pitch: var(--tick-band-pitch);
  --tick-len: var(--tick-band-len);
  --tick-len-major: var(--tick-band-len-major);
}
.statband::before,
.statband::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  height: var(--tick-rule-h);
  background: var(--tick-rule);
  pointer-events: none;
}
.statband::before { inset-block-start: 0; }
.statband::after { inset-block-end: 0; transform: scaleY(-1); }

.statband__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-md) var(--space-lg);
  padding-block: var(--space-band);
}

.statband__cell {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3xs);
}

.statband__fig {
  display: flex;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: var(--text-stat);
  font-weight: var(--weight-bold);
  line-height: var(--leading-flat);
  letter-spacing: var(--tracking-mono-tight);
}
/* the $ and M in $2M are fragments, not footnotes: at --text-h4 against a
   64px figure the value read as a lone "2". */
.statband__unit {
  font-size: var(--text-price);
  font-weight: var(--weight-medium);
  color: var(--text-on-accent-muted);
}
/* a trailing unit needs the space a flex item collapses away */
.statband__unit--sp { margin-inline-start: var(--space-3xs); }

.statband__label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-on-accent-muted);
}

/* 4-up only once a cell can hold the longest reading in the shipped mono face:
   at 768 the cell is 178px and "2,400+" measures 175px before the label, which
   is a cell driven by its own figure. Measured, not estimated. */
@media (min-width: 1200px) {
  .statband__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* ==========================================================================
   06 MEASUREMENT — the positioning argument. Offset overlap: the tall panel
   photograph bleeds past the right viewport edge and the mono readout block
   overlaps its left third. Grid break #1. 1 image.
   ========================================================================== */

.measure { background: var(--surface-page); overflow: hidden; }

/* docs/art-direction.md: surfaces alternate so the page is never one tone for
   more than two blocks. On a page whose neighbours both sit on --surface-page
   this block takes the alternating band instead. */
.measure--alt { background: var(--surface-alt); }

.measure__inner { display: grid; gap: var(--space-stack); }

.measure__media {
  position: relative;
  aspect-ratio: var(--ratio-card);
  border-radius: var(--radius-card);
  clip-path: var(--chamfer-clip);
}
.measure__media::after { background: var(--chamfer-edge), var(--overlay-media); }

.measure__body { display: flex; flex-direction: column; gap: var(--space-md); }

.measure__quote {
  font-family: var(--font-display);
  font-size: var(--text-lead);
  font-weight: var(--weight-medium);
  line-height: var(--leading-body);
  color: var(--text-secondary);
  padding-inline-start: var(--space-md);
  border-inline-start: var(--border-width-slab) solid var(--surface-accent);
}

/* --- the readout ledger --------------------------------------------------
   The head and the rows were two independent grids sharing a written-out track
   list, so the auto column sized to "DEPTH" in one and to "118 um" in the
   other and the headers never sat over their columns. They now share ONE track
   list, declared once on the panel as --measure-cols and inherited: the same
   guarantee subgrid gave, minus the requirement that the panel be a grid.
   The panel is a flex column instead, which is what lets the note sit on the
   panel foot when the pair of panels is stretched to a common height. */
.measure__panel {
  --measure-cols: minmax(0, 1.7fr) minmax(min-content, 1fr) minmax(0, 1.2fr);
  display: flex;
  flex-direction: column;
  padding: var(--card-pad-lg);
  background: var(--chamfer-edge), var(--surface-raised);
  border: var(--border-width-hair) solid var(--border-strong);
  border-radius: var(--radius-card);
  clip-path: var(--chamfer-clip);
}

/* --- the drive-time pair --------------------------------------------------
   The city pages read an area against one figure and have no third datum, so
   the panel's own three tracks would leave a dead column and hang the head
   over nothing. Two tracks, declared once, inherited by the head and the rows
   exactly as the three-track list is. specs/021-city-pages.md block 03. */
.measure__panel--pair { --measure-cols: minmax(0, 1fr) auto; }

/* Below --bp-md the three-track list resolved the name column to 77px and every
   service wrapped to two or three lines, because the third datum was given more
   room than the thing it describes. Narrow, the row is name + figure, and the
   third datum drops to its own line underneath. */
.measure__panel-head,
.measure__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "name  price"
    "meta  meta";
  column-gap: var(--space-sm);
  row-gap: var(--space-3xs);
  align-items: baseline;
  /* docs/uniqueness.md move 6: every horizontal rule on the page is a tick
     rule. background-color stays free for the hover so the two do not fight. */
  background: var(--tick-rule-fine);
  background-position: bottom;
}
.measure__panel-head > span:nth-child(1),
.measure__panel-name { grid-area: name; }
.measure__panel-head > span:nth-child(2),
.measure__depth { grid-area: price; text-align: end; }
.measure__panel-head > span:nth-child(3),
.measure__defect { grid-area: meta; text-align: start; }

.measure__panel-head {
  padding-block-end: var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The rows take whatever height the panel was stretched to. A five-row add-on
   panel beside a twelve-row service panel otherwise left 336px of bare card at
   its foot; grown and centred, the same panel reads as the sparser of two
   instruments rather than as a box someone forgot to fill. Where the panel is
   not stretched — every service page — there is no free space and no growth. */
.measure__row {
  flex: 1 0 auto;
  align-content: center;
  padding-block: var(--space-2xs);
  transition: background-color var(--dur-fast) var(--ease-state);
}
.measure__row:hover { background-color: var(--surface-accent-wash); }

.measure__panel-name {
  font-family: var(--font-display);
  font-size: var(--text-body-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
}
/* The figure is the thing this panel is read for, so it carries the hierarchy
   in size and weight. It was the same tonal value as the name beside it and
   only the accent hue told them apart, which is hierarchy by colour. */
.measure__depth {
  font-family: var(--font-mono);
  font-size: var(--text-price);
  font-weight: var(--weight-bold);
  line-height: var(--leading-flat);
  letter-spacing: var(--tracking-mono-tight);
  color: var(--text-accent);
  white-space: nowrap;
}
.measure__defect {
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-mono-label);
  color: var(--text-muted);
}

/* auto absorbs whatever height a stretched panel was given, so the note sits on
   the panel foot rather than leaving the dead quadrant open underneath it */
.measure__note {
  margin-block-start: auto;
  padding-block-start: var(--space-sm);
  font-size: var(--text-caption);
  line-height: var(--leading-snug);
  color: var(--text-muted);
}

.measure__col { display: flex; flex-direction: column; gap: var(--space-stack); }

/* --- the index price panel: two instrument columns, not one 660px box -----
   Stretched, not start-aligned: the five-row add-on panel ended 302px above the
   twelve-row service panel and left the right quadrant of the section dead. */
.measure__panels { display: grid; gap: var(--grid-gap); }

@media (min-width: 768px) {
  .measure__panel-head,
  .measure__row {
    grid-template-columns: var(--measure-cols);
    grid-template-areas: "name price meta";
  }
  .measure__panel-head > span:nth-child(3),
  .measure__defect { text-align: end; }

  /* two tracks, so the row is one area wide and the "meta" line is not
     reserved under a datum that does not exist */
  .measure__panel--pair .measure__panel-head,
  .measure__panel--pair .measure__row { grid-template-areas: "name price"; }
}

@media (min-width: 1024px) {
  .measure__panels { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }

  .measure__inner {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    align-items: center;
    column-gap: var(--space-3xl);
  }
  /* the head sits on the photograph's top edge. Centred, it floated half a
     screen down the column and opened bare ground between the block above and
     this one's heading. */
  .measure__col { grid-column: 1; grid-row: 1; align-self: start; }
  .measure__media {
    grid-column: 2;
    grid-row: 1;
    aspect-ratio: var(--ratio-tall);
    /* 50vw is measured from this element's own containing block, not the page,
       so the old calc(50% - 50vw) pulled the photograph 670px out and .measure
       clipped a third of it — and, being ratio-locked, it grew to 1664px tall
       and opened a screen of bare ground beside the reading column.
       The gutter plus the wrap's outer margin is the real distance to the
       viewport edge. */
    /* 100vw counts the classic Windows scrollbar and <body> stops growing at
       --width-page, so the raw expression overstated the wrap's outer margin
       by half a scrollbar at every width and by hundreds of pixels above
       1,920 — where the frame ran past the viewport edge and .measure clipped
       it. Capped at the page width the wide case is exact; the ~8px scrollbar
       residue has no CSS remedy, no unit reports the viewport without it. */
    margin-inline-end: calc(var(--gutter) * -1 - max(0px, (min(100vw, var(--width-page)) - var(--width-wide)) / 2));
    border-start-end-radius: var(--radius-sharp);
    border-end-end-radius: var(--radius-sharp);
  }
  /* the offset overlap belongs to the service-page layout only: unscoped it
     also pushed the index's left price panel under its add-on panel */
  .measure__col .measure__panel {
    position: relative;
    z-index: var(--z-raised);
    margin-inline-end: calc(var(--space-4xl) * -1);
  }

  /* A landscape frame crushed into the portrait track loses everything except
     its middle sixth. Where the file is 16:9 the media states its own ratio.
     Declared after .measure__media: same specificity, so order decides. */
  .measure__media--wide { aspect-ratio: var(--ratio-card); }
}

/* --------------------------------------------------------------------------
   THE MIRRORED, UNCARDED VARIANT — one city page's block 03.
   The other city page runs this block as a raised instrument panel overlapping
   a photograph that hangs off the END viewport edge. Printing the same shape
   under the same verbatim h2 is what still made the two pages read as one page
   seen twice, and changing the frame and the eyebrow was a caption change, not
   a structural one. Here the drive times are set inline in the reading column
   as bare ruled rows — no card, no raised surface, no overlap, therefore
   nothing covering the chip — and the photograph takes the START column and
   hangs off the start edge instead. Same component, same rows, mirrored shape.
   -------------------------------------------------------------------------- */
.measure--inline .measure__panel {
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--radius-sharp);
  clip-path: none;
}
/* the frame here is a 3:2 file and states its own ratio, the way --wide does */
.measure--inline .measure__media { aspect-ratio: var(--ratio-bleed); }

@media (min-width: 1024px) {
  /* the reading column is the wide one on this side of the mirror */
  .measure--inline .measure__inner { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
  .measure--inline .measure__col { grid-column: 2; }
  .measure--inline .measure__media {
    grid-column: 1;
    /* stretched, not ratio-locked: at 3:2 the frame closed 531px into an 825px
       row and left ~294px of ground under it. A grid item with a definite
       stretched height ignores aspect-ratio, and .media already covers, so the
       photograph fills the column and the reading column sets the height. */
    align-self: stretch;
    aspect-ratio: auto;
    /* the same reach as the end-side bleed, measured from this element's own
       containing block: the gutter plus whatever the wrap's outer margin is */
    margin-inline-start: calc(var(--gutter) * -1 - max(0px, (min(100vw, var(--width-page)) - var(--width-wide)) / 2));
    margin-inline-end: 0;
    border-start-start-radius: var(--radius-sharp);
    border-end-start-radius: var(--radius-sharp);
    border-start-end-radius: var(--radius-card);
    border-end-end-radius: var(--radius-card);
  }
  /* no overlap and no raised layer: the rows belong to the column */
  .measure--inline .measure__col .measure__panel {
    position: static;
    margin-inline-end: 0;
  }

  /* the frame reaches the start viewport edge here, so the chip reads from the
     inside corner rather than sitting 8px off the edge of the page */
  .measure--inline .measure__media .chip {
    inset-inline-start: auto;
    inset-inline-end: var(--chip-offset);
  }
}

/* --------------------------------------------------------------------------
   THE TIER PANEL — the same instrument, read on three channels instead of one.
   Service · Duration · I · II · III. docs/content.md §Vehicle size tiers.

   Wide, five tracks on the panel's own inherited track list, so the head sits
   over its columns exactly as it does on the three-track version. Narrow, a
   three-column money table does not fit at 390 and a table that scrolls
   sideways hides two thirds of the prices as surely as a dropdown would, so
   the row becomes a card: name and duration on one line, then one full-width
   row per size, each carrying its own numeral. Nothing leaves the DOM.
   -------------------------------------------------------------------------- */
.measure__panel--tiers {
  --measure-cols: minmax(0, 1.7fr) minmax(min-content, 0.8fr) repeat(3, minmax(min-content, 0.78fr));
}

.measure__panel--tiers .measure__panel-head,
.measure__panel--tiers .measure__row {
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: none;
  row-gap: var(--space-2xs);
  align-items: center;
}
.measure__panel--tiers .measure__row { padding-block: var(--space-xs); }

.measure__panel--tiers .measure__panel-name { grid-column: 1; grid-row: 1; }
.measure__panel--tiers .measure__defect { grid-column: 2; grid-row: 1; text-align: end; }
.measure__panel--tiers .tierfig { grid-column: 1 / -1; }

.measure__panel--tiers .measure__panel-head > span { grid-row: 1; }
.measure__panel--tiers .measure__panel-head > span:nth-child(1) { grid-column: 1; text-align: start; }
.measure__panel--tiers .measure__panel-head > span:nth-child(2) { grid-column: 2; text-align: end; }
/* the three channel names have no columns to sit over until the table is a
   table; until then every figure carries its own numeral instead */
.measure__panel--tiers .measure__panel-head > span:nth-child(n + 3) { display: none; }

/* the key is the legend for twenty-one figures: full width, above both panels */
.measure__panels > .tierkey { grid-column: 1 / -1; }

@media (min-width: 768px) {
  .measure__panel--tiers .measure__panel-head,
  .measure__panel--tiers .measure__row {
    grid-template-columns: var(--measure-cols);
    grid-template-areas: none;
    row-gap: var(--space-3xs);
  }
  /* the three channel names, pinned to the three channels. Without an explicit
     column each one auto-places against the base rule's unresolved grid-area
     and the head reads II III I. */
  .measure__panel--tiers .measure__panel-head > span:nth-child(n + 3) {
    display: block;
    grid-row: 1;
    text-align: end;
  }
  .measure__panel--tiers .measure__panel-head > span:nth-child(3) { grid-column: 3; }
  .measure__panel--tiers .measure__panel-head > span:nth-child(4) { grid-column: 4; }
  .measure__panel--tiers .measure__panel-head > span:nth-child(5) { grid-column: 5; }
  .measure__panel--tiers .tierfig { grid-column: auto; grid-row: 1; }
  /* one figure across three channels, not the same number printed three times */
  .measure__panel--tiers .tierfig--span { grid-column: 3 / -1; }
}

@media (min-width: 1024px) {
  /* five channels want the wider of the two tracks; the five-row add-on panel
     beside it does not */
  .measure__panels--tiers { grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); }
}

/* ==========================================================================
   13 QUOTE — split. Form on the left, NAP panel on --radius-slab on the right.
   There is no form server: the form's action is a mailto: to the studio, which
   the browser performs with JS off. With JS on, js/modules/quote.js intercepts
   the submit and answers in .quote__sent, which carries the same message on a
   link the visitor presses. 0 images.
   ========================================================================== */

.quote { background: var(--surface-page); }

.quote__inner { display: grid; gap: var(--space-stack); align-items: start; }

.quote__form { display: grid; gap: var(--space-sm); }

.field { display: grid; gap: var(--space-3xs); }
.field--wide { grid-column: 1 / -1; }

.field__label {
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}
.field__req { color: var(--text-accent); }

.field__input,
.field__select,
.field__textarea {
  --chamfer: var(--chamfer-sm);
  width: 100%;
  min-height: var(--control-h);
  padding: var(--space-2xs) var(--space-sm);
  background: var(--surface-inset);
  border: var(--border-width-hair) solid var(--border-strong);
  border-radius: var(--radius-inset);
  clip-path: var(--chamfer-clip);
  color: var(--text-primary);
  font-size: var(--text-body-sm);
  transition: border-color var(--dur-fast) var(--ease-state), background-color var(--dur-fast) var(--ease-state);
}
.field__textarea { min-height: var(--space-5xl); resize: vertical; line-height: var(--leading-body); }

/* iOS SAFARI ZOOMS THE VIEWPORT on focus for any field whose computed size is
   under 16px, and --text-body-sm is 15. On the site's only booking path that
   throws the layout sideways in the middle of typing and leaves the page
   zoomed afterwards. The other cure, maximum-scale=1 in the viewport meta,
   also kills pinch zoom for low-vision users, so it is not on the table.
   --text-body is exactly 16px, which is the threshold, and the fields are the
   only thing on the page that changes.
   Two conditions, because the two populations are not the same set: every
   phone and portrait tablet by width, and every touch screen at any width by
   pointer. Above both, the 15px calibration stands. */
@media (max-width: 1023px), (pointer: coarse) {
  .field__input,
  .field__select,
  .field__textarea { font-size: var(--text-body); }
}

/* THE SELECT IS THE ONE CONTROL EVERY ENGINE DRAWS DIFFERENTLY. Chrome puts a
   grey chevron inside the padding box, Firefox on Windows draws its own arrow
   and paints the popup from the control's own colours, and Safari and iOS
   overlay a rounded native bezel that ignores the chamfer entirely. Three
   renderings of one field. appearance: none takes the control back in all
   three; the -moz- and -webkit- lines stay because the unprefixed property is
   younger than the engines this pass is aimed at.
   The chevron is redrawn on the wrapper, not on the select: a background-image
   on the control itself is wiped by the `background` shorthand in the hover
   rule below, and this site has no gradient chevrons. Two borders on a square,
   turned 45deg — the same construction as the site's arrow marks. */
.field__select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-inline-end: calc(var(--space-sm) * 2 + var(--icon-sm));
}
.field--select { position: relative; }
.field--select::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--space-sm);
  inset-block-end: calc(var(--control-h) / 2);
  width: var(--space-2xs);
  height: var(--space-2xs);
  border-inline-end: var(--border-width-medium) solid var(--text-muted);
  border-block-end: var(--border-width-medium) solid var(--text-muted);
  transform: translate3d(0, 50%, 0) rotate(45deg);
  pointer-events: none;
}
/* the popup list is UA chrome and inherits nothing, so it is inked here or it
   ships as near-black on near-black in Firefox on Windows, which paints the
   list from the control's own colours rather than from the system menu */
.field__select option { background: var(--surface-inset); color: var(--text-primary); }
.field__input:hover,
.field__select:hover,
.field__textarea:hover { border-color: var(--border-soft); background-color: var(--surface-inset-hover); }
/* chamfered, so the ring is drawn inside rather than as a clipped outline */
.field__input:focus-visible,
.field__select:focus-visible,
.field__textarea:focus-visible {
  outline: none;
  border-color: var(--border-accent);
  box-shadow: var(--focus-ring-inset);
}
.field__input::placeholder, .field__textarea::placeholder { color: var(--text-faint); }

.quote__consent {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-2xs);
  align-items: start;
  font-size: var(--text-caption);
  line-height: var(--leading-snug);
  color: var(--text-muted);
}
.quote__consent input { accent-color: var(--surface-accent); width: var(--icon-md); height: var(--icon-md); }

.quote__submit { justify-self: start; }

/* --- the confirmation panel ----------------------------------------------
   Swapped in for the form after a valid submit, and swapped back out by the
   "edit" button. Both boxes declare their own display, so both need the
   [hidden] pair: an author `display` beats the UA sheet's [hidden] rule and
   the panel would otherwise ship visible. */
.quote__form[hidden],
.quote__sent[hidden] { display: none; }

.quote__sent {
  --chamfer: var(--chamfer-md);
  --chamfer-edge-ink: var(--border-accent-soft);
  display: grid;
  justify-items: start;
  gap: var(--space-sm);
  padding: var(--card-pad-lg);
  border: var(--border-width-hair) solid var(--border-accent-soft);
  border-radius: var(--radius-card);
  clip-path: var(--chamfer-clip);
  background: var(--chamfer-edge), var(--surface-accent-wash);
}
/* chamfered: the outline would be clipped, so the ring is drawn inside */
.quote__sent:focus-visible { outline: none; box-shadow: var(--focus-ring-inset); }

.quote__sent-title { font-size: var(--text-h3); color: var(--text-primary); }
.quote__sent-body {
  max-width: var(--width-measure);
  font-size: var(--text-body-sm);
  line-height: var(--leading-body);
  color: var(--text-secondary);
}
.quote__sent-fallback {
  max-width: var(--width-measure);
  font-size: var(--text-caption);
  line-height: var(--leading-body);
  color: var(--text-muted);
}
.quote__sent-link {
  color: var(--text-accent);
  transition: color var(--dur-fast) var(--ease-state);
}
.quote__sent-link:hover { color: var(--text-accent-strong); }

/* the handoff and the way back, side by side until the column is too narrow */
.quote__sent-acts { display: flex; flex-wrap: wrap; gap: var(--space-2xs); }

/* --- the NAP panel ------------------------------------------------------- */
.quote__nap {
  --chamfer: var(--chamfer-lg);
  display: grid;
  gap: var(--space-md);
  padding: var(--card-pad-lg);
  background: var(--chamfer-edge), var(--surface-raised);
  border: var(--border-width-hair) solid var(--border-hairline);
  border-radius: var(--radius-slab);
  clip-path: var(--chamfer-clip);
}

.nap__row { display: grid; gap: var(--space-3xs); }
.nap__key {
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}
.nap__val { font-size: var(--text-body-sm); line-height: var(--leading-body); color: var(--text-secondary); }
.nap__val--big {
  font-family: var(--font-mono);
  font-size: var(--text-h4);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  transition: color var(--dur-fast) var(--ease-state);
}
a.nap__val--big:hover { color: var(--text-accent); }

.nap__hours { display: grid; gap: var(--space-3xs); }
.nap__hours-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  /* every horizontal rule on this site is a tick rule (docs/uniqueness.md
     move 6). This row was the last plain hairline left. */
  padding-block: var(--space-2xs);
  background: var(--tick-rule-fine);
  background-position: bottom;
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  color: var(--text-muted);
}
.nap__hours-row b { color: var(--text-secondary); font-weight: var(--weight-medium); }

.nap__certs { display: flex; flex-wrap: wrap; gap: var(--space-2xs); }
.nap__cert {
  padding: var(--space-3xs) var(--space-2xs);
  border: var(--border-width-hair) solid var(--border-accent-soft);
  border-radius: var(--radius-pill);
  background: var(--surface-accent-wash);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-mono-label);
}

/* --- /book/: the form IS the page ----------------------------------------
   The only page on the site with no hero, so the block has to clear the fixed
   header itself, and the masthead sets at display scale rather than at h2 —
   .sec-title would put the h1:body ratio at 3.5:1 and fail the type-contrast
   law. Same reasoning specs/020 records for .hero__title. The confirmation
   plate grows with it: at card scale it read as a notice dropped into a page
   whose whole job is the action it answers. */
.quote--page { padding-block-start: calc(var(--header-h) + var(--space-xl)); }
.quote--page .sec-title {
  font-size: var(--text-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-display);
}
.quote--page .quote__sent { justify-self: stretch; --chamfer: var(--chamfer-lg); }
.quote--page .quote__sent-title { font-size: var(--text-h2); }

@media (min-width: 768px) {
  .quote__form { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
  .quote__inner { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); column-gap: var(--space-2xl); }
}

/* ==========================================================================
   14 FOOTER — dense four-column: Studio, Services, Service area, Hours.
   NAP identical to the quote panel and to the JSON-LD. 0 images.
   ========================================================================== */

.footer {
  background: var(--surface-sunken);
  padding-block: var(--space-lg) var(--space-lg);
}
.footer__rule { margin-block-end: var(--space-stack); }

.footer__top {
  display: grid;
  gap: var(--space-stack);
  padding-block-end: var(--space-stack);
  border-block-end: var(--border-width-hair) solid var(--border-hairline);
}


.footer__brand { display: grid; gap: var(--space-sm); align-content: start; }
.footer__mark {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-display);
  font-size: var(--text-h3);
  font-weight: var(--weight-display);
  letter-spacing: var(--tracking-label);
  color: var(--text-primary);
}
/* the identity block is the one place the gauge is allowed to lead, so it runs
   at --brandmark-h rather than the header's --icon-md */
.footer__mark-icon { width: var(--brandmark-h); height: var(--brandmark-h); }
.footer__mark-word { white-space: nowrap; }
.footer__mark-dot { color: var(--text-accent); }
.footer__line { font-size: var(--text-body-sm); line-height: var(--leading-body); color: var(--text-muted); max-width: var(--width-prose); }

.footer__cols { display: grid; gap: var(--space-stack); }

.footer__col-head {
  font-family: var(--font-mono);
  font-size: var(--text-micro);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-block-end: var(--space-xs);
}

.footer__list { display: grid; gap: var(--space-2xs); }
.footer__list a,
.footer__list span {
  font-size: var(--text-body-sm);
  display: inline-block;
}
.footer__list a {
  color: var(--text-muted);
  transition: color var(--dur-fast) var(--ease-state), transform var(--dur-fast) var(--ease-lift);
}
.footer__list a:hover { color: var(--text-primary); transform: translate3d(var(--space-3xs), 0, 0); }
/* A span in this list is a fact, not a destination — the address lines. It was
   inheriting the link colour and the link box, so it read as a target that did
   nothing on click. One step quieter is the whole difference. */
.footer__list span { color: var(--text-faint); }

.footer__area-more {
  margin-block-start: var(--space-2xs);
  max-width: var(--width-measure);
  font-size: var(--text-caption);
  line-height: var(--leading-body);
  color: var(--text-faint);
}

.footer__hours-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block: var(--space-3xs);
  border-block-end: var(--border-width-hair) solid var(--border-hairline);
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  color: var(--text-muted);
}
.footer__hours-row b { color: var(--text-secondary); font-weight: var(--weight-medium); }

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block-start: var(--space-md);
}
.footer__copy { font-size: var(--text-caption); color: var(--text-muted); }
.footer__social, .footer__legal { display: flex; flex-wrap: wrap; gap: var(--space-md); font-family: var(--font-mono); font-size: var(--text-caption); color: var(--text-muted); }
.footer__social a, .footer__legal a { transition: color var(--dur-fast) var(--ease-state); }
.footer__social a:hover, .footer__legal a:hover { color: var(--text-primary); }
.footer__social-link { display: inline-flex; align-items: center; gap: var(--space-3xs); }

.footer__disclaimer {
  margin-block-start: var(--space-sm);
  font-size: var(--text-micro);
  line-height: var(--leading-snug);
  color: var(--text-muted);
  max-width: var(--width-measure);
}

@media (min-width: 768px) {
  .footer__cols { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-lg); }
}
@media (min-width: 1024px) {
  .footer__top { grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: var(--space-2xl); }
}
