/* ===========================================================
   Services — Screen 06, `06-services.md`.

   GROUND BLACK, end to end, with the closing band as the page's
   one exception (§1). An amendment to E1 decided on rendered
   frames: Home, Services and /ajman state; Work and the 47
   record pages prove, on paper.

   Nothing is set on a photograph here, because there is no
   photograph — the page's one visual is a drawing. Every pair
   on it is already measured in §5: #FFFFFF on #000000 21:1 ·
   #EDEDED 17.94:1 · #A6A6A6 8.63:1 · #111111 on #FFFFFF 18.88:1.

   The two dark line values are not interchangeable (continuity
   §9): --color-hairline-dark (#333333, 1.66:1) may only
   separate text sections, which is the one job the block rules
   and the caption rules do here. Nothing on this page is
   bounded or stated by a #333333 line.

   COLOUR IS STATED, NEVER INHERITED. base.css colours every
   heading --color-ink and tokens.css lifts only
   .t-hero/.t-editorial/.t-h2/.t-h3 under .surface-dark — so
   .t-body-lg, every paragraph and every bare element computes
   #111111 on #000000 and disappears. Each rule below that sets
   type on black states its value.

   THREE REGISTERS AND NO INVENTED VALUES. §5 asks for the
   delivery statement at 36px on a 34ch measure; there is no
   36px token and no 34ch one, and CLAUDE.md forbids inventing
   either (the next token is a guideline revision). It ships at
   --fs-h2 / --measure-h2, which is the same call Home's room
   verb took against §5.2's 22px. §5's intent survives exactly:
   the step down from 44px was made because a sentence pair had
   been cut at a fragment's scale.
   =========================================================== */

/* -----------------------------------------------------------
   1 · The claim (§2)
   ----------------------------------------------------------- */

/* The top padding CLEARS THE CHROME at every width. The trigger is fixed at
   the top inline-start — the corner this claim starts in — and the plate
   hangs opposite it. Home's claim ran under the plate at 375, at 768 and
   from ~1025 to ~1200px, and a box test passed it every time because the box
   was 960px wide where the line was 870. Measured here against the rendered
   ink. */
/* 🔴 THE FOOT OF THIS SECTION IS NOT --gap-section, AND THAT IS DELIBERATE
   (Session XXVI, Mahesh on the rendered page). --gap-section is calibrated
   for a TEXT boundary, where the next section's ink starts at its own top
   edge. The range does not: its box is 407px tall and its own upper-left is
   empty by construction, because the set is sorted ascending and the short
   end is 23u against the tall end's 434u. Directly beneath the claim, the
   nearest ink was ~560px away — the section token plus the drawing's own
   internal void, paid twice.

   So the boundary is stated in the units the fault appears in: THE CLAIM'S
   FOOT IS THE GAP TO THE DRAWING'S BOX, NOT TO ITS INK, and it is set to
   the component step. The drawing's own empty upper region supplies the
   rest of the breathing room and does not need help. */
/* 🔴 BOTH ENDS OF THIS SECTION ARE STATED, AND NEITHER IS --gap-section.
   Session XXVI, calibrated against the rendered chrome rather than chosen.

   THE HEAD — the wordmark plate's mark ends at y=82 and --arrival-clearance
   alone put the H1's line box at 128, a clearance of 46px beneath a heading
   whose own line is 59.8px. A claim that clears the chrome by less than one
   line of itself reads as crowded however much room is below it. One line
   of clearance turned out to be the FLOOR, not the target: at 1.17 lines
   it still read tight on the rendered page, so the rule is stated at the
   value that actually holds — THE CLAIM CLEARS THE WORDMARK BY ROUGHLY TWO
   OF ITS OWN LINES. That is 200px of padding, a 115px clearance, 1.92
   line-heights.

   🔴 AND IT IS BUILT FROM --gap-block ×3, WHICH LOOKS ARBITRARY AND IS NOT.
   The obvious spelling is --arrival-clearance + --gap-heading. It was tried
   and p21 caught it: --gap-heading RESTATES ITSELF at 1023 (64→40) and
   again at 767 (40→24), so a clearance built from it inherits those
   breakpoints and CLIFFS — measured, /services stepped 27.4px across
   1024→1023 and 31.7px across 768→767, against p21's 24px limit. Of the
   gap tokens only --gap-block is width-constant, so it is the only one a
   clearance may be built from without importing a breakpoint it did not
   ask for. A rule made of tokens inherits THOSE tokens' breakpoints.

   THE FOOT — --gap-section is calibrated for a TEXT boundary, where the
   next section's ink starts at its own top edge. The range does not: its
   box is 407px tall and its upper-left is empty by construction, because
   the set sorts ascending and the short end is 23u against the tall end's
   434u. Beneath the claim the nearest ink was ~560px away — the section
   token plus the drawing's own internal void, paid twice. The statement
   and the drawing are ONE beat (the sentence reads the drawing), so they
   take the within-beat step and the drawing's own air supplies the rest. */
