/* site/app/screens/book.css — the Open play and Courts tabs.
 * Depends on tokens.css, ui.css, shell.css. Reuses .card .list .row .btn .tag .price
 * .avatar .empty .sheet-panel; everything here is the furniture those do not cover.
 *
 * ⚠ NO RAW HEX IN THIS FILE. A colour written here does not follow the palette into dark
 *   mode, and this screen is used courtside at night more than it is used in daylight.
 *
 * Two calendars live here and they are deliberately close relatives:
 *   COURTS   a month of DAYS, where the tint marks what is still OPEN and a dashed band marks
 *            a week not on sale yet.
 *   PLAY     a month of WEEKS in the News tab's language, one week highlighted.
 * Courts also carries the opening schedule above its months and the court map below its hours.
 * They share the weekday header and the cell metrics so the app has one calendar, drawn
 * twice, rather than two calendars that happen to sit in the same product.
 */

/* ═══ 1 · SHELL ════════════════════════════════════════════════════════════ */

/* ⚠ THE SCREEN IS ITS OWN SIZE CONTAINER, SO THE LAYOUT MEASURES THE FRAME AND NOT THE
   BROWSER WINDOW. The app is drawn inside a phone-width frame; a `@media (min-width: 520px)`
   in here asks the WINDOW how wide it is, so on a tablet the hour grid went to four columns
   inside a 390px frame and every cell came out too narrow to read. A container query asks
   the thing the grid is actually drawn in. Named, so it cannot be captured by a container
   some other file adds later. */
/* ⚠⚠ THE CONTAINER IS THE SCROLLER, NOT THE PANE, AND THE REASON IS `position: fixed`. In
   Chromium before 127 (and the Android emulator's Chrome 124 is exactly that), `container-type`
   still carried layout containment — which makes the element the containing block for its
   FIXED descendants. The commit dock and the sheets are fixed children of the pane, and inside a
   contained pane they anchored to the pane's box instead of the viewport: on a phone that box
   runs 40–56px under the browser's bar, and the scrim landed under the tab bar. Measured on
   the emulator before this moved: scrim [0,12,412,842] against a viewport 786 tall. Nothing
   fixed lives inside the scroller, so the query container can be there without a cost. */
.bk > .pane-scroll{ container: bk-screen / inline-size; }

.bk .bk-body{ padding-top: 2px; }

/* Every inline SVG sits in one of these. Grid rather than inline-block so the glyph is
   optically centred against the text beside it instead of riding the baseline. */
.bk .bk-glyph, .bk .bk-paid-i{ display: grid; place-items: center; }

/* The mode swap. .enter (tokens.css §8) supplies the entrance; this only owns the box. */
.bk .bk-mode{ display: block; }

/* ⚠ LOADING HAS NO WORDS, AND NOTHING AT ALL FOR ITS FIRST 600 ms (PLAN.md §5). Most reads answer
   inside that, and a ring that flashes for a frame is the screen flickering. After it, one small
   ring — the opacity step is its own animation, delayed, so no script has to time it. Under
   reduced motion the ring still appears and does not turn. */
.bk .bk-wait{ display: grid; place-items: center; padding: 56px 0; }
.bk .bk-wait-ring{
  width: 22px; height: 22px; border-radius: 50%; box-sizing: border-box;
  border: 2px solid var(--rule); border-top-color: var(--ink-secondary);
  opacity: 0;
  animation: bk-wait-in 1ms linear 600ms forwards, bk-wait-turn 900ms linear infinite;
}
@keyframes bk-wait-in { to { opacity: 1; } }
@keyframes bk-wait-turn { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce){
  .bk .bk-wait-ring{ animation: bk-wait-in 1ms linear 600ms forwards; }
}

/* ⚠⚠ `.sec-note` MOVED TO shell.css AND IS NO LONGER SCOPED TO `.bk`. It lived here from the
   day it was written, and event.js has been rendering the same class name in five section
   headers ever since — every one of them completely unstyled: 16px inherited body type where
   12px uppercase tertiary was intended. Exactly the shape of the `.btn-navy` bug found one
   rule below this one, and found the same way: by asking which screens render a class and
   whether the selector that styles it can reach them. `.sec-note` is a part of `.sec-head`,
   and `.sec`/`.sec-head` are already global. */


.bk .bk-foot{
  margin: 2px var(--gutter) 0; padding-bottom: var(--s-4);
  font: 400 12.5px/1.5 var(--font-ui); color: var(--ink-tertiary);
}

/* ⚠⚠ `.btn-navy` MOVED TO ui.css AND IS NO LONGER SCOPED TO `.bk`. It lived here, under this
   screen, from the day it was written — and the day event.js reused the class name for its
   "New match" and "Scan a player" buttons, those two got NO STYLING AT ALL. On Chrome that is
   the UA's grey button and it passes for a quiet variant; on Safari it is a grey button with
   BLUE TEXT, which is the only reason anybody noticed. A screen-scoped rule and an
   app-generic class name is a trap: the name says "use me anywhere" and the selector says
   otherwise, silently. */

.bk .btn.is-off{
  background: var(--bg-sunken); color: var(--ink-tertiary); cursor: default;
}

/* ═══ 2 · THE SHARED CALENDAR FURNITURE ════════════════════════════════════
 * ⚠ THE WEEKDAY HEADER MUST SHARE THE GRID OF THE THING BELOW IT, TO THE PIXEL. Both are
 *   `repeat(7, 1fr)` with the same side padding; letting either one drift by a few pixels
 *   puts every date one column off its letter, which reads as a broken calendar long before
 *   anyone works out why.
 */
.bk .bk-cal-head{
  display: grid; grid-template-columns: repeat(7, 1fr);
  margin: 0 var(--gutter) 6px;
}
.bk .bk-cal-dow{
  text-align: center;
  font: 600 9px/1 var(--font-ui); letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-tertiary);
}

/* ═══ 2a · COURTS — THE OPENING SCHEDULE ═══════════════════════════════════
 * One bar per week from this one, captioned by run: "On sale", "Next", "Later". Then the two
 * facts a player plans around — the last day on sale, and the week that opens next with the
 * Sunday 4 PM it opens at.
 * ⚠ THE BARS WEAR THE MONTH GRID'S MARKS FOR THE SAME STATES, so a week reads the same on the
 *   strip and in the calendar: a week not on sale is the dashed sunken band, the week that opens
 *   next has the solid --attn ring. On sale is the one state the grid has no band for (its days
 *   carry their own), so here it is simply a filled bar — the window, drawn as a length.
 * ⚠ NO TRANSITION ON ANY OF IT. It is painted once per visit and changes only when a week goes on
 *   sale; a strip that faded in would be DESIGN.md §7a's flurry of arrivals nobody asked for.
 */
.bk .bk-rel-card{ padding: 14px 12px 12px; margin-bottom: 18px; }
.bk .bk-rel-strip{
  display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr));
  column-gap: 3px; row-gap: 6px; padding: 0 2px;
}
/* The run captions. ⚠ clip, never an ellipsis: "Next" spans one week, about 30px at 320, and
   fits; if a caption ever did not, "NEX…" would be worse than the caption simply stopping. */
