/* site/app/screens/event.css — the live session screen. Scoped under `.ev`.
 *
 * ⚠ EVERY RULE IS UNDER `.ev`, AND THAT LINE IS WHAT MAKES IT TRUE. Seven screens load seven
 *   stylesheets into one document and this file uses names that read generically — .ev-p,
 *   .ev-chip, .ev-score. Unscoped, any of them collides with another screen's idea of the
 *   same word, which is the bug proshop.css records against `.pcard` and cafe.js against
 *   `.step`.
 */

.ev { padding-bottom: 8px; }

/* ── The session's own header ────────────────────────────────────────────── */
.ev .ev-head { padding: 2px var(--gutter) 16px; }
.ev .ev-title {
  margin: 10px 0 0; font-family: var(--font-display); font-weight: 600;
  font-size: 27px; line-height: 1.1; letter-spacing: -.02em; color: var(--ink);
}
.ev .ev-sub {
  margin: 5px 0 0; font: 500 14px/1.4 var(--font-ui); color: var(--ink-secondary);
  font-variant-numeric: tabular-nums;
}
/* ══ THE PAGE'S VERTICAL RHYTHM ═════════════════════════════════════════════
   Syd, 2026-09-15, of the queue: "there are a lot of elements squeezed together."
   Measured at 375px before this block, reading down the queue: heading → caption 10px, caption →
   card 0px, card → list 12px, row → row 6px. The 0 is the one that made the whole section read as
   one dense block — an explanatory line touching the card it explains has no gap to say which of
   the two it belongs to, so the eye gets no grouping at all.

   ⚠ FOUR STEPS, NOT ONE VALUE EVERYWHERE. Proximity is what says what belongs together
   (DESIGN.md §2, and Apple's own grouping principle), so the scale has to be ordered: things
   inside a group sit closer than the gap around the group.
       6px   inside a card (kept)
      12px   heading → its body, and card → the list under it
      14px   a caption away from the thing below it
      28px   section → section
   ⚠ SCOPED TO `.ev`, NOT TO `.sec-head` ITSELF. That class is shell.css's and every screen in the
     app uses it; widening it globally would be nine screens' worth of change to fix one. */
.ev .ev-sec { margin-bottom: 28px; }
.ev .sec-head { padding-bottom: 12px; }
.ev .ev-fine {
  margin: 10px var(--gutter) 14px; font: 400 12.5px/1.45 var(--font-ui); color: var(--ink-tertiary);
}

/* ── The queue's column headings ─────────────────────────────────────────────
   ⚠⚠ THE INSETS ARE THE ROW'S OWN, NOT ROUND NUMBERS. A row is a bordered card inside .ev-q's gutter
     padding, so its first column starts 1px (border) + 6px (padding) in, and its last ends 8 + 1
     from the right. The header repeats those exactly, or the labels sit over the gaps between the
     columns they name. Same gap (7px) for the same reason.
   ⚠ STICKY AT THE TOP OF .pane-scroll. The list opens to forty-four rows; a header that scrolls
     away takes the labels with it and leaves forty rows of unlabelled numbers, which is the state
     it exists to end. The page's bar is outside the scroller, so top: 0 is the right offset.
   ⚠ IT PAINTS ITS OWN BACKGROUND. Stuck, it has rows passing underneath it, and a transparent
     header would have names sliding through the word "Player". */
