/* =========================================================================
   shared.css — components both shells need.

   The admin shell (_base.html) and the player shell (_player_base.html) load
   different stylesheets on purpose: admin.css is desk-shaped, climb.css and
   court.css are courtside-shaped, and court.css styles .pick and .search, which
   two admin templates use for other things. Keeping them apart is deliberate.

   But a few components are genuinely shared, and duplicating them into both is
   how the palette ended up in seven files. They live here, and both shells load
   this.

   Starts with the flash block, which was in admin.css — so a flash set before
   redirecting to a PLAYER page rendered unstyled, and nobody had noticed because
   no player page had a flash until the join button arrived.

   The form primitives followed, with the structured score control: .field and
   friends, .abtn, .radio-row, .dlg-actions, .report-dlg, .panel-title. A form
   field looks the same wherever it is and had no business living in an admin
   stylesheet — _report_dialog.html is included by an ADMIN page and a PLAYER
   page, and only one of them loads admin.css.

   ⚠ THE TWO SHELLS LOAD IT IN OPPOSITE ORDERS, and this paragraph claimed
   otherwise for months — it read "Both shells load this file LAST, so a rule
   moved here still wins wherever it won before." Only half of that is true:

     _base.html          tokens → admin  → SHARED   (shared wins)
     _player_base.html   tokens → SHARED → player   (player wins)

   So a rule moved here from admin.css keeps winning; a rule moved here from
   player.css can be overridden by anything left behind in player.css, which is
   sometimes exactly what you want (see the .klogo desktop step) and is never
   something to assume. Check the order before moving a rule either way.

   Still on the admin shell and still to move: nothing the dialog needs.
   `player_tournament.html` itself uses only .page-title, .page-sub and
   .stub-eyebrow — the three that stand between it and _player_base.html
   (HANDOFF §7 step 5).
   ========================================================================= */

/* Links carry no decoration anywhere in this product; a row, a tab and a card
   are all anchors, and the browser default underlines every one of them.

   This lived in admin.css only, because climb.css never needed it — in
   climb.html every row is a <div> the JS makes tappable, so there were no
   anchors to reset. The moment the player pages became real links, every row,
   tab and chevron rendered underlined and link-coloured. It belongs to both
   shells, so it belongs here. */
a { color: inherit; text-decoration: none; }

/* THE WORDMARK LOCKUP — `templates/_logo.html`, and both bars now draw it.

   It lived in player.css, because the player bar was the only one with a real
   mark on it; the admin bar had `.ah-dot` + the word "Kicksrv" set in Saira
   Condensed, which was the same placeholder the player bar had already
   retired. The 2026-08 admin artwork puts the lockup on both, so the rules
   come here rather than getting a second copy — a wordmark rendered from two
   sets of fills is a wordmark that drifts.

   Sized by HEIGHT, with the width left to the viewBox's own 2.152 aspect.
   Never set both — a logo that can be squashed is a logo that will be. Each
   shell takes its own desktop step at its own breakpoint: player.css at its,
   admin.css at 880px.

   Two fills, no hex — see _logo.html. The word is --acc because in this system
   cyan IS identity (DESIGN.md §2), so the wordmark and the product's accent
   cannot drift apart. */
.klogo { display: block; height: 40px; width: auto; }
.klogo-word { fill: var(--acc); }
.klogo-mark { fill: var(--t-hi); }

.flashes { display: grid; gap: 8px; margin: 0 0 18px; padding: 0; list-style: none; }

.flash {
  padding: 11px 14px;
  border-radius: var(--r-s);
  border: 1px solid var(--border-2);
  background: var(--surface);
  color: var(--t-body);
  font: 600 14px/1.45 var(--sans);
}

.flash-ok {
  border-color: var(--acc-line);
  background: var(--acc-wash);
  color: var(--acc);
}

.flash-error {
  border-color: var(--danger-line);
  background: var(--danger-wash);
  color: var(--danger);
}

.flash-warn {
  border-color: var(--amb-line);
  background: var(--amb-wash);
  color: var(--amb);
}

