/* site/app/screens/pager.css — the pager card and its rack of paddles.
 * Depends on tokens.css + ui.css. Load AFTER both.
 *
 * ⚠ EVERY CLASS IN THIS FILE IS `pg-` PREFIXED, WITH NO PARENT SCOPE. cafe.css scopes itself
 *   under `.cafe` because it uses generic names (.chip, .tile, .rail); this file takes the
 *   other route so the pager can be mounted anywhere — the cafe today, a Tickets row or a
 *   staff screen tomorrow — without inheriting a screen's scope. The two must not both own a
 *   name: `.rail`, `.tile` and `.pager-*` belong to cafe.css and are not touched here.
 *
 * ⚠ NOT ONE RAW COLOUR. Every value is a token. This card is read courtside at night, which
 *   is precisely when a hex written here would stop following the palette.
 *
 * ⚠ INK COMES FROM THE --on-navy PAIR, NEVER FROM --ink. `.card.navy` is navy in light mode
 *   ON PURPOSE (ui.css: an identity moment), and --ink is navy in light mode too — so an
 *   --ink label on this card is navy on navy. In dark mode ui.css swaps the card to
 *   --bg-raised, which is still a dark ground, so the same pair is still correct. That is the
 *   whole answer to "legible in both themes": the rack never sits on a pale surface.
 */

/* ── The card ─────────────────────────────────────────────────────────────── */
.pg-card {
  /* One hairline value for the whole card, mixed from the ink rather than guessed, so it
     tracks the card's ground in both themes. */
  --pg-rule: color-mix(in srgb, var(--cream) 16%, transparent);
  cursor: pointer;
}
/* ⚠ A CLOSED PAGER IS QUIETER, NOT GREYED OUT. It is still the receipt for a real drink and
   somebody may be showing it to a runner. Dimming the whole card would make the number — the
   one thing worth showing — the hardest thing on it to read. */
.pg-card.pg-closed .pg-rackwrap { opacity: .55; }

.pg-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pg-paid {
  font: 500 12px/1 var(--font-ui); color: var(--on-navy-dim);
  font-variant-numeric: tabular-nums;
}

/* ── The head ─────────────────────────────────────────────────────────────────
 * ⚠⚠ A GRID, NOT TWO STACKS SIDE BY SIDE. This is carried verbatim from
 *    community.supply/queue's `.tick-head`, and the comment there names the exact bug it
 *    exists to prevent: two independent columns each set their own rhythm, so *"'NO.' floated
 *    a line above 'YOU ARE BEING SERVED' and 'NOW SERVING' landed between the figure and the
 *    sub-label — five items, no two of them on a shared line."*
 *
 *    Three rows, both columns, one row-gap: caption row, figure row, caption row. What is on
 *    the right of a line is always the answer to what is on its left.
 *
 * ⚠ PLACED EXPLICITLY BY grid-area, NEVER BY SOURCE ORDER. The right-hand row-2 cell is
 *   conditional — "Now serving" is dropped the moment the figure becomes a phrase — and under
 *   auto-flow the surviving cells slide up into the wrong rows.
 */
.pg-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto auto;
  column-gap: 14px;
  row-gap: 6px;
  align-items: baseline;
  margin-top: 16px;
}
.pg-head > .pg-lbl      { grid-area: 1 / 1; }
.pg-head > .pg-side.a   { grid-area: 1 / 2; }
.pg-head > .pg-fig      { grid-area: 2 / 1; }
/* ⚠⚠ THE PHRASE — AND ONLY THE PHRASE — SPANS BOTH COLUMNS, and this is safe ONLY because
   the row-2 right-hand cell is omitted whenever it is set (see head() in pager.js). If a
   `.pg-side.b` is ever emitted alongside `.say`, the two grid areas overlap and print on top
   of each other. A grid will not warn you; nothing will. */