.ev .ev-qh {
  position: sticky; top: 0; z-index: 3;
  display: flex; align-items: center; gap: 7px;
  margin: 0 var(--gutter); padding: 8px 9px 7px 7px;
  background: var(--bg);
  /* ⚠ EXACTLY THE METRICS THE PER-ROW CAPTIONS HAD. They moved here; they did not change size. At
     9.5px/.07em — which is what this was for one build — "RATING" measures 41px against the
     column's 34, so the heading was wider than the figures under it and dragged the whole
     right-hand block 5px out of line with them. Measured: header cell at 258.1, row cell at 263.4. */
  font: 700 8.5px/1 var(--font-ui); letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
/* 21px place + 7px gap + 30px head: "Place" is wider than the number column alone, and the disc
   beside it has no heading to collide with. */
.ev .ev-qh-place { flex: none; width: 58px; }
.ev .ev-qh-who { flex: 1 1 auto; min-width: 0; }
.ev .ev-qh-rt { flex: none; display: flex; gap: 6px; }
/* ⚠ `width`, NOT `min-width`, AND THAT IS THE POINT. These two have to equal the row's cells
   exactly; min-width lets a heading that measures wider than its column push the whole block left
   and silently un-align it from the numbers, which is what happened. */
.ev .ev-qh-c:first-child { width: 29px; }
.ev .ev-qh-c:last-child { width: 34px; }

/* ── A person, as a row ──────────────────────────────────────────────────── */
/* 8px, not 6: these rows are ~62px tall cards with their own borders, and at 6 the borders of
   two neighbours read as one thick rule rather than as two cards. */
.ev .ev-q { display: grid; gap: 8px; padding: 0 var(--gutter); }

/* ⚠⚠ THE REST STATE IS THE VISIBLE ONE. `.is-in` is the displaced state, added when the row is
   built and removed by a setTimeout — and setTimeout runs in a document where rAF and animations do
   not, so the worst case is the finished list with no movement, never a blank one. Identical shape
   to bookings.js's ticket cards and takeover.js's enter, for identical reasons.
   ⚠ CRITICALLY DAMPED, NO OVERSHOOT: a tap on a button is not a throw, so there is no momentum to
     express (apple-design — bounce is for what a finger threw). 8px and a hair of scale is enough
     to read as arriving; more is a list that flings itself at you. */
/* ⚠ THE ARRIVAL MOVES `translate` AND `scale`; `transform` IS THE PRESS'S. pressable() writes the
     row's transform on a tap, and with the arrival on that same property the press rode the 420ms
     arrival curve — a tap answered by a slow sink. Separate properties compose, so the two cannot
     touch each other, and each has its own duration (2026-10-04). */
[data-ready] .ev .ev-p { transition: opacity 420ms cubic-bezier(.22,.61,.36,1),
                                     translate 420ms cubic-bezier(.22,.61,.36,1),
                                     scale 420ms cubic-bezier(.22,.61,.36,1),
                                     transform var(--t-press) var(--ease-out); }
.ev .ev-p.is-in { opacity: 0; translate: 0 8px; scale: .99; }
/* ⚠ SCALED DOWN 2026-09-16. Syd, of the queue: "YOU COULD probably make this font smaller or the
   scale of it, longer @usernames 15 characters longs definately wont be seen." A handle is capped
   at 20 characters (site/functions/_handles.js's MAX_HANDLE_LEN) and the fixtures' longest is
   eleven, so the row had never been drawn against the case it has to survive. Every dimension in
   this block came down a step rather than the name being allowed to take the whole cut. */
.ev .ev-p {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: var(--tap-min); padding: 8px 10px;
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--bg-raised); color: var(--ink); text-align: left; cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
/* ⚠ YOUR OWN ROW IS FILLED, NOT TINTED. In a list of ten names read at arm's length beside a
   court, "which one is me" has to survive being glanced at — and a background tint two shades
   from its neighbour does not. Same decision as the selected tab in the floating nav. */
.ev .ev-p.is-me { border-color: var(--selected-edge); background: var(--selected-soft-bg); }
.ev .ev-p.is-dim { opacity: .55; }
.ev .ev-p-t { flex: 1 1 auto; min-width: 0; display: grid; gap: 2px; }
/* The name line: the club mark, then the name. ⚠ min-width: 0 ON THE LINE, or the flex item
   refuses to shrink below its content and the ellipsis on .ev-p-n never fires — the row grows
   instead and the rating column is pushed off the right edge. */
/* Name, then @handle, lighter and smaller. ⚠ baseline, not center: two type sizes on one line
   centre-aligned sit on two different baselines and read as a typo. ⚠ The handle is flex:none, so
   the NAME is what ellipsises — event.js says why. */
.ev .ev-p-nl { display: flex; align-items: baseline; gap: 5px; min-width: 0; }
.ev .ev-p-h {
  flex: 0 3 auto; min-width: 0;
  font: 400 11.5px/1.2 var(--font-ui); letter-spacing: -.006em;
  color: var(--ink-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ev .ev-p.is-me .ev-p-h { color: var(--selected-soft-ink); opacity: .75; }
/* ⚠⚠ BOTH CAN SHRINK NOW, AND THE NAME SHRINKS LAST. It used to be `flex: none` on the handle,
   which meant a 20-character @username took its full ~110px and the NAME absorbed the entire
   deficit — "Kiko Lim" rendered "Ki…" beside a handle nobody needed in full. Weighting the handle's
   shrink at 3 to the name's 1 makes it give up about three quarters of any shortfall, so the row
   degrades by trimming the identifier and keeps the label. That is the right way round: the name is
   what the row is about, and the profile is one tap away for the rest of the handle. */
.ev .ev-p-n {
  flex: 0 1 auto; min-width: 0;
  font: 600 14px/1.25 var(--font-ui); letter-spacing: -.004em; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⚠ A FLEX LINE NOW, because it carries the club mark as well as words. inline-flex keeps it on
   one line inside the grid cell and lets the mark sit at flex:none beside the text. */
.ev .ev-p-s {
  display: flex; align-items: center; gap: 4px; min-width: 0;
  font: 500 11.5px/1.2 var(--font-ui); color: var(--ink-tertiary);
  font-variant-numeric: tabular-nums;
  overflow: hidden; white-space: nowrap;
}
.ev .ev-p-tag { flex: none; }
/* ⚠ TIGHTER THAN THE APP'S OTHER TAGS, AND ONLY IN A QUEUE ROW. This is the one tag that stands in a
   row of fixed columns — place, head, rating — where the name is the only thing that can give, so
   every px the tag keeps is a px off the name of the person reading it. Measured at 375px with the
   call showing: "Your turn" is 89.7px at the shared .tag metrics, leaving the name 89px for the 93px
   "Syd Tanchan" needs; at 82.8px here it leaves 96px and the name fits. It is the tag's padding and
   tracking only — the type size, the pill and the colour stay the shared ones, so it still reads as
   the same mark as every other tag in the app.
   ⚠ 7px IS NOT A LOT OF HEADROOM AND THE ROW IS STILL TIGHT WHILE YOU ARE CALLED. A display name
     much past twelve characters is ellipsised on YOUR OWN row for as long as `up` is true — which is
     the whole offered/held/arrived stretch, not just the 15-second swipe. The full name is still in
     the row's aria-label and on the profile sheet. Fixing it properly means giving something else up
     — the rating column on that row, the tag's own line, or a shorter word than "Your turn" — and
     that is a call about what a player should see while being called, not a measurement. */
.ev .ev-p.has-pos .ev-p-tag { padding: 5px 7px; letter-spacing: .04em; }

/* ── The queue's three columns: place, person, rating ────────────────────────
   Syd, 2026-09-15: "add a position COLUMN for the queue LIST … your position on the left most (like 1,
   2, 3) … details like YOUR ELO on the right most side of the pill. Then i also want some sort of
   indication if you are friends with that person." playerRow() says why the rating is labelled "Rating".
   ⚠ BOTH OUTER COLUMNS ARE FIXED-WIDTH AND TABULAR, so 1 to 9 and a 1498 against a 1710 stand in
     straight columns down the list — which is what makes a column a column rather than a number in a
     row. The place is 24px because two tabular digits of 16px bold are 19px. */
/* ⚠⚠ AND THE CLUB MARK SPENT THE HEADROOM THE NOTE ABOVE HAD LEFT. 2026-09-16: the mark is 44px
   at the head of the name line, and on YOUR row — the one with a tag as well — "Syd Tanchan" was
   6px short the moment it landed. The gap came down a pixel and the head from 36 to 34, which is
   7px back; the board row on the People tab gave up the same two things for the same reason, so
   the two lists still agree about how big a person's disc is.
   ⚠ THE NOTE ABOVE STILL STANDS AND IS NOW TIGHTER: while `up` is true the tag is "Your turn"
     (82.8px, not "You" at 39), and with the mark in front of the name a display name past about
     SEVEN characters is ellipsised on your own row for the length of the call. That is a bigger
     cut than the twelve characters the note measured before the mark existed. The full name is in
     the row's aria-label and on the profile sheet either way, and the fix is still the same call
     Syd has to make — give up the rating column, the tag's own line, or the mark on your own row
     — rather than another pixel. */
.ev .ev-p.has-pos { gap: 7px; padding-left: 6px; padding-right: 8px; }
.ev .ev-p.has-pos .ev-av { width: 30px; height: 30px; font-size: 11px; }
.ev .ev-p-pos {
  flex: none; width: 21px; text-align: center;
  font: 700 15px/1 var(--font-ui); letter-spacing: -.01em; color: var(--ink-secondary);
  font-variant-numeric: tabular-nums;
}
.ev .ev-p.is-me .ev-p-pos { color: var(--ink); }
/* ⚠⚠ TWO COLUMNS SINCE 2026-09-16 — the rank moved here off the second line, where the club now
   is. Each carries its own heading: "#6 1710" under one word "Rating" would name the rank a rating,
   and this row already refuses to let one number wear two names. Fixed widths and tabular figures,
   so both run straight down the list. */
/* ⚠ THE LABELS SET THIS COLUMN'S WIDTH, NOT THE NUMBERS. "RATING" at the old 10px/.08em is 44px
   wide against a four-digit number's 34, so the caption was the widest thing in the row's
   right-hand third — and every pixel of it comes off a name that has to ellipsise. 9px and tighter
   tracking keeps both words legible and lets the numbers decide the width. */
.ev .ev-p-rt { flex: none; display: flex; align-items: center; gap: 6px; }
/* ⚠ THE NUMBER IS LEFT-ALIGNED TO ITS OWN HEADING, NOT RIGHT-ALIGNED TO THE ROW. Syd, 2026-09-16:
   "left align the rank #1 or that text to the word RANK." The heading is the wider of the two —
   "RANK" against "#1" is 29px against 16 — so ending both at the same right edge left the number
   floating over the second half of its own label with nothing under its first character.
   ⚠⚠ AND THESE WIDTHS ARE NOW THE HEADER'S, NOT THE NUMBERS'. The captions used to sit under every
     row and set each column's width; they live in the sticky header over the list now, so the
     numbers alone would size these cells to 22 and 30px and the two heading words would no longer
     line up with the figures beneath them. 29 and 34 are what "Rank" and "Rating" measure. */
.ev .ev-p-rt-c { display: grid; justify-items: start; min-width: 29px; }
.ev .ev-p-rt-c:last-child { min-width: 34px; }
.ev .ev-p-rt-n {
  font: 700 14px/1 var(--font-ui); letter-spacing: -.01em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ev .ev-p-rt-c:first-child .ev-p-rt-n { color: var(--ink-secondary); }
/* ⚠⚠ THE NARROWEST PHONES GIVE THE NAME ITS ROOM BACK. Every cell in this row but the name is
   `flex: none` — place 24, head 36, rating 43, and on YOUR row a tag as well — so the name is the only
   one that can give, and on your row it is the only one that has to. Measured at 320px with the call
   showing: "Your turn" is 87px and "Syd Tanchan" had 37px of box for 93px of name. Tightening the gap,
   the head and the tag's own padding returns ~50px, which is the difference between a name and an
   initial. ⚠ 340px MATCHES shell.css's tab-bar block — the same phones, the same reason, one number. */
@media (max-width: 340px) {
  .ev .ev-p.has-pos { gap: 7px; padding-left: 6px; padding-right: 8px; }
  .ev .ev-p.has-pos .ev-av { width: 30px; height: 30px; font-size: 11px; }
  .ev .ev-p.has-pos .ev-p-pos { width: 19px; }
  .ev .ev-p.has-pos .ev-p-tag { padding: 5px 6px; letter-spacing: .04em; }
  /* ⚠⚠ AND HERE THE CLUB MARK GIVES UP ITS FOUR CHARACTERS, MEASURED. At 320px with the tag
     shown, "Syd Tanchan" was 29px short on your own row and "Emman Mendoza" 16px on a
     stranger's; without it, none of the six rows is cut. The badge stays, the club's NAME is
     still in the row's aria-label, and the tag is on the People tab two taps away — what is not
     negotiable is the name of the person the row is about. screens/players.css makes the same
     trade at the same breakpoint and says so at greater length. */
  .ev .ev-p .clb-tag { display: none; }
  .ev .ev-p .clb-mark { gap: 0; }
  .ev .ev-p-nl { gap: 3px; }
  /* ⚠ THE HEADER SHRINKS WITH THE COLUMNS IT NAMES, or the two stop lining up on exactly the
     phones where there is least room to be wrong by. */
  .ev .ev-p-rt, .ev .ev-qh-rt { gap: 5px; }
  .ev .ev-p-rt-c { min-width: 26px; }
  .ev .ev-p-rt-c:last-child { min-width: 31px; }
  .ev .ev-qh-c:first-child { width: 26px; }
  .ev .ev-qh-c:last-child { width: 31px; }
  /* place 19 + gap 7 + head 30 */
  .ev .ev-qh-place { width: 56px; }
  /* ⚠ THE @HANDLE COMES OFF, as it does on the People tab at the same breakpoint and for the same
     measurement — it is fixed width in front of a name that is the only thing able to shrink. */
  .ev .ev-p-h { display: none; }
}

/* "Friend" in the second line, with its glyph, in the selected ink: a mark that you know this person,
   quieter than your own filled row and louder than the grey rank beside it. */
.ev .ev-p-fr { color: var(--selected-soft-ink); font-weight: 600; }
.ev .ev-p-fr svg {
  display: inline-block; width: 13px; height: 13px; margin-right: 3px; vertical-align: -2px;
}

/* Initials, not a photo. A roster of ten photos is ten requests on venue wifi for information
   two letters already carry at this size. */
.ev .ev-av {
  flex: none; display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: 50%;
  background: var(--selected-soft-bg); color: var(--selected-soft-ink);
  font: 700 12.5px/1 var(--font-ui); letter-spacing: .02em;
}
.ev .ev-av-s { width: 26px; height: 26px; font-size: 10px; }
/* ⚠ ON YOUR OWN ROW THE DISC IS THE FILLED FORM. The row itself wears --selected-soft-bg (above),
   which is the disc's own colour, so the initial stood bare with no circle behind it — the one row
   in the line without one (seen on the launch build's line, 2026-10-05). */
.ev .ev-p.is-me .ev-av { background: var(--selected-bg); color: var(--selected-ink); }

/* ══ THE PAGE ═══════════════════════════════════════════════════════════════
   Syd, 2026-09-03: "i want the event page to be an actual dedicated page, not a popup."

   ⚠ z-index 65 — OVER THE NAV (60) AND THE FLOATING PILL (59), ON PURPOSE. While you are in a
     live session this is the app; leaving the tab bar visible underneath would offer six ways
     out of a screen whose one exit is Back.
   ⚠ IT ENTERS FROM THE RIGHT AND LEAVES TO THE RIGHT. Spatial consistency: a page that
     arrives one way and departs another reads as two unrelated screens crossing, and destroys
     the sense that what you were on is still behind it.
   ⚠ THE OFFSET LIVES IN @starting-style, NOT IN AN INLINE STYLE CLEARED BY rAF. rAF does not
     run in a backgrounded tab, so an offset cleared there leaves the resting layout
     conditional on a callback of ours having run — the same note cafe.css carries. */
.screen > .ev-page {
  position: absolute; inset: 0; z-index: 65;
  display: flex; flex-direction: column;
  background: var(--bg);
  opacity: 1; transform: translate3d(0, 0, 0);
}
[data-ready] .screen > .ev-page {
  transition: transform 320ms var(--ease-out), opacity 200ms var(--ease-out);
  will-change: transform, opacity;
}
@starting-style {
  .screen > .ev-page.enter { opacity: 0; transform: translate3d(38px, 0, 0); }
}
.screen > .ev-page.is-out { opacity: 0; transform: translate3d(38px, 0, 0); pointer-events: none; }

/* ⚠ THE BAR DOES NOT SCROLL AND THE BODY DOES. That split is the whole reason this stopped
   feeling stuck: as a sheet, one element was both the scroller and the drag-to-dismiss
   surface, so every flick had to be disambiguated. Here the bar is flex:none, the body is
   flex:1 with its own scrollport, and nothing has to guess. */
/* ⚠ THREE COLUMNS, THE OUTER TWO EQUAL, so the crest sits on the page's centre line whatever "Back"
   measures. Syd, 2026-09-15: "header in this image has TOO much empty space, make sure logo is seen on
   header here." The bar used to be a row holding one word, padded 6px under it, and the session header
   padded 4px more before its tag — a strip of nothing above the page's first line. The crest fills the
   bar's middle and the bottom padding goes. */
.ev-page .ev-bar {
  flex: none; display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  padding: calc(4px + var(--safe-top, 0px)) 10px 0;
}
.ev-page .ev-bar > .ev-back { justify-self: start; }
/* 50px wide is 34px tall: the height of the Back label's line plus its chevron, so the two read as one
   row, and well clear of the 44px bar they sit in. */
.ev-page .ev-bar-crest { display: block; width: 50px; height: auto; }
.ev-page .ev-back {
  display: inline-flex; align-items: center; gap: 3px;
  min-height: var(--tap-min); padding: 8px 12px 8px 6px;
  border: 0; background: transparent; cursor: pointer; color: var(--ink);
  font: 600 16px/1 var(--font-ui); letter-spacing: -.01em;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.ev-page .ev-back svg { width: 21px; height: 21px; display: block; }
.ev-page > .ev { flex: 1 1 auto; min-height: 0; display: flex; }
.ev-page > .ev > .pane-scroll { flex: 1 1 auto; }

/* ⚠⚠ WHERE THERE IS A STATUS BAR TO RUN UNDER, THE PAGE RUNS UNDER IT, AND THE BAR BECOMES GLASS.
   Syd, 2026-10-04: "i want the elements to flow behind it like instagram" — said of the tabs, and this
   page covers the tabs, so a solid bar here would put the flat strip straight back the moment a live
   session opens. The bar floats over the body instead (absolute, the tab bar's own material), and the
   body's scroller starts its content below the bar by padding, so the page looks the same at rest and
   the court board slides under Back and the crest as it scrolls.
   ⚠ ONLY UNDER `data-top-inset`, which app.js sets when the device reports a top inset at all. On a
     desk browser there is no status bar to run under, and the page keeps the solid bar it was drawn
     with — the same arrangement it has always had, untouched.
   ⚠ 48px IS THE BAR WITHOUT ITS INSET: 4px of top padding and the 44px Back button (the crest is 34
     and centred in the same row). The queue's sticky header keeps `top: 0` and pins just under the
     bar, for the reason shell.css gives — a sticky top is measured inside the scroller's padding. */
.ev-page { --ev-bar-h: 48px; }
:root[data-top-inset] .ev-page > .ev-bar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 4;
}
/* ⚠⚠ THE GLASS IS A LAYER BEHIND Back AND THE CREST, AND IT FADES OUT OVER ITS LAST 14px — the status
     strip's own edge (shell.css, ".screen::after IS THE EDGE"). Painted as the bar's background it ended
     in a hard line at 110pt: the court board ran sharp right up to it and vanished into the frost, which
     in the simulator read as the same cut the solid bar made (review, 2026-10-04). Whole behind the
     clock, Back and the crest; fading below Back's label, where only the crest's opaque foot sits. */
:root[data-top-inset] .ev-page > .ev-bar::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: var(--chrome);
  -webkit-backdrop-filter: var(--chrome-blur); backdrop-filter: var(--chrome-blur);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 14px), transparent);
}
:root[data-top-inset] .ev-page > .ev > .pane-scroll {
  padding-top: calc(var(--ev-bar-h) + var(--safe-top));
  scroll-padding-top: calc(var(--ev-bar-h) + var(--safe-top));
}
/* ⚠ AND THE QUEUE'S HEADINGS CARRY 14px OF GROUND ABOVE THEM, for the moment they pin under that fade.
     Pinned, rows slide up behind them and out over their top edge — straight into the fade, where a
     masked backdrop is not frosted, so a 14px band of sharp names ran between the headings and the
     glass (the Cafe rail's band, which stuck.js covers there). The headings' own ground, carried up
     under the fade, fills it. In flow it sits in the gap above them, the same colour as the page. */
:root[data-top-inset] .ev .ev-qh { box-shadow: 0 -14px 0 var(--bg); }
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
  :root[data-top-inset] .ev-page > .ev-bar::before {
    background: var(--bg); -webkit-backdrop-filter: none; backdrop-filter: none;
    -webkit-mask-image: none; mask-image: none;
  }
}

/* ══ THE FLOOR ══════════════════════════════════════════════════════════════
   Four courts in ONE card. Syd: "in the size of one card. i want to see an illustration of 4
   courts all with players in it with the scores."

   ⚠ TWO ACROSS, ALWAYS. One column makes each court wide and the card three screens tall,
     which is the four-separate-cards problem with extra steps. Four across gives each court about
     78px — and since 2026-09-14 every corner carries a name, which needs half a court.
   ⚠⚠ THERE IS NO FOUR-ACROSS RULE, AND ONE WAS TRIED AND TAKEN OUT THE SAME DAY. This file had
     `@container (min-width: 560px)` for four across, and nothing above the page declares a container,
     so it never matched. Rewritten as `@media (min-width: 760px)` it did match — on the VIEWPORT, while
     a wide screen shows this app inside a phone-width frame — and at 768px each court was 76px wide
     with twelve of sixteen names cut to an ellipsis. The card is only ever as wide as a phone's
     column, so two across is the layout, not the fallback. */
.ev .ev-floor { margin: 0 var(--gutter); padding: 12px; }
.ev .ev-cts { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 10px; }

/* ── The floor, opened ───────────────────────────────────────────────────────
   Syd, 2026-09-15: "the FOUR courts illustration can be expanded and HAS to have more detail, like i want
   to see the user name, ELO, RANK of all the people ON THE COURT." Open, one court per row, each corner
   two lines (see courtMini()); openableFloor() says why the control is the button at the card's foot.
   ⚠ THE OUTGOING LAYER HOLDS THE OTHER LAYOUT. swapHeight() lifts the old courts into `.swap-out` and then
     build() flips `.is-open`, so while the ghost fades the grid's class already describes the NEW courts:
     opening, the ghost carries the closed two-across ones; closing, the opened single column. Without
     these, the ghost's courts restacked into the wrong grid for the moment they took to fade. */
.ev .ev-floor.is-openable {
  cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out);
}
.ev .ev-cts.is-open { grid-template-columns: minmax(0, 1fr); gap: 20px; }
.ev .ev-cts.is-open > .swap-out { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 10px; }
.ev .ev-cts:not(.is-open) > .swap-out { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; }
.ev .ev-floor-more {
  display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%;
  min-height: var(--tap-min); margin: 12px 0 -6px; padding: 0 12px;
  border: 0; border-top: 1px solid var(--rule); background: transparent; cursor: pointer;
  font: 600 13px/1 var(--font-ui); letter-spacing: -.005em; color: var(--ink-secondary);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.ev .ev-floor-more:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; border-radius: var(--r-md); }
