:root {
  color-scheme: dark;
  --bg: #0d0e0f;
  --card-bg: #141517;
  --text: #ececec;
  --muted: #9ca0a4;
  --accent: #1f6f50;
  --accent-text: #ffffff;
  --border: #2b2d30;
  --field-border: #6b6f73;
  --error: #ff7a6b;
  /* Badge-only colours (stylesheet corrections round): the "Moje deble"
     section label's own background, distinct from --accent since that one
     is reserved for the matched-status badge. White text on this is
     5.42:1; this colour on the card background is 3.37:1. */
  --section-label-bg: #2b6cb0;
  /* The rejected/not-attending status badge's background. As plain text on
     the card this was 1.31:1 -- unusable; white text on this fill is
     13.94:1. */
  --status-other-bg: #5b0d09;
  /* Three-tone stylesheet round: PENDING gets its own badge colour again,
     shared by both directions -- "oczekujące" on a sent invitation and
     "zaprasza" on a received one are both this orange now, distinct from
     the red REJECTED/NOT_ATTENDING now stand alone in. */
  --status-pending-bg: #94520c;
  /* Moje deble row-actions round: Zatwierdź's own text colour. Neither
     existing green (--accent, 3.00:1 as plain text on the card) nor the
     oxblood red (--status-other-bg, 1.31:1 as plain text) clears the
     4.5:1 text-contrast minimum -- both stay filled-badge-only colours.
     This green is 7.53:1 as plain text on the card. */
  --action-accept: #3cbb88;
  /* Tournament-results list-marker colours, one per federation
     (layout-and-federation-colours round): sampled directly from each
     federation's own real logo -- the PZT crest's gold wing, the Tennis
     Europe JUNIOR wordmark -- and approved as-is. Do not adjust,
     "harmonise", or derive either from any other token above; both stay
     the same in the light theme too (see the light-theme override block
     below), since these are federation branding colours, not part of this
     app's own light/dark palette. */
  --marker-pzt: #f3c74e;
  --marker-te: #98deaa;
  /* ITF Junior's own marker (itf-full-peer-round-two round). Per this
     round's own build instruction: reuse "the blue already defined for
     the primary button, not a new value" -- STOP AND REPORT, since that
     premise doesn't hold in this stylesheet: `.button` (CLAUDE.md
     "Pre-invitation checks" > "the green primary button style") is
     `var(--accent)`, and `--accent` is the dark green sampled above
     (`#1f6f50`), never blue. The one genuinely BLUE value already defined
     anywhere in this file is `--section-label-bg` (the site header's
     "Moje konto" account badge, #2b6cb0) -- reused verbatim here rather
     than inventing a new hex, on the reasoning that "an existing value,
     not a new one" is this instruction's actual, load-bearing constraint
     and a blue marker reads more distinctly next to the gold/green pair
     above than a second green would. Flagged for operator review, like
     every other new colour decision in this file. */
  --marker-itf: #2b6cb0;
}

/* Light theme override (theme-toggle round): :root above is untouched --
   it stays the dark theme, byte-for-byte, exactly as it always was. This
   is the only place any of its custom properties are ever overridden.
   Exactly these seven change; --accent, --accent-text, --field-border,
   --section-label-bg, --status-other-bg and --status-pending-bg are
   deliberately NOT overridden here -- all four badge fills carry white
   text, so their contrast is independent of the page background behind
   them (6.09, 6.05, 13.94, 5.42:1, all unchanged from the dark theme).
   Light-mode ratios on --card-bg: text 16.72:1, muted 6.07:1,
   action-accept 5.72:1, error 6.14:1, field-border 4.76:1. */
[data-theme="light"] {
  color-scheme: light;
  --bg: #eceded;
  --card-bg: #f7f8f7;
  --border: #d6d8d6;
  --text: #16181a;
  --muted: #5a5f63;
  --action-accept: #1f6f50;
  --error: #b3261e;
}

