/* ============================================================================
   THE DUMPSTER GUY — MOBILE COUNTY
   Direction B: THE SPEC SHEET.

   THESIS. The container is drawn the way its manufacturer draws it, so the size
   question is answered by the drawing rather than by a paragraph. This refuses
   the category default (hero photo + quote form, blue and orange) AND its
   predictable opposite (the minimal monochrome editorial page), which is what
   this file replaced and why that build read as generic.

   OWN-WORLD. A white sheet on a real 32px drafting grid. Black line-work at
   three weights. Condensed Archivo for display, a fixed-width voice for every
   number, label and annotation. Color exists only inside a photograph.

   THE RULE THAT GOVERNS EVERYTHING: NOTHING IS ENCLOSED. No card, no border
   box, no radius, no shadow. Rank is carried by weight, scale and rule-work
   alone. The only rectangles on the page are drafting instruments: the drawing
   frame and the title block.
   ============================================================================ */

@font-face {
  font-family: 'Archivo';
  src: url('/assets/fonts/archivo-latin.woff2') format('woff2');
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
}

:root {
  --ink:      #000000;   /* line-work, display type, the primary action */
  --paper:    #ffffff;
  --mid:      #5a5a5a;   /* annotation and secondary text, 7.0:1 on paper */
  --rule:     #cfcfcf;   /* hairlines */
  /* Secondary text ON THE BLACK GROUND. --mid is tuned for paper (7.0:1) and
     measures 3.04:1 on black, a real AA failure in the footer and the masthead.
     This measures 7.46:1. Opacity was doing the same job worse: an opacity stack
     on black came out at 1.1:1. */
  --mid-dark: #9a9a9a;

  /* THE ONE COLOR, AND IT HAS ONE JOB: the primary action.
     Steve, 2026-08-29: "the book online button needs to be another color. blue
     was used on the current sites." Both reference sites do the same thing, and
     a booking button that looks like every other black element is a booking
     button nobody finds.
     This is the blue the current sites already run (#046bd2), kept for
     continuity rather than reinvented. White on it measures 5.20:1, past AA;
     the hover measures 6.77:1. It appears on the Book online control and
     NOWHERE else, so the discipline rule is unchanged: the strongest signal on
     the page is reserved for the one action. */
  --go:       #046bd2;
  --go-dark:  #0a58b8;
  --stage:    #fafafa;   /* the drawing stage, a shade off the sheet */

  --measure:  66ch;
  --gutter:   20px;
  --shell:    1240px;

  --sans: 'Archivo', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --mono: ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;

  /* THE LABEL SIZE, AND IT IS A TOKEN BECAUSE IT WAS NOT ONE. `font-size: var(--label)`
     was written out TWELVE separate times — the sheet strip, footer headings,
     spec keys, figcaptions, the drawing's dimensions, review attributions, the
     legal line. Raising the annotation voice off the floor changed exactly one
     of them and left the other eleven at 11px, which is how a value that is
     really one decision ends up impossible to change. */
  /* THE ANNOTATION VOICE, AND IT IS NOT SMALL PRINT. Fifty-six elements on the home
     page at 390px take their size from this token: every spec key, caption, footer
     heading, the facts strip, the drawing's dimensions, the guide's fleet panel. A
     token that dresses more than half the interface is the interface, and 12px was
     the floor of a range meant for the occasional caption.

     IT IS ALSO SET IN MONO, UPPERCASED AND LETTERSPACED, all three of which cost
     legibility at a given pixel size: the fixed-width stack has a smaller x-height
     than Archivo, capitals remove the ascender and descender shapes words are
     recognized by, and tracking pushes the letters apart. Nominal 12px in that
     treatment reads a size smaller than 12px of body copy sitting beside it, which
     is why the page looked like 5px type to Steve while a detector tuned to an
     11px floor passed it.

     So the annotation voice sits ABOVE the body minimum's neighborhood, not below
     it, and it grows with the viewport like everything else here. */
  --label: 13px;
}

@media (min-width: 760px) { :root { --label: 13.5px; } }
@media (min-width: 1080px) { :root { --label: 14px; } }

*, *::before, *::after { box-sizing: border-box; }
html, body { overflow-x: hidden; }

html {
  -webkit-text-size-adjust: 100%;
  scrollbar-color: var(--mid) var(--grid);
  scroll-padding-top: 12px;
}

body {
  margin: 0;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  background-color: var(--paper);
}


::selection { background: var(--ink); color: var(--paper); }
:focus-visible { outline: 2.5px solid var(--ink); outline-offset: 3px; }
:where(a) { color: inherit; }
img, svg, video { max-width: 100%; height: auto; display: block; }
figure { margin: 0; }   /* the UA ships margin:1em 40px and it insets every bleed */

/* Every number on this site is set in the same fixed-width voice. */
.num, .price, .dim, .tel { font-variant-numeric: tabular-nums; }

.shell { width: 100%; max-width: var(--shell); margin-inline: auto; padding-inline: var(--gutter); }

/* ------------------------------------------------------------------ type --
   Two voices. Condensed Archivo carries every heading, at the width that
   matches the logo's own lettering. A fixed-width face carries every label,
   dimension, price and annotation, which is what a drawing does.             */
h1, h2, .display {
  font-family: var(--sans);
  font-stretch: 64%;
  font-weight: 900;
  letter-spacing: -.042em;
  /* TRACKING EATS THE WORD SPACE. -.042em is applied to every gap including the
     one between words, which measured 0.07em across every heading on the page —
     against 0.19em in the body copy on the same page, and 0.70em on the 11px
     labels. At that gap "DUMPSTER RENTALS" reads as one word. This gives the
     space back without loosening the letterfit the condensed voice depends on. */
  word-spacing: .16em;
  line-height: .9;
  margin: 0;
  text-transform: uppercase;
  text-wrap: balance;
}
h1 { font-size: clamp(40px, 11.2vw, 116px); line-height: .86; }
h2 { font-size: clamp(28px, 7.4vw, 60px); }
h3 {
  font-family: var(--sans);
  font-stretch: 84%;
  font-weight: 800;
  font-size: 19px;
  letter-spacing: -.015em;
  line-height: 1.2;
  margin: 0;
}

/* The annotation voice. Every label on the sheet uses this and only this. */
/* THE ANNOTATION VOICE. 11px is the floor for functional text; at 10.5 it failed
   on high-DPI and small viewports. Tracking eased from .14em to .1em too: wide
   tracking is for short labels, and some of these runs reach 120 characters. */
/* 11px WAS DOING ALL THE LABEL WORK ON THIS SITE, and it is under the readable
   floor. It set the sheet strip, every footer column heading, the spec keys, the
   figcaptions, the drawing's dimensions and the review attributions.

   IT TAKES THE --label TOKEN LIKE EVERYTHING ELSE IN THIS VOICE. Hardcoded at 12px
   it stayed at 12 while the token moved, so the masthead's own strip, the page
   eyebrows and the captions were the last things on the page still set at the old
   floor. A voice with two sources of truth has one of them wrong. */
.anno, .sheet, .cap, .band__eyebrow, .terms a {
  font-family: var(--mono);
  font-size: var(--label);
  letter-spacing: .04em;   /* wide tracking is for short labels, below */
  color: var(--mid);
}
/* UPPERCASE IS FOR SHORT LABELS ONLY. Applied to the whole annotation voice it
   put 120-character runs in caps, which strips the word shapes people read by.
   The sheet strip and the captions keep the voice and lose the caps. */
.band__eyebrow, .facts span, .scale__label span, .nav a {
  text-transform: uppercase;
  letter-spacing: .1em;
  /* Two of these were set at 0.85 and 0.90 — leading TIGHTER than the type is
     tall. They are single lines today, so nothing collides; the first wrap, or
     the first reader who raises their default font size, overlaps immediately.
     Sub-1.0 leading belongs to the 60px and 116px display sizes only. */
  line-height: 1.25;
}
/* On the black ground the annotation voice needs the dark-ground color: --mid
   measures 3.04:1 there. */
.foot .shell > p:last-child {
  /* A SENTENCE, not a label: it keeps the annotation voice and gives the tracking back. */
  font-family: var(--mono); font-size: var(--label); letter-spacing: .02em; color: var(--mid-dark);
}

p, li { max-width: var(--measure); }

/* ------------------------------------------------------------ line breaks --
   "COUNTY WRAPS." Steve, and he was right, and it was not one paragraph.
   Measured across the home page, six blocks ended on a line worth less than a
   quarter of their widest: "say in Mobile County:" was TEN PER CENT of the line
   above it — three words alone under a full-width paragraph, splitting a proper
   noun across the break.

   `pretty` is the browser's own orphan control: it reflows the last few lines so
   a block cannot end on a stub. `balance` evens a short block's lines instead,
   which is what a heading or a deck wants — they are two different jobs and the
   wrong one on a long paragraph is expensive to render for no gain, so the split
   is by role, not applied to everything. */
p, li, dd, figcaption, .lede, .spec dd { text-wrap: pretty; }
h1, h2, h3, h4, h5, h6, .cta__line, .hero__offer, .facts__in span { text-wrap: balance; }
/* A place name is one word as far as line breaking is concerned. */
.nowrap { white-space: nowrap; }
p { margin: 0 0 1em; }

/* THE DECK. A page builder's "heading + subheading" arrives as an h2 followed
   immediately by an h3. Set as two headings they collide. It stays an h3 in the
   markup, because demoting it would rewrite the outline on 41 pages and only
   the design may move. It just stops looking like a second title. */
.deck {
  font-family: var(--sans);
  font-stretch: 100%;
  font-weight: 500;
  /* 17px over a 16px paragraph is a ratio of 1.06 — not a step, and with the
     gap missing it read as the paragraph's first line rather than as a
     subheading. The gap is restored below; this is the size half. */
  font-size: clamp(19px, 2vw, 24px);
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  color: var(--mid);
  max-width: 48ch;
  /* THE GAP BELOW BELONGS TO THE DECK. It used to come from `.band > .shell >
     .prose { margin-top: 20px }`, which does not match a prose nested inside a
     column — so in the paired row the deck sat 0px above its paragraph and read
     as that paragraph's first line. Adjacent margins collapse, so this changes
     nothing where the prose rule already applies. */
  margin-top: 14px;
  margin-bottom: 18px;
}

/* ----------------------------------------------------------------- prose -- */
.band > .shell > .prose { margin-top: 20px; }
.prose > * + * { margin-top: .9em; }
.prose > * + :is(h2, h3, h4) { margin-top: 2em; }
.prose :is(h2, h3, h4) + * { margin-top: .6em; }
.prose ul, .prose ol { padding-left: 1.1em; margin-block: .8em; }
.prose li + li { margin-top: .35em; }
/* The opening paragraph carries seven links. At full contrast it reads as
   stripes rather than as a sentence, so the underline sits in the rule gray
   and hover restores it. The links are content and stay. */
.prose a {
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-decoration-thickness: 1px;
  text-underline-offset: .2em;
  transition: text-decoration-color 120ms linear;
}
.prose a:hover, .prose a:focus-visible { text-decoration-color: var(--ink); text-decoration-thickness: 2px; }
/* A heading that happens to be a link is still a heading. The prose underline
   was drawing a rule under every post title on the guide hub and the archives.
   Scoped to the BAND, not to .prose: the renderer emits section titles as a
   direct child of the shell, so a .prose-scoped rule never reached them. */
.band :is(h1, h2, h3, h4) a { text-decoration: none; }
.band :is(h1, h2, h3, h4) a:hover { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: .1em; }
/* ------------------------------------------------------------- THE PLATE --
   A photograph on a drawing sheet is a REGISTERED PLATE, and this is the one
   image treatment on the site. Every content photograph is wrapped in
   figure.plate by the renderer, so a single selector reaches all 225 of them.
   Before this they were bare image elements, which carry no pseudo-elements and
   so could hold no treatment at all: raw rectangles on a page whose premise is a
   drawing sheet.

   The mark is a crop mark, in the sheet's own line weight, sitting just outside
   the image the way registration marks sit outside a printed plate. TWO opposite
   corners, not four: four is a frame, two is a reference.
   ------------------------------------------------------------------------- */
.prose img { width: 100%; height: auto; }

.plate { position: relative; display: block; margin: 26px 0; max-width: 620px; }
/* Two media elements that were adjacent in the client's markup, stacked as one
   grid child rather than fighting for the same cell. */
/* Present to a screen reader and to the parity checker, absent to the eye. Used for
   punctuation that has lost the sentence it belonged to. */
.vh {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.mediastack { display: grid; gap: 22px; }
.mediastack > .plate { margin: 0; max-width: none; }
.mediastack > iframe { margin: 0; max-width: none; }

/* The Google Business Profile embed. Harvested as a 550x700 portrait box, which
   suits no column on this page; sized here and given the sheet's own hairline. */
/* TALLER, ASKED FOR. It was 4/3, which on a 514px column gave a 385px map — enough to
   see the pin and almost nothing around it, and a map you cannot read the surrounding
   streets on is a picture of a map. Square is 33% taller and is what a location embed is
   normally cut to; the harvested original was 550x700, so this moves back toward its own
   proportion rather than away from it. */
.prose iframe {
  display: block; width: 100%; max-width: 620px;
  aspect-ratio: 1 / 1; border: 0;
  /* --surface DOES NOT EXIST. The tokens are --paper, --stage, --rule, --ink, --mid and --go, so
     this resolved to nothing and an embed that fails, or is blocked by a privacy extension, left
     a 514x514 transparent hole where the service-area map should be. --stage is the site's own
     off-white, and the hairline makes an unloaded map read as a plate that has not arrived. */
  margin: 26px 0; background: var(--stage); border: 1px solid var(--rule);
}
.plate img { width: 100%; height: auto; display: block; }
.plate > a { display: block; }

.prose .plate::before,
.prose .plate::after {
  content: '';
  position: absolute;
  width: 15px; height: 15px;
  pointer-events: none;
  /* The marks OPEN by moving outward, not by growing. Animating width and height
     is layout thrash, and scaling would thicken the 2px rule as it went. A
     translate keeps the line weight exact and costs no layout. */
  transition: transform 170ms cubic-bezier(.2, .8, .2, 1);
}
.prose .plate::before {
  top: -5px; left: -5px;
  border-top: 2px solid var(--ink); border-left: 2px solid var(--ink);
}
.prose .plate::after {
  bottom: -5px; right: -5px;
  border-bottom: 2px solid var(--ink); border-right: 2px solid var(--ink);
}
/* A linked plate is the only one that moves: the marks open. The affordance is
   mechanical rather than a color change, because blue stays reserved for the
   primary action. */
.prose .plate:has(> a):hover::before,
.prose .plate:has(> a):focus-within::before { transform: translate(-5px, -5px); }
.prose .plate:has(> a):hover::after,
.prose .plate:has(> a):focus-within::after { transform: translate(5px, 5px); }

/* The caption is the plate's label: annotation voice, under a hairline. */
.prose figcaption {
  font-family: var(--mono); font-size: var(--label); letter-spacing: .04em;
  color: var(--mid);
  margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--rule);
}
.prose--lede > p:first-of-type { font-size: 18px; line-height: 1.5; max-width: 54ch; }
.lede { font-size: 17px; color: var(--mid); max-width: 52ch; }

/* --------------------------------------------------------------- masthead --
   Wordmark and number on one baseline over a 2px rule, then the sheet header:
   the metadata strip every drawing carries.                                   */
/* THE MASTHEAD IS A BLACK FIELD, because the logo is one.
   logo.png is a black square with a white wordmark and no alpha channel. On a
   white ground it can only read as a black box. The brand's own answer is the
   field it was drawn on, and the footer is already the same field, so the sheet
   sits between two brand bands. */
.masthead { background: var(--paper); }
/* The brand's own field, full bleed. The footer is the same field, so the sheet
   sits between two black brand bands. */
.masthead__field { background: var(--ink); color: var(--paper); }
.masthead__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; min-height: 82px;
}
/* logo.png is a 300x300 square in which the wordmark occupies only the middle band;
   the rest is black padding. Shown whole at bar height the wordmark is tiny, so the
   window crops that padding away. The asset is unmodified and the cropped area is
   pure black on a pure black bar, so nothing of the mark is lost or altered. */