/* A section heading between blocks on a page. HANDOFF §7b flagged this as
   "applied but defined NOWHERE": prod_home.html carried the whole recipe as an
   inline style on all three of its headings, because there was no class to use.
   It was real only inside the demo CRM's page-local <style>. */
.sech {
  font: 700 11px var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--t-muted-2); margin: 28px 0 10px;
}

/* =========================================================================
   In-app notifications.

   Amber rather than the accent, deliberately: --acc is this product's "good",
   and one of the three things a bubble can mean is that somebody's run ended.
   Amber says "there is something here" without saying whether it is welcome.
   ========================================================================= */

.news-dot {
  flex: none; align-self: center;
  min-width: 20px; height: 20px; padding: 0 6px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  /* Solid amber with its PAIRED ink. --amb-container is a 14% tint and
     --on-amb is the screen colour, so pairing those two would put near-black
     on near-black; the container tint is for panels, not for a badge that has
     to be legible at 20px. */
  background: var(--amb); color: var(--on-amb);
  font: 700 11px var(--mono); letter-spacing: .02em;
}

.news {
  border: 1px solid var(--amb-line); border-radius: var(--r-m);
  background: var(--amb-wash);
  padding: 12px 14px; margin: 0 0 18px;
  display: flex; flex-direction: column; gap: 6px;
}
.news-line {
  font: 600 13px/1.45 var(--sans); color: var(--amb);
  overflow-wrap: anywhere;
}

/* =========================================================================
   Form primitives — moved verbatim from admin.css.

   `.field` is a COLUMN. Never reach for it to make a row: writing
   <div class="field" style="display:flex"> re-declares `display` without
   overriding `flex-direction`, so it renders a stack with every flex:1 on its
   children inert. That is what left six list pages with no layout (CLAUDE.md
   trap 8, HANDOFF §7a). `.field-row` is the row.
   ========================================================================= */

.field { display: flex; flex-direction: column; margin-bottom: 16px; }
/* ⚠ PAIRED WITH `hidden`, AND THE TRAP IS EXACTLY THE ONE CLAUDE.md RECORDS
   THREE TIMES. The line above sets a `display`, which beats the UA's
   `[hidden] { display: none }` — so a `.field` a script hides stays on screen.
   The report dialog hides its whole Score field on a forfeit, and without this
   it would do so on the ADMIN shell (admin.css opens with a blanket
   `[hidden] { display: none !important }`) and silently fail on the PLAYER
   shell, which deliberately carries no such rule. One surface working and its
   neighbour not is the signature of this bug every time it has appeared here.

   Here rather than on `#scoreField`, for the reason `.abtn[hidden]` is here
   rather than on `.prop-clear`: the class carrying the display is `.field`, so
   patching the one instance that bit would leave every other `.field` in the
   product waiting to. */
.field[hidden] { display: none; }
.field-row { display: flex; flex-direction: column; gap: 0; }
.field-row .field { flex: 1; }
.field-label { font: 700 11px var(--sans); letter-spacing: .04em; color: var(--t-body); margin-bottom: 7px; }
.field-hint { font: 500 11.5px var(--sans); color: var(--t-muted-2); margin-bottom: 8px; }
.field-input {
  font: 500 14px var(--sans); color: var(--t-hi);
  background: var(--surface-2); border: 1px solid var(--border-2); border-radius: var(--r-s);
  padding: 11px 13px; width: 100%; outline: none;
  transition: border-color 150ms ease;
}
.field-input:focus { border-color: var(--acc); }
/* `color` is REPEATED FROM `.field-input` ON PURPOSE, and the duplication is
   the point rather than an oversight. This is a modifier meant to be worn
   WITH `.field-input`, but three templates wore it alone and shipped black
   text on the dark admin ground — `.ashell` paints the surface (admin.css)
   while `font:` shorthand resets everything about the type EXCEPT colour, so
   the browser default survived and nothing looked wrong in the markup. The
   three are fixed; this makes the next one that forgets merely unstyled
   instead of unreadable. */