.sv-claim {
  padding-block: calc(var(--arrival-clearance) + var(--gap-block) * 3) var(--gap-block);
}

/* The ≤1023 restatement that used to sit here is GONE — the value now lives in
   --arrival-clearance (tokens-layout.css), which restates itself at the gap
   tokens' own breakpoint. Seven files carried this identical block; the whole
   point of P3 is that there is one place to change it. */

.sv-claim__line {
  margin: 0;
}

/* .t-body-lg is NOT in the dark lift list. Stated. */
.sv-claim__body {
  margin: var(--gap-block) 0 0;
  color: var(--color-on-dark-body);
}

/* The statement register — ONE moment since Session XXVI. The theme slot
   carries the range statement ("The smallest building here and the
   tallest…"); the delivery and the close are gone, and the drawing sits
   directly under the sentence that reads it. Light, 32px, #FFFFFF. */
.sv-claim__theme {
  margin: 0;
  max-inline-size: var(--measure-h2);
  font-size: var(--fs-h2);
  font-weight: var(--fw-light);
  line-height: var(--lh-h2);
  letter-spacing: var(--ls-h2);
  color: var(--color-on-dark);
}

.sv-claim__theme {
  margin-block-start: var(--gap-heading);
}

/* -----------------------------------------------------------
   2 · The range, drawn (§3) — V-1
   ----------------------------------------------------------- */

/* 🔴 THE RANGE CLOSES ON ITS OWN DRAWING NOW (Mahesh, 9 Aug). The route row
   that used to sit here is gone, so this padding is no longer the distance
   between a ruled text row and the next band — it is the distance between the
   ELEVATION's ground line and the mechanism list, and --gap-section was
   written for the former. Measured with the row still in: drawn ink ended at
   934 and "Master planning" began at 1166, 232px ink to ink, on one
   continuous black field with nothing in between to hold it.

   `--gap-component + --gap-heading`, the same composition the /projects
   arrival takes under its control, and for the same reason: it is the one
   the system already makes out of these two, and CLAUDE.md's spacing clause
   bars inventing a value that is not a token. 200 -> 128. */
.sv-range {
  padding-block-end: calc(var(--gap-component) + var(--gap-heading));
}

/* margin-BLOCK only. A figure reset of `margin: 0` on an element that is or
   sits inside a container clobbers the auto margins that centre it — the
   Work print rendered at the page edge exactly this way. */
.sv-range__figure {
  margin-block: 0;
  margin-inline: 0;
}

.sv-drawing__svg {
  inline-size: 100%;
  block-size: auto;
}

/* THE STROKES (§3.7): 1px, monochrome white, and NON-SCALING. The drawing
   scales with its column — which is safe here in a way it is not on the map,
   because a non-scaling stroke is 1px at 1440 and 1px at 375. What scaling
   does threaten is the floor PITCH, and §3.7 answers that below. */
