/* "Лёд и неон" (Fantiq brand palette) is the default theme since
   2026-08-19 - light is now the opt-in one, via [data-theme="light"] on
   <html>, toggled in the settings panel and remembered in localStorage
   (site.js's THEME_STORAGE_KEY). Bare :root carries the neon values so a
   pre-JS/no-JS paint still matches the new default rather than flashing
   the old light theme first. */
:root {
  color-scheme: dark;
  --bg: #0a1628;
  --surface: #111d32;
  --surface-alt: #16213b;
  --border: #1c2b45;
  --text: #e0f7ff;
  --text-dim: #7c93b0;
  --accent: #00e5ff;
  --accent-text: #0a1628;
  --ice: #13233d;
  --ice-line: #2e4a68;
  --ice-red: #ff4d6d;
  --ice-blue: #00e5ff;
  --success: #00e676;
}

html[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f6f9;
  --surface: #ffffff;
  --surface-alt: #eef1f6;
  --border: #dde2ea;
  --text: #14171c;
  --text-dim: #667085;
  --accent: #1c7ed6;
  --accent-text: #ffffff;
  --ice: #cdeeff;
  --ice-line: #ffffff;
  --ice-red: #d64545;
  --ice-blue: #1c7ed6;
  --success: #1a9e5c;
}

* { box-sizing: border-box; }

/* Unlike <a>, a <button> (and anything else made clickable purely via a JS
   onclick/addEventListener - [onclick], [role="button"]) does NOT get a
   pointer cursor by default in any browser - it shows the plain arrow
   unless every clickable element opts in individually, which several had
   quietly missed (reported 2026-08-20, the "Удалить" swipe-confirm one
   among them). One global rule instead of chasing each class by hand. */
button, [onclick], [role="button"] { cursor: pointer; }
button:disabled { cursor: default; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
}

#app {
  max-width: 1200px;
  margin: 0 auto;
  padding: 1rem 1rem 4rem;
}

/* Outer page margin halved on phones - every row inside .workspace-col
   already fights for width (see the .player-card mobile rules further
   down), so the page's own left/right margin is worth shrinking too
   rather than only ever trimming space inside the cards. */
@media (max-width: 820px) {
  #app { padding-left: 0.5rem; padding-right: 0.5rem; }
}

.site-header { margin-bottom: 1rem; position: relative; }
.site-header-row { display: flex; align-items: center; justify-content: center; gap: 0.6rem; position: relative; }
.site-header h1 { font-family: "Oswald", sans-serif; font-size: 1.8rem; margin: 0; text-align: center; }
.site-header .tagline { color: var(--text-dim); margin: 0.25rem 0 0; font-size: 0.9rem; text-align: center; }
/* Session identity + logout tucked into the header's own top-right corner
   (2026-08-31 redesign) instead of a full centered row under the tagline -
   the username was competing with the logo/tagline for the header's main
   visual weight when it's really just incidental account chrome. */
.session-corner {
  position: absolute;
  top: 0;
  right: 0;
  /* .site-header-row is a full-width flex box that comes later in the DOM and
     is position:relative, so without this it paints over the corner and
     swallows the logout click even though its visible content is centered. */
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.session-identity {
  font-size: 0.78rem;
  color: var(--text-dim);
  max-width: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.session-identity strong { color: var(--text); font-weight: 600; }
/* Round icon-only logout - was a labeled "Выйти" button competing with the
   header's centered branding; an icon reads as account chrome at a glance
   without needing a text label (title/aria-label still carry it for
   tooltips and screen readers). */
.logout-icon-btn {
  flex: none;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
}
.logout-icon-btn svg { width: 16px; height: 16px; }
.logout-icon-btn:hover {
  background: color-mix(in srgb, var(--ice-red) 18%, transparent);
  color: var(--ice-red);
  border-color: color-mix(in srgb, var(--ice-red) 40%, var(--border));
}
/* Narrow phones: drop the name text, keep just the round icon so it never
   crowds the "🏒 Fantiq" title next to it. */
@media (max-width: 480px) {
  .session-identity { display: none; }
}
.hero-banner {
  display: block;
  width: 100%;
  max-width: 720px;
  margin: 0 auto 1.25rem;
  border-radius: 14px;
}
.hero-cta-row {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  justify-content: center;
  margin: 0 auto 1.5rem;
  max-width: 40rem;
}
.hero-cta {
  flex: 1 1 220px;
  max-width: 320px;
  padding: 1rem 1.25rem;
  border-radius: 14px;
  font-weight: 700;
  font-family: "Oswald", sans-serif;
  text-decoration: none;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  transition: transform 150ms ease, box-shadow 150ms ease;
}
.hero-cta-primary {
  background: var(--accent);
  color: var(--accent-text);
  box-shadow: 0 4px 16px color-mix(in srgb, var(--accent) 35%, transparent);
}
.hero-cta-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 22px color-mix(in srgb, var(--accent) 45%, transparent);
}
.hero-cta-secondary {
  background: var(--surface);
  border: 1px dashed var(--border);
  color: var(--text-dim);
  cursor: default;
}
.hero-cta-badge {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ice-red) 20%, transparent);
  color: var(--ice-red);
}
.credentials-form { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }
.credentials-form input {
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text);
  padding: 0.45rem 0.6rem;
  font-size: 0.9rem;
  width: 100%;
  box-sizing: border-box;
}
/* 4 pills (Вход/Регистрация/sports.ru/Почта, since email login graduated
   out of beta 2026-08-20) never fit one row - NOT just on a narrow
   viewport: .login-gate below is capped at max-width: 24rem (384px)
   regardless of screen size, so a single flex row overflowed that same
   fixed-width card on a 1920px desktop too (confirmed via a live
   scrollWidth measurement: 379px of content in a 334px box) - this isn't
   a responsive breakpoint issue, it's unconditional. Always a 2x2 grid. */
.auth-mode-toggle { display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem; width: 100%; }
.auth-mode-toggle .tab-btn { flex: none; }
.auth-status {
  text-align: center;
  font-size: 0.82rem;
  color: var(--text-dim);
  margin: 0.4rem 0 0;
  line-height: 1.4;
}
.auth-status.error { color: #e05252; }
.auth-hint { margin: 0.2rem 0 0; font-size: 0.75rem; color: var(--text-dim); line-height: 1.35; }
.auth-consent-row {
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  margin: 0.3rem 0 0;
  font-size: 0.75rem;
  color: var(--text-dim);
  line-height: 1.35;
  text-align: left;
  cursor: pointer;
}
.auth-consent-row input {
  margin-top: 0.15rem;
  width: auto;
  padding: 0;
  border: none;
  background: none;
}
.link-btn {
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 0.8rem;
  text-decoration: underline;
  cursor: pointer;
  padding: 0.2rem 0;
  align-self: center;
}
.link-btn:hover { color: var(--text); }
.email-status { font-size: 0.82rem; color: var(--text-dim); margin: 0 0 0.4rem; line-height: 1.4; }
.email-status.error { color: #e05252; }
.settings-hint-small { font-size: 0.78rem; color: var(--text-dim); margin: 0 0 0.5rem; line-height: 1.4; }
/* Lowercase Greek "β" (user's ask, 2026-08-24 - replaced the spelled-out
   "бета" text), pinned to the section button's own top-right corner
   instead of sitting inline after the label (which pushed it to its own
   line below the label on .section-btn's column layout - the button this
   sits in needs `position: relative`, see .section-btn/.nav-btn). No
   text-transform:uppercase here on purpose - it would render as "Β"
   (capital beta), the opposite of what was asked. */
.beta-badge {
  position: absolute;
  /* Inside the button's own box, not straddling its edge (real bug,
     2026-08-24: negative offsets pushed it past .section-nav's
     overflow-x: auto clip region on the last button in the row, so it
     visibly got cut off). */
  top: 2px;
  right: 2px;
  display: inline-block;
  font-size: 0.6rem;
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.1rem 0.25rem;
}
/* Same shape as .beta-badge, gold instead of accent-colored - matches the
   gold used for shared/player_detail.js's existing ".pd-pro-label" (the
   first Pro perk's own inline marker, docs/subscription_pro.md), so every
   Pro-only mark in the app reads as one consistent color regardless of
   which file renders it. */
.pro-badge {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #d4a017;
  background: var(--surface-alt);
  border-radius: 4px;
  padding: 0.1rem 0.35rem;
  margin-left: 0.35rem;
  vertical-align: middle;
}
.verify-status { font-size: 0.85rem; margin: 0 0 0.4rem; color: var(--text-dim); }
.verify-status.verified { color: #2f9e58; font-weight: 700; }
.verify-status.pending { color: #a86400; font-weight: 700; }
/* Nickname/email input + a compact action button on the same line - Sports.ru
   and Почта's minimal "type it and go" shape (user's spec, 2026-08-18),
   replacing the old stacked label+input+full-width-button form. */
/* max-width caps it well short of #app's 1200px on a real desktop monitor
   (docs/ui_conventions.md's "don't stretch to fill the available width") -
   width isn't set here at all, so it still shrinks to fit a narrow
   container (a phone viewport) exactly as before. */
.inline-input-row { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; max-width: 22rem; }
.inline-input-row input {
  flex: 1;
  min-width: 0;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-size: 0.9rem;
  box-sizing: border-box;
}
/* Unlabeled arrow button (Sports.ru's submit) - no "Отправить"-style text,
   just "→" (user's explicit "без надписи, типа вперёд"). */
.arrow-btn {
  flex: 0 0 auto;
  width: 2.2rem;
  height: 2.2rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-text, #fff);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.email-verify-banner {
  max-width: 24rem;
  margin: 1rem auto 0;
  padding: 0.5rem 0.8rem;
  border-radius: 8px;
  background: var(--surface-alt);
  text-align: center;
  font-size: 0.85rem;
}
.email-verify-banner.error { color: #e05252; }

.login-gate {
  max-width: 24rem;
  margin: 3rem auto;
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  text-align: center;
}
.login-gate .gate-message {
  margin: 0 0 1rem;
  padding: 0.6rem 0.8rem;
  border-radius: 8px;
  background: var(--surface-alt);
  color: var(--text);
  font-size: 0.9rem;
}
.login-gate .login-hint { margin: 0 0 1.2rem; color: var(--text-dim); font-size: 0.95rem; }
.login-methods { display: flex; flex-direction: column; align-items: center; gap: 0.8rem; }
.telegram-login-widget-wrap { min-height: 40px; display: flex; justify-content: center; }
/* Reused inside Настройки -> Верификация (site.js's refreshVerificationSection)
   for the site-account "link Telegram" widget - same markup/mechanism as
   the login page's, just left-aligned instead of centered since it now
   sits inside a narrow settings column, not a full-width login screen. */
#verify-telegram-widget-wrap { justify-content: flex-start; }

/* Small round (?) button that toggles a collapsible explainer - Sports.ru's
   "как это работает" (user's spec, 2026-08-18): progressive disclosure so
   the instructions aren't always taking up space for a returning user. */
.help-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.1rem;
  margin-left: 0.3rem;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-dim);
  font-size: 0.7rem;
  line-height: 1;
  cursor: pointer;
  vertical-align: middle;
}
.telegram-widget-fallback {
  margin: 0;
  padding: 0.6rem 0.8rem;
  border-radius: 8px;
  background: var(--surface-alt);
  color: var(--text-dim);
  font-size: 0.82rem;
  line-height: 1.4;
}
.login-divider { color: var(--text-dim); font-size: 0.8rem; }

.tab-row {
  display: flex;
  gap: 0.4rem;
  overflow-x: auto;
  padding-bottom: 0.5rem;
  margin-bottom: 0.6rem;
  align-items: center;
}

.tab-btn {
  flex: 0 0 auto;
  padding: 0.5rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-dim);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.tab-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}

.tab-btn.disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ── Section nav (Профиль/Замены/Рейтинг/...) - deliberately NOT styled
   like .tab-btn (pill, single-line, no icon): a two-tier icon-over-label
   card, so it reads as a different kind of navigation (which section of
   the app) rather than another same-level tab row (view mode/league/
   format all just filter what's already on screen). */
.section-nav {
  display: flex;
  gap: 0.5rem;
  overflow-x: auto;
  /* overflow-x: auto with no overflow-y set computes overflow-y to auto too
     (CSS overflow spec) - without this top padding, .section-btn:hover's
     translateY(-2px) clips the button's own top border against that
     invisible y-scroll edge. */
  padding: 3px 0 0.6rem;
  margin-bottom: 0.8rem;
}
.section-btn {
  position: relative; /* anchors .beta-badge's top-right corner placement */
  flex: 1 1 0;
  min-width: 4.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.5rem 0.3rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text-dim);
  cursor: pointer;
  transition: border-color 150ms ease, background 150ms ease, color 150ms ease,
    transform 150ms ease, box-shadow 150ms ease;
}
.section-btn:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}
.section-btn.active {
  border-color: var(--accent);
  background: var(--surface-alt);
  color: var(--accent);
}
.section-icon { font-size: 1.2rem; line-height: 1; }
.section-label {
  font-size: 0.7rem;
  font-weight: 600;
  text-align: center;
  line-height: 1.15;
  white-space: normal; /* e.g. "Статистика хоккеистов" wraps to two lines */
}

/* Settings tab, pulled out of the .section-nav row into its own fixed
   round button, top-right, always on screen regardless of scroll (user's
   ask, 2026-08-20) - overrides the flex/column .section-btn base rules
   above rather than a whole separate class, so active/hover states stay
   identical to the other tabs. */
.settings-fab-btn {
  position: fixed;
  top: 14px;
  right: 14px;
  z-index: 500;
  flex: none;
  min-width: 0;
  width: 46px;
  height: 46px;
  padding: 0;
  border-radius: 50%;
  box-shadow: 0 2px 10px color-mix(in srgb, black 25%, transparent);
  /* Base .section-btn is a column flex with no justify-content, which
     top-aligns its (icon, label) pair - fine when there's a label below
     to fill the rest of the button, but this button has icon only, so
     without this it sits pinned to the top instead of centered in the
     circle. */
  justify-content: center;
}
.settings-fab-btn .section-icon { font-size: 1.3rem; }

/* Opens #pro-perks-modal - same fixed top-right positioning as
   .settings-fab-btn, immediately to its left (user's ask, 2026-08-24:
   "рядом с кнопкой настроек"). Gold, matching .pro-badge's color
   everywhere else Pro is marked, and a pill rather than a circle since
   "Pro" doesn't fit a 46px circle the way a single icon glyph does. */
/* Round, same 46px as .settings-fab-btn (user's ask, 2026-08-24 - was a
   pill, "кнопка Pro не круглая"). "PRO" still fits at this font-size. */
.pro-perks-fab-btn {
  position: fixed;
  top: 14px;
  right: 68px;
  z-index: 500;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 1px solid #d4a017;
  border-radius: 50%;
  background: var(--surface);
  color: #d4a017;
  font-weight: 700;
  font-size: 0.62rem;
  letter-spacing: 0.02em;
  box-shadow: 0 2px 10px color-mix(in srgb, black 25%, transparent);
  cursor: pointer;
}
.pro-perks-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.7rem; }
.pro-perks-list li { padding-left: 1.4rem; position: relative; line-height: 1.4; }
.pro-perks-list li::before { content: "★"; position: absolute; left: 0; color: #d4a017; }

/* #pro-perks-modal overrides the generic centered .manager-modal shape
   (user's ask, 2026-08-24): anchored top-right, right under
   .pro-perks-fab-btn, instead of the screen center every other
   .manager-modal use (Manager Mode room create/join) still gets - and
   opens with a liquid-glass scale+blur-in from the button rather than
   the other modals' instant show/hide. Scoped by #pro-perks-modal on
   every rule so those other modals are untouched. */
.manager-modal#pro-perks-modal { align-items: flex-start; justify-content: flex-end; padding: 68px 16px 1rem 1rem; }
/* Backdrop stays near-transparent (a faint dimming, not the other modals'
   solid-ish scrim) - this reads as a popover anchored to the button, not
   a page-blocking dialog. */
#pro-perks-modal .manager-modal-backdrop {
  background: color-mix(in srgb, black 12%, transparent);
  opacity: 0;
  transition: opacity 0.22s ease;
}
#pro-perks-modal.open .manager-modal-backdrop { opacity: 1; }
/* The "liquid glass" look: a blurred, saturated, translucent pane rather
   than the other modals' flat opaque .surface background. */