.field-textarea { resize: vertical; font: 500 13px var(--mono); line-height: 1.6; color: var(--t-hi); }
select.field-input { cursor: pointer; }
.field-err { font: 600 11.5px var(--sans); color: var(--danger); margin-top: 7px; }
/* A value in the shape of `.field-input` that is not one — the account
   dialog's email row, which is never an `<input>` at all (see
   _account_fields.html) so there is nothing to focus, type into, or submit. */
.field-static { font: 500 14px var(--sans); color: var(--t-body); padding: 11px 0; }

/* A flex item defaults to min-width:auto, which resolves to MIN-CONTENT — and a
   <select>'s min-content width is its widest option, so the reviewer dropdown on
   the join-requests queue refused to shrink and pushed Approve off-screen. It is
   a flex child everywhere it appears, so this is fixed at every width. */
.field-input { min-width: 0; }

/* Emails and member ids carry no break opportunity, so without this a hint
   widens its container past the viewport instead of wrapping. */
.field-hint { overflow-wrap: anywhere; }

/* ⚠ A NATIVE DATE OR TIME CONTROL DOES NOT SHRINK TO `width: 100%`, and that
   is a WebKit fact rather than a bug in this sheet. The control carries an
   intrinsic width from the format it renders (mm/dd/yyyy, --:-- --), and on
   iOS that width wins: the input runs past its container's right edge with the
   left edge still correctly inset, so it reads as a padding problem and is
   not one. Reported on the propose dialog ("the info doesn't fit"), where the
   Day and Time boxes ran off the side of the sheet.

   `appearance: none` is what lets the declared width govern. Paired with an
   explicit `max-width` because the two failures are different: `appearance`
   removes the intrinsic sizing, `max-width` is the backstop for any engine
   that keeps some of it.

   ⚠ SEVEN INPUTS, NOT ONE. Every date and time field in the product wears
   `.field-input` — the propose dialog, the admin's court windows, the wizard's
   lock date and the settings screen's. They all had this and the rule fixes
   all of them, which is why it is here rather than in `bracket.css` beside the
   dialog that reported it.

   The picker indicator is left alone deliberately: `appearance: none` on the
   INPUT does not remove `::-webkit-calendar-picker-indicator`, so the tap
   still opens the native picker. Do not add `appearance: none` to that
   pseudo-element as well — that is the edit that turns these into text boxes
   on a phone. */
.field-input[type="date"],
.field-input[type="time"] {
  -webkit-appearance: none;
  appearance: none;
  max-width: 100%;
}

.panel-title { font: 800 17px var(--cond); letter-spacing: .04em; text-transform: uppercase; color: var(--t-hi); margin-bottom: 14px; }

.abtn {
  display: inline-block; cursor: pointer;
  font: 700 12px var(--sans); letter-spacing: .02em;
  padding: 10px 16px; border-radius: var(--r-s);
  background: var(--surface); border: 1px solid var(--border-2); color: var(--t-hi);
  transition: border-color 150ms ease, background 150ms ease;
}
/* PAIRED WITH `hidden`, AND IT BELONGS HERE RATHER THAN ON EACH BUTTON.
   `display: inline-block` above beats the UA's `[hidden] { display: none }`,
   so ANY `.abtn` a script hides stays on screen — the trap CLAUDE.md records
   for `.lrow` and `.profile-dlg`, sprung a third time in 2026-08i on the
   propose dialog's Clear button, which sat there on a match with nothing to
   clear.

   `bracket.css` already carries `.sched-release[hidden]`, which is the same
   bug patched one button at a time. This rule supersedes that shape: every
   button in the product is covered, including the ones nobody has written
   yet. */
.abtn[hidden] { display: none; }
.abtn:hover { border-color: var(--t-dim); background: var(--surface-2); }
/* THE PRIMARY, AND IT IS ORANGE — 2026-08b §1.1, applied where it always
   should have been. --acc stopped being the CTA fill and became identity and
   linked text; --action carries every filled control. This rule was the last
   place still filling a button with cyan, which is why "Submit for approval"
   in the report dialog came out blue while the pill that opened it was orange.

   IT REPAINTS THE ADMIN AND THE AUTH SCREENS TOO, deliberately. `.abtn` is
   shared, the dialog it is in is shared, and a system with two primary colours
   depending on which screen you are on is the inconsistency this fixes rather
   than a scope this respects. */