.pg-head > .pg-fig.say  { grid-area: 2 / 1 / 3 / 3; }
.pg-head > .pg-side.b   { grid-area: 2 / 2; align-self: start; padding-top: 2px; }
/* ⚠⚠ THE CAPTION SPANS BOTH COLUMNS. Found by measuring in the reference, not by reading: a
   grid's column tracks are shared by every row, so column 2's `auto` track — sized by the
   wider of "No. 247" and "Now serving 244" up on rows 1 and 2 — squeezes column 1 down on
   row 3, where nothing sits beside the caption at all. Held to one column, "Position not
   confirmed" breaks over two lines at 360px and the card grows a line taller for nothing. */
.pg-head > .pg-lbl.under { grid-area: 3 / 1 / 4 / 3; }

.pg-lbl {
  font: 700 10px/1.25 var(--font-ui);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--on-navy-dim);
}

/* ⚠ LABEL AND VALUE ON ONE BASELINE, stacked down the right edge — not a caption with an
   answer beside it. The reference's note is the reason: *"Both are uppercase, 800-weight,
   tracked and muted — set them on one line a few pixels apart and the eye reads a single run
   of type, not a caption and an answer."* The value at 18px is what tells these two apart
   from the card's own captions, which are all label and no number. */
.pg-side {
  display: flex; align-items: baseline; justify-content: flex-end;
  gap: 7px; white-space: nowrap;
}
.pg-side .k {
  font: 700 9px/1 var(--font-ui);
  letter-spacing: .12em; text-transform: uppercase; color: var(--on-navy-dim);
}
.pg-side .v {
  font-family: var(--font-display); font-weight: 600;
  font-size: 18px; line-height: 1; letter-spacing: -.02em;
  color: var(--on-navy); font-variant-numeric: tabular-nums;
}

/* The figure — the one thing this card exists to answer.
 * ⚠⚠ TYPE SCALES WITH WIDTH, NEVER WITH HEIGHT. The reference shipped every display size on
 *    its ticket keyed to `dvh` and Syd caught it immediately: *"the safari and chrome version
 *    is different in terms of look."* `dvh` is whatever height the browser's own toolbar
 *    leaves behind, and that is a different number in Safari, Chrome for iOS and Chrome for
 *    Android ON THE SAME PHONE — so the headline came out a different size in each and the
 *    card read as a different design. Width is identical across browsers on one device. */
.pg-fig {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(34px, 13vw, 56px); line-height: .92;
  letter-spacing: -.04em; color: var(--on-navy);
  font-variant-numeric: tabular-nums;
  /* ⚠ THE 3px IS OPTICAL, NOT STRUCTURAL. The grid's row-gap is equal above and below this
     row; a numeral is not — it has no descender, so with the box cropped to .92 the ink sits
     visibly higher in its box than it does low. The nudge moves the box down and pulls the
     row below back up by the same amount. Re-derive it if the size or line-height moves. */
  margin-top: 3px; margin-bottom: -3px;
}
/* ⚠⚠ WHEN A COUNT STOPS BEING AN ANSWER, THE SAME SLOT HOLDS A PHRASE. The reference's rule,
   in its own words: *"nobody reads '0 people in front of you' and thinks 'my turn' — they
   read a zero."* Here the zero is "0 orders ahead", which reads as an empty bar rather than
   as a drink being made. Same slot, same weight, three registers down so a phrase can hold
   the line a numeral held.
   ⚠ NO OPTICAL NUDGE: a phrase HAS descenders, so its box is already balanced and the same
   3px would tip it the other way. */
.pg-fig.say {
  font-size: clamp(25px, 8.4vw, 32px); line-height: 1.06;
  letter-spacing: -.03em; margin-top: 0; margin-bottom: 0;
}
/* A position nobody could confirm. Muted and a size down, so it reads as absence — which is
   what it is — rather than as a number. */
.pg-fig.unk { color: var(--on-navy-dim); font-size: clamp(30px, 10.6vw, 42px); }