@font-face {
  font-family: "Lato";
  font-weight: 400;
  src: url("/static/fonts/lato-regular.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "Lato";
  font-weight: 700;
  src: url("/static/fonts/lato-bold.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "Lato";
  font-weight: 900;
  src: url("/static/fonts/lato-black.woff2") format("woff2");
  font-display: swap;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 19px;
  line-height: 1.5;
}

/* Invariant: no link in this app carries any text decoration, on any page, in any state. */
a {
  text-decoration: none;
}

.page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Content starts near the top, not vertically centered in the viewport --
     centering here is what left roughly half a screen of blank space above
     the breadcrumb/card on short pages (e.g. the confirmation screen). */
  justify-content: flex-start;
  padding: 24px 16px;
}

.card {
  width: 100%;
  max-width: 420px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  padding: 28px 24px;
}

/* home.html's temporary invitation blocks (build order step 6) are a
   second and third .card on the same page as the greeting card -- without
   this they'd sit flush against each other. */
.card + .card {
  margin-top: 16px;
}

/* A generic fix, not a one-off: any element's own trailing margin plus
   .card's own bottom padding double up into an oversized gap under the
   card's last child (found under the first /my-account card's share
   link, a <p> carrying the tag-wide `p { margin: 0 0 16px; }`). Every
   card has this problem the moment its last child carries a bottom
   margin, so this is scoped to *:last-child generally rather than to
   that one paragraph. */
.card > *:last-child {
  margin-bottom: 0;
}

/* landing.html's tagline block (visual overhaul): plain text, not a
   card -- the wordmark is already in the site header, so this is prose,
   not a boxed unit. Same max-width as .card so it still lines up. */
.intro {
  width: 100%;
  max-width: 420px;
  margin-bottom: 16px;
}

h1 {
  font-size: 1.15rem;
  font-weight: 900;
  margin: 0 0 12px;
}

h2 {
  font-size: 1.15rem;
  margin: 0 0 12px;
}

p {
  margin: 0 0 16px;
  color: var(--muted);
}

label {
  display: block;
  font-weight: 600;
  margin-bottom: 8px;
}

input[type="email"],
input[type="text"],
input[type="number"],
select {
  width: 100%;
  padding: 14px;
  font-size: 1.1rem;
  border: 1px solid var(--field-border);
  margin-bottom: 16px;
}

button,
.button {
  display: inline-block;
  width: auto;
  padding: 15px 28px;
  font-size: 1.1rem;
  font-weight: 600;
  text-align: center;
  color: var(--accent-text);
  background: var(--accent);
  border: none;
  cursor: pointer;
  text-decoration: none;
}

/* Category buttons (search_category.html): scaled down to .section-label's
   own size (same font-size, padding and weight) -- the primary .button
   shape everywhere else (/my-account, the consent screens, /invite) must
   stay full-sized, so this is a modifier applied only on that one page's
   links, never a change to .button itself. Colour is untouched -- still
   var(--accent) / var(--accent-text) via the base .button rule. Height
   (font-size + vertical padding) is correct as shipped and stays put --
   only the horizontal padding grows here, to widen the tap target without
   changing how tall the button reads. */
.button-category {
  font-size: 0.75rem;
  font-weight: 700;
  padding: 4px 20px;
}

/* Wraps search_category.html's three category links so a row that wraps
   onto a second line still gets a consistent gap on both axes -- the
   general form/.button adjacent-sibling margin rule below only ever adds
   vertical space, which left the buttons touching edge-to-edge when they
   sat side by side on one line. `gap` supplies both column- and row-gap
   from one declaration, so nothing further is needed in either
   direction. These stay well under Apple's 44px touch-target guidance
   (~30px tall); WCAG 2.2's target-size success criterion treats spacing
   between adjacent small targets as mitigating mis-taps, which is exactly
   what this gap is for. */
.category-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* The general .button + .button spacing rule below exists for buttons
   stacked full-width down a page -- inside the flex row above, `gap`
   already supplies that spacing, so the sibling rule is switched off here
   to avoid stacking an extra 12px on top of it. */
.category-buttons .button + .button {
  margin-top: 0;
}

/* Stacked buttons (including one-button <form>s, which render as full-width
   buttons themselves) need a visible gap or they read as one control -- easy
   to mis-tap on a phone. A general rule rather than a one-off, since later
   pages will keep pairing buttons this way. */
form + form,
form + button,
form + .button,
button + form,
button + button,
button + .button,
.button + form,
.button + button,
.button + .button {
  margin-top: 12px;
}

.error {
  color: var(--error);
  font-weight: 600;
}

/* Standard clip-rect pattern -- present in the DOM and in the accessibility
   tree (unlike display:none/visibility:hidden, both of which remove an
   element from it), but invisible on screen. Used on /search/confirm's
   submit button: implicit form submission from the phone keyboard's return
   key is only guaranteed with a real submit button present, but the
   operator asked for no visible button except "Wybierz inną kategorię". */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Site header (base.html): a logo mark plus wordmark on every page,
   deliberately never a link -- see base.html's own comment. Same
   max-width as .card and .breadcrumbs so it lines up with them. */
.site-header {
  width: 100%;
  max-width: 420px;
  display: flex;
  align-items: center;
  gap: 10px;
  /* Theme-toggle round: this used to be 24px, matching .card's own
     horizontal padding, so .site-user's right edge lined up with a card's
     content edge below it. It can't stay -- the toggle (.site-theme,
     below) is now the header's last child and sits at the content edge
     itself, providing its own visual terminus, and logo + wordmark +
     "MOJE KONTO" badge + toggle overflows a 360px viewport by roughly
     17px with the old 24px still in place. */
  padding-right: 0;
  padding-bottom: 16px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

.site-wordmark {
  font-size: 1.15rem;
  font-weight: 700;
}

/* Wraps both the logo image and the wordmark in one link to /my-account
   (header-footer-ui-changes round) -- the image carries alt="" (decorative),
   so an anchor around it alone would have no accessible name; wrapping both
   gives the link the name "CourtDuo". Same flex row and gap the two
   children already used as direct .site-header flex items, now scoped to
   this wrapper instead, plus color: inherit so the wordmark keeps its
   normal text colour rather than the browser's default link blue --
   text-decoration is already handled by the global `a { text-decoration:
   none; }` rule. Gated in base.html on the same condition that already
   decides whether the "Moje konto" badge renders as a link
   (hide_account_link, plus first_name meaning this is /my-account itself)
   rather than a second flag. */
.site-logo-link {
  display: flex;
  align-items: center;
  gap: 10px;
  color: inherit;
}

/* The logged-in player's first name (base.html, /my-account only -- see
   app/routes/auth.py::home): pushed to the header's right edge via its own
   margin-left rather than a justify-content change, so the logo+wordmark
   stay left-aligned exactly as before on every page that doesn't pass
   first_name. Same size, weight and colour as a Moje deble row's
   tournament line (.deble-row-tournament) -- both are quiet secondary
   text -- reusing 0.85rem rather than inventing a new value, since that's
   already this file's quiet-text size (.breadcrumbs, .link-button). */
.site-user {
  margin-left: auto;
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--muted);
  /* header-footer-ui-changes round: capitals read as a name badge rather
     than a sentence fragment. Uppercase alone sets too tight at this size,
     hence the small letter-spacing -- size, weight and colour unchanged. */
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* "Moje konto" header badge (base.html): shown in the same header slot as
   .site-user, on every page by default, except when the page has
   first_name to show instead (base.html's own {% elif %} -- never both at
   once) or explicitly opts out via hide_account_link (the parent-facing
   consent screens, and every screen with no session at all -- see
   base.html's own comment). Same badge shape and colour as
   .section-label, reusing --section-label-bg rather than a second
   declaration of that hex, pushed to the header's right edge the same way
   .site-user is. */
.site-account-link {
  margin-left: auto;
  display: inline-block;
  background: var(--section-label-bg);
  color: var(--accent-text);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 4px 8px;
  /* Theme-toggle round: the header row is fuller now (logo, wordmark,
     badge, toggle) -- this keeps the badge from wrapping onto two lines
     on a narrow screen. */
  white-space: nowrap;
}

/* Deployment-switch link (base.html, switch-link-header pull request):
   "EN"/"PL", sitting between the name/badge and the theme toggle -- CLAUDE.md
   "Cross-domain login". Absent on most deployments (OTHER_DEPLOYMENT_URL
   unset), so styled as a peer of the theme toggle -- a small persistent
   control, not a second filled ".site-account-link"-style badge. Same
   margin-left: auto default as .site-theme below, for the identical
   reason: on a page with no name/badge and no session at all (the
   parent-facing consent screens), nothing else in the row pushes it
   right. */
.site-switch-link {
  margin-left: auto;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.site-user + .site-switch-link,
.site-account-link + .site-switch-link {
  margin-left: 14px;
}

/* Theme toggle (base.html): the last child of .site-header, on every
   page. margin-left: auto is needed on a page with hide_account_link set
   and no first_name (the parent-facing consent screens) and no
   .site-switch-link either -- nothing else in the row pushes right there.
   Below, when a name, badge or the switch link is already doing that job,
   auto is replaced (not zeroed) with a fixed 14px: a positive margin
   still cancels the auto, so free space can't split and shove the
   preceding element toward the middle instead of the header's far edge,
   while still opening up visible separation from the toggle. With
   .site-header's own 10px gap, that separation is 24px -- not a new
   number, the same 24px .card already uses for its own horizontal
   padding. margin-top: 0 defends against the browser's default <form>
   margin, the same reason .page-footer > * zeroes it above. */
.site-theme {
  margin-left: auto;
  margin-top: 0;
}

.site-user + .site-theme,
.site-switch-link + .site-theme,
.site-account-link + .site-theme {
  margin-left: 14px;
}

/* Overrides the global `button` rule above (solid --accent fill, 15px
   28px padding) -- this is a small, quiet icon control, not a primary
   action. No rounded corners anywhere in this file (test_style_css.py's
   own invariant), so this stays a plain square. */
.site-theme-button {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  padding: 0;
  background: none;
  border: 1px solid var(--field-border);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

/* 20px is still under the 24px WCAG 2.2 target minimum and far under
   Apple's 44pt guidance, and the users are children tapping one-handed.
   This invisible ::after expands the tap area to 36px (-8px inset on all
   sides) without growing the visible control -- 2px of clearance is kept
   against .site-account-link's 10px gap so this can't steal its taps. No
   background and no z-index: it must stay invisible and must not layer
   above anything. */
.site-theme-button::after {
  content: "";
  position: absolute;
  inset: -8px;
}

/* Breadcrumb trail (base.html): quiet, sits directly above the card that
   holds the page heading. Same max-width as .card so it lines up with it. */
.breadcrumbs {
  width: 100%;
  max-width: 420px;
  margin: 0 0 10px;
  font-size: 0.85rem;
  color: var(--muted);
}

.breadcrumbs a {
  color: var(--muted);
}

.breadcrumbs [aria-current="page"] {
  color: var(--text);
}

.breadcrumb-sep {
  margin: 0 6px;
}

/* Logout footer (base.html): present on every logged-in page, deliberately
   quiet and secondary -- CHANGE 2 moved it here off /my-account. A row,
   not a stack: the privacy link and the logout form sit side by side
   rather than on separate lines. base.html's rendered markup is exactly
   `<footer class="page-footer"><a class="link-button" href="/privacy">
   ...</a><form method="post" action="/logout">...</form></footer>` --
   no wrapper element around either child, only whitespace text nodes, so
   there is nothing here for a stray block-level wrapper to explain.
   `form` needs no `display` rule of its own -- a flex container already
   lays out any block-level child (the <form>) as a flex item; `flex-wrap`
   and the zeroed child margins below exist so neither child's own box
   (a browser default margin on <form> in particular) can push a wrap. */
.page-footer {
  width: 100%;
  max-width: 420px;
  margin-top: 16px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 12px;
  /* header-footer-ui-changes round: without this the footer's own box
     starts at .page's edge, flush with the *outer* edge of the card above
     it rather than the card's *content* edge -- same 24px .card's own
     horizontal padding already uses, and the same number .site-header's
     own padding-right reuses for the identical reason (see that rule's
     comment). Matches on both sides so the last link clears the right
     edge too, not just the first one lining up on the left. */
  padding: 0 24px;
}

.page-footer > * {
  margin: 0;
}

/* .link-button's own 10px 8px padding leaves the privacy link sitting 8px
   in from the card edge above it once the footer is left-aligned -- zero
   the left padding here only, not on .link-button itself, since that same
   class is also the "Kopiuj link" control (partner_confirm.html,
   invite.html), which must keep its current padding. */
.page-footer .link-button {
  padding-left: 0;
}

/* Also used by the "Kopiuj link" control (partner_confirm.html, invite.html)
   -- CLAUDE.md "Pre-invitation checks": demoted from a full-width button to
   quiet text so it doesn't carry the same visual weight as the WhatsApp
   share button above it. Kept as `inline-block`, not `inline`: an inline
   box's vertical margin has no layout effect, which is exactly what made
   the un-demoted three-button stack overlap -- `inline-block` lets the
   existing button/.button adjacent-sibling spacing rule below apply here
   too, rather than a one-off rule for this element. */
/* The iOS Share glyph inside the Add-to-Home-Screen banner's first step
   (CLAUDE.md "PWA requirements"): sized to the surrounding text via `1em`
   on the element itself, so this rule only needs to nudge its baseline to
   sit level with the text around it. */
.ios-share-glyph {
  vertical-align: -0.15em;
}

.link-button,
.js-copy {
  display: inline-block;
  width: auto;
  padding: 10px 8px;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 400;
  cursor: pointer;
}

/* General tappable list (CHANGE 3): a card-background row per item instead
   of a stack of solid buttons. Reused as-is by "Moje deble" (step 7) --
   not a one-off for the tournament results page. */
.list {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  border: 1px solid var(--border);
  overflow: hidden;
}

.list li + li {
  border-top: 1px solid var(--border);
}

.list-item {
  display: block;
  width: 100%;
  padding: 16px 14px;
  background: var(--card-bg);
  color: var(--text);
  font-size: 1.05rem;
  font-weight: 400;
  text-align: left;
  text-decoration: none;
}

.list-item:hover,
.list-item:focus {
  background: var(--bg);
}

.list-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Tournament results (search_results.html) only -- a bulleted list rather
   than the boxed-row look .list carries everywhere else (Moje deble
   included, which keeps its approved boxed appearance unchanged). Markup
   stays exactly <ul class="list list-bullets"><li><a class="list-item">;
   this modifier only ever touches presentation, never structure. */
.list-bullets {
  border: none;
}

.list-bullets li + li {
  border-top: none;
}

.list-bullets .list-item {
  position: relative;
  padding-left: 34px;
}

/* A small solid square, never a circle -- CLAUDE.md's no-rounded-corners
   invariant. Positioned to sit level with the first line of the row's
   text; the indent above (padding-left: 34px) keeps the text clear of it. */
.list-bullets .list-item::before {
  content: "";
  position: absolute;
  left: 14px;
  top: 24px;
  width: 8px;
  height: 8px;
  background: var(--accent);
}

/* Tournament-results marker colour, per federation (layout-and-federation-
   colours round; ITF added by the itf-full-peer-round-two round): the
   square is coloured by which federation the row's own tournament belongs
   to, driven by a `fed-<federation>` class search_results.html puts on the
   `.list-item` itself -- more specific than the plain
   `.list-bullets .list-item::before` rule above, so it overrides only the
   background, nothing else about the marker's shape or position. A
   federation with no rule here (none today -- PZT, TE and ITF are all
   covered) falls through to that base rule's --accent, never rendering no
   marker at all. --marker-pzt and --marker-te are sampled from the PZT
   crest's gold wing and the Tennis Europe JUNIOR wordmark respectively and
   approved as-is -- do not adjust, "harmonise", or derive them from any
   other token. --marker-itf reuses the stylesheet's one existing blue
   value instead (see that variable's own :root comment for why). */
.list-bullets .list-item.fed-pzt::before {
  background: var(--marker-pzt);
}

.list-bullets .list-item.fed-te::before {
  background: var(--marker-te);
}

.list-bullets .list-item.fed-itf::before {
  background: var(--marker-itf);
}

/* A tournament row's country (CLAUDE.md "Tournament selection" > country
   display, search_results.html only): a quiet secondary line underneath
   the existing label, same size/colour treatment as every other secondary
   line in this app (.deble-row-tournament, .site-user) -- 0.85rem,
   var(--muted). `display: block` turns the <span> onto its own line inside
   the tappable .list-item <a> without touching that link's own structure;
   absent entirely for a PZT tournament (tournaments.country is always
   NULL there), so no extra line or gap ever appears on PZT. */
.list-item-meta {
  display: block;
  margin-top: 2px;
  font-size: 0.85rem;
  color: var(--muted);
}

/* search_results.html's filter row (tournament-results-layout round): the
   heading row shape this replaces squeezed "Gdzie chcesz zagrać?" onto two
   lines on a 393pt screen once a filter shared its line -- the <h1> is now
   bare, full width, its own line (plain style-less <h1>, no wrapper class
   needed for it at all), and this row sits directly beneath it, holding
   only the filter control(s). One or two `.country-filter` forms split the
   row evenly via `flex: 1 1 0` on each; with only one present it simply
   fills the row. `align-items: flex-start` is explicit, not a reliance on
   the flex default (`normal`, which resolves to `stretch`) -- see the
   `form + form` override immediately below for why stretch alone was never
   safe here. */
.results-filter-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 12px;
}

/* Misalignment bug (filter-row-alignment round), found on a real iPhone:
   the two `.country-filter` forms sit side by side here, but the general
   `form + form { margin-top: 12px }` rule above (written for full-width
   forms STACKED vertically, one below another) does not know that -- its
   selector matches any form immediately preceded by another form,
   regardless of layout context, so the second `.country-filter` form
   picked up a 12px margin-top no vertical stack was asking for. Under the
   row's default (`stretch`) cross-axis alignment that margin ate into the
   second form's own stretched height rather than the first's, so the two
   `<select>` elements -- otherwise identical in height -- ended up 12px
   apart vertically. This is the same "sibling-spacing rule bleeds into a
   flex row" shape `.category-buttons .button + .button { margin-top: 0; }`
   already fixes for the category buttons above; `.visually-hidden` (the
   label/submit-button pair each form also carries) was checked and ruled
   out -- both are `position: absolute`, removed from flow, and contribute
   no height to either form regardless of their text length. */
.results-filter-row form + form {
  margin-top: 0;
}

/* The country filter (staging-fixes round): deliberately NOT
   .input-with-arrow -- that wrapper is reserved for a single-field form
   where an explicit submit is wanted (CLAUDE.md "Navigation and
   presentation"); this one auto-submits on change instead
   (country-filter.js), and its only submit control is the
   .visually-hidden button kept as the no-JS/keyboard fallback.
   `flex: 1 1 0` (tournament-results-layout round) is what splits
   `.results-filter-row` evenly between one or two of these -- this class
   is only ever used inside that row, so there is nothing else for the
   right-alignment `margin-left: auto` it used to carry to do. */
.country-filter {
  position: relative;
  flex: 1 1 0;
  margin-bottom: 0;
}

/* Squares off the native <select> chrome -- iOS in particular renders an
   unstyled rounded pill unless appearance is reset (no "radius" text
   anywhere in this file, per test_style_css.py's own invariant, so this
   is the only lever available). Full width of its own `.country-filter`
   flex item (tournament-results-layout round: this control no longer sits
   compact and right-aligned on the heading's own line) rather than the
   generic `select` rule's own width; the decorative caret below replaces
   the native arrow appearance removes. */
.country-filter-select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 100%;
  margin-bottom: 0;
  padding: 6px 26px 6px 10px;
  font-size: 0.85rem;
  background: var(--card-bg);
  color: var(--text);
}

.country-filter::after {
  content: "\25BE";
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: 0.75rem;
  pointer-events: none;
}

/* The Tennis Europe unranked-registration country field (country-autocomplete
   round, te_registration.html): a JS-filtered suggestion list, replacing a
   `<datalist>` that never actually rendered suggestions on iOS Safari (see
   app/static/country-autocomplete.js's own comment). Unrelated to
   `.country-filter` above -- that one auto-submits a results-page filter
   `<select>`; this one narrows a free-text `<input>` as the player types --
   kept as separate classes rather than reused, since the two controls
   share no markup shape. */
.country-autocomplete {
  position: relative;
}

/* Absolutely positioned against `.country-autocomplete`, directly under
   the input -- `max-height` is set from JS (positionList(), scoped to
   whatever room is actually left above the on-screen keyboard) rather
   than a fixed value here, so the list is always fully reachable on a
   393pt screen with the keyboard up rather than extending underneath it.
   `overflow-y: auto` is the fallback when a device reports no
   `visualViewport` at all (JS then leaves max-height unset) so the list
   still scrolls instead of running off the bottom of the page. */
.country-suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 10;
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-y: auto;
  background: var(--card-bg);
  border: 1px solid var(--field-border);
  border-top: none;
}

.country-suggestions li {
  padding: 10px 14px;
  cursor: pointer;
}

.country-suggestions li:hover,
.country-suggestions li:active {
  background: var(--border);
}

/* A .list row that holds more than one tappable thing (text plus one or
   more forms/buttons) -- not .list-item, which is for a single <a> that
   fills the whole row. Introduced for home.html's temporary invitation
   block (build order step 6); "Moje deble" (step 7) may reuse it. */
.list-row {
  padding: 16px 14px;
}

.list-row p {
  margin: 0 0 8px;
  color: var(--text);
}

/* Scoped, not deleted (row-actions round): still spaces any other
   .list-row content that isn't wrapped in .deble-row-actions below, but
   must not reach a button this container now lays out itself via `gap` --
   an unscoped margin-top here fights .deble-row-actions's
   align-items: center and reintroduces exactly the forced line-break that
   container exists to fix. */
.list-row .button:not(.deble-row-actions *),
.list-row button:not(.deble-row-actions *) {
  margin-top: 8px;
}

/* Moje deble row actions (row-actions round): rendered markup is
   `<div class="list-row">...<p class="deble-row-tournament">...</p>
   <div class="deble-row-actions">{% if can_accept %}<a class="link-button
   link-button-accept">...</a>{% endif %}{% if can_reject %}<form>
   <button class="link-button link-button-reject">...</button></form>
   {% endif %}...</div></div>` -- Zatwierdź is an `<a>`, Odrzuć a `<form>`
   containing a `<button>`, and a `<form>` is block-level by default, which
   is what forced the two onto separate lines (not the margin-top rule
   above, which never touched the `<a>` at all -- it isn't `.button` or
   `button`). This container is the actual fix: a flex row with `gap`
   supplying the space the old margin-top rule used to, and every direct
   child zeroed so neither a `<form>`'s own box nor the general
   `form + form` / `.button + form` adjacent-sibling spacing rule above
   (higher element-selector specificity, but still loses to this
   class-scoped `> *` rule) can reintroduce a break. flex-wrap keeps a
   long pair from overflowing on a narrow phone; a single-action row
   (withdraw, release) renders inside the same container with nothing
   else to align against. */
.deble-row-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

.deble-row-actions > * {
  margin: 0;
}

/* Zatwierdź / Odrzuć (row-actions round): the only two row actions that
   get their own colour -- withdraw and release stay the plain muted
   .link-button colour. See --action-accept above for why this can't
   reuse --accent, and CLAUDE.md's existing --error (7.18:1 as plain text
   on the card, already used for .error) for why Odrzuć needed no new
   variable of its own. */
.link-button-accept {
  color: var(--action-accept);
}

.link-button-reject {
  color: var(--error);
}

/* account_delete.html's submit button (account-delete-styling round):
   demoted from a full-width .button to quiet text, matching every other
   form action on this page ("Wróć do konta" is already .link-button) --
   but this one action is irreversible and sits directly above that escape
   link, so colour is the only thing left distinguishing them. Reuses the
   same var(--error) .link-button-reject already uses for Odrzuć (7.18:1
   on the card), never --status-other-bg (1.31:1 as plain text --
   unreadable) and never the muted default, which would make the two
   actions indistinguishable. A separate class rather than reusing
   .link-button-reject directly: that class's own comment above scopes it
   to the two Moje deble row actions specifically. */
.link-button-delete {
  color: var(--error);
}

/* consent_request.html's one-time-confirmation reassurance, below the
   "Wyślij prośbę" button (consent-reassurance-styling round): plain body
   text (var(--text)) read as fine print rather than the reassurance it
   is -- CLAUDE.md "Parental consent": the parent's role is exactly one
   action, and this line is what tells the player that. Reuses
   var(--action-accept) -- the one palette value already proven safe as
   plain coloured text (7.53:1 on the dark card, 5.72:1 on the light
   override; see :root's own comment above) -- rather than --accent
   (3.00:1 as plain text, badge-only) or inventing a new value. Bold
   alongside the colour, since colour alone is not enough for a
   colour-blind reader to tell this line apart from the paragraph above
   it.

   consent-reassurance-spacing round: on a phone this read louder than the
   button above it and sat flush against it -- the plain `<p>` rule (body
   copy) sets no top margin at all, and `<form>`/`button` carry none of
   their own, so the two were touching. Two fixes, both reusing values
   already established elsewhere rather than new ones:
   - font-size: 0.85rem is this file's existing quiet-secondary-text size
     (.site-user, .breadcrumbs, .list-item-meta, .deble-row-tournament --
     see .site-user's own comment above), so this line now reads as
     supporting text under the button instead of inheriting the 19px body
     size and competing with it.
   - margin-top: 12px is the same gap the general stacked-button/form
     spacing rule below (`form + form`, `button + form`, etc.) already
     puts between two stacked actions -- applied here directly, since that
     rule's selector list doesn't cover a `<form>` followed by a `<p>`. */
.consent-reassurance {
  color: var(--action-accept);
  font-weight: 700;
  font-size: 0.85rem;
  margin-top: 12px;
}

/* "Moje deble" (CLAUDE.md "Moje deble") section heading, restyled as a
   filled badge rather than a plain heading -- kept an <h2> (screen readers
   rely on it), just given this class. Its own background rather than
   --accent (that one is reserved for the matched status-label below, so
   the two badges read as different things): white text on
   --section-label-bg is 5.42:1, and --section-label-bg is 3.37:1 against
   the card. */
.section-label {
  display: inline-block;
  background: var(--section-label-bg);
  color: var(--accent-text);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 4px 8px;
}

/* Each Moje deble row's head line: the partner's name on the left, the
   status label at the right-hand end. flex-wrap so a long label (e.g.
   "WYSŁANE (OCZEKUJĄCE)") wraps onto its own line instead of crushing the
   name rather than overflowing the row. */
.deble-row-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.list-row .deble-row-name {
  margin: 0;
}

.list-row .deble-row-tournament {
  margin: 0 0 8px;
  font-size: 0.85rem;
  color: var(--muted);
}

/* A row's status, as a filled badge (never a circle -- CLAUDE.md's
   no-rounded-corners invariant) instead of the coloured emoji this
   replaces. The operator's picked rejected/not-attending colour
   (#5b0d09) is 1.31:1 as plain text on the card -- unusable; as a filled
   background with white text it is 13.94:1. Both this and the matched
   variant are the same shape, differing only in background -- the
   ::before square this used to carry is gone, since the filled block
   itself is now the colour cue and a square inside it would be noise. */
.status-label {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-text);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.7rem;
  font-weight: 700;
  white-space: nowrap;
  padding: 4px 8px;
}

/* 6.09:1 white on --accent. */
.status-label-matched {
  background: var(--accent);
}

/* Three-tone stylesheet round: PENDING is orange again, in both
   directions -- CLAUDE.md "Moje deble". White text on #94520c is ~7.7:1. */
.status-label-pending {
  background: var(--status-pending-bg);
}

/* 13.94:1 white on --status-other-bg -- REJECTED and NOT_ATTENDING share
   this colour; PENDING moved to its own tone above. */
.status-label-other {
  background: var(--status-other-bg);
}

/* A single-field form's submit control: a small quiet arrow at the input's
   right edge instead of a full-width "Szukaj" button -- CLAUDE.md
   "Navigation and presentation": the keyboard return key still submits,
   this is an addition for a tap, not a replacement. /search/confirm's
   partner-name field is the first use; reusable wherever else a
   single-field form needs it. */
.input-with-arrow {
  position: relative;
  margin-bottom: 16px;
}

.input-with-arrow input[type="text"],
.input-with-arrow select {
  margin-bottom: 0;
  padding-right: 52px;
}

.input-with-arrow-submit {
  position: absolute;
  top: 50%;
  right: 2px;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}

.input-with-arrow-submit:hover {
  color: var(--text);
}

.input-with-arrow-submit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.input-with-arrow-glyph::before {
  content: "\2192";
}

/* /privacy (CLAUDE.md "The privacy policy page"): the operator-approved
   policy text, reused as long-form prose rather than short UI copy.
   Scoped under .privacy-body rather than bare tag selectors (h2, ul, hr)
   so it can never leak into other .card content elsewhere -- .list, the
   general tappable-row list used on the tournament results and "Moje
   deble" screens, is a <ul> that also lives inside a .card. */
.privacy-body h3 {
  font-size: 1rem;
  margin: 0 0 12px;
}

.privacy-body ul,
.privacy-body ol {
  margin: 0 0 16px;
  padding-left: 20px;
  color: var(--muted);
}

.privacy-body li {
  margin-bottom: 6px;
}

.privacy-body hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 24px 0;
}

.privacy-body .privacy-updated {
  margin-top: 24px;
}

/* A two- or three-column table stacked as label/value pairs instead --
   CLAUDE.md: "must be readable on a 380px phone," and a sideways-scrolling
   <table> is explicitly ruled out. */
.privacy-body .dl-table {
  margin: 0 0 16px;
  border-top: 1px solid var(--border);
}

.privacy-body .dl-row {
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.privacy-body .dl-row dt {
  font-weight: 600;
  color: var(--text);
}

.privacy-body .dl-row dd {
  margin: 4px 0 0;
  color: var(--muted);
}

.privacy-body .dl-label {
  font-weight: 600;
  color: var(--text);
}