.abtn--primary { background: var(--action); border-color: var(--action); color: var(--on-action); }
.abtn--primary:hover { filter: brightness(1.06); }
/* THE SECONDARY — Cancel, Close, Release. §4's outlined tier: white on
   nothing, so it reads as an alternative rather than a second primary. It had
   no rule at all and was falling through to `.abtn`'s filled-surface base. */
.abtn--ghost { background: none; border-color: var(--t-hi); color: var(--t-hi); }
.abtn--ghost:hover { background: var(--surface-2); border-color: var(--t-hi); }
.abtn--danger { color: var(--danger); border-color: var(--danger-line); }
.abtn--danger:hover:not(:disabled) { background: var(--danger-wash); }
/* THE POSITIVE FILL — Publish, and nothing else so far. It is CYAN because
   there is no green in this palette and there is not going to be one: tokens.css
   records the green-to-cyan brand change, and --acc IS this product's "good".
   `.wbtn--ok` (admin.css) is the existing cyan-filled control; this is its
   `.abtn` sibling rather than a second opinion about the same idea.

   NOT `.abtn--primary`, even though both are filled. Orange is "the thing you
   press next" and this is the END of a staging flow — the two sit in the same
   action bar, and a page with two orange fills says neither. */
.abtn--go { background: var(--acc); border-color: var(--acc); color: var(--on-acc); }
.abtn--go:hover { filter: brightness(1.06); }
.abtn:disabled { opacity: .4; cursor: not-allowed; }

/* =========================================================================
   The result dialog, and the score control inside it.
   ========================================================================= */

.report-dlg {
  border: 1px solid var(--border-2); border-radius: var(--r-l);
  background: var(--surface); color: var(--t-body);
  padding: 20px 20px 18px; width: min(420px, calc(100vw - 32px));
}
.report-dlg::backdrop { background: rgba(0, 0, 0, .6); }
.radio-row { display: flex; align-items: center; gap: 9px; padding: 9px 11px; margin-bottom: 7px; border: 1px solid var(--border-2); border-radius: var(--r-s); font: 600 13.5px var(--sans); color: var(--t-body); cursor: pointer; }
.radio-row:hover { border-color: var(--t-dim); }
/* THE SELECTED STATE IS AN ORANGE STROKE. Picking the winner is the decision
   this dialog exists for, and until now the only thing that moved was the
   radio dot itself — a 13px mark inside a box that looked identical either
   way. `:has()` puts it on the row, so the whole hit area says which one is
   chosen. */
.radio-row:has(input:checked) { border-color: var(--action); }
.radio-row input { accent-color: var(--action); }
.dlg-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 16px; }

/* One set: a label and two games boxes. Grid rather than flex so the boxes
   stay the same width whether or not the row carries a label.

   ⚠ THE `[hidden]` COMPANION IS LOAD-BEARING. A `display` beats the UA's
   `hidden`, and set 3 is hidden by script until the first two are split —
   without this it would sit open on the PLAYER shell, which has no blanket
   `[hidden] { display: none }`. That is the same pairing `.set-tb` got wrong
   in this file, and `.report-err` above needs for the same reason. */
.set-row {
  display: grid; grid-template-columns: 34px 1fr 12px 1fr;
  align-items: center; gap: 8px; margin-bottom: 8px;
}
.set-row[hidden] { display: none; }
.set-no { font: 700 11px var(--mono); color: var(--t-muted-2); }
/* Names over the columns. Ellipsis rather than wrap, so a long name cannot
   push the two boxes out of alignment with each other. */
.set-head { margin-bottom: 4px; }
.set-who {
  font: 700 10px var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--t-muted-2); text-align: center;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.set-x { font: 600 12px var(--sans); color: var(--t-dim); text-align: center; }
