/* ════════════════════════════════════════════════════════════════════════
   css/contact.css, /contact.  service.tnaado.ca · Tier 2, no storm.

   ONE JOB, AND IT IS THE MOST-LINKED JOB ON THE SITE. Seventy-three links
   point at /contact; it is also where a paid Google Ads click lands and what
   the firm's Google Business Profile is attached to.

   SIMPLIFIED 2026-08-21. Somebody arrives wanting exactly one of four things,
   the number, the inbox, the address, or a time in the diary, and this page
   used to make all four wait behind 687 words of open prose in four full-width
   sections, with nothing collapsed and the phone number roughly two thousand
   pixels down. It is now: a heading, the four answers, the booking band, three
   collapsed disclosures, and the office-visit band. 687 words of page copy
   became 199, and 95 of those are behind a <details>.

   TWO THINGS THIS SHEET GOT WRONG AND HAS NOW FIXED, both found by measuring a
   live render rather than by re-reading the file. The h1 floor drop from 2.5rem
   to 2.125rem did NOT put the fourth answer above the 390x844 fold, it landed
   at 895 against an 844 fold. And the page had no entrance motion at all. Both
   are addressed in the two blocks at the foot of this file, and both are
   re-measured there rather than asserted.

   What this sheet still does NOT do is author either .bkx band,
   css/booking-band.css owns both, and neither their markup nor their internals
   are this page's to edit.

   WHY IT IS ITS OWN SHEET AND NOT css/work.css. That file opens by saying
   "TWO PAGES, ONE SHEET, because they share a ground, a rail, a type scale
   and the chrome offset, and two copies of those is how the old site's twenty
   pages started to drift". Adding a third unrelated page to it would be the
   drift it was written to prevent. The tokens below are COPIED from it
   unchanged, because they are the measured ones, a third set of slightly
   different reds is the actual failure mode here, not a second file.

   COLOUR, MEASURED, NOT INVENTED. Every value is already on this site:
     --bone     #f5f1e8  17.4:1 on ink   body and headings
     --dim-ink  #b9b3a6   9.4:1 on ink   crumb, eyebrow, secondary copy
     --red-ink  #ef5c6e   6.06:1 on ink  the ONLY small red text on ink
     --red      #c8102e   3.37:1 on ink  rules, fills and display type only
   There is no rgba(bone, .4x) anywhere: that value is 3.77:1 and it is the
   thing css/chrome.css was written to stop reproducing.

   DEGRADATION. Everything here paints, opens and closes with no JavaScript at
   all, the disclosures are native <details> and the height transition is
   ::details-content plus interpolate-size, not a measured animation. The bands
   are authored in their finished state and animate BACKWARDS out of from-only
   keyframes, so if js/booking.js never runs they still render complete and
   static, and the four answers, including the tel: and the mailto, are the
   page's real fallback either way.
   ════════════════════════════════════════════════════════════════════════ */

/* THE RESET, AND IT IS LOAD-BEARING. Without it `.rail`, width:100% plus
   var(--gut) of padding, computes to 430px inside a 390px viewport and the whole
   page scrolls sideways. Measured, not assumed: at 390px --gut resolves to its
   1.25rem floor, and 390 + 2x20 is exactly the 40px of overflow that appeared.
   css/work.css opens with the same line for the same reason. */
*, *::before, *::after { box-sizing: border-box; }

:root {
  --ink:       #0a0a0a;
  --bone:      #f5f1e8;
  --red:       #c8102e;
  --red-ink:   #ef5c6e;   /* 6.06:1 on ink, the only small red on ink */
  --dim-ink:   #b9b3a6;   /* 9.4:1 on ink  */
  --hair-ink:  rgba(245, 241, 232, .14);

  --serif: 'Bodoni Moda', 'Times New Roman', Times, serif;
  --sans:  'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono:  'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --rail: 1136px;
  --gut:  clamp(1.25rem, 4vw, 3rem);

  /* THE CHROME OFFSET. 26px logo + .8rem padding either side (css/chrome.css)
     = 51.6, plus the hairline = 53. A constant, not a measurement: a measured
     value would need script, and the bar is a fixed 26px lockup at every
     width. */
  --bar: 53px;

  --ease: cubic-bezier(.22, 1, .36, 1);
}