.ev .ev-floor-chev { display: block; flex: none; width: 11px; height: 7px; }
.ev .ev-floor-chev svg { display: block; width: 100%; height: 100%; }
[data-ready] .ev .ev-floor-chev { transition: transform var(--t-menu) var(--ease-out); }
.ev .ev-floor.is-open .ev-floor-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  [data-ready] .ev .ev-floor-chev, .ev .ev-floor.is-openable { transition: none; }
  /* ⚠ THE ROWS STILL ARRIVE, THEY JUST DO NOT TRAVEL. Turning the transition off is not enough on
     its own: `.is-in` is a DISPLACED state and a row left holding it with nothing to move it back
     would sit at opacity 0 until its timer fired. Neutralising the displacement is what makes the
     list simply be there. Same pair of lines as bookings.css's ticket cards. */
  [data-ready] .ev .ev-p { transition: none; }
  .ev .ev-p.is-in { opacity: 1; translate: none; scale: none; }
}

/* ⚠⚠ `.ev-ct`, AND IT WAS `.ev-mini` UNTIL IT COLLIDED WITH THE SHUFFLE BUTTON IN THIS SAME
   FILE. I named the little outline button `.ev-mini` for the teams section, then named the
   court cell `.ev-mini` too — so every court inherited `border-radius: 999px` and a hairline
   border and each one wore a faint pill-shaped outline about 13px larger than the drawing
   inside it. It looked like a rendering artefact and I spent three passes hunting it in the
   SVG. The header of this file already warns that these names read generically and collide
   ACROSS screens; it turns out one file is quite capable of colliding with itself.
   The cell is `.ev-ct` now, which also matches the parts it already owns — .ev-ct-floor,
   .ev-ct-line, .ev-ct-net, .ev-ct-p — and the button is `.ev-btn-mini`. */
