/* site/app/screens/players.css — Players.
 * Depends on tokens.css, ui.css and shell.css. Reuses .list .row .tag .btn .avatar .empty
 * .sec .sec-head .segmented .chev .screen-head .pane-scroll — nothing here reinvents one.
 *
 * ⚠ EVERY VALUE IS A TOKEN. A raw hex in this file is a bug, not a shortcut: it does not
 *   follow the palette into dark mode, and that exact mistake is the direct cause of two
 *   shipped defects on this project. There is no sanctioned raw colour in this file.
 *
 * ⚠ EVERY NAME IS PREFIXED `pl-`. This project has already shipped two stylesheets that both
 *   invented `.pcard`, and whichever loaded second broke the other silently. Load order
 *   decides nothing here because no two files can collide.
 */

/* ⚠ THE SCREEN IS ITS OWN SIZE CONTAINER. The wide-screen rule at the bottom of this file
   must ask the APP FRAME how wide it is, not the browser window — the app is drawn inside a
   390px phone frame, so a plain media query fires on a tablet while the frame it is sizing
   is still phone-width. A container query asks the frame, and a container has to be an
   element, which is why render() stamps `.pl` on the pane. */
/* ⚠⚠ THE CONTAINER IS THE SCROLLER, NOT THE PANE, AND THE REASON IS `position: fixed`. In
   Chromium before 127 (and the Android emulator's Chrome 124 is exactly that), `container-type`
   still carried layout containment — which makes the element the containing block for its
   FIXED descendants. The commit dock and the sheets are fixed children of the pane, and inside a
   contained pane they anchored to the pane's box instead of the viewport: on a phone that box
   runs 40–56px under the browser's bar, and the scrim landed under the tab bar. Measured on
   the emulator before this moved: scrim [0,12,412,842] against a viewport 786 tall. Nothing
   fixed lives inside the scroller, so the query container can be there without a cost. */
.pl > .pane-scroll { container: pl-screen / inline-size; }

/* ═══ 1 · THE PREVIEW BAR — not part of the app ══════════════════════════════ */

