/* Krubal is a light product (29 Sep). Telegram's SDK publishes --tg-theme-*
   variables on :root regardless of what our JS does, so any rule that reads them
   follows the viewer's Telegram theme. That produced white text on white cards
   on a dark-mode phone. Nothing here may reference --tg-theme-*; the palette
   below is the single source of truth. */
:root {
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #6b7280;
  --accent: #0f7b3a;
  --accent-fg: #ffffff;
  --accent-ink: #0f7b3a;
  --card-bg: #f5f6f8;
  --border: #e5e7eb;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --gutter: 16px;
  /* ---- CSP-200: one typography system — a single stack + type scale, shared
     (by value) across the Mini App, the public pages, admin, the printable
     statement and the landing page, so the UI reads as one product instead
     of five. No webfont download: system-ui first, with Khmer fallbacks so
     Khmer glyphs never fall back to whatever the device happens to pick. */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans Khmer", "Khmer Sangam MN", "Khmer OS System", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --text-xs: 12px;
  --text-sm: 14px;
  --text-base: 16px;
  --text-lg: 18px;
  --text-xl: 22px;
  --text-2xl: 28px;
  --leading: 1.45;
  --leading-tight: 1.2;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f1115;
    --fg: #f3f4f6;
    --muted: #9ca3af;
    --card-bg: #1a1d23;
    --border: #2a2e37;
  }
}

:root[data-theme="dark"] {
  --bg: #0f1115;
  --fg: #f3f4f6;
  --muted: #9ca3af;
  --card-bg: #1a1d23;
  --border: #2a2e37;
}

/* ---- CSP-85/CSP-107: colour palettes (data-palette on <html>) -------------
   Palette is mostly a SEPARATE axis from light/dark above: light/dark still
   owns --bg/--fg/--muted/--card-bg/--border for every palette except
   "default", which is now the warm-orange look (Yeng, 30 Sep 2026 —
   docs/Design/2026-09-30-theme-inspiration/README.md has the token table) and
   overrides those surface tokens too, since none of the reference screens use
   a stark white paper. Every other palette only sets the hue tokens —
   --accent (fills: buttons, badges, the selected calendar day), --accent-fg
   (text placed ON that fill) and --accent-ink (the accent used AS text
   against the page/card, e.g. links and the active nav label). Two colours
   per hue, same reason the app already forks .tier-kyu/.tier-dan by theme
   below: a fill that reads well with dark text on a light page often needs a
   lighter tint to read as text on a dark one. Contrast ratios (WCAG relative
   luminance) are noted next to each value; see the CSP-107 report for the
   working. Never reference --tg-theme-* here either — same rule as above.
   Every palette name is a real, self-contained block (tests/test_themes.py
   checks this). --tint and --accent-2 are fill/border tokens ONLY — never
   text on the page (mustard-on-cream is 1.7:1, nowhere near AA). */
:root[data-palette="default"] {
  --bg: #fbf5ef;         /* cream — none of the references use stark white */
  --card-bg: #ffffff;
  --fg: #1f1b18;         /* warm near-black; 15.8:1 on --bg */
  --muted: #5a6270;      /* darkened from the shared #6b7280 — that was only
                            4.47:1 on this palette's cream --bg, just under AA;
                            5.68:1 on cream, 6.15:1 on the white card */
  --border: #efe0d2;     /* warm, not the shared cool gray, on cream cards */
  --accent: #f26b1d;
  --accent-fg: #1f1b18;  /* 5.61:1 on --accent */
  --accent-ink: #b44a0c; /* 4.94:1 on cream --bg, 5.35:1 on the white card */
  --tint: #fde2cf;       /* peach fills — banners, the Last-played highlight */
  --accent-2: #f5b52b;   /* mustard — fills/borders/progress only */
}

:root[data-palette="green"] {
  /* CSP-107: this was the whole app's only look until today (CSP-85's
     original "default") — kept selectable rather than deleted (Yeng: the
     picker gets a green swatch, not a removed one). --bg/--fg/--card-bg stay
     the shared base below; only the hue tokens are its own. */
  --accent: #0f7b3a;
  --accent-fg: #ffffff;  /* 5.36:1 on --accent */
  --accent-ink: #0f7b3a; /* 5.36:1 on white */
}

:root[data-palette="rosy"] {
  --accent: #f06595;
  --accent-fg: #1a1a1a;  /* 5.80:1 on --accent */
  --accent-ink: #a61e4d; /* 7.21:1 on white */
}

:root[data-palette="khmer"] {
  /* Cambodian flag: blue + red as the two poles, white/near-white as the
     paper (the shared --bg already is). Colour only — no Angkor Wat
     silhouette anywhere. --accent-2 is the red pole: used only where a
     border/fill already existed (the "needs you" urgent-card tint below),
     never as body text, so its light/dark split doesn't need its own ink. */
  --accent: #032ea1;
  --accent-fg: #ffffff;  /* 11.00:1 on --accent */
  --accent-ink: #032ea1; /* 11.00:1 on white — the blue does double duty */
  --accent-2: #e00025;   /* flag red; 5.00:1 with white text where it's ever a fill */
}

:root[data-palette="sport"] {
  /* CSP-205: "Sport" — bright pitch green + deep black, a deliberately
     harder-edged look (sharp corners, chevron/slash patterns, scoped to
     [data-palette="sport"] further down this file). Own
     --bg/--fg/--card-bg/--muted/--border, same reason "default" above has
     its own: a stark shared white doesn't suit this palette either. */
  --bg: #f4f6f2;
  --card-bg: #ffffff;
  --fg: #0b0b0b;          /* 18.10:1 on --bg */
  --muted: #5a6270;       /* shared #6b7280 is only 4.45:1 on this bg, just
                             under AA — darkened the same way "default" does;
                             5.65:1 on --bg, 6.15:1 on the white card */
  --border: #0b0b0b1f;    /* ~12% black, not the shared cool gray */
  --accent: #39e75f;      /* bright pitch green */
  --accent-fg: #0b0b0b;   /* 11.97:1 on --accent */
  --accent-ink: #0a7a2f;  /* 5.03:1 on --bg, 5.47:1 on the white card */
}

/* CSP-247: Michael's two brand directions, "azure" (azure + graphite) and
   "signal" (signal red + black). Both own their surface tokens, same reason
   "default" and "sport" do: a cool gray ground (#E8ECEF) under white cards,
   ink in the support colour. Michael's gray #929BA5 is only 2.37:1 on the
   ground and 2.82:1 on a white card, so it is never --muted (text); --muted
   is the darkened gray "default" and "sport" already use. The primaries stay
   exact as fills; as text on the gray ground neither clears AA (#087EB4
   3.80:1, #D91F2A 4.23:1), so only --accent-ink is darkened. The Home app bar
   crest + wordmark, the hero band, the section labels and the outline
   secondary button are scoped to these two palettes at the end of this file. */
:root[data-palette="azure"] {
  --bg: #e8ecef;
  --card-bg: #ffffff;
  --fg: #343c46;          /* graphite; 9.40:1 on --bg, 11.17:1 on the card */
  --muted: #5a6270;       /* 5.17:1 on --bg, 6.15:1 on the card */
  --border: #d3d9de;      /* hairlines; Michael's #929BA5 lightened */
  --accent: #087eb4;
  --accent-fg: #ffffff;   /* 4.51:1 on --accent */
  --accent-ink: #076a99;  /* 5.01:1 on --bg, 5.95:1 on the card */
  --tint: #d6eaf4;        /* pale azure; fills only */
  --accent-2: #ff7318;    /* Michael's orange (team avatars); urgent and pending borders, the Collect ring. His gray stays for borders only */
}

:root[data-palette="signal"] {
  --bg: #e8ecef;
  --card-bg: #ffffff;
  --fg: #171b20;          /* black; 14.56:1 on --bg, 17.30:1 on the card */
  --muted: #5a6270;       /* 5.17:1 on --bg, 6.15:1 on the card */
  --border: #d3d9de;
  --accent: #d91f2a;
  --accent-fg: #ffffff;   /* 5.02:1 on --accent */
  --accent-ink: #b81a24;  /* 5.50:1 on --bg, 6.54:1 on the card */
  --tint: #f9dadc;        /* pale red; fills only */
  --accent-2: #ff7318;    /* same orange as azure: a status cue, not the brand gray */
}