.sv-datum,
.sv-rise,
.sv-floors {
  fill: none;
  stroke: var(--color-on-dark);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* -----------------------------------------------------------
   3 · The draw-in (§4) — the section draws itself
   ----------------------------------------------------------- */

/* THE FINISHED DRAWING IS THE SHIPPED STATE. With no JS, a blocked file or
   an error above it, every elevation is already drawn; js/services.js arms
   it by hiding the strokes and plays the draw-in once. This is the inverse
   of a reveal system that starts everything at opacity 0, and it is why the
   page cannot ship blank.

   The dash arithmetic carries no per-building numbers: every path is emitted
   with pathLength="1", so one unit is one whole path however long it really
   is, and the short elevation and the tall one finish together. */
.sv-drawing--armed .sv-datum,
.sv-drawing--armed .sv-rise {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.sv-drawing--armed .sv-floors {
  opacity: 0;
}

/* The choreography (§4): the datum line first, then each elevation in range
   order at 95ms apart — its outline rising bottom-up, its floor lines
   arriving just after it. About 2.6s end to end, once. */
.sv-drawing--drawing .sv-datum {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 620ms var(--ease-out);
}

.sv-drawing--drawing .sv-rise {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 500ms var(--ease-out);
  transition-delay: calc(620ms + var(--elev-index) * 95ms);
}

.sv-drawing--drawing .sv-floors {
  opacity: 1;
  transition: opacity 420ms var(--ease-out);
  transition-delay: calc(620ms + var(--elev-index) * 95ms + 300ms);
}

/* -----------------------------------------------------------
   4 · The route row — the caption slot's successor (Session XXVI)
   ----------------------------------------------------------- */

/* One ruled row under the drawing, the link at the inline-end — the shape
   the two named ends' rows carried, kept for their successor. This is the
   range's one way out to the records and the page's only route besides the
   band's Contact. */
.sv-range__route {
  display: flex;
  justify-content: flex-end;
  margin: var(--gap-component) 0 0;
  padding-block-start: var(--pad-table-row);
  border-block-start: 1px solid var(--color-hairline-dark);
}

/* -----------------------------------------------------------
   5 · The mechanism (§2) — six ruled blocks
   ----------------------------------------------------------- */

.sv-mechanism {
  padding-block-end: var(--gap-section);
}

/* Name and sentence on one ruled row. The #333333 rule separates two text
   sections, which is the one thing 1.66:1 is permitted to do. */
.sv-block {
  display: grid;
  grid-template-columns: 1fr 2fr;
  column-gap: var(--gap-component);
  align-items: start;
  padding-block: var(--gap-block);
  border-block-start: 1px solid var(--color-hairline-dark);
}

.sv-block__name {
  margin: 0;
}

.sv-block__prose {
  margin: 0;
  max-inline-size: var(--measure-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--color-on-dark-body);
}

/* -----------------------------------------------------------
   6 · The specialist list (§2)
   ----------------------------------------------------------- */

/* One ruled block, and every sub-capability the practice offers lives here
   as an item rather than as a block or a page. That is the structure X6 is
   answered by: three disciplines, five or seven change what is in this list
   and never what the page is. */
.sv-speclist {
  margin-block-start: var(--gap-component);
  padding-block-start: var(--gap-block);
  border-block-start: 1px solid var(--color-hairline-dark);
}

.sv-speclist__lead {
  margin: 0 0 var(--gap-block);
  font-size: var(--fs-caption);
  font-weight: var(--fw-medium);
  line-height: var(--lh-caption);
  letter-spacing: var(--ls-caption);
  text-transform: uppercase;
  color: var(--color-on-dark-secondary);
}

/* The items run as one flowing line rather than a column of bullets: it is
   an inventory, not a hierarchy, and a bulleted column of sixteen would
   compete with the six blocks above it. They stay real list items, so the
   count and the boundaries survive for a screen reader. */
.sv-speclist__items {
  margin: 0;
  padding: 0;
  max-inline-size: var(--measure-body-lg);
  list-style: none;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--color-on-dark-body);
}

.sv-speclist__item {
  display: inline;
}

.sv-speclist__item:not(:last-child)::after {
  content: " \00b7 ";
  color: var(--color-ink-secondary);
}

/* -----------------------------------------------------------
   7 · The delivery and the close (§2)
   ----------------------------------------------------------- */

.sv-link {
  display: inline-block;
  border-block-end: 1px solid currentcolor;
  padding-block-end: 2px;
  font-size: var(--fs-metadata);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-nav);
  text-transform: uppercase;
  color: var(--color-on-dark);
  transition: border-block-end-width var(--dur-fast) var(--ease-out);
}

.sv-link:hover,
.sv-link:focus-visible {
  border-block-end-width: 2px;
}

.sv-link:focus-visible {
  outline: 2px solid var(--color-on-dark);
  outline-offset: 4px;
}

/* -----------------------------------------------------------
   8 · The closing band (§2) — the one lit surface
   ----------------------------------------------------------- */

/* E1.2: the band is the inverse of its page, so on a black page it is paper.
   The register is the site's, one on all fifty-four pages that carry it:
   32px light, never an uppercase imperative. */
.sv-band {
  padding-block: calc(var(--gap-section-major) / 2);
  background: var(--color-surface-dark);
}

.sv-band__line {
  margin: 0 0 var(--gap-block);
  max-inline-size: var(--measure-h2);
  font-size: var(--fs-h2);
  font-weight: var(--fw-light);
  line-height: var(--lh-h2);
  letter-spacing: var(--ls-h2);
  color: var(--color-on-dark);
}