html {
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* #book must not land under the fixed bar, and on this page #book is where
     most arrivals are heading. */
  scroll-padding-top: calc(var(--bar) + 1rem);

  /* AND THE SAME AT THE BOTTOM, for the SECOND fixed bar. .footpull is 41px of
     position:fixed at the foot of every page (css/chrome.css: .85rem padding
     over a .6875rem line, none of it fluid, so it is 41px at every width).
     Without this the browser's own scroll-into-view for KEYBOARD FOCUS parks a
     control directly underneath it, measured before the fix, tabbing to the
     #book-visit summary put it 1px under the bar at 1440 and 94px under it at
     390, which is a control a keyboard user cannot see and a sighted mouse user
     never meets. Page-wide because every focusable element near the foot has
     the same problem, not just the one that exposed it. */
  scroll-padding-bottom: calc(41px + 1.25rem);
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
/* Only while the bar is fixed. With no JS the bar is a block in flow and the
   page must NOT reserve space for it twice. */
html.chrome-live body { padding-top: var(--bar); }

.rail { width: 100%; max-width: var(--rail); margin: 0 auto; padding: 0 var(--gut); }

h1, h2 { font-family: var(--serif); font-weight: 500; margin: 0; line-height: 1.02; }
/* 3.5rem, not the 4.5rem this sheet used to carry. On a Tier 1 storm page the
   headline IS the screen; here it is the thing standing between somebody and
   the phone number. Measured at 1440×900: the old size cost 112px and pushed
   the four answers' bottom row to the fold; at 3.5rem the whole band and the
   top of the booking panel are in the first screenful. At 390×844 the floor
   drop from 2.5 to 2.125rem is what puts the fourth answer on screen. */
/* Was clamp(2.125rem, 5.2vw, 3.5rem), 56px rendered, which is what made this
   page read as shouting. Matches the corrected site scale. */
h1 { font-size: clamp(2rem, 4.2vw, 2.875rem); letter-spacing: -.015em; }
h1 em { font-style: italic; color: var(--red-ink); }

/* NO .lede RULE. The <p class="lede"> was cut from contact.html, it read the
   four answers aloud in advance ("Phone, email, the office, or thirty minutes
   in the diary"), which is Call / Email / Visit / Book, in order, twice on one
   screen. The declarations went with it rather than being left to rot: there
   is exactly one page on this sheet and nothing else here uses the class. */

/* ── crumb ─────────────────────────────────────────────────────────────── */
.crumb {
  margin: 0 0 1.5rem; padding: 0;
  font-family: var(--mono); font-size: .75rem; color: var(--dim-ink);
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  list-style: none;
}
.crumb li { display: flex; align-items: center; gap: .5rem; }
.crumb li + li::before { content: '/'; color: var(--red-ink); }
.crumb a { color: var(--dim-ink); text-decoration: none; border-bottom: 1px solid transparent; }
@media (hover: hover) and (pointer: fine) {
  .crumb a:hover { color: var(--bone); border-bottom-color: var(--red); }
}
.crumb a:active { color: var(--bone); }
.crumb [aria-current="page"] { color: var(--bone); }

/* ── eyebrow ───────────────────────────────────────────────────────────── */
.eyebrow {
  display: flex; align-items: center; gap: .75rem;
  margin: 0 0 1.1rem;
  font-family: var(--mono); font-size: .75rem; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--dim-ink);
}
.eyebrow__rule { display: block; width: 2.25rem; height: 2px; background: var(--red); flex: 0 0 auto; }
.eyebrow__num { color: var(--red-ink); }

/* ── the head ──────────────────────────────────────────────────────────── */
.cthead { padding: clamp(1.5rem, 4vw, 2.75rem) 0 clamp(1rem, 2vw, 1.6rem); }