.ev .ev-ct { min-width: 0; }
.ev .ev-ct-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 6px;
  margin-bottom: 5px;
}
.ev .ev-ct-name {
  font: 700 9px/1 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⚠ THE SCORE IS THE CALL — serving team's score, receiving team's, server number — so the FIRST
   number is the dark one: in a call it always belongs to the team with the ball. The server number
   is the quietest of the three, because it is the one a glance needs least. Syd, 2026-09-14: "the
   score should have the service counter also 10-9-2 or 10-9-1".
   ⚠ font-variant-numeric AFTER the `font` shorthand, which resets it — the queue card's count learned
     that the hard way (see .ev-more). */
.ev .ev-ct-score {
  flex: none; font: 600 12px/1 var(--font-ui); color: var(--ink-secondary);
  font-variant-numeric: tabular-nums;
}
.ev .ev-ct-score b { font-weight: 700; color: var(--ink); }
.ev .ev-ct-srv { color: var(--ink-tertiary); }

/* ── The names, outside the lines ───────────────────────────────────────────
   One row over the court and one under it, each split at the net: a left name starts at the court's
   left edge and a right name ends at its right edge, so every name sits over or under its own
   service court. First name and last initial (court.js's shortLabel, the admin station's rule), cut
   with an ellipsis only past the half-court it has.
   ⚠ A FIXED 17px LINE BOX, so the pill behind your name and the ball beside the server's change
     nothing about where the court is drawn: every court in the card starts at the same height. */
.ev .ev-ct-names {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: 6px;
  height: 17px; margin-bottom: 3px;
}
.ev .ev-ct-names.is-below { margin: 3px 0 0; }
.ev .ev-ct-who {
  display: inline-flex; align-items: center; gap: 4px; min-width: 0; max-width: 100%;
  justify-self: start; height: 17px;
  font: 600 11px/17px var(--font-ui); letter-spacing: -.003em; color: var(--ink-secondary);
}
.ev .ev-ct-who.is-end { justify-self: end; }
.ev .ev-ct-nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ── The club mark in a corner ───────────────────────────────── */
/* ⚠ 13px INSIDE A 17px LINE BOX. The line box's height is fixed so that every court in the card
   starts at the same y (see .ev-ct-names above); a badge taller than the box would push the
   drawing down by a pixel on whichever courts happened to have a clubbed player in the top row,
   and four courts that no longer line up is the one thing this card cannot survive. */
.ev .ev-ct-who .clb-logo { width: 13px; height: 13px; border-radius: 4px; font-size: 6px; }
.ev .ev-ct.is-open .ev-ct-who .clb-logo { width: 13px; height: 13px; border-radius: 4px; font-size: 6px; }
.ev .ev-ct-who .clb-mark { gap: 2px; }
/* ⚠ THE TAG FOLLOWS THE CORNER'S OWN INK, not the page's tertiary — a corner is already
   --ink-secondary, and on your filled pill it is --selected-ink. */
.ev .ev-ct-who .clb-tag { color: currentColor; opacity: .66; font-size: 8.5px; letter-spacing: .04em; }
/* On the filled "you" pill the badge's navy ground has almost no contrast against the pill. */
.ev .ev-ct-who.is-me .clb-logo {
  background: color-mix(in srgb, var(--selected-ink) 26%, transparent); color: var(--selected-ink);
}
/* ⚠⚠ AT 320px THE CLOSED CARD CANNOT CARRY THE BADGE AT ALL, AND THIS IS THE MEASUREMENT.
   A closed corner's column is 77px at 375px and about 65 at 320. With the badge in front of the
   name, seven of the sixteen corners were cut — "Paulo S." by 10px, "Chesca Y." by 10 — and a
   court card whose whole point is that you can read who is standing where had become a card of
   ellipses. So on the narrowest phones the closed card keeps the names and drops the badge, and
   the OPEN card keeps the badge and drops the four characters (which still left "Gab Monteverde"
   the tightest corner of the sixteen). The club is in the drawing's aria-label in both states,
   and the queue list below is where it is written out.
   ⚠ 340px MATCHES the queue row's block above and shell.css's tab bar — one number across the app. */
@media (max-width: 340px) {
  .ev .ev-ct:not(.is-open) .ev-ct-who .clb-mark { display: none; }
  .ev .ev-ct.is-open .ev-ct-who .clb-tag { display: none; }
  .ev .ev-ct.is-open .ev-ct-who .clb-mark { gap: 0; }
  .ev .ev-ct.is-open .ev-ct-names { column-gap: 2px; }
}

/* ⚠⚠ THE MOVE ITSELF. playMoves() in event.js sets the starting offset and then clears it; this is
   the only place the duration and the curve live. 420ms on --ease-out is the critically damped end
   of apple-design's UI range (damping 1.0, response ~0.4) — a side-out is a state change, not
   something a finger threw, so it settles rather than bounces.
   ⚠ transform-box/transform-origin ARE REQUIRED ON THE SVG GROUPS. Without them a CSS transform on
     an SVG child resolves against the viewBox origin rather than the element, and the heads fly in
     from the top-left corner of the court instead of from where they were.
   ⚠ NO `will-change`: these animate a few times a match, and a permanent compositor layer on
     sixteen groups costs more than it saves. */
[data-ready] .ev .ev-ct-p,
[data-ready] .ev .ev-ct-who {
  transition: transform 420ms var(--ease-out);
}
.ev .ev-ct-p { transform-box: fill-box; transform-origin: center; }
@media (prefers-reduced-motion: reduce) {
  [data-ready] .ev .ev-ct-p, [data-ready] .ev .ev-ct-who { transition: none; }
}

/* ── Opened: a court the card's width, and a second line in every corner ─────
   The name (whole now), then "Rank #9 · Rating 1688" — and nothing about whether you know the person,
   which is the queue list's fact in this build (courtMini() says why). The corner is a two-row grid
   aligned to its own edge, so a right-hand name and its line both end at the court's right edge, as
   the closed card's names do. */
.ev .ev-ct.is-open .ev-ct-top { margin-bottom: 8px; }
.ev .ev-ct.is-open .ev-ct-name { font-size: 10.5px; }
.ev .ev-ct.is-open .ev-ct-score { font-size: 15px; }
/* ⚠ 6px, DOWN FROM 10, AND THE FOUR PIXELS WENT TO THE NAMES. Open, each corner carries the club
   mark as well as the name, and two of the sixteen (Dana Villanueva, Gab Monteverde) were the
   first to lose letters to it. A column gap between two corners that sit at opposite ends of the
   card is the cheapest four pixels on this layout: nothing ever touches across it. */
.ev .ev-ct.is-open .ev-ct-names { height: auto; margin-bottom: 6px; column-gap: 4px; }
.ev .ev-ct.is-open .ev-ct-names.is-below { margin: 6px 0 0; }
.ev .ev-ct-corner { display: grid; justify-items: start; row-gap: 2px; min-width: 0; }
.ev .ev-ct-corner.is-end { justify-items: end; text-align: right; }
/* ⚠ gap: 3px WHEN OPEN, AND IT IS THE SERVER'S CORNER THAT NEEDS IT. A corner can hold four
   things at once — the club badge, the tag, the ball and the name — and that is three gaps, all
   of them in front of the one element that is allowed to ellipsise. Measured with the mark in
   place: @gabm, who is both clubbed and holding the ball, was the last corner of sixteen still
   losing a character. This is the tightest this layout goes; a fifth thing in a corner needs a
   different corner, not another pixel. */
.ev .ev-ct.is-open .ev-ct-who { height: 20px; font-size: 13px; line-height: 20px; gap: 3px; }
.ev .ev-ct.is-open .ev-ct-who.is-me { padding: 0 8px; }
.ev .ev-ct.is-open .ev-ct-whoball { width: 8px; height: 8px; }
.ev .ev-ct-det {
  display: block; max-width: 100%; min-width: 0;
  font: 500 11.5px/1.25 var(--font-ui); color: var(--ink-tertiary);
  font-variant-numeric: tabular-nums;
}
/* One line where it fits: "Rank #9 · Rating 1688". The separator belongs to the rating rather than
   sitting between them as text, so that stacking them below can simply switch it off. */
.ev .ev-ct-det-a, .ev .ev-ct-det-b {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ev .ev-ct-det-b::before { content: " · "; }
/* ⚠⚠ BELOW 360px THE RATING GETS ITS OWN LINE RATHER THAN AN ELLIPSIS, and that is the whole point.
   Syd, 2026-09-15: "i want to see the user name, ELO, RANK of all the people ON THE COURT." The
   rating is the END of this line, so on one line it is the first thing cut — measured at 320px,
   ten of the sixteen corners read "Rank #12 · Rating 16…", 147px of text in a 127px corner. A
   corner cannot be widened (two of them plus a gap ARE the card), and shrinking the type enough
   would have to shrink again for a three-digit rank. Two lines cost 14px of height and cannot
   lose a digit at any width. ⚠ The ellipsis stays on each part as the last-resort guard. */
@media (max-width: 360px) {
  .ev .ev-ct-det-a, .ev .ev-ct-det-b { display: block; }
  .ev .ev-ct-det-b::before { content: none; }
}
/* ⚠ YOU: A FILLED PILL, THE SAME MARK AS YOUR FILLED HEAD BELOW — navy on the light card, cream on the
   dark one. A shape as well as a colour, so it survives greyscale and sunlight. */
.ev .ev-ct-who.is-me {
  padding: 0 7px; border-radius: var(--r-full);
  background: var(--selected-bg); color: var(--selected-ink); font-weight: 700;
}
/* The ball beside a name: chartreuse with a navy edge, which reads on cream, on navy and on your pill. */
.ev .ev-ct-whoball {
  flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--live);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--navy) 55%, transparent);
}

