/* ════════════════════════════════════════════════════════════════════════════
   css/sell.css — THE FOUR TIER 2 SELLING PAGES UNDER TECHNOLOGY
   service.tnaado.ca

     /web-design-toronto                 websites
     /ecommerce-website-design-toronto   online stores
     /shopify-development-toronto        Shopify
     /custom-software-toronto            custom software

   ONE SHEET FOR FOUR PAGES, deliberately. These are the same document with a
   different subject: hero, what you get, the rates as arithmetic, how long,
   proof, what is not included, four questions, book. A buyer who learns one
   can read any of them, and that is only true if one stylesheet draws all
   four. Four sheets would drift, and the drift would be invisible until a
   figure disagreed with itself on two pages.

   WHAT THIS SHEET IS NOT. It is not a design system and it does not know about
   the storm. There is no scene, no stage, no camera and no carousel on a Tier 2
   page: STRUCTURE.md says one job per page, and the animation belongs to the
   Tier 1 door above it. Nothing here is scroll-linked, and there is no
   JavaScript this sheet depends on — every page renders complete with scripts
   off. The chrome (bar, nav sheet, foot sheet) is css/chrome.css's and is not
   restyled here; the only thing this file does about the chrome is leave it
   alone.

   ─── BRAND, LOCKED ────────────────────────────────────────────────────────
   ink #0a0a0a · red #c8102e · bone #f5f1e8.

   --red is 3.37:1 on ink. As SMALL TEXT ON INK IT IS BANNED. It is for rules,
   fills, and display type at 24px and above, where 3:1 is the bar and 3.37
   clears it. Every small red text run resolves to one of two measured values:

     on ink   --red-ink   #ef5c6e   6.06:1
     on bone  --red-bone  #a4132c   6.60:1

   Measured for the record, so nobody has to re-derive them:
     bone on ink      #f5f1e8 / #0a0a0a   17.5:1
     ink on bone      #0a0a0a / #f5f1e8   17.5:1
     --dim-ink on ink #b9b3a6 / #0a0a0a    9.4:1
     --dim-bone/bone  #4a453c / #f5f1e8    8.4:1
     --red on bone    #c8102e / #f5f1e8    5.2:1   (display only, by rule)

   ─── COMMENT DISCIPLINE, AND WHY IT IS WRITTEN DOWN ───────────────────────
   The previous /web-design-toronto shipped with a stray comment terminator that
   closed a comment early. The rule that followed it — the one that painted the
   free deliverable — never applied, so the copy rendered ink-on-ink at 1.01:1:
   an invisible offer, on a page paying for its own traffic.

   So: every comment here opens once and closes once, never nested, and the
   opener and closer tokens are NEVER WRITTEN AS PROSE ANYWHERE IN THIS FILE,
   not even inside a comment describing them — a comment that talks about the
   terminator contains the terminator, and closes itself where the sentence is.
   That is exactly how this paragraph broke on its first draft, and it broke
   silently: 30 lines of tokens and the whole baseline block were parsed as
   garbage while the file still looked like a considered stylesheet.
   The balance is asserted with a token walk before the file is saved.

   ─── FIGURES ──────────────────────────────────────────────────────────────
   Every column of money uses `font-variant-numeric: tabular-nums`, applied
   here rather than per page, because a rate card whose digits do not align is
   the one visual defect that makes a published price list look unpublished.
   .fig is the class; it carries the mono face and the tabular figures
   together so the two cannot be applied separately.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  --ink:       #0a0a0a;
  --ink-2:     #101010;
  --ink-3:     #161616;
  --bone:      #f5f1e8;
  --bone-2:    #ece7db;
  --red:       #c8102e;
  --red-ink:   #ef5c6e;
  --red-bone:  #a4132c;
  --dim-ink:   #b9b3a6;
  --dim-bone:  #4a453c;
  --hair-ink:  rgba(245, 241, 232, .16);
  --hair-bone: rgba(10, 10, 10, .16);

  /* The three faces index.html loads, from the same URL and at the same
     weights. An earlier selling page shipped Bodoni and Inter, so a reader who
     came through the Technology door met a different site on the other side of
     it. Fraunces is loaded at 400 only: font-synthesis is left at the browser
     default but nothing here asks for a weight that is not in the file. */
  --serif: 'Fraunces', 'Times New Roman', Times, serif;
  --sans:  'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono:  'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --pad:  clamp(1.25rem, 4vw, 3rem);
  --rail: 72rem;
  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* ── BASELINE ──────────────────────────────────────────────────────────────
   Local and small. These pages do not link css/site.css: it is 86KB built for
   the legacy pages, it sets `scroll-behavior: smooth` globally, and none of it
   is needed to draw a rate card. */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { display: block; max-width: 100%; height: auto; }
h1, h2, h3, p, ul, ol, dl, dd, figure, table { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; }

.u-vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* THE FOCUS RING, and it is not optional. Two values, because one ring cannot
   be visible on both grounds: --red-ink on the dark bands, --red-bone on the
   light ones. `--focus` is redefined by .band--bone, so any control inherits
   the right one from the band it sits in without knowing where it is. */
:root { --focus: var(--red-ink); }

a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 1px;
}

/* ── RAIL AND BANDS ────────────────────────────────────────────────────────
   One measure, one rhythm. A band is a full-bleed colour field; the rail is
   the column of content inside it. Nothing on these pages is wider than the
   rail except a photograph that is deliberately bled. */
.rail {
  width: min(100% - (var(--pad) * 2), var(--rail));
  margin-inline: auto;
}

.band {

/* ── THE 2026-08-22 DENSITY PASS ──────────────────────────────────────────
   Ethan: "sizing of sections and images and booking forms all too large ...
   like you're treating the reader as a toddler". The root pass had already
   brought css/reference.css's heading scale down to h1 3.5rem / h2 2.125rem /
   h3 1.4375rem and css/site.css's section padding to ~4.25rem. This sheet is
   brought onto those same two numbers, because five sheets each inventing
   their own display size is how a site ends up shouting on one page and
   whispering on the next. Every value below is the reference scale, not a
   new one. */
  padding-block: clamp(2.25rem, 4.6vw, 4rem);
  border-top: 1px solid var(--hair-ink);
}
.band--first { border-top: 0; }
.band--tight { padding-block: clamp(1.75rem, 3.2vw, 2.75rem); }

.band--bone {
  --focus: var(--red-bone);
  background: var(--bone);
  color: var(--ink);
  border-top-color: var(--hair-bone);
}
.band--ink2 { background: var(--ink-2); }

/* ── TYPE ──────────────────────────────────────────────────────────────────
   Three sizes of heading and one size of body, and that is the whole scale.
   Ethan's complaint about the old site was density, and density is mostly the
   number of distinct type treatments on a page rather than the word count. */
.kicker {
  font-family: var(--mono);
  font-size: .75rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--red-ink);
  margin-bottom: .875rem;
}
.band--bone .kicker { color: var(--red-bone); }

.h2 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.625rem, 3.1vw, 2.125rem);
  line-height: 1.08;
  letter-spacing: -.015em;
  max-width: 30ch;
}
/* Display type only — 30px at the smallest step, so the red is used where the
   brand rule permits it and nowhere else. */
.h2 em { font-style: italic; color: var(--red-ink); }
.band--bone .h2 em { color: var(--red); }