/* ══════════════════════════════════════════════════════════════════════════
   THE FOUR ANSWERS, .ctways
   --------------------------------------------------------------------------
   REPLACED .reach (a three-row list) AND the whole C.01 .ctcard / .ctrec block
   (an address column beside a five-row <dl>). Between them those two said the
   inbox, the address and "by appointment" TWICE EACH on one page, and neither
   of them carried the phone number above the fold, it sat in the third row of
   the second one, roughly two thousand pixels down, on the page a paid Google
   Ads click lands on.

   FOUR CELLS, NOT FOUR CARDS. The separators are the grid's own 1px gap
   painted through from the list's background, so the cells sit on the page's
   ink ground with a hairline between them, no boxes, no radii, no shadows.
   Boxing four routes to one firm makes them look like four products, which is
   the note css/contact.css already carried about .reach and is still right.

   THE FOURTH CELL IS THE ONLY FILLED THING ON THE PAGE. One --red block, one
   primary action, and it is the booking. Everything else here is type.
   ══════════════════════════════════════════════════════════════════════════ */
.ctways {
  padding-top: clamp(1.1rem, 2.5vw, 1.9rem);
  padding-bottom: clamp(1.35rem, 3vw, 2.25rem);
}

/* gap:1px over a --hair-ink ground draws every separator, inner and none
   outer, at any column count, so the 1 / 2 / 4 column steps below need no
   per-cell border bookkeeping and can never double a rule at a wrap point. */