#pro-perks-modal .manager-modal-panel {
  max-width: 340px;
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid color-mix(in srgb, white 25%, transparent);
  box-shadow: 0 12px 32px color-mix(in srgb, black 30%, transparent);
  /* Scale+fade in from the button's own top-right corner (transform-origin)
     rather than growing from the panel's own center - matches "открывается
     из кнопки". Pre-"open" state (scaled down, transparent, shifted up)
     is the rest state right after "hidden" is removed but before "open" is
     added - see wireProPerksModal's rAF-rAF double-frame for why that gap
     has to exist at all. */
  transform-origin: top right;
  transform: scale(0.82) translateY(-10px);
  opacity: 0;
  transition: transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s ease;
}
#pro-perks-modal.open .manager-modal-panel { transform: scale(1) translateY(0); opacity: 1; }
.pro-perks-hint { margin: 1rem 0 0; color: var(--text-dim); font-size: 0.85rem; }

/* Priced CTAs (entitlements.FEATURE_ROLLOUT's "pro_purchase_buttons",
   experimental only) - real product/price for the acquiring-provider
   application, no payment integration yet so both link out to the same
   Telegram contact as .pro-perks-hint's fallback. */
.pro-perks-pricing { margin: 1rem 0 0; display: flex; flex-direction: column; gap: 0.4rem; }
.pro-perks-price { margin: 0.6rem 0 0; font-size: 0.9rem; }
.pro-perks-pricing .primary-btn,
.pro-perks-pricing .secondary-btn {
  display: inline-block;
  text-align: center;
  text-decoration: none;
}
.primary-btn {
  border: none;
  border-radius: 8px;
  padding: 0.5rem 1rem;
  font-weight: 700;
  cursor: pointer;
  background: var(--accent);
  color: var(--accent-text);
}

/* ── Team info panel (Профиль section's right column) ────── */
.team-info { display: flex; flex-direction: column; gap: 0.5rem; }
.team-info-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.6rem 0.7rem;
  background: var(--surface-alt);
  border-radius: 8px;
  font-size: 0.9rem;
}
.team-info-row span { color: var(--text-dim); }
.team-info-row strong { font-family: "JetBrains Mono", monospace; color: var(--text); }
.team-info-link-btn {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font-size: 0.78rem;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.team-info-link-btn:hover { color: var(--text); }
/* Above the "Осталось замен" row (user's ask, 2026-08-31: was inline next
   to the count, moved to its own line above), self-aligned right since
   the row below it is space-between. */
.team-info-history-btn { align-self: flex-end; }

.transfers-history-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  background: var(--surface-alt);
  border-radius: 8px;
  font-size: 0.85rem;
}
.transfers-history-row + .transfers-history-row { margin-top: 0.4rem; }
.transfers-history-ts { color: var(--text-dim); font-size: 0.78rem; white-space: nowrap; }
.transfers-history-players { display: flex; align-items: center; gap: 0.4rem; overflow: hidden; text-overflow: ellipsis; }
.transfers-history-out { color: var(--text-dim); text-decoration: line-through; }
.transfers-history-arrow { color: var(--text-dim); }
.transfers-history-in { color: var(--text); font-weight: 600; }

/* #tabs-format's own row (.tab-row already handles the format buttons'
   layout) plus, as a sibling, #rating-row-controls - the date range and
   "Привести друга" that used to live inside the rating tables themselves
   (user's ask, 2026-08-24: move both OUT of the table containers, up
   into this same row). margin-left: auto pushes the controls to the
   row's right edge, leaving Classic/Daily flush left as before; flex-wrap
   so a narrow viewport drops them to a 2nd line instead of squeezing
   .tab-row's own overflow-x scroll. */
.format-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.6rem; }
.format-row #tabs-format { min-width: 0; margin-bottom: 0; }
/* No margin-left: auto here any more - С/ДО sits in normal flow right
   after Classic/Daily (left half of the row), staying left of the
   boundary with the Влияние table/left of "Рейтинг очков"'s own right
   edge (user's ask, 2026-08-24: "левее правой границы Рейтинга очков").
   Only .influence-rating-header (the invite button) still gets pushed to
   the row's far right, via its own margin-left: auto below. */
/* flex-basis: 100% forces this onto its own line below #tabs-format
   (.format-row keeps flex-wrap), then the 1fr/1fr grid here mirrors
   .workspace-columns' own column split exactly (same gap, same
   minmax(0,1fr) pair) so the period-controls' right edge lands flush on
   the boundary between the Рейтинг очков and Влияние tables below (user's
   ask, 2026-08-24: "правее, к границе рейтинга очков"), instead of
   approximating it inside the Classic/Daily row's leftover flex space. */
.rating-row-controls { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.25rem; align-items: center; flex: 1 1 100%; padding-bottom: 0.6rem; }
/* "с"/"до" date range - same small-caps header look/size as .rating-table
   th (font family, size, letter-spacing), but lowercase rather than
   uppercase (user's ask, 2026-08-24) and right-aligned to this column's
   own edge (justify-self: end) rather than sitting flush left. */
.rating-period-controls {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  justify-self: end;
  font-size: 0.75rem;
  text-transform: lowercase;
  letter-spacing: 0.03em;
  color: var(--text-dim);
}
.rating-period-controls input[type="date"] {
  margin-left: 0.3rem;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text);
  font-size: 0.8rem;
  text-transform: none; /* the input's own value shouldn't be forced uppercase, just the С/ДО labels */
}

/* Онлайн section's date selector (user's 2026-08-25 ask) - same input
   look as .rating-period-controls above, own row above the friends bar. */
.online-date-row { display: flex; margin-bottom: 0.6rem; font-size: 0.85rem; color: var(--text-dim); }
.online-date-row input[type="date"] {
  margin-left: 0.4rem;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--text);
  font-size: 0.85rem;
}

/* ── Rating table (Рейтинг section) ───────────────────────── */
/* min-width: max-content - without it, a table this wide (7 columns in
   the Влияние table since the Топ-100 column landed, 2026-08-25) just gets
   squeezed to fit its 100%-width container instead of triggering the
   wrapper's overflow-x:auto scroll, clipping header text like "Топ-100"
   (real bug, same day). Forcing the table to never shrink below its own
   content width makes the scrollbar the thing that appears instead. */
.rating-table { width: 100%; min-width: max-content; border-collapse: collapse; font-size: 0.9rem; }
/* Admin panel's "Профиль ↗" deep link (site.js's highlightAdminTargetUser) -
   a brief fading flash, not a persistent style, so it reads as "here's the
   row you jumped to" rather than a permanent marker. */
@keyframes admin-target-flash {
  from { background: color-mix(in srgb, var(--accent) 35%, transparent); }
  to { background: transparent; }
}
.admin-target-highlight td { animation: admin-target-flash 4s ease-out; }
.rating-table th {
  text-align: left;
  color: var(--text-dim);
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0;
  padding: 0 0.3rem 0.5rem;
  border-bottom: 2px solid var(--border);
}
/* Grouped "Очки" header (colspan over the За период/Вчера/За всё время
   sub-columns, user's ask, 2026-08-24) - centered over its span, unlike
   every other (left-aligned) header cell, and its own bottom border
   separates it from the sub-header row instead of sitting flush against
   the table body. */
.rating-table thead tr:first-child th[colspan] { text-align: center; border-bottom: 1px solid var(--border); padding-bottom: 0.3rem; }
/* :nth-child(2), not :last-child - the Влияние table (same .rating-table
   class) has only ONE header row, and :last-child would then match that
   single row too, wrongly right-aligning its "Игрок"/"Статус" etc. This
   only ever matches when a 2nd header row actually exists (the
   tournament table's За период/Вчера/За всё время sub-columns). */