.bk .bk-rel-run{
  grid-row: 1; min-width: 0; overflow: hidden; white-space: nowrap;
  font: 700 8.5px/1 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.bk .bk-rel-run.is-next{ color: var(--attn); }
.bk .bk-rel-wk{ grid-row: 2; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.bk .bk-rel-bar{ display: block; height: 8px; border-radius: 4px; box-sizing: border-box; }
.bk .bk-rel-wk.is-sale .bk-rel-bar{ background: var(--ink-secondary); }
.bk .bk-rel-wk.is-next .bk-rel-bar{
  background: var(--bg-sunken); box-shadow: inset 0 0 0 1.5px var(--attn);
}
.bk .bk-rel-wk.is-later .bk-rel-bar{
  background: var(--bg-sunken);
  border: 1px dashed color-mix(in srgb, var(--ink-tertiary) 55%, transparent);
}
/* The week's Sunday, mm/dd. ⚠ 9.5px AND TABULAR: eight of these share 264px at 320, and "09/13"
   at this size is about 26px of a 30px column. */
.bk .bk-rel-d{
  font: 500 9.5px/1 var(--font-ui); letter-spacing: 0; white-space: nowrap;
  color: var(--ink-tertiary); font-variant-numeric: tabular-nums;
}
.bk .bk-rel-wk.is-now .bk-rel-d{ color: var(--ink); font-weight: 700; }
.bk .bk-rel-wk.is-next .bk-rel-d{ color: var(--attn); font-weight: 700; }

.bk .bk-rel-facts{ margin: 14px 0 0; display: flex; flex-direction: column; gap: 10px; }
.bk .bk-rel-fact{
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 12px;
}
.bk .bk-rel-k, .bk .bk-rel-v{ margin: 0; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.bk .bk-rel-v{ align-items: flex-end; text-align: right; }
.bk .bk-rel-k-t{ font: 500 13px/1.25 var(--font-ui); color: var(--ink-secondary); }
.bk .bk-rel-v-t{
  font: 600 14px/1.25 var(--font-ui); letter-spacing: -.006em; color: var(--ink);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* ⚠ --attn, THE STATUS INK, NOT ORANGE: this is the one fact on the card that pulls the eye,
   and it is not a thing to press (tokens.css: 5.62 on --bg-sunken, 6.80 on --bg-raised).
   ⚠ AND IT IS THE WEEK THAT OPENS NEXT — the same week, in the same ink, as the strip's "Next"
   bar above it and the ringed week in the month grid (book.js releaseSection says why it was
   ever anything else). */
.bk .bk-rel-fact.is-next .bk-rel-v-t{ color: var(--attn); }
.bk .bk-rel-sub{
  font: 400 11.5px/1.25 var(--font-ui); color: var(--ink-tertiary);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* A week span, and the line under it, may wrap — but only after a dash or between its parts,
   never inside a date (book.js relFact). */
.bk .bk-rel-v-t.bk-rel-span, .bk .bk-rel-sub.bk-rel-span{ white-space: normal; }
.bk .bk-rel-span > span{ white-space: nowrap; }

/* ═══ 3 · COURTS — THE MONTH ═══════════════════════════════════════════════ */

/* The months offered — two, or three toward a month's end (book.js courtMonths()). Each carries
   its own remaining stock as a word, which is the fastest answer on the screen: a player who
   reads "Fully booked" here never opens it, and one who reads "Opens Sun 10/04" knows when to. */
.bk .bk-months{ display: flex; gap: 8px; margin: 0 var(--gutter) 18px; }
.bk .bk-month{
  flex: 1; min-width: 0; cursor: pointer; text-align: left;
  display: flex; flex-direction: column; gap: 3px;
  padding: 11px 13px; border-radius: var(--r-lg);
  background: var(--bg-raised); border: 1px solid var(--rule); color: var(--ink);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
[data-ready] .bk .bk-month{
  transition: background-color var(--t-press) var(--ease-out),
              border-color var(--t-press) var(--ease-out),
              color var(--t-press) var(--ease-out);
}
.bk .bk-month-n{
  font-family: var(--font-display); font-weight: 600; font-size: 17px; line-height: 1.1;
  letter-spacing: -.014em;
}
.bk .bk-month-s{
  font: 500 11.5px/1.2 var(--font-ui); color: var(--ink-tertiary);
  font-variant-numeric: tabular-nums;
}
/* ⚠ --ink / --bg-raised, NOT --navy / --cream. A selected chip painted raw navy is the same
   colour as the page at night, so the month you had chosen read as a hole and the one you
   had not looked chosen. --ink flips with the theme; the brand constants do not. */
.bk .bk-month[aria-selected="true"]{
  background: var(--ink); border-color: var(--ink); color: var(--bg-raised);
}
.bk .bk-month[aria-selected="true"] .bk-month-s{ color: var(--bg-raised); opacity: .78; }

/* ⚠ THREE CHIPS DO NOT FIT THREE FULL NAMES ON A PHONE, AND THE ADMIN DESK HIT THE SAME WALL.
   At 320 a third of the rail is 64px of text; "September" in 17px Fraunces is about 85, and
   "26 hours · 5 days" about 95. So with three chips the name shortens to "Sep" below 440px of
   frame and the count stacks one part per line — bookgrid.js's monthShort() and its stacked
   sub-line, for the same measurements. Both spellings are in the DOM and aria-hidden; the chip's
   label is what a screen reader hears. */
.bk .bk-month-short{ display: none; }
.bk .bk-months.is-three{ gap: 6px; }
.bk .bk-months.is-three .bk-month{ padding: 10px 11px; }
.bk .bk-months.is-three .bk-month-s{ display: flex; flex-direction: column; gap: 2px; }
.bk .bk-months.is-three .bk-month-dot{ display: none; }
@container bk-screen (max-width: 440px){
  .bk .bk-months.is-three .bk-month-full{ display: none; }
  .bk .bk-months.is-three .bk-month-short{ display: inline; }
}

/* ── The day grid ─────────────────────────────────────────────────────────
 * ⚠ THE OPEN DAYS ARE THE TINTED ONES. Every calendar reflexively marks what is unavailable;
 *   in a month that sells out in a day that paints twenty-six cells and leaves the four that
 *   matter as plain holes. --attn-bg / --attn-ink is the measured pair tokens.css ships for
 *   "a status that has to pull the eye and is not a button" (6.76 light · 8.80 dark), which
 *   is exactly what an opening is. Orange is not available for this: it means "press to pay".
 */
.bk .bk-cells{
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
  margin: 0 var(--gutter) 12px;
}
.bk .bk-cell{
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-height: 52px; padding: 6px 2px;
  border-radius: var(--r-md); border: 1px solid transparent;
  background: transparent; color: var(--ink-tertiary);
}
.bk .bk-cell-n{
  font-family: var(--font-display); font-weight: 600; font-size: 16px; line-height: 1;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
/* ⚠ EVERY CELL CARRIES A WORD, INCLUDING THE DEAD ONES. A hatched square with no word is a
   rendering fault to anyone who has not been told what the hatch means, and the legend
   underneath is read once and then never again. 8.5px is small; it is also uppercase,
   tracked, and only ever four or five characters. */
.bk .bk-cell-w{
  font: 600 8.5px/1 var(--font-ui); letter-spacing: .05em; text-transform: uppercase;
  white-space: nowrap;
}
/* The leading blanks before the 1st. Not a state, so no word and nothing to read. */
.bk .bk-cell.is-blank{ min-height: 52px; }

.bk button.bk-cell{ cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
[data-ready] .bk button.bk-cell{
  transition: background-color var(--t-press) var(--ease-out),
              border-color var(--t-press) var(--ease-out),
              color var(--t-press) var(--ease-out);
}

.bk .bk-cell.is-open{ background: var(--attn-bg); color: var(--attn-ink); }
.bk .bk-cell.is-open .bk-cell-n{ color: var(--attn-ink); }

/* ⚠ THE SELECTED DAY IS A FILLED SURFACE THAT FLIPS WITH THE THEME. --selected-bg is cream
   on a navy page and navy on a cream one; a raw brand colour here would vanish into one of
   the two themes, which is the bug tokens.css documents at length. */
.bk .bk-cell.is-on{
  background: var(--selected-bg); color: var(--selected-ink); border-color: var(--selected-edge);
}
.bk .bk-cell.is-on .bk-cell-n{ color: var(--selected-ink); }

/* ⚠ A FULL DAY IS GONE, NOT DISABLED — and in the DOM it is not a button at all. A greyed
   button still reads as a button, so people tap it, get nothing, and tap it harder. */
.bk .bk-cell.is-full{
  background:
    repeating-linear-gradient(135deg, transparent 0 5px, var(--steel-20) 5px 6px);
  color: var(--ink-tertiary);
}
.bk .bk-cell.is-full .bk-cell-n{
  text-decoration: line-through; text-decoration-thickness: 1.5px;
  color: var(--ink-tertiary);
}
/* Open play holds the courts that day. A different answer from "Full", so a different mark —
   but the SAME strike, because both mean there is no private court to be had. Without the
   strike the tint read as availability, and a tinted day you cannot book is the one mistake
   a month view must not make. */
.bk .bk-cell.is-past{ opacity: .45; }

/* ── A week not on sale yet ───────────────────────────────────────────────
 * ⚠ DASHED, NOT HATCHED, AND ON THE SUNKEN GROUND: the admin desk's mark for a locked day,
 *   word for word (admin:site/admin/screens/book.css — "A LOCKED DAY IS DASHED, NOT HATCHED.
 *   Full means sold; locked means not on sale yet"). The hatch above is sold; this is "not
 *   yet", and a player planning for Sunday 4 PM must not read it as gone. The dash is tertiary
 *   ink at 55%, not --rule, which the admin lane measured as nearly invisible on --bg-sunken
 *   at night.
 * ⚠ ONE BAND PER WEEK, SPANNING THE ROW (book.js lockedWeek). The week is the unit the venue
 *   releases, and this grid is Sunday-first, so a row IS a release week.
 * ⚠ AN OUTLINE, NOT A BORDER. An outline takes no layout, so the band's own seven columns,
 *   on the same `repeat(7, 1fr)` and the same 4px gap as the grid it sits in, put every date
 *   exactly under its weekday letter — the rule §2 of this file exists for. A 1px border would
 *   move every column in by a pixel and the last one by two.
 * ⚠ THE WEEK THAT OPENS NEXT HAS A SOLID --attn RING AND A DOT — the admin calendar's "Opens
 *   next" key and its seam's dot. A shape as well as a colour, so the coming Sunday reads in
 *   greyscale and in sun. */
.bk .bk-lockweek{
  grid-column: 1 / -1;
  display: flex; flex-direction: column; justify-content: center; gap: 6px;
  min-height: 52px; padding: 7px 0 7px;
  border-radius: var(--r-md);
  background: var(--bg-sunken);
  outline: 1px dashed color-mix(in srgb, var(--ink-tertiary) 55%, transparent);
  outline-offset: -1px;
}
.bk .bk-lockweek.is-next{ outline: 1.5px solid var(--attn); outline-offset: -1.5px; }
.bk .bk-lockweek-days{ display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.bk .bk-lockweek-n{
  text-align: center; color: var(--ink-secondary);
  font-family: var(--font-display); font-weight: 600; font-size: 16px; line-height: 1;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
/* 9.5px, a step up from a cell's 8.5px word, because this line has the whole week to itself
   and it carries the one fact on the band that matters: the day and the hour. */
.bk .bk-lockweek-w{
  display: flex; align-items: center; justify-content: center; gap: 5px;
  font: 600 9.5px/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-tertiary); white-space: nowrap; font-variant-numeric: tabular-nums;
}
.bk .bk-lockweek.is-next .bk-lockweek-w{ color: var(--attn); }
.bk .bk-lockweek-dot{
  flex: none; width: 6px; height: 6px; border-radius: var(--r-full); background: var(--attn);
}
/* A single locked day — only drawn if a rule ever put the edge mid-week (book.js says why it
   cannot today). The same mark as the band, so the fallback does not invent a fourth look. */
.bk .bk-cell.is-locked{
  background: var(--bg-sunken);
  outline: 1px dashed color-mix(in srgb, var(--ink-tertiary) 55%, transparent);
  outline-offset: -1px;
}
.bk .bk-cell.is-locked .bk-cell-n{ color: var(--ink-secondary); }

.bk .bk-legend{ display: flex; flex-wrap: wrap; gap: 12px; padding: 0 var(--gutter); }
.bk .bk-key{
  display: inline-flex; align-items: center; gap: 6px;
  font: 500 11px/1 var(--font-ui); color: var(--ink-tertiary);
}
.bk .bk-key-sw{
  width: 13px; height: 13px; border-radius: 4px; flex: none;
  border: 1px solid transparent;
}
.bk .bk-key-sw.is-open{ background: var(--attn-bg); border-color: var(--attn-ink); }
.bk .bk-key-sw.is-full{
  background: repeating-linear-gradient(135deg, transparent 0 3px, var(--steel-20) 3px 4px);
  border-color: var(--rule);
}
.bk .bk-key-sw.is-locked{
  background: var(--bg-sunken);
  border: 1px dashed color-mix(in srgb, var(--ink-tertiary) 55%, transparent);
}

/* ── A real account: the page, blocked off ─────────────────────────────────
 * SPEC (Syd, 2026-10-05): "it should be just the booking page but with everything blocked off".
 * book.js BLOCKED. Every day to come and every slot still to come is ONE mark — the shell's skeleton
 * ink (shell.css `.skel`), flat, no edge — because "not bookable here yet" is one fact, and the
 * open tint, the hatch and the dash each say something about the desk's stock that a player cannot
 * act on from this tab. ⚠ NOT FADED: a blocked cell is still a cell of the grid, and at the past
 * days' .45 the month read as missing rather than closed. The words carry it: "Soon" under every
 * date, the legend's one key, every cell's label, and the "Coming soon" notice over the page. */
.bk .bk-cell.is-blocked{
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--ink-tertiary);
}
.bk .bk-cell.is-blocked .bk-cell-n{ color: var(--ink-secondary); }
/* The day the timetable below is drawn for: a ring, not the picked fill — nothing here was picked. A
   Full day can be that day too (views/courts.js dayMark keeps it). */
.bk .bk-cell.is-blocked.is-shown, .bk .bk-cell.is-full.is-shown{ border-color: var(--ink-tertiary); }
.bk .bk-key-sw.is-blocked{ background: color-mix(in srgb, var(--ink) 6%, transparent); }
/* The months as the chips' cards (book.js monthLabels), with nothing to press: the card the calendar
   under them is drawn for in the ink, as a chosen chip is, the rest raised. ⚠ NO PRESS AND NO POINTER:
   they are spans, and `cursor: pointer` would promise the tap a chip takes. */
.bk .bk-month.is-static{ cursor: default; }
.bk .bk-month.is-static.is-on{ background: var(--ink); border-color: var(--ink); color: var(--bg-raised); }
.bk .bk-month.is-static.is-on .bk-month-s{ color: var(--bg-raised); opacity: .78; }
/* ⚠ THE OPENING SCHEDULE IS THE DESK'S, AND STEPS BACK. "On sale" in navy and "Opens next" in the
   attention ink, at full strength under "Coming soon", read as a sale this tab is running. Its
   facts stay legible; its colour goes, and it sits a step behind the notice. book.js also notes it
   "At the front desk" and drops the countdown, because greying changes the colour, not the words
   (review, 2026-10-05). ⚠ ON WHAT IS INSIDE THE CARD, NOT THE CARD: a filter on the card greys its
   own ground too, and at night the raised navy turned charcoal. */
.bk .bk-blocked .bk-rel-strip, .bk .bk-blocked .bk-rel-facts{ filter: grayscale(1); opacity: .62; }
/* The map's courts wear the blocked mark too, rather than the bright floor of a map with no hour. */
.bk .bk-blocked .fm-c.is-idle .fm-floor{ fill: color-mix(in srgb, var(--ink) 6%, transparent); }
/* The page's shapes, when a blocked tab has no calendar to draw (book.js blockedShapes): the schedule
   card as grey lines over a bar where the strip goes, and the month's cells as blocked days with no
   date. The shell's `.skel`; nothing moves. */
.bk .bk-skel-head{ height: 9px; margin: 2px 0 1px; }
.bk .bk-skel-strip{ height: 30px; border-radius: 8px; }
.bk .bk-skel-fact{ height: 10px; margin-top: 14px; }

/* ═══ 4 · COURTS — THE DAY'S HOURS ═════════════════════════════════════════ */

.bk .bk-dayhead{
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 8px; padding: 0 var(--gutter) 10px;
}
.bk .bk-dayhead-t{
  font: 600 15px/1.2 var(--font-ui); letter-spacing: -.008em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.bk .bk-daytags{ display: flex; flex-wrap: wrap; gap: 5px; }
.bk .bk-daytags .tag{ font-size: 10px; padding: 4px 7px; letter-spacing: .05em; }

/* ══ THE TIMETABLE ══════════════════════════════════════════════════════════
   Syd, 2026-09-03: "i want you to do the samething as this basically like you see a time
   table and the ability to book mulitple courts at the same time."

   ⚠ ONE CSS GRID, NOT A TABLE AND NOT EIGHT COLUMNS OF DIVS. `grid-auto-flow: row` with a
     fixed template means one pass of appends in book.js lays the whole thing out and nothing
     needs to know its own row or column — which is what keeps the JS a loop over hours inside
     a loop over courts rather than a layout engine.
   ⚠⚠ NOTHING SCROLLS SIDEWAYS. Syd, 2026-09-03: "for the courts i dont want to scroll, i want
     it all to be visible and selectable without needing to scroll at all horizontally." The
     first build made the grid `width: max-content` with 40px columns and let it scroll — 46 +
     8×40 + gaps is 402px against 343px of card at 375px wide. That is the wrong trade for this
     screen: the whole reason a timetable beats a list is that availability reads as a SHAPE in
     one glance, and half a shape behind a scroll edge is a list again, one that also hides
     which courts exist.
     So the columns are `1fr` and the grid fits BY CONSTRUCTION at any width. Every pixel spent
     elsewhere is a pixel off every court, which is why the hour column and the gaps are as small
     as they can be and still read. The side padding is the page gutter (tokens.css), not less:
     until 2026-10-07 it was 10px, the one block in the app nearer the edge than 16, and Syd's
     "very close to the edge of the screen" named this table first. Nine courts:

         width     hours  gap  side (--gutter)   court cell
         320px       40     2        16             25.6px   (26.9 at the old 10)
         375px       40     2        20             30.8px   (33.0)
         402px       40     2        20             33.8px   (36.0)

   ⚠ THE CELLS ARE UNDER THE 44px FLOOR — the one place in the app that is true, and it is a
     deliberate trade rather than an oversight. The alternative is fewer courts on screen,
     which is the entire utility being asked for. It is mitigated the way a calendar mitigates
     it: cells are contiguous, so a miss lands on a neighbouring hour or court rather than on
     nothing, the row is 38px tall so the vertical axis stays comfortable, and the summary bar
     states what was picked before any money moves.
   ⚠ THE HOUR COLUMN IS NO LONGER STICKY, because there is nothing left to scroll under it.
     It was `position: sticky; left: 0` with an opaque background; left in place that is a
     paint layer and a stacking context for no reason at all. */
.bk .bk-tt-scroll{ padding: 0 var(--gutter) 4px; }
.bk .bk-tt{
  display: grid; gap: 2px; width: 100%;
  grid-template-columns: 40px repeat(var(--tt-cols), minmax(0, 1fr));
}

/* ⚠ nowrap, AND THE TRACK IS 40px BECAUSE OF IT. At 34px "10 AM" and "12 PM" broke across two
   lines — and a wrapped label makes that ROW taller than its neighbours, so the grid came out
   with three deep rows in the middle of it and stopped reading as a grid at all. The label is
   the one thing here that must never wrap; six pixels of gutter is the cheapest possible fix
   and it comes out of slack rather than out of the cells. */
.bk .bk-tt-rh{
  display: flex; align-items: center; padding-right: 5px; white-space: nowrap;
  font: 600 10.5px/1 var(--font-ui); letter-spacing: -.01em;
  color: var(--ink-secondary); font-variant-numeric: tabular-nums;
}
.bk .bk-tt-ch{
  display: grid; place-items: center; gap: 1px; padding-bottom: 4px;
}
/* The axis label spans every court column. `grid-column: 2 / -1` rather than a hard number,
   so adding a ninth court is a data change and not a CSS one. */
.bk .bk-tt-axis{
  grid-column: 2 / -1; padding-bottom: 2px;
  font: 700 8.5px/1 var(--font-ui); letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-tertiary); text-align: center;
}
.bk .bk-tt-ch{ min-width: 0; }
.bk .bk-tt-ch-n{
  font-family: var(--font-display); font-weight: 600; font-size: 14px; line-height: 1;
  color: var(--ink);
}
/* ⚠ 8px, AND IT IS THE FIRST THING THAT WOULD GO. Courts are not all one price — 500 indoor,
   400 covered — so the column head is where that difference is visible without tapping. At a
   31px column "₱500" measures about 20px, which fits; if a ninth court is ever added and it
   stops fitting, drop this line rather than shrinking the cells. */
.bk .bk-tt-ch-p{
  font: 500 8px/1 var(--font-ui); letter-spacing: 0; color: var(--ink-tertiary);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.bk .bk-tt-c{
  display: grid; place-items: center; height: 38px; border-radius: var(--r-sm);
  border: 1px solid transparent; min-width: 0;
}
/* ⚠ THE OPEN CELL CARRIES THE TINT, NOT THE TAKEN ONE — the same inversion the month grid
   uses, and for the same reason: on a busy evening most cells are taken, and colouring those
   paints the whole grid and leaves the few that matter as holes. */
.bk .bk-tt-c.is-free{
  background: var(--attn-bg); border-color: var(--attn-ink); color: var(--attn-ink);
  cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
[data-ready] .bk .bk-tt-c.is-free{
  transition: background-color var(--t-press) var(--ease-out),
              border-color var(--t-press) var(--ease-out);
}
.bk .bk-tt-c.is-picked{
  background: var(--selected-bg); border-color: var(--selected-edge); color: var(--selected-ink);
}
.bk .bk-tt-c-i{ display: grid; place-items: center; }
/* Taken: hatched and inert, exactly like a full day in the month grid. Not a disabled button —
   a greyed-out button still reads as a button, so people tap it and tap it harder. */
.bk .bk-tt-c.is-taken{
  background: var(--bg-sunken);
  background-image: repeating-linear-gradient(45deg,
    transparent 0 3px, color-mix(in srgb, var(--ink) 9%, transparent) 3px 4px);
}
/* Open play is a different fact from "somebody booked it", and on this tab it is the one that
   is a reason to come rather than to look elsewhere. Chartreuse is ENERGY, never success. */
.bk .bk-tt-c.is-play{
  background: color-mix(in srgb, var(--live) 22%, transparent);
  border-color: color-mix(in srgb, var(--live) 55%, transparent);
}
.bk .bk-tt-c-w{
  font: 700 8px/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-secondary);
}
/* Too late: today, an hour under way or starting inside the lead time, on a court that would
   have been free (book.js hoursSection). ⚠ AN EMPTY WELL, NOT THE HATCH — the sunken ground with a
   hairline edge and no pattern. The hatch says somebody else holds the court, and nobody does;
   its row's bookings and open play keep their own marks beside it.
   ⚠ NOT FADED. At the month grid's .45 a sunken cell on cream is all but the page itself, and it
   read as a cell missing from the grid (checked at 320 in light, 2026-09-19). The edge is what
   makes it a cell, and the same edge is its swatch in the key. */
.bk .bk-tt-c.is-late{ background: var(--bg-sunken); border-color: var(--rule); }
/* ⚠ REAL MODE: AN OPEN CELL THAT IS NOT A BUTTON (book.js hoursSection). It keeps the open tint —
   the court is free, and that is what the grid exists to say — and drops everything that says
   "press me": no pointer, no hover edge. A phone cannot pay yet, so there is nothing to press. */
.bk .bk-tt-c.is-free.is-static{ cursor: default; }
/* Not on sale yet: free, in an hour the server has not released or that is past its horizon. The
   month grid's own mark for a week not on sale — the dash — on an empty cell, so it reads as
   "later", never as taken (the hatch) and never as too late (the sunken well). */
.bk .bk-tt-c.is-locked{ background: transparent; border: 1px dashed var(--ink-tertiary); }
/* Closed: a court the server says is not open, or shut by staff for the hour. The sunken floor
   with a bar across it — a shape, so it is not carried by grey alone (DESIGN.md §2), and not the
   hatch, which says somebody holds the court. */
.bk .bk-tt-c.is-closed{ background: var(--bg-sunken); }
.bk .bk-tt-c.is-closed::after{
  content: ""; width: 9px; height: 1.5px; border-radius: 1px; background: var(--ink-tertiary);
}
/* A closed court's column head says "Closed" where the price was, in the same 8px. */
.bk .bk-tt-ch.is-closed .bk-tt-ch-n{ color: var(--ink-tertiary); }
/* ⚠ THE HOURS THAT HAVE ENDED ARE ONE ROW, NOT TWELVE (book.js says why). The whole grid's width,
   the hour column included, because it is not an hour: it is the part of the day that is gone,
   in words — the one cell here with room for them, so it needs no key. Sunken like a past day;
   not faded, because its words are the whole of it. */
.bk .bk-tt-over{
  grid-column: 1 / -1; display: grid; place-items: center; height: 26px;
  border-radius: var(--r-sm); background: var(--bg-sunken);
}
.bk .bk-tt-over-w{
  font: 700 8.5px/1 var(--font-ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-tertiary); white-space: nowrap; font-variant-numeric: tabular-nums;
}
/* Blocked (book.js BLOCKED): every slot still to come on a real account, one flat mark — the month
   grid's blocked day, at the timetable's size. */
.bk .bk-tt-c.is-blocked{ background: color-mix(in srgb, var(--ink) 6%, transparent); }
/* In the legend the cell is a swatch, so it loses its height and its hit affordances. */
.bk .bk-key-sw.bk-tt-c{ width: 15px; height: 15px; border-radius: 5px; cursor: default; }

/* ══ THE COURT MAP ═════════════════════════════════════════════════════════
   Syd, 2026-09-18: "i want a MAP of the facility under the private booking tab, like pretty
   much like at the buttom under the booking table". The drawing is floormap.js; these are its
   marks, and they are the timetable's marks above, state for state:
     open       --attn-bg with an --attn-ink edge        (.bk-tt-c.is-free)
     picked     --selected-bg, edge in --selected-ink, a tick   (.is-picked)
     open play  --live at 22%, edge at 55%, a ball       (.is-play)
     booked     a hatch on --bg-sunken                   (.is-taken)
     closed     --bg-sunken, and a bar                   (.is-closed)
   so the legend at the foot of the timetable is this map's key too, and the two can never
   disagree about what a colour means.
   ⚠ NOTHING ON IT IS PRESSABLE, SO NOTHING ON IT LOOKS IT: no cursor, no hover, no press scale,
     no focus ring. The courts are pictures and the timetable above is where one is picked
     (book.js mapSection says why). A hover edge on a court that does nothing when clicked is
     the page promising a control that is not there. The two hour arrows are the map's only
     controls, and they are the calendar's .bk-navbtn: 34px drawn, 44px tapped.
   ⚠ EVERY STROKE IS non-scaling-stroke. The plan's viewBox is in metres and each court's in
     half-feet, so a stroke width in either unit would come out a different number of pixels on
     every phone; this states them in CSS pixels instead.
   ⚠ user-select: none AND caret-color: transparent ON THE WHOLE MAP — the admin plan's
     "EVERY PATH TO THAT SLAB, CLOSED AT ONCE": a court number is an SVG <text>, and a selection
     box on one is measured in the drawing's own units, a pale slab across three courts. Nothing
     on a map of nine numbers is worth copying. */
.bk .bk-map-bar{
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 0 var(--gutter) 10px;
}
.bk .bk-map-when{
  flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.bk .bk-map-t{
  font-family: var(--font-display); font-weight: 600; font-size: 18px; line-height: 1.1;
  letter-spacing: -.014em; color: var(--ink); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.bk .bk-map-s{
  font: 500 11.5px/1.2 var(--font-ui); color: var(--ink-tertiary); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* The first and last hour: the arrow stays where it is and goes quiet, so the hour between them
   never moves sideways under a thumb that is stepping. */
.bk .bk-navbtn:disabled{ opacity: .35; cursor: default; }

/* ⚠ ROOM UNDER THE PLAN FOR THE COMMIT PILL, AND MORE WHILE ITS NOTE IS UP. The map is the last
   thing on the tab and the pill floats over the foot of it, so at the end of the scroll the
   bottom row of courts has to finish above the pill. The tight case is a day with no session on
   now: with one, the live pill makes --fnav-clear 50px deeper (shell.css) and none of this bites.
   ⚠ THIS SAID "28px … the pane's own bottom padding clears the pill alone (26px spare at 375)",
     and that was measured with a session live. Measured 2026-09-19 with none, at the end of the
     scroll: 28px left the pill 4–6px clear at 320, 375 and 402 — and the "2 hours maximum per
     court" note, which stacks above the pill (25px, and the dock's 6px gap), sat 25–27px over the
     bottom row, covering Court 5 outright. So 36px for the pill, and 31px more exactly while the
     dock is carrying the note: 12–14px clear in both cases at all three widths. The dock is a
     child of the pane (book.js buildCourt), so the pane can ask. */
.bk .bk-map-plan{ padding: 0 var(--gutter) 36px; }
.bk:has(> .bk-commit .commitdock-note) .bk-map-plan{ padding-bottom: 67px; }
.bk .fm{
  display: block; width: 100%; height: auto; overflow: visible;
  -webkit-user-select: none; user-select: none; caret-color: transparent;
}
.bk .fm ::selection{ background: transparent; }

.bk .fm-floor{ stroke-width: 1px; vector-effect: non-scaling-stroke; }
.bk .fm-zone{ fill: color-mix(in srgb, currentColor 9%, transparent); }
.bk .fm-kitchen, .bk .fm-centre, .bk .fm-net{ vector-effect: non-scaling-stroke; }
.bk .fm-kitchen, .bk .fm-centre{ stroke: currentColor; stroke-width: .75px; opacity: .5; }
.bk .fm-net{ stroke: currentColor; stroke-width: 1.2px; opacity: .8; }
.bk .fm-num{
  fill: currentColor; pointer-events: none;
  font-family: var(--font-ui); font-weight: 700; font-variant-numeric: tabular-nums;
}
.bk .fm-tick{
  fill: none; stroke: currentColor; stroke-width: 2px; vector-effect: non-scaling-stroke;
  stroke-linecap: round; stroke-linejoin: round;
}
.bk .fm-ball{ fill: var(--live); stroke: var(--ink); stroke-width: 1px; vector-effect: non-scaling-stroke; }
.bk .fm-hatch-bg{ fill: var(--bg-sunken); }
.bk .fm-hatch-ln{ stroke: color-mix(in srgb, var(--ink) 16%, transparent); stroke-width: 1.2; }

.bk .fm-c.is-free{ color: var(--attn-ink); }
.bk .fm-c.is-free .fm-floor{ fill: var(--attn-bg); stroke: var(--attn-ink); stroke-width: 1.25px; }
/* ⚠ THE PICKED EDGE IS --selected-ink, NOT --selected-edge. The admin plan's fix for "the
   highlight is EXTREMELY BROKEN": --selected-edge is the same colour as --selected-bg in both
   themes, so two picked neighbours met as one slab. The opposite ink keeps each one a court. */
.bk .fm-c.is-picked{ color: var(--selected-ink); }
.bk .fm-c.is-picked .fm-floor{ fill: var(--selected-bg); stroke: var(--selected-ink); stroke-width: 1.25px; }
.bk .fm-c.is-play{ color: var(--ink-secondary); }
.bk .fm-c.is-play .fm-floor{
  fill: color-mix(in srgb, var(--live) 22%, transparent);
  stroke: color-mix(in srgb, var(--live) 55%, transparent);
}
.bk .fm-c.is-booked{ color: var(--ink-tertiary); }
.bk .fm-c.is-booked .fm-floor{ fill: url(#fm-hatch); stroke: color-mix(in srgb, var(--ink) 16%, transparent); }
.bk .fm-c.is-idle{ color: var(--ink-secondary); }
.bk .fm-c.is-idle .fm-floor{ fill: var(--bg-raised); stroke: var(--rule); }
.bk .fm-c.is-closed{ color: var(--ink-tertiary); }
.bk .fm-c.is-closed .fm-floor{ fill: var(--bg-sunken); stroke: var(--rule); }
.bk .fm-shut{ stroke: var(--ink-tertiary); stroke-width: 1.5px; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.bk .fm-c.is-absent{ color: var(--ink-tertiary); opacity: .4; }
.bk .fm-c.is-absent .fm-floor{ fill: none; stroke: var(--rule); stroke-dasharray: 3 3; }

/* ── Court filter. A custom dropdown, never a native <select> (Syd's standing rule). ── */
.bk .bk-dd{ position: relative; margin: 0 var(--gutter) 16px; }
.bk .bk-dd-trigger{
  width: 100%; display: flex; align-items: center; gap: 12px; cursor: pointer;
  padding: 13px 16px; border: 0; border-radius: var(--r-lg);
  background: var(--bg-raised); box-shadow: var(--shadow-1); color: var(--ink);
  text-align: left;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.bk .bk-dd-lab{ flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.bk .bk-dd-name{ font: 600 16px/1.2 var(--font-ui); letter-spacing: -.008em; color: var(--ink); }
.bk .bk-dd-note{ font: 400 12.5px/1.2 var(--font-ui); color: var(--ink-tertiary); }
.bk .bk-dd-price{ font: 500 13px/1 var(--font-ui); color: var(--ink-secondary); white-space: nowrap; }
.bk .bk-dd-chev{ display: grid; place-items: center; color: var(--ink-tertiary); }
[data-ready] .bk .bk-dd-chev{ transition: transform var(--t-menu) var(--ease-out); }
.bk .bk-dd-trigger[aria-expanded="true"] .bk-dd-chev{ transform: rotate(90deg); }

.bk .bk-dd-menu{
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0; right: 0;
  background: var(--bg-raised); border-radius: var(--r-lg);
  box-shadow: var(--shadow-3); overflow: hidden;
}
.bk .bk-dd-opt{
  width: 100%; display: flex; align-items: center; gap: 12px; cursor: pointer;
  padding: 12px 16px; border: 0; border-bottom: 1px solid var(--rule);
  background: transparent; color: var(--ink); text-align: left;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.bk .bk-dd-opt:last-child{ border-bottom: 0; }
.bk .bk-dd-opt-t{ flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.bk .bk-dd-opt-p{ font: 500 13px/1 var(--font-ui); color: var(--ink-tertiary); }
/* ⚠ THE CHOSEN COURT IS A STATE, NOT THE ACTION. Both of these were --action once, which put
   a second orange thing on a screen whose one orange thing is the pay button — and orange
   text at 13–16px on --bg-raised is the lowest-contrast text on the menu, so the option you
   had selected was the hardest one to read. */
.bk .bk-dd-opt-c{ display: grid; place-items: center; color: var(--ink); }
.bk .bk-dd-opt[aria-selected="true"] .bk-dd-name{ color: var(--ink); font-weight: 700; }

/* Exit is faster than enter, and along the path it entered. */
.bk .bk-dd-menu.is-out{
  opacity: 0; transform: scale(.97) translateY(-3px);
  transition: opacity var(--t-exit) var(--ease-out), transform var(--t-exit) var(--ease-out);
}
@media (hover: hover) and (pointer: fine){
  .bk .bk-dd-opt:hover{ background: var(--bg-sunken); }
  .bk button.bk-month:hover, .bk button.bk-cell:hover,
  .bk button.bk-tt-c.is-free:hover{ border-color: var(--ink); }
}

/* ══ THE COMMIT PILL ═══════════════════════════════════════════════════════
 * ⚠⚠ ALMOST ALL OF THIS MOVED TO shell.css AS `.commitdock` / `.commitpill`. Syd asked for the
 *   same pill on the Pro shop and the Cafe, and three copies of one object is how the Cafe's
 *   version had already drifted into floating above the live pill instead of beside it. What
 *   is left here is only what is true of THIS screen: nothing, at the moment, beyond the note
 *   above the pill.
 * ⚠ THE POSITION, THE FILL, THE ROOM LEFT FOR THE CODE BUTTON AND THE LIVE PILL, AND THE
 *   SHRINK-WITH-THE-NAV ARE ALL IN shell.css. Do not re-add them here for one screen.
 */
.bk .commitdock-note {
  margin: 0; padding: 4px 11px; border-radius: var(--r-full);
  background: var(--chrome);
  backdrop-filter: var(--chrome-blur); -webkit-backdrop-filter: var(--chrome-blur);
  border: 1px solid var(--chrome-edge);
  font: 500 11.5px/1.3 var(--font-ui); color: var(--ink-secondary);
}

/* ═══ 5 · OPEN PLAY — THE MONTH OF WEEKS ═══════════════════════════════════
 * Built to read as a sibling of news.css: the same sunken tray, the same sprung selection
 * block behind the content, Fraunces on the date, and one bar per session under it. The one
 * difference is the axis — News slides a thumb sideways across seven days, this slides a
 * band down the month, because a week is what gets selected here.
 */
.bk .bk-pcal-head{
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 0 var(--gutter) 10px;
}
.bk .bk-pcal-t{
  font-family: var(--font-display); font-weight: 600; font-size: 20px; line-height: 1.1;
  letter-spacing: -.016em; color: var(--ink); margin: 0; text-transform: none;
}
/* ⚠ 34px PAINTED, 44px TAPPED — the .tap-min pattern from tokens.css, written out here
   because a month arrow drawn at 44px is a slab beside a 20px month name. */
.bk .bk-navbtn{
  position: relative;
  display: inline-grid; place-items: center; flex: none;
  width: 34px; height: 34px; padding: 0; cursor: pointer;
  border: 0; border-radius: var(--r-full);
  background: var(--bg-sunken); color: var(--ink);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.bk .bk-navbtn::after{
  content: ""; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: var(--tap-min); height: var(--tap-min);
}

.bk .bk-weeks{
  position: relative;              /* the offsetParent the band is measured against */
  margin: 0 var(--gutter) 4px; padding: 3px;
  background: var(--bg-sunken); border-radius: 14px;
}

/* The sliding selection block. Positioned by JS (a spring), so no transition here — a CSS
   transition on top of a spring fights it and produces two motions at once.
   ⚠ THE SOFT FILL NEEDS THE EDGE. tokens.css measures --selected-soft-bg at 1.27 against the
     page on its own, which is not a selection anyone can see on a sunlit court. */
.bk .bk-band{
  position: absolute; left: 3px; right: 3px; top: 0; height: 0;
  background: var(--selected-soft-bg); border-radius: 11px;
  box-shadow: inset 0 0 0 1.5px var(--selected-edge);
  z-index: 0; will-change: transform;
}

.bk .bk-week{
  position: relative; z-index: 1;
  display: grid; grid-template-columns: repeat(7, 1fr); width: 100%;
  border: 0; background: transparent; cursor: pointer;
  padding: 5px 0; border-radius: 11px;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.bk .bk-wcell{
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 3px 0; color: var(--ink-secondary);
}
/* Days belonging to the neighbouring month. Dimmed, never removed: a week row with three
   blank cells at a month boundary reads as a missing week, and the sessions on those days
   are in the list underneath. */
.bk .bk-wcell.is-out{ opacity: .34; }
.bk .bk-wnum{
  font-family: var(--font-display); font-weight: 600; font-size: 15px; line-height: 1;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
/* Today keeps its own mark inside a selected week, so "this week" and "today" stay two
   different facts. A ring, not a fill — a fill would compete with the week band. */
.bk .bk-wcell.is-today .bk-wnum{
  color: var(--ink); font-weight: 700;
  box-shadow: 0 2px 0 -1px currentColor;
}
.bk .bk-week.is-on .bk-wcell{ color: var(--selected-soft-ink); }
.bk .bk-week.is-on .bk-wnum{ font-weight: 700; }
[data-ready] .bk .bk-wcell{ transition: color var(--t-press) var(--ease-out); }

.bk .bk-bars{ display: flex; gap: 2px; height: 3px; align-items: center; }
.bk .bk-bars i{
  width: 4px; height: 3px; border-radius: 2px;
  background: currentColor; opacity: .5;
}
/* Duos bars are the solid ones. Decoration only — the format is a word on every card below,
   because a player who reads the mark and not the word turns up to a duos night alone. */
.bk .bk-bars i.is-duos{ opacity: .95; }
/* A day with nothing on gets a rule, not an absent mark — an empty slot reads as a bug. */
.bk .bk-bars .bk-bar-none{ width: 8px; height: 1.5px; opacity: .28; }
/* A day outside the window the sessions were read for (book.js dayRead): the same rule, fainter — a
   place kept, not a claim that nothing was on. */
.bk .bk-bars .bk-bar-none.is-unread{ opacity: .14; }

/* ═══ 6 · SESSION CARDS ════════════════════════════════════════════════════ */

.bk .bk-ses{ padding: 0; overflow: hidden; }

/* ⚠⚠ A STACK, NOT A ROW — AND THE MEASUREMENTS ARE WHY.
 *
 * This was `display:flex` with the when-block beside the kind badge, and raising the day
 * and date to display weight inside that narrow column is what broke the card. Measured on
 * the deployed build, `.bk-day`'s box was 109px at 320px wide and 164px at 375px, because
 * the badge ("FIXED PAIRS" + glyph) takes ~130px off the row before anything else is drawn.
 * What has to fit in it:
 *
 *     "Wednesday 09/02/2026"   169px      → wrapped at BOTH 320 and 375
 *     "6:00 – 9:00 PM"         155px      → overflowed the 109px box by 46px at 320
 *     "11:00 AM – 2:00 PM"     201px      → over by 89px at 320, 34px at 375, 22px at 390
 *
 * so a card read as four stacked lines with the time broken across two of them. The card's
 * height varied 108–159px with nothing but the day name and the meridiem changing.
 *
 * ⚠ THE BADGE CANNOT SHARE A LINE WITH THE DATE AT ANY PHONE WIDTH. It is not a tuning
 *   problem: even shortening its label to "SOLO" leaves 161px beside it at 320px, and the
 *   day and date need 235px. So the badge takes its own line and the two display lines below
 *   it each get the card's full width — 252 / 307 / 322px at 320 / 375 / 390.
 *
 * ⚠ REJECTED, WITH NUMBERS: putting the date and the time together on one line under a
 *   day+badge row. It is one line shorter and it overflows — 274px into 252px at 320, and
 *   319px into 307px at 375 on a session that crosses noon. Measured, not guessed.
 *
 * ⚠ SINCE SUPERSEDED IN PART, AND THE NUMBERS ABOVE ARE WHY IT WORKED. `.bk-day` is gone:
 *   the when-block is now two columns (day over date, time right) and the time is printed
 *   without ":00", so the pair that needed 235px + 201px on separate lines needs about 110px
 *   + 125px on one. What survives unchanged is the finding this block exists to record — the
 *   BADGE still cannot share a line with any of it, at any phone width.
 *
 * The row-1 / row-2 split is the sticker's `.s-top` in ../Community Supply — the marker on
 * the left, the state chip flush right, one baseline — and the reason the time now spans the
 * whole card is the same bug that file records on `.tick-head`: a shared column track sizes
 * every row, including the rows with nothing beside them.
 */
.bk .bk-ses-top{
  display: block;
  width: 100%; text-align: left; cursor: pointer;
  border: 0; background: transparent; padding: 14px 18px 10px;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}

/* The eyebrow: what kind of session it is, and what state it is in. Two ends of one line,
   so the badge sits above the price and the state tag above the spots-left tag in the foot
   — the card gets two aligned edges instead of four ragged ones. */
.bk .bk-ses-head{
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 10px;
}

/* ── THE WHEN BLOCK · two columns, split by what kind of fact each one is ─────────────────
   LEFT stacks the calendar answer — which day, then which date. RIGHT carries the clock
   answer, big, on the card's right edge directly under the status chip.

   ⚠ THIS IS A REVISION OF A REVISION, AND BOTH MOVES WERE SYD'S. First the date was pushed
     right to sit under the status chip, because the chip left a notch of dead card beneath it.
     Then: *"switch the position of the time and date, date under the day and time right
     aligned but big."* Syd is right, and the two arrangements are not equivalent: the card's
     right edge is the second thing the eye lands on, so it should hold the fact the card is
     scanned FOR — and between a date and a time, that is the time. "Tuesday / 6 – 9 PM" is
     the card read at a glance. The year is confirmation, not the question.

   ⚠ align-items: BASELINE, and the left column is itself a flex column — so the baseline the
     time shares is the DAY's, and the date hangs below that shared line instead of dragging it
     down. `center` would float the time against the middle of a two-line column and break the
     pairing that makes the row read as one fact. */
.bk .bk-when{
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; min-width: 0;
}
.bk .bk-when-l{ display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* ⚠⚠ TWO FACTS, TWO TREATMENTS — AND THE FIRST ATTEMPT AT THIS WAS THE BUG.
   Syd asked for the day and the date to be emphasised. They were both raised to the same 20px
   display face, differing only in ink colour, and Syd came straight back: *"the date and day
   being same font same line same look is super confusing, i want them to be both distinct and
   both visible."* Syd is right, and the reason is worth keeping written down: TWO ADJACENT
   THINGS IN IDENTICAL TYPE READ AS ONE THING. Emphasis is not the same as equality.

   So the pair splits across the two faces the app already owns, which is the distinction that
   needs no extra ink, weight or rule:
     · the DAY is a word — Fraunces, the display face, full size. It is what people scan for.
     · the DATE is a phrase — Inter, the UI face, smaller and quieter, on the line below.
   Both stay legible; neither is mistaken for the other. Stacking them rather than setting them
   side by side settles that argument for good: they are no longer competing for one line. */
.bk .bk-dow{
  font-family: var(--font-display); font-weight: 600; font-size: 20px; line-height: 1.15;
  letter-spacing: -.018em; color: var(--ink);
}
/* Spelled out, not 09/10/2026 — Syd, 2026-09-03. A heading is read, not scanned in a column,
   and this one now has a line of its own to be read on. `nowrap` because "September 10, 2026"
   broken after "September" reads as two facts. */
.bk .bk-date{
  white-space: nowrap;
  font: 500 14.5px/1.2 var(--font-ui); letter-spacing: .005em;
  color: var(--ink-secondary);
}

/* ⚠⚠ THE TIME ONLY FITS BECAUSE ":00" IS GONE — see rangeHeadline() in book.js.
   The previous layout gave this line the card's full width and the comment here recorded why:
   "11:00 AM – 2:00 PM" is the widest a session time gets and it needs 200px, against 252px of
   card at 320px wide. Sharing that line with a 110px "Wednesday" was therefore impossible at
   any size worth calling big — the time would have had to drop to about 14px, which is the
   opposite of what was asked for. Dropping the empty minutes takes the same string to 131px
   and the pair fits, at a LARGER size than the 22px this line used to be given alone.

   ⚠ 22px AND NOT 24, WHICH IS WHERE THIS FIRST SHIPPED. Measured on the deployed build, the
     worst pair — "Wednesday" beside a session that crosses noon — came to 266px against the
     252px the card has at 320px wide. It looked right on every fixture only because none of
     the fourteen falls on a Wednesday, which is the kind of luck that ships a defect. At
     20/22 with a 10px gap the same pair measures 249px. The numbers, per face:

         face      day 20px    "11 AM – 2 PM" 22px    + 10px gap    fits 252px
         Fraunces    107               131               248            yes
         Georgia*    107               133               250            yes
                                                          *at size-adjust 92% — see fonts.css
   ⚠ `nowrap` stays, and it is still the point. Left to wrap, the old string broke after
     "6:00 – 9:00" and stranded "PM" on a line by itself. */
.bk .bk-time{
  flex: none; text-align: right; white-space: nowrap;
  font-family: var(--font-display); font-weight: 600; font-size: 22px; line-height: 1.15;
  letter-spacing: -.02em; color: var(--ink); font-variant-numeric: tabular-nums;
}

/* ⚠ THE KIND BADGE. Solos and duos never mix, so this is the first thing the eye should
   land on after the time. Two distinct treatments, not two colours of the same shape —
   outlined for solos, filled for duos — plus the word and a glyph, so it survives being
   read in sunlight, by a colourblind player, and by a checker. */
/* ⚠ SMALLER, AND IT NO LONGER STRANDS THE CORNER. Syd, 2026-09-03: "now that the solo entry
   tag is at the top, its too much empty space on the top right of the card (WASTEFUL), make
   it fit, make the tag smaller."
   The tag was 12px/700 with .08em tracking and 12px of side padding — at "FIXED PAIRS" that
   is a 132px pill, so on a 343px card it claimed the entire right-hand column and left a
   band of dead space beneath itself that nothing could use. Dropped to 10.5px with tighter
   tracking and padding it lands near 96px, and .bk-ses-top now lets the text column reclaim
   the width instead of reserving it. The badge is still the loudest single element on the
   card — it is the one thing that makes a wasted trip — it just no longer costs a corner. */
.bk .bk-kind{
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 9px 5px 7px; border-radius: var(--r-full);
  font: 700 10.5px/1 var(--font-ui); letter-spacing: .05em; text-transform: uppercase;
  align-self: flex-start;
}
.bk .bk-kind-i{ display: grid; place-items: center; }
/* The eyebrow of a week with no open play (book.js blankSessionCard): the week's words where a night's
   card has its kind badge. Plain ink at the badge's weight, one line, cut with an ellipsis rather
   than pushing the tag beside it off its edge. */
.bk .bk-ses-name{
  flex: 1; min-width: 0;
  font: 600 13px/1.25 var(--font-ui); letter-spacing: -.004em; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bk .bk-kind.is-solos{
  background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink);
}
/* The same card with no night in it: grey lines the height of the words they stand for, nothing to
   press (placeholder.js skel). */
.bk .bk-ses-blank .bk-ses-top{ cursor: default; }
.bk .bk-ses-blank .bk-when{ align-items: flex-start; }
.bk .bk-skel-dow{ height: 16px; margin: 3px 0 5px; border-radius: 6px; }
.bk .bk-skel-date{ height: 11px; }
.bk .bk-skel-time{ height: 18px; margin-top: 3px; border-radius: 6px; }
.bk .bk-ses-blank .bk-meta{ padding-top: 4px; }
.bk .bk-skel-price{ height: 18px; border-radius: 6px; }
.bk .bk-skel-tag{ height: 20px; border-radius: var(--r-full); }
.bk .bk-kind.is-duos{ background: var(--ink); color: var(--bg-raised); }

.bk .bk-meta{
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 0 18px; font: 400 13px/1.4 var(--font-ui); color: var(--ink-tertiary);
}
.bk .bk-dot{ opacity: .5; }
/* A real night's name, over its facts (book.js realSessionCard): the whole first line, a step darker. */
.bk .bk-meta-name{
  flex-basis: 100%; min-width: 0; color: var(--ink-secondary); font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The partner requirement, stated on the card — not discovered at checkout. A LABEL now,
   not the sentence that started RULE ZERO. */
.bk .bk-req{
  display: flex; align-items: center; gap: 8px;
  margin: 10px 18px 0; padding: 8px 11px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  color: var(--warn);
  font: 600 12.5px/1.35 var(--font-ui);
}
.bk .bk-req-i{ display: grid; place-items: center; flex: none; }

.bk .bk-ses-foot{
  display: flex; align-items: center; justify-content: space-between; gap: 8px 12px;
  padding: 12px 18px 16px;
  /* ⚠ THE ROW WRAPS AS A WHOLE OR THE TAG BREAKS IN HALF. "16 pairs left" beside a duos
     price ("₱250 each · ₱500 a pair") overflows a 390px card, and with nothing said here the
     TAG is what wrapped — two lines of half a pill, which reads as a rendering fault. Let the
     tag drop to its own line intact instead. */
  flex-wrap: wrap;
}
.bk .bk-ses-foot .tag{ white-space: nowrap; }
.bk .bk-price{ display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px 7px; min-width: 0; }
.bk .bk-price .price{
  font-family: var(--font-display); font-weight: 600; font-size: 22px; line-height: 1;
  letter-spacing: -.02em; color: var(--ink);
}
.bk .bk-per{ font: 400 12px/1 var(--font-ui); color: var(--ink-tertiary); }

/* ⚠ THE QUIETEST OF THE THREE STATES, AND IT IS THE ONE THAT APPEARS MOST.
   "Upcoming" is true of nearly every card in the list, so giving it the filled pill that
   "Past" wears would set the same word in the same badge down the whole screen and add no way
   to tell one card from another — a badge that never varies is decoration, and it would also
   make the two OPPOSITE states look identical, which is the confusion this file already
   records once under the day and the date.
   So the three are ranked by how far each departs from the ordinary case:
     · TODAY    chartreuse, filled, a pulsing dot — the one you might act on right now
     · PAST     a filled grey pill — over, and worth saying so plainly
     · UPCOMING a hairline outline in the secondary ink — present, legible, and silent
   Under `prefers-contrast: more` the outline becomes --ink along with everything else, so the
   quiet version does not become the invisible one. */
.bk .tag.bk-tag-soon{
  background: transparent; color: var(--ink-secondary);
  box-shadow: inset 0 0 0 1px var(--rule);
}
.bk .tag.bk-tag-full{
  background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger);
}
/* Scarcity and the payment window: weight and a word, never the action colour. --warn is a
   different hue from --action in both themes, and a tag is a different shape from a button,
   so neither can be mistaken for the thing you press. */
.bk .tag.bk-tag-low{ color: var(--ink); font-weight: 700; }
.bk .tag.bk-tag-hold{
  background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn);
}
.bk .bk-ses.is-full .bk-time, .bk .bk-ses.is-full .bk-price .price{ color: var(--ink-secondary); }

/* ── The expansion ─────────────────────────────────────────────────────────
 * ⚠ 0fr → 1fr, NOT height. `height: auto` cannot be transitioned and a max-height guess is
 *   wrong at some text size or in some language — this animates to the content's real
 *   height, whatever it turns out to be. Same trick, same reason, as .fnav-lab in shell.css;
 *   it is the one place this project animates something other than transform and opacity,
 *   and it is deliberate rather than an oversight.
 * ⚠ The inner element needs min-height:0 and overflow:hidden or the row refuses to collapse.
 */
.bk .bk-more{ display: grid; grid-template-rows: 0fr; opacity: 0; }
.bk .bk-more-in{ min-height: 0; overflow: hidden; padding: 0 18px; }
.bk .bk-ses.is-open .bk-more{ grid-template-rows: 1fr; opacity: 1; }
.bk .bk-ses.is-open .bk-more-in{ padding-bottom: 18px; }

[data-ready] .bk .bk-more{
  transition: grid-template-rows 240ms var(--ease-out), opacity 160ms var(--ease-out);
}

.bk .bk-facts{ display: flex; gap: 8px; margin-bottom: 10px; }
.bk .bk-fact{
  flex: 1; min-width: 0; padding: 9px 10px;
  background: var(--bg-sunken); border-radius: var(--r-md);
  display: flex; flex-direction: column; gap: 3px;
}
.bk .bk-fact-k{
  font: 600 10px/1 var(--font-ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.bk .bk-fact-v{ font: 500 13px/1.25 var(--font-ui); color: var(--ink); }

.bk .bk-fine{
  margin: 10px 0 0; text-align: center;
  font: 400 12px/1.45 var(--font-ui); color: var(--ink-tertiary);
}

/* ═══ 7 · THE SHEET ════════════════════════════════════════════════════════ */

.bk .bk-sheet{ display: flex; flex-direction: column; max-height: 82%; }
.bk .bk-sheet-body{
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding-bottom: 4px;
}
.bk .bk-sheet-h{
  font-family: var(--font-display); font-weight: 600; font-size: 24px; line-height: 1.15;
  letter-spacing: -.018em; margin: 8px 0 0; color: var(--ink);
}
.bk .bk-sheet-p{
  margin: 7px 0 16px; font: 400 14.5px/1.5 var(--font-ui); color: var(--ink-secondary);
}
.bk .bk-sheet-foot{ padding-top: 14px; }
/* ⚠ THE ONLY WAY OUT OF A FLOW WITH A FIVE-MINUTE CLOCK RUNNING, AND IT WAS 39px TALL.
   12px padding either side of a 15px line is 39 — under the 44px floor tokens.css calls "the
   floor, not a target". A missed tap on Cancel is a player jabbing at a sheet while the hold
   timer counts down, which is the worst place in the app to lose a press. */
.bk .bk-cancel{
  display: flex; align-items: center; justify-content: center;
  width: 100%; margin-top: 4px; padding: 12px; cursor: pointer;
  min-height: var(--tap-min);
  border: 0; background: transparent; color: var(--ink-secondary);
  font: 600 15px/1 var(--font-ui); letter-spacing: -.008em;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}

/* Banners. ⚠ Each carries a WORD as well as a colour — "Not reserved", "Not a ticket",
   "Duos · partner required" — because a coloured strip on its own tells a player nothing. */
.bk .bk-banner{
  display: flex; gap: 10px; align-items: flex-start;
  padding: 12px 13px; border-radius: var(--r-md); margin-bottom: 16px;
}
.bk .bk-banner-b{ display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.bk .bk-banner-w{
  font: 500 12.5px/1.4 var(--font-ui); color: var(--ink-secondary);
}
.bk .bk-banner-i{ flex: none; display: grid; place-items: center; margin-top: 1px; }
.bk .bk-banner.is-req{
  align-items: center;
  background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn);
}
.bk .bk-banner.is-req .bk-banner-w{ color: var(--warn); font-weight: 600; }
.bk .bk-banner.is-hold{ background: var(--bg-sunken); color: var(--ink-secondary); }
.bk .bk-banner.is-urgent{ background: color-mix(in srgb, var(--danger) 12%, transparent); }

.bk .bk-hold-state{ display: flex; align-items: center; gap: 9px; }
.bk .bk-hold-clock{
  font-family: var(--font-display); font-weight: 600; font-size: 19px; line-height: 1;
  letter-spacing: -.01em; color: var(--ink);
}
.bk .bk-banner.is-urgent .bk-hold-clock{ color: var(--danger); }

/* The order summary. Tabular figures so the total lines up under the lines above it. */
.bk .bk-order{
  background: var(--bg-sunken); border-radius: var(--r-md);
  padding: 4px 13px; margin: 16px 0;
}
.bk .bk-order-r{
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 10px 0; border-bottom: 1px solid var(--rule);
}
.bk .bk-order-r:last-child{ border-bottom: 0; }
.bk .bk-order-k{ font: 500 13.5px/1.35 var(--font-ui); color: var(--ink); }
.bk .bk-order-v{
  font: 400 13px/1.35 var(--font-ui); color: var(--ink-tertiary);
  text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.bk .bk-order-r.is-total .bk-order-k{ font-weight: 700; }
.bk .bk-order-r.is-total .bk-order-v{
  font-family: var(--font-display); font-weight: 600; font-size: 20px;
  letter-spacing: -.02em; color: var(--ink);
}

/* Partner rows. The tick lives in the row and only appears when it is chosen — a radio
   circle on every row is six empty circles asking to be counted. */
.bk .bk-group{ margin: 0 0 16px; }
.bk .bk-prow{ cursor: pointer; border: 0; width: 100%; text-align: left; background: transparent; }
/* ⚠ ui.css's .title / .meta ARE SPANS HERE, AND SPANS DO NOT STACK. Every other list in the
   app builds these rows out of divs, so the handle and the name ran together on one line —
   "@jmreyesJM Reyes · Played together 08/31/2026" — which is unreadable and looks like a
   missing separator rather than a missing line break. They stay spans because .row is a
   <button> and a <div> inside a button is invalid; the display fix goes here instead. */
.bk .bk-prow .title, .bk .bk-prow .meta{ display: block; }
/* Ink, not --action: the chosen partner is a state, and the sheet's one orange thing is the
   button that takes the money. Orange here also failed contrast — a 13.5px orange name on
   --bg-sunken was the quietest line in the sheet, on the row the player had just chosen. */
.bk .bk-prow-c{ display: grid; place-items: center; color: var(--ink); opacity: 0; }
.bk .bk-prow[aria-checked="true"]{ background: var(--bg-sunken); }
.bk .bk-prow[aria-checked="true"] .bk-prow-c{ opacity: 1; }
.bk .bk-prow[aria-checked="true"] .title{ color: var(--ink); font-weight: 700; }
[data-ready] .bk .bk-prow-c{ transition: opacity var(--t-press) var(--ease-out); }

.bk .bk-invite{ display: flex; gap: 8px; align-items: stretch; }
.bk .bk-field{
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 2px;
  padding: 0 12px; border-radius: var(--r-md);
  background: var(--bg-sunken); border: 1px solid var(--rule);
}
.bk .bk-at{ font: 600 15px/1 var(--font-ui); color: var(--ink-tertiary); }
.bk .bk-input{
  flex: 1; min-width: 0; border: 0; background: transparent; color: var(--ink);
  /* 16px: under that iOS Safari zooms the page on focus — see .shop-search-in in proshop.css. */
  font: 500 16px/1 var(--font-ui); letter-spacing: -.006em;
  padding: 13px 2px; outline: none;
}
.bk .bk-input::placeholder{ color: var(--ink-tertiary); }
.bk .bk-invite-go{ padding: 12px 18px; font-size: 15px; }

/* The paid state. The only celebratory moment on this screen, and it is a rare event —
   DESIGN.md §4.1 says delight is allowed here. It still enters from 0.96, never from 0. */
.bk .bk-paid{ text-align: center; padding-top: 10px; }
.bk .bk-paid-mark{
  width: 68px; height: 68px; margin: 6px auto 14px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok);
}
.bk .bk-paid .bk-sheet-h{ margin-top: 10px; }
.bk .bk-paid .bk-sheet-p{ max-width: 34ch; margin-left: auto; margin-right: auto; }

/* ═══ 8 · REDUCED MOTION ═══════════════════════════════════════════════════
 * Gentler, not absent. The expansion still opens and the dropdown still appears; what goes
 * is the movement — the row growing, the chevron rotating, the menu sliding out.
 * Named animations in this file: .bk-more (grid-template-rows), .bk-dd-chev (rotate),
 * .bk-dd-menu.is-out (scale + translate), .bk-month / .bk-cell / .bk-tt-c / .bk-wcell
 * (colour), .bk-prow-c (opacity), .bk-paid-mark (via .enter in tokens.css). The week band is a
 * JS spring — motion.js snaps it under reduced motion, so there is nothing to do here. The
 * opening schedule, the locked weeks and the court map do not animate at all; the map's two
 * hour arrows are the Play calendar's .bk-navbtn and press exactly as its month arrows do.
 */
@media (prefers-reduced-motion: reduce){
  [data-ready] .bk .bk-more{ transition-property: opacity; }
  .bk .bk-more{ grid-template-rows: 0fr; }
  .bk .bk-ses.is-open .bk-more{ grid-template-rows: 1fr; }
  [data-ready] .bk .bk-dd-chev{ transition: none; }
  .bk .bk-dd-trigger[aria-expanded="true"] .bk-dd-chev{ transform: none; }
  .bk .bk-dd-menu.is-out{ transform: none; transition-property: opacity; }
}

@media (prefers-contrast: more){
  .bk .bk-cell.is-open{ outline: 1px solid var(--attn-ink); outline-offset: -1px; }
  .bk .bk-cell.is-full{ background: none; outline: 1px dashed var(--ink); outline-offset: -1px; }
  .bk .bk-tt-c.is-free{ border-color: var(--ink); }
  .bk .bk-tt-c.is-taken{ background-image: none; background: var(--bg-sunken); border-color: var(--ink-tertiary); }
  /* Booked loses its hatch here and becomes a filled, edged well — so too late becomes an empty
     one, or the two would be the same square. */
  .bk .bk-tt-c.is-late{ background: transparent; border-color: var(--ink-tertiary); }
  .bk .bk-tt-c.is-closed{ border-color: var(--ink-tertiary); }
  .bk .bk-tt-c.is-blocked, .bk .bk-cell.is-blocked{ border-color: var(--ink-tertiary); }
  .bk .bk-kind.is-solos{ box-shadow: inset 0 0 0 2px var(--ink); }
  .bk .bk-band{ box-shadow: inset 0 0 0 2px var(--ink); }
  .bk .bk-lockweek, .bk .bk-cell.is-locked{ outline: 1px dashed var(--ink-secondary); }
  .bk .bk-lockweek.is-next{ outline: 2.5px solid var(--attn); outline-offset: -2.5px; }
  .bk .bk-rel-wk.is-later .bk-rel-bar{ border-color: var(--ink-secondary); }
  .bk .bk-rel-wk.is-next .bk-rel-bar{ box-shadow: inset 0 0 0 2.5px var(--attn); }
  .bk .fm-c.is-free .fm-floor{ stroke: var(--ink); }
  .bk .fm-hatch-ln{ stroke: var(--ink-tertiary); }
}

/* ── How it is paid ────────────────────────────────────────────────────────
   The method choice on the court checkout. ui.css owns `.segmented`; this is the three-up
   thumb width and the spacing inside a sheet.
   ⚠ (100% − 8px) / 3 is 2px of padding either side plus two 2px gaps. The same formula as the
     profile's appearance switch and the match filter — if a fourth method is ever added, this
     number changes with it or the thumb sits under the wrong word. */
.bk .bk-pay-h { margin-top: 18px; }
.bk .bk-pay-methods { margin: 10px 0 18px; }
.bk .bk-pay-methods .thumb { width: calc((100% - 8px) / 3); }

/* The code on the paid screen. ⚠ It is stated here as well as on the ticket because this is
   the one moment somebody might photograph the screen, and a receipt with no reference on it
   is a receipt nobody can quote at a desk. */
.bk .bk-paid-code {
  margin: 10px 0 0; font: 600 13px/1 var(--font-ui); letter-spacing: .12em;
  color: var(--ink-secondary);
}