.sv-band__action {
  display: inline-block;
  padding-block: var(--gap-tight);
  padding-inline: var(--gap-heading);
  border: 1px solid var(--color-on-dark);
  font-size: var(--fs-metadata);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-nav);
  text-transform: uppercase;
  color: var(--color-on-dark);
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}

.sv-band__action:hover,
.sv-band__action:focus-visible {
  background: var(--color-on-dark);
  color: var(--color-surface-dark);
}

/* -----------------------------------------------------------
   9 · Breakpoints — delete, never shrink
   ----------------------------------------------------------- */

/* The block restacks: the name takes its own line above its sentence rather
   than the sentence being squeezed into a third of a phone. Same clause
   pattern as /ajman §3.7 and the About wall. */
@media (max-width: 767px) {
  .sv-block {
    display: block;
  }

  .sv-block__name {
    margin-block-end: var(--gap-tight);
  }

  /* §3.7 — the CORNER RETURNS are deleted whole below 768px. At 375 the
     return sits 3.2px from the silhouette edge — two 1px verticals 3px
     apart are noise, not a volume. The silhouette and the datum carry the
     range at this width, which is what the range is. */
  .sv-rise--return {
    display: none;
  }
}

/* §3.7 — THE INTERNAL RULES ARE DELETED WHOLE below 1280px, all of them or
   none; a rule that cannot be read is worse than an absent one because it
   is still ink.

   🔴 THIS BREAKPOINT HAS NOW MOVED TWICE IN ONE SESSION, AND THAT IS THE
   WHOLE LESSON. It was 768, cut against the old container-width geometry.
   Full-bleed made it 1024. Then RATIO went 6.5 → 5.5, which widened the
   sheet from 1423 to 1685 user units — and a wider sheet rendered at the
   same CSS width is a SMALLER scale, so the pitch shrank and 1024 silently
   stopped clearing. A RULE MADE OF DERIVED GEOMETRY INHERITS THAT
   GEOMETRY'S BREAKPOINTS: change RATIO and this number must be re-derived,
   every time.

   Stated in the units the fault appears in: THE TIGHTEST PITCH BAND MUST
   RENDER ≥5px AGAINST A 1px STROKE. The tightest band is E3.3b's parking
   deck at 7u. Against sheet 1685 that needs a scale of 5/7 = 0.714, so the
   DERIVED MINIMUM IS 1204px. The breakpoint here is 1280 — the nearest
   standard width above it — so the rule is applied with 76px to spare and
   1204..1279 is deleted conservatively rather than kept marginally. That is
   a choice, not the derivation, and it is recorded as one: if a future
   RATIO moves the minimum ABOVE 1280 this number must move with it, and no
   assertion on the board will notice if it does not.

   Deleted, never shrunk: the stroke is non-scaling, so shrinking is not
   even available — the only options are legible or solid. */
@media (max-width: 1279px) {
  .sv-floors {
    display: none;
  }
}

/* -----------------------------------------------------------
   10 · RTL — the drawing flips (§3.7)
   ----------------------------------------------------------- */

/* IT IS AN ORDER, NOT A PLACE. The /ajman map must never flip, because a
   mirrored Emirate puts buildings where they are not; this drawing must,
   because it reads shortest-to-tallest along the reading direction and a
   range that runs against the reading direction is a range read backwards.

   The flip is on the drawing alone. Nothing in it is a place, and no text
   sits inside it to be mirrored. */
[dir="rtl"] .sv-drawing__svg {
  transform: scaleX(-1);
}

/* -----------------------------------------------------------
   11 · Reduced motion
   ----------------------------------------------------------- */

/* tokens.css clamps every transition to 0.01ms under reduce, so nothing
   here needs zeroing. What DOES need stating is the DELAY: a zero duration
   does not cancel a 95ms-per-elevation stagger, and the drawing would sit
   blank for its whole run under the setting that exists to prevent exactly
   that. js/services.js does not arm at all under reduce; this is the belt to
   that brace, and it also covers the reader whose setting changes after the
   script has already armed. */
@media (prefers-reduced-motion: reduce) {
  .sv-drawing--armed .sv-datum,
  .sv-drawing--armed .sv-rise,
  .sv-drawing--drawing .sv-datum,
  .sv-drawing--drawing .sv-rise {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    transition-delay: 0s;
  }

  .sv-drawing--armed .sv-floors,
  .sv-drawing--drawing .sv-floors {
    opacity: 1;
    transition-delay: 0s;
  }
}