.pl-preview {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 var(--gutter) 18px; padding: 9px 12px;
  background: var(--bg-sunken); border: 1px dashed var(--rule); border-radius: var(--r-md);
}
.pl-preview-lab {
  font: 600 11px/1 var(--font-ui); letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.pl-preview-group { display: flex; gap: 6px; flex: 1; flex-wrap: wrap; }
.pl-preview-btn {
  position: relative;
  border: 1px solid var(--rule); background: var(--bg-raised); color: var(--ink-secondary);
  border-radius: var(--r-full); padding: 7px 12px; cursor: pointer;
  font: 500 12px/1 var(--font-ui);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
/* 28px tall on screen, 44 under a thumb — the tokens.css overlay pattern. */
.pl-preview-btn::after {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 100%; height: var(--tap-min);
}
.pl-preview-btn[aria-pressed="true"] {
  background: var(--selected-soft-bg); color: var(--selected-soft-ink);
  border-color: var(--selected-edge); font-weight: 600;
}

/* ═══ 2 · SECTION FURNITURE ══════════════════════════════════════════════════ */

.pl-sec { margin: 0 0 24px; }
.pl-sec .list { margin: 0 var(--gutter); }
.pl-sec .empty { margin: 0 var(--gutter); }

/* The snapshot's period, sitting opposite the section title. `leaderboard_snapshot` is
   immutable per period, so naming the period is naming which board this is. */
.pl-period {
  font: 600 12px/1 var(--font-ui); letter-spacing: .04em;
  color: var(--ink-tertiary); flex: none;
}
.pl-count {
  font: 600 12px/1 var(--font-ui); color: var(--ink-tertiary);
  font-variant-numeric: tabular-nums; flex: none;
}
.pl-caption {
  margin: 0 var(--gutter) 12px;
  font: 400 12px/1.4 var(--font-ui); color: var(--ink-tertiary);
}

/* Thumb width is a calc of the track, so it never needs measuring: 2px padding each side
   plus one 2px gap = 6px of furniture, and the rest splits two ways. */
.pl-seg { margin: 0 var(--gutter) 12px; }
.pl-seg .thumb { width: calc((100% - 6px) / 2); }
/* ⚠ 30px PAINTED. ui.css's .segmented button is 8px of padding around a 14px line, and its
   own comment says the invisible overlay carries the 44px floor — this restates the floor on
   the control itself so the segment is never short even if that overlay is ever clipped by
   an ancestor. Same line is in cafe.css and history's filter for the same reason. */
.pl-seg button { min-height: var(--tap-min); }
.pl-seg button[aria-pressed="true"] { font-weight: 600; color: var(--ink); }

/* ═══ 2a · YOUR POSITION ═════════════════════════════════════ */

/* The card is ui.css's .card; everything here is what it holds. */
.pl-you { padding: 16px 16px 14px; }
.pl-you-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-bottom: 14px;
}
/* ⚠ SMALLER THAN A SECTION HEADING, ON PURPOSE. This is an h2 for the document outline, but it
   labels a card rather than a section of the screen — at .sec-head's size it would compete with
   "Facility ranking" directly below it and the page would read as having two titles. */
.pl-you-h {
  margin: 0; font: 600 12px/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.pl-you-head .tag { flex: none; }

/* The club sheet's stat grid, without its own surface: inside a card, the shadow and the radius
   would be a card drawn on a card. The 1px gap on --rule keeps the hairlines between cells. */
.pl-you-stats { margin: 0 0 14px; box-shadow: none; border-radius: var(--r-md); }
.pl-you-stats .pl-stat { padding: 12px 6px; background: var(--bg-sunken); }
/* The movement cell is the board's, dropped into a stat: it arrives as an inline-flex of an arrow
   and a word, and only the size changes. */
.pl-you-stats .pl-move { font-size: 13px; }
.pl-you-stats .pl-move-new { font-size: 10px; }

.pl-you-btn { font-size: 15px; padding: 12px 18px; }
/* The arrow follows the label rather than leading it: the control is "show me", and the direction
   is what happens next. */
.pl-you-btn .pl-btn-ico { order: 2; }

/* ═══ 2b · THE BREAK IN THE BOARD ═══════════════════════════════ */

/* ⚠ IT IS NOT A ROW AND MUST NOT LOOK LIKE ONE. A break that wore the list's ground and border
   would read as a player called "37 more". It sits BETWEEN two lists, on the page, with the
   count as a plain label. */
.pl-break {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 10px var(--gutter); padding: 2px 0;
  font: 500 12px/1 var(--font-ui); letter-spacing: .02em; color: var(--ink-tertiary);
}
.pl-break-dots { font-size: 15px; line-height: 1; letter-spacing: .1em; }

/* ═══ 3 · ROWS ══════════════════════════════════════════════════════════════ */

/* ⚠ THE ROW IS A <button>, SO IT HAS TO BE UN-BUTTONED. A <button> inherits none of the
   page's font, centres its text, draws a border and paints its own background — all four of
   which make it look nothing like the .row beside it that happens to be a <div>. It is a
   button because the whole row is the tap target: the chevron is 13px of decoration and
   ui.css keeps it pointer-transparent, so a thumb landing on the arrow — which is exactly
   where people aim — must still hit the row. */
.pl-row {
  width: 100%; text-align: left; border: 0; cursor: pointer;
  background: var(--bg-raised); color: inherit; font: inherit;
  border-bottom: 1px solid var(--rule);
  -webkit-tap-highlight-color: transparent;
}
.pl-row:last-child { border-bottom: 0; }
.pl-chev { color: var(--ink-tertiary); }

/* ⚠ THE NAME SHRINKS AND THE HANDLE DOES NOT. `min-width: 0` on the line lets the ellipsis on
   .title fire at all; `flex: none` on the handle decides WHICH of the two gives way. A cut display
   name is still that person; a cut @username is a different account. */
.pl-name-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.pl-name-line .title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ⚠ LIGHTER AND SMALLER, WHICH IS THE WHOLE INSTRUCTION. 13.5px against the name's 16 and
   tertiary ink against full — two channels, so it still reads as secondary in greyscale and in
   sun. Tabular figures because handles like @jr.ong sit above numbers in a column. */
/* ⚠⚠ IT SHRINKS, AND THREE TIMES FASTER THAN THE NAME DOES. `flex: none` meant a 20-character
   @username — the cap in site/functions/_handles.js, against a longest fixture of eleven — took its
   full width and the NAME absorbed the whole shortfall. Syd caught it on the queue: "longer
   @usernames 15 characters longs definately wont be seen." Weighted 3 to the name's 1, the handle
   gives up about three quarters of any deficit, so a tight row trims the identifier and keeps the
   label. The name is what the row is about; the profile has the handle in full. */
.pl-handle {
  flex: 0 3 auto; min-width: 0;
  font: 400 13px/1.2 var(--font-ui); letter-spacing: -.006em;
  color: var(--ink-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pl-board-row.is-me .pl-handle,
.pl-member-row.is-me .pl-handle { color: var(--selected-soft-ink); opacity: .75; }
/* One line each, ellipsised. ⚠ Not `white-space: normal`: a wrapped @username turns a 60px
   list row into a 78px one for that player alone, and a board whose rows are different
   heights stops reading as a ranked column. */
.pl-row .meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-board-row .meta { font-size: 12.5px; }

/* The club as a row's second line: the badge, then the club's name in full. ⚠ THE BADGE KEEPS ITS
   14px HERE rather than growing to meet the type — it is a mark beside a word, not a second avatar,
   and the row already has a 34px disc doing that job. */
.pl-club-line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.pl-club-name-sm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-board-row.is-me .pl-club-line .clb-logo {
  background: color-mix(in srgb, var(--selected-soft-ink) 22%, transparent);
  color: var(--selected-soft-ink);
}

/* ═══ 4 · THE BOARD ═════════════════════════════════════════════════════════ */

/* ⚠ TIGHTER THAN A PLAIN .row, AND IT IS MEASURED. A board row carries six things where an
   ordinary row carries three: rank, avatar, name, @username + match count, rating, movement,
   chevron. At .row's 16px padding and 12px gaps that left about 140px for the name line on a
   390px phone, and "@ton.alvarez · 198 matches" — a perfectly ordinary handle with a dot in
   it — was cut to "198 m…". Four pixels off each gap and each side of the padding is
   invisible and buys back most of a word. */
.pl-board-row { gap: 5px; padding-left: 10px; padding-right: 8px; align-items: center; }
/* ⚠⚠ AND THE CLUB MARK TOOK 44 OF THOSE PIXELS BACK, SO THE ROW GAVE UP EIGHTEEN MORE.
   Measured on a 375px phone the moment the mark landed: the name box fell to 163px, "Dana
   Villanueva" wanted 123px and got 114, and row 5's "@ton.alvarez · 198 matches" was clipped at
   149px against 162 — which is the EXACT defect the paragraph above was written to fix, re-made
   by a 44px badge. Four pixels came off each gap and each side of the padding again, and the
   avatar went from 40 to 34: a person's disc and a club's badge in one row is two tiles, and the
   person's does not need to be the largest thing on it when their name is beside it. Re-measured
   after: name box 181px, nothing on the board clipped.
   ⚠ 34px IS STILL OVER THE 24px THE AVATAR NEEDS TO CARRY TWO LETTERS, and the row's own tap
     target is 44px tall regardless — the disc is not the control, the row is. */
/* ⚠ 30px NOW, DOWN FROM 34, AND THE @HANDLE IS WHAT BOUGHT IT. The name line carries the handle
   since 2026-09-16, which is ~56px of fixed width in front of a name that has to ellipsise; every
   pixel back is a letter. The disc is two initials of a name written beside it in full — it is the
   least informative thing on the row and so it is the first to give. */
.pl-board-row .avatar { width: 30px; height: 30px; font-size: 11.5px; }


/* ⚠ FIXED WIDTH AND TABULAR, so #1 and #128 do not move the column of names beside them as the
   numbers grow a digit. 1.75em fits three digits at this weight; the board is capped at 186.
   ⚠⚠ CENTRED, NOT RIGHT-ALIGNED, AND THE MEASUREMENT IS WHY. Right-aligned in a column sized for
     three digits, a single digit sat 22px from the row's content edge and 5px from the avatar —
     all the air on one side, the number jammed against the disc and reading as part of it rather
     than as its own column. Measured at 375px: the "1" glyph ran 48→56 in a box of 26→56 with the
     avatar at 61. Centred it sits 37→45, with about eleven pixels of its own either side and a
     wider gap to the avatar than inside the column, which is the order those two spaces should be
     in.
   ⚠ IT ALSO MATCHES THE QUEUE, whose place column (event.css's .ev-p-pos) has been centred since
     it was built. Two lists of people numbered down the left, numbered the same way. */
.pl-rank {
  flex: none; width: 1.75em; text-align: center;
  font-family: var(--font-display); font-weight: 600;
  font-size: 17px; line-height: 1; letter-spacing: -.015em;
  color: var(--ink-secondary); font-variant-numeric: tabular-nums;
}

.pl-right {
  flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
}
.pl-rating {
  font-family: var(--font-display); font-weight: 600;
  font-size: 17px; line-height: 1; letter-spacing: -.02em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* The provisional row's third line: the standing word, then the floor it is short of. */
.pl-standing { display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.pl-standing .tag { flex: none; }
.pl-floor { font: 400 11.5px/1.2 var(--font-ui); color: var(--ink-tertiary); }
/* ⚠ The tinted "me" row repaints its own ink, and --ink-tertiary would vanish into it. */
.pl-board-row.is-me .pl-floor { color: var(--selected-soft-ink); opacity: .82; }
/* Three lines, so the row aligns from the top rather than centring the avatar against a
   block that is now taller than it. */
.pl-board-row.is-prov { align-items: flex-start; }
.pl-board-row.is-prov .pl-rank,
.pl-board-row.is-prov .avatar,
.pl-board-row.is-prov .pl-chev { margin-top: 2px; }

/* ── Movement ─────────────────────────────────────────────────────────────── */
/* ⚠ NOT --ok AND NOT --danger, AND THIS IS THE RULE, NOT A PREFERENCE.
   --ok means "this worked" — a paid ticket, a landed save. Climbing a leaderboard is not a
   success and dropping is not a failure; green-and-red turns a neutral fact into a verdict on
   a person, and it spends the one colour the app reserves for "the write went through".
   --chartreuse is out for the same kind of reason (DESIGN.md §2: energy, never status).
   So direction is carried by THE WORD ("Up 3" / "Down 2" / "Even" / "New") and the arrow,
   with ink weight as a third, quietest channel. Read the board in greyscale: it still reads. */
.pl-move {
  display: inline-flex; align-items: center; gap: 3px;
  font: 500 11.5px/1 var(--font-ui); letter-spacing: .005em;
  color: var(--ink-tertiary); white-space: nowrap;
}
.pl-move.is-up { color: var(--ink); font-weight: 600; }
.pl-move-ico { flex: none; display: block; }
/* A first appearance is not a movement of zero, so it does not wear the movement's shape at
   all — it is a neutral tag, which is what the schema's NULL rank_change actually means. */
.pl-move-new { padding: 4px 7px; font-size: 10px; letter-spacing: .06em; }

/* ── The viewer's own row ─────────────────────────────────────────────────── */
/* ⚠ A TINT *AND* THE WORD "Me". A tint alone is the first thing to disappear in direct sun on
   a court, which is where this screen is read; the tag is what survives it. */
.pl-board-row.is-me { background: var(--selected-soft-bg); }
.pl-board-row.is-me .title,
.pl-board-row.is-me .pl-rank,
.pl-board-row.is-me .pl-rating { color: var(--selected-soft-ink); }
.pl-board-row.is-me .meta { color: var(--selected-soft-ink); opacity: .78; }
.pl-board-row.is-me .pl-move { color: var(--selected-soft-ink); }
/* ⚠ 5px OF SIDE PADDING, DOWN FROM 7 IN TWO STEPS, AND BOTH WERE PAID TO THE CLUB MARK. The row
   this tag sits on is the one about the reader, and it is the only row on the board carrying the
   mark, a tag AND a name. Measured at 375px: with 6px "Syd Tanchan" was still 0.9px short, which
   is an ellipsis on your own name for the sake of two pixels of pill. */
.pl-me {
  flex: none; padding: 3px 5px; font-size: 10px; letter-spacing: .06em;
  background: var(--selected-bg); color: var(--selected-ink);
}

/* The pinned row is a separate list, set apart from the board above it — the viewer is not
   in that slice, and running the two together would read as position 13. */
.pl-pin { margin-top: 10px; }
.pl-pin::before {
  content: ""; display: block; height: 1px; margin: 0 var(--gutter) 10px; background: var(--rule);
}

/* ═══ 5 · EMPTY STATES ══════════════════════════════════════════════════════ */

/* ⚠ A LABEL AND A CONTROL, NOT A CONSOLATION. shell.css's .empty centres a single line of
   tertiary ink; all this adds is room for the one button that changes the state. */
.pl-empty { display: grid; justify-items: center; gap: 14px; }
.pl-empty-btn { font-size: 15px; padding: 11px 18px; }
.pl-btn-ico { flex: none; display: block; }

/* ═══ 6 · SEARCH ════════════════════════════════════════════════════════════ */

/* ⚠⚠ 36px, DOWN FROM 50, AND FULLY ROUNDED. Syd, 2026-09-16: "make it look more aesthetically
   pleasing /apple-design (not too fat)". A 50px boxy field directly under a display-size title is
   the heaviest thing on the screen; iOS's own search field is a short capsule on a sunken fill with
   no border at all, and that is what this is now. The 44px tap floor is kept by the ::after overlay
   below rather than by making the painted control 44 tall — the same trick tokens.css uses for the
   segmented control and the preview pills.
   ⚠ THE BORDER IS GONE AND THE FILL DOES THE WORK. A 1px --rule around a capsule this short reads
   as a pill-shaped button; the fill alone reads as a field. The focus ring still draws. */
.pl-field {
  position: relative;
  display: flex; align-items: center; gap: 7px;
  margin: 0 var(--gutter); padding: 0 12px; min-height: 36px;
  background: var(--bg-sunken); border: 0; border-radius: var(--r-full);
}
.pl-field::after {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%); height: var(--tap-min); pointer-events: none;
}
/* The search section sits directly under the screen title, so it carries less air than a section. */
.pl-search { margin-bottom: 18px; }
.pl-search .pl-out:not(:empty) { margin-top: 12px; }

/* The clear control: only rendered when there is something in the field. */
.pl-clear {
  flex: none; display: grid; place-items: center;
  width: 20px; height: 20px; padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  background: var(--steel-40); color: var(--bg-raised);
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.pl-clear-ico { display: block; }
.pl-clear[hidden] { display: none; }
/* ⚠ focus-within, NOT :focus ON THE INPUT. The ring belongs to the box the person sees; put
   it on the input and it draws inside the box — a rounded rectangle inset in another one.
   ⚠ AN OUTER RING, NOT A BORDER, now that the field has none: a border would change the control's
   height on focus and shift everything under it by two pixels. */
.pl-field:focus-within {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 22%, transparent);
}
.pl-search-ico { flex: none; color: var(--ink-tertiary); display: block; }
.pl-at { flex: none; font: 500 16px/1 var(--font-ui); color: var(--ink-tertiary); }

/* ⚠ 16px MINIMUM, AND IT IS NOT A TASTE CALL. Safari zooms the whole page in when a focused
   input's text is under 16px, and it does not zoom back out — the player is left on a
   sideways-scrolling page wondering what they broke. */
.pl-input {
  flex: 1; min-width: 0; width: 100%;
  border: 0; background: transparent; outline: none; padding: 8px 0;
  font: 400 16px/1.35 var(--font-ui); letter-spacing: -.006em; color: var(--ink);
}
.pl-input::placeholder { color: var(--ink-tertiary); }

/* Printed on focus and while there is input — players.js says why it is not there at rest. */
.pl-rules {
  margin: 8px var(--gutter) 0; max-width: 44ch;
  font: 400 11.5px/1.5 var(--font-ui); color: var(--ink-tertiary);
}
.pl-rules[hidden] { display: none; }

.pl-out { margin-top: 12px; }
.pl-out .list { margin: 0 var(--gutter); }

/* ⚠ THE REFUSAL CARRIES A WORD AS WELL AS A POSITION ON THE SCREEN. "Refused" / "Too many" /
   "No match" / "Keep typing" — the tag is the state, the line beside it is what to do. An
   empty result area with nothing in it reads as a broken search box. */
.pl-refuse {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0 var(--gutter); padding: 13px 15px;
  background: var(--bg-sunken); border-radius: var(--r-md);
}
.pl-refuse .tag { flex: none; }
.pl-refuse p {
  margin: 0; flex: 1; min-width: 0;
  font: 400 13px/1.45 var(--font-ui); color: var(--ink-tertiary);
}

/* ═══ 6a · CLUBS ════════════════════════════════════════════════════════════ */

/* ⚠ THE MARK ITSELF IS NOT HERE. `.clb-mark`, `.clb-logo` and `.clb-tag` are in app/clubs.css,
   which loads with the shell, because the same mark is drawn by five screens. What is below is
   this tab's furniture: a club board row, the club section, the club sheet and its forms. */

/* ── A club on the board ──────────────────────────────────────────────────── */

/* The club row is the player row's geometry with a badge where the avatar was, so the two
   boards do not step sideways when the segment is switched. */
.pl-club-row .pl-rating { letter-spacing: -.03em; }
/* ⚠ THE CLUB'S BADGE IS THE AVATAR'S SIZE, NOT ITS OWN. .clb-logo.is-lg is 40px, which is what
   an avatar used to be on this row; both boards are the same list with one segment between them,
   so a 40px badge against a 34px disc would make the rows step sideways on every switch. */
.pl-club-row .clb-logo { width: 34px; height: 34px; border-radius: 10px; font-size: 13px; }
.pl-club-avg {
  font: 500 11.5px/1 var(--font-ui); color: var(--ink-tertiary);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pl-club-row.is-me .pl-club-avg { color: var(--selected-soft-ink); opacity: .8; }

/* "Full" and "Owner" — the default tag, at the size .pl-me is, so a name line carrying two of
   them does not change height. ⚠ NOT --attn: a full club is a fact, not something wrong. */
.pl-full {
  flex: none; padding: 3px 6px; font-size: 10px; letter-spacing: .06em;
}

/* ── The section ──────────────────────────────────────────────────────────── */

/* A heading inside a section, for the groups that hang under the club card. Smaller than the
   section's own h2 so the hierarchy is visible without a second rule. */
.pl-sub-head {
  margin: 18px var(--gutter) 8px;
  font: 600 12px/1 var(--font-ui); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-tertiary);
}

/* Two controls where the empty state normally has one. ⚠ They wrap rather than shrink: at
   320px "Create a club" and "Browse clubs" side by side leave 8 characters each. */
.pl-empty-pair { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }

.pl-wide-btn { display: flex; width: calc(100% - 2 * var(--gutter)); margin: 10px var(--gutter) 0; font-size: 15px; }

/* ── A row that carries its own controls ──────────────────────────────────── */

/* ⚠ NOT A BUTTON WITH BUTTONS IN IT — players.js says why. It is a <div>, so it needs none of
   the un-buttoning .pl-row does, but it does need the same border and ground. */
/* ⚠⚠ THE CONTROLS GO ON THEIR OWN LINE, AND THAT IS MEASURED. "Approve" and "Decline" side by
   side are 200px; a request row is 343px on a 375px phone, less a 40px avatar and its gaps, so
   one line left about 90px for a name — "Ria Navarro" wrapped onto two lines and "@ria ·
   provisional" was cut to "@ria · prov…". A flex basis of 60% on the person makes the wrap
   deterministic rather than dependent on how long a name happens to be: the two buttons always
   drop to a second line and right-align under it, on every width and every name. */
.pl-invite-row { background: var(--bg-raised); flex-wrap: wrap; row-gap: 10px; }
.pl-invite-row .grow { flex: 1 1 60%; }
.pl-invite-row .title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-row-acts { display: flex; align-items: center; gap: 2px; flex: none; margin-left: auto; }
.pl-mini-btn {
  min-height: 34px; padding: 9px 14px; font-size: 14px; letter-spacing: -.006em;
}
/* 34px painted, 44px under a thumb — the tokens.css overlay pattern, restated because these
   two sit side by side in a row that is already tight. */
.pl-mini-btn, .pl-mini-link { position: relative; }
.pl-mini-btn::after, .pl-mini-link::after {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%); height: var(--tap-min);
}
.pl-mini-link { min-height: 34px; padding: 9px 10px; font-size: 14px; }

/* ── Links ────────────────────────────────────────────────────────────────── */

/* The quiet, full-width control under a primary one. Deliberately the same shape as
   profile.css's .pf-link and deliberately not that class: this screen must not depend on
   another screen's stylesheet for a control of its own. */
.pl-link {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-height: var(--tap-min); padding: 10px 14px; width: 100%; margin-top: 4px;
  border: 0; background: transparent; color: var(--ink-secondary); border-radius: var(--r-full);
  font: 600 15px/1 var(--font-ui); letter-spacing: -.008em; cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
[data-ready] .pl-link { transition: transform var(--t-press) var(--ease-out); }
.pl-link-danger { color: var(--danger); }
.pl-mini-link { width: auto; margin-top: 0; color: var(--ink-secondary); }

/* ── The sync line ────────────────────────────────────────────────────────── */

/* ⚠ A LINE, NOT A FLOATING PILL, AND THE REASON IS AT THE TOP OF THIS FILE. profile.css's
   .pf-pill is `position: fixed`, and a fixed element inside this screen's query container
   anchors to the container's box rather than to the viewport on Chromium before 127 — which is
   the Android emulator this project reviews on. A line where the change happened costs nothing
   and cannot land under the tab bar.
   ⚠ EVERY STATE IS A WORD (DESIGN.md §2); the colours below only agree with a word players.js
   already wrote into the element. "Waiting for signal" must never be the success colour. */
.pl-sync {
  margin: 12px var(--gutter) 0;
  font: 600 13px/1.4 var(--font-ui); letter-spacing: -.004em;
  color: var(--ink-tertiary);
}
.pl-sync[data-state="saved"]  { color: var(--ok); }
.pl-sync[data-state="queued"] { color: var(--attn); }

/* ═══ 6b · THE CLUB SHEET ═══════════════════════════════════════════════════ */

.pl-club { padding: 4px 0 calc(24px + var(--safe-bottom)); }

.pl-club-id { display: flex; align-items: flex-start; gap: 14px; margin: 0 var(--gutter) 18px; }
.pl-club-who { flex: 1; min-width: 0; }
.pl-club-name {
  margin: 2px 0 0; font-family: var(--font-display); font-weight: 600;
  font-size: 26px; line-height: 1.15; letter-spacing: -.014em; color: var(--ink);
  /* One line is not forced: a club may legitimately be called "Third Shot Society" and cutting
     it would make the sheet the one place its own name is wrong. */
  overflow-wrap: anywhere;
}
.pl-club-sub {
  margin-top: 5px;
  font: 600 12.5px/1.3 var(--font-ui); letter-spacing: .05em;
  color: var(--ink-tertiary); text-transform: uppercase;
}
.pl-club-blurb {
  margin: 8px 0 0; max-width: 40ch;
  font: 400 14px/1.45 var(--font-ui); color: var(--ink-secondary);
}

/* ── The four numbers ─────────────────────────────────────────────────────── */

/* ⚠ FOUR ACROSS, AND THEY WRAP TO TWO RATHER THAN SHRINKING. "Collective" over "27,136" needs
   about 78px; four of those plus the gaps is 336px, which fits a 390px phone and does not fit
   a 320px one. auto-fit with a floor is what turns the fourth cell onto a second line instead
   of squeezing "Collective" to "Collect…". */
.pl-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(72px, 1fr));
  gap: 1px; margin: 0 var(--gutter) 20px;
  background: var(--rule); border-radius: var(--r-lg); overflow: hidden;
  box-shadow: var(--shadow-1);
}
.pl-stat {
  display: grid; justify-items: center; gap: 4px;
  padding: 14px 6px; background: var(--bg-raised);
}
.pl-stat-v {
  font-family: var(--font-display); font-weight: 600;
  font-size: 19px; line-height: 1; letter-spacing: -.02em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.pl-stat-k {
  font: 500 11px/1 var(--font-ui); letter-spacing: .04em; color: var(--ink-tertiary);
  text-align: center;
}

/* ── The action block ─────────────────────────────────────────────────────── */

.pl-club-acts { margin: 0 var(--gutter) 6px; }
.pl-club-refuse { margin: 0; }
.pl-btn-pair { display: flex; gap: 10px; }
.pl-half-btn { flex: 1; min-width: 0; font-size: 15px; padding: 12px 10px; }
.pl-note {
  margin: 10px 4px 0; max-width: 44ch;
  font: 400 12.5px/1.45 var(--font-ui); color: var(--ink-tertiary);
}
.pl-note[hidden] { display: none; }
.pl-manage .pl-note { margin-left: var(--gutter); margin-right: var(--gutter); }

.pl-club-list { margin: 0 var(--gutter); }
/* The member rows are reads, not controls (players.js says why), so nothing here makes one
   look pressable — no chevron, no hover, no press scale. */
.pl-member-row { gap: 9px; padding-left: 10px; padding-right: 12px; }
/* The same disc as the board row, and for the same reason — these are the same people in the
   same shape of list, two taps apart. */
.pl-member-row .avatar { width: 34px; height: 34px; font-size: 13px; }
.pl-member-row.is-me { background: var(--selected-soft-bg); }
.pl-member-row.is-me .title,
.pl-member-row.is-me .pl-rank,
.pl-member-row.is-me .pl-rating { color: var(--selected-soft-ink); }
.pl-member-row.is-me .meta { color: var(--selected-soft-ink); opacity: .78; }
.pl-member-row .pl-rating { flex: none; }

/* ═══ 6bb · THE MANAGEMENT VIEW ════════════════════════════════ */

/* The second view of the club sheet. players.js says why it is a view and not a second sheet. */
/* Less the back button's own 10px, so its chevron starts on the gutter over the club's logo, the way the
   Cafe's and the Shop's back buttons do (it was 8px, 18 to the chevron: 2px outside a 20px gutter). */
.pl-manage-top { margin: 0 calc(var(--gutter) - 10px) 4px; }
.pl-back {
  display: inline-flex; align-items: center; gap: 4px;
  min-height: var(--tap-min); padding: 8px 10px;
  border: 0; background: transparent; color: var(--ink-secondary); border-radius: var(--r-full);
  font: 600 15px/1 var(--font-ui); letter-spacing: -.008em; cursor: pointer;
  touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.pl-back-ico { flex: none; display: block; }
.pl-manage-id { margin-bottom: 14px; align-items: center; }
.pl-manage-name {
  margin: 0; font-family: var(--font-display); font-weight: 600;
  font-size: 21px; line-height: 1.15; letter-spacing: -.012em; color: var(--ink);
  overflow-wrap: anywhere;
}
.pl-manage-seg { margin: 0 var(--gutter) 6px; }

/* ⚠ A SETTING ROW IS LABEL, VALUE, CHEVRON — the value is the thing that is currently true, and
   it is the part that gives way, not the label. A "Description" whose own word was ellipsised
   would be a row nobody can identify. */
.pl-edit-row { gap: 12px; padding-left: 14px; padding-right: 12px; }
.pl-edit-k { flex: none; font: 500 16px/1.3 var(--font-ui); color: var(--ink); }
.pl-edit-v {
  flex: 1; min-width: 0; text-align: right;
  font: 400 15px/1.3 var(--font-ui); color: var(--ink-tertiary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pl-invite-find { margin-bottom: 4px; }
/* Inside the sheet the field lines up with the lists either side of it, not with the screen. */
.pl-field-inline { margin: 0 var(--gutter); }
.pl-invite-find .pl-out .list { margin: 12px var(--gutter) 0; }
.pl-invite-find .pl-refuse { margin: 10px var(--gutter) 0; }

/* ⚠ SET APART, BECAUSE WHAT IS IN IT CANNOT BE UNDONE. Not a red box — that would make the
   management screen end on a warning — a rule and some air, so the last control is not the one
   your thumb lands on when the sheet settles. */
.pl-danger-zone { margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--rule); }
.pl-btn-danger { background: var(--danger); color: var(--bg-raised); }
.pl-btn-danger:active { opacity: .9; }

/* ═══ 6c · THE FORMS ════════════════════════════════════════════════════════ */

/* On the page gutter, like the sheet's own title bar above it and profile.css's `.pf-form`: at 16 the
   fields sat 4px outside the title on every phone from 375 (gutter review, 2026-10-07). */
.pl-form { padding: 14px var(--gutter) calc(12px + var(--safe-bottom)); }
.pl-fld + .pl-fld { margin-top: 18px; }
.pl-fld-l { display: block; }
.pl-fld-k {
  display: block; padding: 0 4px 7px;
  font: 600 12px/1 var(--font-ui); letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink-tertiary);
}
.pl-fld-k-loose { margin-top: 20px; }
/* ⚠ 16px MINIMUM. Safari zooms the whole page in when a focused input's text is under 16px and
   it does not zoom back out — the player is left on a sideways-scrolling page wondering what
   they broke. The same line is on .pl-input and for the same reason. */
.pl-fld-i {
  display: block; width: 100%; box-sizing: border-box;
  padding: 13px 14px; min-height: 50px;
  background: var(--bg-sunken); border: 1px solid var(--rule); border-radius: var(--r-md);
  outline: none; color: var(--ink);
  font: 400 16px/1.35 var(--font-ui); letter-spacing: -.006em;
}
/* ⚠ THE TAG FIELD IS WIDE-TRACKED AND UPPERCASE, so what is typed looks like what will be
   drawn beside sixteen names. A tag previewed in sentence case is a tag whose owner is
   surprised by it. */
.pl-fld-i.mono-nums { text-transform: uppercase; letter-spacing: .14em; font-weight: 600; }
.pl-fld-i:focus {
  border-color: var(--focus);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 22%, transparent);
}
.pl-fld-hint {
  margin: 8px 4px 0; max-width: 46ch;
  font: 400 12px/1.5 var(--font-ui); color: var(--ink-tertiary);
}
.pl-fld-out:not(:empty) { margin-top: 10px; }
.pl-fld-refuse { margin: 0; }
/* ⚠ "Available" IS THE ONLY GREEN ON THIS SCREEN AND IT IS EARNED: it means a write would go
   through, which is what --ok means everywhere else in this app. Nothing on the leaderboard
   wears it — see the movement cell's note. */
.pl-fld-refuse.is-ok .tag { background: transparent; color: var(--ok); box-shadow: inset 0 0 0 1px var(--ok); }

/* Hidden, but a real <input type="file"> — never `display: none`, which takes it out of the
   accessibility tree as well and leaves the visible button labelling nothing. */
.pl-file {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.pl-make { margin-top: 22px; }
.pl-make:disabled { opacity: .45; cursor: default; }

/* Three segments: 2px of padding each side plus two 2px gaps = 8px of furniture. */
.pl-seg3 .thumb { width: calc((100% - 8px) / 3); }
/* ⚠ ONLY INSIDE A FORM, whose own padding is the gutter. Unscoped, this also beat `.pl-manage-seg`
   (same weight, later in the file) and drew the management view's "Who can join" switch edge to edge,
   0px from both sides of the screen (gutter review, 2026-10-07). */
.pl-form .pl-seg3 { margin-left: 0; margin-right: 0; }

/* ── The narrowest phones give the name its room back ───────────────── */
/* ⚠⚠ AT 320px THE BOARD ROW CANNOT HOLD BOTH THE FOUR CHARACTERS AND THE NAME, AND THAT IS
   MEASURED, NOT ASSUMED. A board row carries seven things; at 320px, with the club mark in
   front of the name, TWELVE of the fifteen names were cut — "Gab Monteverde" by 47px, "Dana
   Villanueva" by 42, "Migs Alcantara" by 36. Without the mark the same board cut two. So below
   340px the mark drops its TAG and keeps its badge, and the row gives up another seventeen
   pixels of gap, padding, rank column and disc. Re-measured after: no name on the board is cut.
   ⚠ IT IS THE SAME TRADE THE CLOSED COURT CARD MAKES (see clubs.css's .is-bare, and event.js
     for that measurement), and it is allowed for the same reason: the badge is still there, the
     club's NAME is still read aloud by the mark's own .sr-only span, and the profile one tap
     away says the club in full. What is NOT allowed is cutting the person's name to keep a
     four-character code — a name is the row's subject and the tag is an affiliation.
   ⚠ 340px MATCHES event.css's queue-row block and shell.css's tab bar — the same phones, the
     same reason, one number across the app. */
@media (max-width: 340px) {
  .pl-board-row { gap: 5px; padding-left: 8px; padding-right: 6px; }
  .pl-board-row .avatar { width: 30px; height: 30px; font-size: 11.5px; }
  .pl-club-row .clb-logo { width: 30px; height: 30px; border-radius: 9px; font-size: 11.5px; }
  .pl-rank { width: 1.5em; font-size: 16px; }
  .pl-board-row .clb-mark .clb-tag { display: none; }
  .pl-board-row .clb-mark { gap: 0; }
  /* ⚠⚠ AND THE @HANDLE COMES OFF THE NAME LINE ENTIRELY BELOW 340px. Measured at 320: with it,
     THIRTY-EIGHT of the fifty-four names were cut and "Migs Alcantara" — fourteen characters, the
     top of the board — rendered "Migs Alcan…". The handle is ~52px of fixed width in front of a
     name that has to give way, and on the narrowest phones there is nothing left to take it from.
     The same trade as the club tag two lines up, for the same reason: the row's subject is the
     person, the handle identifies them somewhere with room, and the profile is one tap away. */
  .pl-handle { display: none; }
  .pl-move { font-size: 11px; }
  .pl-me, .pl-full { padding: 3px 5px; letter-spacing: .04em; }
  /* The club board's right column is the widest thing on that row and it is set by "Avg 1696",
     not by the collective above it. Two points off the caption is six pixels for the name. */
  .pl-club-avg { font-size: 10px; }
  /* ⚠ THE TAG DROPS TO ITS OWN LINE RATHER THAN EATING THE CLUB'S NAME. At 320px "Kitchen
     Cartel" beside "YOURS" was 24px short, and a club board whose rows are two words of a name
     and an ellipsis is not a board. Only the one or two tagged rows grow; the rest keep the
     column. The PLAYER board is not allowed to do this — there the tag is on the row about the
     reader, and a taller "me" row would break the ranked column at exactly the place the eye
     stops. Clubs have five rows and no such row. */
  .pl-club-row .pl-name-line { flex-wrap: wrap; row-gap: 3px; }
  /* The member list inside the club sheet takes the same two measures: the row tightens, and the
     "Owner" tag wraps under the name rather than through it. A sixteen-row list is not a ranked
     column the eye runs down — it is read one name at a time. */
  .pl-member-row { gap: 7px; padding-left: 8px; padding-right: 10px; }
  .pl-member-row .pl-name-line { flex-wrap: wrap; row-gap: 3px; }
}

/* ═══ 6d · ARRIVING ON YOUR OWN ROW ═════════════════════════════ */

/* ⚠⚠ A RING, NOT A BACKGROUND, because the row this lands on is ALREADY tinted — it is the
   viewer's own. A flash that changed the background would fight `.is-me` and read as the tint
   glitching; an inset ring sits on top of it and is the same mark the focus state uses, so the
   two agree when a keyboard is what did the jumping.
   ⚠ NO animation-fill-mode. The row's resting state is "no ring", which is exactly where the
   animation ends, so it needs no fill — and this project has already shipped `fill-mode: both`
   on a card that then stayed in its start state and rendered invisible. Nothing about whether
   this row can be SEEN depends on the animation running. */
@keyframes pl-found {
  0%, 62% { box-shadow: inset 0 0 0 2px var(--selected-edge); }
  100%    { box-shadow: inset 0 0 0 2px transparent; }
}
.pl-board-row.is-found { animation: pl-found 1.5s var(--ease-out); }

/* ═══ 7 · FOCUS ═════════════════════════════════════════════════════════════ */

/* ⚠ EVERY CONTROL HERE IS A <button>, SO NOTHING GIVES IT A RING FOR FREE ONCE THE UA
   default is overridden by the background and border above. This app has a public sign-up:
   assume every input method arrives. --focus is theme-aware (navy in light, orange in dark);
   there is no --focus-ring, and writing one resolves to nothing and silently uses a
   fallback — which is how the profile lost its ring in dark mode once already. */
.pl-row:focus-visible,
.pl-preview-btn:focus-visible,
.pl-empty-btn:focus-visible,
.pl-wide-btn:focus-visible,
.pl-half-btn:focus-visible,
.pl-mini-btn:focus-visible,
.pl-mini-link:focus-visible,
.pl-link:focus-visible,
.pl-back:focus-visible,
.pl-clear:focus-visible,
.pl-edit-row:focus-visible,
.pl-make:focus-visible,
.pl-seg button:focus-visible {
  outline: 2px solid var(--focus); outline-offset: -2px; border-radius: var(--r-sm);
}
/* ⚠ THE FILE INPUT IS VISUALLY HIDDEN AND STILL FOCUSABLE, so the ring has to land on the
   button that opens it rather than on a 1px box in the corner of the sheet. */
.pl-file:focus-visible + .btn { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ═══ 8 · WIDE SCREENS ══════════════════════════════════════════════════════ */

/* A desktop browser gets the tablet layout in a readable column, per DESIGN.md — not a
   stretched phone with a leaderboard row a metre wide. */
@container pl-screen (min-width: 700px) {
  .pl-sec .list,
  .pl-sec .empty,
  .pl-seg,
  .pl-field,
  .pl-out .list,
  .pl-refuse,
  .pl-preview { max-width: 620px; margin-left: auto; margin-right: auto; }
  .pl-wide-btn { max-width: 620px; width: 100%; margin-left: auto; margin-right: auto; }
  .pl-you { max-width: 620px; margin-left: auto; margin-right: auto; }
  .pl-field { max-width: 620px; margin-left: auto; margin-right: auto; }
  .pl-break { max-width: 620px; margin-left: auto; margin-right: auto; }
  .pl-caption, .pl-rules, .pl-sub-head { max-width: 620px; margin-left: auto; margin-right: auto; }
}

/* ═══ 9 · ACCESSIBILITY ═════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  /* The scope switch's thumb is driven by spring() in motion.js, which reads the preference at
     use time and jumps straight to the target. Named here anyway, per DESIGN.md §8: the next
     animation added to this file needs somewhere obvious to be turned down, and an absent block
     is where one gets forgotten. */
  .pl-seg .thumb { transition: none; }
  /* ⚠ GENTLER, NOT ABSENT (DESIGN.md §8). The flash is the only confirmation that the jump
     button did anything when the row was already on screen; removing it outright would leave a
     reader with Reduce Motion on pressing a button that appears dead. The ring is held for the
     same 1.5s players.js's timer holds the class, and simply does not fade. jumpToMe() already
     scrolls instantly rather than smoothly under the same preference. */
  .pl-board-row.is-found { animation: none; box-shadow: inset 0 0 0 2px var(--selected-edge); }
}

@media (prefers-contrast: more) {
  .pl-preview { border-color: var(--ink); }
  .pl-field { border-color: var(--ink); }
  .pl-fld-i { border-color: var(--ink); }
  .pl-board-row.is-me,
  .pl-member-row.is-me { box-shadow: inset 3px 0 0 var(--selected-edge); }
}

/* ═══ THE REAL TAB — players.js renderReal() ═══════════════════════════════════
 * Syd, 2026-10-05: "all the elements should still be there just like with a note that none
 * assigned". The sample tab's frame, drawn with its own classes above; this is only what that frame
 * needs when nobody is in it. ⚠ NOTHING HERE MOVES (the skeletons do not shimmer — shell.css `.skel`
 * says why), so there is nothing for the reduced-motion block above to name. */

/* The search, switched off. ⚠ THE FIELD KEEPS ITS SHAPE AND ITS INK; ONLY THE CURSOR AND THE FOCUS
   GO. A field faded to nothing reads as a rendering fault; the placeholder says it is off. Safari
   dims a disabled input's text to about 40% on its own, which on --bg-sunken would put the words
   under the contrast floor — hence opacity: 1 and the fill colour restated. */
.pl-field.is-off { cursor: default; }
.pl-input:disabled { opacity: 1; cursor: default; -webkit-text-fill-color: var(--ink-tertiary); }

/* A control drawn and switched off (players.js offBtn, scopeOff): the sample's own button, its words
   and glyph in the tertiary ink, on the same fill. ⚠ NOT FADED: at the opacity a disabled button
   usually gets, "Show me on the board" on --bg-sunken falls under the contrast floor, and a button
   nobody can read is not "still there". ui.css's .btn and .segmented set a colour, which beats the
   browser's own grey for a disabled button, so it is restated here. */
.pl-you-btn:disabled, .pl-empty-btn:disabled { color: var(--ink-tertiary); cursor: default; }
.pl-seg.is-off button { cursor: default; }
.pl-seg.is-off button[aria-pressed="false"] { color: var(--ink-tertiary); }
/* A section title with "Coming soon" beside it (players.js soonTitle): Profile's group-head pairing
   (`.pf-ghead-top`, `.pf-ghead-tag`), so the tag is the same size on both tabs and the 12px heading
   keeps its line. */
.pl-soon-title { display: flex; align-items: center; gap: 6px; min-width: 0; }
.pl-soon-tag { padding: 3px 7px; font-size: 10px; letter-spacing: .06em; flex: none; }
/* The snapshot's caption ("186 ranked · updated …" on the sample tab), as a line of its height. */
.pl-skel-caption { height: 9px; margin-top: 2px; }

/* A grey board row: the board row's own box and rhythm (`.pl-board-row`), with shapes where a rank,
   a disc, a name and a rating would be. The disc is the board's 30px. */
.pl-skel-row { cursor: default; }
.pl-skel-n { width: 14px; height: 12px; margin: 0 auto; border-radius: 4px; }
.pl-skel-av { width: 30px; height: 30px; border-radius: 50%; }
.pl-skel-name { height: 11px; }
.pl-skel-meta { height: 8px; margin-top: 7px; }
.pl-skel-rt { width: 34px; height: 14px; border-radius: 4px; }