.h3 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.375rem;
  line-height: 1.2;
  letter-spacing: -.01em;
}

.lede {
  margin-top: 1.25rem;
  max-width: 58ch;
  font-size: 1.0625rem;
  color: var(--dim-ink);
}
.band--bone .lede { color: var(--dim-bone); }
.lede b, .lede strong { color: var(--bone); font-weight: 600; }
.band--bone .lede b, .band--bone .lede strong { color: var(--ink); font-weight: 600; }

.note {
  margin-top: 1.5rem;
  max-width: 62ch;
  font-size: .9375rem;
  color: var(--dim-ink);
}
.band--bone .note { color: var(--dim-bone); }
.note b { color: var(--bone); font-weight: 600; }
.band--bone .note b { color: var(--ink); }

.note a, .lede a {
  color: var(--red-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.band--bone .note a, .band--bone .lede a { color: var(--red-bone); }

/* FIGURES. The mono face and tabular figures in one class, so a column of
   money can never get one without the other. */
.fig {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* ── BUTTONS ───────────────────────────────────────────────────────────────
   Two only. Solid is the booking action and there is exactly one per band. */
.btn {
  display: inline-flex; align-items: center; gap: .625rem;
  padding: .875rem 1.375rem;
  border: 1px solid currentColor;
  font-family: var(--sans);
  font-size: .9375rem;
  font-weight: 600;
  text-decoration: none;
  transition: background-color .18s var(--ease), color .18s var(--ease);
}
.btn i { font-style: normal; }
.btn:hover { background: rgba(245, 241, 232, .1); }
.band--bone .btn:hover { background: rgba(10, 10, 10, .07); }

.btn--solid {
  background: var(--bone); color: var(--ink); border-color: var(--bone);
}
.btn--solid:hover { background: #fff; border-color: #fff; }
.band--bone .btn--solid {
  background: var(--red); color: var(--bone); border-color: var(--red);
}
.band--bone .btn--solid:hover { background: #a4132c; border-color: #a4132c; }

.btns { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2rem; }

/* ── HERO ──────────────────────────────────────────────────────────────────
   FIVE THINGS AND NO MORE, and the count is the design. Ethan, of the version
   before this one: "what im seeing on chrome is so ugly and complciated and
   overwhelming". Measured at 1440x900 that version put SIXTEEN separate things
   on the first screen — two chrome rows, an eyebrow, a headline, a four-line
   paragraph, a bordered price box carrying three more lines, two buttons, a
   mailto sentence, a four-item fact strip, a photograph, a caption and a dense
   six-line paragraph. Every one of them competed and none of them led.

   What is left: the headline, two lines of lede, the price, ONE button and the
   photograph. What went where:
     - the eyebrow was deleted; it said the same words as the H1
     - the second button became the bypass row's "02 Prices", which is a link
       to the same anchor and is already on every page
     - the mailto line was deleted; the closing booking band carries the address
     - the fact strip moved down to the timeline band, where two of its four
       facts are about time anyway
     - the caption was deleted; it repeated the address the lede states
     - the free-deliverable paragraph was deleted; the terminal booking band was
       already printing the whole offer, so the hero copy was a duplicate rather
       than a placement — see THE FREE DELIVERABLE below
   Everything above is elaboration and belongs below the fold. */
/* THE HERO OWNS THE FIRST SCREEN, and it is sized to do that rather than
   left to land wherever the content stops. Cut to five things the hero got
   SHORT — at 1440x900 it ended at y=760 and the next band's bone field pushed a
   140px empty slab into the bottom of the first screen with its kicker sitting
   on the viewport edge. Emptier, but not calmer: a bright bar with a cropped
   heading in it reads as a mistake.

   THE CHROME IS position:fixed AND NEVER SCROLLS AWAY. Measured: 94px at 1440
   (52 menubar + 42 bypass) and 86px at 390 (52 + 34) — 10.4% and 10.2% of those
   two viewports, taken off every screen of the page and not only the first. So
   a hero asking for 100svh overflows the first screen by exactly that much and
   the subtraction is not optional. It is a local variable because css/chrome.css
   owns the real number and this file may not edit it; if the bar ever changes
   height, this is the one place to follow it.

   The remainder is deliberate: the next band's edge lands just inside the fold,
   so the reader gets a colour change telling them to keep going and nothing
   else. */
.hero {
  --chrome-h: 5.75rem;              /* 86px of chrome + the body offset */
  padding-block: clamp(2rem, 4vw, 3.25rem) clamp(2.25rem, 4.6vw, 3.75rem);
  min-height: calc(100svh - var(--chrome-h) - 5rem);
  display: grid;
  align-content: start;
}

/* ON A PHONE the content sits at the top of that height and the slack falls
   BELOW it — `start`, not `center`. Centred, a five-thing hero floated in the
   middle of the screen with 150px of nothing above the headline, which is a
   different kind of wrong. The remainder pushes the next band's heading off the
   first screen and leaves only its edge showing.

   AT 1440 the slack goes to both sides, because the hero is a composition
   there rather than a stack, and the photograph beside the type needs room
   above and below it to sit in. */
@media (min-width: 53.75rem) {
  .hero {
    --chrome-h: 6.25rem;            /* 100px: 94px of chrome + the body offset */
    min-height: calc(100svh - var(--chrome-h) - 2.5rem);
    align-content: center;
  }
}

.hero__grid {
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
}
@media (min-width: 62rem) {
  /* Centred, not top-aligned. With the column cut to four things it is now
     shorter than the photograph beside it, and `start` left a visible hole
     under the button — the thing that reads as an unfinished layout. */
  .hero__grid { grid-template-columns: 1.05fr .95fr; align-items: center; }
}

.hero__h1 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.25rem, 5.2vw, 3.5rem);
  line-height: 1.02;
  letter-spacing: -.022em;
  max-width: 18ch;
}
.hero__h1 em { font-style: italic; color: var(--red-ink); }

/* Two lines at 1440, three at 390. The measure is the limit that enforces it —
   the old 52ch ran to four lines and a four-line paragraph in a hero is a wall,
   not an introduction. */
.hero__lede {
  margin-top: 1.375rem;
  max-width: 42ch;
  font-size: 1.0625rem;
  color: var(--dim-ink);
}

/* THE PRICE. The anchor figure, and it is the first number on the page on
   purpose: PRICES.md's rule is low to high, and a page that opens on its
   ceiling is closed to everyone who cannot spend the ceiling today.

   IT IS NO LONGER A BOX. The bordered, filled, red-barred plate was one of the
   two things making the same argument as the fact strip, and a box around a
   number in a hero that also contains a photograph gives the eye two frames to
   choose between. A hairline above it separates it from the lede and nothing
   else is drawn. NEVER ANIMATED: it paints at full opacity on frame one at
   375px with JS off. Nothing in this file gives it an opacity, a transform or
   a delay. */
.plate {
  margin-top: clamp(2rem, 4vw, 2.75rem);
  padding-top: 1.375rem;
  border-top: 1px solid var(--hair-ink);
  max-width: 26rem;
}
.band--bone .plate { border-top-color: var(--hair-bone); }
.plate__label {
  font-family: var(--mono);
  font-size: .6875rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--red-ink);
}
.band--bone .plate__label { color: var(--red-bone); }
.plate__row { display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap; margin-top: .5rem; }
.plate__n {
  font-family: var(--serif);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2.25rem, 5.2vw, 3.5rem);
  line-height: 1;
  letter-spacing: -.02em;
}
.plate__unit {
  font-family: var(--mono);
  font-size: .8125rem;
  letter-spacing: .06em;
  color: var(--dim-ink);
}
.band--bone .plate__unit { color: var(--dim-bone); }

/* THE HERO PHOTOGRAPH. Real work, at a real size. Ethan: "i love the photo
   secltions the layout etc" — so the photograph is a column of the hero, not a
   decoration behind the type. The caption it used to carry is gone: it named
   the address the lede already states, and a mono caption stacked above a
   bold-led paragraph was two registers doing one job. The alt text still says
   what the picture is, which is where that description belongs. */
.shot { margin: 0; }
.shot img {
  width: 100%;
  border: 1px solid var(--hair-ink);
  background: var(--ink-3);
}
.band--bone .shot img { border-color: var(--hair-bone); }

/* THE TRUST ROW. Four facts, one of which is always the address. It is no
   longer in the hero — it and the price were making the same argument, and the
   brief is to pick one. It closes the timeline band instead, where "written
   scope in 2 business days" is on topic rather than competing. */
.trust {
  display: flex; flex-wrap: wrap;
  gap: .5rem 1.75rem;
  margin-top: clamp(2.25rem, 5vw, 3rem);
  padding-top: 1.25rem;
  border-top: 1px solid var(--hair-ink);
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .05em;
  color: var(--dim-ink);
}
.band--bone .trust { border-top-color: var(--hair-bone); color: var(--dim-bone); }
.trust li { display: flex; align-items: center; gap: .5rem; }
.trust li::before {
  content: ''; width: 6px; height: 6px; flex: none;
  background: var(--red);
}

/* ── THE FREE DELIVERABLE — IT ALREADY HAD A HOME ──────────────────────────
   The hero used to carry six lines of dense prose about the free deliverable,
   crushed under the photograph: a strong offer, drowning. The obvious fix was
   to give it a band of its own with room around it. It turned out not to need
   one. The terminal booking band already prints it in full — `.bkx__offer-k`,
   `.bkx__offer-h` and `.bkx__offer-p` in css/booking-band.css — with a kicker,
   a heading naming the deliverable and the same paragraph, below the fold, with
   room around it, immediately beside the control that books the call. So the
   hero copy was not a placement, it was a duplicate, and the fix was to delete
   it rather than to move it. Verified on all seven selling pages: every one of
   them names its deliverable in the booking band.

   Do not reintroduce a hero note, and do not add a third placement. An earlier
   version of these pages carried the offer THREE times — hero, a band of its
   own, and the closing band — and that is how a page becomes the thing Ethan
   called overwhelming. */

/* ── CARDS: WHAT YOU GET ───────────────────────────────────────────────────
   Three at most, ever. The old pages carried a WHY US grid and a PROOF pair on
   top of this, and both became one link each. A paragraph a buyer does not
   need in order to decide is a link, not a shorter paragraph. */
.cards {
  display: grid;
  gap: 1px;
  margin-top: 2.5rem;
  background: var(--hair-ink);
  border: 1px solid var(--hair-ink);
}
.band--bone .cards { background: var(--hair-bone); border-color: var(--hair-bone); }
@media (min-width: 48rem) { .cards { grid-template-columns: repeat(3, 1fr); } }

.card { padding: 1.75rem 1.5rem; background: var(--ink); }
.band--bone .card { background: var(--bone); }
.card__n {
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .14em;
  color: var(--red-ink);
}
.band--bone .card__n { color: var(--red-bone); }
.card h3 { margin: .625rem 0 .625rem; }
.card p { font-size: .9375rem; color: var(--dim-ink); }
.band--bone .card p { color: var(--dim-bone); }

/* ── THE RATE LIST ─────────────────────────────────────────────────────────
   A LIST, NOT A TABLE, and the reason is 320px. A three-column table of rates
   either overflows the viewport or needs its own scroller; a flex row wraps the
   note under the name and keeps the figure on the right at every width. The
   figure column is a fixed character measure with tabular digits, so the
   money still forms a column — which is the only thing the table was for. */
.rates { margin-top: 2.5rem; border-top: 1px solid var(--hair-ink); }
.band--bone .rates { border-top-color: var(--hair-bone); }

.rate {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .75rem 1.5rem; flex-wrap: wrap;
  padding: 1.0625rem 0;
  border-bottom: 1px solid var(--hair-ink);
}
.band--bone .rate { border-bottom-color: var(--hair-bone); }
.rate__t { flex: 1 1 16rem; min-width: 0; }
.rate__name { font-size: 1rem; font-weight: 600; }
.rate__note {
  display: block; margin-top: .1875rem;
  font-size: .8125rem; color: var(--dim-ink);
}
.band--bone .rate__note { color: var(--dim-bone); }
.rate__fig {
  flex: none;
  min-width: 8.5ch;
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-size: 1.0625rem;
  font-weight: 500;
}
.rate--head {
  padding: .75rem 0;
  font-family: var(--mono);
  font-size: .6875rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--dim-ink);
}
.band--bone .rate--head { color: var(--dim-bone); }