.set-games {
  font: 700 15px var(--mono); color: var(--t-hi); text-align: center;
  background: var(--surface-2); border: 1px solid var(--border-2);
  border-radius: var(--r-s); padding: 10px 6px; width: 100%;
  outline: none; min-width: 0;
  transition: border-color 150ms ease;
}
.set-games:focus { border-color: var(--acc); }
/* The spinners are noise on a two-digit field and shrink the tap target. */
.set-games::-webkit-outer-spin-button,
.set-games::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.set-games { -moz-appearance: textfield; appearance: textfield; }

/* `.set-tb`, `.set-tb-q` and `.set-tb-opt` are GONE (2026-08e) along with the
   tiebreak question they painted — a set that went to a tiebreak is now typed
   as the finished score, 7–6 or 9–8, and nothing interrupts to ask.

   ⚠ THE RULE ITSELF WAS A BUG, and worth recording rather than just deleting.
   `.set-tb` set `display: flex` with no `.set-tb[hidden]` companion, and the
   script hid the panel by writing `hidden` to it — which is only a UA style
   and loses to any class rule with a `display`. So on the PLAYER shell the
   panels sat open over empty boxes while the script believed they were away.
   The admin shell was immune: admin.css opens with a blanket
   `[hidden] { display: none !important }` and player.css deliberately does
   not. That is the third time this pairing has cost this repo (CLAUDE.md
   records `.lrow` and `.profile-dlg`), and the FIRST time in this file —
   which is why `test_anything_the_server_hides_stays_hidden_in_css`, written
   for exactly this and reading only player.css, did not see it. It reads
   shared.css too now. */

/* The server's refusal, shown in the dialog so a rejected score does not cost
   the player everything they typed. --danger because it IS an error, unlike
   `.score-preview` below, which is only ever a restatement.

   ⚠ THE `[hidden]` COMPANION IS NOT OPTIONAL. This sets `display`, and
   `hidden` is only a UA style that any class rule with a `display` beats — on
   the PLAYER shell, which has no blanket `[hidden] { display: none }`, the
   banner would sit open and empty above every result dialog. That is exactly
   how `.set-tb` shipped three panels over empty boxes, in this same file.
   test_anything_the_server_hides_stays_hidden_in_css now reads shared.css and
   would catch it, but the pairing belongs here rather than being discovered. */
.report-err {
  display: block; margin: 0 0 14px; padding: 9px 12px;
  border: 1px solid var(--danger-line); border-radius: var(--r-s);
  background: var(--danger-wash); color: var(--danger);
  font: 600 12.5px/1.45 var(--sans);
}
.report-err[hidden] { display: none; }

/* What the boxes compose, echoed back so the score being saved is visible
   before saving it. The server still validates it — this is not a verdict. */
.score-preview {
  font: 700 13px var(--mono); letter-spacing: .04em; color: var(--acc);
  margin-top: 4px; min-height: 18px;
}
.score-preview.is-empty { color: var(--t-dim); }

/* Touch. Same reasoning as bracket.css:157 and admin.css's own block: there is
   no hover to reveal anything, and a 24-30px target is far below what a finger
   needs. These live here because the player shell does not load admin.css. */
@media (hover: none) {
  .abtn { min-height: 44px; }
  .set-games { min-height: 44px; }
}

/* Desktop is the additive layer, the same polarity as admin.css and climb.css. */
@media (min-width: 880px) {
  .field-row { flex-direction: row; gap: 14px; }
}

/* =========================================================================
   The sign-in tabs — Club admin / Player.

   Here rather than in climb.css, which already has a .tabs strip, because the
   sign-in page extends the ADMIN shell and that shell does not load climb.css.
   Pulling climb.css in for two links would drag court.css's .pick and .search
   into pages that use those class names for other things — the collision this
   file's header exists to describe.

   They are LINKS carrying ?as=, not buttons: each door has to be reachable by
   URL, and the page has to work with no JavaScript.
   ========================================================================= */

.signin-tabs { display: flex; gap: 8px; margin: 0 0 18px; }

.signin-tab {
  flex: 1; text-align: center;
  font: 700 12px var(--sans); letter-spacing: .02em;
  padding: 11px 14px; border-radius: var(--r-s);
  background: var(--surface); border: 1px solid var(--border-2);
  color: var(--t-dim);
  transition: border-color 150ms ease, background 150ms ease, color 150ms ease;
}
.signin-tab:hover { border-color: var(--t-dim); background: var(--surface-2); }