/* ── The rack ─────────────────────────────────────────────────────────────────
 * The line, drawn as paddles standing in a rack. Index 0 is at the bar and sits hard against
 * the left edge; the line runs right and continues off the card.
 *
 * ⚠⚠ THE BOX IS SIZED FOR THE BIGGEST GLYPH IN IT, NOT THE PLAIN ONE, AND THE OLD SUM HERE
 *    WAS WRONG — WHICH IS HOW IT GOT CROPPED AGAIN. It read "44 × 1.14 + a 6px lift is 56;
 *    the box is 62", and that ignores `transform-origin: 50% 96%`. Scaling about a point 96%
 *    of the way DOWN the glyph pushes the top up by 0.96 × 44 × (scale − 1), not by half the
 *    growth. On 2026-09-03 the marked paddle went to 1.22 and a 9px lift on the strength of
 *    that sum, and Syd saw the result immediately: "paddle of queue gets cropped when being
 *    lifted up."
 *
 *    THE SUM THAT IS ACTUALLY TRUE. Clearance above the marked paddle, in px:
 *
 *        H/2 − 22 − 42.24 × (scale − 1) − lift ≥ 0        (42.24 = 0.96 × 44)
 *
 *    At H 74, scale 1.18, lift 5 that is 37 − 22 − 7.60 − 5 = 2.40px of room. Change ANY of
 *    the three and put the numbers back through this line — and check the left inset in
 *    pager.js while you are there, because scale widens the glyph at index 0 as well.
 * ⚠ THE FADE IS ON THE CONTAINER AND ON THE RIGHT EDGE ONLY. A queue has a hard front, and
 *   fading the left would say the line continues into people already served. Dimming each
 *   glyph by distance instead was the reference's first attempt and Syd rejected it: the
 *   fourth paddle was half gone in the middle of the card, where there is room to see it.
 * ⚠ `-webkit-mask-image` as well — Safari still needs the prefix for a mask on a plain
 *   element, and without it the rack runs to a hard cut instead of fading.
 * ⚠ NOT A CONTROL. It is a picture of the queue; the only thing a finger can do here is
 *   nothing, so it takes no pointer events and is announced as an image with the count in
 *   words.
 */
/* ⚠ TWO BOXES, NOT ONE. The wrapper is unmasked and carries the "+N"; only the inner box
   clips and fades. See the note in pager.js on why the reference's single box hides the one
   label that explains itself. */
.pg-rackwrap { position: relative; margin-top: 14px; }
.pg-rack {
  position: relative; width: 100%; height: 74px;
  overflow: hidden; pointer-events: none;
  /* ⚠ THE ONLY RAW HEX IN THIS FILE, AND IT IS NOT A COLOUR. A mask reads the ALPHA channel
     only, so #000 here means "fully opaque" — the paint is irrelevant. A token would be
     wrong twice over: it would follow the theme (changing nothing) and it would invite
     somebody to "fix" the fade by editing a colour. */
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 82%, transparent 100%);
          mask-image: linear-gradient(to right, #000 0, #000 82%, transparent 100%);
  transition: opacity var(--t-menu) var(--ease-out);
}
/* ⚠ THE ORIGIN IS THE HANDLE, NOT THE CENTRE. Both the tilt and the shrink pivot there, so
   every paddle stands on one floor line and the small ones at the back look further away
   rather than floating. Move this to `center` and the rack becomes a row of chips.
   ⚠ THE TRANSITION IS THE LINE MOVING. buildRack() renders the new rack in the OLD positions
   and releases it one frame later; this transition is what turns that into the queue
   shuffling forward. Take it away and the same code becomes a teleport. 420ms: long enough
   to read as movement, short enough that two advances do not queue up behind each other. */
.pg-p {
  position: absolute; left: 9px; top: 50%;
  width: 26px; height: 44px; margin-top: -22px;
  transform-origin: 50% 96%;
  /* ⚠ MEASURED ON THE NAVY CARD, NOT COPIED FROM THE REFERENCE. The reference's deck runs at
     .88 for a waiting card and .38 for filler, but those are dark cards on cream paper. Cream
     on navy at the same numbers is a wall of near-white. The first pass went the other way
     and pitched filler at .16 — 🐛 which made the tail vanish about two thirds across, so the
     rack visibly ENDED in the middle of the card instead of continuing under the fade, which
     is the exact complaint the mask exists to answer (*"the fade out should happen towards
     the margins of the ticket already"*). These three are the values where the line reaches
     the margin and the filler still reads as filler. */
  /* ⚠ RAISED FROM .48 (and filler from .24) ON 2026-09-03. Syd: the queue page "is all messed
     up". On the deployed card the waiting paddles were legible in a screenshot only if you
     knew to look for them — a picture of the line that reads as two shapes and some noise is
     not reporting anything. These are the values where every paddle in the rack is visible on
     navy while the three states stay clearly ranked. */
  opacity: .62;
  will-change: transform;
  transition: transform 420ms var(--ease-drawer), opacity 380ms var(--ease-out);
}
.pg-p svg { width: 100%; height: 100%; display: block; }
.pg-blade { fill: var(--on-navy); }