.rating-table thead tr:nth-child(2) th { padding-top: 0.3rem; text-align: right; }
.rating-table td { padding: 0.4rem; border-bottom: 1px solid var(--border); }
.rating-table td.rt-rank { color: var(--text-dim); font-family: "JetBrains Mono", monospace; width: 2rem; }
/* Sticky #/Игрок columns (user's ask, 2026-08-29: "чтобы при прокрутке
   рейтинга вправо, колонка с никами была всегда видна") - the wrapper
   (#rating-table/.influence-rating-table-wrap) already scrolls
   horizontally rather than letting cells wrap (see that rule's comment),
   which on a phone-width table with 7+ columns means the name scrolls
   straight off screen with nothing left to say whose row you're looking
   at. Both columns need an opaque background (var(--bg), matching the
   page background behind the table - neither the table nor its wrapper
   sets one of its own) or the scrolled-past cells behind them would show
   through. left: 2rem on .rt-name matches .rt-rank's own fixed width
   exactly, so the two form one continuous frozen block with no gap. */
.rating-table td.rt-rank, .rating-table th.rt-rank {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--bg);
}
.rating-table td.rt-name, .rating-table th.rt-name {
  position: sticky;
  left: 2rem;
  z-index: 1;
  background: var(--bg);
  white-space: nowrap;
}
.rating-table.rt-clickable tbody tr:hover td.rt-rank,
.rating-table.rt-clickable tbody tr:hover td.rt-name {
  background: var(--surface-alt);
}
.rating-table td.rt-points { font-family: "JetBrains Mono", monospace; font-weight: 700; text-align: right; color: var(--success); }
/* Только на правой (Влияние) таблице - раскрашивается через inline
   color-mix() per-row in site.js (rank-based grey->green gradient, best in
   the list is pure --success). Font/weight stay static here; only the
   color itself is set inline. */
.rating-table td.rt-influence { text-align: right; font-family: "JetBrains Mono", monospace; font-weight: 700; white-space: nowrap; }
/* Друзья/Верифик./Поддержка/Топ-100 (Влияние table) - numbers centered
   under their header, not left-aligned like a name column (user's ask,
   2026-08-25). */
.rating-table th.rt-center, .rating-table td.rt-center { text-align: center; }
/* Fantiq Pro perk (docs/subscription_pro.md's 3rd) - a tournament-rating
   row opens the owner's squad read-only (site.js's openSquadView). Only
   the left (tournament) table's rows are clickable - the right-hand
   Влияние table isn't squad-scoped at all, nothing to open there. */
.rating-table.rt-clickable tbody tr { cursor: pointer; }
.rating-table.rt-clickable tbody tr:hover { background: var(--surface-alt); }

/* Squad-view modal - same centered-overlay-with-backdrop shape as
   shared/player_detail.js's #player-detail, own smaller namespace since
   reusing that component would mean threading a "squad mode" through code
   built entirely around one player. Lower z-index than #player-detail (30)
   on purpose - a card tapped from inside this modal opens on top of it. */
#squad-view-modal {
  position: fixed;
  inset: 0;
  z-index: 25;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
#squad-view-modal.hidden { display: none; }
/* #transfers-history-modal reuses .squad-view-* classes for its INNER
   elements (backdrop/panel/header/body) but is its own top-level id, so it
   needs this same fixed-overlay/centering block itself, not just the
   shared inner classes - real bug, 2026-08-31: without this it rendered
   inline in normal document flow (pushed down after the footer) instead
   of as a centered overlay, since only #squad-view-modal (not a shared
   class) ever had position:fixed. */
#transfers-history-modal {
  position: fixed;
  inset: 0;
  z-index: 25;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
#transfers-history-modal.hidden { display: none; }
.squad-view-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); }
.squad-view-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 560px;
  max-height: 85vh;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}
.squad-view-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 1rem 2.6rem 0.8rem 1rem;
  border-bottom: 1px solid var(--border);
}
.squad-view-header h3 { margin: 0; font-size: 1.05rem; }
.squad-view-close-btn {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: var(--surface-alt);
  color: var(--text-dim);
  font-size: 0.9rem;
  cursor: pointer;
}
.squad-view-body { overflow-y: auto; padding: 0.8rem 1rem 1.2rem; }

.influence-rating-col { min-width: 0; }
/* "Привести друга" button/reveal - now up in .rating-row-controls (moved
   2026-08-24, see that class's own comment), not next to this table any
   more; kept as its own class since site.js's wireInfluenceRatingInvite
   still selects it by these ids. */
.influence-rating-header { display: flex; align-items: center; justify-content: flex-end; gap: 0.6rem; min-width: 0; margin-left: auto; }
.influence-rating-header #influence-invite-btn { flex-shrink: 0; }
/* min-width: 0 lets the revealed link box actually shrink (its own
   .invite-link-text is flex:1 + ellipsis) instead of forcing the header -
   and the button beside it - wider than the column (real bug that hit the
   old side-by-side layout, 2026-08-20; same fix, new layout). */
.influence-rating-header .invite-link-reveal { min-width: 0; flex: 1 1 auto; margin-top: 0; }
/* Table cells/headers never wrap to a second line (real bug, 2026-08-24 -
   two-word level names like "Зал Славы" wrapped when the column got
   squeezed); horizontal scroll on the wrapper is the fallback for a
   viewport too narrow to fit every column, per CLAUDE.md's wide-content
   rule, rather than letting cell text wrap and break row height. */
#rating-table, .influence-rating-table-wrap { overflow-x: auto; }
/* "Общий зачёт"/"Рейтинг влияния" headings above the two Рейтинг tables
   (user's ask, 2026-08-25) - static markup (templates/site/index.html),
   not JS-rendered, since the table containers below get their innerHTML
   fully replaced on every load. */
.rating-table-title { margin: 0 0 0.5rem; font-size: 0.95rem; color: var(--text-dim); font-weight: 600; }
.rating-table th, .rating-table td { white-space: nowrap; }
/* Level-thresholds legend is a hover tooltip now (native `title`, set in
   site.js's influenceLevelsTooltip) instead of a visible list, so the
   two tables split a plain, perfectly symmetric 1fr/1fr - the boundary
   between them lands exactly on this box's own horizontal center
   ("контейнеры двух рейтингов должны быть симметричны относительно
   центральной оси сайта", user's ask, 2026-08-24). That center is only
   the PAGE's own center, though, if the box itself spans the page's full
   content width - hence max-width: none below (2nd ask, same message:
   "расширь обе таблицы рейтингов до границ кнопок разделов"), unlike
   every other .workspace-columns pair (Squad/Picker etc.), which stay
   capped at 60rem and centered narrower than the section buttons above
   them. */
#section-rating-content.workspace-columns { max-width: none; }
/* 0-3 tightly-packed, overlapping green checkmarks - one per completed
   verification method, same overlap look as Telegram's own double-
   checkmark read receipt (user's own spec, 2026-08-20). */
.verify-checkmarks { display: inline-flex; margin-left: 0.3rem; }
.verify-checkmark {
  color: var(--success);
  font-size: 0.78rem;
  font-weight: 700;
  margin-left: -0.32rem;
}
.verify-checkmark:first-child { margin-left: 0; }
/* Founder badge (entitlements.FEATURE_ROLLOUT's "founder_badge", 2026-08-25) -
   plain text in a purple outline pill, same inline-after-name placement as
   the checkmarks above and the same shape as .pro-badge/.beta-badge, just
   outlined instead of filled (purple isn't used anywhere else in the
   palette, so an outline reads as its own distinct tier rather than
   competing with the filled gold PRO pill). Fixed hex regardless of theme,
   same convention .pro-badge already uses. */
.founder-badge {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #a855f7;
  background: transparent;
  border: 1px solid #a855f7;
  border-radius: 4px;
  padding: 0.05rem 0.32rem;
  margin-left: 0.35rem;
  vertical-align: middle;
  position: relative;
  top: -0.1em; /* the outline pill's own box otherwise sits visually lower than the name text next to it (user's ask, 2026-08-25) */
}

/* ── Статистика (Pro-only, docs/subscription_pro.md) ──────────────── */
.stats-table-controls { display: flex; gap: 0.6rem; margin-bottom: 0.8rem; flex-wrap: wrap; }
/* Same styled look as .picker-filters select (Замены's own filter row) -
   was a bare unstyled browser <select>, user's ask, 2026-08-24. */
.stats-table-controls select {
  max-width: 12rem;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 0.45rem 0.6rem;
  font-size: 0.9rem;
  cursor: pointer;
}
/* The one place this table is allowed to make the PAGE-level content
   overflow sideways (CLAUDE.md's UI layout priorities: the page itself
   must never scroll horizontally, a component's own content can) - 23
   stat columns plus name/team/position never fit a normal viewport, and
   dropping columns would defeat the point of "вся доступная стата". */
.table-scroll { overflow-x: auto; }
.stats-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; white-space: nowrap; }
.stats-table th, .stats-table td { padding: 0.4rem 0.55rem; text-align: right; border-bottom: 1px solid var(--border); }
.stats-table th {
  color: var(--text-dim);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border-bottom: 2px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--surface);
}
.stats-table-name { text-align: left; font-weight: 600; }
.stats-table th.stats-table-sortable { cursor: pointer; user-select: none; }
.stats-table th.stats-table-sortable:hover { color: var(--text); }
/* Small direction glyph, not a separate column - added via ::after so it
   never shifts the header's own text out of alignment with its column's
   data cells. */
.stats-table th.sorted-asc::after { content: " ▲"; color: var(--accent); }
.stats-table th.sorted-desc::after { content: " ▼"; color: var(--accent); }

/* ── Онлайн section (experimental) ─────────────────────────────────
   Card grid, not columns/a flat list (product decision) - auto-fit so
   4-6 cards fit across a normal desktop width without overflow, and it
   degrades to 1-2 per row on narrow/mobile widths automatically rather
   than needing a separate mobile layout. */
.online-friends-bar { position: relative; margin-bottom: 1rem; }
.online-friends-bar input {
  width: 100%;
  padding: 0.55rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 0.9rem;
}
.online-search-results {
  position: absolute;
  top: calc(100% + 0.25rem);
  left: 0;
  right: 0;
  z-index: 5;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  max-height: 220px;
  overflow-y: auto;
}
.online-search-result-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.88rem;
}
.online-search-result-row:last-child { border-bottom: none; }
.online-search-result-row button {
  border: none;
  background: var(--accent);
  color: #fff;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
/* auto-fill (not auto-fit) + a fixed upper bound (not 1fr) - see
   docs/ui_conventions.md's "don't stretch to fill the available width":
   1fr would let 2-3 cards balloon to 400-600px each on a wide desktop
   monitor once the grid runs out of room for another column. Leftover row
   space stays empty instead of being force-fed into existing cards. */
.online-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 240px));
  gap: 0.75rem;
}
.online-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.75rem 0.85rem;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  transition: box-shadow 150ms ease, transform 150ms ease;
}
.online-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  transform: translateY(-2px);
}
.online-card-self { border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.online-card-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  font-size: 0.92rem;
  margin-bottom: 0.5rem;
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--border);
}
/* flex: 1 pushes .online-card-total/.online-remove-friend-btn to the
   header's right edge without needing justify-content: space-between
   (which would've put the total in the middle with a 3rd child, the
   remove button, present). */
.online-card-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Today's total next to the nickname (user's ask, 2026-08-24). */
.online-card-total { color: var(--success); font-family: "JetBrains Mono", monospace; }
.online-remove-friend-btn {
  border: none;
  background: none;
  color: var(--text-dim);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.25rem;
}
.online-card-empty { color: var(--text-dim); font-size: 0.85rem; }
.online-card-locked { cursor: pointer; }
/* Uniform row height, no wrapping (CLAUDE.md's UI layout priorities) -
   name gets whatever width the meta column doesn't need, ellipsis rather
   than a second line. */