/* flex: none, OR THE WORDMARK IS CRUSHED ON A SMALL PHONE. The masthead bar is a flex row and
   this anchor was shrinkable, so at 320px it was squeezed to 43px wide while `img { max-width:
   100% }` clamped the picture with it — and the height below is FORCED, so the logo rendered
   43x115: a 62% horizontal squash of a 300x300 source. Measured 62.3% at 320, 27.5% at 360,
   14.4% at 375, clean only from 400 up. The letterforms were visibly compressed on every phone
   narrower than an iPhone. */
.masthead__logo { display: block; height: 54px; overflow: hidden; flex: none; }
.masthead__logo img { height: 155px; width: auto; margin-top: -50px; }
/* Asked for bigger. 76px was smaller than the heading beside it, which made the one
   place the wordmark actually renders as an image the quietest thing in the column.
   The source is 300x300, so 132 is still well inside it and stays crisp on a 2x screen. */
.foot__logo { width: 132px; height: auto; margin-bottom: 18px; }
@media (min-width: 760px) { .foot__logo { width: 156px; } }
.masthead__tel {
  font-family: var(--sans); font-stretch: 74%; font-weight: 800;
  font-size: 21px; letter-spacing: -.02em; text-decoration: none;
  color: var(--paper);
  display: inline-flex; align-items: center; min-height: 44px;
}
.masthead__tel:hover { text-decoration: underline; text-decoration-thickness: 2px; }

/* `padding: 10px 0` is a SHORTHAND and it overwrote .shell's padding-inline, so
   this was the only element on the page starting at x=0 while the h1 above it
   started at x=20. It also scrolls with no fade, unlike the nav, so the two
   facts past the right edge — including the only price in the header — had
   nothing at all saying they were there. */
.sheet {
  display: flex; gap: 22px; align-items: center;
  padding: 10px var(--gutter); border-bottom: 1px solid var(--rule);
  overflow-x: auto; white-space: nowrap; scrollbar-width: none;
  -webkit-mask-image: linear-gradient(90deg, #000 92%, transparent);
          mask-image: linear-gradient(90deg, #000 92%, transparent);
}
@media (min-width: 680px) { .sheet { -webkit-mask-image: none; mask-image: none; } }
.sheet::-webkit-scrollbar { display: none; }

/* The foot of the header: wayfinding on the left, the two conversions on the right. */
.headbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; border-bottom: 1px solid var(--rule);
}
/* The two conversions live in the masthead row, on the brand's own black field,
   so they cost no vertical space at any width. */
.headbar__actions { display: flex; gap: 8px; flex: none; }
.masthead__bar .headbar__actions { margin-left: auto; }
/* DECLARED TWICE, AND INERT BOTH TIMES. `.btn--ondark` and `.btn` are both (0,1,0) and `.btn` is
   written later in the file, so it won every property: the modifier's three declarations never
   applied, at any width, on any page — the "on dark" button rendered as the ordinary white one.
   The sibling `.btn--primary` works only because it happens to sit AFTER `.btn`. Kept here beside
   its siblings and given the one class of extra weight that makes source order stop deciding it. */
.btn.btn--ondark { background: transparent; border-color: var(--paper); color: var(--paper); }
.btn.btn--ondark:hover { background: var(--paper); color: var(--ink); }
@media (max-width: 619px) {
  /* 350px of shell has to carry a wordmark and two controls. The padding gives
     way before the labels do — shortening those would be rewriting the client's
     own words for the sake of a layout. */
  .masthead__bar { gap: 10px; min-height: 66px; }
  .masthead__logo { height: 40px; }
  .masthead__logo img { height: 115px; margin-top: -37px; }
  .masthead__bar .headbar__actions { gap: 6px; }
  /* 44px stays the floor even in a compressed header; height costs nothing here. */
  .masthead__bar .btn--sm { min-height: 44px; padding: 0 10px; font-size: 14px; letter-spacing: 0; }
}
.btn--sm {
  min-height: 44px; padding: 0 16px; font-size: 14px; border-width: 1.5px;
}
.sheet > span { flex: none; }
.sheet b { color: var(--ink); font-weight: 400; }

/* The nav is a sheet index, not a menu bar. */
/* WRAPS RATHER THAN SCROLLS, now that there are four things in it instead of seven.
   "DUMPSTER RENTALS" is 178px of a 335px phone row on its own, so keeping the old single
   scrolling row left Demolition, About and Contact half past the right edge — the same
   problem the scroller was added to solve, moved along by one element. Two rows on a phone,
   one row from 760 up, and nothing hidden at either width. */
.nav { min-width: 0; display: flex; align-items: center; flex-wrap: wrap; column-gap: 22px; }

/* THE NAV WAS 3 PIXELS WIDE ON A PHONE. Measured at 375: .headbar is a flex row
   holding the nav and the two conversion buttons; the buttons are `flex: none`
   and take about 250px of a 335px shell, and `min-width: 0` let the nav absorb
   the entire shortfall. It collapsed to 3px with eight links inside it, four of
   them rendering past the right edge of the phone, and there is no menu button
   on this site to reach them with. A visitor could not get to Prices or FAQs.

   No hamburger: eight links fit a scrolling row and a hidden menu is one more
   tap between a visitor and the two pages that sell. They stack instead — the
   conversions on their own full-width row, the nav on its own below, where it
   has the whole shell to scroll inside.

   AND IT WRAPS UNTIL 1080, NOT 760. The nav row needs 646px and the two buttons
   take about 250 more; with gutters that is ~960px before anything fits side by
   side. Keyed at 760 the row stayed horizontal at 768 and the buttons sat ON TOP
   of the links: the design detector measured "Demolition" as 100% covered by the
   phone button. My own nav check missed it because it measured the scroll
   container, not what was painted over what. */
@media (max-width: 1079px) {
  /* The nav has the row to itself now that the buttons sit in the masthead. */
  .headbar { flex-wrap: wrap; gap: 0; }
  .nav { flex: 1 0 100%; }
}
.nav__rest {
  display: flex; gap: 22px; margin: 0; padding: 9px 0; list-style: none;
  overflow-x: auto; scrollbar-width: none;
}
.nav__rest::-webkit-scrollbar { display: none; }
/* THE FADE ONLY BELONGS WHERE THE ROW SCROLLS — and 759px was the wrong width to
   ask at. The row needs 646px and does not fit until 1080, so between 760 and
   1079 it scrolled with the fade switched OFF and clipped hard mid-word: at 768
   "GUIDE" rendered as "GUID" against a straight edge, and at 1024 "Contact" was
   simply gone. Keyed to the width where the row actually stops fitting. */
@media (max-width: 1079px) {
  .nav__rest {
    -webkit-mask-image: linear-gradient(90deg, #000 90%, transparent);
            mask-image: linear-gradient(90deg, #000 90%, transparent);
  }
}
.nav li { max-width: none; }
/* The primary nav is a navigation, not a footnote. At the annotation voice's
   10.5px it read as fine print on the one row a visitor uses to get around. */
.nav a {
  display: inline-flex; align-items: center; min-height: 46px;
  font-family: var(--mono); font-size: 15px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--mid); text-decoration: none; white-space: nowrap;
}
.nav a:hover { color: var(--ink); }
.nav a[aria-current='page'] { color: var(--ink); text-decoration: underline; text-underline-offset: .4em; }

/* ----------------------------------------------- the grouped nav heading --
   Four pages about the same subject, read at different depths, under one label.
   A <details>, so it opens by tap, click and keyboard with no script, and the
   browser owns aria-expanded. It sits OUTSIDE .nav__rest on purpose: that list
   is `overflow-x: auto` on a phone, and an absolutely positioned panel cannot
   escape a scroll container, so a flyout inside it would be clipped at the row's
   edge. As a sibling it is positioned against .nav, which clips nothing. */
.navgrp { flex: none; position: relative; }
.navgrp__label {
  display: inline-flex; align-items: center; gap: 8px; min-height: 46px;
  font-family: var(--mono); font-size: 15px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--mid); white-space: nowrap; cursor: pointer;
  list-style: none;                    /* Firefox's default marker */
}
.navgrp__label::-webkit-details-marker { display: none; }
.navgrp__label:hover { color: var(--ink); }
.navgrp__label[aria-current='true'] { color: var(--ink); }
/* Our own marker, in the drawing voice: a caret that turns rather than a triangle
   the browser drew. Squared off, because nothing else on this site is rounded. */
.navgrp__label::after {
  content: ""; width: 7px; height: 7px; flex: none;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg); transition: transform 140ms ease;
}
.navgrp[open] > .navgrp__label::after { transform: translateY(1px) rotate(-135deg); }
.navgrp[open] > .navgrp__label { color: var(--ink); }

/* The panel hangs off its own label at every width. Anchored to the label rather than to the
   shell, so it needs no viewport arithmetic and cannot be clipped by the row wrapping under it. */
.navgrp__menu {
  position: absolute; z-index: 30; left: -16px; top: calc(100% - 4px); min-width: 232px;
  display: flex; flex-direction: column; gap: 0; margin: 0; padding: 4px 0;
  list-style: none; background: var(--paper);
  border: 1.5px solid var(--ink); border-top-width: 2px;
}
.navgrp__menu li + li { border-top: 1px solid var(--rule); }
.navgrp__menu a {
  display: flex; align-items: center; min-height: 48px; padding: 0 16px;
  font-family: var(--mono); font-size: 15px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--mid); text-decoration: none;
}
.navgrp__menu a:hover { color: var(--ink); background: var(--wash, #f4f4f4); }
.navgrp__menu a[aria-current='page'] { color: var(--ink); text-decoration: none; }
/* The current page gets a struck edge rather than an underline: in a stacked list
   an underline reads as a divider between two rows. */
.navgrp__menu a[aria-current='page']::before {
  content: ""; width: 3px; align-self: stretch; margin: 8px 12px 8px -8px;
  background: var(--ink);
}

@media (max-width: 759px) {
  /* The three remaining links take the row under the heading, with the whole shell to sit in. */
  .nav__rest { flex: 1 1 100%; padding-top: 0; }
}

/* ------------------------------------------------------------ SALES HERO --
   The offer, the promise and both conversions in the first viewport, over the
   owner's own plate. The previous version stacked a photo, then a category
   headline, then a 1,757-byte paragraph, and only then the buttons.
   The scrim is what makes white display type survive a plate that contains
   both a black container and a white garage door. */
/* CLIPS. The drift animation scales the plate past 1, so the image overflows the
   hero box by a few pixels top and bottom while the scrim covers exactly the box.
   Those few pixels showed as un-darkened strips of photograph above and below the
   hero. Without a clip, any scale on the plate leaks. */
.hero {
  position: relative; isolation: isolate; overflow: hidden;
  /* A ground color under the plate. The headline is white; if the photograph
     fails to load there is otherwise nothing behind it but paper. */
  background: var(--ink);
}
.hero__bg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: -2;
  /* THE PHOTO HAS THE OWNER'S LOGO PLATE BAKED INTO ITS BOTTOM-LEFT CORNER.
     A phone crops ~200px horizontally from a 3:2 source in a near-square frame,
     which left the right edge of that plate stranded at the screen edge as a
     sliced black box reading "ER". A sliver of a logo looks broken; the whole
     thing is redundant under a masthead that already carries it. So the crop
     puts it out of frame at both ends: shifted right on a phone, where the crop
     is horizontal, and shifted up on desktop, where the crop is vertical.
     The trucks sit in the middle band and stay in view either way. */
  object-position: 68% 55%;
}
.hero::after {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(0,0,0,.58) 0%, rgba(0,0,0,.42) 34%, rgba(0,0,0,.86) 100%);
}
.hero__inner { padding-block: 54px 34px; color: var(--paper); }
.hero h1 { color: var(--paper); }
.hero__eyebrow {
  font-family: var(--mono); font-size: var(--label); letter-spacing: .1em;
  text-transform: uppercase; color: #e2e2e2; margin: 0 0 16px;
}
.hero__offer {
  font-family: var(--sans); font-stretch: 92%; font-weight: 500;
  font-size: 17px; line-height: 1.4; color: var(--paper);
  max-width: 40ch; margin: 18px 0 0;
}
.hero__offer .price { font-weight: 800; }
.hero .actions { margin-top: 24px; }
/* Full width and stacked on a phone. Sized to their own text the two buttons came
   out ragged, and the primary action is worth the whole column. */
.hero .actions .btn { flex: 1 1 100%; }
/* The territory alone on a phone; the hours are already stated in the offer line
   below and in the facts strip, and the pair wrapped to two lines. */
.hero__eyebrow .hero__hours { display: none; }
/* A ruled button on a photograph has to carry its own edge. The primary keeps
   its blue there too, so the same control looks the same everywhere. */
/* The duplicate of the block above, removed. It was byte-identical and equally inert. */

/* --------------------------------------------------------- the facts strip --
   Trust immediately after the offer, the way both reference sites do it.
   Facts only, every one from franchise data. No invented claim.              */
.facts { border-bottom: 1px solid var(--rule); background: var(--paper); }
.facts__in {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: 0;
}
.facts__in > div {
  padding: 16px 0; border-bottom: 1px solid var(--rule);
}
.facts__in > div:nth-child(odd) { padding-right: 16px; }
.facts__in > div:nth-child(even) { padding-left: 16px; border-left: 1px solid var(--rule); }
.facts__in > div:nth-last-child(-n+2) { border-bottom: 0; }
.facts b {
  display: block; font-family: var(--sans); font-stretch: 78%; font-weight: 800;
  font-size: 17px; letter-spacing: -.01em; line-height: 1.15;
}
.facts span {
  display: block; font-family: var(--mono); font-size: var(--label); letter-spacing: .1em;
  text-transform: uppercase; color: var(--mid); margin-top: 5px;
}

/* ------------------------------------------------------- BAND ORDERING --
   THE ONE PLACE BANDS ARE REORDERED, and it is one place because having two was
   a bug: an early rule hoisted the index with `order: -1` while a later wrapper
   pushed the same band down, so it kept turning up somewhere new and I kept
   fixing it in whichever mechanism I had open. Anything that moves a band goes
   in this block or it does not happen.

   Document order never changes — parity reads the DOM and the client's sequence
   is frozen. This moves pixels only.

   THE HONEST COST: a screen reader and the tab key follow document order, so
   they meet the index where the client published it rather than where it now
   appears. For a list of jump links that is a small mismatch and the right
   trade; for body copy it would not be. */
main { display: flex; flex-direction: column; }
main > *            { order: var(--o, 500); }
main > .band--title { order: 0; }   /* a page's own title band, above everything (#953) */
main > .hero        { order: 1; }
main > .facts       { order: 2; }
main > .band--first { order: 3; }
main > .band--index { order: 4; }   /* second section on every page */

/* --------------------------------------------------------------- SURFACE --
   THE PAGE HAD NO RHYTHM AND THIS IS WHY. Measured on the home page: bands 02
   through 13 were TWELVE CONSECUTIVE IDENTICAL CONTAINERS — transparent ground,
   1240px, one column, 32px of padding, a 1px rule between. That is the entire
   structural vocabulary the page had, so over 8,156px the eye had nothing to
   hold. Every treatment I built lived INSIDE those identical boxes, which is
   why fixing them one at a time never changed how the page read.

   A band flips by REDEFINING --ink AND --paper INSIDE ITSELF. Every rule in this
   file already draws its line-work from those two tokens, so one class inverts
   a whole band — the drawing, the spec rules, the buttons, the hairlines —
   without a single per-element override. That is what makes this a system
   rather than another patch.

   It does not break the governing rule. NOTHING IS ENCLOSED still holds: a
   full-bleed surface is a ground, not a box. The hero and the footer are
   already this exact ink field, so the page reads as one sheet with plates
   struck on it, not as a stack of cards.                                     */
.band--ink {
  --ink:   #ffffff;
  --paper: #000000;
  --mid:   var(--mid-dark);
  --rule:  #3a3a3a;
  background: #000; color: #fff;
  border-top: 0 !important;
}
.band--ink + .band { border-top: 0; }

/* ------------------------------------------------------------------ band --
   One of the client's sections, given a boundary. A 2px rule opens a major
   band, a hairline separates minor ones. No fills, no cards.                  */