/* The selected door reads as selected without borrowing .abtn--primary, which
   is the accent fill the "Sign in" button below uses. Two accent-filled
   controls stacked would make the tab look like the action. */
.signin-tab--active {
  color: var(--t-hi); background: var(--surface-2);
  /* An orange STROKE, not a fill — the same rule the report dialog's winner
     row and the draw editor's armed player follow. The note above still holds:
     a fill here would make the tab look like the action instead of the
     "Sign in" button under it. */
  border-color: var(--action);
}

@media (hover: none) { .signin-tab { min-height: 44px; } }


/* =========================================================================
   THE ERROR PAGE — templates/error.html, and NOTHING else.

   It lives here rather than in admin.css or player.css because the page it
   styles deliberately loads neither. A refusal is rendered when something has
   already gone wrong, and the most ordinary thing to have gone wrong is the
   database — so the error template extends no shell, includes no nav, and
   loads exactly tokens.css and this file. See the note at the top of
   error.html for the full argument.

   THIS IS THE ONLY BLOCK IN THIS FILE THAT SETS `body`, and it has to: neither
   shell's reset is loaded, so without it the page renders as unstyled black
   text on browser white, which is a worse 404 than Werkzeug's. It is keyed to
   `.errpage` — a class only error.html carries — so it cannot reach a real
   page even if some future template loads this sheet without a shell.

   --screen-player rather than --screen: the club's members are the people who
   will actually meet this page, and the navy is the ground every one of their
   other pages sits on. An admin seeing it too is fine; one product, one
   palette.
   ========================================================================= */

body.errpage {
  margin: 0;
  /* ⚠ NOT INHERITED FROM ANYWHERE. `box-sizing: border-box` is declared in
     admin.css and in player.css, and this page loads NEITHER — so the padding
     below was ADDED to the 100vh rather than sitting inside it, the body came
     out 876px tall in an 812px viewport, and a page with four short lines on it
     scrolled. Found in the browser at 375px; nothing else could see it. */
  box-sizing: border-box;
  min-height: 100vh;
  background: var(--screen-player);
  color: var(--t-body);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px 20px;
}

.err {
  width: 100%;
  max-width: 420px;
  text-align: center;
}

.err-brand { display: inline-block; margin-bottom: 34px; }
.err-brand .klogo { height: 30px; }

/* The number is a watermark, not a headline: it names the protocol, and the
   sentence under it names the problem. Mono because every other machine-fact
   in this product is (a rank, a score, an "as of" stamp), and dim because a
   person who has hit a 404 does not need "404" said loudly at them. */
.err-code {
  margin: 0 0 10px;
  font: 700 12px var(--mono);
  letter-spacing: .12em;
  color: var(--t-dim);
}

.err-head {
  margin: 0 0 12px;
  font: 800 30px var(--cond);
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--t-hi);
}

.err-body {
  margin: 0 0 26px;
  font: 500 14px var(--sans);
  line-height: 1.55;
  color: var(--t-body);
}

/* .abtn is a full-width block by default in a form; here it is one control on
   an otherwise empty page, so it sizes to its text and centres. */
.err-go { display: inline-flex; }

/* =========================================================================
   The account avatar and its dropdown menu — both shells' replacement for
   the standalone Sign out button. One component, in shared.css, because
   admin.css and player.css each need it identically and a rule left behind
   in one is the seven-file palette drift this file exists to stop.
   ========================================================================= */

.account-menu-wrap { position: relative; display: flex; align-items: center; }

.account-avatar {
  width: 32px; height: 32px; border-radius: var(--r-full);
  background: var(--acc); color: var(--on-acc);
  display: inline-flex; align-items: center; justify-content: center;
  font: 700 13px var(--sans); text-decoration: none; flex: none;
}

/* Ships `hidden` (see _account_menu.html) and toggled by
   static/account_menu.js. Its shown state and hidden override are declared
   TOGETHER here rather than trusting either shell's own [hidden] handling:
   admin.css carries a blanket `[hidden]{display:none!important}`, but
   player.css deliberately does not (CLAUDE.md — a `display` rule on a class
   beats a bare `[hidden]` unless the hidden state is keyed to the class
   itself). This file is loaded by both, so it has to be safe alone. */