/* Three values, no ambiguity — the same discipline as the reference's deck (*"Pale = waiting,
   mid-grey = being served, red = you"*), translated: dim = waiting, solid = at the bar,
   chartreuse = yours. Filler is fainter than any of them and carries no ball, because it is
   the rack and not a person. A picture that invented four customers to look busy would be
   lying about the one number the card exists to report. */
.pg-first { opacity: 1; }
.pg-pad   { opacity: .34; }

/* ⚠⚠ COLOUR ALONE DOES NOT CARRY WHICH PADDLE IS YOURS, and that is not a preference. This
   app is read in sunlight by whoever walks in, and chartreuse against cream is exactly the
   pair a red-green colour deficiency flattens. So the marked paddle is ALSO the only one that
   stands upright (pager.js sets rotate(0) for it), the only one lifted clear of the line, and
   the largest thing in the rack.
   ⚠ THE BALL IS GONE — Syd, 2026-09-03. That is one shape signal fewer, so the other three
     were strengthened to compensate: scale 1.14 → 1.22 and lift 6px → 9px, both in pager.js.
     A dark ring is added here as a fourth, which costs nothing and survives greyscale. */
.pg-me { opacity: 1; }
/* ⚠ FILL ONLY. A stroke was added here on 2026-09-03 as a fourth non-colour signal and it
   broke the drawing: the blade and the handle are two overlapping rects, so stroking each of
   them draws an outline THROUGH the join and the paddle reads as a blade floating above a
   separate stick. Syd: "paddle shape is separated." An outline would have to be one traced
   path around the silhouette, and pager.js already explains at length why this glyph is two
   rectangles and not a traced path. Posture, lift and size carry it. */
.pg-me .pg-blade { fill: var(--live); }

/* Served, and falling out of the rack. The transform is composed inline by pager.js — see the
   note there on why it cannot live in this rule. */
.pg-gone { opacity: 0; }

/* What is not drawn, stated. ⚠ Past the cap the picture stops carrying information and the
   count in words is the honest answer; this marks the difference rather than hiding it. */
.pg-more {
  position: absolute; right: 0; top: 0;
  font: 700 10px/1 var(--font-ui); letter-spacing: .02em;
  color: var(--on-navy-dim);
}

/* The rack rising in, once. ⚠ A transition with @starting-style, not a keyframe: a keyframe
   restarts from zero if anything re-renders mid-flight, and this card is re-rendered by its
   host. The stagger is per-glyph and small — 22ms is a ripple, 80ms is a queue forming. */
.pg-rack-in .pg-p { transition-delay: calc(var(--pg-i, 0) * 22ms); }
@starting-style { .pg-rack-in .pg-p { opacity: 0; } }

/* ── Destination ──────────────────────────────────────────────────────────────
 * The one fact the head does not always carry, on one baseline, in as few words as the venue
 * allows: a court number, or the counter. ⚠ It is never a free-text court — pager.js takes
 * whatever the booking said. Someone who can type "6" while standing on court 5 is how a
 * flat white ends up in a stranger's hand.
 */
.pg-foot {
  display: flex; align-items: baseline; gap: 9px;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--pg-rule);
}
.pg-foot .k {
  font: 700 9px/1 var(--font-ui);
  letter-spacing: .12em; text-transform: uppercase; color: var(--on-navy-dim);
}
.pg-foot .v {
  font-family: var(--font-display); font-weight: 600;
  font-size: 22px; line-height: 1; letter-spacing: -.02em; color: var(--on-navy);
}