.online-player-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.3rem 0;
  font-size: 0.85rem;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
}
.online-player-row:last-child { border-bottom: none; }
.online-player-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
  min-width: 0;
}
.online-player-meta {
  flex: 0 0 auto;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.8rem;
  color: var(--text-dim);
  white-space: nowrap;
}

/* "Сегодня играли" - every real KHL team playing today, below Друзья
   (user's 2026-08-25 ask). Same .online-grid/.online-card shape, with a
   small group-title row (Вратари/Защитники/Нападающие) splitting each
   card's player list into its 3 position groups. */
.today-rosters-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 240px)); gap: 0.75rem; margin-top: 0.5rem; }
.today-roster-group-title {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-dim);
  margin: 0.4rem 0 0.15rem;
}
.today-roster-group-title:first-of-type { margin-top: 0; }

/* ── Приведи друга card (top of Настройки) ────────────────────────── */
.invite-card {
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 12%, transparent), transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
  border-radius: 10px;
  padding: 1rem 1.1rem;
  margin-bottom: 1.2rem;
}
.invite-card h3 { margin: 0 0 0.4rem; font-family: "Oswald", sans-serif; font-size: 1.05rem; }
/* Plain counter, replacing the old progress bar (2026-08-20) - see
   site.js's refreshInviteSection for why the bar never actually
   communicated real progress. */
.invite-counter { font-size: 2rem; font-weight: 700; line-height: 1; margin: 0 0 0.3rem; color: var(--accent); }
.invite-level { font-weight: 700; margin: 0 0 0.5rem; }
.invite-explanation { margin: 0 0 0.6rem; font-size: 0.82rem; color: var(--text-dim); }
.invite-explanation summary { cursor: pointer; font-weight: 600; }
.invite-explanation ul { margin: 0.4rem 0 0; padding-left: 1.2rem; }
.invite-explanation li { margin-bottom: 0.2rem; }
/* Настройки' Влияние card: both links shown right away, each directly
   under its own button (user's ask, 2026-08-24 - was a single shared
   reveal-on-click box for both). */
/* 2x2: site button/link left column, Telegram button/link right column
   (user's ask, 2026-08-24) - replaces the old stacked
   .invite-actions-col/.invite-action-row (button+link per method,
   methods stacked vertically). */
.invite-actions-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem 0.75rem; margin-top: 0.7rem; }
.invite-actions-grid .secondary-btn { width: 100%; }
/* Both buttons' Russian text ("Приведи друга на сайт"/"...в Telegram")
   is too long for a half-width column on a phone - same 820px mobile
   breakpoint as .workspace-columns above, collapsing back to one column
   (stacked site-then-Telegram) rather than letting the button text wrap
   mid-word. */
@media (max-width: 480px) {
  .invite-actions-grid { grid-template-columns: 1fr; }
}
/* position: relative is the positioning context for
   .invite-generated-hint-small's absolute placement (see that rule) -
   only the Рейтинг/Влияние header's reveal uses the hint span, so it's
   harmless on the other .invite-link-reveal uses (Настройки' two link
   rows). */
.invite-link-reveal { margin-top: 0.7rem; position: relative; }
/* Positioning context for .copied-badge's absolute placement above it
   (see that rule) - every link (Настройки' two, Рейтинг's one) is
   wrapped in one of these. */
.invite-link-line { position: relative; display: flex; align-items: center; gap: 0.6rem; }
.invite-link-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
  background: color-mix(in srgb, var(--text) 6%, transparent);
  border: 1px dashed color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 8px;
  padding: 0.5rem 0.6rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.8rem;
  color: var(--accent);
  cursor: pointer;
}
/* Floats above the link instead of sitting inline next to it (user's
   ask, 2026-08-24: "не двигает кнопку и ссылку") - .invite-link-line's
   own position: relative anchors it. */
.copied-badge {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-bottom: 0.3rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--success);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--success);
  white-space: nowrap;
  box-shadow: 0 2px 6px color-mix(in srgb, black 20%, transparent);
}
.invite-generated-hint { margin: 0.5rem 0 0; font-size: 0.8rem; color: var(--text-dim); }
/* Floats above the link box instead of sitting in normal flow above it
   (real bug, 2026-08-24: it used to push .invite-link-line, its row, and
   the whole interface down whenever the link got generated) - same
   float-without-shift trick as .copied-badge above. */
.invite-generated-hint-small {
  position: absolute;
  bottom: 100%;
  left: 0;
  margin-bottom: 0.2rem;
  font-size: 0.75rem;
  color: var(--text-dim);
  white-space: nowrap;
}

/* ── Rules text / stats placeholder (full-width content pages) ─── */
.rules-text h2 { font-family: "Oswald", sans-serif; font-size: 1.05rem; margin: 1.2rem 0 0.4rem; }
.rules-text h2:first-child { margin-top: 0; }
.rules-text p { margin: 0 0 0.6rem; line-height: 1.5; color: var(--text); }
.placeholder-block { text-align: center; color: var(--text-dim); padding: 3rem 1rem; }

.soon-badge {
  font-size: 0.65rem;
  font-weight: 700;
  background: var(--surface-alt);
  color: var(--text-dim);
  border-radius: 6px;
  padding: 0.1rem 0.35rem;
  margin-left: 0.3rem;
  vertical-align: middle;
}

/* Settings' three groups (Интерфейс/Аккаунт/Верификация), each a top-level
   <details> - collapsed by default EXCEPT Верификация (site.js never sets
   its `open` attribute, only the template does, once, since the point is
   nudging an unfinished action, not remembering a UI preference the user
   might collapse right back). */
.settings-group-collapsible {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0 0.9rem;
  margin-bottom: 1rem;
}
.settings-group-collapsible > summary.settings-group-title {
  margin: 0;
  padding: 0.8rem 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  list-style: none;
}
.settings-group-collapsible > summary.settings-group-title::-webkit-details-marker { display: none; }
.settings-group-collapsible > summary.settings-group-title::before { content: "▸ "; }
.settings-group-collapsible[open] > summary.settings-group-title::before { content: "▾ "; }
.settings-group-collapsible[open] > summary.settings-group-title { border-bottom: 1px solid var(--border); }
.settings-group-collapsible > .settings-section:first-of-type { margin-top: 0.9rem; }
.settings-group-collapsible > .settings-section:last-of-type,
.settings-group-collapsible > details.settings-section:last-of-type { margin-bottom: 0.9rem; }

.settings-section { margin-bottom: 1rem; }
.settings-section:last-of-type { margin-bottom: 0.6rem; }
.settings-hint { margin: 0 0 0.6rem; color: var(--text-dim); font-size: 0.85rem; }
/* Collapsed by default (details has no "open" attribute) - the drag-order
   list was taking up the same amount of vertical space as everything else
   in Settings combined for a control most people set once and never touch
   again. */
details.settings-section summary.settings-hint {
  cursor: pointer;
  margin-bottom: 0;
  list-style: none;
}
details.settings-section summary.settings-hint::-webkit-details-marker { display: none; }
details.settings-section summary.settings-hint::before { content: "▸ "; }
details.settings-section[open] summary.settings-hint::before { content: "▾ "; }
details.settings-section[open] summary.settings-hint { margin-bottom: 0.6rem; }
details.settings-section #format-order-list { margin-top: 0; }

/* Single compact toggle (was a two-button row) - shows the current
   theme's own label/icon, click flips to the other. */
.theme-toggle-btn {
  display: inline-block;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 20px;
  background: var(--surface-alt);
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
}

#format-order-list, #bio-field-order-list { list-style: none; margin: 0; padding: 0; }
.bio-field-checkbox-label { display: flex; align-items: center; gap: 0.4rem; cursor: pointer; }

/* Drag-and-drop reorder, not up/down buttons (user's "перетаскиванием"
   spec, 2026-08-18) - one compact row per format with a drag handle. */
.order-drag-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.6rem;
  background: var(--surface-alt);
  border-radius: 8px;
  margin-bottom: 0.3rem;
  cursor: grab;
  user-select: none;
}
.order-drag-item.dragging { opacity: 0.4; }
.order-drag-handle { color: var(--text-dim); font-size: 0.9rem; line-height: 1; }

/* Same max-width + centering as .workspace-columns below it, so its right
   edge lines up with the subs (picker) column's own right edge - this bar
   used to span the full (wider) page width on its own, which pushed
   .budget-bar-actions' buttons out past where the narrowed workspace
   actually ends. */
.budget-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  max-width: 60rem;
  margin: 0 auto 1rem;
  font-family: "JetBrains Mono", monospace;
  color: var(--text-dim);
  font-size: 0.9rem;
}

.budget-bar #budget-remaining { color: var(--success); font-weight: 700; }
/* Grouped together (not spread across the bar via .budget-bar's own
   space-between, which would leave a gap between them) - space-between
   only separates this group from .budget-text on the left. */
.budget-bar-actions { display: flex; align-items: center; gap: 0.5rem; }
#autofill-btn { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }

/* Primary action (accent-filled, unlike #autofill-btn's neutral
   .secondary-btn) - nothing reaches the server until this is pressed (see
   site.js's docstring on the draft/save model), so it's deliberately the
   most visually prominent control in the bar. Disabled (see site.js's
   updateSaveButton) whenever there's nothing unsaved or the squad still
   has an empty slot - the backend rejects the latter outright. */
#save-btn {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  border: none;
  border-radius: 8px;
  padding: 0.5rem 1rem;
  font-weight: 700;
  cursor: pointer;
  background: var(--accent);
  color: var(--accent-text);
}
#save-btn:disabled {
  background: var(--surface-alt);
  color: var(--text-dim);
  cursor: default;
}

.secondary-btn {
  background: var(--surface-alt);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 1rem;
  cursor: pointer;
}
.danger-btn { color: var(--ice-red); border-color: color-mix(in srgb, var(--ice-red) 45%, var(--border)); }

.hidden { display: none !important; }

/* Section-switch fade: replaying a CSS animation on an element that just
   went from display:none to visible needs no JS - the browser restarts
   animation state whenever an element re-enters layout, so this alone
   smooths section-btn.js's plain class-toggle without touching site.js. */
#section-squad-content:not(.hidden),
#section-online-content:not(.hidden),
#section-rating-content:not(.hidden),
#section-stats-content:not(.hidden),
#section-settings-content:not(.hidden),
#section-rules-content:not(.hidden) {
  animation: section-fade-in 220ms ease;
}
@keyframes section-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  #section-squad-content:not(.hidden),
  #section-online-content:not(.hidden),
  #section-rating-content:not(.hidden),
  #section-stats-content:not(.hidden),
  #section-settings-content:not(.hidden),
  #section-rules-content:not(.hidden) {
    animation: none;
  }
  .section-btn:hover { transform: none; }
  .online-card:hover { transform: none; }
  .hero-cta-primary:hover { transform: none; }
}

/* ── Workspace: view-mode rail, squad, picker.
   The rail is deliberately OUT of the grid that centers squad/picker - see
   .workspace-columns below for why - so .workspace itself is just a
   positioning context (position: relative) for the rail to sit in via
   absolute positioning, "by leftover principle": it takes only the width
   its own (now vertical, narrow) text needs, wherever that lands in the
   space .workspace-columns doesn't use, rather than claiming a dedicated
   grid track that would push the centered pair off-axis. */
.workspace {
  position: relative;
}

/* squad/picker: equal width (1fr/1fr) AND, as a pair, narrower than the
   full workspace and centered on it via margin: 0 auto - so the boundary
   between them lands exactly on #app's own horizontal center, independent
   of the view-mode rail's width (see .workspace's comment). minmax(0, 1fr),
   not bare 1fr: a bare 1fr track still sizes to its content's min-content
   width first and only THEN distributes leftover space, so a wide child
   (e.g. .picker-filters' selects, or a long jersey/face rink row) could
   force that column wider than its fair half and drag the boundary off-
   center; minmax(0, 1fr) pins the track's minimum to 0, so the fr unit
   alone decides the split and content overflows *inside* the column
   instead of resizing it. */