.ev .ev-ct-svg { display: block; width: 100%; height: auto; }
/* The court surface: a shade, not a colour. Four saturated rectangles in one card would be the
   loudest thing on a screen whose subject is the people standing on them.
   ⚠ THE LINES ARE --ink-tertiary, NOT --rule. `--rule` is a 20%-alpha hairline meant for
     separating rows of text on a flat background; at 0.9 units inside an 88-wide viewBox that
     is a quarter-opacity half-pixel, and the court came out as a grey smudge with no lines
     visible in it at all. A court whose lines cannot be seen is a rectangle, and a rectangle
     answers none of the three questions this drawing exists to answer. */
.ev .ev-ct-floor { fill: var(--bg-sunken); stroke: var(--ink-tertiary); stroke-width: .8; }
.ev .ev-ct-kitchen { fill: color-mix(in srgb, var(--ink) 11%, transparent); }
.ev .ev-ct-line { stroke: var(--ink-tertiary); stroke-width: .8; opacity: .75; }
.ev .ev-ct-net { stroke: var(--ink); stroke-width: 1.5; }

/* ⚠⚠ `.ev-ct-disc`, NOT A BARE `circle`, BECAUSE THERE ARE TWO CIRCLES IN A SEAT NOW. The
   private board gives every seat a second, invisible circle at r 9 to stand as the tap target
   — see the note in event.js — and a bare `circle` selector painted that one too, so each
   seat wore a 68px filled disc with the real mark lost inside it. A selector that matches by
   ELEMENT rather than by role is fine right up until a second element of that type arrives.
   ⚠ AND THE FONT SIZE IS NOT SET HERE. It comes from the `font-size` attribute in event.js,
     which is derived from the seat radius so the same drawing works at both sizes. Written as
     the `font` SHORTHAND this rule silently overrode that attribute — CSS beats a presentation
     attribute — and the private board's initials came out at the lobby's 5.4px inside a mark
     twice that size. Set the parts, never the shorthand. */
.ev .ev-ct-disc { fill: var(--bg-raised); stroke: var(--ink-tertiary); stroke-width: .8; }
.ev .ev-ct-hit { fill: transparent; stroke: none; }
.ev .ev-ct-p text {
  font-weight: 700; font-family: var(--font-ui); fill: var(--ink-secondary);
  letter-spacing: .01em;
}
/* Serving pair: filled, so the pair reads as one unit rather than two marked players. */
.ev .ev-ct-p.is-serving .ev-ct-disc { fill: var(--selected-soft-bg); stroke: var(--selected-soft-ink); }
.ev .ev-ct-p.is-serving text { fill: var(--selected-soft-ink); }
/* The lobby's server: one filled head and the ball on its net side — site/admin/courtsvg.css's marks,
   so the station and the players' card draw a serve the same way. */
.ev .ev-ct-p.is-server .ev-ct-disc { fill: var(--selected-soft-bg); stroke: var(--selected-soft-ink); }
.ev .ev-ct-ball { fill: var(--live); stroke: var(--ink); stroke-width: .5; }

/* ── An empty seat is a seat, not a missing player ──────────────────────────
   ⚠ DASHED, AND WITH A "+" IN IT. A blank gap where a mark should be reads as the drawing
     having failed; a dashed outline reads as a place for somebody. The glyph is what makes it
     pressable rather than merely absent — DESIGN.md §2, a state gets a colour, a word and a
     glyph, and here the glyph is doing all the work because there is no room for the word. */
.ev .ev-ct-p.is-empty .ev-ct-disc {
  fill: none; stroke: var(--ink-tertiary); stroke-width: .9;
  stroke-dasharray: 2.6 2.2; opacity: .8;
}
.ev .ev-ct-p.is-empty text { fill: var(--ink-tertiary); font-weight: 500; }

/* Live seats — the private board only. The lobby's four minis are a picture and nothing in
   them is pressable, so nothing there should suggest it is. */
.ev .ev-ct-svg.is-live .ev-ct-p { cursor: pointer; }
.ev .ev-ct-svg.is-live .ev-ct-p:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 1px; border-radius: 50%;
}
@media (hover: hover) {
  .ev .ev-ct-svg.is-live .ev-ct-p:hover .ev-ct-disc { stroke: var(--ink); opacity: 1; }
}
/* ⚠⚠ YOU ARE THE FILLED, SELECTED HEAD — AND THIS RULE COMES AFTER THE SERVING ONES ON PURPOSE. It was
   `.is-me circle` in chartreuse, and on Syd's own screenshot (2026-09-14) his head showed only a
   thick ring: `.ev-ct-p.is-serving .ev-ct-disc` weighs (0,4,0) and beat `.ev-ct-p.is-me circle` at
   (0,3,1), so a player on the serving team lost their colour. The bare `circle` also painted the
   private board's invisible r-9 tap target. Both now name .ev-ct-disc at equal weight, and "you"
   is last, so it wins.
   ⚠ NOT CHARTREUSE ANY MORE, because on these courts chartreuse is the ball. Syd asked for "more
     obvious": --selected-bg is the strongest mark either theme has — navy on the light card, cream
     on the dark one — and it pairs with the pill behind your name. */
.ev .ev-ct-p.is-me .ev-ct-disc { fill: var(--selected-bg); stroke: var(--selected-bg); stroke-width: 1; }
.ev .ev-ct-p.is-me text { fill: var(--selected-ink); }
.ev .ev-ct.is-mine .ev-ct-name { color: var(--ink); }