.account-menu {
  display: flex; flex-direction: column;
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 30;
  min-width: 220px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border-2);
  border-radius: var(--r-l); box-shadow: 0 8px 24px rgba(0, 0, 0, .28);
}
.account-menu[hidden] { display: none; }
/* `.account-menu-wrap .account-menu form`, not the bare descendant form —
   player.css's `.pnav-out form { display: flex; align-items: center; }`
   loads AFTER this file in the player shell and is equal-specificity to a
   plain `.account-menu form`, so it would win the cascade and turn every row
   here into a centred flex row instead of the full-width block a menu row
   needs. Two ancestor classes outranks it regardless of load order. */
.account-menu-wrap .account-menu form { display: block; width: 100%; margin: 0; }

.account-menu-row {
  display: flex; align-items: center; width: 100%; min-height: 44px;
  text-align: left;
  font: 600 13.5px var(--sans); color: var(--t-hi);
  background: none; border: 0; border-radius: var(--r-s);
  padding: 0 11px; cursor: pointer;
  /* For the one <a> row (My History): no underline, and `width: 100%` must
     include the padding — the player shell's reset does not set box-sizing
     on anchors the way buttons get it. Inert on the <button> rows. */
  text-decoration: none; box-sizing: border-box;
}
.account-menu-row:hover { background: var(--surface-2); }
.account-menu-row--danger { color: var(--danger); }
.account-menu-row--danger:hover { background: var(--danger-wash); }

/* THE `More` DISCLOSURE, hiding the one irreversible row — see
   `_account_menu.html` for why it is behind one at all.

   `list-style` PLUS the WebKit pseudo-element, the same pair `.menu-btn`
   (admin.css) and `.rules-open` (player.css) each need, or Safari draws its
   own triangle beside the chevron. `.account-more-body` is deliberately not
   styled: giving a `<details>`'s hidden child a `display` is the trap this
   repo has sprung three times.

   ⚠ THE CHEVRON IS A `›` IN THE MARKUP, ROTATED — not a `▾` in a
   `content:`. This file is loaded by BOTH shells, and the two do not share a
   font stack: `▾` is outside Archivo's `unicode-range` (tokens.css) and
   rendered as tofu on the player shell when it was tried. `›` is the glyph
   `.lcard-foot-chev` already ships on the same pages, and rotating it is what
   `_data_card.html`'s footer arrow and this control have in common. */
.account-more > summary.account-more-btn {
  list-style: none; cursor: pointer; user-select: none;
  justify-content: space-between;
}
.account-more > summary.account-more-btn::-webkit-details-marker { display: none; }
.account-more-chev {
  color: var(--t-muted); font-size: 15px; line-height: 1;
  transform: rotate(90deg); transition: transform 120ms ease;
}
.account-more[open] .account-more-chev { transform: rotate(-90deg); }
.account-menu-divider { height: 1px; background: var(--border-2); margin: 6px 4px; }

@media (min-width: 880px) {
  .err-head { font-size: var(--fs-display-2); line-height: 1.02; }
}

/* ---- contacting a player ---------------------------------------------------
   THE SHEET MOVED HERE FROM player.css, and the move is the change. It is
   opened from TWO SHELLS now: the player's event card (Your-match row) and the
   ADMIN's eligibility panel, where it lets a club chase whoever has not
   answered. `_base.html` deliberately does not load player.css — linking it is
   the edit CLAUDE.md names as the one that quietly makes it a shared file — so
   rules only that sheet carried rendered as unstyled text on the admin page.
   Both shells load this one.

   The BUTTON did not move and should not: `.crow-contact` is the player card's
   pill and stays in player.css; `.fld-contact` is the admin row's and lives in
   admin.css. What is shared is the sheet, because it is one dialog.

   Its values come from `services.people.contact_card`, whose callers each
   decide who may see them — the other half of a match the viewer is in, or a
   list only an administrator of that club can load.

   ⚠ `.contact-dlg` DELIBERATELY SETS NO `display`. It wears `.report-dlg`,
   which sets none either and takes `block` from the UA on open — that is
   exactly why `.report-dlg` was never caught by the closed-dialog trap that
   bit `.profile-dlg` (CLAUDE.md). Give this class a `display` and a closed,
   empty contact sheet paints on every page that includes it; if it ever needs
   one, it needs `.contact-dlg[open]` in the same breath. */