@media (prefers-color-scheme: dark) {
  /* green's light ink (#0f7b3a) is only 3.52:1 on the dark surface below —
     dormant today (app.js pins data-theme="light" on every boot) but still a
     real fail the moment dark mode is switched on, so it gets the same
     brighter-on-dark treatment as the others rather than a silent gap. */
  :root:not([data-theme="light"])[data-palette="green"] { --accent-ink: #7ed491; } /* 10.54:1 on dark bg, 9.42:1 on dark card */
  :root:not([data-theme="light"])[data-palette="rosy"]  { --accent-ink: #f783ac; } /* 7.92:1 on dark bg, 7.07:1 on dark card */
  :root:not([data-theme="light"])[data-palette="khmer"] { --accent-ink: #7ca8ff; } /* 8.01:1 on dark bg, 7.16:1 on dark card */
  /* default's cream is a LIGHT-mode look only — in dark mode it falls back to
     the same shared dark surface as every other palette (higher specificity
     than the plain :root[data-palette="default"] block above, so this wins
     regardless of source order), keeping just the brighter-on-dark ink
     (identical value to the retired "orange" palette's own dark ink). */
  :root:not([data-theme="light"])[data-palette="default"] {
    --bg: #0f1115;
    --fg: #f3f4f6;
    --muted: #9ca3af;
    --card-bg: #1a1d23;
    --border: #2a2e37;
    --accent-ink: #fdba74; /* 11.20:1 on dark bg, 10.01:1 on dark card */
  }
  /* CSP-205: sport's near-white bg is also a LIGHT-mode-only look — in dark
     mode it falls back to its own dark pair (not the shared dark surface,
     so the green keeps reading as "pitch" rather than going generic). */
  :root:not([data-theme="light"])[data-palette="sport"] {
    --bg: #0b0b0b;
    --fg: #f2f5f0;
    --muted: #9ca3af;
    --card-bg: #151715;
    --border: #2a2e37;
    --accent-ink: #39e75f; /* 11.97:1 on dark bg, 10.96:1 on dark card */
  }
  /* CSP-247: the gray ground and graphite/black ink are LIGHT-mode only; in
     dark mode both fall back to the shared dark surface with a brighter ink,
     same treatment as "default". */
  :root:not([data-theme="light"])[data-palette="azure"] {
    --bg: #0f1115;
    --fg: #f3f4f6;
    --muted: #9ca3af;
    --card-bg: #1a1d23;
    --border: #2a2e37;
    --accent-ink: #5ab8e6; /* 8.49:1 on dark bg, 7.58:1 on dark card */
  }
  :root:not([data-theme="light"])[data-palette="signal"] {
    --bg: #0f1115;
    --fg: #f3f4f6;
    --muted: #9ca3af;
    --card-bg: #1a1d23;
    --border: #2a2e37;
    --accent-ink: #ff8a90; /* 8.36:1 on dark bg, 7.47:1 on dark card */
  }
}

:root[data-theme="dark"][data-palette="green"]   { --accent-ink: #7ed491; }
:root[data-theme="dark"][data-palette="rosy"]    { --accent-ink: #f783ac; }
:root[data-theme="dark"][data-palette="khmer"]   { --accent-ink: #7ca8ff; }
:root[data-theme="dark"][data-palette="default"] {
  --bg: #0f1115;
  --fg: #f3f4f6;
  --muted: #9ca3af;
  --card-bg: #1a1d23;
  --border: #2a2e37;
  --accent-ink: #fdba74;
}
:root[data-theme="dark"][data-palette="sport"] {
  --bg: #0b0b0b;
  --fg: #f2f5f0;
  --muted: #9ca3af;
  --card-bg: #151715;
  --border: #2a2e37;
  --accent-ink: #39e75f;
}
:root[data-theme="dark"][data-palette="azure"] {
  --bg: #0f1115;
  --fg: #f3f4f6;
  --muted: #9ca3af;
  --card-bg: #1a1d23;
  --border: #2a2e37;
  --accent-ink: #5ab8e6;
}
:root[data-theme="dark"][data-palette="signal"] {
  --bg: #0f1115;
  --fg: #f3f4f6;
  --muted: #9ca3af;
  --card-bg: #1a1d23;
  --border: #2a2e37;
  --accent-ink: #ff8a90;
}

* { box-sizing: border-box; }

/* CSP-200: form controls and links don't inherit the page font by default —
   that's why buttons/selects were rendering in the browser default
   (Arial/Roboto 13.33px) instead of the app's own typeface. */
button, input, select, textarea, a { font: inherit; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  overflow-x: hidden;
  font-family: var(--font-sans);
  line-height: var(--leading);
}

h1, h2, h3 { line-height: var(--leading-tight); }
h1 { font-size: var(--text-2xl); font-weight: 700; }
h2 { font-size: var(--text-xl); font-weight: 700; }
h3 { font-size: var(--text-lg); font-weight: 600; }

/* CSP-200: Khmer script needs more vertical room than Latin at the same
   font-size — applied wherever the app marks Khmer text, plus the page
   language itself. */
:lang(km), [lang="km"], .km { line-height: 1.65; }

[x-cloak] { display: none !important; }

#app {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

.appbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: calc(var(--safe-top) + 12px) var(--gutter) 12px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.appbar-back {
  background: none;
  border: none;
  font-size: var(--text-xl);
  line-height: 1;
  color: var(--fg);
  cursor: pointer;
  padding: 0 4px;
  /* CSP-156: measured 15×22 px on a phone — the glyph stays 22px, the hit
     area grows to the 44×44 px minimum via a centred flex box, not padding
     that would also shift the appbar's own layout. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}
.appbar-title {
  font-size: var(--text-lg);
  font-weight: 600;
  margin: 0;
  /* CSP-145: the EN|KM toggle now sits at the right of the appbar — the
     title takes the remaining space and truncates rather than pushing the
     toggle off-screen or wrapping the header onto two lines on a phone. */
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* CSP-145: compact EN|KM toggle in the appbar — deliberately smaller than
   `.locale-toggle` (the Me tab's own switch, kept as-is) so it never crowds
   the title on a phone, per Yeng's "English and Khmer I can toggle" ask. */
.appbar-locale { display: flex; gap: 4px; flex: 0 0 auto; }
.appbar-locale button {
  position: relative;
  padding: 4px 8px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  font-size: var(--text-xs);
  font-weight: 600;
  min-height: 30px;
  cursor: pointer;
}
/* CSP-156: measured 33×30 px — kept visually compact (Yeng: "never crowds
   the title"), but a transparent ::before extends the actual tap target to
   44×44 px centred on the pill. The two pills' hit areas overlap slightly in
   the 4px gap between them; that's an accepted trade-off of keeping both
   pills this close together rather than shrinking the title further. */
.appbar-locale button::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}
/* CSP-280: only the raw route fallback (a screen with no nav.* string) is
   capitalised; a translated title renders exactly as written ("Post a game"). */
.appbar-title-raw { text-transform: capitalize; }
.appbar-locale button.active { border-color: var(--accent); color: var(--accent-ink, var(--accent)); }

.content {
  flex: 1 1 auto;
  padding: 16px var(--gutter) calc(88px + var(--safe-bottom));
  overflow-y: auto;
}

.card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 18px; /* CSP-107: the warm-orange look's softer, more rounded card */
  padding: 16px;
  margin-bottom: 12px;
}
.card h3 { margin: 0 0 4px; }
.badge {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 8px;
  margin-top: 6px;
}

.empty-state { text-align: center; padding: 48px 16px; color: var(--muted); }

.bottomnav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: space-around;
  padding: 6px var(--gutter) calc(6px + var(--safe-bottom));
  background: var(--bg);
  border-top: 1px solid var(--border);
  /* CSP-247: same layer as .appbar. Without it the Krama card's content
     (z-index 1) painted over the tab bar once Home was long enough to scroll
     under it. */
  z-index: 10;
}
.bottomnav button {
  background: none;
  border: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  color: var(--muted);
  font-size: var(--text-xs);
  padding: 4px 8px;
  cursor: pointer;
}
.bottomnav button.active { color: var(--accent-ink, var(--accent)); }
.bottomnav svg { width: 22px; height: 22px; }

.guest-panel { padding: 64px var(--gutter); text-align: center; }
/* CSP-295: signed-out EN | KH switch, centred above the copy; 40px+ tap targets. */
.guest-locale { justify-content: center; margin: 0 0 24px; }
.guest-locale button { min-height: 40px; min-width: 56px; }

.btn {
  display: inline-block;
  background: var(--accent);
  color: var(--accent-fg, #ffffff);
  border: none;
  border-radius: 10px;
  padding: 10px 20px;
  text-decoration: none;
  font-size: var(--text-base);
  font-weight: 600;
  margin-top: 12px;
  cursor: pointer;
}

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(76px + var(--safe-bottom));
  transform: translateX(-50%);
  background: #1a1a1a;
  color: #ffffff;
  padding: 8px 16px;
  border-radius: 8px;
  font-size: var(--text-sm);
  max-width: calc(100% - 32px);
  z-index: 20;
}

.locale-toggle { display: flex; gap: 8px; margin-top: 8px; }
.locale-toggle button {
  padding: 6px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  cursor: pointer;
}
.locale-toggle button.active { border-color: var(--accent); color: var(--accent-ink, var(--accent)); }

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

.join-placeholder { text-align: center; padding-top: 64px; }

@media (min-width: 431px) {
  #app { max-width: 430px; margin: 0 auto; box-shadow: 0 0 0 1px var(--border); }
}

/* ---- CSP-10b/11b screens: forms, lists, avatars, team page ---- */
.form { display: flex; flex-direction: column; gap: 12px; }
.field { display: flex; flex-direction: column; gap: 4px; font-size: var(--text-sm); color: var(--muted); }
.input {
  width: 100%;
  font: inherit;
  font-size: var(--text-base); /* equals 16px: prevents iOS zoom on focus */
  color: var(--fg);
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
}
.input:focus { outline: 2px solid var(--accent); outline-offset: 0; }
textarea.input { resize: vertical; }

.btn:disabled { opacity: 0.5; cursor: default; }
/* CSP-156: measured 27–30 px tall on a phone (Collect, Sides, Share,
   Statement, Approve/Decline, Edit/Delete, I'm in/Maybe/I'm out all use this
   class) — min-height only, padding/font-size untouched, so the label never
   grows, just the box around it. Flex-centred so the taller box doesn't
   push the text to its top edge. */
.btn-small {
  padding: 6px 12px;
  font-size: var(--text-sm);
  font-weight: 600;
  margin-top: 0;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.btn-secondary { background: var(--card-bg); color: var(--fg); border: 1px solid var(--border); }
.btn-danger { background: #b91c1c; color: #fff; }
/* CSP-156: measured 22×29 px (also the calendar month ‹/› arrows, which
   reuse this class) — the glyph stays at font-size 18px, the hit area grows
   to 44×44 px via a centred flex box. */
.btn-icon {
  background: none;
  border: none;
  font-size: var(--text-lg);
  color: var(--muted);
  padding: 4px 8px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}
.link-like { color: var(--accent-ink, var(--accent)); cursor: pointer; font-size: var(--text-sm); }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* CSP-156: wrap rather than overflow — a row-between pairing a label with a
   couple of small buttons (the common case) must not force the row wider
   than the phone, now that those buttons have a taller minimum hit area. */
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.row-gap { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.center { text-align: center; }
.small { font-size: var(--text-xs); }

.list { list-style: none; margin: 8px 0 16px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.list-item {
  display: flex; align-items: center; gap: 12px;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 12px;
  padding: 10px 12px; cursor: pointer;
  /* CSP-153 (UX audit P2): this class is also used on the <a class="list-item
     match-row"> rows (Matches/other-games discovery, app.html) — without this
     reset those rendered as plain browser-default blue, underlined links. */
  color: inherit; text-decoration: none;
}
.list-item-static { cursor: default; }
.list-main { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; }
.list-main strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.crest, .avatar { width: 40px; height: 40px; border-radius: 10px; object-fit: cover; flex: 0 0 auto; }
.avatar { border-radius: 50%; }
.crest-lg, .avatar-lg { width: 72px; height: 72px; }
.crest-placeholder {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--accent-fg, #fff); font-weight: 700; font-size: var(--text-sm);
}
.avatar-pick { display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; }

.team-header { display: flex; gap: 14px; align-items: center; margin-bottom: 12px; }
.team-header h2 { margin: 0 0 2px; }

.pill { font-size: var(--text-xs); font-weight: 600; border-radius: 999px; padding: 2px 8px; background: var(--bg); border: 1px solid var(--border); color: var(--muted); white-space: nowrap; }
.invite-link { display: block; font-size: var(--text-xs); word-break: break-all; background: var(--bg); border: 1px dashed var(--border); border-radius: 8px; padding: 8px; margin: 8px 0; }
/* CSP-143 — the "/link CODE" box in the team page's Telegram group panel: big
   and monospace so the code is easy to read and retype if copy fails. */
.telegram-link-code { display: block; font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 700; letter-spacing: 0.03em; text-align: center; word-break: break-all; background: var(--bg); border: 1px dashed var(--border); border-radius: 8px; padding: 10px; margin: 8px 0; }
.qr { display: block; width: 160px; height: 160px; margin: 12px auto 0; border-radius: 8px; background: #fff; }

.menu { position: relative; }
.menu-items {
  position: absolute; right: 0; top: 28px; z-index: 5; min-width: 180px;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px; padding: 4px;
  display: flex; flex-direction: column;
}
.menu-items button { text-align: left; background: none; border: none; color: var(--fg); padding: 8px 10px; font: inherit; cursor: pointer; border-radius: 6px; }
.menu-items button:hover { background: var(--bg); }
.menu-items button.danger { color: #b91c1c; }

.steps { display: flex; gap: 8px; margin: 4px 0 12px; }
.steps span {
  width: 26px; height: 26px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--text-xs); border: 1px solid var(--border); color: var(--muted);
}
.steps span.active { background: var(--accent); color: var(--accent-fg, #fff); border-color: var(--accent); }

.join-page { max-width: 430px; margin: 0 auto; padding: 48px var(--gutter); text-align: center; }
.join-page .crest-lg { margin: 0 auto 12px; display: flex; }

/* ---- CSP-14: passport + matches ---- */
.passport-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.tier-badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; font-weight: 600; font-size: var(--text-sm); }
.tier-kyu { background: color-mix(in srgb, #2e7d32 18%, transparent); color: #2e7d32; }
.tier-dan { background: color-mix(in srgb, #b8860b 22%, transparent); color: #b8860b; }
:root[data-theme="dark"] .tier-kyu, :root:not([data-theme="light"]) .tier-kyu { color: #7ed491; }
:root[data-theme="dark"] .tier-dan, :root:not([data-theme="light"]) .tier-dan { color: #f2c94c; }
.rating-big { font-size: var(--text-2xl); font-weight: 700; line-height: 1; letter-spacing: -1px; }
.rating-band { font-size: var(--text-base); opacity: .7; margin-left: 6px; font-weight: 400; }
.progress { height: 8px; border-radius: 999px; background: color-mix(in srgb, currentColor 12%, transparent); overflow: hidden; margin: 8px 0 4px; }
.progress > span { display: block; height: 100%; border-radius: 999px; background: #2e7d32; }
.spark { width: 100%; height: 56px; display: block; }
.spark polyline { fill: none; stroke: #2e7d32; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.spark circle { fill: #2e7d32; }
.form-pills { display: flex; gap: 6px; flex-wrap: wrap; }
.form-pill { width: 26px; height: 26px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: var(--text-xs); font-weight: 700; color: #fff; }
.form-W { background: #2e7d32; } .form-D { background: #8d8d8d; } .form-L { background: #c62828; }

/* ---- CSP-202: Krama rating (how often someone plays, not skill) ----
   Fixed tier colours (not theme-dependent — this is the one place a specific
   colour is the whole point, like the tier-kyu/tier-dan badges above) set a
   --krama-color custom property per tier modifier class, read by both the
   Home card's decorative check stripe and the small teammate-profile chip.
   Tier text itself is always the page's normal --fg/--muted ink, never white
   on the tier colour, so AA contrast holds regardless of tier. */
.krama-silver { --krama-color: #a7adb4; }
.krama-yellow { --krama-color: #e8b931; }
.krama-green  { --krama-color: #2e9e5b; }
.krama-blue   { --krama-color: #2f6fd1; }
.krama-red    { --krama-color: #c8312b; }

/* CSP-202 Krama ribbon (inline SVG, colour via currentColor = tier colour). */
.krama-ribbon { display: block; width: 140px; height: auto; margin: 8px auto 4px; color: var(--krama-color); }
.krama-ribbon-band, .krama-ribbon-knot, .krama-ribbon-tail { stroke: var(--krama-color); stroke-width: 1.5; }
/* Yearly graph: training (tier colour) vs matches (ink), last 12 months. */
.krama-graph { margin: 8px 0 0; }
.krama-graph svg { display: block; width: 100%; height: auto; aspect-ratio: 300 / 112; overflow: visible; }
.krama-graph-axis { stroke: var(--border, #e5ddd5); stroke-width: 1; }
.krama-graph-training { fill: none; stroke: color-mix(in srgb, var(--krama-color) 72%, var(--fg)); stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; }
.krama-graph-matches { fill: none; stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 4 4; stroke-linejoin: round; stroke-linecap: round; }
.krama-graph-dot-training { fill: color-mix(in srgb, var(--krama-color) 72%, var(--fg)); }
.krama-graph-dot-matches { fill: var(--muted); }
.krama-graph-peak { fill: var(--muted); font-size: var(--text-xs); }
.krama-graph-grid { stroke-dasharray: 2 4; }
.krama-graph-month { fill: var(--muted); font-size: var(--text-xs); }
.krama-graph-month.is-current { fill: var(--fg); font-weight: 700; }
.krama-graph-legend { display: flex; gap: 16px; font-size: var(--text-sm); color: var(--muted); margin-top: 4px; }
.krama-legend-training::before, .krama-legend-matches::before { content: ""; display: inline-block; width: 16px; height: 0; margin-right: 6px; vertical-align: middle; }
.krama-legend-training::before { border-top: 3px solid color-mix(in srgb, var(--krama-color) 72%, var(--fg)); }
.krama-legend-matches::before { border-top: 2px dashed var(--muted); }
/* CSP-203 Krama card: the scarf check as a soft watermark, a quiet tier pill. */
.krama-card { position: relative; overflow: hidden; isolation: isolate; } /* keeps its z-indexed children out of the page stacking order */
.krama-card > *:not(.krama-watermark) { position: relative; z-index: 1; }
.krama-watermark {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-color: transparent;
  background-image:
    linear-gradient(90deg, color-mix(in srgb, var(--krama-color, transparent) 55%, transparent) 50%, transparent 50%),
    linear-gradient(0deg, color-mix(in srgb, var(--krama-color, transparent) 55%, transparent) 50%, transparent 50%);
  background-size: 14px 14px;
  opacity: 0.16;
  -webkit-mask-image: linear-gradient(200deg, #000 0%, rgba(0, 0, 0, 0.35) 45%, transparent 75%);
  mask-image: linear-gradient(200deg, #000 0%, rgba(0, 0, 0, 0.35) 45%, transparent 75%);
}
.krama-pill {
  font-size: var(--text-sm); font-weight: 600; padding: 3px 12px; border-radius: 999px;
  color: color-mix(in srgb, var(--krama-color) 55%, var(--fg));
  background: color-mix(in srgb, var(--krama-color) 18%, var(--card-bg, #fff));
  border: 1px solid color-mix(in srgb, var(--krama-color) 45%, transparent);
}
.krama-graph-area { fill: color-mix(in srgb, var(--krama-color) 16%, transparent); stroke: none; }
.krama-chip { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm); font-weight: 600; }
.krama-chip::before {
  content: "";
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--krama-color);
}
.match-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.match-score { font-weight: 700; font-variant-numeric: tabular-nums; }
.status-pill { font-size: var(--text-xs); font-weight: 600; padding: 2px 8px; border-radius: 999px; background: color-mix(in srgb, currentColor 10%, transparent); }
.section-title { margin: 18px 0 8px; font-size: var(--text-base); opacity: .8; text-transform: uppercase; letter-spacing: .04em; }
.lineup-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; margin: 8px 0; }
.lineup-grid label { display: flex; align-items: center; gap: 6px; font-size: var(--text-sm); }
.timeline { list-style: none; padding: 0; margin: 0; }
.timeline li { padding: 6px 0; border-bottom: 1px solid color-mix(in srgb, currentColor 10%, transparent); font-size: var(--text-sm); }
.delta-pos { color: #2e7d32; } .delta-neg { color: #c62828; }

/* CSP-15 reflections */
.goal-card { border-left: 4px solid var(--accent, #0f7b3a); }
.goal-text { font-size: var(--text-base); margin: 4px 0; }
.mood-row { display: flex; gap: 8px; margin-top: 6px; }
.mood { font-size: var(--text-2xl); background: transparent; border: 2px solid transparent; border-radius: 12px; padding: 4px 8px; }
.mood.active { border-color: var(--accent, #0f7b3a); background: var(--card-2, rgba(15,123,58,0.08)); }
.row-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* ---- CSP-66: Home dashboard (PSM04) ---- */
.mono { font-variant-numeric: tabular-nums; }
.card-link { display: block; color: inherit; text-decoration: none; cursor: pointer; }
.card-urgent { border-color: color-mix(in srgb, var(--accent-2, var(--accent)) 45%, var(--border)); }

/* CSP-74: a player's team is the most important thing on Home — the row went
   from a small identity "chip" to a real card (crest at .crest-lg-ish size, the
   name prominent, next fixture underneath) so it reads as primary, not a label.
   Still a horizontally-scrolling row at 390px with 2-3 teams (never shrunk to
   fit) — see the ticket's "scroll, don't shrink" instruction. */
.teams-row { display: flex; gap: 10px; overflow-x: auto; margin: 8px 0 16px; padding-bottom: 2px; -webkit-overflow-scrolling: touch; }
.team-chip {
  flex: 0 0 auto; width: 156px;
  display: flex; flex-direction: column; gap: 10px; padding: 16px;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 18px;
  color: inherit; text-decoration: none;
}
.team-chip .crest, .team-chip .crest-placeholder { width: 60px; height: 60px; border-radius: 16px; font-size: var(--text-xl); }
.team-chip-main { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.team-chip-main strong {
  font-size: var(--text-base); font-weight: 700; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.team-chip-main .mono { font-size: var(--text-xs); }
/* The "+" tile is deliberately the plainest thing in the row — dashed border,
   muted (not accent) ink, no crest-sized presence — so a real team always reads
   as the primary object and this is clearly the secondary, lesser affordance. */
.team-chip-add {
  flex: 0 0 auto; width: 60px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 18px; border: 1px dashed var(--border); background: none;
  color: var(--muted); font-size: var(--text-xl); font-weight: 700;
}

.team-tag {
  flex: 0 0 auto; font-size: var(--text-xs); font-weight: 700; letter-spacing: .02em;
  padding: 2px 7px; border-radius: 999px; background: var(--fg); color: var(--bg);
}
:root[data-theme="dark"] .team-tag, :root:not([data-theme="light"]) .team-tag { background: var(--fg); color: var(--bg); }

.upcoming-item {
  display: flex; flex-direction: column; gap: 0;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 12px;
  padding: 10px 12px; color: inherit; text-decoration: none;
}
.goal-line {
  display: flex; align-items: flex-start; gap: 6px; margin-top: 9px; padding-top: 9px;
  border-top: 1px solid var(--border); font-size: var(--text-xs); color: var(--muted); line-height: 1.4;
}

/* ---- Kbach Phni Pleung watermark (CSP-42) -------------------------------
   Anchored behind the top of the screen so it reads under the app bar and the
   passport card, never under a list of results. Decorative, non-interactive. */
.kbach-watermark {
  position: fixed;
  top: calc(var(--safe-top) - 26px);
  right: -96px;
  width: 250px;
  height: auto;
  color: var(--fg);
  opacity: 0.055;
  pointer-events: none;
  user-select: none;
  z-index: 0;
}
/* Guarded like every other dark rule here: the app pins data-theme="light",
   so a phone in dark mode must not lift the watermark against a light ground. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .kbach-watermark { opacity: 0.08; }
}
#app { position: relative; z-index: 1; }

/* ---- CSP-71: calendar month grid + agenda ---- */
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  margin: 10px 0 16px;
}
.cal-wd {
  text-align: center;
  font-size: var(--text-xs);
  color: var(--muted);
  padding-bottom: 2px;
}
.cal-cell {
  position: relative;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card-bg);
  color: inherit;
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  padding: 0;
}
.cal-cell-muted { color: var(--muted); opacity: 0.55; }
.cal-cell-today { border-color: var(--accent); }
.cal-cell-selected { background: var(--accent); color: var(--accent-fg, #fff); border-color: var(--accent); }
.cal-dot {
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--accent);
}
.cal-cell-selected .cal-dot { background: #fff; }
/* CSP-107: a past day the viewer actually played gets a FILLED marker — a
   distinct shape from an upcoming day's small outline dot above, not just a
   colour swap, so it reads as "this already happened" at a glance. */
.cal-dot-played {
  position: absolute;
  bottom: 3px;
  left: 50%;
  transform: translateX(-50%);
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 2px var(--tint, var(--card-bg));
}
.cal-cell-selected .cal-dot-played { background: #fff; box-shadow: 0 0 0 2px var(--accent); }

/* ---- CSP-146: Team Admin dashboard — "Treasury by week" (strip + month grid)
   Yeng, 1 Oct, looking at the Team Admin block on desktop: a week-number
   dashboard of Collected vs Spending, with a calendar beside it to spot which
   week needs a look. Phone: the strip scrolls horizontally with the current
   week snapped into view; the grid stacks below it. Desktop: both fit without
   scrolling (see the >=900px override further down). */
.week-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
  margin: 8px 0;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.week-cell {
  flex: 0 0 auto;
  width: 72px;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 6px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card-bg);
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-align: left;
}
.week-cell-current { border-color: var(--accent); border-width: 2px; }
.week-cell-label {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-xs);
  color: var(--muted);
}
.week-bars {
  display: flex;
  flex-direction: column;
  gap: 2px;
  height: 22px;
  justify-content: flex-end;
}
.week-bar { height: 5px; border-radius: 3px; background: transparent; }
.week-bar-collected { background: var(--accent); }
.week-bar-spent { background: var(--muted); }
.week-net { font-size: var(--text-xs); font-weight: 700; font-variant-numeric: tabular-nums; }
/* Deliberately not `--accent-2` (app.css's own "fills/borders/progress only"
   token — mustard in the default theme, flag red in another, never a
   positive/negative signal; see the dot below) — a plain green/red pair for
   "net ≥ 0 / net < 0", the one place this page needs that exact semantic. */
.week-net-positive { color: #1f9d55; }
.week-net-negative { color: #c0392b; }
.week-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent-2, var(--accent));
  margin-left: auto;
  flex: 0 0 auto;
}

/* Phone: strip then grid, stacked (the >=900px override above turns this into
   a two-column grid, strip beside the calendar). */
.week-dashboard-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
/* Without this, a grid track sizes to the min-content of its widest item —
   `.week-strip`'s own `overflow-x: auto` row of 12 non-shrinking cells —
   which blows the whole column (and its sibling row sharing the same single
   1fr track) out past the viewport instead of actually scrolling inside it.
   Standard CSS grid/flex fix: a grid/flex item's automatic min-width is its
   content size unless told otherwise. */
.week-dash-col { min-width: 0; }

.week-grid { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }
.week-grid-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card-bg);
  color: inherit;
  font: inherit;
  cursor: pointer;
  width: 100%;
  text-align: left;
}
.week-grid-row:disabled { opacity: 0.45; cursor: default; }
.week-grid-row-current { border-color: var(--accent); }
.week-grid-days { display: flex; gap: 3px; flex: 1 1 auto; }
.week-grid-day {
  width: 20px;
  height: 20px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  color: var(--fg);
  background: var(--bg);
}
.week-grid-day-out { color: var(--muted); opacity: 0.5; }
.week-grid-net {
  font-size: var(--text-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  min-width: 56px;
  text-align: right;
  flex: 0 0 auto;
}

/* ---- CSP-70: team page banner (TMM02/TMM03) ------------------------------ */
.team-banner {
  display: block;
  width: 100%;
  height: 110px;
  object-fit: cover;
  border-radius: 14px;
  margin-bottom: 12px;
  background: var(--card-bg);
}

/* ---- CSP-77: sessions (Home/team/matches wiring + the session screen) ----
   An attendee waiting on approval is the organiser's to-do list (the ticket's
   "sort people waiting for approval to the top") — the same accent-tinted
   border already used for an urgent card, so it reads as "needs you" without a
   new color. */
.attendee-pending { border-color: color-mix(in srgb, var(--accent-2, var(--accent)) 45%, var(--border)); }

/* ---- CSP-85/CSP-107: theme picker (Me screen) ------------------------------
   A swatch, not just a name — a colour choice needs the colour in front of you.
   Dot colours are the literal hex values from the :root[data-palette] blocks
   above; tests/test_brand_palettes.py requires a dot per palette. The khmer dot splits
   blue/white/red so it reads as "the flag colours" without drawing the flag
   itself (no Angkor Wat silhouette, colour only). */
.theme-picker { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 8px; }
.theme-swatch {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  background: none; border: none; padding: 4px; font: inherit; color: var(--fg); cursor: pointer;
}
.theme-swatch-dot {
  width: 34px; height: 34px; border-radius: 50%;
  border: 2px solid var(--border);
  box-shadow: 0 0 0 2px var(--bg);
}
.theme-swatch.active .theme-swatch-dot { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--fg); }
.theme-swatch-dot.palette-default { background: #f26b1d; }
.theme-swatch-dot.palette-green { background: #0f7b3a; }
.theme-swatch-dot.palette-rosy { background: #f06595; }
.theme-swatch-dot.palette-khmer {
  background: linear-gradient(135deg, #032ea1 0 44%, #ffffff 44% 56%, #e00025 56% 100%);
}
/* CSP-205: same split-gradient technique as the khmer dot above — half
   bright green, half deep black, a thin diagonal slash between the two
   rather than a straight half/half line, so it reads as "the sport look"
   without needing the shape rules below. */
.theme-swatch-dot.palette-sport {
  background: linear-gradient(135deg, #39e75f 0 46%, #0b0b0b 50%, #0b0b0b 100%);
}
/* CSP-247: primary + support colour, half and half. */
.theme-swatch-dot.palette-azure {
  background: linear-gradient(135deg, #087eb4 0 50%, #343c46 50% 100%);
}
.theme-swatch-dot.palette-signal {
  background: linear-gradient(135deg, #d91f2a 0 50%, #171b20 50% 100%);
}
.theme-swatch-label { font-size: var(--text-xs); color: var(--muted); }

/* ---- CSP-93: Collect (SEM02) — the organiser's money screen -----------------
   `claimed` must never read as `confirmed` at a glance — that distinction is
   the entire point of this screen, so it gets a genuinely different colour
   (amber, "unverified"), not just different text: unpaid stays neutral,
   claimed is amber, confirmed is green. The row itself carries a tinted left
   border too, so the state is legible before the label is even read — same
   "colour is the fast read, text is the confirmation" idea as .form-pill and
   .tier-kyu/.tier-dan above. Dark-mode inks follow the same dual-guard
   convention as those. */
.paid-row-unpaid { border-left: 4px solid var(--border); }
.paid-row-claimed {
  border-left: 4px solid #b8860b;
  background: color-mix(in srgb, #b8860b 9%, var(--card-bg));
}
.paid-row-confirmed {
  border-left: 4px solid #2e7d32;
  background: color-mix(in srgb, #2e7d32 11%, var(--card-bg));
}
.collect-paid-btn {
  border: 1px solid var(--border);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
  min-height: 30px;
  padding: 4px 12px;
}
.collect-paid-btn:disabled { opacity: 0.6; cursor: default; }
.paid-pill-unpaid { color: var(--muted); }
.paid-pill-claimed {
  background: color-mix(in srgb, #b8860b 26%, transparent);
  color: #8a6600;
  border-color: transparent;
}
.paid-pill-confirmed {
  background: color-mix(in srgb, #2e7d32 22%, transparent);
  color: #1e5c26;
  border-color: transparent;
}
:root[data-theme="dark"] .paid-pill-claimed, :root:not([data-theme="light"]) .paid-pill-claimed { color: #f2c94c; }
:root[data-theme="dark"] .paid-pill-confirmed, :root:not([data-theme="light"]) .paid-pill-confirmed { color: #7ed491; }

.collect-total { font-size: var(--text-2xl); font-weight: 700; letter-spacing: -0.5px; margin: 4px 0 0; }
.collect-stats { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); }
.collect-stat { font-size: var(--text-xs); color: var(--muted); }

/* ---- CSP-97: chat (ADR-014 §5) ---- */
.bottomnav button { position: relative; }
.nav-badge {
  position: absolute;
  top: -2px;
  right: 14px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: #b91c1c;
  color: #fff;
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}
.banner {
  background: color-mix(in srgb, #b91c1c 12%, transparent);
  color: #b91c1c;
  border-radius: 10px;
  padding: 8px 12px;
  font-size: var(--text-sm);
  margin-bottom: 12px;
}
/* CSP-156: `.banner` is the pink/error look above — wrong for a plain
   one-time affordance hint (Collect's "tap a pill to confirm" tip is not an
   error). Added alongside `.banner`, not a replacement for it; every other
   banner on the error path keeps the red look. */
.banner-neutral {
  background: var(--card-bg);
  color: var(--fg);
  border: 1px solid var(--border);
}
.chat-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.chat-header h2 { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-thread { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; padding-bottom: 72px; }
.chat-bubble {
  max-width: 78%;
  padding: 8px 12px;
  border-radius: 14px;
  font-size: var(--text-sm);
  word-break: break-word;
}
.chat-bubble p { margin: 0; }
.chat-bubble-mine {
  align-self: flex-end;
  background: var(--accent);
  color: var(--accent-fg, #ffffff);
  border-bottom-right-radius: 4px;
}
.chat-bubble-theirs {
  align-self: flex-start;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-bottom-left-radius: 4px;
}
.chat-composer {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(60px + var(--safe-bottom));
  display: flex;
  gap: 8px;
  padding: 8px var(--gutter);
  background: var(--bg);
  border-top: 1px solid var(--border);
  z-index: 15;
}
.chat-composer input { flex: 1 1 auto; }
@media (min-width: 431px) {
  .chat-composer { max-width: 430px; margin: 0 auto; }
}

/* ---- CSP-100: Sides (SEM03) — split-into-sides, move-by-hand, copy as text ---- */
.side-count-btn { min-width: 44px; }
.side-card h3 { text-transform: uppercase; letter-spacing: 0.04em; font-size: var(--text-sm); color: var(--muted); }
.side-member { cursor: pointer; }
.side-member-selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, var(--card-bg));
}
.side-number { flex: 0 0 auto; width: 24px; text-align: right; color: var(--muted); font-size: var(--text-sm); }
/* Extra clearance under the last side card so the sticky copy/share bar below
   never sits on top of it (owner's standing phone rule). */
.sides-list { padding-bottom: 64px; }
.sticky-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(60px + var(--safe-bottom));
  display: flex;
  gap: 8px;
  padding: 10px var(--gutter);
  background: var(--bg);
  border-top: 1px solid var(--border);
  z-index: 15;
}
.sticky-bar .btn, .sticky-bar .btn-secondary { flex: 1 1 auto; margin-top: 0; text-align: center; }
@media (min-width: 431px) {
  .sticky-bar { max-width: 430px; margin: 0 auto; }
}

/* ---- CSP-105: Session (SEM01) + Collect (SEM02), finished -------------------
   The owner's own read of the v0 Collect screen ("not sure if this is your
   finished product") was a fair complaint about plain grey cards with no
   hierarchy. `.kbach-card` gives the header-facts card and the temperature
   card the same "this is the important one" treatment `.card-urgent` already
   uses elsewhere on this page — in `var(--accent)`, the app's own CK V3
   primary, rather than a hardcoded colour, so it works with whichever of the
   four CSP-85 palettes a player has chosen instead of fighting it. The kbach
   ornament itself is the watermark already drawn once for the whole shell
   (see .kbach-watermark above) — every screen already carries it, Session and
   Collect included, with nothing extra needed here. */
.kbach-card { border-left: 4px solid var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--card-bg)); }
.kbach-card h3 { color: var(--accent-ink, var(--accent)); }

.collect-header { display: flex; flex-direction: column; gap: 4px; }
.collect-header .status-pill { align-self: flex-start; margin-top: 4px; }
.collect-header-share { margin-top: 10px; }

/* ---- CSP-105 (ADR-015 §2/§3): the session's temperature ---- */
.heat-reading { font-size: var(--text-xl); font-weight: 700; margin: 4px 0; }
.heat-btn { font-size: var(--text-base); min-width: 52px; text-align: center; }

/* ---- CSP-105: Collect's paid pill now reads as a control, not a label — a
   chevron on the two non-terminal states (never on `confirmed`, which is
   already the settled, no-further-action state) and a pressed state on tap,
   CSS-only so it behaves the same in every browser/webview with no extra JS. */
.collect-paid-btn.has-chevron::after { content: "\203A"; margin-left: 4px; opacity: .7; }
.collect-paid-btn:active { transform: scale(0.96); opacity: .85; }
.tap-hint { display: block; }

/* ---- CSP-107: "Last played" (Home, bottom) ---------------------------------
   The owner's ask: make yesterday's game the thing a player is glad to see.
   `--tint` is the peach fill the design note calls out for exactly this — a
   highlight, not a status colour, so it never fights `.status-pill`'s
   confirmed/warning/error (those stay semantic, untouched by any palette).
   Palettes without --tint (green/rosy/khmer) fall back to the ordinary card —
   a quieter look there, never a visual regression. */
.card-last-played {
  background: var(--tint, var(--card-bg));
  border-color: var(--accent);
}
.card-last-played h3 { color: var(--accent-ink, var(--accent)); }
/* The card opens the session on tap; the venue name inside it is its OWN,
   smaller tap target (opens the map) — never nested <a> tags, so this is a
   button styled as inline text, stopping its own click from also firing the
   card's. */
.venue-tap {
  background: none; border: none; padding: 0; margin: 0; font: inherit;
  color: var(--accent-ink, var(--accent)); text-decoration: underline;
  cursor: pointer;
}

/* ---- CSP-107: number-first stat (ref-2/ref-3 shape) — effort made visible,
   never a result: a plain count, never a streak, badge or "days in a row". */
.stat-tile { display: flex; align-items: baseline; gap: 6px; margin-top: 8px; }
.stat-number { font-size: var(--text-2xl); font-weight: 700; line-height: 1; color: var(--fg); }
.stat-label { font-size: var(--text-sm); color: var(--muted); }

/* ---- CSP-107: pill segment (Fixtures | Venues on Matches) ------------------
   Same shape as the theme picker's swatch row: a track with a raised active
   pill, not two separate buttons, so it reads as one control with two states. */
.segment { display: inline-flex; gap: 2px; background: var(--card-bg); border: 1px solid var(--border); border-radius: 999px; padding: 3px; margin: 8px 0; }
.segment-btn {
  border: none; background: none; font: inherit; font-size: var(--text-sm); font-weight: 600;
  color: var(--muted); padding: 6px 16px; border-radius: 999px; cursor: pointer;
}
.segment-btn.active { background: var(--accent); color: var(--accent-fg, #fff); }

/* ---- CSP-107: a past calendar day's own list rows — same shape as the
   month's played marker: distinct from an upcoming fixture row, never a
   result (no score, no "won/lost" — this is a training session, not a match). */
.cal-past-row .cal-past-meta { display: flex; flex-wrap: wrap; gap: 4px 8px; margin-top: 2px; }

/* ---- CSP-107: Collect's collected/expected ring (SVG, no library) ---------
   Pure decoration alongside the existing text summary, never a replacement for
   it — the numbers are still the source of truth. */
.collect-ring { flex: 0 0 auto; }
.collect-ring-track { fill: none; stroke: var(--border); stroke-width: 8; }
.collect-ring-fill { fill: none; stroke: var(--accent-2, var(--accent)); stroke-width: 8; stroke-linecap: round; transform: rotate(-90deg); transform-origin: 50% 50%; }

/* ---- CSP-111: venue type-ahead — a suggestion list under the venue box
   (facilities first, then a team's own past venues), and a "use Google Maps"
   fallback chip when nothing matched. Absolute-positioned so opening it never
   shifts anything else on the page (phone-first: no layout jump). */
.venue-suggest { position: relative; }
.venue-suggest-list {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 12;
  margin: 4px 0 0; padding: 4px; list-style: none;
  background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px;
  max-height: 240px; overflow-y: auto;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
}
.venue-suggest-item {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; font: inherit; color: var(--fg);
  padding: 8px 10px; border-radius: 8px; cursor: pointer;
}
.venue-suggest-item:hover, .venue-suggest-item:active { background: var(--bg); }
.venue-suggest-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.venue-suggest-sub { font-size: var(--text-xs); color: var(--muted); white-space: nowrap; }
.venue-suggest-map { flex: 0 0 auto; font-size: var(--text-sm); }
.venue-suggest-fallback {
  display: inline-flex; align-items: center; margin-top: 6px; font-size: var(--text-xs);
  border-radius: 999px; padding: 6px 12px; background: var(--card-bg);
  border: 1px dashed var(--border); color: var(--accent-ink, var(--accent)); cursor: pointer;
}
.collect-ring-label { font-size: var(--text-xs); fill: var(--muted); text-anchor: middle; }

/* ---- CSP-134 (ADR-016 §4b): the Treasury "Ledger" — reads like a
   spreadsheet (">= 600px, .ledger-table-wrap below) or a stack of cards
   ("< 600px, .ledger-cards further down — CSP-154 finding 2: the table's
   IN/OUT/CUR columns and its own totals row ran off a 390px screen with no
   scroll hint). Sticky header + sticky first (date) column so a long period
   never loses its bearings while scrolling the table on a wider screen. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 8px 0;
  border: 1px solid var(--border);
  border-radius: 10px;
}
.ledger-table {
  width: 100%;
  min-width: 640px; /* forces horizontal scroll on a phone rather than squashing columns unreadably */
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.ledger-table th, .ledger-table td {
  padding: 8px 10px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  vertical-align: middle;
}
.ledger-table th.num, .ledger-table td.num { text-align: right; }
.ledger-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--card-bg);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
}
.ledger-table th:first-child, .ledger-table td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--bg);
}
.ledger-table thead th:first-child { z-index: 3; background: var(--card-bg); }
.ledger-table td.ledger-col-session { white-space: normal; max-width: 160px; }
.ledger-row-income .ledger-col-session { cursor: pointer; color: var(--accent-ink, var(--accent)); }
.ledger-row-deleted { opacity: 0.45; text-decoration: line-through; }
.ledger-row-edit td, .ledger-row-edit .input { white-space: normal; }
.ledger-table .input { min-width: 90px; }
/* Keep the row's edit/delete/history buttons on one line — the table's own
   horizontal scroll handles overflow, same as every other column, rather than
   the buttons wrapping onto several lines and blowing the row height out
   (the generic .row-gap utility wraps by default; not wanted inside a table). */
.ledger-actions { white-space: nowrap; }
.ledger-actions .row-gap { flex-wrap: nowrap; margin-top: 0; }
.ledger-table tr.subtotal td { font-weight: 700; border-top: 2px solid var(--fg); }

@media (min-width: 720px) {
  .ledger-table { min-width: 0; font-size: var(--text-sm); }
}

/* ---- CSP-154 finding 2: the Ledger's phone (< 600px) card layout + summary
   block, in place of the spreadsheet-style table below 600px (the table
   itself, and its own "Totals" footer row, stay for >= 600px — see the
   min-width query at the bottom of this block). Each ".ledger-card" mirrors
   one <tr> of the table (same sortedLedgerRows() data, same handlers); the
   add-row and edit-row forms are a second template in app.html, not a CSS
   reshuffle of the table's own <tr> — the table mixes <input>/<select>
   controls with plain text in the same column depending on edit state,
   which a generic "table -> block" CSS conversion does not stack cleanly. */
.ledger-cards { display: flex; flex-direction: column; gap: 10px; margin: 8px 0; }
.ledger-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card-bg);
  padding: 10px 12px;
}
.ledger-card-edit { border-style: dashed; }
.ledger-card-deleted { opacity: 0.45; text-decoration: line-through; }
.ledger-card-top { gap: 10px; }
.ledger-card-income .ledger-card-top { cursor: pointer; }
.ledger-card-income .ledger-card-label { color: var(--accent-ink, var(--accent)); }
.ledger-card-label { font-size: var(--text-sm); }
.ledger-card-note { margin: 2px 0 0; }
.ledger-card-actions { margin-top: 8px; flex-wrap: wrap; }
.ledger-amount { font-size: var(--text-sm); font-weight: 700; white-space: nowrap; flex: 0 0 auto; }
/* Deliberately not `--accent-2` (app.css's own "fills/borders/progress only"
   token) — the same plain green/ink pair `.week-net-positive` already uses
   for "this number is money coming in vs. going out", the one semantic this
   card needs regardless of theme palette. */
.ledger-amount-in { color: #1f9d55; }
.ledger-amount-out { color: var(--fg); }
/* CSP-184: a session's own "Expected" row/card — ticks are a status, never
   the calculator, so this is deliberately not styled as money moved. */
.ledger-amount-expected { color: var(--muted); font-weight: 400; }
.ledger-card .field { margin: 6px 0; }

.ledger-summary { display: flex; flex-direction: column; gap: 8px; margin: 8px 0; }
.ledger-summary-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card-bg);
  padding: 8px 12px;
  font-size: var(--text-sm);
}
.ledger-summary-currency { font-weight: 700; margin-right: 4px; }
.ledger-summary-item { white-space: nowrap; }

/* CSP-159/160: the owed-back badge and receipt lightbox the Ledger table/
   cards and the Team Admin workspace's Money tab both use. */
.ledger-owed-badge { background: var(--warn-bg, #fdf0d5); color: var(--warn-ink, #8a5a00); }
/* A pill inside a flex row of 40px buttons must not stretch into a blob. */
.ledger-owed-badge { align-self: center; flex: 0 0 auto; height: auto; min-height: 0; white-space: nowrap; }
.ledger-amount-owed { color: var(--warn-ink, #8a5a00); }
.receipt-lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.8);
  padding: 24px;
}
.receipt-lightbox img {
  max-width: 100%;
  max-height: 100%;
  border-radius: 8px;
  background: #fff;
}
.receipt-lightbox-close {
  position: absolute;
  top: 16px;
  right: 16px;
  background: rgba(255, 255, 255, 0.9);
  width: 44px;
  height: 44px;
}
/* CSP-165: Replace/Remove under the full-size photo — @click.stop on the
   wrapper keeps a tap here from closing the lightbox (the backdrop click
   handler lives on the lightbox itself). */
.receipt-lightbox-actions {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}

/* CSP-165: the 48x48 thumbnail that replaces the plain 🧾 icon everywhere
   has_receipt is shown — a real tap target (>=44px), object-fit: cover so a
   portrait or landscape photo both fill the box without distortion. The
   placeholder glyph (no thumbnail fetched yet, or the fetch failed) sits in
   the same box so nothing jumps around once it loads. */
.receipt-thumb-btn {
  width: 48px;
  height: 48px;
  min-width: 48px;
  min-height: 48px;
  border-radius: 8px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--border, #e2d9cc);
  background: var(--surface-2, #f4ede1);
  flex: 0 0 auto;
}
.receipt-thumb-img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* "＋ Receipt" on a row with none yet — a tap target, not a bare text link. */
.receipt-add-btn {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 4px;
  white-space: nowrap;
}
.receipts-usage-warn { color: var(--warn-ink, #8a5a00); font-weight: 600; }
/* CSP-184: a recorded collection short of what the ticks expected (Collect
   card, session Expenses card, Team Admin Money/Ledger rows). */
.collection-gap-warn { color: var(--warn-ink, #8a5a00); font-weight: 600; }
.ledger-expected-row { color: var(--muted); }

.ledger-table-wrap { display: none; }

@media (min-width: 600px) {
  .ledger-cards { display: none; }
  .ledger-summary { display: none; }
  .ledger-table-wrap { display: block; }
}

/* ---- CSP-158: Team Admin workspace ---------------------------------------
   < 900px: a horizontally scrollable chip row at the top, no burger — every
   section is one tap away, never a drawer. >= 900px (inside the CSP-141
   query below): a sticky left sub-menu beside the content pane instead,
   same two-column idea as `.team-grid`/`.home-grid` already use. */
.admin-chip-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 6px;
  margin-bottom: 14px;
  -webkit-overflow-scrolling: touch;
}
.admin-chip {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  min-height: 40px;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  font: inherit;
  font-size: var(--text-sm);
  white-space: nowrap;
  cursor: pointer;
}
.admin-chip.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.admin-submenu { display: none; }

.admin-header-title { font-size: var(--text-lg); font-weight: 700; }

/* Overview: one card per currency, quick-action buttons, last-count line. */
.admin-overview-figure { font-size: var(--text-2xl); font-weight: 700; }
.admin-overview-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px 16px; margin: 10px 0; }
.admin-overview-grid .muted { font-size: var(--text-xs); }

/* Owed back: grouped by person × currency. */
.owed-group-total { font-size: var(--text-lg); font-weight: 700; }

/* Kit register: a condition chip, colour-coded good -> lost. */
.kit-chip { padding: 2px 10px; border-radius: 999px; font-size: var(--text-xs); border: 1px solid var(--border); }
/* CSP-158 review fix (P2 7c): the form's chips are tap targets (pick a
   condition) and need the 40px floor every other tap target in this app
   gets (see .admin-chip above); the display-only chip in the item list
   (kitConditionChipClass) is read-only and stays compact. */
.kit-chip-select { display: inline-flex; align-items: center; min-height: 40px; padding: 0 14px; cursor: pointer; }
.kit-chip-good { background: #e6f4ea; color: #1f9d55; border-color: transparent; }
.kit-chip-worn { background: #fdf0d5; color: #8a5a00; border-color: transparent; }
.kit-chip-replace { background: #fde2e1; color: #b3261e; border-color: transparent; }
.kit-chip-lost { background: var(--card-bg); color: var(--muted); }

/* Count the kitty: the live difference while counting, and each history row. */
.count-diff { font-weight: 700; }
.count-diff-matched { color: #1f9d55; }
.count-diff-off { color: #b3261e; }

/* ---- CSP-141: desktop layout (>= 900px) ----------------------------------
   Same screens, same code, no feature changes — only the shell gains a left
   sidebar (replacing the bottom tab bar) and a handful of screens gain a
   second CSS-grid column. Everything below 900px is untouched: every new
   rule in this file lives inside the one `(min-width: 900px)` query below,
   or — for the sidebar itself — is the `display: none` default a desktop
   media query then overrides (so a phone never lays it out at all). */
.sidebar { display: none; }

@media (min-width: 900px) {
  /* The phone build centers a 430px column with a 1px border so a lone
     phone screen doesn't look lost on a wide viewport (see the 431px query
     above) — that is exactly the "phone column in the middle of a laptop
     screen" bug this ticket exists to fix, so it's undone here first. */
  #app {
    max-width: none;
    margin: 0;
    box-shadow: none;
    flex-direction: row;
    align-items: stretch;
  }

  .sidebar {
    display: flex;
    flex-direction: column;
    width: 240px;
    flex: 0 0 240px;
    padding: 20px 16px;
    border-right: 1px solid var(--border);
    background: var(--bg);
    position: sticky;
    top: 0;
    height: 100dvh;
    overflow-y: auto;
  }
  .sidebar-brand { font-size: var(--text-lg); font-weight: 700; margin: 4px 4px 18px; }
  .sidebar-nav { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; }
  .sidebar-nav-item {
    display: flex; align-items: center; gap: 10px; width: 100%;
    background: none; border: none; text-align: left;
    padding: 10px 12px; border-radius: 10px;
    color: var(--fg); font: inherit; font-size: var(--text-sm); cursor: pointer;
  }
  .sidebar-nav-item svg { width: 20px; height: 20px; flex: 0 0 auto; color: var(--muted); }
  .sidebar-nav-item:hover { background: var(--card-bg); }
  .sidebar-nav-item.active { background: var(--card-bg); color: var(--accent-ink, var(--accent)); }
  .sidebar-nav-item.active svg { color: var(--accent-ink, var(--accent)); }
  .sidebar-nav-item .nav-badge { position: static; margin-left: auto; }
  /* CSP-145: EN|KM toggle in the sidebar footer, above the version line —
     the Me tab keeps its own switch (`.locale-toggle`) too; this just gives
     desktop a second, always-visible place to flip the whole app's language
     without leaving the current screen. */
  .sidebar-locale { margin: 16px 4px 0; }
  .sidebar-footer { margin: 8px 4px 0; font-size: var(--text-xs); color: var(--muted); }

  /* the bottom tab bar is replaced by the sidebar above */
  .bottomnav { display: none; }

  /* `.app-main` carries on exactly what `#app` used to do (column flex, full
     height) — it exists only so the sidebar can sit beside it rather than
     above/below it in the flex flow; see app.html. */
  .app-main { min-width: 0; }

  .content {
    max-width: 1200px;
    margin: 0 auto;
    padding-top: 24px;
    padding-bottom: 32px;
  }
  /* --gutter already drives .content's left/right padding and .appbar's
     padding — widening it here gives both the "32px gutters" the ticket
     asks for without duplicating the value. */
  :root { --gutter: 32px; }

  /* Forms stay a readable width even in the wide column; cards/lists/tables
     already stretch to fill it with no change needed. */
  .form { max-width: 560px; }

  /* Tables: full width, no forced horizontal scroll, a hover row so a mouse
     user can track their place (sticky header + right-aligned numbers are
     already in place above for every width). */
  .table-scroll { overflow-x: visible; }
  .ledger-table tbody tr:hover td { background: var(--card-bg); }

  /* Home (PSM04): teams row beside upcoming instead of stacked. */
  .home-grid { display: grid; grid-template-columns: minmax(280px, 380px) 1fr; gap: 24px; align-items: start; }

  /* Team page (TMM02/03): schedule/sessions/treasury beside the roster. */
  .team-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start; }

  /* CSP-158: Team Admin workspace — the chip row is replaced by a sticky
     left sub-menu inside the content area (beside the app's own sidebar,
     not instead of it — see app.html's comment on the shell). */
  .admin-chip-row { display: none; }
  .admin-layout { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 28px; align-items: start; }
  .admin-submenu {
    display: flex;
    flex-direction: column;
    gap: 2px;
    position: sticky;
    top: 24px;
  }
  .admin-submenu-item {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 40px;
    padding: 8px 12px;
    border-radius: 10px;
    border: none;
    background: none;
    color: var(--fg);
    font: inherit;
    font-size: var(--text-sm);
    text-align: left;
    cursor: pointer;
  }
  .admin-submenu-item:hover { background: var(--card-bg); }
  .admin-submenu-item.active { background: var(--card-bg); color: var(--accent-ink, var(--accent)); font-weight: 600; }
  .admin-overview-grid { grid-template-columns: repeat(4, 1fr); }
  /* The workspace column is ~200px narrower than other screens and desktop
     tables never scroll (.table-scroll above), so text columns wrap here
     instead — the actions column must never end up off-screen. */
  .admin-content .ledger-table { min-width: 0; }
  .admin-content .ledger-table td { white-space: normal; }
  .admin-content .ledger-table td.num,
  .admin-content .ledger-table td.ledger-actions { white-space: nowrap; }

  /* CSP-145: the header pills are redundant once the sidebar's own toggle is
     visible (Yeng, 1 Oct: "is it normal to have the language option in two
     places?" — no). One toggle per layout: phone keeps the appbar pair, the
     sidebar's `.sidebar-locale` takes over from here up. */
  .appbar-locale { display: none; }

  /* CSP-146: the week strip fits without horizontal scroll on a wide screen;
     the grid sits beside it instead of below. */
  .week-dashboard-grid { grid-template-columns: 1.4fr 1fr; }
  .week-strip { flex-wrap: wrap; overflow-x: visible; }
  .week-cell { width: 84px; }

  /* Session screen (SEM01), organiser block only: summary/contribution left,
     Collect/Sides/Expenses right, 2:3 — grid-column + order place the
     existing cards without moving their markup (see app.html's comment). */
  .session-organiser-grid { display: grid; grid-template-columns: 2fr 3fr; gap: 20px; align-items: start; }
  .session-organiser-grid .sg-left { grid-column: 1; }
  .session-organiser-grid .sg-right { grid-column: 2; }

  /* Calendar: a little more breathing room now that the column is wider;
     the month grid still shows a dot, not a label — see app.html's comment
     on why a visible session title was not added. */
  .cal-grid { gap: 8px; }
  .cal-cell { font-size: var(--text-base); }

  /* Keyboard/desktop niceties (CSP-141 §3). Static, not measured against the
     phone screenshots: an outline only ever appears on :focus-visible and a
     cursor is never rendered into a screenshot, so neither can move a pixel
     in the phone-parity diff. */
  button, .btn, .btn-secondary, .btn-small, .btn-icon, .link-like, a.card-link {
    cursor: pointer;
  }
  a, button, input, select, textarea {
    outline-offset: 2px;
  }
  a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
    outline: 2px solid var(--accent);
  }

  /* CSP-201: the "Find a pitch" map gets more room once there's a whole
     laptop screen to use (still toggle-only, not a side-by-side list+map —
     see screens.js's comment on that trade-off). */
  .book-map-wrap, .book-map { height: 60vh; }
}

/* CSP-195: team rules are plain text with line breaks; a pasted URL or an unbroken Khmer run must wrap, never widen the card. */
.rules-text { white-space: pre-line; overflow-wrap: anywhere; }

/* ---- CSP-201: "Find a pitch" List|Map toggle, filters, and the Leaflet map ----
   Self-hosted Leaflet only (app/tma/static/vendor/leaflet/, served at
   /app-static/vendor/leaflet/) — no Google Maps JS, no API key, loaded lazily
   by screens.js the first time Map view is shown. */
.book-filters { display: flex; gap: 8px; margin: 10px 0; }
.book-filters .field { flex: 1 1 0; min-width: 0; }
.book-filters select {
  min-height: 44px;
  padding: 8px 10px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  font: inherit;
  font-size: var(--text-sm);
}

.seg-toggle {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  margin: 4px 0 12px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.seg-btn {
  min-height: 40px;
  min-width: 44px;
  padding: 6px 16px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}
.seg-btn.active { background: var(--accent); color: var(--accent-fg); }

.book-map-wrap { position: relative; margin: 8px 0; }
.book-map {
  height: 360px;
  min-height: 360px;
  width: 100%;
  border-radius: 12px;
  overflow: hidden;
  z-index: 0;
}
.book-map-locate {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 1;
}
.map-popup { font-size: var(--text-sm); }
.map-popup-actions { display: flex; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
/* CSP-201 review P3: Leaflet ships its own default font stack/size — pin it
   back to the typography system (CSP-200) so a map popup reads like the
   rest of the app, not like a different product. */
.leaflet-container, .leaflet-popup-content { font-family: var(--font-sans); font-size: var(--text-sm); }

.list-item .list-main-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; }

/* ============================================================================
   CSP-205: "Sport" palette — shape + decorative pattern, scoped entirely to
   [data-palette="sport"]. Every other palette only swaps colour tokens; this
   one also swaps the geometry (sharp, chamfered corners instead of the soft
   rounded ones every other palette shares) and adds two CSS-gradient-only
   patterns (a diagonal slash band, a faint chevron texture) — no webfonts,
   no images, no layout change, tap targets unchanged. */

/* ---- sharp corners -------------------------------------------------------
   Flat 2px radius everywhere, replacing each control's own rounder value
   (18px cards, 10px buttons/inputs, 999px pills/segments/chips). */
:root[data-palette="sport"] .card,
:root[data-palette="sport"] .btn,
:root[data-palette="sport"] .btn-small,
:root[data-palette="sport"] .btn-secondary,
:root[data-palette="sport"] .btn-danger,
:root[data-palette="sport"] .input,
:root[data-palette="sport"] select,
:root[data-palette="sport"] .pill,
:root[data-palette="sport"] .segment,
:root[data-palette="sport"] .segment-btn,
:root[data-palette="sport"] .seg-toggle,
:root[data-palette="sport"] .seg-btn,
:root[data-palette="sport"] .admin-chip {
  border-radius: 2px;
}

/* ---- chamfer: cut the top-right and bottom-left corners -------------------
   ~10px on cards, ~6px on the smaller controls. Because clip-path clips an
   element's whole painted box (background, border, children — the same
   stacking the .krama-watermark pseudo already relies on above), the slash
   band ::before added to .card further down inherits the same cut edge for
   free. */
:root[data-palette="sport"] .card {
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
}
:root[data-palette="sport"] .btn,
:root[data-palette="sport"] .btn-small,
:root[data-palette="sport"] .btn-secondary,
:root[data-palette="sport"] .btn-danger,
:root[data-palette="sport"] .input,
:root[data-palette="sport"] select,
:root[data-palette="sport"] .pill,
:root[data-palette="sport"] .segment,
:root[data-palette="sport"] .segment-btn,
:root[data-palette="sport"] .seg-toggle,
:root[data-palette="sport"] .seg-btn,
:root[data-palette="sport"] .admin-chip {
  clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 6px 100%, 0 calc(100% - 6px));
}

/* Bottom-nav active state has no fill of its own to cut a corner into on any
   other palette — give it one here only, so the chamfer has something to
   apply to, same idea as the other "active" chips above. */
:root[data-palette="sport"] .bottomnav button.active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-radius: 2px;
  clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 6px 100%, 0 calc(100% - 6px));
}

/* Focus rings: a clip-path clips an outline drawn outside the border box
   just like it clips everything else, so the default outline-based focus
   styles (.input:focus above, the >=900px :focus-visible rule below) would
   get cut off at the chamfer. Inset box-shadow stays inside the clipped
   shape instead, so it's always fully visible. Higher specificity than both
   of those rules (attribute selector + element), so this wins regardless of
   source order. */
:root[data-palette="sport"] .input:focus {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent);
}
:root[data-palette="sport"] a:focus-visible,
:root[data-palette="sport"] button:focus-visible,
:root[data-palette="sport"] input:focus-visible,
:root[data-palette="sport"] select:focus-visible,
:root[data-palette="sport"] textarea:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* ---- section titles: uppercase (shared already) + a short slash --------- */
:root[data-palette="sport"] .section-title {
  display: flex;
  align-items: center;
  gap: 8px;
}
:root[data-palette="sport"] .section-title::before {
  content: "";
  flex: 0 0 auto;
  width: 14px;
  height: 3px;
  background: var(--accent);
  transform: skewX(-20deg);
}

/* ---- patterns (CSS gradients only) ---------------------------------------
   (a) a diagonal slash band as a 4px top edge on cards and the app bar.
   Excluded from .krama-card on purpose — Yeng: "the Krama stats card keeps
   its own watermark", so it only gets the shared corner treatment above,
   nothing else here. */
:root[data-palette="sport"] .card:not(.krama-card) {
  position: relative;
}
:root[data-palette="sport"] .card:not(.krama-card)::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: repeating-linear-gradient(115deg, var(--accent) 0 6px, #0b0b0b 6px 12px);
  pointer-events: none;
}
:root[data-palette="sport"] .appbar {
  position: relative; /* already sticky-positioned, kept explicit for clarity */
}
:root[data-palette="sport"] .appbar::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: repeating-linear-gradient(115deg, var(--accent) 0 6px, #0b0b0b 6px 12px);
  pointer-events: none;
}
/* (b) a faint chevron (»») texture — two offset diagonal gradients, the
   classic CSS-only herringbone/arrow tile. Low-alpha via color-mix so it
   never competes with text: the app bar's title/back button (--fg-based,
   this is the Home screen's header exactly like every other screen's, there
   being no separate "header" element) and a primary button's label. */
:root[data-palette="sport"] .appbar {
  background-image:
    linear-gradient(45deg, color-mix(in srgb, var(--fg) 6%, transparent) 12%, transparent 12.5%, transparent 87%, color-mix(in srgb, var(--fg) 6%, transparent) 87.5%, color-mix(in srgb, var(--fg) 6%, transparent)),
    linear-gradient(135deg, color-mix(in srgb, var(--fg) 6%, transparent) 12%, transparent 12.5%, transparent 87%, color-mix(in srgb, var(--fg) 6%, transparent) 87.5%, color-mix(in srgb, var(--fg) 6%, transparent));
  background-size: 22px 22px;
}
:root[data-palette="sport"] .btn:not(.btn-danger):not(.btn-secondary):hover,
:root[data-palette="sport"] .btn:not(.btn-danger):not(.btn-secondary):active {
  background-image:
    linear-gradient(45deg, color-mix(in srgb, currentColor 16%, transparent) 12%, transparent 12.5%, transparent 87%, color-mix(in srgb, currentColor 16%, transparent) 87.5%, color-mix(in srgb, currentColor 16%, transparent)),
    linear-gradient(135deg, color-mix(in srgb, currentColor 16%, transparent) 12%, transparent 12.5%, transparent 87%, color-mix(in srgb, currentColor 16%, transparent) 87.5%, color-mix(in srgb, currentColor 16%, transparent));
  background-size: 16px 16px;
}

/* (c) primary buttons: black fill + green text in light mode, green fill +
   black text in dark mode (the one deliberate inversion of the usual
   accent/accent-fg roles), with a small trailing chevron. Excludes
   .btn-danger (destructive actions keep their own red, not reinterpreted as
   "primary") and .btn-secondary (already a card-bg/bordered look). Both
   pairs clear AA: #39e75f on #0b0b0b (and the reverse) is 11.97:1. */
:root[data-palette="sport"] .btn:not(.btn-danger):not(.btn-secondary) {
  background-color: #0b0b0b;
  color: var(--accent);
}
/* Labels that already end in an arrow (e.g. "Open Team Admin →") carry
   .btn-has-arrow so the chevron is not doubled. */
:root[data-palette="sport"] .btn:not(.btn-danger):not(.btn-secondary):not(.btn-has-arrow)::after {
  content: " \203A"; /* › */
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-palette="sport"] .btn:not(.btn-danger):not(.btn-secondary) {
    background-color: var(--accent);
    color: #0b0b0b;
  }
}
:root[data-theme="dark"][data-palette="sport"] .btn:not(.btn-danger):not(.btn-secondary) {
  background-color: var(--accent);
  color: #0b0b0b;
}

/* ============================================================================
   CSP-247: Michael's brand directions, "azure" and "signal". Colour comes
   from the tokens at the top of this file; everything below is what tokens
   cannot express, scoped to the two palettes the same way "sport" is above.
   The markup (.appbar-brand, .home-hero) lives once in app.html and is
   display:none for every other palette (CSP-248: azure is the default, so
   most players see them; the older palettes stay as they were).
   Both images are CSS backgrounds on purpose: a display:none element's
   background is never fetched, so other palettes download nothing extra. */
.appbar-brand,
.home-hero { display: none; }

/* ---- Home app bar: tinted crest + KRUBAL wordmark (Home only) ------------ */
:root[data-palette="azure"] .appbar-home .appbar-brand,
:root[data-palette="signal"] .appbar-home .appbar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
}
:root[data-palette="azure"] .appbar-home .appbar-title,
:root[data-palette="signal"] .appbar-home .appbar-title {
  /* The wordmark takes the visual place; the h1 stays for screen readers. */
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0);
}
.appbar-crest {
  flex: 0 0 auto;
  width: 34px;
  height: 35px; /* the crest art is 246 x 254 */
  background: center / contain no-repeat;
}
:root[data-palette="azure"] .appbar-crest { background-image: url("/static/brand/crest-azure.svg?v=1"); }
:root[data-palette="signal"] .appbar-crest { background-image: url("/static/brand/crest-signal.svg?v=1"); }
.appbar-wordmark {
  font-size: var(--text-xl);
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--fg);
}

/* ---- EN|KM: primary fill on the active pill, plain text on the other ---- */
:root[data-palette="azure"] .appbar-locale button,
:root[data-palette="signal"] .appbar-locale button {
  border-color: transparent;
  background: transparent;
}
:root[data-palette="azure"] .appbar-locale button.active,
:root[data-palette="signal"] .appbar-locale button.active {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--accent-fg);
}

/* ---- Home hero band ------------------------------------------------------
   Full bleed on a phone (cancels .content's own padding), primary fill, the
   Kbach leaf-scroll strip (pattern 01) on the right as a background, white
   text. White on #087EB4 is 4.51:1 and on #D91F2A 5.02:1, so the subtitle
   clears AA at body size too. The text column stops at 64 % so it never runs
   over the pattern. */
:root[data-palette="azure"] .home-hero,
:root[data-palette="signal"] .home-hero {
  display: block;
  position: relative;
  overflow: hidden;
  margin: -16px calc(-1 * var(--gutter)) 16px;
  padding: 22px var(--gutter) 24px;
  background-color: var(--accent);
  color: var(--accent-fg);
}
:root[data-palette="azure"] .home-hero::after,
:root[data-palette="signal"] .home-hero::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 33%;
  background-repeat: no-repeat;
  background-size: auto 100%;
  background-position: 38% center;
  pointer-events: none;
}
:root[data-palette="azure"] .home-hero::after { background-image: url("/static/brand/pattern-leaf-azure.svg?v=1"); }
:root[data-palette="signal"] .home-hero::after { background-image: url("/static/brand/pattern-leaf-signal.svg?v=1"); }
.home-hero-title {
  position: relative;
  z-index: 1;
  max-width: 64%;
  margin: 0 0 8px;
  font-size: var(--text-xl);
  font-weight: 700;
  line-height: var(--leading-tight);
  overflow-wrap: anywhere;
  color: inherit;
}
/* Khmer stacked vowels and subscripts touch at the tight Latin leading. */
:lang(km) .home-hero-title { line-height: var(--leading); }
.home-hero-subtitle {
  position: relative;
  z-index: 1;
  max-width: 64%;
  margin: 0;
  font-size: var(--text-base);
  color: inherit;
}
@media (min-width: 900px) {
  :root[data-palette="azure"] .home-hero,
  :root[data-palette="signal"] .home-hero {
    margin: 0 0 20px;
    border-radius: 18px;
  }
}

/* ---- section labels: uppercase (shared already), tracked, bold, ink ----- */
:root[data-palette="azure"] .section-title,
:root[data-palette="signal"] .section-title {
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: .08em;
  opacity: 1;
  color: var(--fg);
}

/* ---- secondary button: outline in the primary ("Review result") -------- */
:root[data-palette="azure"] .btn-secondary,
:root[data-palette="signal"] .btn-secondary {
  background: var(--card-bg);
  color: var(--accent-ink);
  border: 1px solid var(--accent);
}

/* ---- CSP-280: Find a match (ADR-006) ------------------------------------
   Request cards, filter and stepper chips (40 px tap targets, same height as
   .btn-small), the accept sheet (fixed to the bottom, its own scroll, so the
   choices are never off-screen) and the comments thread. */
.mrq-section { margin: 4px 0 16px; }
.mrq-list { display: flex; flex-direction: column; gap: 10px; }
.mrq-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.mrq-chip {
  display: inline-flex; align-items: center; min-height: 40px; padding: 0 14px;
  border-radius: 999px; border: 1px solid var(--border); background: var(--card-bg);
  color: var(--fg); font: inherit; font-size: var(--text-sm); font-weight: 600; cursor: pointer;
}
.mrq-chip.active { background: var(--accent); color: var(--accent-fg, #fff); border-color: var(--accent); }
.mrq-btn { min-height: 44px; }
.mrq-card { display: flex; flex-direction: column; gap: 6px; }
.mrq-card-head { display: flex; align-items: center; gap: 10px; }
.mrq-when { font-weight: 700; }
.mrq-where { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.mrq-map { display: inline-flex; align-items: center; min-height: 40px; padding: 0 8px; color: var(--accent-ink, var(--accent)); }
.mrq-facts { display: flex; flex-wrap: wrap; gap: 4px 12px; color: var(--muted); }
/* Three lines on a board card; the detail screen shows the note in full. */
.mrq-note {
  margin: 0; overflow: hidden; white-space: normal; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3;
}
.mrq-note.mrq-note-full { display: block; -webkit-line-clamp: none; line-clamp: none; white-space: pre-wrap; }
.mrq-status { align-self: flex-start; }
.mrq-actions { margin-top: 4px; }
.mrq-armed { outline: 2px solid var(--accent); outline-offset: 1px; }
.mrq-error { color: #b91c1c; font-size: var(--text-sm); margin: 4px 0 0; }
.mrq-resolved { font-weight: 600; margin: 8px 0; }
.mrq-step { margin: 0 0 4px; }
.mrq-more { margin: 8px 0; }
.mrq-more-summary { display: flex; align-items: center; min-height: 44px; cursor: pointer; font-weight: 600; color: var(--accent-ink, var(--accent)); }
/* The Back/Next bar is fixed above the bottom nav; the stepper reserves its
   height (44 px buttons + 16 px padding + a little air) on top of .content's
   own nav padding, so every control scrolls into view above the bar. */
.mrq-new { --mrq-bar-h: 60px; --mrq-nav-h: 64px; padding-bottom: calc(var(--mrq-bar-h) + 16px); }
.mrq-stepper-actions {
  position: fixed; left: 0; right: 0; z-index: 9;
  bottom: calc(var(--mrq-nav-h) + var(--safe-bottom));
  background: var(--bg); padding: 8px var(--gutter); margin: 0;
  border-top: 1px solid var(--border);
}
.mrq-sheet-backdrop {
  position: fixed; inset: 0; z-index: 60; background: rgba(0, 0, 0, .35);
  display: flex; align-items: flex-end; justify-content: center;
}
.mrq-sheet {
  width: 100%; max-width: 560px; max-height: 80vh; overflow-y: auto;
  background: var(--card-bg); border-radius: 16px 16px 0 0;
  padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  display: flex; flex-direction: column; gap: 8px;
}
.mrq-sheet h3 { margin: 0 0 4px; }
.mrq-sheet-option, .mrq-sheet-close { width: 100%; min-height: 44px; margin-top: 6px; }
.mrq-sheet-option.mrq-sheet-picked { border-color: var(--accent); font-weight: 600; }
.mrq-comment-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.mrq-comment { display: flex; gap: 10px; align-items: flex-start; }
.mrq-comment-body { margin: 4px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.mrq-composer { margin-top: 12px; }
/* CSP-290 (ADR-006 M13 to M16): offers, the RSVP chips, Who's in, the By link
   pill and the venue and share cards. Buttons keep .mrq-btn (44 px), chips 40 px. */
.mrq-pill-link { margin-left: auto; }
.mrq-tbd { font-style: italic; color: var(--muted); }
.mrq-players-in { margin: 0; font-weight: 600; }
.mrq-rsvp .mrq-chips { margin: 4px 0; }
.mrq-offers { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.mrq-offer {
  display: flex; flex-direction: column; gap: 4px; padding: 10px;
  border: 1px solid var(--border); border-radius: 12px;
}
.mrq-offers-count, .mrq-own-offer { margin: 0; font-weight: 600; }
.mrq-made { display: flex; flex-direction: column; gap: 6px; }
.mrq-venue, .mrq-share { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
.mrq-whos-in { margin-top: 12px; }
.mrq-rsvp-list { list-style: none; margin: 4px 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.mrq-rsvp-item { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 40px; }
.mrq-rsvp-me { font-weight: 700; }
.mrq-comment-to { display: block; }
@media (min-width: 900px) {
  .mrq-stepper-actions { position: sticky; bottom: 0; padding: 8px 0; border-top: 0; }
  .mrq-new { padding-bottom: 16px; }
  .mrq-sheet-backdrop { align-items: center; }
  .mrq-sheet { border-radius: 16px; }
}