.workspace-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
  max-width: 60rem;
  margin: 0 auto;
}

@media (max-width: 820px) {
  .workspace-columns { grid-template-columns: 1fr; max-width: none; }
}

/* View-mode selector (Список/Джерси/Лица) - a narrow rail pinned to the
   workspace's left edge, sized to its own (vertical, see .tab-btn below)
   content rather than a fixed box - see .workspace's comment for why this
   is position: absolute instead of a grid column. */
.view-tabs-vertical {
  position: absolute;
  left: 0;
  top: 0;
  flex-direction: column;
  overflow-x: visible;
  width: auto;
}
/* Rotated via writing-mode (not a transform: rotate, which keeps the
   original horizontal box and just repaints the glyphs inside it) so the
   button's own layout box actually becomes narrow-and-tall instead of
   staying wide-and-short with sideways text drawn inside it. */
.view-tabs-vertical .tab-btn {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  width: auto;
  padding: 0.7rem 0.4rem;
  white-space: nowrap;
}

@media (max-width: 820px) {
  .view-tabs-vertical {
    position: static;
    flex-direction: row;
    margin-bottom: 0.6rem;
  }
  .view-tabs-vertical .tab-btn {
    writing-mode: horizontal-tb;
    width: auto;
  }
}

.workspace-col {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1rem;
  min-width: 0;
}

/* Halved to match #app's own mobile margin reduction below - otherwise
   the white card container's OWN padding still leaves a wide unused strip
   around the rows even after the page's outer margin shrinks. */
@media (max-width: 820px) {
  .workspace-col { padding: 0.5rem; }
}

.line-title {
  font-family: "Oswald", sans-serif;
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-dim);
  /* Slightly more room between звенья (user's ask, 2026-08-24) - was 1rem. */
  margin: 1.35rem 0 0.4rem;
}
.line-title:first-child { margin-top: 0; }

/* ── List view ─────────────────────────────────────────── */
/* .slot-list's gap (blocks visually separated, squad window) vs
   .picker-list's zero gap (blocks flush against each other, substitution
   window) is one of the three deliberate squad<->subs differences (the
   other two: .slot-row's grey background, and squad-only slot-position/
   .slot-remove) - see the shared .slot-row, .picker-row rule below, which
   is otherwise the SAME block shape (border, radius, padding, gap) in
   both windows. */
.slot-list { display: flex; flex-direction: column; gap: 0.4rem; }

/* Shared block shape for both windows - border/radius identical.
   .picker-row keeps the flat single-box shape (border/radius/padding/gap
   all on itself, no inner wrapper - it has nothing to swipe). .slot-row is
   now a THIRD deliberate squad<->subs difference on top of the two below
   (grey background, squad-only slot-position/.slot-remove): its visible
   content lives in a nested .slot-row-inner that a left-swipe can slide
   independently of the outer box, which stays put as the clipping frame
   revealing .slot-swipe-delete underneath (see that rule + site.js's
   wireSwipeToDelete). Padding/gap/background that used to sit directly on
   .slot-row moved onto .slot-row-inner unchanged - the two together are
   still pixel-identical to a plain .picker-row from the outside. */
.picker-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.5rem;
  cursor: pointer;
}

.slot-row {
  position: relative;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden; /* clips .slot-row-inner to this box as it slides left */
}

.slot-row-inner {
  position: relative; /* .slot-remove anchors to this, not the outer .slot-row */
  display: flex;
  align-items: center;
  gap: 0.4rem; /* was 0.7rem - the LW/C/RW label + this gap + .slot-position's own width read as excessive dead space on desktop before the surname, see .slot-position's comment */
  padding: 0.5rem;
  padding-right: 1.7rem;
  background: var(--surface-alt);
  cursor: pointer;
  touch-action: pan-y; /* vertical scroll stays native; JS claims horizontal drags only past its own direction-lock threshold */
}

.slot-row.empty { color: var(--text-dim); border-style: dashed; }
.slot-row.selected { outline: 2px solid var(--accent); outline-offset: -1px; }
.slot-row.locked { opacity: 0.7; }
/* Профиль section: no .slot-remove ("x") button at all, so there's no
   corner reserve to keep - back to the plain 0.5rem every side, exactly
   matching .picker-row. The card itself stays clickable even here: a tap
   opens the player-detail window, which is read-only anyway (see site.js's
   openPlayerDetail tap matrix). */
.slot-row.readonly .slot-row-inner { padding-right: 0.5rem; cursor: default; }

/* Revealed behind .slot-row-inner as it slides left - see
   site.js's wireSwipeToDelete. Not rendered for empty/readonly/locked
   slots at all (no swipe listener attached, see that function), so
   there's nothing to accidentally reveal there. */
.slot-swipe-delete {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 84px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #c33;
  color: #fff;
  font-family: "Oswald", sans-serif;
  font-weight: 600;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  pointer-events: none; /* purely visual - touchend on .slot-row-inner decides the outcome */
  cursor: pointer;
}

.slot-position {
  font-family: "JetBrains Mono", monospace;
  font-weight: 700;
  font-size: 0.8rem;
  color: var(--text-dim);
  /* Was 1.8rem - on desktop (where this box, unlike mobile, isn't already
     fighting for width) that plus .slot-row-inner's gap and the stripe/
     skill-reserve after it added up to a wide dead zone before the surname
     even started (~65px in a ~440px-wide card, see the .player-card
     comment for the full breakdown). 1.3rem still comfortably fits the
     longest label ("LD"/"RD") without clipping. */
  width: 1.3rem;
  flex: 0 0 auto;
  text-align: center; /* centered in the box between the row's left edge and the club stripe, not hugging either side (product decision, 2026-08-18 - was right-aligned to hug the stripe) */
}

.slot-empty-label { flex: 1; color: var(--text-dim); font-style: italic; padding: 0 0.4rem; cursor: pointer; }

.slot-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 18px;
  height: 18px;
  border: none;
  border-radius: 50%;
  background: rgba(214, 69, 69, 0.15);
  color: #c33;
  font-size: 11px;
  line-height: 18px;
  padding: 0;
  cursor: pointer;
}

/* Shared player-card layout, named grid areas - ONE constant template,
   now identical at EVERY viewport and on all three surfaces (desktop site,
   mobile site, Mini App - see frontend/styles.css's matching rule):
     row 1: [stripe][name, left            ][stats][price]
     row 2: [stripe][time+opponent, center ][stats][price]

   The two season averages are ONE cell (.p-stats), not two competing grid
   columns. That's the 2026-08-17 rework: as two columns they permanently
   fought the name for width - and one of them ("Текущий") renders "–" for
   every player in the league until the new season has match data (see
   routers/players.py's avg_points_this_season), so half that width bought
   nothing. Stacked inside one right-aligned cell they read as a pair
   (прошлый above, dim and small; текущий below, bold - a visual hierarchy
   instead of a header legend, which the squad window and the Mini App don't
   have) and free the difference for the surname. The full picture per
   season/per match now lives in the player-detail window a tap away
   (player_detail.py, site.js's openPlayerDetail).
   Skills (LW/C/RW/... badges) are NOT a grid column at all - they're an
   absolutely-positioned overlay (see .p-skills below) tucked between the
   stripe and where the name/time TEXT starts (name/meta reserve constant
   left padding for this, whether or not skills actually render). This is
   what makes the stripe/name/skills relationship identical in every
   format: Classic just leaves that reserved padding empty, Pro/Daily fill
   it - nothing else on the row ever shifts. Used by both a filled squad
   slot (.player-card in .slot-row, "окно состава" - the squad window,
   left column) and a picker row (.player-card in .picker-row, "окно
   замен" - the substitution window, right column) - mirrors the Mini
   App's .slot-main/.player-row (frontend/app.js's playerCardHtml) so a
   player reads identically wherever it appears. The column header
   (site.js's renderPickerColumnHeader) reuses this exact same grid so its
   controls land exactly above the matching data column, by construction
   rather than by matching pixel values by hand. */
.player-card {
  display: grid;
  grid-template-columns: 8px 1fr 40px 46px 52px;
  grid-template-rows: auto auto;
  grid-template-areas:
    "stripe name  points stats price"
    "stripe meta  points stats price";
  position: relative;
  align-items: center;
  align-content: start;
  gap: 2px 8px;
  min-height: 42px;
  flex: 1;
  min-width: 0;
}

.mono { font-family: "JetBrains Mono", monospace; font-variant-numeric: tabular-nums; }