.band { padding-block: 32px; }
.band + .band { border-top: 1px solid var(--rule); }
.band--open { border-top: 0 !important; }
.band--title { padding-block: 26px 30px; }
.band--sizes { border-top: 0 !important; }
/* A HEADING BELONGS TO THE THING BELOW IT. "Dumpster Sizes & Prices" sat in its own band
   and the cards in the next one, each contributing its own block padding, so on the desktop
   there were 108px between the heading and the first photograph: 54 of trailing padding and
   54 of leading. A heading that far from what it heads reads as the end of the section above.

   Matched by ADJACENCY, not by a class. The rule this replaces was
   `.band.band--sizes-intro + .band--sizes`, and no template has ever emitted
   `band--sizes-intro`, so it had been dead the whole time while looking exactly like a
   working rule. `:has(+ …)` cannot fall out of step with the markup that way. */
.band:has(+ .band--sizes) { padding-bottom: 0; }
.band:has(+ .band--sizes) + .band--sizes { padding-top: 18px; }
@media (min-width: 760px) {
  .band:has(+ .band--sizes) + .band--sizes { padding-top: 28px; }
}

/* THE PLATE. The one drawing on the page, struck on ink, with the heading that
   introduces it. This is the page's center of gravity and the first change of
   surface since the hero, so it runs deeper than a content band. */
.band--plate { padding-block: 54px 46px; }
.band--plate .prose { margin-bottom: 4px; }
.band--plate .prose p { color: var(--mid-dark); }
/* THE DRAWING RUNS FULL WIDTH HERE AND MUST NOT BE PUT IN A SIDE COLUMN.
   Above 1080px the drawing is already the header row of the card grid: four
   silhouettes on the same four columns as the four cards, each one directly
   above the container it describes. I tried to float it in a 440px column beside
   the heading and broke exactly that — the rows laid out as columns and ran off
   the right edge of the page. The cards now sit on paper immediately below, on
   the same columns, so the alignment survives the change of surface and the
   drawing reads as the grid's own header. */
.band--plate > .shell > .scale { margin-bottom: 0; }

/* THE SECOND PLATE: the customers' own words, on ink.
   The tail of the page was four flat bands in a row — testimonials, contact,
   how it works, the closing call — and it died there. Proof is the moment worth
   striking, so it takes the page's other ink field. Hero, drawing, proof: the
   page now changes surface three times, at its opening, its center and before
   its close, instead of running 6,000px of unbroken white. */
.band--proof { padding-block: 54px; }

/* --------------------------------------------------------------- segments --
   Residential and Commercial: the two halves of the same business, which the
   page was rendering as unrelated bands. Measured before this: 383px of bare
   copy followed by 752px with a photograph split two ways by the shape rules.

   They mirror. A carries the photograph the size band hands it, on the LEFT;
   B keeps its own photograph on the right, where SHAPE 1 already puts it. Same
   ratio, same 56px gutter, opposite hands, so the two read across as a pair.

   Below 900px both stack, photograph first, which is the document order.       */
.band--segment, .band--medialead { padding-block: 48px; }
.segment__media { margin: 0 0 20px; }
/* 760, NOT 900, AND THAT GAP WAS THE "MESS ON TABLET".
   The size grid goes two-up at 760, the shape rules key at 760, the contact pair
   keys at 760 — and these did not. Between 760 and 899 the page ran half on one
   system and half on the other: photographs and the map dropped to full width
   and stacked over their copy while the cards beside them stayed in columns.
   One tablet breakpoint, and it is 760. */
@media (min-width: 760px) {
  .band--medialead > .shell {
    display: grid; grid-template-columns: 1fr 1.3fr; column-gap: 30px; align-items: start;
  }
  /* The photograph spans the whole band; the copy stacks in the second column. */
  .band--medialead > .shell > .segment__media {
    grid-column: 1; grid-row: 1 / span 99; margin: 0; align-self: start;
  }
  .band--medialead > .shell > :not(.segment__media) { grid-column: 2; }
  /* Above SHAPE 2, which caps a lone figure at 620px and outscores a two-class
     selector. The same trap as the closing plate, so the same answer. */
  .band--medialead > .shell > .prose.segment__media > .plate { max-width: none; margin: 0; }
  /* 3/4 made the map the tallest single element on the desktop page and it alone
     set the band's 794px height, leaving 121px of empty column beside the copy.
     The base rule is 4/3 and there was no reason for desktop to differ. */
  /* SQUARE HERE TOO. The base rule sets 1/1 and this one was still pinning 4/3, so the
     map went taller on phones and stayed flat on the desktop — the only place anybody was
     looking at it. A ratio stated in two rules is a ratio that changes in one of them. */
  .band--medialead > .shell > .prose.segment__media > iframe { width: 100%; max-width: none; margin: 0; aspect-ratio: 1 / 1; }
  /* NO FORCED RATIO ON THE PHOTOGRAPH. 5/4 threw away 29% of a 1140x648 frame at
     1280 while 768 showed the same file untouched — three different box ratios
     for one role. The owner framed the shot; the layout does not get to re-crop
     it once per breakpoint. `align-self: start` above keeps the frame the
     ratio's rather than the band's. */

  /* SHAPE 5 SPLITS COPY-ONLY PROSE INTO TWO COLUMNS, which is right in a
     full-width band and wrong here: the copy is already inside a 1.18fr column,
     so the band read as three. This has to outscore SHAPE 5's (0,4,2). */
  .band:is(.band--segment, .band--medialead) > .shell > .prose:not(.reviews):not(.segment__media) { columns: auto; }
}

/* The client's own "On This Page" runs tighter than a content band. */
.band--index { padding-block: 26px; }
/* Short labels in a full-width band: capped so the detector's character count
   reflects the line a reader actually sees rather than the box it sits in. */
.band__eyebrow { margin-bottom: 12px; max-width: 40ch; }
.prose figcaption { max-width: var(--measure); }
/* Only emitted where the drawing has to introduce itself. */
.scale__heading { font-size: clamp(22px, 5vw, 32px); margin-bottom: 4px; }
.cap {
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  padding-bottom: 10px; border-bottom: 1px solid var(--rule);
}

/* --------------------------------------------------------------- actions -- */
.actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 22px; }
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 54px; padding: 0 26px;
  font-family: var(--sans); font-stretch: 82%; font-weight: 800;
  font-size: 15px; letter-spacing: .02em; text-transform: uppercase;
  text-decoration: none; border: 1.5px solid var(--ink);
  background: var(--paper); color: var(--ink); cursor: pointer;
  transition: background 130ms linear, color 130ms linear;
}
.btn:hover { background: var(--ink); color: var(--paper); }
/* The one colored element in the page body. */
.btn--primary { background: var(--go); border-color: var(--go); color: var(--paper); }
.btn--primary:hover { background: var(--go-dark); border-color: var(--go-dark); color: var(--paper); }
/* A PRIMARY THAT IS NOT THE BOOKING. DESIGN.md gives --go exactly one job — "the Book
   online control is blue" — so a control that leads the eye on a panel about something
   else has to lead in ink instead. Solid black carries the same weight as solid blue
   without spending the one signal the page reserves. It inverts on hover, the mirror of
   the outlined button's inversion, so the two read as one system. */