/* ── THE SUM ───────────────────────────────────────────────────────────────
   Arithmetic as a design element. PRICES.md's whole promise is that a buyer
   can add the job up themselves, so every total on these pages is printed as
   its own addition with the operands visible. A table here, because this IS
   tabular and it has a total row — two columns, so it holds at 320px without a
   scroller. */
.sum {
  width: 100%;
  margin-top: 2.5rem;
  border-collapse: collapse;
  font-size: .9375rem;
}
.sum caption {
  caption-side: top;
  text-align: left;
  padding-bottom: .875rem;
  font-family: var(--mono);
  font-size: .6875rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--dim-ink);
}
.band--bone .sum caption { color: var(--dim-bone); }
.sum th, .sum td {
  padding: .9375rem .5rem;
  border-bottom: 1px solid var(--hair-ink);
  vertical-align: baseline;
}
.band--bone .sum th, .band--bone .sum td { border-bottom-color: var(--hair-bone); }
.sum th { text-align: left; font-weight: 600; padding-left: 0; }
.sum th small {
  display: block; margin-top: .1875rem;
  font-size: .8125rem; font-weight: 400; color: var(--dim-ink);
}
.band--bone .sum th small { color: var(--dim-bone); }
.sum td {
  text-align: right; padding-right: 0;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-weight: 500;
  white-space: nowrap;
}
.sum tr:last-child th, .sum tr:last-child td { border-bottom: 0; }

.sum__save td, .sum__save th { color: var(--red-ink); }
.band--bone .sum__save td, .band--bone .sum__save th { color: var(--red-bone); }

.sum__total th, .sum__total td {
  border-top: 2px solid var(--red);
  padding-top: 1.125rem;
  font-size: 1.125rem;
}
.sum__total td { font-size: 1.375rem; }

/* ── TIERS ─────────────────────────────────────────────────────────────────
   THE CEILING IS A CAP, NOT A SUM. There is no equals sign anywhere in this
   block, and there must never be one: features are priced FROM $1,800 and rise
   with difficulty, so the distance between a tier's maximum contents at list
   rates and its ceiling is real headroom. Printing `contents = ceiling` would
   claim a job at the cap is the cheapest possible version of itself. */