.contact-rows { display: flex; flex-direction: column; gap: 8px; }
/* Links, not text: a tap should open a message or mail rather than ask
   somebody to copy a string off a phone screen. */
.contact-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 10px 12px; border-radius: var(--r-s);
  border: 1px solid var(--border-2); background: var(--surface-2);
}
/* ⚠ PAIRED WITH `hidden`, AND NOT OPTIONAL. The sheet is filled by script, so
   a person with an email and no phone has one row hidden by the attribute —
   which `display: flex` above beats. The ADMIN shell has a blanket
   `[hidden] { display: none !important }` and would be fine; the PLAYER shell
   deliberately has none, so without this a player would get an empty Phone row
   linking to `sms:` while the admin saw it work. One shell working and its
   neighbour not is this trap's signature (CLAUDE.md). */
.contact-row[hidden] { display: none; }
.contact-row:hover { border-color: var(--acc-line); }
/* The fixed line under the name, spacing the rows off it. It was an inline
   `style` back when one page had this dialog; a shared partial with a
   hand-written margin in it is a rule nothing can find. */
.contact-lede { margin-bottom: 12px; }
.contact-k {
  flex: 0 0 52px; font: 700 10px var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--t-muted-2);
}
.contact-v {
  font: 600 13.5px var(--sans); color: var(--t-hi);
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
   VIEWING AS A PLAYER (RULES.md R14) — the bar that says whose pages these are.

   IN SHARED.CSS BECAUSE BOTH SHELLS SHOW IT. `_player_base.html` and the admin
   `_base.html` both load this sheet, and the override is player-blueprint-only
   — so an admin who walks onto an admin page mid-view needs the bar there too,
   from the same rules rather than from a second copy that has to agree.

   NO HUE, DELIBERATELY, and the near misses are all taken:
     --amb    is a live challenge on the player shell, and only that (§5.5);
     --acc    is cyan, which means "this is you" — actively false here;
     --danger means something is wrong, and nothing is;
     --action is "the thing you are meant to press", and a full-bleed orange
              bar out-shouts every real action pill on the page beneath it.

   So: neutral ground, and THE STATE IS A WORD. That is the same conclusion the
   four card washes and the beaten/lost row both arrived at the long way round —
   a colour needs a legend, and a sentence does not.
   --------------------------------------------------------------------------- */
.vabar {
  background: var(--surface-panel);
  border-bottom: 1px solid var(--neg-line);
  /* The one non-hue distinguishing device: a weight of neutral across the top,
     enough that the page reads as sitting under something. */
  box-shadow: inset 0 3px 0 0 var(--neg);
}
.vabar-in {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px;
  max-width: 1100px; margin: 0 auto; padding: 9px 16px;
}
.vabar-what {
  font: 600 11px/1 var(--sans);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--t-court);
}
.vabar-who { font: 600 14px/1.2 var(--sans); color: var(--t-hi); }
.vabar-meta { font: 400 12px/1.2 var(--sans); color: var(--t-court); }
/* Pushes the control to the far end on a wide bar and lets it wrap under on a
   phone, rather than a second breakpoint saying the same thing. */
.vabar-end { margin-left: auto; }
/* The SECONDARY tier the player shell already uses for `.pnav-signout` — an
   outlined box, not a filled one. Ending a view is an ordinary way out, not
   the thing on the screen you are meant to press. */
.vabar-btn {
  font: 600 12px/1 var(--sans);
  color: var(--t-hi);
  background: transparent;
  border: 1px solid var(--border-2);
  border-radius: var(--r-s);
  padding: 7px 12px;
  cursor: pointer;
}
.vabar-btn:hover { border-color: var(--t-court); }