/* -----------------------------------------------------------
   12 · The closing wall — Session XXXIV
   ----------------------------------------------------------- */

/* THE DESCENT. Row occupancy rises 5-6-7-8-9 down the band, so tile size
   falls monotonically. The arrangement is carried by the MARKUP (one div per
   row, N tiles in it) and the size by `flex: 1`, which is why no row needs a
   column count in CSS and why changing the beat is a one-line edit in
   build/lib/services.js rather than a CSS rewrite.

   Every tile is 4:3 because every `thumb` derivative is 4:3, so `object-fit`
   crops nothing at all here — the variety is row-to-row scale, not shape.
   That is deliberate: Freya rendered the aspect-varied bento and it BEHEADS
   buildings, because a landscape frame forced into a portrait cell flips the
   crop axis. A wall of buildings may not cut their tops off. */
/* THE GROUT IS 8px, NOT --gap-inline's 12. At nine to seventeen tiles per
   row the gap is spent up to sixteen times across the viewport, so 12px
   costs 192px of the widest row — more than one whole tile. The grout is a
   function of the COLUMN COUNT, which is why it is stated here rather than
   inherited from a token cut for two- and three-column work. */
/* The wall is one link. `display: block` because an inline anchor would
   give the rows an inline formatting context and put a text baseline gap
   under the last row — the classic image-in-anchor descender. The focus
   ring is on the ANCHOR, not the tiles: there is one tab stop here, and a
   keyboard reader must be able to see the thing they are about to follow. */
.sv-wall__route {
  display: block;
  text-decoration: none;
}

.sv-wall__route:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}

.sv-wall {
  --wall-grout: 8px;

  display: flex;
  flex-direction: column;
  gap: var(--wall-grout);
}

.sv-wall__row {
  display: flex;
  gap: var(--wall-grout);
}

/* The eyebrow, in the specialist list's own form (§5). It sits in the
   container while the wall runs full-bleed beneath it, so the label is
   aligned to the page's text column and the pictures are not. */
.sv-wall__lead {
  /* The lead rides its own .container so it lands on the page's axis; p21
     measures the INK against that axis at sixteen widths and went red when
     this sat on the section instead — 80px against 120 at 1200. A label for
     a full-bleed band still belongs to the text column. */
  font-size: var(--fs-caption);
  letter-spacing: var(--ls-caption);
  text-transform: uppercase;
  color: var(--ink-dim);
  margin: 0 0 var(--gap-block);
}

/* `min-width: 0` is load-bearing: a flex item's default `min-width: auto` is
   its CONTENT width, so without this the 9-tile row refuses to shrink below
   nine intrinsic image widths and the row overflows the viewport instead of
   dividing it. The failure is invisible at 1440 and total at 375. */
.sv-wall__tile {
  flex: 1 1 0;
  min-width: 0;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  height: auto;
  width: 100%;
}

/* BELOW 768 THE BEAT IS ABANDONED, NOT SHRUNK — the same doctrine as §3.7's
   floor lines. Carried down, the 9-tile row renders ~40px tiles at 375 and a
   descent nobody can read; `display: contents` dissolves the row divs so all
   35 tiles become direct children of one wrapping flex container and the band
   re-forms as a uniform 4-up mosaic. One DOM, two arrangements, no script.

   `flex-grow: 0` matters on the last line: with `1` the remainder tiles
   stretch to fill the row and the wall ends on three enormous pictures — the
   fault that killed the uniform-frieze option, arriving at the one edge that
   meets the black band. */
@media (max-width: 767px) {
  .sv-wall {
    flex-direction: row;
    flex-wrap: wrap;
  }

  .sv-wall__row {
    display: contents;
  }

  .sv-wall__tile {
    flex: 0 0 calc((100% - var(--wall-grout) * 5) / 6);
  }
}

/* AND ONE STEP FURTHER DOWN. At 375 a 4-up tile computes to 85px — below
   the size at which a building is a building rather than a texture. Three
   up gives 113px. The breakpoint is 480 rather than a device width because
   the number that matters is the TILE, and the tile is a division of the
   viewport: 4-up crosses 100px at ~436px of viewport, so 480 clears it with
   room. Derived, like §3.7's 1280 — and it must be re-derived if the
   column count or the gap changes. */
@media (max-width: 479px) {
  .sv-wall__tile {
    flex: 0 0 calc((100% - var(--wall-grout) * 3) / 4);
  }
}