.tiers { display: grid; gap: 1px; margin-top: 2.5rem; background: var(--hair-ink); border: 1px solid var(--hair-ink); }
.band--bone .tiers { background: var(--hair-bone); border-color: var(--hair-bone); }
@media (min-width: 56rem) { .tiers { grid-template-columns: repeat(3, 1fr); } }

.tier { padding: 1.5rem; background: var(--ink); }
.band--bone .tier { background: var(--bone); }
.tier__n {
  font-family: var(--mono); font-size: .75rem; letter-spacing: .14em;
  color: var(--dim-ink);
}
.band--bone .tier__n { color: var(--dim-bone); }
.tier__name { margin-top: .375rem; font-family: var(--serif); font-size: 1.25rem; }
.tier__range {
  margin-top: .625rem;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-size: 1rem; font-weight: 500;
}
.tier__body { margin-top: .75rem; font-size: .875rem; color: var(--dim-ink); }
.band--bone .tier__body { color: var(--dim-bone); }
.tier__cap {
  display: block; margin-top: .625rem;
  font-size: .8125rem; color: var(--red-ink);
}
.band--bone .tier__cap { color: var(--red-bone); }

/* ── STEPS ─────────────────────────────────────────────────────────────────
   Four or five stages with real durations, and the durations add up in the
   line under the list. Nothing here is "fast" or "agile"; it is a number of
   weeks. */
.steps { margin-top: 2.5rem; }
.step {
  display: grid;
  gap: .375rem 1.5rem;
  padding: 1.375rem 0;
  border-top: 1px solid var(--hair-ink);
}
.band--bone .step { border-top-color: var(--hair-bone); }
@media (min-width: 48rem) {
  .step { grid-template-columns: 3.5rem 1fr 11rem; align-items: baseline; }
}
.step__n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: .875rem; font-weight: 500;
  color: var(--red-ink);
}
.band--bone .step__n { color: var(--red-bone); }
.step__name { font-weight: 600; }
.step__name span { display: block; margin-top: .25rem; font-weight: 400; font-size: .9375rem; color: var(--dim-ink); }
.band--bone .step__name span { color: var(--dim-bone); }
.step__dur {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-size: .875rem;
  color: var(--dim-ink);
}
.band--bone .step__dur { color: var(--dim-bone); }
@media (min-width: 48rem) { .step__dur { text-align: right; } }

/* ── PROOF ─────────────────────────────────────────────────────────────────
   Photographs and real captures of software this firm runs. Two per page, at a
   size where they can be read. Every caption says what it is and, where the
   capture is fixture data, says that too — a dashboard full of invented
   numbers presented as a client result is the one thing on a selling page that
   cannot be walked back. */
.proofs { display: grid; gap: clamp(1.25rem, 2.4vw, 1.75rem); margin-top: 1.75rem; }
@media (min-width: 56rem) { .proofs { grid-template-columns: 1fr 1fr; } }

.proof__label {
  font-family: var(--mono); font-size: .6875rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--red-ink);
  margin-bottom: .75rem;
}
.band--bone .proof__label { color: var(--red-bone); }
/* The capture, capped. The label above it and the heading and paragraph below
   it keep the figure's full width, so nothing reflows — only the picture gets
   shorter. Measured before: 1198x750 on /custom-software-toronto, the tallest
   single element on that page. */
.proof img { max-width: min(100%, 44rem); }
.proof h3 { margin: 1rem 0 .5rem; }
.proof p { font-size: .9375rem; color: var(--dim-ink); }
.band--bone .proof p { color: var(--dim-bone); }

/* ── NOT INCLUDED ──────────────────────────────────────────────────────────
   A row of short lines rather than a section with a heading and a paragraph.
   It has to be on the page — a figure with an undisclosed exclusion is not a
   published price — and it has to be small, because it is the one part of the
   page that argues against itself. */
.gate { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.75rem; }
.gate li {
  padding: .5rem .875rem;
  border: 1px solid var(--hair-ink);
  font-size: .875rem;
  color: var(--dim-ink);
}
.band--bone .gate li { border-color: var(--hair-bone); color: var(--dim-bone); }

/* ── QUESTIONS ─────────────────────────────────────────────────────────────
   Native <details>. It opens with scripts off, it is keyboard-operable without
   help, and it collapses the four longest answers on the page — which is most
   of the density Ethan named. */
.faqs { margin-top: 2.25rem; border-top: 1px solid var(--hair-ink); }
.band--bone .faqs { border-top-color: var(--hair-bone); }

.faq { border-bottom: 1px solid var(--hair-ink); }
.band--bone .faq { border-bottom-color: var(--hair-bone); }
.faq > summary {
  display: flex; align-items: baseline; gap: .875rem;
  padding: 1.125rem .25rem 1.125rem 0;
  cursor: pointer;
  font-family: var(--serif);
  font-size: 1.1875rem;
  line-height: 1.3;
  list-style: none;
}
.faq > summary::-webkit-details-marker { display: none; }
.faq > summary::after {
  content: '+';
  margin-left: auto;
  flex: none;
  font-family: var(--mono);
  font-size: 1.125rem;
  color: var(--red-ink);
}
.band--bone .faq > summary::after { color: var(--red-bone); }
.faq[open] > summary::after { content: '\2212'; }
.faq__a { padding: 0 0 1.375rem; max-width: 68ch; font-size: .9375rem; color: var(--dim-ink); }
.band--bone .faq__a { color: var(--dim-bone); }
.faq__a p + p { margin-top: .875rem; }
.faq__a b { color: var(--bone); font-weight: 600; }
.band--bone .faq__a b { color: var(--ink); }

/* ── THE BOOKING FLOOR — DELETED 2026-08-21 ────────────────────────────────
   These four pages used to end in a static `.band.book` section. They now end
   in the real `.bkx` booking band (css/booking-band.css), which takes the
   booking on the page itself. The block that lived here — `.book`, `.book__h2`,
   `.book__grid`, `.book__fine`, `.book__mail` and the whole `.next` column —
   was kept alive by ONE survivor: `.book__mail`, the "Or email contact@tnaado.ca"
   line under each hero. That line has now been cut from all four heroes (the
   closing booking band already carries the address, so it was saying the same
   thing twice on one page), which retired the last user and the whole block
   with it. Do not restore it: a second email sentence above the fold is one of
   the sixteen things the simplification pass was counting.
   ══════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════════
   PAGE-SPECIFIC BLOCKS
   One per page, and each one could not sit on any of the other three. They are
   in this sheet rather than in four more files because they share every token
   above and because a fifth stylesheet per page is four more places for the
   brand to drift.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── /web-design-toronto — THREE WIDTHS ────────────────────────────────────
   The page's own subject, drawn: the same page laid out at 375, 768 and 1440,
   side by side, so a reader can see that "responsive" means redrawn rather
   than shrunk. Three static wireframes, no animation, no script, no
   scroll-linking — which also means there is no reduced-motion branch to get
   wrong and nothing to fail with JS off. */
.wf { display: grid; gap: 1.5rem; margin-top: 2.5rem; }
@media (min-width: 52rem) { .wf { grid-template-columns: .8fr 1fr 1.7fr; align-items: end; } }