.btn--ink { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.btn--ink:hover { background: var(--paper); color: var(--ink); }
.btn[aria-disabled='true'] { opacity: .45; pointer-events: none; }

.band--cta { border-top: 2px solid var(--ink) !important; }
.cta__line {
  font-family: var(--sans); font-stretch: 76%; font-weight: 800;
  /* 1.05 is the 116px H1's leading, and it was applied to a paragraph that is
     22px on a phone: descenders sat on the cap-line of the line beneath. */
  font-size: clamp(22px, 5.4vw, 38px); line-height: 1.3;
  /* 24ch broke this into four uneven lines with "delivery" alone on one of them.
     It is a closing statement, not a column of copy. */
  letter-spacing: -.03em; max-width: 34ch; margin: 0;
}

/* ------------------------------------------- THE SIGNATURE: the drawing ----
   Every container at ONE shared scale, drawn with the sheet's own line-work.
   Height carries the scale factor; width falls out of each container's aspect
   ratio, so both dimensions sit on one scale and the 30 is visibly longer
   while the 20 is visibly taller.                                            */
.scale {
  max-width: 440px;
  margin: 0 0 26px;
  padding: 18px 0 0;
  display: grid;
  gap: 14px;
}
/* ---- THE FOOTPRINT, plan view. Drawn first and biggest, because "will it fit on
   my drive" is the question people actually stall on, and because the answer is a
   surprise: three of the four containers are the same box. ---- */
.scale__ask {
  font-family: var(--sans); font-stretch: 70%; font-weight: 900;
  text-transform: uppercase; font-size: clamp(15px, 3.4vw, 19px);
  letter-spacing: .01em; word-spacing: .1em;
  margin: 0 0 14px; padding-top: 10px; border-top: 2px solid var(--ink);
  /* `p, li { max-width: var(--measure) }` caps every paragraph on the site at 66ch,
     which is right for prose and wrong for a heading whose job is to carry a rule
     across the drawing. It stopped at 616px of 1176 and the rule stopped with it. */
  max-width: none;
}
.scale__ask--2 { margin-top: 38px; }
/* THE PLAN, from above, at one scale. The outer rectangle is the largest footprint
   in the fleet; the inner one is the footprint the smaller containers share. They
   sit inside one another because that is literally the relationship: same box,
   more of it. Drawn as two rectangles in a row they wrapped onto two lines at
   1176px and stopped being a comparison at all. */
.scale__plan {
  --maxlen: 22; --maxwid: 8;
  display: flex; align-items: stretch;
  /* Capped by width rather than by height, because the aspect ratio is fixed and
     an uncapped 22x8 plan is 427px tall at full measure. */
  width: min(100%, 640px);
  aspect-ratio: var(--maxlen) / var(--maxwid);
  border: 2px solid var(--ink);
}
.scale__plansmall {
  --len: 14;
  width: calc(var(--len) / var(--maxlen) * 100%);
  border-right: 2px solid var(--ink);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; padding: 6px; min-width: 0;
}
.scale__planextra {
  flex: 1 1 auto; display: grid; place-items: center; min-width: 0;
  /* Hatched, so the extra length reads as the difference rather than as a second
     compartment. Drawn with a gradient rather than an asset so it costs nothing
     and inverts with the band. */
  background: repeating-linear-gradient(
    -45deg, transparent 0 7px, color-mix(in srgb, var(--ink) 28%, transparent) 7px 9px);
}
.scale__planwho {
  font-family: var(--sans); font-stretch: 66%; font-weight: 900;
  font-size: clamp(17px, 3vw, 30px); letter-spacing: .02em; text-align: center;
  line-height: 1; word-spacing: .06em;
}
.scale__planplus {
  font-family: var(--sans); font-stretch: 70%; font-weight: 900;
  font-size: clamp(14px, 2.2vw, 22px); letter-spacing: .02em;
}
.scale__planfoot {
  margin: 8px 0 0; max-width: none;
  font-family: var(--mono); font-size: var(--label); color: var(--mid); letter-spacing: .04em;
}
.scale__planfoot b { font-family: var(--sans); font-stretch: 82%; font-weight: 800; color: var(--ink); }
.scale__plandim {
  font-family: var(--mono); font-size: var(--label); color: var(--mid); letter-spacing: .04em;
}
/* A NOTE ON A DRAWING SHEET IS RULED OFF AND LABELED, not bracketed by a thick bar
   down its left edge. The 3px accent rail is the single most recognizable tell of
   generated UI and it was the only one on this site; a hairline above and the label in
   the annotation voice say the same thing in this sheet's own vocabulary. The label is
   a pseudo-element, so it adds no node and cannot move a parity block. */
.scale__note {
  margin: 18px 0 0; padding-top: 10px; border-top: 1px solid var(--rule);
  font-size: 15px; line-height: 1.5; color: var(--mid); max-width: 56ch;
}
.scale__note::before {
  content: 'Note'; display: block; margin-bottom: 4px;
  font-family: var(--mono); font-size: var(--label); letter-spacing: .12em;
  text-transform: uppercase; color: var(--mid-dark);
}
.scale__note b { color: var(--ink); }

/* ---- THE ELEVATION AND THE CAPACITY ---- */
.scale__rows { display: grid; gap: 18px; }
.scale__row {
  display: grid; grid-template-columns: 46px minmax(0, 1fr);
  gap: 4px 16px; align-items: end;
}
.scale__label {
  font-family: var(--sans); font-stretch: 62%; font-weight: 900;
  font-size: 22px; line-height: .85; text-align: right; font-variant-numeric: tabular-nums;
}
.scale__label span {
  display: block; font-family: var(--mono); font-size: var(--label); font-weight: 400;
  letter-spacing: .1em; color: var(--mid); margin-top: 5px;
}
/* The rail is the drawing's ground line. */
.scale__track { display: flex; align-items: flex-end; height: 64px; border-bottom: 1px solid var(--ink); }
.scale__box {
  --len: 14; --ht: 4; --maxht: 6;
  position: relative; display: block;
  height: calc(var(--ht) / var(--maxht) * 100%);
  width: auto;
  aspect-ratio: var(--len) / var(--ht);
  background: var(--ink);
  transform-origin: left bottom;
  animation: draw 620ms cubic-bezier(.16, 1, .3, 1) both;
}
/* THE CAPACITY, AS THINGS YOU CAN COUNT. "4 pickup loads" and "10 pickup loads" are
   the same shape on a page and read as the same claim. Four blocks against ten do
   not. Every block is the same size on every row, which is the entire point of
   them: the encoding only means anything across rows. */
.scale__cap { grid-column: 2; display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.scale__units { display: flex; gap: 3px; align-items: flex-end; }
.scale__unit { width: 9px; height: 14px; background: var(--ink); display: block; }
.scale__caplab {
  font-family: var(--mono); font-size: var(--label); color: var(--mid); letter-spacing: .03em;
}
.scale__caplab b { font-family: var(--sans); font-stretch: 82%; font-weight: 800; color: var(--ink); }
.scale__dim { grid-column: 2; max-width: 62ch; }
.scale__dim b { font-family: var(--sans); font-stretch: 82%; font-weight: 800; color: var(--ink); }

/* The MAXIMUM LOADING LEVEL line, which is painted on the real steel. */
.scale__box::after {
  content: ''; position: absolute; inset: 5px 5px auto 5px;
  height: 2px; background: var(--paper); opacity: .55;
}

@keyframes draw { from { transform: scaleX(.04); } to { transform: scaleX(1); } }

/* ============================================================================
   MOTION

   Three moments, each tied to something the page is actually saying, rather than
   effects scattered over it.

   1. The silhouettes draw to their true widths, so the size difference arrives
      as a movement instead of a static diagram. (keyframes draw, above.)
   2. The fleet drifts almost imperceptibly behind the offer, so the opening is
      alive without anything moving under the reader's eye.
   3. Content rises as it enters the viewport, once, a short distance.

   SAFETY. The reveal lives inside @supports (animation-timeline: view()). Where
   scroll-driven animation is unsupported the rule never applies and every
   element is simply visible: no library, no JavaScript, and no way for content
   to be left hidden. prefers-reduced-motion turns all of it off.
   ============================================================================ */
@media (prefers-reduced-motion: no-preference) {
  .hero__bg {
    animation: drift 22s ease-in-out infinite alternate;
    will-change: transform;
  }
}
/* 5.5% over 34s was arithmetically motion and perceptually nothing. */
@keyframes drift { from { transform: scale(1) translate3d(0,0,0); } to { transform: scale(1.11) translate3d(-1.2%,0,0); } }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .band > .shell > :is(h1, h2),
    .band > .shell > .deck,
    .sizecard,
    .facts__in > div,
    .panel,
    .scale__row {
      animation: rise linear both;
      animation-timeline: view();
      /* Across the whole ENTRY, so the movement happens while the element is
         actually coming into view. `entry 4% cover 22%` finished before anything
         was comfortably on screen: running, and invisible. */
      animation-range: entry 0% entry 92%;
    }
    /* Stagger across a row so a grid arrives as a sequence, not a block. */
    .sizecard:nth-child(2), .facts__in > div:nth-child(2) { animation-range: entry 0% entry 100%; }
    .sizecard:nth-child(3), .facts__in > div:nth-child(3) { animation-range: entry 0% cover 8%; }
    .sizecard:nth-child(4), .facts__in > div:nth-child(4) { animation-range: entry 0% cover 16%; }
  }
}
@keyframes rise {
  from { opacity: 0; transform: translateY(30px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  /* Everything final and still, nothing hidden. */
  .scale__box, .hero__bg { animation: none !important; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* --------------------------------------------------- the drawing's dimensions --
   A drawing that does not state its measurements is a picture. */
.scale__dim {
  display: block;
  font-family: var(--mono); font-size: var(--label); letter-spacing: .04em;
  color: var(--mid); margin-top: 2px;
}

/* The rating in the facts strip. The star drawing is shared with the review list,
   but the flex rule that lays it out was scoped to `.reviews`, so out here the
   SVGs fell back to the reset's `display: block` and stacked into a vertical
   column of five. Sharing a component means sharing its layout, not just its
   markup. */
/* starsMarkup returns the whole `.stars` span, so the flex container to fix is the
   INNER one — styling the wrapper laid out a single child and left five block-level
   SVGs stacked inside it. Reading the emitted markup settled in one line what two
   guesses at the wrapper did not. */
.facts__stars { display: inline-block; vertical-align: -2px; }
.facts__stars .stars { display: inline-flex; gap: 2px; }
.facts__stars .star { display: block; fill: var(--ink); }
.facts__in a { color: inherit; }
.facts__in a:hover { color: var(--ink); }

/* --------------------------------------------------------- the spec block ----
   The additive facts on a size card. A ruled list, not a card: the governing
   rule still holds, nothing is enclosed. */
.spec { margin: 16px 0 0; border-top: 1px solid var(--ink); }
.spec__row {
  display: grid; grid-template-columns: 96px 1fr; gap: 14px;
  padding: 9px 0; border-bottom: 1px solid var(--rule);
}
.spec dt {
  font-family: var(--mono); font-size: var(--label); letter-spacing: .04em;
  color: var(--mid); margin: 0;
}
.spec dd { margin: 0; font-size: 15px; line-height: 1.45; }
/* Three equivalents, one per line, so the eye compares like with like down the
   row of cards rather than reading a run-on sentence. */
.holds { display: grid; gap: 2px; }
.holds b { font-family: var(--sans); font-stretch: 82%; font-weight: 800; }
.spec__row--price dd { font-family: var(--sans); font-stretch: 80%; font-weight: 800; font-size: 19px; }

/* ------------------------------------------------------------ how it works ----
   Five steps, numbered because these ARE a sequence, which is the one case the
   design system's no-numbering ban exempts. */
.band--how { border-top: 2px solid var(--ink) !important; }
.how { list-style: none; margin: 30px 0 0; padding: 0; display: grid; gap: 0; counter-reset: step; }
.how__step {
  max-width: none;
  display: grid; grid-template-columns: 44px 1fr; gap: 18px;
  padding: 20px 0; border-bottom: 1px solid var(--rule);
}
.how__step:first-child { border-top: 1px solid var(--rule); }
.how__n {
  font-family: var(--sans); font-stretch: 64%; font-weight: 900;
  font-size: 26px; line-height: .9; color: var(--ink); font-variant-numeric: tabular-nums;
}
/* THE COPY BELONGS BESIDE THE NUMBER, NOT UNDER IT. .how__step is a two-column
   grid (44px number, then content) with THREE children, so auto-placement put
   the number in row 1 col 1, the heading in row 1 col 2, and the paragraph in
   row 2 COL 1 — the 44px number column. Measured at 375: the paragraph rendered
   44px wide and ran to 22 lines for 22 words, one word per line, on all five
   steps. Nothing in the markup looks wrong; the grid simply had a spare cell. */
.how__step h3 { grid-column: 2; font-size: 20px; margin: 0; }
.how__step p  { grid-column: 2; margin: 7px 0 0; font-size: 15px; max-width: 62ch; }

@media (min-width: 760px) {
  .how { grid-template-columns: repeat(5, minmax(0, 1fr)); column-gap: 26px; }
  .how__step {
    grid-template-columns: 1fr; gap: 0;
    /* one column here, so the explicit placement above must be released */
    border-top: 2px solid var(--ink); border-bottom: 0;
    padding: 14px 0 0;
  }
  .how__step:first-child { border-top: 2px solid var(--ink); }
  .how__n { font-size: 22px; }
  .how__step h3 { grid-column: 1; margin-top: 10px; font-size: 19px; }
  .how__step p  { grid-column: 1; font-size: 15px; }
  .spec__row { grid-template-columns: 88px 1fr; gap: 12px; }
}

/* ------------------------------------------------------------ size sheets --
   The client's own four size sections. NOT cards: each is a sheet with a photo
   reference, a display number, and its specs in a title block.                */
.sizegrid { display: grid; gap: 0; }
.sizecard {
  display: flex; flex-direction: column;
  padding: 26px 0 28px; border-top: 1px solid var(--rule);
}
/* SUBGRID, so the four cards line up row for row. Sized independently, the 30
   yard's shorter copy pulled its spec block 23px above the other three. Every
   card has the same five parts — photo, number, copy, spec, actions — so they
   share the grid's own rows. THE ROW COUNT IS NOT DECORATIVE: it must equal the
   number of children a card renders. Adding the action pair without moving this
   from four to five left the buttons outside the subgrid, where they stopped
   lining up and the whole point of the rule was lost. */
@supports (grid-template-rows: subgrid) {
  @media (min-width: 760px) {
    /* TEN ROWS, NOT FIVE: the spec block's own six rows join the grid.
       "Driveway" wraps to two lines on the 30 yard alone, which pushed that
       card's "Starts at $500" and its rule 19px below the other three prices —
       the exact ragged edge the card-level subgrid exists to prevent, one level
       further in. Nesting subgrid is what makes every card's Footprint, Side
       height, Driveway, Loading, Best for and Starts at share a baseline. */
    /* THE COUNTS COME FROM THE RENDERER, because the spec suppresses any row the
       client's copy on that page already states — seven rows on one page, two on
       another. A literal `repeat(11, auto)` was right for exactly one of them and
       silently wrong for the other, and a subgrid whose count does not match its
       content is what put four card titles on two different rows. --rows is a
       genuinely dynamic value, which is the one case an inline custom property
       is the right tool. */
    .sizegrid { grid-template-rows: repeat(var(--rows, 11), auto); }
    .sizecard {
      display: grid;
      grid-row: span var(--rows, 11);
      grid-template-rows: subgrid;
      row-gap: 0;
    }
    /* SEVEN, because the spec grew a "Holds about" row. This count is not
       decorative: it must equal the rows the block actually renders, or the
       spare ones fall outside the subgrid and stop lining up. */
    .sizecard .spec {
      display: grid;
      grid-row: span var(--spec-rows, 7);
      grid-template-rows: subgrid;
      row-gap: 0;
    }
  }
}
.sizecard:first-child { border-top: 0; }
.sizecard__media { margin: 0 0 16px; }
/* MOST BOOKED (#757), set like online booking's badge (.picker__flag), over the
   photo's top-left corner. Absolute, so the subgrid never gives it a row. The
   26px is the card's own padding-top, where the photo starts. */
.sizecard { position: relative; }
.sizecard__flag {
  position: absolute; top: calc(26px + 10px); left: 10px; z-index: 1;
  font-family: var(--mono); font-size: var(--label); font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; background: var(--ink); color: var(--paper); padding: 3px 6px;
}
/* The card's own photograph is rendered LAST so document order is untouched, and
   placed in the first row so it reads as the card's lead like every other. */
.sizecard__media--own { grid-row: 1; margin: 0 0 16px; }
.sizecard__media .plate { margin: 0; max-width: none; }
/* The figure lifted out of the last size card, sitting after the grid where it
   already was in document order.

   IT WAS RENDERING AS A BROKEN FIFTH CARD. At the plate's default 620px it sat
   half the width of a 1176px grid, hard against the left edge, directly beneath
   the 30 yard column, with a caption naming the 10 yard and no heading above it.
   Nothing on the page explained why it was there.

   It is the only photograph on the page of a container standing on somebody's
   actual driveway, which is the last question a size grid leaves open: what does
   this look like in front of my house. So it runs the full width of the grid as
   the band's closing plate. Wide, it reads as a deliberate final beat and the
   caption reads as a label for the photograph rather than for the card above it. */
.sizegrid__after { margin-top: 34px; }
/* THE SELECTOR IS LONG ON PURPOSE. SHAPE 2 further down — "a photograph on its
   own is a figure, not a billboard" — caps a lone figure at 620px, and it scores
   (0,3,2), so `.sizegrid__after .plate` at (0,2,0) lost silently: the rule was in
   the file, served, and doing nothing. Measured, not assumed; computed max-width
   came back 620px. This matches SHAPE 2's own structure and adds a class. */
.band > .shell > .prose.sizegrid__after > .plate { max-width: none; margin: 0; }
@media (min-width: 760px) {
  .band > .shell > .prose.sizegrid__after > .plate img {
    aspect-ratio: 21 / 9; object-fit: cover; object-position: 50% 62%;
  }
}
.sizecard__media img { width: 100%; aspect-ratio: 3/2; object-fit: cover; }
/* A CARD CANNOT OUTRANK THE SECTION IT SITS IN. At 375 the section h2 computed
   28px and these computed 34px, so the four cards read as the page's top level
   and "Dumpster Sizes & Prices" read as their caption. The desktop ramp was
   right (60 -> 34); only the mobile floor was inverted. */
.sizecard__title { font-size: clamp(23px, 6.2vw, 52px); }
.sizecard__body { font-size: 15px; margin-top: 12px; }
.sizecard__body p { max-width: none; }
.sizecard__body img { margin-top: 14px; }

/* --------------------------------------------------------- section index --
   The client's own "On This Page", as a ruled jump strip. I stripped this to a
   bare bulleted list at one point and that read as unfinished rather than as
   restrained — a list of jump links is a control, and a control with no styling
   looks broken. It is quiet, not absent: the annotation voice, one rule above and
   below, no numbering and no chevrons, laid in columns so six links are two lines
   rather than six.                                                            */
.sectionindex ul {
  list-style: none; margin: 0; padding: 0;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  columns: 2; column-gap: 44px;
}
.sectionindex li { max-width: none; margin: 0; break-inside: avoid; }
/* The rule belongs to the LINK, not to the item. On the item the text sat flush
   against the line above it with no inset — the link's own min-height centers its
   text but the detector reads the boundary, and it is right: a border with
   nothing between it and the words is a cramped container. */
.sectionindex li + li > a { border-top: 1px solid var(--rule); }
.sectionindex a {
  display: flex; align-items: center; min-height: 46px;
  text-decoration: none; color: var(--ink);
  font-family: var(--sans); font-stretch: 88%; font-weight: 700;
  font-size: 15px; letter-spacing: -.01em;
}
.sectionindex a::after {
  content: ''; margin-left: auto; width: 7px; height: 7px;
  border-top: 1.5px solid var(--mid); border-right: 1.5px solid var(--mid);
  transform: rotate(45deg); transition: transform 130ms linear, border-color 130ms linear;
}
.sectionindex a:hover::after { border-color: var(--ink); transform: rotate(45deg) translateX(3px); }
@media (max-width: 619px) { .sectionindex ul { columns: 1; } }

/* ----------------------------------------------------------------- panel --
   The short "Contact Us" blocks, twice on nearly every page. A title block,
   not a card: ruled top and bottom, never boxed.                             */
.panel { border-top: 2px solid var(--ink); border-bottom: 1px solid var(--rule); padding: 18px 0 20px; }
.panel__title { font-size: clamp(26px, 6.4vw, 40px); }
/* IN THE PAIRED ROW IT IS SUBORDINATE, SO IT SHOULD LOOK IT. Side by side the
   aside's h2 computed 40px against the main column's 60px, baselines 18px apart —
   close enough to read as an accident rather than as a level. The markup stays an
   h2 (the outline is the client's); only the scale says aside. */
.pair__aside .panel__title { font-size: clamp(22px, 4.2vw, 30px); }
/* A MEASURE, EVEN IN A FULL-WIDTH PANEL. The closing contact block spans the
   whole 1176px shell, so its copy ran 157 characters a line — Baymard measured
   that copy over 80 CPL is skipped 41% more often than copy at 60-70. The panel
   can be full width; the words inside it cannot. */
.panel .prose { font-size: 15px; margin-top: 12px; max-width: var(--measure); }

/* The conversion pair at the foot of a size card. Stacked and full width, always:
   at four columns a card is about 250px wide and two buttons side by side wrapped
   mid-word. `align-self: end` pins them to the bottom of their subgrid row so all
   four sit on one line however tall the copy above them runs. */
/* THE PRICE, AT THE POINT OF DECISION. It used to sit in the middle of the body
   copy with the whole spec table between it and the buttons, which is the one
   place a price does no work. It is the last thing weighed before acting, so it
   sits on the action.
   `margin-top: auto` moves from the actions to this block, because whichever
   element is first in the pair has to be the one that takes up the slack — left
   on the actions it pushed them to the bottom and left the price stranded again,
   one row higher. */
.sizecard__price {
  margin-top: auto; padding-top: 20px;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.sizecard__price p {
  margin: 0; max-width: none;
  font-family: var(--mono); font-size: var(--label); letter-spacing: .05em;
  text-transform: uppercase; color: var(--mid);
}
/* The number itself is the client's own <b>, so it is styled where it sits rather
   than rebuilt. On the pages that do not bold it, the paragraph's own size still
   reads as an annotation, which is honest: it is a starting price, not a total. */
.sizecard__price b {
  font-family: var(--sans); font-stretch: 72%; font-weight: 900;
  font-size: 30px; line-height: 1; letter-spacing: -.01em; color: var(--ink);
}
.actions--card { margin-top: 10px; padding-top: 0; gap: 8px; }
.actions--card .btn { flex: 1 1 100%; }
/* A card with no price line keeps the old behavior, or its buttons float up. */
.sizecard:not(:has(.sizecard__price)) .actions--card { margin-top: auto; padding-top: 20px; }

/* PRICES HIDDEN (#739). "Call For Pricing" takes the number's place and its weight,
   in normal case, because it is the action now rather than an annotation. */
.price-call { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.sizecard__price .price-call {
  font-family: var(--sans); font-stretch: 72%; font-weight: 900;
  font-size: 22px; line-height: 1.1; letter-spacing: 0; text-transform: none; color: var(--ink);
}

/* TWO PRICES (#750): the short rental above the main one, each on its own line,
   so the eye compares figure to figure down one edge. */
.sizecard__price:has(p + p) { flex-direction: column; align-items: flex-start; gap: 6px; }
.spec dd.prices { display: grid; gap: 4px; }

/* THE RENTAL TERMS, UNDER THE PRICE THEY QUALIFY (#739). Set like the price's own
   caption, mono and mid-gray, so they read as the small print of that number and
   never compete with it. A hairline separates them from the figure above. */
.sizecard__terms ul {
  list-style: none; margin: 10px 0 0; padding: 10px 0 0;
  border-top: 1px solid var(--rule);
  display: grid; gap: 4px;
  font-family: var(--mono); font-size: var(--label); line-height: 1.4; color: var(--mid);
}
.sizecard__terms li { margin: 0; padding: 0; }

/* ------------------------------------------------------------ paired band --
   A short contact panel beside the long section that follows it. Two thin bands
   stacked read as two stalls in the page; one row reads as one idea. Document
   order is untouched — the panel is still first in the markup, so parity reads
   it first — and the grid only decides which column each one lands in. It lands
   on the RIGHT; see the pinned columns below.

   The split waits for 1080px. At 760 the shell is about 700px wide, the aside
   column came out at 230px, and the buttons inside it (min-width 210px plus
   padding) overflowed it. */
.pair { display: grid; gap: 34px; }
/* TWO COLUMNS FROM TABLET UP. It waited for 1080 because at 760 the aside came
   out 230px wide and the buttons inside it overflowed. The buttons wrap now, so
   the only thing that was ever needed was a column that does not demand 320px. */
/* CONTACT US SITS ON THE RIGHT. Asked for. The narrow track is the SECOND one now and
   both children are pinned, because the panel is still first in the markup and would
   otherwise auto-place into column 1. Document order is untouched, so parity still reads
   the panel first; this decides columns only, which is what the grid was always for. */
@media (min-width: 760px) {
  .pair { grid-template-columns: 1.55fr minmax(230px, 1fr); gap: 32px; align-items: start; }
  .pair__aside { grid-column: 2; grid-row: 1; }
  .pair__main  { grid-column: 1; grid-row: 1; }
  /* The panel carries a 2px rule at its top and the column beside it carried
     nothing, so the row's top edge was ragged by the height of one heading. The
     same rule on both columns is what makes the two read as one band.

     IT BELONGS AT THE BREAKPOINT THE COLUMNS DO, not one above it. It sat at 1080
     while the split starts at 760, so every width from 760 to 1079 had the ragged
     edge this rule exists to remove — a whole breakpoint band where the fix was
     written, correct, and simply not applied. Stacked, below 760, the second rule
     would read as a divider between two sections that are already separated, which
     is why it is scoped to the two-column case rather than set unconditionally. */
  .pair__main { border-top: 2px solid var(--ink); padding-top: 18px; }
}
@media (min-width: 1080px) {
  .band--medialead > .shell { grid-template-columns: 1fr 1.18fr; column-gap: 56px; }
  .pair { grid-template-columns: 1.9fr minmax(320px, 1fr); gap: 56px; align-items: start; }
  /* The masthead does not stick, so this offset is clearance from the viewport
     edge, not from a bar. */
  .pair__aside { position: sticky; top: 32px; }
}

/* ------------------------------------------------------------- reviews ----
   The client's own review lists: 48 real Google reviews on /customer-reviews and
   three testimonials on the home and city pages. Frozen content, nothing written
   or reordered here.

   A CAROUSEL, WHICH IS WHAT WAS ASKED FOR. I built a bare scroll track first,
   measured 48 reviews at 17,280px inside a 1,176px window, concluded a carousel
   was wrong and shipped static columns. The measurement was sound and the
   conclusion was not: 17,280px of scroll is what a track WITHOUT CONTROLS costs.
   A carousel is the track plus the controls that move it, and the controls were
   the part I never built. They are here now, so the distance is paged rather
   than dragged.

   The track is a native scroller with scroll-snap, so it works on touch, on a
   trackpad and from the keyboard with no script at all. The buttons are an
   enhancement on top, and they carry no text, so no parity block is created.
   No enclosure, per the governing rule: a hairline above each, never a box.   */
.carousel { position: relative; margin-top: 22px; }

.reviews .revtrack {
  list-style: none;
  margin: 0;
  padding: 0 0 18px;
  /* FLEX, NOT GRID, AND THE REASON IS A MEASUREMENT. As a grid with
     `grid-auto-columns: minmax(0, ...)` the 48-review track rendered every slide
     0px wide and 1,885px tall: a 0 minimum lets the grid shrink all 48 columns to
     nothing when they cannot fit, which is exactly the case a carousel is. A flex
     item with flex-shrink 0 cannot collapse, so the track overflows on purpose
     and the controls page it. */
  display: flex;
  gap: 30px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* NO SCROLLBAR, EVER. Both spellings: the standard property and the WebKit
     pseudo-element, because neither alone covers every engine. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.reviews .revtrack::-webkit-scrollbar { display: none; width: 0; height: 0; }
/* NO SNAP AND NO SMOOTH BEHAVIOR. The track is written one frame at a time, and
   both of those fight a per-frame write: snap drags the track back to the nearest
   slide, smooth queues an animation against the next frame's value. Either one
   turns a drift into a stutter. */

/* The partly visible slide at the edge is the only thing that says "there is
   more", so the track keeps a soft edge rather than a hard crop. */
/* THE FADE IS ON THE TRAILING EDGE ONLY. A ramp on the left ran over the first
   card, which sits at x=0: sampled luminance on the stem of the "S" in "Shirley"
   came back 9/255 against a black band — about 1.05:1 — so the name read as
   "hirley" and the first star vanished. The right edge is where "there is more"
   needs saying; the left edge is where the reader starts. */
.carousel {
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 52px), transparent);
          mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 52px), transparent);
}
/* A keyboard user has to be able to enter the region, and has to be able to see
   that they have. */
.carousel:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; }

.reviews .revtrack > li {
  /* THE NEXT CARD MUST PEEK. At exactly 100% the card filled the track and
     nothing was partly visible, so on a phone — where there are no controls and
     no scrollbar — nothing at all told a reader there were nine more. The
     comment above claimed a peek that the measurement said did not exist. */
  flex: 0 0 calc(100% - 46px);
  scroll-snap-align: start;
  max-width: none;
  margin: 0;
  padding-top: 14px;
  border-top: 2px solid var(--ink);
  font-size: 16px;
  line-height: 1.45;
  text-wrap: pretty;
}
/* The rating, drawn rather than typed. */
.reviews .stars { display: flex; gap: 2px; margin-bottom: 8px; }
.reviews .star { display: block; fill: var(--ink); }

/* The attribution: <b> on some pages, <strong> on others, the same thing here. */
.reviews .revtrack > li > :is(b, strong) {
  display: block;
  font-family: var(--mono);
  font-size: var(--label);
  font-weight: 400;
  letter-spacing: .04em;
  color: var(--mid);
  margin-bottom: 7px;
}
/* The nested "Review: ..." list on /customer-reviews is the quotation itself. */
.reviews .revtrack > li ul { list-style: none; padding: 0; margin: 0; }
.reviews .revtrack > li li { max-width: none; margin: 0; }
.reviews > p { max-width: var(--measure); }

@media (min-width: 620px) {
  .reviews .revtrack > li { flex-basis: calc((100% - 30px) / 2); font-size: 17px; }
}
@media (min-width: 1080px) {
  .reviews .revtrack > li { flex-basis: calc((100% - 60px) / 3); }
}

/* ---------------------------------------------------------- touch targets --
   72 of 109 links and buttons measured under 44px tall at 375. The footer was the
   worst of it — all 28 links at 16-17px, the footer's own tel: link included — and
   the in-content phone number on each size card, which is THE conversion on this
   site, measured 95.8x16px. Two footer links measured a 0.0px gap between them.

   A list of links is a list of controls on a phone, whatever it is on a desktop. */
@media (max-width: 759px) {
  .foot li a,
  .prose ul li a,
  .sectionindex a { display: block; min-height: 44px; padding-block: 13px; }
  .foot li a { padding-block: 13px; }
  /* THE NUMBER IS A CONTROL, NOT A FOOTNOTE — but it is still inside a sentence,
     so it may only grow DOWNWARDS. My first version added 4px of horizontal
     padding and a 10px margin between adjacent links, and both showed up as gaps
     in running copy: "Call or text us at  251-415-4545" and "Contact us by
     Contact Form or   Email Directly". A touch target is a vertical problem;
     solving it horizontally rewrites the client's spacing. */
  .prose a[href^="tel:"] {
    display: inline-flex; align-items: center; min-height: 44px; padding: 0;
  }
  /* A thumb landing on the seam between "call" and "order" had a coin-flip
     between dialling and navigating. */
  .callbar { gap: 8px; padding: 0 8px 6px; background: var(--paper); }
  .callbar a + a { border-left: 0; }

  /* I DELETED TWO THINGS THAT WERE ASKED FOR, and counting them is not a reason.
     The header pair and the per-card Call button were both explicit requests
     ("move the phone number down to the bottom and add a book online button so 2
     buttons" / "add the order online button per dumpster"). I counted twenty
     buttons on the phone, called it clutter and hid them. Repetition of the two
     actions down a long sales page is the point of a long sales page; my tally
     was not a finding, it was me overruling an instruction. Both are back. */
}

/* THIS BLOCK LIVED INSIDE `@media (max-width: 759px)` and I never noticed, so none
   of it applied at desktop — and the `min-width: 760px` query nested inside it
   could not match at ANY width. The FAQ had no columns and no rules above 760:
   eight questions running the full 1,176px shell at 157 characters a line.
   A nested media query that contradicts its parent is dead code that looks alive.
   Verify the BLOCK a rule lands in, not just the rule. */
/* -------------------------------------------------------------------- FAQ --
   Eight questions rendered as an undifferentiated wall of bold-then-paragraph,
   1,400px tall, with nothing separating one answer from the next question. The
   client writes them as an h3 "Q: ..." followed by a paragraph "A: ...", which
   is already a definition list in everything but tag name — so it is ruled and
   set like one. Two columns above 900px, because eight of them down one column
   is a page of its own. */
.faq { columns: 1; }
.faq > :is(h3, h4) {
  break-after: avoid-column;
  margin: 0; padding-top: 16px;
  border-top: 1px solid var(--rule);
  font-size: 17px; line-height: 1.35;
}
.faq > :is(h3, h4) + p { break-before: avoid-column; }
.faq > p { margin: 8px 0 24px; max-width: none; font-size: 15px; color: var(--mid); }
.faq > :first-child { padding-top: 0; border-top: 0; }
@media (min-width: 760px) {
  .faq { columns: 2; column-gap: 40px; }
}


.terms { display: flex; flex-wrap: wrap; gap: 18px; margin: 0; max-width: none; }
.terms a { text-decoration: none; border-bottom: 1px solid var(--rule); padding-bottom: 3px; }
.terms a:hover { border-color: var(--ink); color: var(--ink); }

/* ---------------------------------------------------------------- footer -- */
.foot { background: var(--ink); color: var(--paper); padding-block: 34px 30px; margin-top: 0; }
.foot a { color: var(--paper); }
/* A HEADING CANNOT BE SMALLER AND LIGHTER THAN ITS OWN CONTENT. These computed
   11px/400 over the 15px/400 links beneath them — 0.73x the size of the thing
   they label, at the same weight — so each read as the first item in its list
   rather than as its title.

   MERGED HERE RATHER THAN ADDED ABOVE. I first wrote this as a `.foot__cols h2`
   rule, which scores exactly the same as `.foot h2` and sat EARLIER in the file,
   so it lost on source order and did nothing at all. Two rules of equal weight
   fighting over one property is not a fix, it is a coin toss decided by line
   number. */
.foot h2 {
  /* .08em of tracking on lowercase text is the detector's "wide-tracking" finding and it is
     right: wide tracking is a CAPS device. These are three-word column labels, which is exactly
     what caps are for, so they take the caps rather than losing the tracking. */
  font-family: var(--mono); font-size: 13px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--paper); font-stretch: 100%; line-height: 1.4;
  margin-bottom: 6px;
}
.foot ul { list-style: none; margin: 12px 0 0; padding: 0; }
.foot li { padding: 3px 0; font-size: 15px; }
.foot__cols { display: grid; gap: 30px; }
.foot__legal {
  border-top: 1px solid #262626; margin-top: 30px; padding-top: 18px;
  /* .1em on a lowercase sentence is tracking used where it does not belong: it is a
     CAPS device, and this is a copyright line, not a label. */
  font-family: var(--mono); font-size: var(--label); letter-spacing: .02em;
  text-transform: none; color: var(--mid-dark);
}

.skip { position: absolute; left: -9999px; background: var(--ink); color: var(--paper); padding: 12px 18px; z-index: 10; }
.skip:focus { left: 12px; top: 12px; }

/* ============================================================================
   Tablet and up. Base styles above are phone (375px) and are not touched here.
   ============================================================================ */
@media (min-width: 760px) {
  :root { --gutter: 32px; }
  body { font-size: 17px; }
  /* size now comes from the clamp above */
  .prose--lede > p:first-of-type { font-size: 21px; }

  .band { padding-block: 54px; }

  /* THE OPENING IS A COMPOSITION, NOT A HALF-EMPTY BAND.
     One column capped at a reading measure left the right half of a 1240px shell
     empty, which read as a broken two-column layout. The client's opening carries
     three paragraphs, so the first becomes the statement and the rest support it
     beside. No invented content, full width used. */
  /* ==========================================================================
     CONTENT SHAPES

     The client's 460 sections arrive in a handful of shapes, and one treatment
     for all of them is what left bands half empty with a photograph dumped
     underneath. Measured across all 41 pages:

       copy + ONE photograph   ~160 sections   35%
       copy only               ~150            33%
       a list on its own        ~70            15%
       a photograph alone        26             6%

     So the layout is keyed on the shape itself with :has(), not hand-applied
     band by band. Fixing these one at a time was the mistake; there are 160 of
     the first one.
     ========================================================================== */

  /* NOT INSIDE A MEDIALEAD BAND. Its copy already sits in the column beside the lead
     photograph, so splitting that column again made three: /about-us ran the logo, 276px
     of text and a 77px truck photo across an iPad (Steve, 2026-10-04, #484). There the
     photograph flows under its paragraph at the column's width, as SHAPE 5 already does. */
  /* SHAPE 1 — copy beside ONE photograph.
     Stacked in source order this is a narrow column of text with a large image
     under it and half the shell empty. Copy runs down column one, the photograph
     holds column two and spans it, so the columns finish together. */
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> :is(img, figure)):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) {
    display: grid;
    grid-template-columns: 1.18fr 1fr;
    column-gap: 56px;
    align-items: start;
  }
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> :is(img, figure)):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) > p {
    grid-column: 1; max-width: none;
  }
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> :is(img, figure)):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) > :is(ul, ol) {
    grid-column: 1; max-width: none;
  }
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> :is(img, figure)):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) > :is(img, figure) {
    grid-column: 2; grid-row: 1 / span 99;
    width: 100%; max-width: none; margin: 0;
  }

  /* ONE SIZE THROUGH THE INTRO. Steve, 2026-08-31: "make the font the same size for that
     section." It ran the first paragraph at 21px and the second at 17px, a four-pixel step that
     is too small to read as hierarchy and too large to read as a mistake. The two paragraphs are
     one thought, and the section already has a photograph beside it doing the attention-getting,
     so the type does not need to do it as well. Kept at the larger of the two, because the
     opening paragraph is still the page's opening paragraph. */
  .band--open > .shell > .prose--lede > p {
    font-size: 19px; line-height: 1.5; letter-spacing: -.004em;
  }
  .band--open > .shell > .prose--lede > p:first-of-type { margin-top: 0; }

  /* SHAPE 2 — a photograph on its own. It is a figure, not a billboard. */
  .band > .shell > .prose:not(:has(> p)) > :is(img, figure) { max-width: 620px; }

  /* SHAPE 5 — COPY ONLY, and the one I never built.
     150 of 460 sections carry nothing but paragraphs, a third of the site. At a
     66ch measure in a 1240px shell they ran as a narrow column down the left with
     the right half blank, which is the "obviously ugly section using no design
     strategy" on this page. Left-aligned text against an empty half reads as
     broken; the same text in two columns reads as editorial and uses the width.
     Two or more paragraphs only, so each column has something in it. */
  .band > .shell > .prose:not(.reviews):not(:has(> :is(img, figure, iframe, ul, ol, table, h3, h4))):has(> p:nth-of-type(2)) {
    columns: 2;
    column-gap: 64px;
  }
  .band > .shell > .prose:not(.reviews):not(:has(> :is(img, figure, iframe, ul, ol, table, h3, h4))):has(> p:nth-of-type(2)) > p {
    max-width: none;
    break-inside: avoid-column;
    margin-top: 0;
  }
  .band > .shell > .prose:not(.reviews):not(:has(> :is(img, figure, iframe, ul, ol, table, h3, h4))):has(> p:nth-of-type(2)) > p + p {
    margin-top: 1em;
  }

  /* ONE MEDIA GROUP, OR NONE OF THIS APPLIES.
     `grid-row: 1 / span 99` places an element in the whole of column two, and it
     is safe for exactly ONE element and catastrophic for two: they land on the
     same rows and paint over each other. Each rule below used to guard only
     against a duplicate of its OWN type, which is how a photograph and a map
     ended up drawn on top of one another. Wrapping adjacent media in .mediastack
     fixed that pair and reintroduced the same bug one level up — /dumpster-sizes
     holds TWO .mediastack groups in one prose, both were given row 1 / span 99,
     and the 10 and 20 yard photographs rendered in the identical 338px box, as
     did the 15 and the 30. Five photographs painted into two slots, each under
     the wrong caption.

     So the guard is now the SAME on every rule and counts media of every kind. A
     prose carrying more than one media group gets no two-column treatment at all
     and flows in document order, which is always correct if not always pretty.
     Document order is also the only order allowed here: a figcaption is a parity
     block, so media cannot be regrouped to make a layout work. */
  /* A SECTION CARRYING BOTH A PHOTOGRAPH AND THE MAP. The two shapes below are
     written independently and each guards only against a duplicate of its OWN
     media type, so a section holding one of each fired both and placed them in
     the same column at the same row span, drawn on top of each other. The
     renderer now wraps adjacent media in .mediastack, which makes the pair one
     grid child — the only thing the span-99 idiom can place correctly. */
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> .mediastack):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) {
    display: grid; grid-template-columns: 1.18fr 1fr; column-gap: 56px; align-items: start;
  }
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> .mediastack):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) > :is(p, ul, ol) {
    grid-column: 1; max-width: none;
  }
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> .mediastack):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) > .mediastack {
    grid-column: 2; grid-row: 1 / span 99;
  }

  /* A MAP IS A PLATE. The client's Google Business Profile embed sits in the
     service-area sections, so copy runs left and the map holds the column beside
     it, the same shape as copy beside a photograph. */
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> iframe):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) {
    display: grid;
    grid-template-columns: 1.18fr 1fr;
    column-gap: 56px;
    align-items: start;
  }
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> iframe):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) > :is(p, ul, ol) {
    grid-column: 1; max-width: none;
  }
  .band:not(.band--medialead) > .shell > .prose:has(> p):has(> iframe):not(:has(> :is(img, figure, iframe, .mediastack) ~ :is(img, figure, iframe, .mediastack))) > iframe {
    grid-column: 2; grid-row: 1 / span 99;
  }

  /* SHAPE 4 — a post grid. "Related Posts" is a FLAT repeating triple, three
     times over: a linked thumbnail, an h3 title, a byline paragraph. Nine
     siblings in a row, which stacked vertically ran to 1405px.
     Three explicit rows plus column auto-flow lays each post down its own column
     without touching the markup. Keyed on there being a THIRD direct-child
     anchor holding an image, which is what separates a post grid from a single
     featured thumbnail. */
  .band > .shell > .prose:has(> figure:nth-of-type(3)) {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: auto auto auto;
    grid-auto-flow: column;
    column-gap: 34px;
    align-items: start;
  }
  .band > .shell > .prose:has(> figure:nth-of-type(3)) > figure img {
    width: 100%; aspect-ratio: 16 / 9; object-fit: cover; margin: 0;
  }
  .band > .shell > .prose:has(> figure:nth-of-type(3)) > h3 {
    margin: 14px 0 0; font-size: 19px; max-width: none;
  }
  .band > .shell > .prose:has(> figure:nth-of-type(3)) > h3 > a { text-decoration: none; }
  .band > .shell > .prose:has(> figure:nth-of-type(3)) > h3 > a:hover { text-decoration: underline; }
  .band > .shell > .prose:has(> figure:nth-of-type(3)) > p {
    margin: 8px 0 0; max-width: none;
    font-family: var(--mono); font-size: var(--label); letter-spacing: .1em;
    text-transform: uppercase; color: var(--mid);
  }

  /* SHAPE 3 — a long list on its own runs in columns rather than as one tall
     thin strip down the left of a 1240px shell. The review track and the
     client's page index carry their own layout and are left alone. */
  .band > .shell > .prose > :is(ul, ol):not(.revtrack):has(> li:nth-child(7)) {
    columns: 2;
    column-gap: 48px;
    max-width: none;
  }
  .band > .shell > .prose > :is(ul, ol):not(.revtrack):has(> li:nth-child(7)) > li {
    break-inside: avoid;
  }
  .band--title { padding-block: 36px 30px; }

  .hero__inner { padding-block: 92px 64px; }
  .hero__bg { object-position: 50% 38%; }
  .masthead__logo { height: 64px; }
  .masthead__logo img { height: 183px; margin-top: -59px; }
  .hero .actions .btn { flex: 0 0 auto; min-width: 210px; }
  .hero__eyebrow .hero__hours { display: inline; }
  .hero__offer { font-size: 20px; max-width: 44ch; }
  .facts__in { grid-template-columns: repeat(4, 1fr); }
  .facts__in > div { border-bottom: 0; padding: 22px 0; }
  .facts__in > div + div { border-left: 1px solid var(--rule); padding-left: 26px; }
  .facts__in > div:nth-child(odd) { padding-right: 0; }
  .facts__in > div:not(:last-child) { padding-right: 26px; }
  .facts b { font-size: 20px; }

  /* The cap stopped the four ground rules 144px short of every other right edge
     in the band between 760 and 1079. The drawing is the band's subject; it gets
     the band's width. */
  .scale { max-width: none; }
  .scale__track { height: 96px; }
  .scale__row { grid-template-columns: 58px 1fr; gap: 18px; }
  .scale__label { font-size: 27px; }

  .sizegrid { grid-template-columns: repeat(2, 1fr); column-gap: 40px; }
  .sizecard { padding: 30px 0 34px; }
  .sizecard:nth-child(2) { border-top: 0; }

  .sectionindex ul { columns: 2; column-gap: 44px; }
  .sectionindex li { break-inside: avoid; }

  .band--cta .shell { display: flex; align-items: center; justify-content: space-between; gap: 34px; flex-wrap: wrap; }
  .band--cta .actions { margin-top: 0; }

  /* FOUR BLOCKS, SO NEVER THREE TRACKS. This was repeat(3, 1fr), which orphaned "More" onto a
     second row on its own with the width of two columns empty beside it, while the service area
     ran fifteen links deep in the column above. Between 760 and 1080 there is room for two, and
     two takes four blocks in two clean rows with nothing stranded. Four columns return at 1080
     below, which is where they fit. */
  .foot__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 44px; align-items: start; }
}