.p-stripe {
  grid-area: stripe;
  align-self: stretch;
  width: 8px;
  border-radius: 3px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.p-stripe-edge, .p-stripe-middle { flex: 1 1 33.333%; }

/* Overlay, not a grid participant (see .player-card's comment) - anchored
   at the fixed x-offset where the name/meta column begins (8px stripe +
   8px column-gap), sitting inside that column's own reserved left
   padding rather than pushing it. */
.p-skills {
  position: absolute;
  left: 16px;
  top: 0;
  bottom: 0;
  width: 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  font-size: 10px;
  color: var(--text-dim);
  line-height: 1.15;
  pointer-events: none;
}

/* Flex wrapper (added 2026-08-21 for .p-popularity) rather than a single
   text node - the name itself still needs to shrink+ellipsis, while the
   popularity badge next to it (top row, right up against the "points"
   column - user's spec) never should. */
.p-name {
  grid-area: name;
  padding-left: 24px; /* reserved for .p-skills - constant across formats, see .player-card's comment */
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}
.p-name-text {
  font-family: "Oswald", sans-serif;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15px;
  line-height: 1.3;
  color: var(--text);
  min-width: 0;
  flex: 1 1 auto;
}
/* % of this format's saved squads that include the player - same
   font-size/weight as .p-today-points ("такого же шрифта как набранные
   очки"), same color as the name ("цвет как у фамилии") rather than
   .p-today-points' own gradient color, which only makes sense for a
   per-match outcome, not a static pool-wide stat. */
.p-popularity {
  flex: 0 0 auto;
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
}

/* Mobile (<=820px, the same breakpoint the workspace collapses to one
   column at): reclaim horizontal width from every column that isn't the
   name, rather than letting the name wrap. A wrapped second line grows
   THAT row's height only (grid rows are "auto") while every other row
   stays at its single-line height - rows visibly jumping in height as you
   scroll past whichever one happened to wrap is worse than a rare
   ellipsis, so this reverts to nowrap+ellipsis (the base .p-name rule,
   no override needed) and instead narrows everything around it:
   stat-last/stat-this stack into one column right next to price (already
   true before this comment existed), that column itself narrows further,
   grid/flex gaps tighten, and .slot-position shrinks - freeing enough
   width in practice that ellipsis should be the rare exception, not
   .p-name's earlier "always wraps instead" stance. */
@media (max-width: 820px) {
  /* The GRID is no longer overridden here - .player-card's template is one
     constant shape at every width now that the two season averages share a
     single cell (see that rule). All that's left to reclaim on a phone is
     spacing: tighter gaps and a slightly narrower stats/price pair. */
  .player-card {
    grid-template-columns: 8px 1fr 34px 40px 46px;
    gap: 2px 4px; /* tighter column-gap than desktop's 8px - hugs the stats block against price */
    min-height: 40px;
  }
  .p-stat-this { font-size: 14px; }

  /* padding-right stays at the same reserve as desktop (1.7rem, see the
     base .slot-row rule) - NOT shrunk here. A previous version of this
     rule used the "padding: 0.4rem" shorthand, which also zeroed
     padding-right and let .slot-remove's absolutely-positioned corner
     button sit directly on top of the price text. */
  /* Left edge -> stripe distance ~halved (was ~34px: 6.4px padding + 20.8px
     label box + 7.2px gap) by tightening the two purely-empty contributors
     (left padding, the label->stripe gap) and trimming the label box down
     to what its own text actually needs at the SAME font-size (0.72rem
     JetBrains Mono, 2 characters ≈ 14px) rather than shrinking that font -
     ~21px now. */
  .slot-row-inner { gap: 0.15rem; padding: 0.4rem 1.7rem 0.4rem 0.15rem; }
  /* Centered in the box, same as the base rule (product decision,
     2026-08-18) - was left-aligned here specifically since the base
     rule's right-align hugged the stripe and this mobile layout's
     left padding was thin enough that "hug the stripe" vs "hug the
     border" read as two visibly different positions; centering makes
     that distinction moot. */
  .slot-position { width: 1rem; font-size: 0.72rem; text-align: center; }

  /* Only the column WIDTHS still differ here - the stacked "двухэтажная"
     header shape moved into the base .picker-column-header rule, since the
     card it sits above now has that same shape at every width. Kept in
     lockstep with .player-card's mobile columns above, deliberately: the
     header is pinned to the data grid's template, so the two must change
     together or every label drifts off its column. */
  .picker-column-header {
    grid-template-columns: 8px 1fr 34px 40px 46px;
    gap: 2px 6px;
  }
}

/* Fixed 2-column grid (skull | time) instead of a centered flex row - a
   flex row re-centers its whole content whenever the skull span appears or
   disappears, which visibly shifted the time text horizontally at the
   exact moment it mattered most (a match starting). The skull column is
   reserved at a constant width whether or not its span is actually present
   (a plain length in grid-template-columns always reserves that track,
   empty or not), so the time column's position never moves regardless of
   what's around it. Today's points used to be a 3rd column HERE, sharing
   this one thin row (meta/row 2) with skull+time - on a narrow card the
   combined content could exceed the available width, and since the row is
   centered + overflow: hidden, BOTH ends (skull AND points) clipped
   symmetrically while only the time text in the middle survived (real bug
   on the Mini App, e.g. a goalie's "-25" - reported 2026-08-19, same
   underlying grid shared with .slot-main/.player-row there). Points now
   lives in its own full-height column (.p-today-points, "points" grid area
   below), vertically centered like .p-stats/.p-price, with no row-2 width
   competition at all. */
.p-meta {
  grid-area: meta;
  padding-left: 24px; /* same reserve as .p-name, see .player-card's comment */
  display: grid;
  grid-template-columns: 16px auto;
  justify-content: center;
  align-items: center;
  column-gap: 4px;
  line-height: 1.3;
  overflow: hidden; /* skull sits alongside the time text now, not instead of it - clip rather than wrap, so a long combination never grows row 2's height and drags the whole card taller (see .player-card's comment on why wrapping is avoided) */
  white-space: nowrap;
}
.p-time-skull { grid-column: 1; justify-self: center; }
.p-time-value { grid-column: 2; font-size: 12px; color: var(--text-dim); justify-self: center; }
.p-time-value.soon { color: #a86400; }
.p-time-value.locked { color: #c33; }
.p-time-value.upcoming { color: var(--text-dim); }
.p-time-skull { font-size: 12px; }
/* Goalie net/bench icons share .p-time-skull's grid slot but read too
   small at the skull's own size (user's ask, 2026-08-27) - bumped up
   without touching the skull itself. */
.p-goalie-icon { font-size: 18px; }
/* Own column ("points" grid area), spans both rows, vertically centered -
   same treatment as .p-stats/.p-price. Bare number, no "очков" word. Color
   comes from site.js's todayPointsColor() as an inline style (a gradient
   can't be expressed as a fixed CSS color), not from here. */
.p-today-points {
  grid-area: points;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  font-weight: 700;
}

/* One cell, two stacked numbers, both flush right against price (see
   .player-card's comment). Which is which is carried by weight/size/colour
   rather than a label: прошлый сезон is the small dim one on top, текущий
   the bold one below - the same order the picker's stacked column header
   above them uses, and the same order the Mini App uses where there is no
   header at all. */
.p-stats {
  grid-area: stats;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  line-height: 1.2;
  min-width: 0; /* a flex item's default min-width is "auto" (content width), which let a wide number ignore the 40px stats track and spill into .p-price - real bug, reported 2026-08-20 */
  overflow: hidden;
}
.p-stat-last, .p-stat-this {
  max-width: 100%;
  overflow: hidden;
  text-overflow: clip; /* a numeric value truncated with "…" reads as a different, shorter number - clip (not ellipsis) is the honest failure mode if a value is ever still too wide */
  white-space: nowrap;
}
.p-stat-last { font-size: 11px; color: var(--text-dim); }
.p-stat-this { font-size: 15px; font-weight: 600; color: var(--text); }
/* A missing value drops back to the small dim treatment even in the bold
   "текущий" row - otherwise the largest, heaviest thing on the card is a
   dash carrying no information, which is exactly what every player looks
   like until the new season has match data (routers/players.py's
   avg_points_this_season). */
.p-stat-dash { font-size: 11px; font-weight: 400; color: var(--text-dim); }

/* Spans both grid rows (not just the "price" area's own row 2) and sized
   up - applies identically in .slot-row and .picker-row now that both
   render price inside .player-card the same way (site.js's renderSlotList
   docstring): neither row has anything else competing for that space
   (squad's .slot-remove is a corner overlay outside the card, not a grid
   participant), so price always gets the full row height. */
/* Dark gray, not green (user's own call, 2026-08-19) - price is
   informational, not a status to react to, unlike today's points'
   maroon-to-green/purple gradient, so it deliberately stays neutral rather
   than competing for attention or implying "good". */
.p-price { grid-area: price; font-weight: 700; font-size: 20px; line-height: 1.3; text-align: right; color: var(--text-dim); }

/* ── Rink background for jersey/faces line layout ─────────── */
.rink {
  position: relative;
  background: var(--ice);
  border: 4px solid var(--ice-line);
  border-radius: 28px;
  padding: 1.2rem 0.8rem;
  display: flex;
  flex-direction: column;
  /* Slightly more room between звенья (user's ask, 2026-08-24) - was 0.9rem. */
  gap: 1.15rem;
  overflow: hidden;
}

.rink::before {
  /* center red line */
  content: "";
  position: absolute;
  left: 6%;
  right: 6%;
  top: 50%;
  height: 3px;
  background: var(--ice-red);
  transform: translateY(-50%);
  z-index: 0;
}

.rink-line {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.rink-row {
  display: flex;
  justify-content: center;
  gap: 0.9rem;
}

.rink-goalies {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
  gap: 1.4rem;
  padding-top: 0.6rem;
  border-top: 3px solid var(--ice-blue);
}

/* ── Jersey cards ──────────────────────────────────────── */
.jersey-card {
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  width: 84px;
}

.jersey-shirt {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 10px 10px 4px 4px;
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Oswald", sans-serif;
  font-size: 1.5rem;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,0.4);
}

.jersey-shirt.empty {
  background: rgba(255,255,255,0.5);
  border-style: dashed;
  color: var(--text-dim);
  font-size: 1.4rem;
}

.jersey-shirt.selected { outline: 3px solid var(--accent); outline-offset: 2px; }

.jersey-label {
  font-size: 0.7rem;
  color: var(--text-dim);
  text-align: center;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: var(--surface);
  border-radius: 4px;
  padding: 0 0.2rem;
}
.jersey-price {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.65rem;
  color: var(--text-dim);
  font-weight: 700;
}

/* ── Face cards ────────────────────────────────────────── */
.face-card {
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  width: 84px;
}

.face-circle {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Oswald", sans-serif;
  font-weight: 700;
  font-size: 1.2rem;
  color: #fff;
  border: 2px solid var(--border);
}

.face-circle.empty {
  background: rgba(255,255,255,0.5);
  border-style: dashed;
  color: var(--text-dim);
  font-size: 1.4rem;
}

.face-circle.selected { outline: 3px solid var(--accent); outline-offset: 2px; }

.face-label {
  font-size: 0.7rem;
  color: var(--text-dim);
  text-align: center;
  max-width: 84px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  background: var(--surface);
  border-radius: 4px;
  padding: 0 0.2rem;
}
.face-price {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.65rem;
  color: var(--text-dim);
  font-weight: 700;
}

/* ── Picker panel (always visible, right column) ──────────── */
.picker-panel-header {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 0.7rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--border);
}

.picker-slot-nav { display: flex; align-items: center; gap: 0.5rem; position: relative; }
/* Small "?" circle, absolutely positioned to the right edge (user's ask,
   2026-08-24) rather than a 3rd flex child - the row's arrows are hidden
   for everyone but beta testers (see .nav-btn below), so most of the time
   this would be the ONLY thing balancing .picker-slot-label's flex:1
   centering, and an extra flex child pushes that center point left. This
   overlays instead, so centering is exactly what it was before this
   button existed. */
.picker-hint-btn {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  flex: 0 0 auto;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-alt);
  color: var(--text-dim);
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1;
  cursor: help;
  padding: 0;
}
/* Hidden for everyone except beta testers (is_experimental_tester) - user's
   correction, 2026-08-20 ("убери для всех кроме beta-тестеров", the
   opposite of this rule's first version, which showed them to everyone
   BUT beta). See site.js's main(), docs/beta_differences.md. */
.picker-slot-nav .nav-btn { display: none; }
.picker-slot-nav .picker-slot-label { flex: 1; text-align: center; }
.picker-slot-nav.show-arrows .nav-btn { display: block; }
.picker-slot-nav.show-arrows .picker-slot-label { flex: initial; text-align: inherit; }

.nav-btn {
  flex: 0 0 auto;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-alt);
  color: var(--text);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.picker-slot-label {
  flex: 1;
  text-align: center;
  font-family: "Oswald", sans-serif;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text);
}

#picker-search {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 0.5rem 0.7rem;
}

/* Fixed grid columns - Daily (3 filters visible): Поиск 50% / Доступность
   20% / Команда 30%. Classic/Pro (#picker-availability display:none - see
   selectSlot, which also toggles .no-availability here): Поиск 50% /
   Команда 50% - a plain 2-column 1fr/1fr template, not the 3-column one
   with an empty middle track, per product decision (Classic/Pro have no
   day-availability concept at all, so there's nothing to reserve space
   for there, unlike Daily). */
.picker-filters {
  display: grid;
  grid-template-columns: 5fr 2fr 3fr;
  gap: 0.5rem;
}
.picker-filters.no-availability {
  grid-template-columns: 1fr 1fr;
}
.picker-filters #picker-search { min-width: 0; } /* background/border/padding already come from the general #picker-search rule above */
/* max-width caps it well short of #app's 1200px on a real desktop monitor
   (docs/ui_conventions.md's "don't stretch to fill the available width") -
   width: 100% is kept so it still shrinks to fit a narrow container. */
.settings-select {
  width: 100%;
  max-width: 22rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 0.45rem 0.6rem;
  font-size: 0.9rem;
}
.inline-input-row .settings-select { flex: 1; min-width: 0; margin-bottom: 0; width: auto; max-width: none; }
.picker-filters select {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  padding: 0.4rem 0.5rem;
  min-width: 0;
}

.price-range {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding-top: 0.2rem;
}
.price-range-label { font-size: 1.1rem; font-weight: 700; color: var(--text); }
.price-range-track { position: relative; width: 100%; height: 24px; }
.price-range-track-bg {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 4px;
  transform: translateY(-50%);
  background: var(--border);
  border-radius: 2px;
}
.price-range-track input[type="range"] {
  position: absolute;
  left: 0; right: 0; top: 50%;
  transform: translateY(-50%);
  width: 100%;
  margin: 0;
  background: none;
  pointer-events: none;
  -webkit-appearance: none;
  appearance: none;
}
.price-range-track input[type="range"]::-webkit-slider-runnable-track { background: transparent; }
.price-range-track input[type="range"]::-moz-range-track { background: transparent; }
.price-range-track input[type="range"]::-webkit-slider-thumb {
  pointer-events: auto;
  -webkit-appearance: none;
  appearance: none;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--surface);
  cursor: pointer;
}
.price-range-track input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--surface);
  cursor: pointer;
}