.wf__col { border: 1px solid var(--hair-bone); background: #fff; padding: .75rem; }
.wf__w {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: .6875rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--dim-bone);
  margin-bottom: .625rem;
}
.wf__page { display: grid; gap: 6px; }
.wf__bar { display: flex; align-items: center; gap: 5px; padding-bottom: 6px; border-bottom: 1px solid rgba(10,10,10,.14); }
.wf__logo { width: 26px; height: 8px; background: var(--red); flex: none; }
.wf__links { display: flex; gap: 4px; margin-left: auto; }
.wf__links i { display: block; width: 16px; height: 5px; background: rgba(10,10,10,.28); }
.wf__burger { margin-left: auto; display: grid; gap: 2px; }
.wf__burger i { display: block; width: 11px; height: 2px; background: rgba(10,10,10,.5); }
.wf__cta { width: 30px; height: 10px; background: var(--ink); flex: none; }
.wf__hero { display: grid; gap: 6px; }
.wf__hero--two { grid-template-columns: 1fr 1fr; }
.wf__type { display: grid; gap: 4px; align-content: start; }
.wf__type i { display: block; height: 5px; background: rgba(10,10,10,.72); }
.wf__type i:nth-child(1) { height: 11px; }
.wf__type i:nth-child(3) { width: 70%; background: rgba(10,10,10,.32); }
.wf__type i:nth-child(4) { width: 45%; background: var(--red); height: 8px; }
.wf__img { background: rgba(10,10,10,.14); min-height: 40px; }
.wf__row { display: grid; gap: 6px; }
.wf__row--3 { grid-template-columns: repeat(3, 1fr); }
.wf__row--2 { grid-template-columns: repeat(2, 1fr); }
.wf__tile { background: rgba(10,10,10,.09); border: 1px solid rgba(10,10,10,.1); min-height: 28px; }
.wf__note { margin-top: .875rem; max-width: 74ch; font-size: .8125rem; line-height: 1.6; color: var(--dim-bone); }

/* ── /ecommerce-website-design-toronto — THE CART ──────────────────────────
   The bundle priced the way the buyer's own customers will see a total: line,
   line, saving, subtotal. The saving is a LINE rather than a number applied
   afterwards, so the subtotal is genuinely the sum of what is above it. */
.cart { border: 1px solid var(--hair-ink); background: var(--ink-2); padding: clamp(1.25rem, 3vw, 2rem); margin-top: 2.5rem; }
.band--bone .cart { border-color: var(--hair-bone); background: var(--bone-2); }
.cart__head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding-bottom: 1rem; margin-bottom: .5rem;
  border-bottom: 1px solid var(--hair-ink);
  font-family: var(--mono); font-size: .6875rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--dim-ink);
}
.band--bone .cart__head { border-bottom-color: var(--hair-bone); color: var(--dim-bone); }
.cart .sum { margin-top: 0; }
.cart .sum caption { display: none; }

/* ── /shopify-development-toronto — WHAT GETS WRITTEN ──────────────────────
   The file list of a Shopify Online Store 2.0 theme, with the six templates
   priced as the six pages in the $6,000 floor and the two webhook
   subscriptions priced as the two integrations. It is the clearest available
   answer to "why is this $6,000 when a theme is $180": the money is a file
   list, and the file list is on the page.

   Its own scroller, because a monospaced tree cannot be made to fit 320px and
   must never widen the document instead. */