@media (min-width: 1080px) {
  /* THE DRAWING BECOMES THE HEADER ROW OF THE CARD GRID.
     Stacked as four rows it occupied the left 40% of a 1240px shell and left the
     rest blank. On the same four columns as the cards below, each silhouette sits
     directly above the container it describes, still on one shared scale, and the
     comparison is read across the row instead of down a list. */
  .scale {
    max-width: none;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: 34px;
    align-items: end;
    margin-bottom: 22px;
  }
  /* FOUR COLUMNS WAS RIGHT WHEN EACH ROW SAID TWO THINGS. It now carries length,
     sides, capacity and price, which will not sit under a 270px silhouette — so
     above 1080 the drawing goes back to a single stacked list, where every row
     has the full measure and the four ground lines line up down the page. That
     comparison is stronger than the four-across one ever was: the eye reads the
     22ft against the 14ft immediately when they share a left edge. */
  .scale { grid-template-columns: 1fr; max-width: none; }
  .scale__row { grid-template-columns: 58px 1fr; gap: 18px; }
  .scale__label { font-size: 27px; }
  /* ONE rail height per breakpoint. There were two of these in this block, 76 then
     54, because an edit meant for the 760 block landed here — both blocks held the
     same text, so the anchor matched the wrong one and the later rule silently won.
     The rail is set by the WIDEST box: boxes are drawn from height and width falls
     out of the aspect ratio, so the 30 yard at 22ft is the constraint. Down one
     full-width column it has ~1100px to use. */
  .scale__track { height: 128px; }

  .sizegrid { grid-template-columns: repeat(var(--cols, 4), 1fr); column-gap: 34px; }
  .sizecard { border-top: 0; padding-top: 30px; }
  .sizecard__title { font-size: 34px; }
  .foot__cols { grid-template-columns: 2fr 1fr 1fr 1fr; }
}