/* ── The rail ─────────────────────────────────────────────────────────────────
 * Four stops through to collected. ⚠ A colour with no word beside it says nothing — the fill
 * alone would tell a reader they are three-quarters of the way through something unnamed.
 */
.pg-rail { margin-top: 18px; }
.pg-track {
  height: 3px; border-radius: 2px; overflow: hidden;
  background: color-mix(in srgb, var(--cream) 18%, transparent);
}
/* ⚠ CHARTREUSE, NOT ORANGE. --action is the colour of a thing you press and there is nothing
   to press on a pager; --live is the colour of a thing happening now, which is what this is.
   tokens.css is explicit that the two are not interchangeable. */
.pg-fill {
  display: block; width: 100%; height: 100%;
  background: var(--live); transform-origin: left center;
  transition: transform 420ms var(--ease-out);
}
/* ⚠ THE CELLS ARE NOT EQUAL WIDTH, AND THAT WAS FOUND BY LOOKING AT THE DEPLOYED CARD.
   `flex:1` split the 275px rail into four 68.8px cells. Three of the stops fit; "ON THE WAY"
   needs about 78px at this size and tracking, so it — and only it — wrapped onto a second
   line. The rail then read as three labels on one baseline and a fourth hanging below them,
   which looks like a broken row rather than a fourth stop. Natural widths with
   space-between: the four labels total ~212px in 275px, so every one fits on one line and
   the spare 63px goes into the gaps where it is invisible. `nowrap` makes the no-wrap rule
   explicit instead of relying on the box happening to be wide enough. */
.pg-stops { display: flex; justify-content: space-between; gap: 6px; margin-top: 9px; }
.pg-stops span {
  flex: 0 1 auto; min-width: 0; text-align: center; white-space: nowrap;
  font: 600 10px/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase;
  color: var(--on-navy); opacity: .42;
  transition: opacity var(--t-menu) var(--ease-out);
}
.pg-stops span:first-child { text-align: left; }
.pg-stops span:last-child  { text-align: right; }
.pg-stops span.on { opacity: 1; }

/* ── The preview control ──────────────────────────────────────────────────────
 * ⚠ LABELLED AS WHAT IT IS. There is nothing for a player to press on a real pager; this
 *   exists so the state machine can be walked through in a review, and it says so rather
 *   than passing itself off as part of the app. It goes out with the prototype LINE model in
 *   pager.js — the two are the same piece of scaffolding.
 */
.pg-pv {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0 16px 16px; padding: 10px 14px;
  border: 1px dashed var(--rule); border-radius: var(--r-md);
}
.pg-pv-lab {
  font: 600 10px/1 var(--font-ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.pg-pv-btn {
  min-height: var(--tap-min); padding: 0 16px;
  border: 1px solid var(--rule); border-radius: var(--r-full);
  background: var(--bg-raised); color: var(--ink);
  font: 600 13px/1 var(--font-ui); letter-spacing: .02em;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
  transition: transform var(--t-press) var(--ease-out);
}

/* ── Reduced motion ───────────────────────────────────────────────────────────
 * ⚠ A GENTLER EQUIVALENT, NOT NOTHING. The queue advancing still has to be visible or the
 *   card silently rearranges itself; what goes is the travel and the stagger, not the change
 *   of state. tokens.css already clamps every transition to 160ms globally — these two rules
 *   remove the parts that are movement for its own sake.
 */
@media (prefers-reduced-motion: reduce) {
  .pg-rack-in .pg-p { transition-delay: 0ms; }
  @starting-style { .pg-rack-in .pg-p { opacity: 0; } }
}

/* ⚠ THE RACK IS THE FIRST THING TO GO IN HIGH CONTRAST, NOT THE LAST. A mask fade and six
   opacity steps are the two things a contrast-raised display cannot render honestly: the
   filler at .16 becomes either invisible or as solid as a real person. Three values only,
   and the mask off — the count above is still the true answer. */
@media (prefers-contrast: more) {
  .pg-rack { -webkit-mask-image: none; mask-image: none; }
  .pg-p    { opacity: 1; }
  .pg-pad  { opacity: .3; }
}