/* ── The queue card. The pager's card, with people in it. ────────────────── */
.ev .ev-qcard { margin: 0 var(--gutter) 14px; padding: 18px 18px 16px; }
/* ⚠⚠ THE SAME CONTENT BOX IN BOTH THEMES, BECAUSE THE RACK'S GRID IS PICKED FROM ITS WIDTH. ui.css gives
   .card.navy a 1px border at night (so the navy card does not vanish into a navy page), which made this
   card's content 2px narrower in dark mode — enough, a review measured, for light and dark to draw a
   different number of paddles for the same queue on a 393px and a 414px iPhone, and to shift the header
   text by a pixel when the theme changed at sunset. The padding gives the pixel back. The two selectors
   mirror ui.css's exactly; change them together. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ev .ev-qcard { padding: 15px 17px 13px; }
}
:root[data-theme="dark"] .ev .ev-qcard { padding: 15px 17px 13px; }
.ev .ev-qcard-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
.ev .ev-qcard-lab, .ev .ev-qcard-of {
  font: 700 10.5px/1 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--on-navy-dim);
}
.ev .ev-qcard-big { display: flex; align-items: baseline; gap: 10px; margin-top: 10px; }
.ev .ev-qcard-n {
  font-family: var(--font-display); font-weight: 600; font-size: 44px; line-height: .95;
  letter-spacing: -.03em; color: var(--on-navy); font-variant-numeric: tabular-nums;
}
.ev .ev-qcard-w {
  font: 700 10.5px/1.2 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--on-navy-dim);
}

/* ⚠ THE TICKET AS DESIGNED — one row of whole 20px paddles — standing on a grid that runs the length of
   the card. rackGrid() in site/app/rack.js picks, from the rack's width alone, how many slots and the gap
   between them (as near the designed 2px as the width allows: 2.08px on a 375px phone, 1.79px on a 393);
   event.js writes that gap as `--rack-gap`. A row carrying a count uses every slot, so it reaches both
   edges by construction; a short line with no count stands on the same grid and simply ends sooner.
   ⚠ NOTHING HERE STRETCHES OR POOLS SPACE. Earlier versions did both, and a review measured each: gaps
     spread by `space-between` breathed from 2.3px to 5.7px between neighbouring queue states, and a spacer
     that pooled the spare room beside the count left a hole about one paddle wide that read as a missing
     person. The grid leaves no spare room to put anywhere.
   ⚠ THE PADDLES MAY SHRINK, THE COUNTS MAY NOT — and on a phone at normal size neither happens. A count
     fits its two slots up to 999 (see .is-wide below). Shrinking is for the first frame of a rack drawn
     from a guess, and for a card narrower than eight slots can stand on at the minimum gap: a phone at
     a large page zoom, where a 375pt iPhone at 200% gives a 120px rack. The 10px floor is what fits that
     rack — two counts and four paddles, with no gap — instead of spilling into the card's padding, which
     it did at a 14px floor. Past that it does not fit: a row with two counts needs 120px and one with a
     single count 100px, so on a 375pt phone above 200% zoom, or a 320pt phone at 200%, the row runs past
     the rack, and past the card's edge on a page under 170px. Height follows width through aspect-ratio
     and the row is bottom-aligned, so a smaller paddle still stands on the same floor. */
.ev .ev-rack { display: flex; flex-direction: column; row-gap: 10px; margin-top: 12px; }
/* ⚠ flex: none ON THE ROW. swapHeight() pins the rack at its old height while the new rows are already
   inside, and a column flex item may shrink to its content — a 34px paddle — so every row lost 6px for
   the length of the spring: the paddles jumped up, your raised paddle's head was clipped, and all of it
   slid back at the end (review, 2026-09-14). A row that cannot shrink keeps 40px and the clip hides it. */
.ev .ev-rack-row { display: flex; flex: none; align-items: flex-end; gap: var(--rack-gap, 2px); height: 40px; }
.ev .ev-pad { flex: 0 1 20px; min-width: 10px; aspect-ratio: 20 / 34; }

/* ── The ticket, opened ──────────────────────────────────────────────────────
   Syd, 2026-09-14: "make the queue ticket expandable, if you click it it will expand to show all the
   paddles (multiple rows is fine as long as expanded". Closed, the rack is the one row above; open, it is
   every paddle in rows on the same grid, 10px apart — enough that a row of heads does not read as the
   handles of the row above. openable() in event.js says why the whole card is the control.
   ⚠ THE OUTGOING LAYER IS LAID OUT LIKE THE RACK. swapHeight() lifts the old rows into `.swap-out`, which
     tokens.css positions but does not lay out; without this the rows it carries would stack with no gap
     for the moment they take to fade. */
.ev .ev-rack > .swap-out { display: flex; flex-direction: column; row-gap: 10px; }
.ev .ev-qcard.is-openable {
  cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out);
}
.ev .ev-qcard.is-openable:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* A line that fits on the closed row does not open (syncOpenable()), so its card must not press like a
   control either. pressable() writes an inline transform; this outranks it only while that is so. */
.ev .ev-qcard:not(.is-openable) { transform: none !important; }
.ev .ev-qcard-of { display: inline-flex; align-items: center; gap: 6px; }
/* Points down for "there is more", and turns over once it is showing. */
.ev .ev-qcard-chev { display: block; flex: none; width: 11px; height: 7px; }
.ev .ev-qcard-chev svg { display: block; width: 100%; height: 100%; }
[data-ready] .ev .ev-qcard-chev { transition: transform var(--t-menu) var(--ease-out); }
.ev .ev-qcard.is-open .ev-qcard-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  [data-ready] .ev .ev-qcard-chev, .ev .ev-qcard.is-openable { transition: none; }
}
/* Solid tones mixed against the card's navy — the colours opacity .55 and .26 used to produce. */
.ev .ev-pad svg {
  width: 100%; height: 100%; display: block;
  fill: color-mix(in srgb, var(--on-navy) 55%, var(--navy));
}
.ev .ev-pad.is-behind svg { fill: color-mix(in srgb, var(--on-navy) 26%, var(--navy)); }
/* ⚠ YOUR PADDLE IS CHARTREUSE, TALLER AND FULLY LIT — three signals, not one colour. The queue rack
   in pager.js carries the long note on why: this app is read in sunlight by people who may not
   separate chartreuse from cream. `scale` from its foot, so it grows to the rack's 40px without
   pushing anything. */
.ev .ev-pad.is-me { scale: 1.18; transform-origin: 50% 100%; }
.ev .ev-pad.is-me svg { fill: var(--live); }
/* ⚠ THE COUNT IS QUIET ON PURPOSE. It is a fact about the line, not a control of its own — a tap on it
   opens the ticket like a tap anywhere on the card, which the chevron beside "N in line" says — so it
   gets the card's secondary ink on a faint capsule, not a button's fill. Centred on the
   paddle HEADS rather than on the row: the heads are where the eye reads the line, and a capsule
   centred on the whole 40px sat visibly low beside them. Tabular figures so "+34" and "+38" are
   the same width and the row does not shift when someone joins. */
.ev .ev-more {
  /* ⚠ EXACTLY TWO SLOTS, UP TO 999: two paddles and the gap between them, 40px + --rack-gap,
     made of this min-width and the 3px margin on the side facing the paddles. That is what lets a row
     with a count use every slot of the grid, and what keeps "+9" becoming "+10" — or a leading count
     losing a digit as you move up — from moving a single paddle. Text centred, so the number sits in
     the middle of its two slots. */
  flex: 0 0 auto; min-width: calc(37px + var(--rack-gap, 2px));
  align-self: flex-start; margin: 7px 0 0;
  display: inline-flex; align-items: center; justify-content: center; height: 20px; padding: 0 7px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--on-navy) 10%, transparent);
  color: var(--on-navy-dim);
  font: 600 11.5px/1 var(--font-ui); letter-spacing: .01em;
  /* ⚠ DECLARED HERE, AFTER THE SHORTHAND, AND NOT LEFT TO .mono-nums. `font:` resets
     font-variant-numeric, and the .mono-nums class this used to rely on loses to this rule — so the
     tabular figures were never applied, and in a version that spread spare room across the gaps a
     review measured "+10" becoming "+11" moving every paddle 2.5px. The count is two slots wide now up to
     999, so its digits cannot move the row; tabular figures still keep the number itself from jittering
     inside its capsule. */
  font-variant-numeric: tabular-nums;
}
/* The 3px on the side facing the paddles; the other side is the ticket's edge. */
.ev .ev-more.is-lead { margin-right: 3px; }
.ev .ev-more.is-trail { margin-left: 3px; }
/* ⚠ A THREE-DIGIT COUNT IS SET TIGHTER SO IT STILL FITS ITS TWO SLOTS. "+100" in the standard capsule is
   44.2px wide, against 38.25px for two slots at the tightest grid — so the paddles shrank to make room,
   and a count crossing from "+99" to "+100" slid every paddle 3–6px, the one digit change the grid did
   not absorb. Less padding and a slightly tighter tracking bring it to about 37.3px. Four digits would
   still not fit; no venue's line reaches a thousand. */
.ev .ev-more.is-wide { padding: 0 4px; letter-spacing: -.01em; }