/* ------------------------------------------------------- sticky call bar --
   Mobile only. A call is one of the two conversions and the pages are long, so
   the number stays reachable. Chrome, not content: it adds no parity block.   */
.callbar {
  position: fixed; inset: auto 0 0 0; z-index: 20;
  display: flex; border-top: 1.5px solid var(--ink);
}
.callbar a {
  flex: 1; min-height: 56px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--paper); color: var(--ink); text-decoration: none;
  font-family: var(--sans); font-stretch: 82%; font-weight: 800;
  font-size: 15px; letter-spacing: .02em; text-transform: uppercase;
}
.callbar a + a { border-left: 1.5px solid var(--ink); }
.callbar a.is-primary { background: var(--go); color: var(--paper); }
body { padding-bottom: 58px; }

@media (min-width: 760px) {
  .callbar { display: none; }
  body { padding-bottom: 0; }
}

/* ------------------------------------------------------------- the fork --
   THE OPENING SECTION. PRODUCT.md names two audiences — the homeowner who does
   not know what size they need, and the contractor who resents being walked
   through a quiz to say "20 yard, Tuesday" — and calls serving both without
   making either do the other's work the central design problem. Until this it
   happened nowhere before /order-online, and the section was two paragraphs
   beside a photograph, the same arrangement this page then uses three more
   times below.

   Chosen from three structures put up for review (impeccable, surface scope,
   seed 7a42f6b0). Mobile first, as everything here is: the routes lead at
   phone width because most of this traffic is a phone, and the grid only
   applies from 760 up.

   SIX CLASSES ON THE GRID SELECTORS, deliberately. SHAPE 1 below scores
   (0,5,2), not the (0,3,4) it looks like: :has() and :not() take their
   argument's specificity and :is(img, figure, iframe, .mediastack) carries a
   class. Five classes lost to it silently and the section rendered as the old
   two-column layout. */
.doors { border-top: 2px solid var(--ink); margin-top: 26px; }
/* A tel: link inside .prose is inline-flex site-wide, at (0,2,1). The phone
   route IS one, and it put its own title and subtitle on a single line while
   the other two stacked. */
/* `.prose a` underlines every link in a prose block at (0,1,1), which beat
   `.doors__d`'s text-decoration:none and drew one underline across BOTH lines of
   every route. A block route with an arrow does not need a resting underline; the
   affordance is the shift and the arrow. The title picks one up on hover only,
   hairline weight, and the description never does. */
.band--doors .doors a.doors__d {
  display: block; align-items: normal; text-decoration: none;
}
.band--doors .doors a.doors__d:hover .doors__k {
  text-decoration: underline; text-decoration-thickness: 1px;
  text-decoration-color: var(--rule); text-underline-offset: .18em;
}
.doors__d {
  padding: 18px 34px 19px 0; border-bottom: 1px solid var(--rule);
  text-decoration: none; position: relative;
}
/* THE SHIFT IS A TRANSFORM ON THE TEXT, NOT A PADDING ON THE ROW. Animating
   padding-left relaid out the route on every frame of the hover. Translating the ROW
   instead is wrong in the other direction: it carries the row's bottom rule and its
   arrow 10px right with it, so the ruled stack breaks alignment on hover. Only the two
   text spans move, which is what the padding was moving all along, and they move on
   the compositor. */
.doors__k, .doors__s { transition: transform 130ms ease-out; }
.doors__d:last-child { border-bottom: 2px solid var(--ink); }
/* Centered on the row, not pinned to a fixed offset from its top. At 20px it sat
   level with the title on the two display rows and level with the SUBTITLE on the
   phone row, whose title is set smaller. */
.doors__d::after {
  content: '\2192'; position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  font-size: 19px; transition: transform 130ms ease-out;
}
.doors__d:hover :is(.doors__k, .doors__s) { transform: translateX(10px); }
.doors__d:hover::after { transform: translateY(-50%) translateX(5px); }
.doors__k {
  display: block; font-stretch: 72%; font-weight: 900; text-transform: uppercase;
  /* THE FLOOR WAS 17px, WHICH IS THE BODY SIZE. On a phone the route's title and the
     paragraph above it measured the same, so the only thing separating them was weight.
     A title has to be bigger than the thing it titles. */
  font-size: clamp(20px, 1.7vw, 21px); line-height: 1.15; letter-spacing: .005em;
}
.doors__s {
  display: block; margin-top: 5px; line-height: 1.45;
  font-size: var(--doorsbody, 17px); color: var(--mid);
}
/* The one colored mark on the sheet, on the action the business wants most. */
.doors__d--go .doors__k { color: var(--go); }
/* The phone route is a route like the other two, so it is set like them. It was
   given its own smaller, wider, sentence-case treatment and read as a footnote
   under two headings. */

/* Phone: the routes lead, then the copy, then the photograph. */
/* ONE BODY SIZE FOR THE WHOLE BAND, AND IT IS A TOKEN BECAUSE IT WAS NOT ONE.
   Steve has now asked four times for one font size in this section. Measured on the
   deployed page rather than read off the stylesheet, it had FOUR:
     18px   `.prose--lede > p:first-of-type`, base, so first paragraph only
     16px   the base body size, so the second paragraph, on phones only
     17px   the unifying rule — which lived INSIDE the 760 media query, so it only ever
            fixed the desktop and the phone kept the 18/16 step every time
     15.5px `.doors__s`, the three route subtitles, at every width
   Every previous fix edited the 17px rule, which is why the desktop kept coming back
   clean and the phone never did. The token is declared on the band, so a rule that wants
   this voice cannot pick a number of its own. Secondary text is separated by COLOR, not
   by size: --mid on the subtitles, ink on the copy. */
.band--doors { --doorsbody: 17px; }
.band.band--open.band--doors > .shell > .prose--lede.prose { display: flex; flex-direction: column; }
.band.band--open.band--doors > .shell > .prose--lede.prose > .doors          { order: 1; margin-top: 0; }
/* MARGINS DO NOT COLLAPSE INSIDE A FLEX CONTAINER. `.prose p` carries a 17px bottom
   margin and this rule adds a 24px top one; in normal flow they would collapse to 24, and
   as flex items they add to 41. Two paragraphs of one thought sat a paragraph and a half
   apart on phones. The bottom is zeroed rather than the top reduced, so the gap is stated
   in one place. The strip below brings its own 34px. */
.band.band--open.band--doors > .shell > .prose--lede.prose > p {
  order: 2; margin: 24px 0 0;
  font-size: var(--doorsbody); line-height: 1.6; max-width: none;
}
.band.band--open.band--doors > .shell > .prose--lede.prose > :is(img,figure) { order: 3; margin-top: 26px; }
/* THE STRIP NEEDS AN `order` OF ITS OWN. The phone layout is a flex column whose
   children are sequenced by `order`, and anything without one defaults to 0 — ahead
   of every element that has one. The strip landed above the routes and above the copy,
   which is the first thing on the page on a phone. Desktop hid it: the grid there was
   already full for two rows, so auto-placement happened to drop it in the right place.
   It is pinned in both, because "happens to land correctly" is not a layout. */
.band.band--open.band--doors > .shell > .prose--lede.prose > .plates { order: 4; }

@media (min-width: 760px) {
  .band.band--open.band--doors > .shell > .prose--lede.prose {
    display: grid; grid-template-columns: repeat(12, 1fr); column-gap: 40px; row-gap: 0;
  }
  /* Rows pinned. `order` reorders boxes but auto-placement still fills row 1,
     so the figure claimed the top row and dragged everything out of line. */
  /* Layout only. The size comes from --doorsbody in the base block; a literal here is
     what made this rule fix the desktop and leave the phone alone, four times running. */
  .band.band--open.band--doors > .shell > .prose--lede.prose > p {
    grid-column: 1 / span 6; max-width: none; margin: 0 0 16px;
  }
  /* ONE SIZE, ONE COLOR, both paragraphs. Steve asked for that once already and
     this reintroduced a 17/16 step with the second one grayed. Two paragraphs of
     one thought, and a four-pixel step is too small to read as hierarchy and too
     large to read as deliberate. */
  .band.band--open.band--doors > .shell > .prose--lede.prose > p:first-of-type  { grid-row: 1; }
  .band.band--open.band--doors > .shell > .prose--lede.prose > p:nth-of-type(2) { grid-row: 2; }
  /* Level with the copy, spanning both its rows, so the two audiences are
     offered at the moment the company introduces itself. */
  .band.band--open.band--doors > .shell > .prose--lede.prose > .doors {
    grid-row: 1 / span 2; grid-column: 8 / -1; align-self: start; margin-top: 0;
  }
  .band.band--open.band--doors > .shell > .prose--lede.prose > :is(img, figure) {
    grid-row: 3; grid-column: 1 / -1; max-width: none; margin: 40px 0 0;
  }
  .band.band--open.band--doors > .shell > .prose--lede.prose > .plates {
    grid-row: 3; grid-column: 1 / -1;
  }
  .band.band--open.band--doors > .shell > .prose--lede.prose :is(img, figure) img {
    width: 100%; height: clamp(200px, 24vw, 300px); object-fit: cover; object-position: 50% 52%;
  }
}

/* ------------------------------------------------------------- the rail --
   The owner's photographs of real jobs, drifting under the opening section.
   Same drift as the review carousel, same pause rules; only the cargo differs.

   THE SCROLLER IS THE ELEMENT WITH overflow, and the drift script sets
   scrollLeft on it, so .jobtrack owns the overflow and .joblist is only the
   frame. Putting overflow on the frame gives a rail that cannot move and looks
   identical to one that is merely paused. */