.tree {
  margin-top: 2.5rem;
  border: 1px solid var(--hair-ink);
  background: var(--ink-2);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.tree__inner { min-width: 22rem; padding: 1.25rem clamp(1rem, 3vw, 1.75rem); }
.tree ul { margin: 0; }
.tree li {
  display: flex; align-items: baseline; gap: 1rem;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-size: .8125rem;
  line-height: 2;
  color: var(--dim-ink);
  white-space: nowrap;
}
.tree li b { color: var(--bone); font-weight: 500; }
.tree__d { color: var(--red-ink); }
.tree__p { margin-left: auto; color: var(--bone); font-weight: 500; }
.tree__p span { color: var(--dim-ink); font-weight: 400; }
.tree__i1 { padding-left: 1.25rem; }
.tree__i2 { padding-left: 2.5rem; }
.tree__foot {
  display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap;
  margin-top: .75rem; padding-top: 1rem;
  border-top: 2px solid var(--red);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-size: .9375rem;
}
.tree__foot b { font-size: 1.25rem; font-weight: 500; margin-left: auto; }

/* ── /custom-software-toronto — THE QUOTE, DRAWN TO SCALE ──────────────────
   Four segments, each as wide as the money it stands for, over the itemised
   lines that sum to $95,000. The point of drawing it is that it shows where a
   six-figure quote actually goes: the eight API integrations are the thin
   segment, and the base plus the seven features are two thirds of the build.
   The percentages are computed from the figures, not chosen. */
.qbar { display: flex; margin-top: 2.5rem; border: 1px solid var(--hair-ink); }
.qbar__seg {
  padding: 1.125rem .75rem;
  border-right: 1px solid var(--ink);
  min-width: 0;
}
.qbar__seg:last-child { border-right: 0; }
.qbar__seg:nth-child(1) { background: var(--red); color: var(--bone); }
.qbar__seg:nth-child(2) { background: #7c1020; color: var(--bone); }
.qbar__seg:nth-child(3) { background: #3a1218; color: var(--bone); }
.qbar__seg:nth-child(4) { background: #1d1416; color: var(--bone); }
.qbar__k {
  font-family: var(--mono); font-size: .625rem; letter-spacing: .12em;
  text-transform: uppercase; opacity: .85;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.qbar__v {
  margin-top: .25rem;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-size: .9375rem; font-weight: 500;
}
.qbar__pc { margin-top: .125rem; font-family: var(--mono); font-size: .6875rem; opacity: .8; }
@media (max-width: 40rem) {
  .qbar { flex-direction: column; }
  .qbar__seg { border-right: 0; border-bottom: 1px solid var(--ink); }
  .qbar__seg:last-child { border-bottom: 0; }
}

/* The seven features are the BREAKDOWN OF ONE QUOTED APPLICATION and are
   labelled as such wherever they appear. They are not a rate card: a standalone
   special feature starts at $1,800, and the $1,800 line in this list sits at
   that floor only because it is priced inside a larger build. */
.feats { margin-top: 2rem; }

/* --- /custom-software-toronto - THE FOUR SEGMENT WIDTHS -------------------
   ADDED 2026-08-21 for the three remaining Technology selling pages. ADDITIVE
   ONLY: no rule above this line is changed, reordered or removed.

   .qbar above draws the four lines of the $95,000 quote. These two queries are
   what make its widths BE the money rather than merely suggest it: each
   segment carries --w, its share of $95,000 as a plain number, and the
   flex-basis is calc(var(--w) * 1%). box-sizing is border-box for everything
   in this sheet, so the four bases sum to exactly 100% INCLUDING their
   padding, and 36.8 + 26.5 + 7.6 + 29.1 = 100.0 exactly.

   WHY THE STRIP STACKS BELOW 72rem, which is wider than it looks.
   The narrow segment is the eight integrations at 7.6%. Its widest line is the
   figure, "$7,200", which is about 54px in the mono face at .9375rem. With
   .75rem of padding on each side the segment needs about 78px of border-box
   width, so the rail has to be at least ~1030px before 7.6% of it can hold the
   figure. At 768px the same share is 54px of border box - the figure would be
   clipped, and a clipped figure is worse than a stacked one. So below 72rem
   the four segments become four full-width rows: the widths stop carrying the
   proportions and the printed figures carry them alone.

   Either way NO FIGURE IS EVER GATED BEHIND THIS ELEMENT. The full arithmetic
   is restated in a caption OUTSIDE the strip and again as a two-column table
   below it, both of which render at every width, with scripts off and with
   reduced motion on. The strip is a picture of a sum that is also printed. */
@media (min-width: 72rem) {
  .qbar__seg { flex: 0 0 calc(var(--w) * 1%); }
}
@media (max-width: 71.99rem) {
  .qbar { flex-direction: column; }
  .qbar__seg { border-right: 0; border-bottom: 1px solid var(--ink); }
  .qbar__seg:last-child { border-bottom: 0; }
}

/* .feats holds the seven-feature decomposition. It is a .sum table, which
   already carries the tabular figures and the total rule, so all this needs is
   the label above it - the seven lines are the breakdown of ONE quoted
   application and mislabelling them as a rate card is the specific error this
   class exists to prevent. */
.feats__h {
  font-family: var(--mono); font-size: .6875rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--dim-ink);
}
.band--bone .feats__h { color: var(--dim-bone); }
.feats .sum { margin-top: 1rem; }

/* MEASURED, NOT DECLARED. The muting on .qbar__k and .qbar__pc is `opacity`,
   which composites the whole painted glyph against the segment fill behind it
   rather than only changing a declared colour. On the three dark segments that
   costs nothing. On the FIRST segment, filled with --red, the composited values
   are 4.04:1 for the 10px label at .85 and 3.71:1 for the 11px percentage at
   .8 - both under the 4.5 bar for small text, and both invisible to any check
   that reads the colour instead of the composite. So that one segment paints
   both at full strength, which measures 5.22:1, the same value the bone type on
   every other red fill in this sheet already carries. ADDITIVE: nothing above
   is changed. */
.qbar__seg:nth-child(1) .qbar__k,
.qbar__seg:nth-child(1) .qbar__pc { opacity: 1; }

/* ── THE TERMINAL BOOKING BAND — STACKING ONLY ─────────────────────────────
   The band itself is authored entirely in css/booking-band.css and nothing
   here restyles it. This is the ONE thing that file cannot know, because it
   is a fact about the PAGE and not about the component: `.bkx` must sit above
   whatever the page paints around it.

   booking-band.css gives `.bkx` `position: relative` and no z-index, so it
   creates no stacking context of its own and any positioned sibling later in
   the document — or any earlier one lifted by a page-level rule — paints over
   it. That is not theoretical: the old web-design page had exactly this
   defect, a sticky element covering the booking panel, and a booking panel you
   cannot click is the same as no booking panel on a page that takes paid ads.

   `2` rather than `1` on purpose: 1 is the value booking-band.css already uses
   INSIDE the band (`.bkx__inner`, above the emblem), so a band-level 1 would
   read as the same layer and invite somebody to "simplify" one of them away.
   Nothing in the site chrome is affected — the fixed header and foot sheet run
   at 1190+ in css/chrome.css and still paint above, which is correct. */
.bkx { z-index: 2; }

/* --- /ios-app-development-toronto - THE TWO LIVE APPS ---------------------
   ADDED 2026-08-21. ADDITIVE ONLY: every selector below is either a NEW
   modifier class or a NEW element class that appears nowhere else in this
   repo, so no page that shipped before today is repainted by a single byte of
   it. Nothing above this line is changed, reordered or removed.

   WHY THE TWO MODIFIERS EXIST. .cards is repeat(3, 1fr) and .tiers is
   repeat(3, 1fr), which is right for three children and visibly broken for
   two: the third column stays empty and the row reads as a card that failed to
   load. The iOS page has exactly TWO live App Store apps and exactly TWO tiers
   large enough to hold an application, and both counts are facts rather than
   layout choices - there are not three apps on the store, and saying so is the
   most-corrected error in this project. So the grid follows the fact.

   .card__store is the only NEW interactive element on that page: a real link
   to a real App Store listing, which is the strongest proof on this site
   because it is the one claim a stranger can check without us. It is styled as
   a link and not as a button on purpose - .btn--solid is the booking action and
   there is exactly one of those per band.

   CONTRAST, MEASURED, NOT DECLARED. Both new text runs are small type, so
   --red at 3.37:1 is banned and neither uses it. .card__st and .card__store
   resolve to --red-ink #ef5c6e on ink (6.06:1) and --red-bone #a4132c on bone
   (6.60:1). The iOS page puts these cards on a .band--bone, so the value that
   actually paints is 6.60:1. No opacity is applied to either, so the composited
   value equals the declared one. */
@media (min-width: 48rem) {
  .cards--two { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 56rem) {
  .tiers--two { grid-template-columns: repeat(2, 1fr); }
}

/* `.card .card__st` and not `.card__st`: `.card p` above is (0,1,1) and a bare
   class is (0,1,0), so a single-class rule on a <p> inside a card LOSES to it
   and the status line would paint as dim body copy at 15px. That is the exact
   failure mode this sheet's history is full of - a rule that reads correct and
   never applies. Two classes, measured against the rule it has to beat. */
.card .card__st {
  margin-top: .5rem;
  margin-bottom: .625rem;
  font-family: var(--mono);
  font-size: .6875rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--red-ink);
}
.band--bone .card .card__st { color: var(--red-bone); }

.card__store {
  display: inline-flex; align-items: baseline; gap: .5rem;
  margin-top: 1rem;
  font-family: var(--mono);
  font-size: .8125rem;
  letter-spacing: .04em;
  color: var(--red-ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.band--bone .card__store { color: var(--red-bone); }
.card__store i { font-style: normal; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ══════════════════════════════════════════════════════════════════════════
   /ios-app-development-toronto — THE APP SHOWCASE.  2026-09-03.

   ADDITIVE ONLY. Every selector below is a new element or modifier class that
   appears in exactly one HTML file in this repo, so nothing that shipped
   before today is repainted by a byte of it. Nothing above this line is
   changed, reordered or removed.

   WHY IT EXISTS. Ethan, three complaints in a row about that one page: "hero
   needs ap showcase not photo of torotno", "that whole page has no real
   showcase of our aps at what what teh fukc", "on app didpalays it should show
   screenshots of the apps not just list them as itmes. also include the apple
   store link thing for those live". The page was selling iPhone builds behind
   a photograph of Union Station and naming nine of our own applications in a
   comma-separated sentence. Three classes answer all of it: .phone draws the
   frame, .appfan is the hero, .appshow is the card grid.

   WHAT IS DELIBERATELY NOT HERE:
     a device mockup image   the frame is drawn in CSS — a border, a radius and
                             a shadow. A photographed or rendered iPhone shell
                             is an asset that has to be licensed, kept in
                             /assets, and re-cut every time Apple changes the
                             hardware. Two rules and no file.
     object-fit / aspect-ratio on the captures.  See .phone img below: the
                             captures are 322x618 and 322x700 and normalising
                             them to one ratio means cropping or stretching a
                             real screenshot. Frames are sized by WIDTH only.
     any width on the badge  Apple's artwork is used at its intrinsic 120x40.
     a carousel              work/software.html has nine apps and needs one.
                             This page has three in one section and six in the
                             other, and a grid that holds them needs no script.
   ────────────────────────────────────────────────────────────────────────── */

/* ── THE PHONE FRAME ──────────────────────────────────────────────────────
   One rule set, three uses: the hero fan, the showcase cards, and — with
   --desk — Citadel's landscape desktop capture.

   THE IMAGE KEEPS ITS OWN HEIGHT. `height: auto` and no aspect-ratio, which
   is the whole reason the 618px and 700px captures can sit in the same grid
   without one of them being cropped or squashed. The frames line up on their
   width and the heights come out where the pixels put them.

   PADDING IS THE BEZEL. .375rem of frame colour around a radius-inset image
   reads as a device without drawing a notch, a speaker or a home bar — three
   more things to get subtly wrong at 105px wide. */
.phone {
  position: relative;
  margin: 0;
  padding: .375rem;
  background: var(--ink-3);
  border: 1px solid var(--hair-ink);
  border-radius: 1.5rem;
  box-shadow: 0 1.25rem 2.5rem rgba(0, 0, 0, .42);
}
.band--bone .phone {
  background: var(--bone-2);
  border-color: var(--hair-bone);
  box-shadow: 0 1.25rem 2.5rem rgba(10, 10, 10, .14);
}
.phone img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 1.175rem;   /* the 1.5rem frame minus its own .375rem padding */
}

/* CITADEL IS A MAC AND ITS FRAME SAYS SO. A 1200x768 landscape capture in a
   1.5rem-radius phone frame reads as a tablet nobody makes. Shallower radius,
   same bezel, same shadow. */
.phone--desk { border-radius: .625rem; }
.phone--desk img { border-radius: .3125rem; }

/* ── THE HERO FAN ─────────────────────────────────────────────────────────
   Three phones, overlapped, vertically centred, middle one widest and on top.

   THE OVERLAP IS ARITHMETIC, NOT TASTE. Percentage widths and percentage
   margins both resolve against the flex container's inline size, so the total
   is exactly computable and can be held under 100% at every width:

       total = w(l) + w(c) + w(r) - overlap - overlap
             = .38C + .42C + .38C - .13C - .13C
             = .92C

   Eight per cent of slack, centred, at every viewport. That matters because
   three phones side by side in a GRID inside .rail are about 110px wide each
   at 390 and the screenshots stop being readable; overlapped they are about
   133px, and the middle one 147px. Measured at 390x844 and 1440x900.

   NO ROTATION. A 5deg tilt on a 208x452 frame adds 39px to its bounding box,
   which pushed the outer phones to 516px inside a 520px column at 1440 — four
   pixels from a horizontal scrollbar in the hero. It also softens the type
   inside a real screenshot. The overlap alone reads as a fan.

   THE OVERLAP IS ONLY BELOW 48rem, AND THAT IS THE CORRECTION FROM THE FIRST
   BUILD OF THIS FAN. Overlapping at every width was measured at 1440x900 and
   the middle phone covered THIRTY-FOUR PER CENT of each outer one — the whole
   right half of Find The Title and the whole left half of Mr. Indecisive,
   including the restaurant name and the review pill the capture exists to
   show. A showcase that hides a third of two of its three screenshots is a
   composition, not a showcase.

   So the overlap is now what it always should have been: a response to running
   out of room, applied only where the room has actually run out. Above 48rem
   the three phones sit in a gapped row with every pixel of all three visible;
   below it they overlap, because three phones in a 350px rail are 116px wide
   each and a 116px screenshot is a smudge.

   ONE MAX WIDTH, IN REM, so the fan stops growing on a wide monitor while the
   percentages keep it safe on a narrow one. */
.appfan {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: .5rem;
}
.appfan .phone {
  flex: 0 0 auto;
  width: 36%;
}
.appfan .phone--c {
  width: 42%;
  z-index: 2;                /* .phone is position:relative, so this applies */
}
/* .36 + .42 + .36 - .09 - .09 = .96 of the container. Six per cent of slack at
   every width below 48rem, so this can never be the cause of a scrollbar. */
.appfan .phone--l { margin-right: -9%; }
.appfan .phone--r { margin-left: -9%; }

/* NO OVERLAP ONCE THERE IS ROOM. Measured: the hero's right column is ~520px
   at 1440x900 and the single-column rail is ~706px at 768. Three phones at
   29/33/29 per cent plus two 1rem gaps come to 505px and 675px — inside both,
   with all three captures whole. The middle phone stays wider because the
   fan still needs a centre; it no longer needs to sit on top of anything. */
@media (min-width: 48rem) {
  .appfan { gap: clamp(.625rem, 1.4vw, 1rem); }
  .appfan .phone { width: 29%; }
  .appfan .phone--c { width: 33%; }
  .appfan .phone--l { margin-right: 0; }
  .appfan .phone--r { margin-left: 0; }
}

/* ── THE SHOWCASE GRID ────────────────────────────────────────────────────
   #get holds three cards, #apps holds six, and both are the same component:
   a capture, a status line, a name, a metadata line, a sentence, and — only
   where the app is genuinely on the App Store — Apple's badge.

   THE FRAMES ARE SIZED BY HEIGHT HERE, NOT BY WIDTH, AND THAT IS THE WHOLE
   TRICK OF THIS SECTION. Sized by width — the obvious way, and the way this
   was built first — three equal-width frames holding a 618px capture and two
   700px ones come out 472px, 533px and 533px tall. The captures then end on
   three different lines and every card body starts at a different height, so
   the row reads as three things that failed to line up. Measured at 1440x900:
   61px of rag between Mr. Indecisive's label and the other two.

   Setting the HEIGHT instead and letting each width fall where the pixels put
   it inverts the problem into a non-problem. Every frame is exactly as tall as
   every other, so every body starts on the same line, and the price is that
   Mr. Indecisive's frame is about 30px wider than its neighbours — which is
   invisible in a gapped row, unlike 61px of vertical rag.

   NOTHING IS CROPPED AND NOTHING IS STRETCHED. `height: 100%; width: auto` on
   the image and `width: fit-content` on the frame means the frame shrink-wraps
   whatever width the capture's own ratio produces. There is no object-fit, no
   aspect-ratio and no max-height on the image anywhere in this block.

   THE HEIGHT IS A CONTAINER QUERY, NOT A VIEWPORT ONE, because the thing that
   constrains a card is its COLUMN, not the window. Same page, same viewport:
   the column is ~357px at 1440 in three columns and ~350px at 390 in one, so
   a vw-based height would size those two cases completely differently for no
   reason. 145cqw capped at 32rem, measured:

     column 350px (390, 1 col)  -> 507px tall, widths 233 / 233 / 264
     column 357px (1440, 3 col) -> capped 512px, widths 236 / 236 / 267
     column 219px (768,  3 col) -> 318px tall, widths 146 / 146 / 166

   Widest case is the 618 capture at height x .521 = .755 of its own column, so
   the frame can never be wider than the column it sits in, and 267px is under
   the 322px native width of every capture, so nothing is ever upscaled. */
.appshow {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.25rem);
  margin-top: 2.25rem;
}
@media (min-width: 48rem) {
  .appshow { grid-template-columns: repeat(3, 1fr); }
}
/* A FLEX COLUMN SO THE BADGES BOTTOM-ALIGN. The three descriptions in #get run
   to two, three and three lines, so authored in normal flow the three App Store
   badges landed on three different lines — 24px of rag between Mr. Indecisive's
   and the other two, measured at 1440x900. Grid items stretch to the row height
   by default, so the card is already as tall as the tallest of the three; all
   this needs is for the badge to claim the leftover space above it. Nothing
   here changes the phone, which carries an explicit height of its own. */
.appshow__it {
  display: flex;
  flex-direction: column;
  container-type: inline-size;   /* what 145cqw below is measured against */
}
.appshow__body { display: flex; flex-direction: column; flex: 1 1 auto; }
/* :not(.phone--desk) IS LOAD-BEARING. Citadel's capture is 1200x768 landscape.
   Height-sizing it to 512px would make it 800px WIDE, which is not a card, it
   is a billboard breaking out of the rail. The equal-height rule is for the
   portrait phones that have to line up with each other; the one desktop frame
   is sized by width in its own block further down. */
.appshow .phone:not(.phone--desk) {
  width: fit-content;
  height: min(32rem, 145cqw);
  margin-inline: auto;
}
.appshow .phone:not(.phone--desk) img { height: 100%; width: auto; }
.appshow__body { margin-top: 1.25rem; }

/* TWO CLASSES, NOT ONE, AND THAT IS THE POINT OF THIS COMMENT. `.card p` in
   this sheet is (0,1,1) and its equivalents are everywhere; a bare
   `.appshow__st` on a <p> is (0,1,0) and loses to any descendant-plus-element
   rule that reaches it. This sheet's history is full of rules that read
   correct and never apply, so both micro-labels are written as
   `.appshow__body .appshow__st` — (0,2,0), which beats every element-qualified
   rule in the file without an !important. */
.appshow__body .appshow__st {
  margin: 0 0 .5rem;
  font-family: var(--mono);
  font-size: .6875rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--red-ink);
}
.band--bone .appshow__body .appshow__st { color: var(--red-bone); }

/* BUILT-NOT-PUBLISHED IS NOT PAINTED RED. Red is the store status. The six in
   #apps are dim, because the difference between "you can download this" and
   "we run this" is the single most-corrected fact in this project and it
   should not depend on a reader parsing two shades of the same colour. */
.appshow__body .appshow__st--built {
  color: var(--dim-ink);
  letter-spacing: .14em;
}
.band--bone .appshow__body .appshow__st--built { color: var(--dim-bone); }

.appshow__body .appshow__meta {
  margin: .375rem 0 .625rem;
  font-family: var(--mono);
  font-size: .75rem;
  letter-spacing: .02em;
  color: var(--dim-ink);
}
.band--bone .appshow__body .appshow__meta { color: var(--dim-bone); }
.appshow__body h3 { margin: 0; }
.appshow__body p { font-size: .9375rem; color: var(--dim-ink); }
.band--bone .appshow__body p { color: var(--dim-bone); }
/* THE DESCRIPTION EATS THE CARD'S LEFTOVER HEIGHT, so whatever follows it sits
   on the same line across the whole row. In #get that is the App Store badge:
   the three descriptions run to two, three and three lines, and authored in
   normal flow the badges landed 24px apart — measured at 1440x900. In #apps
   there is nothing after the description and the auto margin is inert. */
.appshow__body p:last-of-type { margin-bottom: auto; }
.appshow__body p a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }

/* ── APPLE'S BADGE ────────────────────────────────────────────────────────
   Apple's artwork, unaltered: not recoloured, not redrawn, not cropped, not
   stretched. There is deliberately NO width, NO height, NO max-width and NO
   background on the <img> — the intrinsic 120x40 in the SVG stands, which is
   also how work/software.html uses the same two files. The only two things
   this rule adds are the clear space Apple's guidelines require and a focus
   ring that sits OUTSIDE that clear space rather than inside it.

   WHICH FILE, WHERE. black on .band--bone (#f5f1e8), white on ink. Apple
   ships both for exactly this reason and both are already in /assets/store/.
   Today only the black one is referenced, because all three store apps are in
   the bone band; the white rule below is here so that moving a badged card
   onto an ink band is a class change and not a bug. */
/* THE SLACK IS ABSORBED BY THE PARAGRAPH ABOVE, NOT BY THIS BOX, which is why
   the margin here is a plain 1rem. Pushing the badge down with `margin-top:
   auto` was the obvious way and it is wrong twice: auto collapses to 0 in a
   card with no slack, so the lockup butts against the paragraph and loses the
   clear space Apple's guidelines require, and replacing that gap with padding
   drags the focus ring 22px off centre because an outline is drawn round the
   padding box. `.appshow__body p:last-of-type { margin-bottom: auto }` further
   up does the same job with none of that.

   `align-self: start` keeps the box shrink-wrapped to the 120px artwork: a
   blockified flex child would stretch its focus ring across the whole column. */
.appshow__badge {
  display: inline-block;
  align-self: start;
  margin: 1rem 0 0;
  padding: 0;
  line-height: 0;
  border-radius: 6px;
  transition: opacity .28s var(--ease);
}
.appshow__badge img { display: block; }
.appshow__badge:hover { opacity: .82; }
.appshow__badge:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 6px;
}

/* ── THE WIDE CARD ────────────────────────────────────────────────────────
   One member, Citadel, and it is wide because its capture is landscape rather
   than because it is important. Below 56rem it stacks like the rest; above it
   the row is the full grid width with the capture beside the copy, because a
   1200x768 image squeezed into a third of the rail is unreadable and the same
   image at full width with nothing next to it leaves a 500px hole. */
@media (min-width: 48rem) {
  .appshow__it--wide { grid-column: 1 / -1; }
}
@media (min-width: 56rem) {
  .appshow__it--wide {
    display: grid;
    grid-template-columns: 1.35fr 1fr;
    gap: clamp(1.5rem, 3vw, 2.5rem);
    align-items: center;
  }
  .appshow__it--wide .appshow__body { margin-top: 0; }
}
.appshow__it--wide .phone--desk {
  width: 100%;
  max-width: 40rem;
  margin-inline: auto;
}

/* ── D.02, MOVED IN FROM /technology ──────────────────────────────────────
   THE ONLY THING css/d02-webbuild.css NEEDS THAT THIS PAGE DOES NOT HAVE.
   That stylesheet was written for a page that loads css/site.css, where the
   three faces are named --font-display, --font-body and --font-mono. This
   sheet names them --serif, --sans and --mono. Twelve var(--font-mono)
   references in d02-webbuild.css carry NO fallback value, so without the
   three lines below the whole D.02 block renders in the browser's default
   face — a silent, total typographic failure in a section that is otherwise
   completely self-contained.

   SCOPED TO .wbd, NOT :root. Publishing --font-* aliases at the root of this
   sheet would hand every other page that loads it a second name for the same
   three faces, which is how a codebase ends up with two vocabularies and a
   rule that picks the wrong one. Three lines, one component, no reach. */
.wbd {
  --font-display: var(--serif);
  --font-body: var(--sans);
  --font-mono: var(--mono);
}