/* Column headers - pinned to the SAME grid template as .player-card (see
   that rule's comment) so each label sits directly above its actual data
   column instead of an independent equal-share row (an earlier version of
   this rule deliberately avoided that pinning - see git history - but the
   mismatch it caused between header and data columns was worse than the
   crowding that motivated it). "Играющие" has no data column of its own
   (it's a day filter, not a stat) - it shares the "name" column's cell
   with the "Игрок" sort button (see .col-header-name), same as the name
   text and skills share that column's space one row down. */
.picker-column-header {
  display: grid;
  grid-template-columns: 8px 1fr 40px 46px 52px;
  grid-template-rows: auto auto;
  align-items: center;
  gap: 2px 8px;
  /* Left/right 0.5rem matches .picker-row's own padding exactly (see the
     shared .slot-row, .picker-row rule) - .picker-row's .player-card sits
     inset by that padding, so without matching it here the header's grid
     columns started flush at x=0 while the data columns started 0.5rem to
     the right of it, throwing every column out of alignment by that
     amount despite using the identical column template. */
  padding: 0 0.5rem 0.5rem;
  border-bottom: 2px solid var(--border);
  margin-bottom: 0.3rem;
}

.col-header-name {
  grid-column: 2;
  grid-row: 1 / 3;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}
.col-header-name .play-filter-select { flex: 0 1 auto; }

.col-sort-btn {
  min-width: 0;
  background: none;
  border: none;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 700;
  font-size: 0.75rem;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.col-sort-btn.col-name { text-align: left; }
/* Прошлый/Текущий stack in ONE column, directly above the two numbers of
   .p-stats they sort by (the mobile layout the user signed off on, now the
   layout at every width - see .player-card's comment). overflow: visible
   (not the base rule's hidden+ellipsis) is deliberate - the label must
   never be truncated, so it spills into the wide "name" gutter beside it
   instead of clipping. text-align: right (not center, despite stacking one
   per row) - .p-stats below it is right-aligned against price
   (align-items: flex-end), and a CENTERED label with symmetric overflow
   bleeds equally in both directions, which on a narrow 40px column reached
   past the 8px column-gap into "Цена" itself (real bug, found on every
   viewport, not just mobile - 2026-08-19). Right-aligning pins the label's
   right edge at the column's own right edge, so all of its overflow spills
   left into the wide gutter and none of it can ever touch the price
   column, matching where .p-stats' own numbers sit one row down. */
.col-sort-btn.col-stat-last,
.col-sort-btn.col-stat-this {
  grid-column: 4;
  text-align: right;
  /* Nudges the right-anchor point left of the column's true right edge
     (user's ask, 2026-08-24 - "подвинь левее") without giving up the
     right-align itself, which the comment above still needs (keeps
     overflow spilling away from "Цена", not into it). */
  margin-right: 0.35rem;
  overflow: visible;
  text-overflow: clip;
}
.col-sort-btn.col-stat-last { grid-row: 1; }
.col-sort-btn.col-stat-this { grid-row: 2; }
.col-sort-btn.col-price { grid-column: 5; grid-row: 1 / 3; text-align: right; }
.col-sort-btn:hover { color: var(--text); }
.col-sort-btn.active { color: var(--accent); }
.col-sort-btn .sort-arrow { font-size: 0.65rem; margin-left: 1px; }

.play-filter-select {
  min-width: 0;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 700;
  font-size: 0.7rem;
  color: var(--text-dim);
  padding: 0.15rem 0.2rem;
  cursor: pointer;
}
.play-filter-select.active { color: var(--accent); border-color: var(--accent); }

/* Zero gap between rows (the other deliberate squad<->subs difference,
   see .slot-list's comment) - .picker-row's own block shape is the shared
   .slot-row, .picker-row rule above. */
/* ~10 rows in view before scrolling (user's ask, 2026-08-24 - was 60vh,
   only fit ~7-8 on a typical laptop viewport). .picker-row is ~58px tall
   (0.5rem padding + content + border) with zero gap between rows, so
   10 * 58px = 580px; min() keeps it from overflowing a short viewport. */
.picker-list { max-height: min(75vh, 580px); overflow-y: auto; }

.picker-row:hover { background: var(--surface-alt); }
/* Dimmed, but still clickable: a locked player can't be picked (site.js's
   renderPickerList attaches no pick handler) yet must still open his
   detail window while browsing - pointer-events: none used to swallow that
   tap too. */
.picker-row.locked { opacity: 0.5; }
.picker-row.taken { opacity: 0.35; pointer-events: none; }
.picker-empty { color: var(--text-dim); text-align: center; padding: 1.5rem 0; }
/* Squad-loading spinner (user's ask, 2026-08-24) - #squad-container shown
   while loadSquad's fetch is in flight, replaced by the real squad on
   completion (site.js's refreshSectionContent). */
.spinner-wrap { display: flex; justify-content: center; padding: 2.5rem 0; }
.spinner {
  width: 32px;
  height: 32px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spinner-spin 0.8s linear infinite;
}
@keyframes spinner-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
}

#error-banner {
  position: fixed;
  left: 50%;
  bottom: 1.2rem;
  transform: translateX(-50%);
  background: #c33;
  color: #fff;
  padding: 0.7rem 1.1rem;
  border-radius: 10px;
  max-width: 90vw;
  z-index: 20;
  font-size: 0.9rem;
}

footer {
  text-align: center;
  margin-top: 3rem;
  color: var(--text-dim);
  font-size: 0.8rem;
}

/* ── Manager Mode section (experimental) ─────────────────────────────
   docs/manager_mode_plan.md. Reuses .secondary-btn/.picker-empty/
   .squad-view-close-btn from elsewhere in this file - only genuinely new
   shapes get their own class here. */
.manager-list-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; }
.manager-join-row { display: flex; gap: 0.5rem; margin-bottom: 1rem; max-width: 360px; }
.manager-join-row input {
  flex: 1;
  padding: 0.5rem 0.7rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.manager-rooms-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 280px)); gap: 0.75rem; }
.manager-room-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.75rem 0.85rem;
  background: var(--surface);
  cursor: pointer;
  transition: box-shadow 150ms ease, transform 150ms ease;
}
.manager-room-card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); transform: translateY(-2px); }
.manager-room-card-header { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.4rem; }
.manager-room-card-meta { color: var(--text-dim); font-size: 0.85rem; }
.manager-status-badge {
  font-size: 0.72rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.manager-detail-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; }
.manager-detail-header h2 { margin: 0; flex: 1; }

/* Same centered-overlay-with-backdrop shape as #squad-view-modal, own
   namespace since two modals (create room / pick player) share it here. */
.manager-modal { position: fixed; inset: 0; z-index: 25; display: flex; align-items: center; justify-content: center; padding: 1rem; }
.manager-modal.hidden { display: none; }
.manager-modal-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); }
.manager-modal-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 480px;
  max-height: 85vh;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}
.manager-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 1rem 2.6rem 0.8rem 1rem;
  border-bottom: 1px solid var(--border);
}
.manager-modal-header h3 { margin: 0; font-size: 1.05rem; }
.manager-modal-body { padding: 1rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.65rem; }
.manager-modal-body label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; color: var(--text-dim); }
.manager-modal-body input, .manager-modal-body select {
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface-alt);
  color: var(--text);
  font-size: 0.9rem;
}
.manager-modal-body button#manager-create-room-submit-btn,
.manager-modal-body button#manager-pick-confirm-btn { margin-top: 0.25rem; }
.manager-advanced-fields { border: 1px solid var(--border); border-radius: 8px; padding: 0.5rem 0.7rem; }
.manager-advanced-fields summary { cursor: pointer; color: var(--text-dim); font-size: 0.85rem; }
.manager-advanced-fields label { margin-top: 0.5rem; }

.manager-joining-panel p { margin: 0.3rem 0; }
.manager-invite-row { margin-top: 0.6rem; }
.manager-invite-row code {
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.15rem 0.5rem;
}

.manager-turn-banner {
  padding: 0.65rem 0.9rem;
  border-radius: 10px;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  margin-bottom: 1rem;
  font-weight: 600;
}
.manager-turn-banner.my-turn { background: color-mix(in srgb, var(--accent) 18%, var(--surface-alt)); border-color: var(--accent); }

.manager-slot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 150px)); gap: 0.5rem; margin-bottom: 1rem; }
.manager-slot-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.5rem 0.6rem;
  background: var(--surface);
  font-size: 0.85rem;
}
.manager-slot-card.is-bench { opacity: 0.75; border-style: dashed; }
.manager-slot-card.clickable { cursor: pointer; }
.manager-slot-card.clickable:hover { border-color: var(--accent); }
.manager-slot-key { font-weight: 700; text-transform: uppercase; font-size: 0.72rem; color: var(--text-dim); margin-bottom: 0.2rem; }

.manager-pick-results, .manager-wishlist-panel #manager-wishlist-results {
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
}
.manager-pick-result-row { padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--border); cursor: pointer; font-size: 0.88rem; }
.manager-pick-result-row:last-child { border-bottom: none; }
.manager-pick-result-row:hover { background: var(--surface-alt); }
.manager-pick-result-row.selected { background: color-mix(in srgb, var(--accent) 20%, var(--surface)); }
.manager-pick-terms { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.5rem; }

.manager-wishlist-panel input { width: 100%; margin-bottom: 0.5rem; }
.manager-wishlist-remove-btn {
  border: none;
  background: none;
  color: var(--text-dim);
  cursor: pointer;
  margin-left: 0.4rem;
}

.manager-picks-log { display: flex; flex-direction: column; gap: 0.3rem; max-height: 320px; overflow-y: auto; }
.manager-pick-log-row {
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  font-size: 0.85rem;
}

/* ── Top-level areas (Фэнтези / Прогнозы / Новости) + Прогнозы section ──
   #toplevel-nav sits above the section nav as a bolder segmented control
   so it reads as a different navigation tier. Hidden entirely for
   non-testers (site.js). */
.toplevel-nav {
  display: flex;
  gap: 0.4rem;
  margin: 0 0 0.9rem;
  padding: 0.3rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
}
.toplevel-btn {
  flex: 1 1 0;
  padding: 0.55rem 0.6rem;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--text-dim);
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.toplevel-btn.active {
  background: var(--accent);
  color: var(--accent-text);
}

#area-predict-content { margin: 0 auto; }
.predict-intro h2 { margin: 0 0 0.4rem; font-size: 1.15rem; }
.predict-rules { color: var(--text-dim); font-size: 0.85rem; line-height: 1.45; margin: 0 0 0.4rem; }
.predict-deadline { font-size: 0.85rem; margin: 0 0 0.8rem; }
.predict-deadline em { color: var(--text-dim); font-style: normal; }
.predict-hint { font-size: 0.78rem; color: var(--text-dim); margin: 0.5rem 0 0; }
.predict-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.8rem 1.2rem; margin-bottom: 0.6rem; }
.predict-save-status { font-size: 0.85rem; margin: 0.5rem 0 0; color: var(--accent); }
.predict-save-status.error { color: #ff6b6b; }
#predict-standings-save, [id$="-save"].secondary-btn { margin-top: 0.7rem; }

/* discrete: ordered list */
.predict-discrete { display: flex; flex-direction: column; gap: 0.25rem; }
.predict-disc-row {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--border); border-radius: 8px; background: var(--surface);
}
.predict-place { font-weight: 700; color: var(--accent); min-width: 1.5rem; text-align: center; }
.predict-team { flex: 1; }
.predict-disc-arrows button {
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
  border-radius: 6px; width: 1.8rem; height: 1.8rem; cursor: pointer; font-size: 0.8rem;
}
.predict-disc-arrows button:disabled { opacity: 0.3; cursor: default; }

/* differentiated grid */
/* Two 11x11 grids sit side by side inside .predict-standings-cols, so the
   table must fit its half-column with no horizontal scroll - fixed layout,
   equal-width cells, abbreviations (not full names) in the row labels. The
   wrap keeps overflow-x only as a last-resort safety on very narrow phones
   (below the 900px breakpoint the two grids already stack vertically). */