/* THE EXHIBIT. Heavy rule and label on top, hairline underneath, plates between —
   the strip is a bounded thing on the sheet rather than a row of pictures floating
   after the copy. */
.plates { margin-top: 34px; padding-bottom: 18px; border-bottom: 1px solid var(--rule); }
.band--doors .plates { grid-column: 1 / -1; }
.joblist { margin-top: 0; }

/* THE TITLE BLOCK. A spec sheet does not drop photographs onto the page loose; it
   rules them off and names what they show. The rule is the heavy weight, so the strip
   reads as one exhibit rather than six unrelated pictures, and the label answers the
   question the fork above it just asked — where is it going. */
.joblist__t {
  font-family: var(--mono); font-size: var(--label); font-weight: 400;
  letter-spacing: .12em; text-transform: uppercase; color: var(--mid);
  margin: 0 0 12px; padding-top: 9px; max-width: none;
  border-top: 1.5px solid var(--ink);
}

/* NO SCROLL SNAPPING. Snap and a per-frame drift are mutually exclusive: the script
   writes scrollLeft about sixty times a second and the snap engine pulls every write
   back to the nearest snap point, so the rail sits still while `data-drift` says on
   and reason() says running. Measured, not guessed — setting scrollLeft to 200 came
   back as 19 on this track and as 200 on the review track, whose only difference is
   that it never had snapping. The review rail has gone without it since it was built,
   which is why it drifts and this one did not. */
/* `.prose ol` INDENTS ITS LIST 18.7px AND OUTRANKS `.jobtrack`. Left unbeaten the strip
   sits a fifth of an inch right of the rule above it and the copy beside it, which on a
   sheet ruled to a grid is the one misalignment the eye finds every time. */
.joblist .jobtrack {
  display: flex; align-items: flex-start; gap: 12px; list-style: none; margin: 0; padding: 0;
  overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
}
.jobtrack::-webkit-scrollbar { display: none; width: 0; height: 0; }

/* THE CONTACT FRAME, AND HOW IT RELATES TO `.plate` ABOVE.
   `.plate` is this site's one treatment for a photograph IN THE COPY: crop marks at two
   opposite corners, deliberately not a frame, because a full box around a 620px image
   fights the sheet. A thumbnail in a strip is a different object on the same sheet — a
   contact frame — and a contact frame IS a closed box, at the middle line weight so it
   reads lighter than a plate's 2px marks. The caption is byte-for-byte the plate's
   figcaption voice: mono, --label, .04em, under a hairline. One system, two scales.

   If this ever needs to become crop marks instead, the change is here and only here. */
/* THE MARGIN HAS TO BE KILLED AT (0,2,1), NOT (0,1,1). `.prose li + li` sets a 5.95px
   top margin and outranks `.jobtrack > li`, so every plate but the first started 6px
   lower; with the flex default of stretch they then resolved 6px SHORTER, all six
   flush at the bottom and stepped at the top. It reads as six photographs of unequal
   height, which is exactly what it was. `align-items: flex-start` above stops the
   stretch from laundering a margin into a height in the first place. */
.joblist .jobtrack > li {
  flex: 0 0 auto; max-width: none; margin: 0;
  width: 172px;
  border: 1.5px solid var(--ink); background: var(--paper); padding: 5px;
}
/* EVERY PLATE IS THE SAME RECTANGLE, and the height is stated rather than derived.
   An aspect-ratio alone leaves the height hostage to whatever width the flex line
   resolves to, so a strip that is meant to read as one row of identical plates comes
   out a fraction taller at one breakpoint than another. Width and height are both
   pinned here; `cover` absorbs the difference between the six source crops. */
.jobtrack img {
  width: 100%; height: 108px; object-fit: cover; display: block;
  aspect-ratio: auto;
}
/* The frame's own focus ring, because the rail is keyboard-scrollable. */
.joblist:focus-visible { outline: 2.5px solid var(--ink); outline-offset: 4px; }

/* THE FADE IS NARROWER HERE THAN ON THE REVIEW RAIL. The shared .carousel mask fades
   the last 52px, which was a quarter of a review card and is nearly a third of a
   plate — wide enough to read as a half-drawn frame rather than as "there is more
   this way". The plates are smaller, so the signal has to be too. */
.joblist { -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 26px), transparent);
                   mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 26px), transparent); }

@media (min-width: 760px) {
  .joblist .jobtrack { gap: 14px; }
  .joblist .jobtrack > li { width: 208px; padding: 6px; }
  .jobtrack img { height: 130px; }
}
/* A STRAY `}` LIVED HERE and is deliberately gone. It closed nothing, so it was discarded
   harmlessly — and it silently ate the next rule anybody appended to this file. Demonstrated:
   a rule added after it never entered the stylesheet at all, cssRules.length unchanged. That is
   the exact failure two other comments in this project already warn about, sitting in the file
   as a live trap for the next person to add a line. */

/* The footer's phone line. Was a `style="margin-top:10px"` attribute, the only inline style in
   the codebase, in a project whose second rule is that there are none. */
.foot__tel { margin-top: 10px; }

/* --------------------------------------------------- a set of services, one per row --
   Each service's copy and the photograph OF THAT SERVICE share a grid row. The row
   number is handed over in a custom property by Sections::rowMedia, which joins them on
   the caption text rather than on document position — the two plates in every source run
   belong to two different services, one above and one below, so any positional rule puts
   half of them beside the wrong words. It shipped that way once; the join is what fixes it.

   Base is the phone: one column, in document order, exactly as it rendered before. */
.services > .svc { display: block; }

@media (min-width: 760px) {
  /* 760, matching the size grid, the shape rules and band--medialead. The first version
     opened at 900, which put a 3,062px cliff on one pixel and left an iPad in portrait
     reading the unfixed page. The stylesheet's own note says: one tablet breakpoint, and
     it is 760. */
  .services {
    display: grid; grid-template-columns: 1.15fr 1fr;
    column-gap: 44px; align-items: start;
  }
  /* THE SIDES ALTERNATE, and which side each row takes is decided in PHP by counting PLATES
     rather than rows — one photograph here covers two services, so alternating on the row index
     produced right, right, right, left, right: an irregularity that reads as a mistake rather
     than a rhythm. Counting plates gives right, right, left, right, left, and the repeat at the
     start is explained by the picture that spans two services. */
  .services > .svc         { grid-row: var(--r); }
  .services > .svc--left   { grid-column: 1; }
  .services > .svc--right  { grid-column: 2; }
  /* THE PHOTOGRAPH IS ALIGNED TO ITS COPY, TOP AND BOTTOM. Not floated at the top of a row it
     happens to share.

     Measured before this rule: every plate began 21px ABOVE the heading it belongs to, because the
     copy carries 26px of padding under its hairline and the plate carried 6px of margin; and on
     six of seven rows the plate was TALLER than the copy beside it — by 24 to 110px — so the row
     height was set by the picture and the text column ended in dead space above the next rule.
     Two columns flowing past each other rather than one row read as a unit.

     `stretch` makes the plate exactly as tall as its copy, the top padding matches the copy's so
     the image's top edge and the service name's cap-height start on the same line, and the image
     takes whatever is left after the caption. The crop is the right trade: these are landscape
     site photographs and the subject is central in all seven. */
  .services > .svcfig--left  { grid-column: 1; }
  .services > .svcfig--right { grid-column: 2; }
  .services > .svcfig {
    grid-row: var(--r) / span var(--rs, 1);
    max-width: none; margin: 0;
    padding-top: 26px;                 /* == the copy's padding, so the tops line up */
    /* THE COPY SIZES THE ROW; THE PLATE FILLS IT. `align-self: stretch` alone was not enough —
       a grid row sizes to the max-content of BOTH its items, so the natural height of a 514px-wide
       photograph set the row and the plate still overhung its copy by 50 to 229px. `height: 0`
       removes the plate from that calculation entirely, so the row is exactly as tall as the words
       beside it, and `min-height: 100%` then fills that height back in. */
    /* NO `overflow: hidden`. The registration ticks sit at -5px outside the plate on two corners
       and clipping the box erased them — the one detail that ties a photograph to the drafting
       language, removed to solve a cropping problem `object-fit: cover` already solves. */
    align-self: start; height: 0; min-height: 100%;
    display: flex; flex-direction: column;
  }
  /* The first row sits under the section heading, not under a rule, so it has no padding to match. */
  .services > .svcfig--first { padding-top: 0; }
  .services > .svcfig > img {
    flex: 1 1 0; min-height: 0; width: 100%; height: 100%; object-fit: cover;
  }
  /* A plate whose caption names no service is not guessed at, so it keeps the full width
     it always had and simply flows. */
  .services > .svcfig--loose { grid-column: 1 / -1; margin: 10px 0 34px; }

  /* THE SET HAS TO READ AS A SET. Every service name was 19px against 17px body — a 1.12
     ratio — and the .duo wrapper had cut the 2em of air the prose rule gives a heading,
     so the space above a new service equalled the space between two paragraphs of the
     same one. A person arriving asks "do they do pools", scans for their word, and every
     word they are scanning was the same size as the prose around it.

     The hairline is the site's own list device, the one .sectionindex draws between rows. */
  /* THE RULE RUNS ACROSS BOTH COLUMNS. On the copy alone it zigzagged with the alternation and
     read as an accident; drawn on the plate as well it is one ruled row per service, broken only
     at the gutter, which is how a drawing rules a table. */
  .services > .svc:not(:first-child),
  .services > .svcfig:not(.svcfig--first) {
    margin-top: 0; padding-top: 26px; border-top: 1px solid var(--rule);
  }
  .services > .svc:first-child { padding-top: 0; }
  .services > .svc > :is(h3, h4) {
    margin-top: 0; margin-bottom: 10px;
    font-stretch: 76%; font-weight: 900; font-size: 24px; line-height: 1.1;
    color: var(--ink);
  }
  .services > .svc > p:last-child { margin-bottom: 30px; }
  /* The plate lines up with the service name rather than floating at the top of the row. */
  .services > .svcfig .plate__cap, .services > .svcfig figcaption { margin-top: 8px; }
}

@media (min-width: 1080px) {
  .services { column-gap: 56px; }
  .services > .svc > :is(h3, h4) { font-size: 27px; }
}

/* ------------------------------------------------------- copy beside media, N times --
   A .duo is one run of copy and the ONE media group that follows it. Sections::pairMedia
   builds them only where a section holds two or more media groups — the case the shape
   rules below deliberately refuse, because `grid-row: 1 / span 99` places one element in
   a whole column and paints two on top of each other.

   Bounding the problem rather than widening it: each duo holds exactly one media group,
   so the span-99 idiom's precondition is guaranteed by construction instead of guarded
   for, and two media groups can no longer collide because they are never in one grid.

   Base is the phone: one column, media under its copy, which is what it already did. */
.duo > .mediastack { margin-block: 18px 30px; }

@media (min-width: 900px) {
  .duo {
    display: grid; grid-template-columns: 1.18fr 1fr;
    column-gap: 56px; align-items: start;
  }
  /* Everything that is words goes left. Named rather than "not media", so an element type
     nobody anticipated lands in column one and reads, instead of landing in column two and
     covering a photograph. */
  .duo > :is(p, h3, h4, ul, ol, blockquote, table) { grid-column: 1; max-width: none; }
  .duo > :is(.mediastack, figure, img, iframe) {
    grid-column: 2; grid-row: 1 / span 99; margin-block: 4px 0;
  }
  /* The gap between two pairs is the gap between two subjects, so it is the band's rhythm
     rather than a paragraph's. */
  .duo + .duo { margin-top: 44px; }
  /* A STACK OF TWO PHOTOGRAPHS IS TALLER THAN THE COPY BESIDE IT, AND THE COPY COLUMN GETS
     THE HOLE. Measured on the first pair: 330px of copy against a 590px stack, so 380px of
     white opened under the paragraph before the next pair began — the same emptiness the
     single column had, turned on its side.

     The media in a pair therefore runs ACROSS rather than down: two photographs side by side
     are about 230px tall instead of 590px, which is shorter than the copy, so the copy sets
     the row height and the column with the hole in it is the outer one, where a gap reads as
     margin rather than as something missing. */
  .duo > .mediastack { grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 14px; }
  /* SEVEN PHOTOGRAPHS AT 882px TALL WAS THE OTHER HALF OF THE PROBLEM. In a column half the
     shell's width they are already smaller; this stops a portrait plate from setting a row
     height no amount of copy beside it can fill. */
  .duo .plate img, .duo > .mediastack img { max-height: 300px; object-fit: cover; }
  .duo > .mediastack .plate img { max-height: 210px; }
}


/* ============================================================ service pages ==
   THE FIRST VERSION OF THIS BLOCK WAS FOUR RULES AND A MEDIA QUERY, and it
   produced eleven identical full-width bands: every shell 1240px, one column of
   prose in each, five consecutive sections within 176px of each other in height.
   Steve, 2026-09-02: "it's 1 half column all the way down with no design
   whatsoever."

   Nothing in DESIGN.md changed to fix it. No new color, no new face, no
   enclosure. What the page needed was three widths instead of one and five
   compositions instead of one, which is what follows.

   THREE WIDTHS, AND THEY MUST BE VISIBLY DIFFERENT:
     stage    the 1240px shell, for headings, the fork, the step run, the index
     measure  ~620px at 66ch, for copy that has a photograph beside it
     bleed    edge to edge, spent once, on the close
   ------------------------------------------------------------------------- */

/* The headline is not the thesis on these pages; the specification under it is,
   so the headline is sized to leave room rather than to win the screen. */
.band--svc h1 { font-size: clamp(38px, 6.4vw, 74px); }
.band--svc { padding-bottom: 6px; }
.band--svc + .band { padding-top: 12px; }

/* THE STANDFIRST. This sentence is why the page exists and it was setting at
   15px in --mid, which is body copy. In ink, above body size, on its own
   measure so it breaks where it means to rather than at 1176px. */
/* THE INSTRUCTION FOR READING WHAT FOLLOWS. This class was emitted by the
   template and never styled, so it rendered as a default paragraph hard against
   the heading above it: on three sections the h2 and its lead were touching.
   Above body size, in ink, with real space over it. */
.sec__lead {
  margin: 18px 0 0; max-width: 46ch;
  font-size: clamp(17px, 2vw, 20px); line-height: 1.45; color: var(--ink);
}
.band--sec h2 { margin-bottom: 0; }

.standfirst {
  margin: 14px 0 0; max-width: 30ch;
  font-size: clamp(19px, 2.4vw, 23px); line-height: 1.35; color: var(--ink);
}

/* ---------------------------------------- the specification and its plate -- */
.specplate__grid { display: grid; gap: 26px; }
/* On a phone the photograph leads. Ten spec rows stack label-over-value at
   375px, so spec-first would be a screen and a half of table before any
   evidence; the intent of the fold contract is that facts and proof arrive
   together, and at this width that means the plate first. */
.specplate__fig { order: -1; margin: 0; max-width: none; }
.specplate__fig img { width: 100%; height: auto; display: block; }
.spec--job { max-width: none; margin-top: 4px; }
.spec--job .spec__row { grid-template-columns: 1fr; gap: 4px; padding: 11px 0; }
.spec--job dt { letter-spacing: .06em; text-transform: uppercase; }
.spec--job dd { font-size: 16px; }
.spec__note { margin: 18px 0 0; max-width: 60ch; font-size: 15.5px; line-height: 1.55; color: var(--mid); }
.spec__note--after { margin-top: 6px; }
/* THE ONLY FIGURE ON A PAGE THAT PUBLISHES NO FIGURES. Set in the display voice
   because that irony is the point of the section, not despite it. */
.spec__tel {
  display: inline-block; margin: 12px 0 0;
  font-family: var(--sans); font-stretch: 64%; font-weight: 900;
  font-size: clamp(28px, 4vw, 38px); letter-spacing: -.02em;
  color: var(--ink); text-decoration: none;
  border-bottom: 3px solid var(--ink); padding-bottom: 2px;
}
.spec__tel:hover { color: var(--go); border-color: var(--go); }