.ways {
  margin: 0; padding: 0; list-style: none;
  display: grid;
  gap: 1px;
  background: var(--hair-ink);
  border-top: 2px solid var(--red);
  border-bottom: 1px solid var(--hair-ink);
}
@media (min-width: 560px) { .ways { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* 1160, and the number is measured, not chosen. Four columns only work once
   the rail has reached its 1136px cap: that gives a 259px cell, 226px of it
   inside the padding, and the widest indivisible run in the band,
   "Toronto, Ontario M6K 0C7" at 15px JetBrains Mono, is 216px. At 1024 the
   same cell is 200px inside and the postcode drops to its own line, which is
   the one thing a pasteable address must not do. So everything between 560 and
   1160 gets two columns, where each cell has 470px and nothing wraps at all. */
@media (min-width: 1160px) { .ways { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.way {
  background: var(--ink);
  padding: clamp(.9rem, 2vw, 1.35rem) clamp(.9rem, 1.5vw, 1.05rem)
           clamp(.95rem, 2.2vw, 1.5rem);
  display: flex; flex-direction: column;
  gap: .45rem;
}
/* The gutter is the .rail's, not the cell's, so the first and last cells
   line up with the h1 above them at every column count. */
@media (max-width: 559px) { .way { padding-left: 0; padding-right: 0; } }

.way__k {
  margin: 0;
  font-family: var(--mono); font-size: .6875rem; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--red-ink);
}

/* The value IS the answer, so it is the largest thing in the cell and, where
   it can be, it is the link itself rather than a link inside a sentence.
   Mono, because a number and an address are a record you are meant to select
   and paste, the same reasoning the old .ctcard__lines carried.

   UNDERLINED AT REST, not on hover. This page's links are its fallback path
   and must be unmistakably links to somebody skimming and to somebody with no
   pointer to hover with. A grow-in underline would look better and would be
   the wrong call here. */
.way__v {
  margin: 0;
  font-family: var(--mono);
  font-size: clamp(1rem, 1.5vw, 1.1875rem);
  line-height: 1.35;
  font-style: normal;              /* <address> inherits italic in every UA sheet */
  color: var(--bone);
  -webkit-user-select: text; user-select: text;
}
a.way__v {
  align-self: flex-start;
  text-decoration: underline;
  text-decoration-color: var(--red);
  text-decoration-thickness: 1px;
  text-underline-offset: .2em;
  border-radius: 2px;
  transition: color .18s var(--ease), text-decoration-color .18s var(--ease);
}
/* Only the address has no break opportunity that a UA will take on its own. */
.way__v--wrap { overflow-wrap: anywhere; }

/* Four lines of mono is already the tallest thing in the band, so it takes a
   step DOWN the scale rather than the .way__v default, at 1.1875rem the
   street line wrapped inside a quarter-rail column at 1440 ("1050 King Street
   / West"), which is the one thing a pasteable address must not do. */
.way__addr {
  font-size: .9375rem;             /* 15px, see the 1160px note above .way */
  line-height: 1.75;
}
.way__addr b { font-weight: 500; }

.way__note {
  margin: auto 0 0;                /* pins the notes to a common baseline row */
  font-size: .8125rem; line-height: 1.55; color: var(--dim-ink);
}
/* `auto` only has room to push in the multi-column layouts; in the stacked one
   it would open a gap the size of nothing. */
@media (max-width: 559px) { .way__note { margin-top: .1rem; } }

/* ── the one filled thing ────────────────────────────────────────────────── */
.way--cta { background: var(--ink); }
.way__btn {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: .55rem;
  padding: .72rem 1.15rem;
  background: var(--red);
  color: var(--bone);
  font-family: var(--mono); font-size: .8125rem; font-weight: 500;
  letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none;
  transition: transform .2s var(--ease);
}
.way__btn-arrow { transition: transform .2s var(--ease); }

@media (hover: hover) and (pointer: fine) {
  a.way__v:not(.way__btn):hover { color: var(--red-ink); text-decoration-color: var(--red-ink); }
  .way__note a:hover { color: var(--red-ink); text-decoration-color: var(--red-ink); }
  .way__btn:hover { transform: translateY(-2px); }
  .way__btn:hover .way__btn-arrow { transform: translateX(3px); }
}
a.way__v:active { color: var(--red-ink); }
.way__btn:active { transform: translateY(0); }

/* --red-ink on a --red fill is 1.8:1 and would be an invisible focus ring, so
   the filled control takes the bone ring instead. Everything else on this page
   keeps the red one. */
.way__btn:focus-visible {
  outline: 2px solid var(--bone);
  outline-offset: 3px;
}

/* Category and area served, the last two fields a Business Profile asks for,
   and the only reason this line exists. It is not a sentence anybody needs to
   read, so it is set as fine print rather than as copy. */
.ctfine {
  margin: clamp(.9rem, 2vw, 1.25rem) 0 0;
  max-width: 74ch;
  font-size: .8125rem; line-height: 1.6; color: var(--dim-ink);
}

/* ══════════════════════════════════════════════════════════════════════════
   C.01 · THE DETAIL, COLLAPSED, .ctdetail
   --------------------------------------------------------------------------
   REPLACED .ctbook / .ctsteps: an eight-item ordered list, 418 words, all of
   it open at once, sitting between the two booking bands. Three items survive
   and they are collapsed.

   NATIVE <details>. It opens with JavaScript off, it is keyboard-operable with
   no help, and find-in-page expands a closed one (Chrome 102+, Safari 18,
   Firefox 139). The visual language, mono +/− marker in --red-ink, serif
   summary, hairline rows, is the one css/sell.css already uses for .faq, so
   the two read as one system; the class names are separate because that sheet
   is not loaded here and copying a selector across sheets is how a brand
   drifts.
   ══════════════════════════════════════════════════════════════════════════ */
.ctdetail {
  padding-top: clamp(2rem, 4.5vw, 3.25rem);
  padding-bottom: clamp(2rem, 4.5vw, 3.25rem);
}
.ctdetail h2 {
  font-size: clamp(1.75rem, 3.6vw, 2.75rem);
  letter-spacing: -.01em;
  max-width: 22ch;
}
.ctdetail h2 em { font-style: italic; color: var(--red-ink); }

.ctqs {
  margin-top: clamp(1.3rem, 3vw, 2rem);
  border-top: 1px solid var(--hair-ink);
}

.ctq {
  border-bottom: 1px solid var(--hair-ink);
  /* Opts `auto` into being an animatable length, which is what makes the
     height transition below possible without measuring anything in script.
     Inherited, so it has to sit on the <details> and not on the pseudo. */
  interpolate-size: allow-keywords;
}

.ctq__q {
  display: flex; align-items: baseline; gap: .875rem;
  padding: 1.15rem .25rem 1.15rem 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--serif);
  font-size: clamp(1.0625rem, 1.7vw, 1.25rem);
  line-height: 1.3;
  color: var(--bone);
}
/* Three engines, three ways of drawing the default triangle. */
.ctq__q::-webkit-details-marker { display: none; }
.ctq__q::marker { content: ''; }

.ctq__q::after {
  content: '+';
  margin-left: auto; flex: none;
  font-family: var(--mono); font-size: 1.125rem; line-height: 1;
  color: var(--red-ink);
}
.ctq[open] > .ctq__q::after { content: '\2212'; }

/* Inset, because a 3px outset ring on a full-width summary is clipped by the
   row's own bottom hairline. */
.ctq__q:focus-visible {
  outline: 2px solid var(--red-ink);
  outline-offset: -3px;
  border-radius: 2px;
}

.ctq__a {
  padding: 0 0 1.4rem;
  max-width: 66ch;
  font-size: .9375rem; line-height: 1.65;
  color: var(--dim-ink);
}
.ctq__a p { margin: 0; }
.ctq__a p + p { margin-top: .875rem; }
.ctq__a b { color: var(--bone); font-weight: 600; }

/* THE TRANSITION, AND IT NEEDS NO SCRIPT. ::details-content is the UA's own
   wrapper around everything after the <summary>; animating its block-size
   between 0 and auto is only legal because of interpolate-size above.
   content-visibility has to ride along with allow-discrete or the UA flips it
   to hidden on the first frame of the close and the row snaps shut.

   Where ::details-content is unsupported the whole rule is dropped and the
   disclosure opens instantly, which is a supported state, not a broken one. */
.ctq::details-content {
  block-size: 0;
  overflow: clip;
  transition:
    block-size .3s var(--ease),
    content-visibility .3s allow-discrete;
}
.ctq[open]::details-content { block-size: auto; }

@media (prefers-reduced-motion: reduce) {
  .ctq::details-content,
  .ctq[open]::details-content { transition: none; }
  a.way__v, .way__btn, .way__btn-arrow { transition: none; }
  .way__btn:hover { transform: none; }
  .way__btn:hover .way__btn-arrow { transform: none; }
}

/* ── shared link treatment ─────────────────────────────────────────────────
   The small links that live inside a sentence rather than being the answer
   themselves. Underlined at rest for the reason given above .way__v. */
.way__note a,
.ctfine a,
.ctq__a a {
  color: var(--bone);
  text-decoration: underline;
  text-decoration-color: var(--red);
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
  border-radius: 2px;
}
@media (hover: hover) and (pointer: fine) {
  .ctq__a a:hover { color: var(--red-ink); text-decoration-color: var(--red-ink); }
}
/* 3:1 against the ink ground, and it never relies on the underline it shares
   with the resting state. */
.crumb a:focus-visible,
a.way__v:focus-visible,
.way__note a:focus-visible,
.ctq__a a:focus-visible {
  outline: 2px solid var(--red-ink);
  outline-offset: 3px;
}

/* ── the hand-off to the bands ─────────────────────────────────────────────
   Both .bkx bands paint their own ink ground and their own top hairline, so
   this sheet contributes nothing to them beyond the breathing room the section
   above each one carries in its own padding-bottom. */

/* ══════════════════════════════════════════════════════════════════════════
   THE MOBILE FOLD, measured, because the note above h1 claimed this was
   already fixed and it was not.
   --------------------------------------------------------------------------
   The h1 floor drop from 2.5rem to 2.125rem was supposed to put the fourth
   answer on screen at 390x844. Measured in Chromium at that exact viewport it
   did not: the Book cell ran 772 -> 895 against an 844 fold, so the page's
   PRIMARY ACTION was 51px below it. The heading was two lines of 34px type and
   the four cells were carrying desktop padding on a phone.

   Two things fixed it. The h1 is now three words and sets on one line, worth
   34px on its own; and the head furniture and the cells give back the rest
   here. Nothing is scaled down, the type sizes are untouched, this is only
   the vertical rhythm tightening at the width where 50px decides whether
   somebody sees the booking button.

   Re-measured after the change: 371 -> 815, all four cells above the fold with
   29px to spare. If any of these numbers is edited, re-measure; a comment is
   not a test.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 559px) {
  .crumb   { margin-bottom: 1rem; }
  .eyebrow { margin-bottom: .8rem; }
  .cthead  { padding-bottom: .5rem; }
  .ctways  { padding-top: .75rem; padding-bottom: 1.05rem; }
  .way     { padding-top: .8rem; padding-bottom: .85rem; gap: .38rem; }
}

/* ══════════════════════════════════════════════════════════════════════════
   MOTION, a cascade in, and nothing else.
   --------------------------------------------------------------------------
   This page had no entrance motion at all: two hover transitions and the
   native ::details-content height, and that was the lot. It read as unfinished
   for the same reason the rest of the rebuild did.

   WHAT IT DOES NOT DO, DELIBERATELY. No storm, storms are Tier 1 and a funnel
   floor a visitor has already decided to arrive at should not make him wait
   for a renderer. No scroll listener, no rAF, no script of any kind. Nothing
   here can desynchronise js/camera.js's damped follower because nothing here
   reads scroll position: above the fold it is the document timeline, below it
   is the UA's own view() timeline, which is a compositor input, not a
   measurement this sheet takes.

   FROM-ONLY KEYFRAMES, which is this sheet's existing convention (see the
   DEGRADATION note at the top): the authored state IS the finished state and
   the animation runs BACKWARDS out of it. So `animation: none`, which is what
   prefers-reduced-motion gets, and what a browser with no @keyframes support
   gets, leaves every element exactly where it belongs, fully opaque, with no
   parallel set of declarations to keep in sync.

   THE CASCADE IS FAST ON PURPOSE. The last of the four answers is fully in at
   720ms. This is the page a paid click lands on; a phone number that fades in
   over a second and a half is a phone number somebody has to wait for.
   ══════════════════════════════════════════════════════════════════════════ */
@keyframes ct-rise { from { opacity: 0; transform: translateY(12px); } }
@keyframes ct-draw { from { transform: scaleX(0); } }

.cthead .crumb        { animation: ct-rise .46s      var(--ease) both; }
.cthead .eyebrow      { animation: ct-rise .46s .04s var(--ease) both; }
.cthead h1            { animation: ct-rise .52s .10s var(--ease) both; }

/* The one red rule on the page draws itself in from the left, which is the
   gesture the eyebrow already is, a rule with a label after it. */
.cthead .eyebrow__rule {
  transform-origin: left center;
  animation: ct-draw .5s .08s var(--ease) both;
}

/* The answers, in the order somebody reads them. ONLY THE DELAY VARIES, and it
   varies as a custom property rather than as a second declaration on a
   higher-specificity selector, see the BUG 1 note in the reduced-motion block
   below for what that cost the first time. */
.way { animation: ct-rise .42s var(--ct-in, 0s) var(--ease) both; }
.way:nth-child(1) { --ct-in: .18s; }
.way:nth-child(2) { --ct-in: .22s; }
.way:nth-child(3) { --ct-in: .26s; }
.way:nth-child(4) { --ct-in: .30s; }

/* Below the fold, a load animation is motion nobody sees, it has finished
   before the reader arrives. These get the same keyframe on the document
   timeline as a floor, then take the view() timeline where the UA has one, so
   each row rises as it actually comes up. Each element carries its own view()
   range, so the stagger is the layout's, not a list of hand-written delays
   that would have to be maintained when a row is added. */
.ctdetail .eyebrow,
.ctdetail h2,
.ctq { animation: ct-rise .5s var(--ease) both; }
.ctdetail .eyebrow__rule {
  transform-origin: left center;
  animation: ct-draw .5s var(--ease) both;
}

@supports (animation-timeline: view()) {
  .ctdetail .eyebrow,
  .ctdetail h2,
  .ctdetail .eyebrow__rule,
  .ctq {
    animation-timeline: view();
    animation-range: cover 1% cover 20%;
    animation-delay: 0s;
  }
}

/* Reduced motion, last, so it wins over the @supports block above. With
   from-only keyframes, removing the animation IS the finished state. */
@media (prefers-reduced-motion: reduce) {
  .cthead .crumb,
  .cthead .eyebrow,
  .cthead .eyebrow__rule,
  .cthead h1,
  .way,
  .ctdetail .eyebrow,
  .ctdetail .eyebrow__rule,
  .ctdetail h2,
  .ctq { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   #book-visit · THE VISIT BAND'S DISCLOSURE, .ctvisit
   --------------------------------------------------------------------------
   The band itself is css/booking-band.css's and is not touched by one
   declaration in this block. Everything here is the SUMMARY and the box the
   band now sits in.

   WHY IT EXISTS. Two full month grids stacked open was the page's real bulk,
   more than half the document height at 1440, and the second one is for the
   rarer request. Collapsing it is the "dropdown" Ethan asked for by name, and
   unlike collapsing prose it removes actual weight rather than hiding words:
   js/booking.js's IntersectionObserver never fires while it is shut, so the
   availability request for the visit diary is never made until somebody asks
   for it. Measured: a cold load fires ONE POST to booking-availability;
   opening this fires the second. Document height, both diaries painted:
   2775px closed / 4106px open at 390, 2172px / 2976px at 1440.

   DO NOT MEASURE THIS BAND WITH offsetHeight, and this is the one trap on the
   page. Chromium shuts a <details> with `content-visibility: hidden` on
   ::details-content, NOT display:none, so the .bkx inside still reports
   offsetHeight 900 at 390 while contributing zero to layout. Summing the two
   .bkx offsetHeights is what produced the false reading that 84% of the phone
   page was booking band; the honest figure is 59.9%, of which this band is
   5.8%. checkVisibility() returns false and innerText is empty while shut,
   use either of those, or the document height, and never offsetHeight.

   FULL BLEED, NOT RAILED. The summary is the width of the band it opens, so
   the band appears to unfold out of the row rather than out of a narrower
   column floating above it. That is why .ctvisit is NOT inside a .rail, the
   padding is taken here, against the same var(--gut) the rail uses, so the
   summary's text starts on the same vertical line as every other section's.

   NO `interpolate-size` HEIGHT TRANSITION HERE, unlike .ctq above, and that is
   deliberate: .ctq animates a paragraph, this would animate a month grid, a
   form and a Turnstile widget through every intermediate height while the
   band's own entrance sequence is playing inside it. Two animations
   interpolating the same box fight. So the box opens immediately and the
   MOTION is the band's own, booking.js adds .is-on the first frame the band
   intersects, which is the first frame after this opens, so the sequence
   css/booking-band.css already ships plays as it unfolds.
   ══════════════════════════════════════════════════════════════════════════ */
.ctvisit {
  border-top: 1px solid var(--hair-ink);

  /* CLEARANCE FOR THE FIXED FOOTER BAR, and it is a real measurement, not a
     guess. .footpull in css/chrome.css is `padding: .85rem var(--c-pad)` over a
     .6875rem line, 41px, and constant at every width because none of those
     three values is fluid. It is position:fixed, so it sits ON the last thing
     in <main>.

     THIS DID NOT MATTER BEFORE and it does now. The page used to END with the
     visit band, whose own bottom padding is what the bar was covering, nothing
     lost. It now ends with an INTERACTIVE ROW, and measured at 1440x900 the bar
     was covering the bottom 41px of that row's hit area, at 390x844 the same.
     A control you cannot click the bottom of is a broken control.

     Sits on the <details> rather than the <summary> so it applies in BOTH
     states: closed, it clears the summary; open, it clears the band's own
     mailto fallback, which is then the last line on the page. */
  padding-bottom: calc(41px + clamp(1.25rem, 3vw, 2rem));
}

.ctvisit__s {
  /* The row itself. Grid rather than flex so the sign column is a fixed track
     and the heading cannot shove it off the right edge at 390. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 1rem;

  width: 100%; max-width: var(--rail);
  margin: 0 auto;
  padding: clamp(1.5rem, 3.4vw, 2.25rem) var(--gut);

  cursor: pointer;
  list-style: none;
  transition: background-color .25s var(--ease);
}
/* Three engines, three ways of drawing the default triangle, same trio as
   .ctq__q above. */
.ctvisit__s::-webkit-details-marker { display: none; }
.ctvisit__s::marker { content: ''; }

.ctvisit__s:hover { background-color: rgba(245, 241, 232, .035); }

.ctvisit__s:focus-visible {
  outline: 2px solid var(--red-ink);
  outline-offset: -4px;
  border-radius: 2px;
}

.ctvisit__s-txt { display: block; }

.ctvisit__s-k {
  display: block;
  font-family: var(--mono);
  font-size: .6875rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--red-ink);
  margin-bottom: .5rem;
}

.ctvisit__s-h {
  display: block;
  font-family: var(--serif); font-weight: 500;
  font-size: clamp(1.375rem, 3vw, 2rem);
  line-height: 1.1; letter-spacing: -.01em;
  color: var(--bone);
}

/* THE SIGN. A ring with a + in it, drawn in CSS so there is no icon file and
   no inline SVG repeated per state. It rotates 45deg on open, which turns the
   + into a x, the close affordance, using one transform rather than a
   second glyph. aria-hidden in the markup: the <details> already exposes its
   own expanded state, and a screen reader announcing "plus" over that is
   noise. */
.ctvisit__s-sign {
  flex: none;
  display: grid; place-items: center;
  inline-size: 2.75rem; block-size: 2.75rem;
  border: 1px solid var(--hair-ink);
  border-radius: 50%;
  transition: transform .3s var(--ease),
              border-color .25s var(--ease),
              background-color .25s var(--ease);
}
.ctvisit__s-sign::before,
.ctvisit__s-sign::after {
  content: '';
  grid-area: 1 / 1;
  background: var(--red-ink);
}
.ctvisit__s-sign::before { inline-size: .875rem; block-size: 1px; }
.ctvisit__s-sign::after  { inline-size: 1px; block-size: .875rem; }

.ctvisit__s:hover .ctvisit__s-sign {
  border-color: var(--red-ink);
  background-color: rgba(200, 16, 46, .12);
}
.ctvisit[open] > .ctvisit__s .ctvisit__s-sign { transform: rotate(45deg); }

@media (max-width: 560px) {
  .ctvisit__s-sign { inline-size: 2.25rem; block-size: 2.25rem; }
  .ctvisit__s-sign::before { inline-size: .75rem; }
  .ctvisit__s-sign::after  { block-size: .75rem; }
}

/* The summary rises with the rest of the below-fold content, on the same
   view() timeline as .ctq. The BAND inside animates itself and must not be
   given a second animation from here.

   THE RANGE IS `entry`, NOT `cover`, AND THAT IS THE WHOLE POINT. This is the
   LAST element on the page, and a `cover` range needs scroll the page does not
   have left to give: `cover 20%` is only reached once the element has travelled
   a fifth of the way across the viewport, which at the foot of a 2337px page
   never happens. Measured at the absolute bottom, closed, at 1440 wide:

     viewport 700   opacity 1.000      viewport 1300  opacity 0.975
     viewport 900   opacity 0.999      viewport 1600  opacity 0.937
     viewport 1100  opacity 0.992      viewport 1800  opacity 0.904

   So on any tall display the summary sat permanently short of the state it is
   authored in, never grossly, which is exactly why it would have shipped.
   `entry 100%` resolves the moment the element is fully inside the viewport,
   which is a thing that always happens to an element the reader can see, so the
   animation completes at every viewport height instead of most of them.
   Re-measured across the same sweep after the change: 1.000 throughout. */
.ctvisit__s { animation: ct-rise .5s var(--ease) both; }
@supports (animation-timeline: view()) {
  .ctvisit__s {
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
    animation-delay: 0s;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ctvisit__s { animation: none; transition: none; }
  .ctvisit__s-sign { transition: none; }
}
