/* ══════════════════════════════════════════════════════════════════════════
   booking-band.css — THE TERMINAL BOOKING BAND.  `.bkx`   (T-1331)
   Companion to js/booking.js. Replaces `section.sell-band` as the last
   element inside </main> on every selling page.
   ──────────────────────────────────────────────────────────────────────────

   ══ THE MARKUP CONTRACT — COPY THIS BLOCK, EDIT ONLY THE COPY ═════════════

   Nine pages mount this. Nobody has to open js/booking.js or this file to
   change a word: every sentence in the band is a slot in the markup below.
   Edit the text between the tags. Do not rename a class and do not remove an
   element — an absent element is treated as "this mount has no such part",
   which for the fields means the band cannot book.

   <section class="bkx is-dark" id="book" data-bk
            data-bk-service="technology"
            data-bk-mode="call"
            data-bk-fallback="Email contact@tnaado.ca and we'll set a time.">

     <div class="stormeye bkx__eye" data-storm data-storm-mode="emblem"
          data-storm-spin="3" data-storm-layer="under"></div>

     <div class="bkx__inner">
       <div class="bkx__say">
         <p  class="bkx__k">Book the scoping call</p>
         <h2 class="bkx__h">Tell us what it has to do, <em>and we will price it.</em></h2>
         <p  class="bkx__sub">Thirty minutes, on the calendar of the person who
             would run it. No deck, no discovery fee.</p>

         <div class="bkx__offer">
           <p class="bkx__offer-k">Free, on the call</p>
           <p class="bkx__offer-h">A written scope with the price in it</p>
           <p class="bkx__offer-p">Line items, the ceiling and what it costs to
              run, sent the same day &mdash; yours whether or not you hire us.</p>
         </div>
       </div>

       <div class="bkx__panel">
         <div class="bkx__panel-head">
           <p class="bkx__panel-t">Pick a time</p>
           <p class="bkx__tz" data-bk-tz>All times Toronto</p>
         </div>

         <div class="bkx__slots" data-bk-slots></div>
         <p   class="bkx__status" data-bk-status role="status" aria-live="polite"></p>

         <div class="bkx__fields">
           <p class="bkx__field">
             <label for="bkxName">Name</label>
             <input id="bkxName" name="name" type="text" autocomplete="name" required>
           </p>
           <p class="bkx__field">
             <label for="bkxEmail">Email</label>
             <input id="bkxEmail" name="email" type="email" autocomplete="email" required>
           </p>
           <p class="bkx__field bkx__field--wide">
             <label for="bkxNeed">What you need <span>(optional)</span></label>
             <input id="bkxNeed" name="message" type="text" autocomplete="off"
                    maxlength="180">
           </p>
         </div>

         <div class="bkx__hp" aria-hidden="true"
              style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden">
           <label for="bkxWeb">Website</label>
           <input id="bkxWeb" name="website_url" type="text" tabindex="-1" autocomplete="off">
         </div>

         <div class="bkx__act">
           <button type="button" class="bkx__go" data-bk-go>
             Book this time <span aria-hidden="true">&rarr;</span></button>
           <a class="bkx__alt" href="/contact">Use the contact form
             <span aria-hidden="true">&rarr;</span></a>
         </div>

         <p class="bkx__fallback">Prefer email?
           <a href="mailto:contact@tnaado.ca">contact@tnaado.ca</a>
           and we&rsquo;ll set a time.</p>

         <div class="bkx__turnstile" data-bk-turnstile></div>
         <div class="bkx__done" data-bk-done role="status" aria-live="polite" hidden></div>
       </div>
     </div>
   </section>

   HEAD / FOOT:
     <link rel="stylesheet" href="/css/booking-band.css?v=v20260816b1">
     <script src="/js/booking.js?v=v20260816b1" defer></script>

   ══ FOUR THINGS IN THAT BLOCK ARE LOAD-BEARING ════════════════════════════

   1. NO `data-reveal` ANYWHERE INSIDE `.bkx`. js/home-scroll.js drives
      [data-reveal] with its own opacity/transform transition; on an element
      this file also animates the two fight and the winner is whichever ran
      last. The entrance below covers every part of the band already.

   2. `is-dark` — site.css names dark sections POSITIVELY (l.1845). Without it
      site.css paints `main > section` bone and re-points --tone-* to the ink
      scale. This file survives either way (see GROUND below), but the marker
      is the canonical one and it is what the next reader will look for.

   3. `type="button"` on `.bkx__go`. There is no <form> here, so it is not the
      submit trap /contact has — but the band gets pasted into pages that do
      have forms, and a type-less button inside one submits it.

   4. THE HONEYPOT SHIPS ON EVERY MOUNT. booking-create is public and
      unauthenticated and these pages take paid traffic; a junk booking is not
      just inbox clutter, it is a conversion Smart Bidding learns to buy more
      of. Inline style rather than a class, exactly as /contact does it, so a
      honeypot cannot stop being hidden because a stylesheet moved.

   If a page ever mounts TWO bands, suffix every id (`bkxName2`, …). The
   script never reads an id — it queries inside its own root — but two
   `for="bkxName"` labels on one page point at the same input.

   ══ THE THREE ATTRIBUTES ══════════════════════════════════════════════════

   data-bk-service   WHICH CALENDAR ANSWERS. One of exactly eight slugs:

                       technology        media        consulting    not-sure
                       piece-technology  piece-media  piece-consulting
                       visit

                     The three `piece-*` slugs carry the free-deliverable
                     floor of three business days. They are separate slugs
                     rather than a flag because the notice has to reach the
                     PRODUCER of the piece — only a producer can recut a video
                     or build a screen — and a single `piece` slug would route
                     every one of them to a single role.

                     A VALUE OUTSIDE THAT LIST FAILS SAFE BUT SILENTLY: the
                     server collapses it to `not-sure`, which books the owner
                     on the 120-minute floor. The booking still works, so a
                     typo here produces no error anywhere — it produces a
                     wrongly-routed call. Check the slug against the list.

   data-bk-mode      "call" or "visit". Copy only: it picks the noun in the
                     sentences the script generates ("each call runs 30
                     minutes" / "each visit"), and it drops the Google Meet
                     line from the confirmation for a visit, where there is no
                     Meet link to promise. Nothing about routing or the
                     endpoint changes.

   data-bk-fallback  The one sentence spliced into every failure message. See
                     THE FALLBACK LINE below.

   ══ GROUND, AND WHY THIS FILE CANNOT BE OUT-SPECIFIED ═════════════════════

   The band is INK. The panel inside it is BONE. That inversion is the whole
   depth read: a light object on a dark ground is the thing being thrown
   forward, and it is also the only ground on which a real form is inviting
   rather than merely legible.

   site.css's light register is `main > section:not(:where(…))` at (0,0,2) and
   its tonal rules are `:where(main,.sv-section) :where(h1…h6,dt,b,strong)` at
   (0,0,2). Both are beaten by an ordinary class. So instead of relying on the
   register list, this file does what site.css's own dark-panel block (l.2045)
   does: RESTATES THE GROUND-RELATIVE TOKENS on the element that owns the
   ground. `.bkx` restates the bone scale, `.bkx__panel` restates the ink
   scale. Every generic rule in site.css then resolves correctly inside each
   of them without knowing anything about this component, and dropping
   `is-dark` from the markup costs nothing but clarity.

   Two traps avoided by NAMING rather than by fighting:
     · site.css paints `:is(.eyebrow,[class*="__eyebrow"],[class*="eyebrow"])`
       at (0,1,2) inside the light register — higher than a plain class. The
       kicker here is `.bkx__k`, which contains no such substring. (This is
       the same reason `.sell-band__k` is called `__k`.)
     · the same register sets `border-color` on `:where(hr,[class*="rule"])`.
       No class in this file contains "rule".

   Nothing in this file is !important and nothing needs to be.

   ══ COLOUR — MEASURED ON THE COMPOSITED VALUE, NOT THE DECLARED ONE ═══════

   No value below is invented. Every literal is already on this site
   (site.css, booking.css, sell.css, tech-webbuild.css) and every alpha is
   composited before it is measured.

   ON THE INK BAND (#0a0a0a)
     #f5f1e8                     17.6:1   headline, offer head, focus ring
     rgba(245,241,232,.70)        9.2:1   subline, offer body
     rgba(245,241,232,.58)        6.5:1   --on-black-faint / --tone-cap, for
                                          any generic rule that reads them
     #e8455a                      5.1:1   kicker, offer label, the <em>
     #c8102e                      3.4:1   FILLS AND RULES ONLY — never text.
                                          It is the offer's red spine and the
                                          band's top hairline, nothing else.
     rgba(245,241,232,.14)         —      --tone-rule, decorative only

   ON THE BONE PANEL (#faf8f3, 18.7:1 against the band)
     #0a0a0a                     18.7:1   panel title, input text, chip label
     rgba(10,10,10,.82)          11.6:1   the panel's inherited body tone
     rgba(10,10,10,.70)           7.5:1   labels, timezone, fallback line
     rgba(10,10,10,.72)           7.3:1   status line
     rgba(10,10,10,.62)           5.6:1   optional hint, taken chips
     #7a0819                     10.6:1   day headings, error text, the
                                          fallback line's mailto
     #a4132c                      7.3:1   chip hover label
     #c8102e                      5.5:1   focus ring (needs 3:1)
     #fff on #c8102e              5.9:1   the chosen chip, the book button
     #fff on #a50d26              7.8:1   its hover
     #1f7a3d                      5.1:1   the confirmed rule
     rgba(10,10,10,.46)           3.2:1   input, chip and ghost-button borders
                                          — the ONLY thing identifying those
                                          controls, so 1.4.11's 3:1 applies.
                                          booking.css uses .22 (1.6:1) for the
                                          same job; that is a miss in a file
                                          this one does not own.

   ══ THE ENTRANCE ═════════════════════════════════════════════════════════

   Contract copied from css/tech-webbuild.css — read its header. Restated
   because it is the part most likely to be broken by a later edit:

   THE FINISHED STATE IS THIS STYLESHEET'S DEFAULT. Every element is authored
   in its final appearance. The animations run BACKWARDS out of `from`-only
   keyframes, held through their delay by `animation-fill-mode: both`, and the
   `to` is whatever the element already computes to. Consequences, all of them
   the point:

     · js/booking.js 404s, is blocked, or throws before the observer → `is-on`
       never lands, no animation property ever applies, and the band renders
       COMPLETE AND STATIC: headline, offer, fields, fallback, all of it.
     · prefers-reduced-motion is two lines at the foot of this file instead of
       a parallel set of declarations that has to be kept in sync.
     · there is no no-JS block, because there is nothing for one to restore.

   THE WHOLE SEQUENCE RUNS OFF ONE CLASS added once by an IntersectionObserver
   and never removed. Every beat is an `animation-delay`. No rAF, no scroll
   listener, no timer chain. CSS animations composite off the main thread, so
   the run stays smooth while a long page is still settling — which is exactly
   the condition this band is seen in, since it is the last element on it.

   Z-AXIS, TOWARD THE READER. `perspective` on the section, `preserve-3d` on
   the inner, and the panel arrives from translateZ(-320px) scale(.86). At
   perspective 1400px that Z alone is a 0.81 projection, so the panel starts
   at roughly 70% and grows past the reader's eye rather than sliding in.

   THE EMBLEM RUNS LATE ON PURPOSE. Its scale-up starts AFTER the panel has
   been thrown and finishes well after the panel has landed, so the panel
   reads as ejected by something that is still expanding behind it. Starting
   them together reads as two things fading in; starting the emblem first
   reads as a background. Late and long is what makes it the cause.

   NOTE FOR js/storm.js: that file sizes its canvas from getBoundingClientRect
   on the host, and this file transforms the host. Mount-time measurement is
   safe — `is-on` cannot land before DOMContentLoaded — and ResizeObserver
   reports the border box, which a transform does not change. Only a manual
   re-measure DURING the 1.9s run would read a scaled box.

   MOTION IS ENTRANCE + STATE ONLY. Nothing here loops, and the slot grid
   never animates: a grid of times that moves while it is being read is worse
   than a static one.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── THE BAND ─────────────────────────────────────────────────────────────
   Tokens first. Restating the dark scale here is what makes the component
   correct inside ANY band, light or dark, without editing site.css — the
   same move site.css itself makes for .t-dark and friends at l.2045. */