/* ------------------------------------------------------------- the fork --- */
/* IT DIVIDES, IT DOES NOT ENCLOSE. Horizontal hairlines per row, one 2px ink
   rule under the heads, and ONE vertical hairline that runs the full height and
   never turns a corner. No shape closes, so the governing rule holds. A
   comparison reaches for two cards by reflex; this is what the reflex costs. */
.fork { margin: 22px 0 0; }
.fork__head { display: none; }
.fork__row { display: grid; gap: 6px 0; padding: 14px 0; border-bottom: 1px solid var(--rule); }
.fork__row:first-of-type { border-top: 2px solid var(--ink); }
.fork__label {
  font-family: var(--mono); font-size: var(--label); letter-spacing: .06em;
  text-transform: uppercase; color: var(--mid);
}
.fork__cell { font-size: 15.5px; line-height: 1.5; }
.fork__cell p { margin: 0 0 10px; max-width: none; }
.fork__cell p:last-child { margin-bottom: 0; }
/* On a phone the two values sit side by side as short cells so a reader can
   still compare them. Stacking all of one option then all of the other destroys
   the only thing a comparison does. */
.fork__row:not(.fork__row--tall) { grid-template-columns: 1fr 1fr; gap: 4px 16px; }
.fork__row:not(.fork__row--tall) .fork__label { grid-column: 1 / -1; }
.fork__row:not(.fork__row--tall) .fork__cell + .fork__cell {
  border-left: 1px solid var(--rule); padding-left: 16px;
}
.fork__resolve {
  margin: 22px 0 0; padding-top: 18px; border-top: 2px solid var(--ink);
  max-width: 34ch; font-size: clamp(19px, 2.4vw, 22px); line-height: 1.35; color: var(--ink);
}

/* --------------------------------------------------------- the pull quote -- */
/* A sentence already in the copy, set once at display scale. Not decoration:
   on the pool page it is the emotional center of the whole argument and it was
   rendering as the last five words of the third gray paragraph. */
.pull {
  margin: 26px 0 0; padding: 16px 0;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  font-family: var(--sans); font-stretch: 64%; font-weight: 900;
  text-transform: uppercase; letter-spacing: -.03em; line-height: 1.06;
  font-size: clamp(26px, 4.4vw, 34px); color: var(--ink); max-width: 18ch;
}

/* ------------------------------------------------- copy beside a plate ----- */
/* NAMED .svcduo AND NOT .duo. `.duo` is an existing component for the harvested
   pages, and it places any direct `figure` at `grid-column: 2; grid-row: 1 /
   span 99`. Reusing the name put my figure in ninety-nine grid rows: the section
   measured 4,707px with children of 474px and 333px, and the page went from
   5,709px to 15,063px. A name collision, not a layout mistake, and it is why the
   plate below resets both grid properties explicitly rather than trusting that
   nothing else claims them. */
.svcduo { display: grid; gap: 24px; }
.svcduo__plate { margin: 0; max-width: none; grid-row: auto; grid-column: auto; }
.svcduo__plate img { width: 100%; height: auto; display: block; }
/* Named findings rather than more paragraphs. "The first is buoyancy" was a
   sentence; as a label it is a finding on a sheet, which is what it is. */
.find { padding: 14px 0; border-top: 1px solid var(--rule); }
.find:first-child { border-top: 2px solid var(--ink); }
.find__k {
  display: block; margin-bottom: 6px;
  font-family: var(--mono); font-size: var(--label); letter-spacing: .06em;
  text-transform: uppercase; color: var(--mid);
}
.find__v p { margin: 0 0 10px; }
.find__v p:last-child { margin-bottom: 0; }

/* ------------------------------------------------------- the step run ------ */
/* A vertical run, not a grid. Seven does not divide into the five-column desktop
   grid .how uses elsewhere, and an <ol> inside two-column prose reads 1-2-3 down
   the left then jumps back to the top for 4-5-6-7, which breaks the one thing a
   sequence has to preserve. The numbering exemption is DESIGN.md's own: numbers
   are permitted where they are real wayfinding, and a sequence is. */
/* ONE COLUMN, EXPLICITLY. `.how` sets a five-column grid at desktop for the
   home page's five-step run, so seven steps landed 5 + 2 and left the orphan row
   the whole vertical-run decision exists to avoid. Measured: five 214px columns,
   two rows, seven steps. Overriding the track list is the only way a modifier
   can win against a component that defines its own. */
.how--run { margin-top: 24px; grid-template-columns: 1fr; }
.how--run .how__step { grid-template-columns: 52px 1fr; gap: 16px; align-items: start; }
.how--run .how__n { font-size: 30px; line-height: .9; font-variant-numeric: tabular-nums; color: var(--ink); }
.how--run .how__t { margin: 0 0 4px; font-size: 17px; }
.how--run .how__body p { margin: 0; max-width: 64ch; font-size: 15px; color: var(--mid); }

/* --------------------------------------------------------------- the faq --- */
.faq--indexed { columns: 1; margin-top: 22px; }
.faq__i, .faq__last { display: grid; grid-template-columns: 34px 1fr; gap: 0 12px; }
.faq__i { padding-top: 16px; border-top: 1px solid var(--rule); margin-bottom: 20px; break-inside: avoid; }
.faq__n {
  grid-row: 1 / span 2;
  font-family: var(--mono); font-size: var(--label); letter-spacing: .04em; color: var(--mid);
  font-variant-numeric: tabular-nums; padding-top: 3px;
}
.faq--indexed h3, .faq__last h3 {
  margin: 0 0 6px; border: 0; padding: 0;
  font-family: var(--sans); font-stretch: 76%; font-weight: 800; line-height: 1.2;
  font-size: 17px; color: var(--ink);
}
.faq--indexed p, .faq__last p { margin: 0; max-width: 52ch; font-size: 15px; line-height: 1.5; color: var(--mid); }
/* The money question is always last, and last in a two-column flow is
   bottom-right, the least-read position on the page. The copy order does not
   change; the setting does, and its answer hands straight into the black band. */
.faq__last { margin-top: 8px; padding-top: 20px; border-top: 2px solid var(--ink); }
.faq__last h3 { font-size: clamp(21px, 3vw, 26px); font-stretch: 64%; font-weight: 900; text-transform: uppercase; letter-spacing: -.02em; }
.faq__last p { font-size: 16.5px; max-width: 62ch; color: var(--ink); }

/* -------------------------------------------------------- the area index --- */
.area__grid { display: grid; gap: 18px; }
.area__k {
  margin: 26px 0 10px; padding-top: 14px; border-top: 2px solid var(--ink);
  font-family: var(--mono); font-size: var(--label); letter-spacing: .06em;
  text-transform: uppercase; color: var(--mid);
}
.band--area .terms { gap: 10px 20px; }
.band--area .terms a { font-size: 15px; }

/* -------------------------------------------------------------- the close -- */
.band--close { padding-block: 46px 52px; }
.close__line {
  margin: 0; max-width: 22ch;
  font-family: var(--sans); font-stretch: 64%; font-weight: 900;
  text-transform: uppercase; letter-spacing: -.03em; line-height: 1.04;
  font-size: clamp(30px, 5.6vw, 48px); color: #fff;
}
.close__tel {
  display: inline-block; margin: 20px 0 0;
  font-family: var(--sans); font-stretch: 64%; font-weight: 900;
  font-size: clamp(28px, 4.6vw, 40px); letter-spacing: -.02em;
  color: #fff; text-decoration: none; border-bottom: 3px solid #fff; padding-bottom: 2px;
}
.close__tel:hover { border-color: var(--go); color: var(--go); }
.band--close .actions { margin-top: 26px; }
.band--close .btn--primary { min-height: 56px; }

@media (min-width: 760px) {
  .specplate__grid { grid-template-columns: 1fr 1fr; gap: 40px; align-items: start; }
  /* The plate returns to the right once there is room, and its top registration
     tick sits on the specification's opening ink rule. */
  .specplate__fig { order: 0; }
  .specplate__fig img { aspect-ratio: 4 / 5; object-fit: cover; }
  .spec--job .spec__row { grid-template-columns: 168px 1fr; gap: 20px; align-items: baseline; }

  .fork__head {
    display: grid; grid-template-columns: 160px 1fr 1fr; gap: 0 24px;
    padding-bottom: 10px; border-bottom: 2px solid var(--ink);
  }
  .fork__h {
    font-family: var(--sans); font-stretch: 64%; font-weight: 900;
    text-transform: uppercase; letter-spacing: -.02em; font-size: clamp(20px, 2.4vw, 30px);
  }
  .fork__h + .fork__h { border-left: 1px solid var(--rule); padding-left: 24px; }
  .fork__row, .fork__row--tall { grid-template-columns: 160px 1fr 1fr; gap: 0 24px; }
  .fork__row:first-of-type { border-top: 0; }
  .fork__row .fork__label { grid-column: auto; }
  .fork__row .fork__cell + .fork__cell { border-left: 1px solid var(--rule); padding-left: 24px; }
  .fork__row:not(.fork__row--tall) .fork__cell + .fork__cell { padding-left: 24px; }
  .fork__resolve { max-width: 44ch; }

  .svcduo { grid-template-columns: 1.15fr 1fr; gap: 40px; align-items: start; }
  .sec__prose--pulled { display: grid; grid-template-columns: 1.4fr 1fr; gap: 40px; align-items: start; }
  .pull { margin-top: 4px; }
  .how--run { grid-template-columns: 1fr; }
  .how--run .how__step { grid-template-columns: 88px 1fr; gap: 22px; }
  /* The numerals sit in their own gutter with a rule down it, the way a revision
     column does on a drawing, so the eye runs a hard left edge and cannot lose
     its place in the sequence. */
  .how--run .how__n { border-right: 1px solid var(--rule); padding-right: 22px; min-height: 52px; }
  .how--run .how__n { font-size: 40px; }
  .how--run .how__t { font-size: 20px; }
  .faq--indexed { columns: 2; column-gap: 44px; }
  .faq__i, .faq__last { grid-template-columns: 44px 1fr; }
  .area__grid { grid-template-columns: 1fr 1fr; gap: 40px; }
}

/* ------------------------------------------------- two sets, side by side -- */
/* NOT THE FORK. The fork compares parallel attributes across two options, row by
   row. This is two lists of different lengths about different things, and pairing
   them into rows would invent a correspondence the content does not have. Same
   language — one vertical hairline, heads on a rule, nothing enclosed — because
   it belongs to the same drawing; different structure, because it is different
   content. */
.cols { display: grid; gap: 26px; margin-top: 22px; border-top: 2px solid var(--ink); padding-top: 18px; }
.cols__h {
  margin: 0 0 12px;
  font-family: var(--sans); font-stretch: 64%; font-weight: 900;
  text-transform: uppercase; letter-spacing: -.02em; font-size: clamp(18px, 2.2vw, 24px);
}
.cols__list { list-style: none; margin: 0; padding: 0; }
.cols__list li {
  padding: 9px 0; border-bottom: 1px solid var(--rule);
  font-size: 15.5px; line-height: 1.45;
}
@media (min-width: 760px) {
  .cols { grid-template-columns: 1fr 1fr; gap: 0 40px; }
  .cols__c + .cols__c { border-left: 1px solid var(--rule); padding-left: 40px; }
}

/* ------------------------------------------------------- the rehearsal mark --- */
/* Appended at TOP LEVEL, and checked, because this file is 2,400 lines of nested
   breakpoints and the last block appended to it landed inside an open
   @media (max-width: 759px) and silently did nothing above 760px.

   IT PARTICIPATES IN NO LAYOUT. position: fixed, so it moves nothing and changes no
   measurement on a page somebody is judging. It sits above the call bar rather than
   under it, because the call bar is fixed to the same edge. */
.rehearsal-mark {
  position: fixed; z-index: 30;
  left: 10px; bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  margin: 0; padding: 4px 9px;
  background: var(--ink); color: var(--paper);
  font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase;
  /* Readable, and not competing with the page. Full strength on hover or focus for
     anybody who wants to read the title. */
  opacity: .45; transition: opacity 120ms ease-out;
}
.rehearsal-mark:hover { opacity: 1; }
/* The call bar is fixed to the same edge, so the mark clears it when there is one. Which
   pages have one is decided in the layout and handed here as a class: `:has()` inside
   `:not()` matched nothing in practice, and the failure was silent and looked deliberate. */
.rehearsal-mark--above-bar { bottom: calc(68px + env(safe-area-inset-bottom, 0px)); }

/* ------------------------------------------------------------ phone menu (#787) --
   Below 760 the call bar fixed to the foot of the screen already carries Call and Order online,
   so the masthead's copies go and a Menu button takes their place. The nav folds away only once
   site.js has revealed the button (.has-menu), so with no script it stays open. Open, it is a
   list: one link per row at 48px, and the two groups open in place rather than as flyouts. */
.menubtn { display: none; }
@media (max-width: 759px) {
  .masthead__bar .headbar__actions { display: none; }
  .menubtn:not([hidden]) {
    display: inline-flex; align-items: center; gap: 10px; margin-left: auto;
    min-height: 44px; padding: 0 14px; border: 1.5px solid var(--paper); border-radius: 0;
    background: transparent; color: var(--paper); cursor: pointer;
    font-family: var(--mono); font-size: 14px; letter-spacing: .08em; text-transform: uppercase;
  }
  .menubtn__bars, .menubtn__bars::before, .menubtn__bars::after {
    display: block; width: 18px; height: 2px; background: currentColor;
    transition: transform 140ms ease, opacity 140ms ease;
  }
  .menubtn__bars { position: relative; }
  .menubtn__bars::before, .menubtn__bars::after { content: ""; position: absolute; left: 0; }
  .menubtn__bars::before { top: -6px; }
  .menubtn__bars::after  { top: 6px; }
  .is-open .menubtn__bars { background: transparent; }
  .is-open .menubtn__bars::before { transform: translateY(6px) rotate(45deg); }
  .is-open .menubtn__bars::after  { transform: translateY(-6px) rotate(-45deg); }

  .has-menu:not(.is-open) .headbar { display: none; }
  .has-menu .nav { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .has-menu .nav > * + * { border-top: 1px solid var(--rule); }
  .has-menu .navgrp__label { display: flex; min-height: 48px; justify-content: space-between; }
  .has-menu .navgrp__menu {
    position: static; min-width: 0; padding: 0 0 6px 16px; border: 0; background: none;
  }
  .has-menu .navgrp__menu li + li { border-top: 0; }
  .has-menu .navgrp__menu a { min-height: 44px; padding: 0; }
  /* The current page's bar hangs in the indent, so its label lines up with the others. */
  .has-menu .navgrp__menu a[aria-current='page']::before { margin: 8px 5px 8px -8px; }
  .has-menu .nav__rest {
    flex-direction: column; gap: 0; padding: 0; overflow: visible;
    -webkit-mask-image: none; mask-image: none;
  }
  .has-menu .nav__rest li + li { border-top: 1px solid var(--rule); }
  .has-menu .nav__rest a { min-height: 48px; }
}

/* Footer social links (#832), as icons (#856): one wrapping row of 44px tap targets, each icon
   22px in the footer's own text color, so it needs no per-brand palette to stay legible. */
.foot__social { display: flex; flex-wrap: wrap; gap: 4px 8px; margin: 10px 0 0; padding: 0; list-style: none; }
.foot .foot__social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; min-height: 44px; padding: 0; margin-left: -11px; margin-right: 0;
  border-radius: 50%; color: var(--paper); opacity: .85;
}
.foot .foot__social li:not(:first-child) a { margin-left: 0; }
.foot .foot__social a:hover, .foot .foot__social a:focus-visible { opacity: 1; background: rgb(255 255 255 / .1); }
.foot__icon { width: 22px; height: 22px; fill: currentColor; }

/* The Suite's chat bubble (#470) sits 20px off the bottom of the screen, which on a phone put it
   on top of the call bar's Order online button. Where the template drew a bar, lift the bubble and
   its panel clear of it, the same 68px the rehearsal mark uses. The widget adds .msc-root at the
   end of <body>, after the bar, so the sibling selector only matches when a bar is there. */
@media (max-width: 759px) {
  .callbar ~ .msc-root .msc-launcher { bottom: calc(68px + env(safe-area-inset-bottom, 0px)); }
  .callbar ~ .msc-root .msc-panel {
    bottom: calc(140px + env(safe-area-inset-bottom, 0px));
    max-height: calc(100vh - 188px);
  }
}