.predict-grid-wrap { overflow-x: auto; }
.predict-grid { border-collapse: collapse; table-layout: fixed; width: 100%; font-size: 0.82rem; }
.predict-grid th, .predict-grid td {
  border: 1px solid var(--border); text-align: center; padding: 0.15rem 0;
  height: 2.2rem; overflow: hidden;
}
.predict-grid th:first-child, .predict-grid td:first-child { width: 2.3rem; }   /* ож. (avg place) */
.predict-grid th:nth-child(2), .predict-grid td:nth-child(2) { width: 3.4rem; } /* abbr */
.predict-grid th:last-child, .predict-grid td:last-child { width: 2rem; }
.predict-grid-avg {
  text-align: right; color: var(--text-dim); font-variant-numeric: tabular-nums;
  font-size: 0.72rem; padding: 0 0.25rem;
}
.predict-grid-team {
  text-align: left; white-space: nowrap; font-weight: 600;
  padding: 0 0.25rem; text-overflow: ellipsis; overflow: hidden;
}
.predict-grid-foot td:first-child { text-align: left; white-space: nowrap; padding: 0 0.25rem; }
.predict-cell { cursor: pointer; user-select: none; -webkit-touch-callout: none; }
.predict-cell:hover { outline: 2px solid var(--accent); outline-offset: -2px; }
.predict-grid-sum { font-weight: 700; }
.predict-grid-foot td { color: var(--text-dim); font-size: 0.8rem; }

/* stat nominations - one card per metric, cards flow left->right; inside
   each, a vertical 1..5 ranked list (empty ranks shown as placeholder
   rows, "как рейтинг"). */
.predict-nom-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
  gap: 0.9rem; margin-bottom: 1rem;
}
/* Main individual-stats block: a fixed 4 across (the other groups stay
   auto-fill ~5). Drops to 2 on a phone. */
.predict-nom-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 720px) {
  .predict-nom-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* Player nominations are shown in groups (main / оборона / вратари), each
   in its own grid so goalie cards break to a fresh row after a gap. */
.predict-nom-group { margin-top: 1.6rem; }
.predict-nom-group-h {
  margin: 0 0 0.6rem; font-size: 0.8rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim);
}
.predict-nom-card {
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
  padding: 0.7rem 0.8rem; display: flex; flex-direction: column; gap: 0.5rem;
}
.predict-nom-card .predict-nom-title {
  font-weight: 700; font-size: 0.88rem; margin: 0; color: var(--accent);
}
.predict-rank-list { display: flex; flex-direction: column; gap: 0.25rem; }
.predict-rank-row {
  display: flex; align-items: center; gap: 0.5rem; cursor: pointer;
  padding: 0.3rem 0.45rem; border: 1px solid var(--border); border-radius: 7px;
  background: var(--bg); font-size: 0.85rem;
}
.predict-rank-row:hover { border-color: var(--accent); }
.predict-rank-row.filled { border-color: var(--accent); }
/* pre-filled from last-season top-5, not yet claimed by the user */
.predict-rank-row.prefilled { border-style: dashed; border-color: var(--border); }
.predict-rank-row.prefilled .predict-rank-name { color: var(--text-dim); font-style: italic; }
.predict-prefill-hint { margin: 0 0 1rem; }
.predict-nom-card.partial { border-color: #d97706; }
.predict-nom-warn {
  font-size: 0.72rem; font-weight: 600; color: #d97706; margin: -0.15rem 0 0.1rem;
}
.predict-rank-num { font-weight: 700; color: var(--text-dim); min-width: 1.1rem; text-align: center; }
.predict-rank-row.filled .predict-rank-num { color: var(--accent); }
.predict-rank-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.predict-rank-row:not(.filled) .predict-rank-name { color: var(--text-dim); }
.predict-rank-add { font-size: 0.7rem; color: var(--text-dim); }
.predict-rank-x {
  border: none; background: none; color: #ff6b6b; font-size: 1rem; line-height: 1;
  padding: 0 0.15rem; cursor: pointer;
}

/* subject picker overlay */
.predict-picker-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,0.5);
  display: flex; align-items: center; justify-content: center; z-index: 2000;
}
.predict-picker {
  background: var(--bg); border: 1px solid var(--border); border-radius: 12px;
  padding: 1rem; width: min(420px, 92vw); max-height: 80vh; display: flex; flex-direction: column; gap: 0.6rem;
}
.predict-picker-input {
  padding: 0.6rem; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface); color: var(--text); font-size: 0.95rem;
}
.predict-picker-list {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 0.2rem;
}
.predict-picker-list button, .predict-picker-current button {
  /* extra right padding so every value sits the same distance from the edge */
  text-align: left; padding: 0.28rem 1.4rem 0.28rem 0.6rem; border: 1px solid var(--border);
  border-radius: 7px; background: var(--surface); color: var(--text); cursor: pointer;
  display: flex; flex-direction: row; align-items: center; justify-content: space-between;
  gap: 0.6rem; font-size: 0.9rem; width: 100%;
}
.predict-picker-list button:hover, .predict-picker-current button:hover { border-color: var(--accent); }
.predict-pick-line { display: flex; align-items: baseline; gap: 0.35rem; min-width: 0; flex: 1 1 auto; }
.predict-pick-rank {
  font-family: "JetBrains Mono", monospace; font-size: 0.68rem; color: var(--accent);
  border: 1px solid var(--accent); border-radius: 4px; padding: 0 0.3rem; flex: 0 0 auto;
  min-width: 1.1rem; text-align: center; align-self: center;
}
.predict-pick-pos { color: var(--text-dim); font-size: 0.72rem; flex: 0 0 auto; text-transform: uppercase; }
.predict-pick-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* the metric value, styled like a fantasy player-card price (.p-price) */
.predict-pick-val {
  flex: 0 0 auto; font-weight: 700; font-size: 1.05rem; line-height: 1.2;
  color: var(--text-dim); font-variant-numeric: tabular-nums; text-align: right;
  white-space: nowrap;
}
/* the "ср. X.X" per-game figure trailing a player's season total */
.predict-pick-avg { font-weight: 400; font-size: 0.74rem; color: var(--text-dim); }
.predict-statmode { margin-bottom: 0.8rem; }
/* On/off switch (replaced the two-tab row) - "За сезон" | "За игру" */
.predict-switch {
  display: inline-flex; align-items: center; gap: 0.45rem; cursor: pointer;
  font-size: 0.8rem; color: var(--text-dim); user-select: none;
}
.predict-switch input { position: absolute; opacity: 0; pointer-events: none; }
.predict-switch span.on { color: var(--text); font-weight: 600; }
.predict-switch-track {
  position: relative; width: 2.1rem; height: 1.1rem; border-radius: 0.55rem;
  background: var(--border); transition: background 0.15s;
}
.predict-switch-knob {
  position: absolute; top: 0.15rem; left: 0.15rem; width: 0.8rem; height: 0.8rem;
  border-radius: 50%; background: #fff; transition: transform 0.15s;
}
.predict-switch input:checked ~ .predict-switch-track { background: var(--accent); }
.predict-switch input:checked ~ .predict-switch-track .predict-switch-knob { transform: translateX(1rem); }
.predict-sub { font-size: 0.72rem; color: var(--text-dim); }
/* the current pick, pinned above the scrollable list while swapping it */
.predict-picker-current:empty { display: none; }
.predict-picker-current {
  flex: 0 0 auto; padding-bottom: 0.5rem; border-bottom: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 0.2rem;
}
.predict-picker-current-label {
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-dim);
}
.predict-picker-current button { border-color: var(--accent); }

/* leaderboards */
.predict-boards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
.predict-board h3 { margin: 0 0 0.4rem; font-size: 0.95rem; }
.predict-board table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
.predict-board th, .predict-board td { border-bottom: 1px solid var(--border); padding: 0.3rem 0.4rem; text-align: left; }
.predict-board tr.me { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.predict-board tr.late { color: var(--text-dim); }

/* Прогнозы - additions (drag reorder, countdown, grid meta) */
.predict-countdown { color: var(--text-dim); font-style: italic; }
.predict-disc-row { cursor: grab; }
/* The row left behind while dragging - a dashed hole, not a dimmed copy;
   the drag image itself stays fully opaque (see renderDiscrete). */
.predict-disc-row.predict-disc-ghost {
  opacity: 0.35; border-style: dashed; background: transparent;
}
.predict-disc-row.drop-before { box-shadow: inset 0 3px 0 #38bdf8; }
.predict-disc-row.drop-after { box-shadow: inset 0 -3px 0 #38bdf8; }
.predict-disc-handle { color: var(--text-dim); cursor: grab; font-size: 1rem; line-height: 1; }
/* playoff watershed line after the 8th place - deliberately thin/tight */
.predict-playoff-line {
  display: flex; align-items: center; gap: 0.4rem; margin: -0.15rem 0;
  color: #38bdf8; font-size: 0.56rem; text-transform: uppercase; letter-spacing: 0.08em;
}
.predict-playoff-line::before, .predict-playoff-line::after {
  content: ""; flex: 1; height: 1px; background: #38bdf8; opacity: 0.7;
}
.predict-step { display: flex; align-items: center; gap: 0.4rem; font-size: 0.8rem; color: var(--text-dim); }
.predict-step input { width: 7rem; }
.predict-step span { font-weight: 700; color: var(--accent); min-width: 0.9rem; text-align: center; }
.predict-grid-meta { margin-top: 0.6rem; font-size: 0.85rem; }
.predict-grid .predict-cell { height: 2.2rem; font-size: 0.8rem; }

/* Прогнозы - Запад / Восток side by side in the standings tab */
.predict-standings-cols { display: flex; gap: 1.5rem; align-items: flex-start; }
.predict-standings-col { flex: 1 1 0; min-width: 0; }
.predict-standings-col > h3 { margin: 0 0 0.5rem; font-size: 0.95rem; color: var(--text-dim); }
@media (max-width: 900px) {
  .predict-standings-cols { flex-direction: column; }
}

/* Прогнозы - feed of other participants' latest standings predictions
   (reuses .online-grid/.online-card from the Онлайн section) */
.predict-feed { margin-top: 1.5rem; }
.predict-feed-title { font-size: 0.95rem; margin: 0 0 0.6rem; }
.predict-feed-metric {
  display: inline-flex; align-items: center; gap: 0.5rem; margin-bottom: 0.8rem;
  font-size: 0.8rem; color: var(--text-dim);
}
.predict-feed-metric select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  background: var(--surface-alt);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='none' stroke='%23888' stroke-width='1.5' d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.6rem center;
  border: 1px solid var(--border); border-radius: 8px;
  color: var(--text); font: inherit; font-size: 0.82rem;
  padding: 0.35rem 1.7rem 0.35rem 0.6rem;
  cursor: pointer;
}
.predict-feed-metric select:hover { border-color: var(--accent); }
.predict-feed-metric select:focus { outline: none; border-color: var(--accent); }
.predict-feed-picks {
  margin: 0.35rem 0 0; padding-left: 1.3rem; font-size: 0.82rem;
  display: flex; flex-direction: column; gap: 0.1rem;
}
.predict-feed-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.predict-feed-when { font-size: 0.72rem; color: var(--text-dim); white-space: nowrap; flex: 0 0 auto; }
/* Both conferences stacked as narrow vertical lists, side by side, so the
   participant card is tall and slim rather than wide. */
.predict-feed-confs { display: flex; gap: 0.6rem; margin-top: 0.4rem; }
.predict-feed-conf { display: flex; flex-direction: column; gap: 0.15rem; flex: 1; min-width: 0; }
.predict-feed-conf-label { font-weight: 700; color: var(--text-dim); font-size: 0.7rem; margin-bottom: 0.1rem; }
.predict-feed-team {
  display: flex; align-items: center; gap: 0.3rem;
  font-family: "JetBrains Mono", monospace; font-size: 0.72rem;
  padding: 0.05rem 0.3rem; border: 1px solid var(--border); border-radius: 4px;
  background: var(--bg); color: var(--text-dim);
}
.predict-feed-team-pos {
  font-size: 0.64rem; color: var(--text-dim); opacity: 0.65;
  min-width: 1ch; text-align: right;
}