.bkx {
  --bkx-e: var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
  --bkx-rail: 1180px;
  --bkx-pad: clamp(1.15rem, 4vw, 2.5rem);

  /* Ground-relative tones, DARK. --ink-true rather than --ink: --ink is
     inverted to bone inside light scopes and an ink ground that flipped to
     bone would take the whole band with it. */
  --on-black: #f5f1e8;
  --on-black-mute: rgba(245, 241, 232, .70);
  --on-black-faint: rgba(245, 241, 232, .58);
  --rule-on-black: rgba(245, 241, 232, .14);
  --tone-head: #f5f1e8;
  --tone-cap: rgba(245, 241, 232, .58);
  --tone-eyebrow: #e8455a;
  --tone-rule: rgba(245, 241, 232, .14);
  --tone-accent: #e8455a;

  position: relative;
  /* CLIP, NOT HIDDEN, AND THE DIFFERENCE IS A LIVE BUG. `overflow: hidden`
     makes this section a SCROLLPORT -- a non-scrolling one -- and a sticky
     descendant resolves its offsets against the nearest scrollport. So
     .bkx__held, the held-slot block whose own comment says "STICKY IS THE WHOLE
     POINT", never stuck on any of the 21 pages carrying this band.

     MEASURED, controlled A/B at five scroll offsets on two pages:
       as shipped (hidden):  heldTop 79.8 -> -120.2 -> -420.2 -> -820.2 -> -1320.2
       with clip:            pinned at 80 at every one
     It scrolled away 1:1 with the page, which is the definition of not sticky.

     `clip` clips exactly the same pixels -- it exists for this -- but does not
     create a scroll container. The clipping is still needed: the panel animates
     in from translateZ(-320px) scale(.86) and would otherwise paint outside the
     section. */
  overflow: clip;
  background: var(--ink-true, #0a0a0a);
  color: var(--on-black);
  border-top: 1px solid rgba(200, 16, 46, .4);
  /* Was clamp(2.8rem, 6vw, 4.6rem) — 74px top AND bottom at 1440, on a band
     measured at 1008px, i.e. 45% of /contact. A form does not need a hero's
     breathing room; it needs to be findable and quick. */
  padding-block: clamp(2rem, 3.6vw, 2.75rem);

  /* The depth the panel is thrown through. On the SECTION, so the inner can
     preserve-3d without becoming the perspective origin itself. */
  perspective: 1400px;
  perspective-origin: 62% 42%;
}

.bkx__inner {
  position: relative;
  z-index: 1;                     /* above the emblem, which is layer "under" */
  transform-style: preserve-3d;   /* or the panel's translateZ flattens to nil */
  max-width: var(--bkx-rail);
  margin-inline: auto;
  padding-inline: var(--bkx-pad);
  box-sizing: border-box;
  display: grid;
  /* Was clamp(1.8rem, 4vw, 3.2rem) — 51px between every row of the band. */
  gap: clamp(1.4rem, 2.4vw, 2rem);
}

@media (min-width: 900px) {
  .bkx__inner {
    grid-template-columns: minmax(0, .92fr) minmax(0, 1.08fr);
    /* CENTRE, not start. The panel's height is data — four days of open times
       makes it roughly twice the height of the copy, and none makes it a
       quarter of it. Aligned to the top, the copy column reads as a block
       that ran out; centred, the two read as a composition at every height
       the endpoint can produce. */
    align-items: center;
  }
}

/* ── THE COPY SLOTS ───────────────────────────────────────────────────── */

.bkx__k {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  font-weight: 600;              /* small mono needs weight to reach its colour */
  color: #e8455a;
  margin: 0 0 .9rem;
}

.bkx__h {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 2.8vw, 2.125rem);
  line-height: 1.12;
  letter-spacing: -.015em;
  color: #f5f1e8;
  margin: 0;
  max-width: 20ch;
}
.bkx__h em { font-style: italic; color: #e8455a; }

.bkx__sub {
  font-size: .92rem;
  line-height: 1.62;
  color: rgba(245, 241, 232, .70);
  margin: 1rem 0 0;
  max-width: 46ch;
}

/* ── THE OFFER ────────────────────────────────────────────────────────────
   The free deliverable. A red spine rather than a card, so it reads as a
   pulled-out clause of the pitch and not as a second panel competing with
   the real one on the right. */

.bkx__offer {
  margin-top: clamp(1.4rem, 3vw, 2rem);
  padding: .95rem 0 .95rem 1.1rem;
  border-left: 2px solid var(--red, #c8102e);
  max-width: 46ch;
}

.bkx__offer-k {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 600;
  color: #e8455a;
  margin: 0 0 .45rem;
}

.bkx__offer-h {
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.35;
  color: #f5f1e8;
  margin: 0;
}

.bkx__offer-p {
  font-size: .86rem;
  line-height: 1.6;
  color: rgba(245, 241, 232, .70);
  margin: .5rem 0 0;
}

/* ── THE PANEL ────────────────────────────────────────────────────────────
   Bone on ink. The tokens go back to the INK scale here for the same reason
   they were set to the bone scale on .bkx: site.css's unscoped tonal rules
   are (0,0,2) and read whatever is in scope, so a <strong> in here must find
   the light-ground head colour or it paints bone on bone. */

.bkx__panel {
  --on-black: var(--ink-1, #0a0a0a);
  --on-black-mute: var(--ink-3, rgba(10, 10, 10, .70));
  --on-black-faint: var(--ink-4, rgba(10, 10, 10, .62));
  --rule-on-black: rgba(10, 10, 10, .16);
  --tone-head: var(--ink-1, #0a0a0a);
  --tone-cap: var(--ink-4, rgba(10, 10, 10, .62));
  --tone-eyebrow: #a4132c;
  --tone-rule: rgba(10, 10, 10, .16);
  --tone-accent: var(--red, #c8102e);

  position: relative;
  background: var(--bone, #faf8f3);
  color: var(--ink-2, rgba(10, 10, 10, .82));
  padding: clamp(1.2rem, 2.8vw, 1.9rem);
  /* The estate's soft-top-left / cut-bottom-right card shape — the same one
     firm-suite.css gives .ct-aside and booking.css gives the picker. */
  border-radius: 1.4rem 0 0 0;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 1.6rem), calc(100% - 1.6rem) 100%, 0 100%);
}

.bkx__panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem 1rem;
  padding-bottom: .85rem;
  border-bottom: 1px solid rgba(10, 10, 10, .12);
}

.bkx__panel-t {
  font-family: var(--font-body);
  font-size: .95rem;
  font-weight: 600;
  color: var(--ink-true, #0a0a0a);
  margin: 0;
}

/* Never optional. A visitor in Vancouver must not have to guess which 2pm. */
.bkx__tz {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  font-weight: 600;
  color: rgba(10, 10, 10, .70);
  margin: 0;
}

/* ── SLOTS ────────────────────────────────────────────────────────────────
   Empty in the markup and filled by the script. `:empty` collapses its own
   margin so a band whose times never loaded has no hole where they were. */

.bkx__slots {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 1rem;
}
.bkx__slots:empty { margin-top: 0; }

.bkx__day-name {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 600;
  color: #7a0819;
  margin: 0 0 .5rem;
}

.bkx__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5.6rem, 1fr));
  gap: .45rem;
}

.bkx__slot {
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .04em;
  font-weight: 500;
  color: var(--ink-true, #0a0a0a);
  background: #fff;
  /* .46 alpha = 3.2:1. The border is the only thing identifying this control,
     so SC 1.4.11 applies to it and a hairline does not clear it. */
  border: 1px solid rgba(10, 10, 10, .46);
  border-radius: 0;
  min-height: 44px;              /* the primary tap target on a phone */
  padding: .5rem .35rem;
  text-align: center;
  cursor: pointer;
  transition: border-color .18s var(--bkx-e),
              background-color .18s var(--bkx-e),
              color .18s var(--bkx-e);
}

.bkx__slot:hover:not(:disabled) { border-color: var(--red, #c8102e); color: #a4132c; }

.bkx__slot:focus-visible {
  outline: 2px solid var(--red, #c8102e);
  outline-offset: 2px;
}

.bkx__slot[aria-pressed="true"] {
  background: var(--red, #c8102e);
  border-color: var(--red, #c8102e);
  color: #fff;
  font-weight: 600;
}

/* TAKEN. Struck through and muted, and that is the ENTIRE message — no title,
   no tooltip, no reason. The endpoint sends a boolean and there is nothing
   else here to render even if a later version sent more. */
.bkx__slot:disabled {
  background: rgba(10, 10, 10, .05);
  border-color: rgba(10, 10, 10, .16);
  color: rgba(10, 10, 10, .62);
  text-decoration: line-through;
  cursor: not-allowed;
}


/* ── THE CALENDAR ─────────────────────────────────────────────────────────
   `.bkxc`, built by js/booking.js into [data-bk-slots].

   A MONTH, not a list. Seven columns, weeks as rows, every day of the month
   present including the ones nobody can book — a grid that changes shape when
   the weekends are cut out is not a calendar and cannot be scanned.

   STATE IS NEVER COLOUR ALONE. Every square carries a second channel: a count
   when times are free, an em dash when the day is spoken for, a middle dot
   when it is beyond the horizon, and nothing at all on a closed weekend. The
   whole grid survives greyscale, and every square also carries a full
   aria-label naming the date and the state.

   THE UNFOLD IS A ROW OF THE GRID. It sits between two weeks, so the calendar
   opens where the visitor clicked. Its height animates through
   grid-template-rows: 0fr -> 1fr, which is the one way to transition to a
   content height nobody has measured; `.bkxc__clip` carries the overflow so
   the times are hidden while it is closed rather than merely transparent.  */

.bkxc {
  margin-top: 1rem;
}

/* ── month bar ── */

.bkxc__nav {
  display: grid;
  /* The arrow tracks SIZE TO THE ARROWS instead of restating their width.
     This was `2.25rem 1fr 2.25rem`, and it was the 8px horizontal overflow
     measured at 390: .bkxc__navbtn below carries the WCAG 2.5.5 floor of
     min-width 44px, so each button rendered 44px inside a 36px track and hung
     8px past it. The right-hand one ended at x=352.4+8 while the panel's
     content box ended at 352.4, so .bkxc__nav, .bkxc and .bkx__slots all
     measured scrollWidth 323 against clientWidth 315.

     It only escaped notice because 8px is narrower than .bkx__panel's 19.2px
     padding, so the spill landed in the gutter rather than off the screen.

     The track width was a COPY of the button width, and the two fell out of
     step the moment the buttons were raised 36 → 44 for the touch-target fix
     (see the comment on .bkxc__navbtn). `auto` deletes the copy: the track is
     whatever the button is, so it is 44px on a coarse pointer and 36px in the
     `pointer: fine` block at the end of this file, with nothing to keep in
     sync and no way for the two to disagree again. */
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: .5rem;
  margin-bottom: .75rem;
}

.bkxc__month {
  font-family: var(--font-body);
  font-size: .95rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ink-true, #0a0a0a);
  text-align: center;
  margin: 0;
}

.bkxc__navbtn {
  /* 44px is the WCAG 2.5.5 floor and applies wherever the pointer may be coarse.
     These two were 2.25rem (36px) unconditionally — the only controls on the band
     that ignored the rule, on all 20 booking pages. The compact size now lives in
     the pointer:fine block below, like every other relaxation here. */
  min-width: 44px;
  min-height: 44px;
  display: grid;
  place-items: center;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--ink-true, #0a0a0a);
  background: #fff;
  border: 1px solid rgba(10, 10, 10, .46);
  border-radius: 0;
  cursor: pointer;
  transition: border-color .18s var(--bkx-e), color .18s var(--bkx-e);
}
.bkxc__navbtn:hover:not([aria-disabled="true"]) { border-color: var(--red, #c8102e); color: #a4132c; }
.bkxc__navbtn:focus-visible { outline: 2px solid var(--red, #c8102e); outline-offset: 2px; }

/* DISABLED, NOT HIDDEN. At the current month the back control stays exactly
   where it was and says so — a control that disappears at the edge moves the
   month label with it and teaches the visitor nothing. */
.bkxc__navbtn[aria-disabled="true"] {
  color: rgba(10, 10, 10, .34);
  border-color: rgba(10, 10, 10, .16);
  background: rgba(10, 10, 10, .04);
  cursor: not-allowed;
}

/* ── the grid ── */

.bkxc__week {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: .25rem;
}
.bkxc__weeks { display: flex; flex-direction: column; gap: .25rem; }

.bkxc__week--head { margin-bottom: .3rem; }

.bkxc__dow {
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  font-weight: 600;
  color: rgba(10, 10, 10, .62);
  text-align: center;
  padding-bottom: .2rem;
}

.bkxc__cell, .bkxc__pad { display: block; min-width: 0; }

.bkxc__day {
  position: relative;
  width: 100%;
  min-height: 44px;                 /* the tap target, on the smallest phone */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .1rem;
  padding: .3rem .15rem;
  font-family: var(--font-mono);
  background: #fff;
  /* .46 alpha = 3.2:1. The border is the only thing identifying this control,
     so SC 1.4.11 applies to it and a hairline does not clear it. */
  border: 1px solid rgba(10, 10, 10, .46);
  border-radius: 0;
  cursor: pointer;
  transition: border-color .18s var(--bkx-e),
              background-color .18s var(--bkx-e),
              color .18s var(--bkx-e);
}

.bkxc__num {
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ink-true, #0a0a0a);
  line-height: 1;
}

.bkxc__mark {
  font-size: .54rem;
  letter-spacing: .06em;
  line-height: 1;
  min-height: .54rem;               /* holds its line so squares never jitter */
  color: rgba(10, 10, 10, .62);
}

.bkxc__day:focus-visible { outline: 2px solid var(--red, #c8102e); outline-offset: 2px; z-index: 1; }

/* BOOKABLE. The count is the message; red is the confirmation. */
.bkxc__day.is-open:hover { border-color: var(--red, #c8102e); }
.bkxc__day.is-open .bkxc__mark { color: #a4132c; font-weight: 700; }

/* FULLY BOOKED — inside the horizon, nothing left. Struck number and an em
   dash, which is a different statement from "not open yet" below. */
.bkxc__day.is-full {
  background: rgba(10, 10, 10, .05);
  border-color: rgba(10, 10, 10, .16);
  cursor: not-allowed;
}
.bkxc__day.is-full .bkxc__num { color: rgba(10, 10, 10, .62); text-decoration: line-through; }

/* CLOSED — a weekend. Present and quiet, so the grid keeps its shape and the
   rule ("we book weekdays") is learned from the picture. */
.bkxc__day.is-off {
  background: transparent;
  border-color: rgba(10, 10, 10, .10);
  cursor: default;
}
.bkxc__day.is-off .bkxc__num { color: rgba(10, 10, 10, .34); font-weight: 500; }

/* PAST. */
.bkxc__day.is-past {
  background: transparent;
  border-color: rgba(10, 10, 10, .10);
  cursor: not-allowed;
}
.bkxc__day.is-past .bkxc__num { color: rgba(10, 10, 10, .30); text-decoration: line-through; }

/* BEYOND THE HORIZON — a working day we have not opened yet. Dashed, because
   a dashed edge reads as provisional and a solid grey one reads as dead. */
.bkxc__day.is-beyond {
  background: transparent;
  border-style: dashed;
  border-color: rgba(10, 10, 10, .22);
  cursor: not-allowed;
}
.bkxc__day.is-beyond .bkxc__num { color: rgba(10, 10, 10, .40); }

/* TODAY. An underline rather than a fill, so it can coexist with any state. */
.bkxc__day.is-today .bkxc__num { box-shadow: 0 2px 0 -1px currentColor; }

/* OPEN. The square and the drawer under it read as one object. */
.bkxc__day[aria-expanded="true"] {
  background: var(--red, #c8102e);
  border-color: var(--red, #c8102e);
}
.bkxc__day[aria-expanded="true"] .bkxc__num,
.bkxc__day[aria-expanded="true"] .bkxc__mark { color: #fff; }

/* ── the unfold ── */

.bkxc__unfoldrow { display: block; }

.bkxc__unfoldcell { display: block; }

.bkxc__clip {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .34s var(--bkx-e);
}
.bkxc__unfoldrow.is-open .bkxc__clip { grid-template-rows: 1fr; }

.bkxc__unfold {
  overflow: hidden;                 /* the half that actually hides the times */
  min-height: 0;
  opacity: 0;
  transition: opacity .2s var(--bkx-e) .06s;
  /* Tied to the red square above it by a rule down its left edge, so it reads
     as that day opening rather than as a second panel that appeared. */
  border-left: 2px solid var(--red, #c8102e);
  padding-left: .7rem;
}
.bkxc__unfoldrow.is-open .bkxc__unfold { opacity: 1; }

.bkxc__unfold > * { margin-top: .55rem; }

.bkxc__unfold-h {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 600;
  color: #7a0819;
  margin: .55rem 0 .5rem;
}

/* ── legend ── */

.bkxc__legend {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .9rem;
  margin: .8rem 0 0;
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 600;
  color: rgba(10, 10, 10, .62);
}
.bkxc__key { display: inline-flex; align-items: center; gap: .35rem; }
.bkxc__key i {
  width: .6rem; height: .6rem;
  border: 1px solid rgba(10, 10, 10, .46);
  background: #fff;
}
.bkxc__key--full i { background: rgba(10, 10, 10, .12); border-color: rgba(10, 10, 10, .16); }
.bkxc__key--off  i { background: transparent; border-color: rgba(10, 10, 10, .10); }
.bkxc__key--open i { border-color: var(--red, #c8102e); }
.bkxc__key--beyond i { background: transparent; border-style: dashed; border-color: rgba(10, 10, 10, .22); }

/* ── STATUS ───────────────────────────────────────────────────────────────
   One live region for loading, empty and error. It holds its height when
   empty so the fields below do not jump as messages arrive. */

.bkx__status {
  font-family: var(--font-mono);
  font-size: .66rem;
  line-height: 1.6;
  letter-spacing: .05em;
  color: rgba(10, 10, 10, .72);
  margin: .85rem 0 0;
  min-height: 1.2em;
}
.bkx__status--err { color: #7a0819; font-weight: 600; }

/* ── FIELDS ───────────────────────────────────────────────────────────────
   Two, plus one optional line. Everything else booking-create accepts is
   optional and every extra box costs a booking. */

.bkx__fields {
  display: grid;
  gap: .8rem;
  margin-top: 1.1rem;
}
@media (min-width: 520px) {
  .bkx__fields { grid-template-columns: 1fr 1fr; }
  .bkx__field--wide { grid-column: 1 / -1; }
}

.bkx__field { display: flex; flex-direction: column; gap: .35rem; margin: 0; }

.bkx__field label {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  font-weight: 600;
  color: rgba(10, 10, 10, .70);
}
.bkx__field label span {
  font-weight: 500;
  letter-spacing: .1em;
  color: rgba(10, 10, 10, .62);
}

.bkx__field input {
  font-family: var(--font-body);
  font-size: .92rem;
  color: var(--ink-true, #0a0a0a);
  background: #fff;
  border: 1px solid rgba(10, 10, 10, .46);
  border-radius: 0;
  min-height: 44px;
  padding: .6rem .75rem;
  width: 100%;
  box-sizing: border-box;
  transition: border-color .18s var(--bkx-e);
}
.bkx__field input:hover { border-color: rgba(10, 10, 10, .62); }
.bkx__field input:focus-visible {
  outline: 2px solid var(--red, #c8102e);
  outline-offset: 1px;
  border-color: var(--red, #c8102e);
}
/* The script marks a field the server rejected. Colour is never the only
   signal — the status line above says what is wrong in words. */
.bkx__field input[aria-invalid="true"] { border-color: #7a0819; }

/* ── THE TOPIC CONTROL ────────────────────────────────────────────────────
   /contact only. It is the first thing in the panel because it decides the
   grid underneath it — which role the booking routes to, and how much notice
   that role needs.

   IT IS DELIBERATELY THE LOUDEST FIELD IN THE PANEL. Everything else here is
   a detail on a booking; this one decides WHOSE booking it is. A quiet select
   that reads as an optional preference gets left on its default, and its
   default is "not sure", which routes every enquiry on the site's main door to
   one person. The red left rule is the same device the unfolded day uses, for
   the same reason: this is a choice, not a label. */

.bkx__topic {
  border-left: 2px solid var(--red, #c8102e);
  padding-left: .7rem;
  margin-bottom: .5rem;
}

.bkx__topic select {
  font-family: var(--font-body);
  font-size: .92rem;
  color: var(--ink-true, #0a0a0a);
  background: #fff;
  border: 1px solid rgba(10, 10, 10, .46);
  border-radius: 0;
  min-height: 44px;
  padding: .6rem 2.2rem .6rem .75rem;
  width: 100%;
  box-sizing: border-box;
  cursor: pointer;
  /* The caret is drawn rather than inherited, because the platform one differs
     on every OS and one of them is invisible on a bone ground. Encoded inline:
     the CSP on this site allows no external image host. */
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M0 0h10L5 6z' fill='%23c8102e'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .8rem center;
  background-size: .6rem .36rem;
  transition: border-color .18s var(--bkx-e);
}
.bkx__topic select:hover { border-color: rgba(10, 10, 10, .62); }
.bkx__topic select:focus-visible {
  outline: 2px solid var(--red, #c8102e);
  outline-offset: 1px;
  border-color: var(--red, #c8102e);
}

/* ── THE HELD SLOT ───────────────────────────────────────────────────────
   Built by js/booking.js and inserted between the calendar and the fields.

   STICKY IS THE WHOLE POINT. The chosen time is the reason the visitor is
   still on the page, and the fields underneath are long enough on a phone that
   the grid — and with it any evidence of what they picked — leaves the screen
   the moment they start typing. Pinned under the site header, it is on screen
   for the entire length of the form.

   `--header-h` is site.css's own token, the same one .bkx[id] reads for its
   scroll-margin, so the block lands under the sticky header rather than behind
   it. The fallback matches that rule's. */

.bkx__held {
  position: sticky;
  top: calc(var(--header-h, 72px) + 8px);
  z-index: 2;
  margin: .9rem 0 1.1rem;
  padding: .8rem .9rem .75rem;
  background: var(--ink-true, #0a0a0a);
  color: var(--bone, #faf8f3);
  /* The estate's cut corner, mirrored from .bkx__panel so the block reads as
     part of the card rather than as something dropped on top of it. */
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - .9rem), calc(100% - .9rem) 100%, 0 100%);
}

.bkx__held-k {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 600;
  /* #ef5c6e, not --red: at this size on ink the brand red is 3.37:1 and fails.
     Same substitution the confirmation email makes, for the same reason. */
  color: #ef5c6e;
  margin: 0 0 .2rem;
}

.bkx__held-when {
  font-family: var(--font-body);
  font-size: 1.02rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--bone, #faf8f3);
  margin: 0;
}

.bkx__held-meta {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: rgba(245, 241, 232, .72);
  margin: .3rem 0 0;
}

/* THE HONEST SENTENCE. Nothing is reserved server-side on selection — see the
   note in js/booking.js — so this says what is actually true rather than
   implying a countdown that does not exist. */
.bkx__held-note {
  font-family: var(--font-body);
  font-size: .76rem;
  line-height: 1.45;
  color: rgba(245, 241, 232, .62);
  margin: .45rem 0 0;
  max-width: 46ch;
}

.bkx__held-change {
  display: inline-block;
  margin-top: .55rem;
  padding: 0;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 600;
  color: #ef5c6e;
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  cursor: pointer;
}
.bkx__held-change:hover { color: var(--bone, #faf8f3); }
.bkx__held-change:focus-visible { outline: 2px solid #ef5c6e; outline-offset: 3px; }

/* ── THE FREE-PIECE SWITCH ────────────────────────────────────────────────
   A real <input type="checkbox">, not a styled div: the native control carries
   its own keyboard behaviour, its own announced state and its own focus ring,
   and every hand-rolled switch on the web has had to reimplement all three
   badly. Only the size and the alignment are ours. */

.bkx__piece {
  display: flex;
  align-items: flex-start;
  gap: .55rem;
  margin: 0 0 1rem;
  padding-left: .7rem;
}

.bkx__piece input {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  margin: .15rem 0 0;
  accent-color: var(--red, #c8102e);
  cursor: pointer;
}
.bkx__piece input:focus-visible { outline: 2px solid var(--red, #c8102e); outline-offset: 2px; }

.bkx__piece label {
  font-family: var(--font-body);
  font-size: .84rem;
  line-height: 1.45;
  font-weight: 600;
  color: var(--ink-true, #0a0a0a);
  cursor: pointer;
}
.bkx__piece label span {
  display: block;
  font-weight: 400;
  font-size: .78rem;
  color: rgba(10, 10, 10, .70);
}

/* NOT HIDDEN WHEN IT DOES NOT APPLY. "I'm not sure yet" has no free piece to
   bring, because there is nothing to build for somebody who has not said what
   they want. Dimming it in place keeps the panel's height steady and lets the
   visitor see the option they would get back by choosing a practice. */
.bkx__piece.is-off { opacity: .45; }
.bkx__piece.is-off label { cursor: not-allowed; }

/* ── ACTIONS ──────────────────────────────────────────────────────────────
   Two controls, always both. The button is the booking; the link is a real
   href that works with no JavaScript at all, which is what stops the button
   being a dead end when js/booking.js never loads. */

.bkx__act {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .7rem;
  margin-top: 1.2rem;
}

.bkx__go,
.bkx__alt {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-family: var(--font-body);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  min-height: 44px;
  padding: .7rem 1.3rem;
  border-radius: 0;
  cursor: pointer;
  transition: background-color .2s var(--bkx-e),
              border-color .2s var(--bkx-e),
              color .2s var(--bkx-e),
              transform .2s var(--bkx-e);
}

.bkx__go {
  background: var(--red, #c8102e);
  border: 1px solid var(--red, #c8102e);
  color: #fff;
}
.bkx__go:hover:not(:disabled) { background: #a50d26; border-color: #a50d26; transform: translateY(-1px); }
.bkx__go:active:not(:disabled) { transform: scale(.98); transition-duration: 100ms; }
.bkx__go:disabled { opacity: .55; cursor: progress; transform: none; }

.bkx__alt {
  background: transparent;
  border: 1px solid rgba(10, 10, 10, .46);
  color: var(--ink-true, #0a0a0a);
}
.bkx__alt:hover { background: var(--ink-true, #0a0a0a); border-color: var(--ink-true, #0a0a0a); color: var(--bone, #faf8f3); }

/* ── FOCUS, AND WHY IT IS WRITTEN THIS WAY ────────────────────────────────
   sell.css ships a site-wide ring at `.sell a:focus-visible` / `.sell
   button:focus-visible`, which is (0,2,1) — HIGHER than any `.bkx__go:focus-
   visible` this file can write, so a plain component rule loses and the ring
   colour would depend on which stylesheet loaded last. Two problems with
   inheriting it here:

     · its colour is --s-red-lt #a4132c, calibrated for a bone ground. It is
       7.3:1 on the panel, which is fine — but 2.6:1 on the band's ink, which
       is a focus indicator you cannot see. sell.css has a dark-ground
       override list for exactly this and `.bkx` is not on it, because that
       file is not this task's to edit.
     · a page that does not load sell.css gets no ring standard at all.

   `section.bkx …` is (0,2,2) and wins outright, in either load order and
   with sell.css absent. Bone ring on the ink band, red ring on the bone
   panel, both measured: #f5f1e8 on ink 17.6:1, #c8102e on bone 5.5:1, and
   1.4.11 asks for 3:1.                                                   */

section.bkx a:focus-visible,
section.bkx button:focus-visible,
section.bkx summary:focus-visible,
section.bkx input:focus-visible {
  outline: 2px solid #f5f1e8;
  outline-offset: 3px;
}
section.bkx .bkx__panel a:focus-visible,
section.bkx .bkx__panel button:focus-visible,
section.bkx .bkx__panel summary:focus-visible,
section.bkx .bkx__panel input:focus-visible {
  outline-color: var(--red, #c8102e);
}
section.bkx .bkx__panel input:focus-visible { outline-offset: 1px; }

/* ── THE FALLBACK LINE ────────────────────────────────────────────────────
   ALWAYS VISIBLE, ALWAYS. It is the one sentence that is true no matter what
   failed — no script, no availability, no calendar, no network. `data-bk-
   fallback` on the section carries the same sentence as plain text for the
   script to splice into its error copy; if that attribute is missing the
   script reads this element instead, so there is one source of truth either
   way and neither can be the wrong one for the page it is on.
   (The bug this replaces: booking.js hard-coded "Send the form below instead"
   into six error strings — false on every page that has no form below it.) */

.bkx__fallback {
  font-size: .8rem;
  line-height: 1.6;
  color: rgba(10, 10, 10, .70);
  margin: .95rem 0 0;
}
.bkx__fallback a {
  color: #7a0819;
  text-decoration: underline;
  text-underline-offset: .15em;
}
.bkx__fallback a:focus-visible { outline: 2px solid var(--red, #c8102e); outline-offset: 2px; }

.bkx__turnstile:not(:empty) { margin-top: .95rem; }

/* ── CONFIRMED ────────────────────────────────────────────────────────────
   Ships `hidden`. This is the one element in the band that is ALLOWED to be
   absent, because until a booking succeeds there is nothing true to put in
   it — and it is additive, so its absence removes nothing. */

.bkx__done[hidden] { display: none; }
.bkx__done {
  margin-top: 1rem;
  padding: .95rem 1.05rem;
  background: rgba(31, 122, 61, .07);
  border-left: 3px solid #1f7a3d;
}
.bkx__done > div { margin: 0; font-size: .9rem; line-height: 1.6; color: rgba(10, 10, 10, .84); }
.bkx__done > div + div { margin-top: .4rem; }
.bkx__done strong { font-weight: 600; color: var(--ink-true, #0a0a0a); }

/* ── THE EMBLEM ───────────────────────────────────────────────────────────
   js/storm.js owns what is drawn inside; storm-eye.css owns the host's
   position, containment and inertness. This file owns only where it is
   thrown from. If storm.js 404s the div is empty and the band is unchanged —
   nothing here is load-bearing for a single word on the page. */

.bkx__eye {
  /* Toward the panel, so the throw reads as coming from behind it. */
  transform-origin: 72% 46%;
  will-change: transform, opacity;
}
@media (max-width: 899px) {
  .bkx__eye { transform-origin: 50% 30%; }
}

/* ══ THE ENTRANCE ═════════════════════════════════════════════════════════
   ONE class. Every beat below is an animation-delay off it. Read the header
   before changing any of this: the keyframes are `from`-only on purpose and
   the `to` is the authored state above.                                     */

@keyframes bkx-lift {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
}

@keyframes bkx-throw {
  from { opacity: 0; transform: translate3d(0, 22px, -320px) scale(.86); }
}

@keyframes bkx-swell {
  from { opacity: 0; transform: scale(.42); }
}

.bkx.is-on .bkx__k,
.bkx.is-on .bkx__h,
.bkx.is-on .bkx__sub,
.bkx.is-on .bkx__offer,
.bkx.is-on .bkx__panel-head,
.bkx.is-on .bkx__slots,
.bkx.is-on .bkx__status,
.bkx.is-on .bkx__field,
.bkx.is-on .bkx__act,
.bkx.is-on .bkx__fallback {
  animation: bkx-lift .62s var(--bkx-e) both;
}

.bkx.is-on .bkx__k { animation-delay: .02s; }
.bkx.is-on .bkx__h { animation-delay: .09s; animation-duration: .72s; }
.bkx.is-on .bkx__sub { animation-delay: .18s; }
.bkx.is-on .bkx__offer { animation-delay: .28s; animation-duration: .68s; }

/* THE THROW. Later than the copy on the left, because the reader's eye starts
   at the headline and the panel should arrive into the space it moves to. */
.bkx.is-on .bkx__panel {
  animation: bkx-throw .9s var(--bkx-e) .10s both;
}

/* Inside the panel, ON THE TAIL OF THE THROW — not after it.
   --ease-out-expo covers ~90% of its distance in the first third of its
   duration, so the panel is visibly a solid card by ~250ms even though the
   animation runs to 1.0s. Starting the contents at .58s, as this first did,
   left a bone rectangle sitting empty on screen for a third of a second,
   which reads as a component that failed to load rather than one arriving.
   Measured in the browser, not reasoned about.

   They stay 2D. A translateZ inside a translateZ reads as the panel coming
   apart rather than as its contents settling into it. */
.bkx.is-on .bkx__panel-head { animation-delay: .30s; }
.bkx.is-on .bkx__slots { animation-delay: .36s; }
.bkx.is-on .bkx__status { animation-delay: .40s; }
.bkx.is-on .bkx__field:nth-child(1) { animation-delay: .44s; }
.bkx.is-on .bkx__field:nth-child(2) { animation-delay: .49s; }
.bkx.is-on .bkx__field:nth-child(3) { animation-delay: .54s; }
.bkx.is-on .bkx__act { animation-delay: .60s; }
.bkx.is-on .bkx__fallback { animation-delay: .66s; }

/* THE EMBLEM — LATE AND LONG. Starts .32s after the throw begins and is still
   growing 1.6s later, so the panel reads as ejected by something that has not
   finished expanding. See the header; this ordering is the effect. */
.bkx.is-on .bkx__eye {
  animation: bkx-swell 1.5s var(--bkx-e) .26s both;
}

/* ── NARROW ───────────────────────────────────────────────────────────── */

@media (max-width: 560px) {
  .bkx__panel { clip-path: none; border-radius: 1rem 0 0 0; }
  .bkx__grid { grid-template-columns: repeat(auto-fill, minmax(5rem, 1fr)); }

  /* SEVEN COLUMNS AT 390px, not a fallback to a list. He asked to see every
     day at once and that is most true on the smallest screen, where scrolling
     a fortnight of chips costs the most. At 390 the panel's inner width is
     about 318px, so a square is ~41px wide — tight, and still a 44px tap
     target because the height holds it. The count drops to a dot-sized glyph
     rather than being removed: the second channel must survive the breakpoint
     or the grid stops being readable without colour. */
  .bkxc__week { gap: .18rem; }
  .bkxc__weeks { gap: .18rem; }
  .bkxc__day { padding: .25rem .05rem; }
  .bkxc__num { font-size: .78rem; }
  .bkxc__mark { font-size: .5rem; }
  .bkxc__dow { font-size: .5rem; letter-spacing: .06em; }
  .bkxc__unfold { padding-left: .5rem; }
  /* On a 390px screen the sticky block is competing with the field it is
     meant to be helping somebody fill in, so the reassurance line goes and the
     two facts that matter stay. */
  .bkx__held { padding: .7rem .75rem .65rem; }
  .bkx__held-note { display: none; }
  .bkx__held-when { font-size: .95rem; }
  .bkx__act { align-items: stretch; }
  .bkx__go, .bkx__alt { flex: 1 1 100%; justify-content: center; }
}

/* ── REDUCED MOTION ───────────────────────────────────────────────────────
   Two rules, because the finished state is the authored state. Dropping the
   animations leaves the complete band — headline, offer, panel, fields,
   fallback — not a broken one. js/booking.js also never adds `is-on` under
   reduced motion, so this is the second of two independent guarantees.  */

@media (prefers-reduced-motion: reduce) {
  .bkx, .bkx * { animation: none !important; }
  .bkx__slot, .bkx__field input, .bkx__go, .bkx__alt { transition: none !important; }
  .bkx__go:hover, .bkx__go:active { transform: none !important; }

  /* THE UNFOLD OPENS INSTANTLY AND COMPLETELY. Belt and braces: js/booking.js
     already skips the two-frame open under reduced motion, and this guarantees
     the finished state even if the media query and the script ever disagree.
     The information is identical; only the movement is gone. */
  .bkxc__clip, .bkxc__unfold, .bkxc__day, .bkxc__navbtn { transition: none !important; }
  .bkxc__unfoldrow .bkxc__clip { grid-template-rows: 1fr; }
  .bkxc__unfoldrow .bkxc__unfold { opacity: 1; }
}

/* ── ANCHOR OFFSET ────────────────────────────────────────────────────
   sell.css:281 gives scroll-margin-top to .sell-sec[id], .sell-hero[id]
   and .sell-band[id] — but this band replaced .sell-band, so a page that
   carries .bkx instead lost the rule silently. Every external #book deep
   link was landing under the sticky header on every page with the band,
   which is the one link most likely to arrive from an email or an ad.
   Same calc as sell.css, reading site.css's own --header-h. */
.bkx[id] {
  scroll-margin-top: calc(var(--header-h, 72px) + 24px);
}


/* THE PRIMARY BUTTON NEEDS ITS OWN RING, AND IT MUST NOT BE RED.
   .bkx__go is filled --red and lives inside .bkx__panel, which paints a --red
   ring — red on red measures 1.00:1, i.e. no focus indicator on the one control
   that submits a booking. Bone fails for the mirror reason: `outline-offset`
   draws the ring in the gap over the bone panel, so a bone ring disappears into
   the panel instead of into the button.
   Ink is the only value that clears BOTH grounds: 3.36:1 on the red fill,
   near-maximum on the bone behind the gap. WCAG 1.4.11 asks for 3:1.
   Specificity is (0,3,1) so it beats the .bkx__panel rule above in any order. */
section.bkx .bkx__panel .bkx__go:focus-visible,
section.bkx .bkx__go:focus-visible {
  outline: 3px solid var(--ink, #0a0a0a);
  outline-offset: 3px;
}


/* ══ DENSITY ON A FINE POINTER ════════════════════════════════════════════
   Ethan: "booking forms all to large its like youre treating the reader as a
   todler". Measured at 1440 the band was 1080px — 46% of /contact — driven by a
   44px floor on six calendar rows and every slot chip.

   44px is WCAG 2.5.5 (AAA) and is the right floor FOR A FINGER, so it is kept
   wherever the pointer may be coarse. `pointer: fine` means a mouse or trackpad,
   where 2.5.8 (AA) asks 24px, so 32px is still comfortably compliant and takes
   roughly a quarter off the grid's height. The media query is the honest way to
   say "this is a mouse", rather than a width breakpoint that guesses.        */
@media (pointer: fine) and (min-width: 48rem) {
  .bkxc__day { min-height: 32px; }
  .bkx__slot { min-height: 32px; padding-block: .3rem; }
  /* the month arrows, compact only where the browser reports a mouse */
  .bkxc__navbtn { min-width: 2.25rem; min-height: 2.25rem; width: 2.25rem; height: 2.25rem; }
  .bkx__field input, .bkx__topic select { min-height: 36px; padding-block: .45rem; }
  .bkx { --bkx-pad: clamp(1rem, 2.4vw, 1.6rem); }
  .bkx__panel { padding: clamp(1rem, 1.9vw, 1.35rem); }
}