/* ── Your turn ───────────────────────────────────────────────────────────── */
.ev .ev-turn {
  margin: 0 var(--gutter) 20px; padding: 18px;
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--live) 20%, var(--bg-raised));
  border: 1px solid color-mix(in srgb, var(--live) 55%, transparent);
}
.ev .ev-turn.is-done {
  background: var(--bg-raised); border-color: var(--rule);
}
.ev .ev-turn-t {
  margin: 0; font-family: var(--font-display); font-weight: 600; font-size: 22px;
  line-height: 1.1; letter-spacing: -.018em; color: var(--ink);
}
.ev .ev-turn-s {
  margin: 6px 0 0; font: 500 13.5px/1.45 var(--font-ui); color: var(--ink-secondary);
}
.ev .ev-count { font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.ev .ev-turn-alt { margin-top: 14px; }
/* ⚠ THE PLAIN "ACCEPT" UNDER THE SLIDE, FOR EVERYONE WHO CANNOT DRAG (see turnPanel()). Always there for
   VoiceOver and Switch Control; drawn when the phone asks for reduced motion — the handover's rule — or
   when a keyboard focuses it, so focus never lands on something invisible; hidden from sight otherwise,
   because for a thumb the slide is the control and two ways to accept on one panel is one too many. */
.ev .ev-accept { display: block; width: 100%; margin-top: 10px; }
@media (prefers-reduced-motion: no-preference) {
  .ev .ev-accept:not(:focus-visible) {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }
}

/* ⚠ THE PLATE IS CREAM AND THE CODE IS DRAWN ONTO IT, quiet zone included — the canvas paints
   its own margin so the code survives a screenshot, a crop and any later restyling of this
   box. The same reasoning, and the same renderer, as the ticket: site/app/qr.js. */
.ev .ev-qr {
  display: grid; justify-items: center; gap: 8px; margin-top: 14px; padding: 14px;
  border-radius: var(--r-md); background: var(--bg-sunken);
}
.ev .ev-qr-canvas {
  display: block; width: min(210px, 100%); height: auto; aspect-ratio: 1;
  border-radius: var(--r-sm);
}
.ev .ev-qr-code {
  font: 700 13px/1 var(--font-ui); letter-spacing: .1em; color: var(--ink-secondary);
}

/* ⚠ THE SLIDE IS THE CAFE'S SLIDE-TO-PAY, AND THE SHARED RULE IS THE POINT: the two
   irreversible things in this app both take a gesture rather than a press. `touch-action:
   none` on the knob or the browser scrolls the page instead of moving it. */
.ev .ev-slide {
  position: relative; display: flex; align-items: center;
  height: 56px; margin-top: 16px; padding: 4px;
  border-radius: var(--r-full); background: var(--bg-sunken); overflow: hidden;
}
/* ⚠ THE WINDOW IS THE OLD FILL'S OWN BOX AND SHAPE — inset 4px, fully rounded — and the fill is a
   full-width capsule slid along inside it by a transform (event.js, acceptSlider()). Its round right
   end follows the knob, the window's round left end is where it starts, and nothing is laid out
   while it moves. `translateX(calc(52px - 100%))` is the resting place: the first 52px showing. */
.ev .ev-slide-clip {
  position: absolute; inset: 4px; overflow: hidden;
  border-radius: var(--r-full); pointer-events: none;
}
.ev .ev-slide-fill {
  position: absolute; inset: 0;
  border-radius: var(--r-full); background: color-mix(in srgb, var(--live) 55%, transparent);
  transform: translateX(calc(52px - 100%));
}
.ev .ev-slide-lab {
  position: absolute; left: 0; right: 0; text-align: center;
  font: 700 13px/1 var(--font-ui); letter-spacing: .04em; color: var(--ink-secondary);
  pointer-events: none;
}
.ev .ev-knob {
  position: relative; z-index: 1; flex: none;
  display: grid; place-items: center; width: 48px; height: 48px;
  border: 0; border-radius: 50%; cursor: grab;
  background: var(--ink); color: var(--bg);
  touch-action: none; -webkit-tap-highlight-color: transparent;
}
.ev .ev-knob:active { cursor: grabbing; }
.ev .ev-knob svg { width: 20px; height: 20px; }

/* ── Preview control ─────────────────────────────────────────────────────── */
/* Dashed, and labelled "preview", exactly like the pager's. Nothing that only exists because
   there is no desk yet may look like part of the product. */
.ev .ev-pv {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px 12px;
  margin: 4px var(--gutter) 8px; padding: 12px 14px;
  border: 1px dashed var(--rule); border-radius: var(--r-md);
  /* ⚠ IT WRAPS. The label and up to three buttons ("Call me up", "End the match", "Score a point") were
     one row that could not shrink: at 390 the last button ended 4px past the screen, at 320 74px past it
     (gutter audit, 2026-10-07). Wrapped, a button that does not fit starts a second row at the right,
     under the others; the label's auto margin keeps it at the left of the first. */
  flex-wrap: wrap;
}
.ev .ev-pv-lab {
  margin-right: auto;
  font: 700 10px/1 var(--font-ui); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.ev .ev-pv-btn { flex: none; }

/* ── Teams ───────────────────────────────────────────────────────────────── */
.ev .ev-teams {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: stretch; gap: 8px;
  padding: 0 16px;
}
.ev .ev-team {
  display: flex; flex-direction: column; gap: 6px; padding: 12px 10px;
  border: 1px solid var(--rule); border-radius: var(--r-md); background: var(--bg-raised);
}
.ev .ev-team.is-serving { border-color: var(--ink-tertiary); }
.ev .ev-team-lab {
  font: 700 9.5px/1 var(--font-ui); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-tertiary); margin-bottom: 2px;
}
.ev .ev-chip-btn { border: 0; cursor: pointer; justify-content: flex-start; }
.ev .ev-slot {
  min-height: 34px; border-radius: var(--r-full); cursor: pointer;
  border: 1px dashed var(--rule); background: transparent; color: var(--ink-tertiary);
  font: 600 12px/1 var(--font-ui);
}
.ev .ev-vs {
  align-self: center;
  font: 600 11px/1 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.ev .ev-btn-mini {
  border: 1px solid var(--rule); border-radius: var(--r-full); background: transparent;
  padding: 6px 12px; cursor: pointer; color: var(--ink-secondary);
  font: 600 12px/1 var(--font-ui);
}

/* ── The scoreboard ──────────────────────────────────────────────────────── */
.ev .ev-board {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 10px;
  padding: 0 16px;
}
.ev .ev-board.is-idle { opacity: .45; }
.ev .ev-board-c { display: grid; justify-items: center; gap: 7px; }
.ev .ev-board-lab {
  font: 700 9.5px/1 var(--font-ui); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
/* ⚠ THE WHOLE NUMBER IS THE BUTTON, AND IT IS THIS BIG BECAUSE OF WHERE IT IS USED. Between
   points, one-handed, without looking down properly. A 44px target beside a number would be
   correct on a desk and wrong at the side of a court. */
.ev .ev-board-btn {
  width: 100%; min-height: 96px; cursor: pointer;
  border: 1px solid var(--rule); border-radius: var(--r-lg); background: var(--bg-raised);
  font-family: var(--font-display); font-weight: 600; font-size: 46px; line-height: 1;
  letter-spacing: -.03em; color: var(--ink); font-variant-numeric: tabular-nums;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.ev .ev-board-c.is-serving .ev-board-btn { border-color: var(--ink); }
.ev .ev-board-d { font: 500 20px/1 var(--font-ui); color: var(--ink-tertiary); }
.ev .ev-actions { display: flex; gap: 8px; margin: 14px var(--gutter) 0; }
/* ⚠⚠ SMALLER THAN A FULL .btn, AND MEASURED RATHER THAN EYEBALLED. Two of these sit side by
   side inside a court card: 375 phone − 40 page gutters − 28 card padding − 8 gap leaves
   about 149px each. "Undo a point" at ui.css's 17px is ~105px of text inside 44px of
   horizontal padding = 149 — exactly at the limit, which in practice means it wrapped onto two
   lines and the pair rendered as four lines of tall buttons. At 15px with 15px of padding it
   is 121px and there is room to spare in both. The 44px tap height is untouched: .btn's
   min-height still holds, and only the type and the side padding come down. */
.ev .ev-act {
  flex: 1 1 0; min-width: 0;
  font-size: 15px; letter-spacing: -.008em; padding-inline: 15px;
}
/* Inside a court card the row is already inset by the card's own padding. */
.ev .ev-bc .ev-actions { margin: 12px 0 0; }
.ev .ev-scan { margin: 12px var(--gutter) 0; width: calc(100% - 2 * var(--gutter)); }

/* ── The new-match and scan sheets ───────────────────────────────────────── */
.ev-new, .ev-scan-sheet { padding: 4px 0 12px; }
.ev-new .ev-new-now {
  margin: 0 var(--gutter) 14px; font: 500 13.5px/1.4 var(--font-ui); color: var(--ink-secondary);
}
.ev-new .ev-opt {
  display: flex; align-items: center; width: calc(100% - 2 * var(--gutter)); margin: 0 var(--gutter) 8px;
  min-height: var(--tap-min); padding: 13px 16px; cursor: pointer; text-align: left;
  border: 1px solid var(--rule); border-radius: var(--r-md);
  background: var(--bg-raised); color: var(--ink);
}
.ev-new .ev-opt-t { display: grid; gap: 3px; }
.ev-new .ev-opt-n { font: 600 15px/1.2 var(--font-ui); color: var(--ink); }
.ev-new .ev-opt-s { font: 400 12.5px/1.35 var(--font-ui); color: var(--ink-tertiary); }

@media (prefers-reduced-motion: reduce) {
  .ev .ev-p, .ev .ev-board-btn, .ev .ev-opt { transition-property: background-color, border-color; }
}
@media (prefers-contrast: more) {
  .ev .ev-p, .ev .ev-team, .ev .ev-board-btn { border-color: var(--ink); }
  .ev .ev-ct-floor, .ev .ev-ct-line { stroke: var(--ink); }
  .ev .ev-ct-disc { stroke: var(--ink); }
}

/* ══ THE PRIVATE SESSION'S BOARDS ═══════════════════════════════════════════
   One card per booked court. Syd, 2026-09-04: "the private session also needs to be able to
   accomodate multiple courts (like score controls and everything) … each court booked will
   show up in the session page for private bookings."

   ⚠ A CARD PER COURT, WHERE THE LOBBY USES ONE CARD FOR ALL FOUR — and that is not an
   inconsistency, it is the difference between watching and running. In the lobby the subject
   is the FLOOR, taken in at a glance, so four courts share one card. Here the subject is one
   MATCH at a time: you fill its seats, you score it, you start its next game, and the next
   court over is a separate job with its own score that must never be one tap from the wrong
   one. Two boards in one card would put two "New match" buttons side by side. */
.ev .ev-bc { margin: 0 var(--gutter) 12px; padding: 14px 14px 12px; }
.ev .ev-bc-top {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 10px;
}
.ev .ev-bc-name {
  font: 600 17px/1.2 var(--font-ui); letter-spacing: -.01em; color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ev .ev-bc-match { flex: none; }
/* The drawing sits in its own band so the score below is not crowded against the baseline. */
.ev .ev-bc .ev-ct-svg { margin-bottom: 12px; }

/* ── The picker, and the invite field ─────────────────────────────────────── */
.ev-pick .ev-pick-h { margin-top: 18px; }

/* ⚠ THE INVITE PICKER'S OWN RULES LIVE IN shell.css UNDER `.invite`, NOT HERE. It is shared
   with book.js's paid sheet (see invite.js), and a stylesheet scoped to `.ev` cannot reach a
   sheet the Courts tab opened — the picker would have rendered as unstyled rows there and
   nothing would have said why. Shared markup, shared stylesheet. */

/* The line's tail. ⚠ A FACT, NOT A CONTROL — no chevron, no press, no border. There is nothing
   to expand to: the rest of the line is a number this prototype knows, not people it can name.
   Styled as the quietest thing in the section so it reads as the list ending rather than as a
   row that failed to load. */
.ev .ev-q-rest {
  margin: 0; padding: 11px 4px 2px;
  text-align: center;
  font: 500 12.5px/1.3 var(--font-ui); color: var(--ink-tertiary);
}
/* ── Opening the line ────────────────────────────────────────────────────────
   The same control as the courts card's foot button, because it does the same job on the same
   screen — one shape for "there is more of this, press to see it". Full width and 44px tall, so
   it is a thumb target rather than a link at the end of a list. */
.ev .ev-q-more {
  display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%;
  min-height: var(--tap-min); margin-top: 2px; padding: 0 12px;
  border: 0; border-radius: var(--r-md); background: transparent; cursor: pointer;
  font: 600 13px/1 var(--font-ui); letter-spacing: -.005em; color: var(--ink-secondary);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.ev .ev-q-more:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.ev .ev-q-chev { display: block; flex: none; width: 11px; height: 7px; }
.ev .ev-q-chev svg { display: block; width: 100%; height: 100%; }
[data-ready] .ev .ev-q-chev { transition: transform var(--t-menu) var(--ease-out); }
.ev .ev-q-more[aria-expanded="true"] .ev-q-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  [data-ready] .ev .ev-q-chev { transition: none; }
}

/* ── The scan, inside the blocking layer ────────────────────────────────────
   The same code as the ticket's, on navy. The page's own .ev-qr rules are built for a cream card,
   so the layer re-states the two that would be invisible on it rather than inheriting them. */
.ev-tko-scan { display: grid; justify-items: center; gap: 10px; }
.ev-tko-scan .ev-tko-scan-lab {
  margin: 0; font: 600 12px/1.3 var(--font-ui); letter-spacing: .04em;
  text-transform: uppercase; color: var(--on-navy-dim);
}
/* ⚠ A WHITE PLATE UNDER THE CODE, ALWAYS. A QR is read by a camera looking for a light quiet zone
   around dark modules; drawn straight onto navy it is a pattern no scanner will resolve. */
.ev-tko-scan .ev-qr-canvas {
  width: 172px; height: 172px; display: block;
  background: #fff; border-radius: var(--r-md); padding: 10px;
}
.ev-tko-scan .ev-qr-code {
  font: 600 15px/1 var(--font-ui); letter-spacing: .14em; color: var(--on-navy);
}
@media (max-width: 340px) {
  .ev-tko-scan .ev-qr-canvas { width: 146px; height: 146px; }
}

/* ══ THE PAGE ON REAL DATA (screens/eventreal.js) ════════════════════════════
   The same page shell, panel, courts card, queue card and table as the sample page — .ev-page,
   .ev-head, .ev-turn, .ev-floor, .ev-qcard, .ev-q — so the two read as one screen (Syd, 2026-10-05:
   "make sure the preview and the current app are 1:1"). What is new is only what the real page has
   and the sample did not: the grey shapes where the sample has other people, the courts card's foot
   as words, the ticket as a card of its own, the holder under it, a loading mark, and the call's two
   quieter answers. Everything is under `.evr`, or under `.ev` inside the call screen. */

/* ── The courts, empty ──────────────────────────────────────────────────── */
/* ⚠ THE SAMPLE'S CELL, WITH A GREY LINE FOR EVERY NAME (placeholder.js skel). Centred on the line box the
   names would have, so an empty court stands exactly where a filled one does. */
.ev .evr-ct .ev-ct-top { align-items: center; height: 12px; }
.ev .evr-ct-skel { height: 7px; border-radius: 4px; }
/* ── The table: a place ahead is a number and grey shapes ──────────────────── */
.ev .evr-anon, .ev .evr-me { cursor: default; }
.ev .ev-p.has-pos .evr-skav { background: color-mix(in srgb, var(--ink) 8%, transparent); }
.ev .evr-skn { height: 10px; }
.ev .evr-sks { height: 8px; margin-top: 2px; }
@media (prefers-contrast: more) {
  .ev .ev-p.has-pos .evr-skav { background: color-mix(in srgb, var(--ink) 16%, transparent); }
}
/* The fine line under the card says what the card cannot: not scanned in, the desk offline, passed over. */
.ev .evr-below { margin-top: 0; }

/* ── The ticket: the code on its plate, then whose it is ──────────────────── */
.ev .evr-ticket { display: grid; justify-items: center; gap: 0; padding: 16px; }
.ev .evr-plate { width: 100%; margin-top: 0; }
/* ⚠ THE CODE'S TEXT WRAPS BETWEEN GROUPS, NEVER INSIDE ONE. A uid printed in fours is read aloud at
   the door four at a time; a group split across two lines is a group read wrong. */
.ev .evr-plate .ev-qr-code {
  max-width: 100%; text-align: center; overflow-wrap: normal; word-break: keep-all;
}
/* The holder is the door check: a person reads it and looks up at a face (DESIGN.md §7), so it is
   the second-largest thing on the card after the code. */
.ev .evr-holder {
  margin: 12px 0 0; font: 600 17px/1.2 var(--font-ui); letter-spacing: -.01em; color: var(--ink);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ev .evr-later { margin-top: 0; }

/* ── The place in line ───────────────────────────────────────────────────── */
/* ⚠ THE STALE-DESK SENTENCE IS A FACT ABOUT THE LINE, NOT A WARNING COLOUR: the same ink as the line
   above it, set apart by a rule so it does not read as part of the position. */
.ev .evr-note {
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule);
}
/* ⚠ THE POSITION IS THE LARGEST THING ON THE PAGE (DESIGN.md §7) — and since 2026-10-05 it is the
   sample's: the queue card's big number, the people ahead (.ev-qcard-n, `.evr-place` only marks it for
   scripts/blank-smoke.mjs). It had a white panel and a display-size "#7" of its own until then. */
.ev .ev-turn .evr-call { margin-top: 16px; }

/* ⚠ LOADING HAS NO WORDS, AND THE MARK WAITS 600 ms (PLAN.md §5). A read that answers quickly never
   shows it; one that does not gets a small ring, not a sentence. The rest state is visible, so a
   document that never animates still shows the ring rather than nothing. */
.ev .evr-spin {
  width: 22px; height: 22px; margin: 18px auto;
  border-radius: 50%; border: 2px solid var(--rule); border-top-color: var(--ink-tertiary);
  opacity: 1;
}
@media (prefers-reduced-motion: no-preference) {
  .ev .evr-spin { animation: evr-wait 0s linear 600ms both, evr-turn 900ms linear 600ms infinite; }
}
@media (prefers-reduced-motion: reduce) {
  .ev .evr-spin { animation: evr-wait 0s linear 600ms both; }
}
@keyframes evr-wait { from { opacity: 0; } to { opacity: 1; } }
@keyframes evr-turn { to { transform: rotate(360deg); } }

/* ── The call: Accept, then the two quieter answers side by side ─────────── */
.ev .evr-ans-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; }
.ev .evr-ans:disabled { opacity: .55; cursor: default; }
.ev .evr-ans-note {
  margin: 10px 0 0; min-height: 1.45em;
  font: 500 13.5px/1.45 var(--font-ui); color: var(--ink-secondary); text-align: center;
}
.ev .evr-ans-note:empty { min-height: 0; margin: 0; }
.ev .evr-call-desk {
  margin: 0; font: 600 15px/1.4 var(--font-ui); color: var(--ink);
}
/* Inside the call screen the ground is navy, so the two text colours come from the on-navy pair —
   the same reason takeover.css restates its own buttons. */
.tko .ev .evr-ans-note { color: var(--on-navy-dim); }
.tko .ev .evr-call-desk { color: var(--on-navy); text-align: center; }
