/* SportOS — общая дизайн-система (2026-07-27, редизайн палитры — прежний
   фиолетово-лаймовый дуотон пользователь оценил как «заезженный геймерский
   RGB»). Шесть переключаемых тем (data-theme на <html>, см. app.js
   applyStoredTheme()/cycleTheme() — тот же приём, что уже отработан в
   соседнем проекте football_app): Ember (по умолчанию) / Clay / Royal /
   Midnight Steel / Volt / Arctic (единственная светлая) — каждая со своей
   парой шрифтов (--font-heading/--font-body/--font-mono), не только
   цветом. Один связный набор токенов/классов, подключаемый линком во всех
   layout-шаблонах — правки здесь сразу отражаются везде, без дублирования
   инлайновых <style> по каждому шаблону. */

:root, :root[data-theme="ember"] {
  --bg: #12100e;
  --bg-elevated: #18150f;
  --accent: #FF5A1F;
  --accent-bright: #FF7A47;
  --accent-deep: #C23F0F;
  --glass-bg: rgba(255, 248, 240, .045);
  --glass-bg-strong: rgba(255, 248, 240, .08);
  --glass-border: rgba(255, 248, 240, .1);
  --blur: 18px;
  --text: #F5EFE6;
  --text-dim: #b3a99a;
  --text-faint: #7d7365;
  --danger: #ff5470;
  --success: #38e07a;
  --warning: #ffc84a;
  --radius: 14px;
  --radius-sm: 9px;
  --shadow-glow: 0 8px 32px rgba(255, 90, 31, .25);
  --ease: cubic-bezier(.22, 1, .36, 1);
  --btn-secondary-bg: #3a352c;
  --btn-danger-bg: linear-gradient(135deg, #ff6b81, #c81e3a);
  --font-heading: "Archivo", "Inter", system-ui, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: var(--font-heading);
  --theme-label: "Ember";
  --theme-swatch: #FF5A1F;
}

:root[data-theme="clay"] {
  --bg: #1c1310;
  --bg-elevated: #241a15;
  --accent: #C1622E;
  --accent-bright: #E08A50;
  --accent-deep: #8F4419;
  --glass-bg: rgba(250, 235, 220, .045);
  --glass-bg-strong: rgba(250, 235, 220, .08);
  --glass-border: rgba(250, 235, 220, .1);
  --text: #F2E4D6;
  --text-dim: #b8a48f;
  --text-faint: #7f6f5e;
  --danger: #ff5470;
  --success: #38e07a;
  --warning: #ffc84a;
  --shadow-glow: 0 8px 32px rgba(193, 98, 46, .25);
  --btn-secondary-bg: #3a2e24;
  --btn-danger-bg: linear-gradient(135deg, #ff6b81, #c81e3a);
  --font-heading: "Fraunces", "Inter", serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: var(--font-heading);
  --theme-label: "Clay";
  --theme-swatch: #C1622E;
}

:root[data-theme="royal"] {
  --bg: #1a0e14;
  --bg-elevated: #22121a;
  --accent: #D9B872;
  --accent-bright: #EDD8A0;
  --accent-deep: #A98C4E;
  --glass-bg: rgba(245, 235, 245, .045);
  --glass-bg-strong: rgba(245, 235, 245, .08);
  --glass-border: rgba(245, 235, 245, .1);
  --text: #F0E6EC;
  --text-dim: #b3a0ac;
  --text-faint: #7a6a72;
  --danger: #ff5470;
  --success: #38e07a;
  --warning: #ffc84a;
  --shadow-glow: 0 8px 32px rgba(217, 184, 114, .22);
  --btn-secondary-bg: #38222f;
  --btn-danger-bg: linear-gradient(135deg, #ff6b81, #c81e3a);
  --font-heading: "Playfair Display", "Inter", serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: var(--font-heading);
  --theme-label: "Royal";
  --theme-swatch: #D9B872;
}

:root[data-theme="midnight-steel"] {
  --bg: #10161f;
  --bg-elevated: #141c27;
  --accent: #29D9C8;
  --accent-bright: #6BEEE1;
  --accent-deep: #1AA396;
  --glass-bg: rgba(220, 232, 240, .045);
  --glass-bg-strong: rgba(220, 232, 240, .08);
  --glass-border: rgba(220, 232, 240, .1);
  --text: #E4ECF2;
  --text-dim: #93a4b3;
  --text-faint: #5f707f;
  --danger: #ff5470;
  --success: #38e07a;
  --warning: #ffc84a;
  --shadow-glow: 0 8px 32px rgba(41, 217, 200, .22);
  --btn-secondary-bg: #232f3d;
  --btn-danger-bg: linear-gradient(135deg, #ff6b81, #c81e3a);
  --font-heading: "Archivo", "Inter", system-ui, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, monospace;
  --theme-label: "Midnight Steel";
  --theme-swatch: #29D9C8;
}

:root[data-theme="volt"] {
  --bg: #0a0a0b;
  --bg-elevated: #101011;
  --accent: #E8FF4D;
  --accent-bright: #F2FF8A;
  --accent-deep: #A8BF2E;
  --glass-bg: rgba(232, 232, 236, .045);
  --glass-bg-strong: rgba(232, 232, 236, .08);
  --glass-border: rgba(232, 232, 236, .1);
  --text: #EDEDF0;
  --text-dim: #9a9aa0;
  --text-faint: #67676c;
  --danger: #ff5470;
  --success: #38e07a;
  --warning: #ffc84a;
  --shadow-glow: 0 8px 32px rgba(232, 255, 77, .2);
  --btn-secondary-bg: #29292b;
  --btn-danger-bg: linear-gradient(135deg, #ff6b81, #c81e3a);
  --font-heading: "Space Grotesk", "Inter", system-ui, sans-serif;
  --font-body: "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: var(--font-heading);
  --theme-label: "Volt";
  --theme-swatch: #E8FF4D;
}

/* Arctic — единственная светлая тема набора: часть токенов (border/glass)
   инвертирована на тёмный оттенок поверх светлого фона, а не просто
   светлая копия тёмных значений — иначе стеклянные карточки/бейджи не
   читаются на белом (тот же урок, что и в football_app-теме contrast). */
:root[data-theme="arctic"] {
  --bg: #F4F6F8;
  --bg-elevated: #FFFFFF;
  --accent: #2B7FD6;
  --accent-bright: #4E9BEA;
  --accent-deep: #1F5FA3;
  --glass-bg: rgba(20, 24, 30, .035);
  --glass-bg-strong: rgba(20, 24, 30, .06);
  --glass-border: rgba(20, 24, 30, .1);
  --text: #1A2027;
  --text-dim: #5b6773;
  --text-faint: #8a95a1;
  --danger: #d6304a;
  --success: #1f9e5c;
  --warning: #b8790a;
  --shadow-glow: 0 8px 32px rgba(43, 127, 214, .18);
  --btn-secondary-bg: #e2e6eb;
  --btn-danger-bg: linear-gradient(135deg, #ff6b81, #c81e3a);
  --font-heading: "Sora", "Inter", system-ui, sans-serif;
  --font-body: "Sora", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: var(--font-heading);
  --theme-label: "Arctic";
  --theme-swatch: #2B7FD6;
}
:root[data-theme="arctic"] .btn-secondary-bg,
:root[data-theme="arctic"] a.btn, :root[data-theme="arctic"] button.btn { color: #fff; }
:root[data-theme="arctic"] ::selection { color: #fff; }

/* ---------- Свайп-навигация между разделами панели ---------- */
/* Cross-document View Transitions (Baseline с 2025-10-14) — родной
   браузерный API для многостраничных (не SPA) переходов, без имитации
   роутинга на JS. Опознаётся браузером на ОБЕИХ страницах перехода — этот
   блок должен быть в общем app.css, подключённом везде. Направление
   (свайп вправо/влево между разделами nav) вычисляется в app.js через
   Navigation API (`pagereveal`) и передаётся типом transition —
   см. initSwipeNav(). */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

::view-transition-group(root) { animation-duration: .5s; animation-timing-function: var(--ease); }

/* Настоящая 3D-глубина (не плоский slide+fade, как было раньше) — старая
   страница уходит "листом", уменьшаясь и уходя в перспективу, новая
   заходит с лёгким встречным поворотом. perspective() здесь — ФУНКЦИЯ
   внутри transform, не CSS-свойство на предке — не создаёт containing
   block для fixed-элементов страницы (в отличие от `perspective` на html),
   безопасно для #swipe-dots/.bg-ambient и т.п. */
@keyframes swipe-slide-out-next {
  to { transform: perspective(1400px) translateX(-22%) translateZ(-120px) rotateY(-10deg) scale(.92); opacity: 0; filter: brightness(.7); }
}
@keyframes swipe-slide-in-next {
  from { transform: perspective(1400px) translateX(100%) rotateY(14deg) scale(.96); opacity: 0; }
  to { transform: none; opacity: 1; }
}
@keyframes swipe-slide-out-previous {
  to { transform: perspective(1400px) translateX(22%) translateZ(-120px) rotateY(10deg) scale(.92); opacity: 0; filter: brightness(.7); }
}
@keyframes swipe-slide-in-previous {
  from { transform: perspective(1400px) translateX(-100%) rotateY(-14deg) scale(.96); opacity: 0; }
  to { transform: none; opacity: 1; }
}

html:active-view-transition-type(swipe-next)::view-transition-old(root) { animation-name: swipe-slide-out-next; }
html:active-view-transition-type(swipe-next)::view-transition-new(root) { animation-name: swipe-slide-in-next; }
html:active-view-transition-type(swipe-previous)::view-transition-old(root) { animation-name: swipe-slide-out-previous; }
html:active-view-transition-type(swipe-previous)::view-transition-new(root) { animation-name: swipe-slide-in-previous; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(root) { animation: none !important; }
}

/* ---------- Перетаскивание мышью/тачем/пером ("зажать и перелистнуть") ---------- */
/* См. app.js initSwipeNav() — единые Pointer Events, #app-main реально
   тянется за курсором/пальцем с 3D-наклоном ДО отпускания (раньше свайп
   срабатывал "вслепую", без какой-либо обратной связи во время жеста, и
   не реагировал на мышь вообще, только на тач). */
#app-main.swipe-grabbable { cursor: grab; touch-action: pan-y; }
#app-main.swipe-dragging { cursor: grabbing; transition: none !important; user-select: none; will-change: transform, opacity; }
#app-main.swipe-settling { transition: transform .4s var(--ease), opacity .4s var(--ease); will-change: transform, opacity; }

@media (prefers-reduced-motion: reduce) {
  #app-main.swipe-grabbable, #app-main.swipe-dragging { cursor: default; }
}

/* Гарантированная анимация входа новой страницы — обычный CSS @keyframes,
   НЕ зависит от того, поддерживает ли браузер/включена ли у пользователя
   Cross-Document View Transitions (см. app.js — код, ставящий эти классы,
   диспетчерски отдельный от initSwipeNav). Живая проверка показала, что
   View Transition не всегда включается по факту (Vivaldi/настройки
   пользователя, диагностировать точную причину без браузерного инструмента
   на моей стороне не удалось) — а смена страницы при свайпе всё равно
   обязана выглядеть плавной, поэтому смена URL "маскируется" под неё этим
   независимым слоем поверх настоящей навигации. */
@keyframes app-main-enter-next {
  from { transform: perspective(1400px) translateX(20%) rotateY(10deg) scale(.95); opacity: 0; }
  to { transform: none; opacity: 1; }
}
@keyframes app-main-enter-previous {
  from { transform: perspective(1400px) translateX(-20%) rotateY(-10deg) scale(.95); opacity: 0; }
  to { transform: none; opacity: 1; }
}
#app-main.enter-swipe-next { animation: app-main-enter-next .45s var(--ease) both; }
#app-main.enter-swipe-previous { animation: app-main-enter-previous .45s var(--ease) both; }

/* Приземление после "варп"-выбора вида спорта (см. #sport-warp-overlay
   ниже) — материализация из увеличенного/размытого состояния, а не слайд
   сбоку (тот сюда не подходит по смыслу — на предыдущем экране ничего не
   "улетало вбок", оно росло из центра). */
@keyframes app-main-enter-warp {
  from { transform: scale(1.5); opacity: 0; filter: blur(10px); }
  to { transform: none; opacity: 1; filter: none; }
}
#app-main.enter-sport-warp { animation: app-main-enter-warp .5s var(--ease) both; }

@media (prefers-reduced-motion: reduce) {
  #app-main.enter-swipe-next, #app-main.enter-swipe-previous, #app-main.enter-sport-warp { animation: none !important; }
}

/* ---------- "Варп"-переход при выборе вида спорта (org/sport_select.html) ---------- */
/* См. app.js — при отправке формы выбора карточка не просто отправляет
   POST мгновенно, а сперва "раскручивается" и вырастает на весь экран
   (прямой запрос пользователя — "нажал на кнопку, она быстро завращалась,
   увеличилась во весь экран, и мы плавно перешли"), и только потом реально
   отправляется форма. #sport-warp-overlay — клон карточки, промотированный
   в position:fixed на месте оригинала и анимированный до размеров экрана;
   .sport-warp-spin — отдельный вложенный слой для быстрого вращения
   содержимого (иконка+название), независимо от роста внешнего контейнера. */
#sport-warp-overlay {
  position: fixed; z-index: 200; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  background: var(--glass-bg); backdrop-filter: blur(var(--blur));
  border: 1px solid var(--glass-border); box-sizing: border-box;
  transition: top .55s var(--ease), left .55s var(--ease), width .55s var(--ease), height .55s var(--ease), border-radius .55s var(--ease), background .55s var(--ease);
  color: var(--text); pointer-events: none; overflow: hidden;
}
#sport-warp-overlay.sport-warp-grown { border-radius: 0; background: var(--bg); }
#sport-warp-overlay .sport-warp-spin {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  animation: sport-warp-spin .6s cubic-bezier(.3, 0, .2, 1) both;
}
#sport-warp-overlay .sport-card-icon { font-size: 2.6rem; }
@keyframes sport-warp-spin {
  from { transform: rotate(0deg) scale(1); }
  to { transform: rotate(900deg) scale(7); }
}
.sport-grid-warping > *:not(#sport-warp-overlay) { transition: opacity .3s var(--ease); opacity: 0; pointer-events: none; }

/* "Удар об экран" — короткая вспышка перед тем, как экран разлетится на
   осколки (прямой запрос пользователя: "врезается в монитор, разбивается
   на мелкие осколки"). Тряска — на самом body (см. app.js GSAP-таймлайн). */
#sport-warp-flash {
  position: fixed; inset: 0; z-index: 210; background: #fff; opacity: 0; pointer-events: none;
}

/* Мелкие осколки, на которые "рассыпается" выросший оверлей — см.
   app.js:shatterAndThen(). Позиционируются/красятся из JS (left/top/width/
   height/background — считаются под текущий размер экрана и цвет
   оверлея), здесь только базовое поведение слоя. */
#sport-warp-shatter { position: fixed; inset: 0; z-index: 205; pointer-events: none; }
.sport-warp-tile { position: absolute; will-change: transform, opacity; }

@media (prefers-reduced-motion: reduce) {
  #sport-warp-overlay, #sport-warp-overlay .sport-warp-spin { transition: none !important; animation: none !important; }
  #sport-warp-flash, #sport-warp-shatter { display: none !important; }
}

/* Подсказка соседнего раздела у края экрана во время перетаскивания —
   позиция/прозрачность считаются в JS (updateDragVisual), т.к. зависят от
   расстояния перетаскивания в реальном времени, не только hover/CSS. */
#swipe-peek {
  position: fixed; top: 50%; transform: translateY(-50%);
  z-index: 45; pointer-events: none;
  display: flex; align-items: center; gap: 10px;
  padding: 14px 22px; border-radius: 16px;
  background: var(--glass-bg); backdrop-filter: blur(var(--blur));
  border: 1px solid var(--glass-border);
  font-size: .95rem; font-weight: 700; color: var(--text);
  opacity: 0; white-space: nowrap;
  box-shadow: 0 16px 46px rgba(0, 0, 0, .4);
}
#swipe-peek .arrow { color: var(--accent); font-size: 1.25rem; line-height: 1; }

/* Точки-индикатор текущего раздела — те же data-swipe-секции, что и логика
   свайпа (см. app.js buildSwipeSections()), рендерятся JS-ом. */
#swipe-dots {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%); z-index: 40;
  display: flex; gap: 8px; padding: 8px 12px; border-radius: 20px;
  background: rgba(0, 0, 0, .25); backdrop-filter: blur(var(--blur));
  border: 1px solid var(--glass-border);
}
#swipe-dots a {
  width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint);
  transition: background .2s, transform .2s; display: block;
}
#swipe-dots a:hover { background: var(--text-dim); }
#swipe-dots a.active { background: var(--accent); transform: scale(1.3); }
@media (max-width: 640px) { #swipe-dots { display: none; } }

/* ---------- fx.js — общие анимационные утилиты (2026-07-27) ---------- */

/* Кросс-фейд при смене темы (40-список, п.2) — CSS custom properties сами
   по себе не анимируются браузером, но свойства, которые НА НИХ ссылаются
   (background-color/color/border-color), интерполируются нормально, если
   у них есть transition — так что смена data-theme на <html> (см. app.js
   applyTheme()) отражается плавным перетеканием цвета, а не мгновенным
   скачком, без единой правки самого JS. */
body, .card, header.navbar, #section-sidebar, .btn, .badge, table, input, select, textarea {
  transition: background-color .4s ease, color .4s ease, border-color .4s ease;
}
@media (prefers-reduced-motion: reduce) {
  body, .card, header.navbar, #section-sidebar, .btn, .badge, table, input, select, textarea { transition: none; }
}

/* 3D mini-flip у кнопки-свотча темы при смене — 20-список, п.17. */
#theme-swatch-dot { display: inline-block; transform-style: preserve-3d; }
.fx-swatch-flip { animation: fx-swatch-flip .5s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes fx-swatch-flip {
  0% { transform: rotateY(0deg); }
  50% { transform: rotateY(180deg) scale(.6); }
  100% { transform: rotateY(360deg); }
}
@media (prefers-reduced-motion: reduce) { .fx-swatch-flip { animation: none; } }

/* Кастомный курсор с trailing-эффектом — 20-список, п.16 (только на
   странице с data-fx-cursor, см. fx.js). */
body[data-fx-cursor], body[data-fx-cursor] a, body[data-fx-cursor] button { cursor: none; }
#fx-cursor-dot, #fx-cursor-ring {
  position: fixed; top: 0; left: 0; border-radius: 50%; pointer-events: none; z-index: 500; opacity: 0;
}
#fx-cursor-dot { width: 6px; height: 6px; margin: -3px 0 0 -3px; background: var(--accent-bright); transition: opacity .2s; }
#fx-cursor-ring {
  width: 34px; height: 34px; margin: -17px 0 0 -17px; border: 1.5px solid var(--accent);
  transition: opacity .2s, width .2s, height .2s, margin .2s;
}
#fx-cursor-ring.fx-cursor-ring-active { width: 24px; height: 24px; margin: -12px 0 0 -12px; border-color: var(--accent-bright); }
@media (prefers-reduced-motion: reduce) {
  body[data-fx-cursor] { cursor: auto; }
  #fx-cursor-dot, #fx-cursor-ring { display: none; }
}

/* Полоска прогресса чтения страницы (40-список, п.7) — см. fx.js. */
#fx-scroll-progress {
  position: fixed; top: 0; left: 0; height: 3px; width: 100%;
  background: linear-gradient(90deg, var(--accent-bright), var(--accent-deep));
  transform-origin: left; transform: scaleX(0); z-index: 400; pointer-events: none;
}

@keyframes fx-shake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-5px); }
  40%, 60% { transform: translateX(5px); }
}
.fx-shake { animation: fx-shake .5s; }

.fx-ripple {
  position: absolute; border-radius: 50%; background: rgba(255, 255, 255, .35);
  transform: scale(0); animation: fx-ripple .55s ease-out; pointer-events: none;
}
@keyframes fx-ripple { to { transform: scale(1); opacity: 0; } }

.fx-confetti { position: fixed; inset: 0; z-index: 300; pointer-events: none; }
.fx-confetti span {
  position: absolute; width: 8px; height: 13px; border-radius: 2px;
  animation: fx-confetti-fall 1.5s cubic-bezier(.15, .6, .4, 1) forwards;
}
@keyframes fx-confetti-fall {
  to { transform: translate(var(--dx), calc(var(--dy) + 280px)) rotate(var(--rot)); opacity: 0; }
}

/* Пульс-вспышка на счёте при голе/очке — оригинальный 20-список, п.1. */
.fx-score-pulse { display: inline-block; animation: fx-score-pulse .5s var(--ease); }
@keyframes fx-score-pulse {
  0% { transform: scale(1); color: inherit; }
  30% { transform: scale(1.35); color: var(--accent-bright); text-shadow: 0 0 18px color-mix(in srgb, var(--accent) 60%, transparent); }
  100% { transform: scale(1); color: inherit; text-shadow: none; }
}

/* "Морфинг" иконок play/pause таймера при смене состояния — 20-список,
   п.13. Честная оговорка: настоящий SVG path-морфинг требует платного
   GSAP MorphSVGPlugin (нет в проекте) — здесь rotate+scale переход между
   эмодзи-иконками, тот же принцип "не мгновенная смена", без выдуманной
   технологии. */
.fx-icon-morph { display: inline-block; }
.fx-icon-morph-play { animation: fx-icon-morph .4s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes fx-icon-morph {
  0% { transform: rotate(0deg) scale(1); opacity: 1; }
  40% { transform: rotate(180deg) scale(.4); opacity: .3; }
  100% { transform: rotate(360deg) scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .fx-icon-morph-play { animation: none; } }

/* Вращающийся 3D-значок медали над топ-3 в турнирной таблице — 20-список,
   п.11. Чистый CSS 3D-transform (rotateY бесконечно) — легче, чем
   отдельная Three.js-сцена на каждую строку таблицы. */
.fx-trophy-spin {
  display: inline-block; margin-right: 4px; transform-style: preserve-3d;
  animation: fx-trophy-spin 3s linear infinite;
}
@keyframes fx-trophy-spin { from { transform: perspective(200px) rotateY(0deg); } to { transform: perspective(200px) rotateY(360deg); } }
@media (prefers-reduced-motion: reduce) { .fx-trophy-spin { animation: none; } }

/* Линейный "наливающийся" столбик (топ-бомбардиры и т.п.) — 20-список,
   п.18. Ширина заполняется анимированно из JS (fx.js:barFill). */
.fx-bar-track { width: 110px; height: 6px; border-radius: 3px; background: var(--glass-border); margin-top: 5px; overflow: hidden; }
.fx-bar-fill { height: 100%; width: 0; border-radius: 3px; background: linear-gradient(90deg, var(--accent), var(--accent-bright)); }

/* Радиальный прогресс (донат-кольцо) — 20-список, п.10. --pct (0..100)
   заполняется анимированно из JS (см. fx.js:radialFill). */
.fx-radial {
  --pct: 0; width: 64px; height: 64px; border-radius: 50%; position: relative; flex-shrink: 0;
  background: conic-gradient(var(--accent-bright) calc(var(--pct) * 1%), var(--glass-border) 0);
}
.fx-radial::before { content: ""; position: absolute; inset: 7px; border-radius: 50%; background: var(--bg); }
.fx-radial span {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: .78rem; font-weight: 700; color: var(--text);
}

/* Голографический tilt+shine на логотипах команд — 20-список, п.8. Блик
   (::after) следует за курсором через --mx/--my (см. fx.js), наклон
   (rotateX/rotateY) — тот же generic-tilt механизм, что уже используется
   для .stat-card/.sport-card (app.js TILT_SELECTOR), .fx-holo просто
   добавлен в тот же селектор. */
.fx-holo { position: relative; display: inline-block; overflow: hidden; border-radius: 8px; }
.fx-holo::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: 0;
  background: radial-gradient(120px circle at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, .55), transparent 60%);
  mix-blend-mode: overlay; transition: opacity .25s;
}
.fx-holo:hover::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .fx-holo::after { display: none; } }

/* SVG/CSS-траектория мяча у гола в хронологии событий — 20-список, п.3.
   CSS Motion Path (offset-path/offset-distance, Baseline) — точка "летит"
   по дуге при появлении строки, вместо статичной иконки. */
.fx-goal-trail {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent-bright); margin-right: 6px; vertical-align: middle;
  offset-path: path("M0,9 Q13,-9 26,9"); animation: fx-goal-trail-move .9s ease-out both;
}
@keyframes fx-goal-trail-move { from { offset-distance: 0%; } to { offset-distance: 100%; } }

/* Пульсирующее кольцо у "живого" индикатора соединения — 20-список, п.2. */
.fx-live-dot { position: relative; display: inline-block; }
.fx-live-dot.pulsing::before {
  content: ""; position: absolute; inset: -5px; border-radius: 50%;
  border: 2px solid currentColor; opacity: .55; animation: fx-live-pulse 1.6s ease-out infinite;
}
@keyframes fx-live-pulse { to { transform: scale(2.1); opacity: 0; } }

.fx-skeleton-line {
  height: 14px; border-radius: 6px; margin: 10px 0;
  background: linear-gradient(90deg, var(--glass-bg) 25%, color-mix(in srgb, var(--text) 10%, transparent) 50%, var(--glass-bg) 75%);
  background-size: 200% 100%; animation: fx-shimmer 1.4s ease-in-out infinite;
}
@keyframes fx-shimmer { to { background-position: -200% 0; } }

@keyframes fx-row-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.fx-row-in { animation: fx-row-in .4s var(--ease) both; }

/* Bounce-строка для только что созданной записи (40-список, п.9) — не
   путать с fx-row-in (тот — обычный stagger-reveal ВСЕХ строк при
   первой отрисовке таблицы); эта — единственная строка, найденная по
   ?created=<id> в URL после редиректа из формы создания, с пружинным
   перелётом и затухающей подсветкой, чтобы глаз сразу нашёл новую запись
   в уже существующем списке. */
@keyframes fx-row-bounce-in {
  0%   { opacity: 0; transform: scale(.92) translateY(-14px); }
  55%  { opacity: 1; transform: scale(1.015) translateY(2px); }
  75%  { transform: scale(.997) translateY(0); }
  100% { opacity: 1; transform: none; }
}
@keyframes fx-row-highlight-fade {
  0%   { background-color: color-mix(in srgb, var(--accent) 22%, transparent); }
  100% { background-color: transparent; }
}
.fx-row-bounce-in { animation: fx-row-bounce-in .6s cubic-bezier(.34,1.56,.64,1) both, fx-row-highlight-fade 1.8s ease-out .6s both; }
.fx-row-bounce-in td { position: relative; }

/* Пузырьковая диаграмма бомбардиров (40-список, п.11) — каждый круг
   "вырастает" из нуля с небольшой задержкой по порядку (--i), затем
   слегка пульсирует при наведении. transform-origin по умолчанию для SVG
   <circle> — верхний левый угол ViewBox, не сама фигура, поэтому задаём
   transform-box явно. */
.fx-bubble {
  transform-box: fill-box; transform-origin: center;
  animation: fx-bubble-in .5s cubic-bezier(.34,1.56,.64,1) both;
  animation-delay: calc(var(--i, 0) * 60ms);
  transition: r .25s ease, opacity .2s ease;
  cursor: pointer;
}
.fx-bubble:hover { opacity: .85; }
@keyframes fx-bubble-in { from { transform: scale(0); opacity: 0; } to { transform: scale(1); opacity: 1; } }

/* Сетка плей-офф (40-список, п.13/14) — колонка за колонкой, матч за
   матчем: "анимированная сборка" сетки при первой отрисовке страницы.
   .bracket-drawing/.bracket-drawn — состояния барабана жеребьёвки (см.
   tournaments/detail.html, только сразу после автогенерации сетки кубка). */
.bracket { display: flex; gap: 40px; overflow-x: auto; padding: 24px 4px 32px; }
.bracket-round {
  display: flex; flex-direction: column; justify-content: space-around; min-width: 190px;
  opacity: 0; animation: fx-bracket-round-in .5s ease both;
  animation-delay: calc(var(--round-i, 0) * 220ms);
}
.bracket-round-title { font-size: .78rem; color: var(--text-dim); text-align: center; margin-bottom: 10px; text-transform: uppercase; letter-spacing: .04em; }
.bracket-match {
  position: relative; background: var(--glass-bg); border: 1px solid var(--glass-border); border-radius: 10px;
  margin: 14px 0; padding: 6px 10px; backdrop-filter: blur(var(--blur));
  opacity: 0; animation: fx-bracket-match-in .4s ease both;
  animation-delay: calc(var(--round-i, 0) * 220ms + var(--match-i, 0) * 90ms + 150ms);
}
.bracket-team { display: flex; justify-content: space-between; gap: 10px; padding: 4px 0; color: var(--text); text-decoration: none; font-size: .85rem; }
.bracket-team:hover .bracket-team-name { color: var(--accent-bright); }
.bracket-team + .bracket-team { border-top: 1px solid var(--glass-border); }
.bracket-score { font-weight: 700; color: var(--accent-bright); }
.bracket-round:not(:last-child) .bracket-match::after {
  content: ""; position: absolute; top: 50%; right: -20px; width: 20px; height: 1px; background: var(--glass-border);
}
@keyframes fx-bracket-round-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes fx-bracket-match-in { from { opacity: 0; transform: translateX(-14px); } to { opacity: 1; transform: none; } }
.bracket-drawing .bracket-team-name { color: var(--accent-bright); }
.bracket-drawn .bracket-match { animation: fx-bracket-settle .4s ease; }
@keyframes fx-bracket-settle { 0% { transform: scale(1.04); } 100% { transform: scale(1); } }

/* Блик, бегущий по карточке при скролле (40-список, п.17) — сама
   вертикальная "плывущая" парраллакс-анимация задаётся напрямую GSAP
   (SportOSFx.initCardParallax), здесь только статичная подложка блика,
   двигаемая через --shine-pos (0%..100%, GSAP ScrollTrigger onUpdate). */
.fx-parallax-card { position: relative; overflow: hidden; will-change: transform; }
.fx-parallax-card::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
  background: linear-gradient(115deg, transparent 35%, color-mix(in srgb, var(--accent-bright) 20%, transparent) 50%, transparent 65%);
  background-size: 260% 260%; background-position: var(--shine-pos, -40%) 0%;
  mix-blend-mode: overlay;
}

/* Куб-переход между вкладками статистики (40-список, п.18) — публичная
   /t/<slug>/stats: "Бомбардиры" и "Дисциплина" — настоящий CSS 3D-поворот
   (perspective + rotateY + backface-visibility), не имитация fade/slide.
   Только 2 грани куба реально используются, но приём тот же, что у
   полноценного 6-гранного куба. */
.stat-tabs { display: flex; gap: 8px; margin: 4px 0 20px; }
.stat-tab-btn {
  padding: 8px 18px; border-radius: 10px; border: 1px solid var(--glass-border); background: var(--glass-bg);
  color: var(--text-dim); font-size: .88rem; cursor: pointer; transition: color .2s ease, border-color .2s ease, background-color .2s ease;
}
.stat-tab-btn.active { color: var(--accent-bright); border-color: color-mix(in srgb, var(--accent) 45%, transparent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.stat-cube { position: relative; perspective: 1600px; }
.stat-cube-face { backface-visibility: hidden; transition: transform .6s cubic-bezier(.65, 0, .35, 1), opacity .25s ease .12s; }
.stat-cube-front { position: relative; transform: rotateY(0deg) translateZ(0); }
.stat-cube-right { position: absolute; inset: 0; transform: rotateY(90deg) translateZ(240px); opacity: 0; pointer-events: none; }
.stat-cube.show-carded .stat-cube-front { position: absolute; inset: 0; transform: rotateY(-90deg) translateZ(240px); opacity: 0; pointer-events: none; }
.stat-cube.show-carded .stat-cube-right { position: relative; transform: rotateY(0deg) translateZ(0); opacity: 1; pointer-events: auto; }

/* Цветовая волна при завершении матча (40-список, п.20) — расширяющееся
   кольцо от точки события (см. SportOSFx.colorWave), поверх всего экрана,
   но не блокирует клики (pointer-events: none). */
.fx-color-wave {
  position: fixed; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%;
  border: 2px solid var(--accent-bright); pointer-events: none; z-index: 9000;
  animation: fx-color-wave-expand 1.1s cubic-bezier(.2, .7, .3, 1) forwards;
}
@keyframes fx-color-wave-expand {
  0% { width: 10px; height: 10px; margin: -5px 0 0 -5px; opacity: .9; border-width: 3px; }
  100% { width: 2400px; height: 2400px; margin: -1200px 0 0 -1200px; opacity: 0; border-width: 1px; }
}

/* Бегущая строка последних событий турнира (40-список, п.23) — публичная
   /t/<slug>/ — бесшовная CSS-лента (контент продублирован дважды в
   шаблоне, анимация сдвигает ровно на половину общей ширины). */
.event-ticker {
  overflow: hidden; white-space: nowrap; border: 1px solid var(--glass-border); border-radius: 10px;
  background: var(--glass-bg); backdrop-filter: blur(var(--blur)); padding: 10px 0; margin-bottom: 24px;
}
.event-ticker-track { display: inline-flex; animation: fx-ticker-scroll 32s linear infinite; }
.event-ticker:hover .event-ticker-track { animation-play-state: paused; }
.event-ticker-item { display: inline-flex; align-items: center; gap: 8px; padding: 0 22px; font-size: .88rem; color: var(--text-dim); }
.event-ticker-item strong { color: var(--text); }
.event-ticker-sep { color: var(--accent-bright); margin-left: 22px; }
@keyframes fx-ticker-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* Стрелка-анимация трансфера (40-список, п.24) — карточка игрока,
   история трансферов: линия "дорисовывается" от старой команды к новой,
   затем проступает наконечник стрелки. */
.transfer-arrow-cell { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.transfer-arrow line { stroke-dasharray: 100; stroke-dashoffset: 100; animation: fx-transfer-draw 1s ease forwards; }
.transfer-arrow polygon { opacity: 0; transform-origin: 53px 8px; animation: fx-transfer-arrowhead-in .3s ease forwards; animation-delay: .9s; }
@keyframes fx-transfer-draw { to { stroke-dashoffset: 0; } }
@keyframes fx-transfer-arrowhead-in { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: scale(1); } }

/* Прогресс загрузки файла (40-список, п.26) — см. fx.js, форма с
   data-fx-upload-progress. */
.fx-upload-progress { display: none; height: 6px; border-radius: 4px; background: var(--glass-border); margin-top: 12px; overflow: hidden; }
.fx-upload-progress-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent), var(--accent-bright)); transition: width .15s ease; }

/* Растворение фото при загрузке превью (40-список, п.25) — см. fx.js. */
.fx-photo-preview {
  display: block; max-height: 90px; max-width: 140px; border-radius: 8px; margin-top: 8px;
  object-fit: cover; opacity: 0; filter: blur(6px); transition: opacity .5s ease, filter .5s ease;
}
.fx-photo-preview.fx-photo-preview-in { opacity: 1; filter: blur(0); }

/* Парящие логотипы спонсоров (40-список, п.28) — публичная страница
   турнира, каждый логотип со своей фазой (--i) — не двигаются синхронно. */
.sponsor-float-row { display: flex; flex-wrap: wrap; gap: 28px; align-items: center; padding: 12px 0; }
.sponsor-float-item {
  display: flex; align-items: center; justify-content: center; height: 56px; min-width: 90px;
  filter: grayscale(.3); opacity: .85; transition: filter .2s ease, opacity .2s ease;
  animation: fx-sponsor-float 4.5s ease-in-out infinite; animation-delay: calc(var(--i, 0) * -.6s);
}
.sponsor-float-item:hover { filter: grayscale(0); opacity: 1; }
.sponsor-float-item img { max-height: 100%; max-width: 140px; object-fit: contain; }
.sponsor-float-item span { color: var(--text-dim); font-size: .85rem; font-weight: 600; }
@keyframes fx-sponsor-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }

/* Разрезание обложки новости (40-список, п.30) — вертикальные "жалюзи"
   поверх картинки схлопываются по очереди (см. news_detail.html). */
.news-cover-slice { position: relative; overflow: hidden; border-radius: 10px; }
.news-cover-slat {
  position: absolute; top: 0; bottom: 0; background: var(--bg, #12100e);
  transform-origin: top; animation: fx-cover-slat-open .55s cubic-bezier(.6, 0, .3, 1) both;
}
@keyframes fx-cover-slat-open { from { transform: scaleY(1); } to { transform: scaleY(0); } }

/* Свечение непрочитанных уведомлений (40-список, п.33). */
.fx-unread-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--accent-bright);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-bright) 60%, transparent);
  animation: fx-unread-glow 1.8s ease-out infinite;
}
@keyframes fx-unread-glow {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-bright) 55%, transparent); }
  70% { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Искры вокруг курсора при драге маркера (40-список, п.22) — короткая
   вспышка-частица, разлетающаяся от точки курсора и затухающая. */
.fx-spark {
  position: fixed; width: 5px; height: 5px; border-radius: 50%; background: var(--accent-bright);
  pointer-events: none; z-index: 9500; margin: -2.5px 0 0 -2.5px;
  animation: fx-spark-fly .5s ease-out forwards;
}
@keyframes fx-spark-fly {
  0% { transform: translate(0, 0) scale(1); opacity: 1; }
  100% { transform: translate(var(--dx), var(--dy)) scale(0); opacity: 0; }
}

/* Одометр-тиканье таймера матча (40-список, п.21) — секундный "тик",
   как у механических часов, на панели оператора и на самой трансляции
   (overlay/master.html). */
@keyframes fx-timer-tick { 0% { transform: scale(1.1); } 100% { transform: scale(1); } }
.fx-timer-tick { animation: fx-timer-tick .28s ease; }

[data-fx-magnetic] { display: inline-block; will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  .fx-shake, .fx-ripple, .fx-confetti span, .fx-skeleton-line, .fx-row-in, .fx-score-pulse, .fx-live-dot.pulsing::before, .fx-goal-trail, .fx-row-bounce-in, .fx-bubble { animation: none !important; }
  .bracket-round, .bracket-match { animation: none !important; opacity: 1 !important; }
  .stat-cube-face { transition: none !important; }
  .fx-color-wave { animation: none !important; display: none !important; }
  .fx-timer-tick { animation: none !important; }
  .fx-spark { display: none !important; }
  .event-ticker-track { animation: none !important; }
  .event-ticker { overflow-x: auto; }
  .transfer-arrow line, .transfer-arrow polygon { animation: none !important; stroke-dashoffset: 0 !important; opacity: 1 !important; transform: none !important; }
  .fx-photo-preview { transition: none !important; }
  .sponsor-float-item { animation: none !important; }
  .fx-unread-dot { animation: none !important; }
  .news-cover-slat { animation: none !important; display: none !important; }
  textarea:focus, input[type="text"]:focus, input[type="search"]:focus, input:not([type]):focus { animation: none !important; }
  .bg-ambient span { animation: none !important; opacity: var(--o) !important; }
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  font-family: var(--font-body);
  margin: 0;
  color: var(--text);
  background: var(--bg);
  background-image:
    radial-gradient(ellipse 900px 500px at 8% -10%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 60%),
    radial-gradient(ellipse 700px 500px at 100% 10%, color-mix(in srgb, var(--accent-bright) 10%, transparent), transparent 55%),
    radial-gradient(ellipse 800px 600px at 50% 120%, color-mix(in srgb, var(--accent-deep) 16%, transparent), transparent 60%);
  background-attachment: fixed;
  min-height: 100vh;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--accent); color: #fff; }

a { color: var(--accent-bright); text-decoration: none; }
a:hover { color: var(--accent); }

h1, h2, h3 { font-family: "Archivo", "Inter", system-ui, sans-serif; font-weight: 700; letter-spacing: -.01em; color: var(--text); }
h1 { font-size: 1.7rem; margin: 0 0 6px; }
h2 { font-size: 1.15rem; margin: 32px 0 14px; }
.subtitle { color: var(--text-dim); margin-bottom: 26px; font-size: .95rem; }

/* ---------- Layout shell ---------- */

main { max-width: 1080px; margin: 32px auto 80px; padding: 0 24px; position: relative; z-index: 1; }

/* ---------- Контекстное боковое меню раздела ---------- */
/* См. app.js initSectionSidebar() — на страницах внутри группы с выпадающим
   меню (База данных/Финансы/Администрирование) слева появляется список
   остальных пунктов этой же группы для быстрого перехода (прямой запрос
   пользователя: "открываем Матчи — слева турниры/сезоны/команды..."). Не
   дублирует разметку — ссылки клонируются JS-ом из уже существующего
   .nav-dropdown-menu-inner той же группы, один источник истины. */
.content-with-sidebar {
  display: flex; align-items: flex-start; gap: 28px;
  max-width: 1280px; margin: 32px auto 80px; padding: 0 24px;
}
.content-with-sidebar main, .content-with-sidebar #app-main {
  max-width: none; margin: 0; padding: 0; flex: 1 1 auto; min-width: 0;
}
#section-sidebar {
  flex: 0 0 200px; position: sticky; top: 90px;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--glass-bg); backdrop-filter: blur(var(--blur));
  border: 1px solid var(--glass-border); border-radius: var(--radius);
  padding: 14px;
}
.section-sidebar-heading {
  font-size: .78rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-faint); padding: 4px 10px 10px;
}
#section-sidebar a {
  display: block; padding: 9px 10px; border-radius: 8px; font-size: .92rem;
  color: var(--text-dim); text-decoration: none; transition: background .15s, color .15s;
}
#section-sidebar a:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--text); }
#section-sidebar a.active { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--text); font-weight: 600; }
@media (max-width: 900px) {
  .content-with-sidebar { display: block; }
  .content-with-sidebar main, .content-with-sidebar #app-main { max-width: 1080px; margin: 0 auto; }
  #section-sidebar { display: none; } /* на узком экране — обычные дропдаун/свайп-навигация, как и раньше */
}

.bg-ambient { position: fixed; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.bg-ambient span {
  position: absolute; border-radius: 50%; filter: blur(90px); opacity: .5;
  will-change: transform;
  /* "Дыхание" пятен фона в спокойном ритме (40-список, п.36) — только
     opacity, НЕ transform: JS-параллакс (app.js) уже анимирует x/y/scale
     через GSAP на том же transform — конфликтовать за одно свойство с CSS
     keyframe нельзя, opacity им не используется вообще. --o хранит
     "базовую" непрозрачность каждого пятна (см. nth-child ниже), keyframe
     дышит вокруг неё. */
  --o: .5;
  animation: fx-ambient-breathe 4.2s ease-in-out infinite;
}
.bg-ambient span:nth-child(1) { width: 480px; height: 480px; background: var(--accent-deep); top: -120px; left: -80px; --o: .5; }
.bg-ambient span:nth-child(2) { width: 360px; height: 360px; background: var(--accent-bright); --o: .14; top: 30%; right: -100px; animation-delay: -1.4s; }
.bg-ambient span:nth-child(3) { width: 420px; height: 420px; background: var(--accent); --o: .18; bottom: -140px; left: 30%; animation-delay: -2.8s; }
@keyframes fx-ambient-breathe { 0%, 100% { opacity: var(--o); } 50% { opacity: calc(var(--o) * 1.6); } }
/* Arctic (светлая) — блики того же градиента на белом фоне выглядели бы
   мутным пятном при той же непрозрачности тёмных тем, приглушаем сильнее */
:root[data-theme="arctic"] .bg-ambient span { opacity: .35 !important; filter: blur(110px); }
:root[data-theme="arctic"] .bg-ambient span:nth-child(2) { opacity: .12 !important; }
:root[data-theme="arctic"] .bg-ambient span:nth-child(3) { opacity: .15 !important; }

/* ---------- Glass navbar ---------- */

header.navbar {
  /* НЕ backdrop-filter: blur() здесь (продолжение диагностики жалобы на
     рывки скролла, 2026-07-29 — снятие блюра с <table> не помогло
     полностью). header.navbar — position: sticky и виден ВСЕГДА во время
     скролла на КАЖДОЙ странице сайта (не только там, где под ним таблица)
     — это ровно тот паттерн (прилипающий размытый элемент, перерасчёт
     блюра всего проезжающего под ним контента на каждый кадр), который
     чаще всего называют главной причиной такого рода жалоб в браузерах.
     Фон здесь и так почти непрозрачный (rgba(...,.72)) — визуально разница
     без блюра минимальна, а стоимость рендера теперь не растёт с длиной
     страницы. */
  background: rgba(10, 10, 18, .82);
  border-bottom: 1px solid var(--glass-border);
  color: #fff;
  padding: 14px 28px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: sticky;
  top: 0;
  z-index: 100;
  flex-wrap: wrap;
  gap: 10px;
}
header.navbar .brand {
  color: #fff; text-decoration: none; font-weight: 700; font-size: 1.05rem;
  display: flex; align-items: center; gap: 9px; letter-spacing: -.01em;
}
header.navbar .brand svg { flex-shrink: 0; }
header.navbar nav { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; position: relative; }
/* Подложка под активным разделом навигации (40-список, п.38) — скользящий
   "пилюль"-фон под текущим top-level пунктом (см. app.js:updateNavPill()),
   плавно едет между пунктами при переходе между разделами вместо мгновенной
   смены. Дочерние ссылки/дропдауны должны стоять НАД пилюлей по умолчанию
   (position:absolute красится позже static-контента в общем случае). */
header.navbar nav > a, header.navbar nav > .nav-dropdown { position: relative; z-index: 1; }
#nav-active-pill {
  position: absolute; z-index: 0; border-radius: 8px; opacity: 0;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent), 0 0 14px color-mix(in srgb, var(--accent) 25%, transparent);
  transition: left .35s cubic-bezier(.65, 0, .35, 1), top .35s cubic-bezier(.65, 0, .35, 1),
              width .35s cubic-bezier(.65, 0, .35, 1), height .35s cubic-bezier(.65, 0, .35, 1), opacity .2s ease;
  pointer-events: none;
}
#nav-active-pill.visible { opacity: 1; }
@media (prefers-reduced-motion: reduce) { #nav-active-pill { transition: opacity .2s ease; } }
header.navbar nav > a, .nav-dropdown-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--text-dim); font-weight: 400; font-size: .88rem;
  padding: 8px 12px; border-radius: 8px; transition: color .15s, background .15s;
  cursor: pointer; user-select: none;
}
header.navbar nav > a:hover, .nav-dropdown-toggle:hover, .nav-dropdown:hover .nav-dropdown-toggle {
  color: #fff; background: rgba(255, 255, 255, .06);
}
.nav-dropdown { position: relative; }
.nav-dropdown-menu { display: none; position: absolute; top: 100%; left: 0; padding-top: 8px; min-width: 200px; z-index: 50; }
.nav-dropdown:hover .nav-dropdown-menu { display: block; }
.nav-dropdown-menu-inner {
  background: rgba(15, 15, 26, .92); backdrop-filter: blur(var(--blur));
  border: 1px solid var(--glass-border); border-radius: var(--radius-sm);
  padding: 6px; box-shadow: 0 12px 32px rgba(0, 0, 0, .5);
}
.nav-dropdown-menu-inner a {
  display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-radius: 7px;
  white-space: nowrap; color: var(--text-dim); font-size: .88rem;
}
.nav-dropdown-menu-inner a:hover { background: rgba(255, 255, 255, .07); color: #fff; }
.family-switch {
  color: var(--text-dim) !important; font-size: .85rem; padding: 8px 12px; border-radius: 8px;
}
.family-switch:hover { background: rgba(255, 255, 255, .06); color: #fff !important; }

/* ---------- Переключатель темы ---------- */

.theme-swatch-dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block; flex-shrink: 0; background: var(--accent); box-shadow: 0 0 0 2px rgba(255, 255, 255, .15); }
.theme-option {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 9px 12px; border-radius: 7px;
  white-space: nowrap; color: var(--text-dim); font-size: .88rem; background: none; border: none; cursor: pointer;
  font-family: inherit; text-align: left;
}
.theme-option:hover { background: rgba(255, 255, 255, .07); color: var(--text); }
.theme-option.active { color: var(--text); font-weight: 600; }
.theme-option.active::after { content: '✓'; margin-left: auto; color: var(--accent); }

/* Public site sub-nav (tournament sections) */
nav.subnav {
  background: rgba(255, 255, 255, .03); backdrop-filter: blur(var(--blur));
  border-bottom: 1px solid var(--glass-border);
  padding: 10px 28px; display: flex; gap: 6px; flex-wrap: wrap;
}
nav.subnav a {
  color: var(--text-dim); font-size: .88rem; padding: 7px 12px; border-radius: 8px; transition: all .15s;
}
nav.subnav a:hover { color: #fff; background: rgba(255, 255, 255, .06); }

footer { text-align: center; color: var(--text-faint); font-size: .82rem; padding: 50px 0 30px; position: relative; z-index: 1; }

/* ---------- Cards / grids ---------- */

.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 32px; }

.stat-card, .card {
  background: var(--glass-bg);
  backdrop-filter: blur(var(--blur));
  -webkit-backdrop-filter: blur(var(--blur));
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  padding: 20px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, .25);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.stat-card:hover, .card:hover { border-color: color-mix(in srgb, var(--accent) 35%, transparent); }
.stat-card .num { font-family: var(--font-heading); font-size: 1.9rem; font-weight: 800; background: linear-gradient(120deg, var(--accent-bright), var(--accent)); -webkit-background-clip: text; background-clip: text; color: transparent; transition: transform .2s var(--ease); }
.stat-card .label { color: var(--text-dim); font-size: .85rem; margin-top: 4px; }
.stat-card a { display: block; }
.stat-card .num:hover { transform: scale(1.08); } /* мягкая "растяжка" числа при наведении (40-список, п.38) */

/* 3D-флип карточки статистики по наведению — 20-список, п.5. Только
   стек .fx-flip-inner получает perspective/rotateY, сама .stat-card не
   трогается — совместимо с уже существующим 3D-tilt по курсору (см.
   app.js TILT_SELECTOR), который крутит РОДИТЕЛЯ .stat-card независимо. */
.stat-card.fx-flip { min-height: 108px; perspective: 900px; }
.fx-flip-inner {
  position: relative; width: 100%; height: 100%; min-height: 68px;
  transition: transform .5s var(--ease); transform-style: preserve-3d;
}
.stat-card.fx-flip:hover .fx-flip-inner { transform: rotateY(180deg); }
.fx-flip-face {
  position: absolute; inset: 0; backface-visibility: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.fx-flip-back { transform: rotateY(180deg); color: var(--accent-bright); font-weight: 700; font-size: .95rem; }
@media (prefers-reduced-motion: reduce) {
  .fx-flip-inner { transition: none; }
  .stat-card.fx-flip:hover .fx-flip-inner { transform: none; }
  .stat-card.fx-flip:hover .fx-flip-back { display: none; }
}

.sport-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 18px; }
.sport-card-form { margin: 0; height: 100%; }
.sport-card {
  /* Фиксированная высота — раньше карточка "Плавания"/"Фигурного катания"
     (длинные названия, переносятся на 2-3 строки) заметно отличалась по
     размеру от короткой "Регби"/"Тенниса" (см. живую проверку). Теперь
     содержимое центрируется внутри одинаковой для всех рамки, а не
     раздувает её. */
  width: 100%; height: 168px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  background: var(--glass-bg); backdrop-filter: blur(var(--blur));
  border: 1px solid var(--glass-border); border-radius: var(--radius);
  padding: 16px; cursor: pointer; font-family: inherit; box-sizing: border-box;
  transition: transform .2s var(--ease), border-color .2s, box-shadow .2s;
  color: var(--text);
}
button.sport-card:hover { border-color: var(--accent); box-shadow: var(--shadow-glow), 0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent); }
.sport-card-icon { font-size: 2.6rem; line-height: 1; flex-shrink: 0; }
.sport-card-name { font-weight: 600; font-size: .92rem; color: var(--text); text-align: center; line-height: 1.25; }
.sport-card-disabled { opacity: .35; filter: grayscale(1); cursor: not-allowed; }
button.sport-card-disabled:hover { transform: none; border-color: var(--glass-border); box-shadow: none; }

/* ---------- Tables ---------- */

table {
  width: 100%; border-collapse: collapse;
  /* НЕ backdrop-filter: blur() здесь (реальная жалоба пользователя на
     рывки при скролле /platform/audit-log, 2026-07-29) — таблица под
     прилипающей (position: sticky) размытой шапкой (header.navbar) на
     каждый кадр скролла заставляла браузер пересчитывать блюр всей
     проезжающей под шапкой строки данных, а на длинных таблицах (50+
     строк) это заметно дороже, чем на маленьких карточках. Таблица данных
     не "плавает" поверх динамичного фона так, как карточки — эффект
     стекла здесь почти не читался, а цена рендера была непропорциональна. */
  background: var(--glass-bg);
  border: 1px solid var(--glass-border); border-radius: var(--radius); overflow: hidden;
}
th, td { text-align: left; padding: 13px 16px; border-bottom: 1px solid rgba(255, 255, 255, .06); font-size: .92rem; }
th { color: var(--text-faint); font-weight: 600; font-size: .74rem; text-transform: uppercase; letter-spacing: .04em; }
tr:last-child td { border-bottom: none; }
tbody tr { transition: background .15s; }
tbody tr:hover { background: rgba(255, 255, 255, .025); }

/* ---------- Buttons ---------- */

a.btn, button.btn {
  display: inline-flex; align-items: center; gap: 7px;
  background: linear-gradient(135deg, var(--accent-bright), var(--accent-deep));
  color: #fff; text-decoration: none; padding: 10px 20px; border-radius: var(--radius-sm);
  border: none; font-size: .92rem; font-weight: 600; cursor: pointer; font-family: inherit;
  transition: transform .15s var(--ease), box-shadow .2s var(--ease), filter .15s;
  box-shadow: 0 4px 16px rgba(76, 0, 203, .35);
}
a.btn:hover, button.btn:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(76, 0, 203, .5); filter: brightness(1.08); }
a.btn:active, button.btn:active, .pressed { transform: translateY(0) scale(.97); }
button.btn:disabled { opacity: .4; cursor: not-allowed; transform: none; box-shadow: none; }
.btn-row { margin-bottom: 22px; display: flex; gap: 10px; flex-wrap: wrap; }

a.btn-outline, button.btn-outline {
  background: rgba(255, 255, 255, .05); box-shadow: none; border: 1px solid var(--glass-border); color: var(--text);
}
a.btn-outline:hover, button.btn-outline:hover { background: rgba(255, 255, 255, .09); box-shadow: none; border-color: rgba(157, 226, 48, .4); }
a.btn-lg, button.btn-lg { padding: 15px 30px; font-size: 1.02rem; border-radius: 12px; }

/* ---------- Forms ---------- */

label { display: block; margin-top: 16px; font-size: .88rem; color: var(--text-dim); }
input, select, textarea {
  width: 100%; padding: 11px 12px; margin-top: 5px;
  background: rgba(255, 255, 255, .04); border: 1px solid var(--glass-border);
  border-radius: var(--radius-sm); font-size: .95rem; font-family: inherit; color: var(--text);
  transition: border-color .2s, background .2s, box-shadow .2s;
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
input:focus, select:focus, textarea:focus {
  /* Glow-рамка при фокусе (40-список, п.5) — раньше был захардкожен старый
     лаймовый цвет ("#9DE230"-эпоха до редизайна палитры), не подстраивался
     под текущую тему; теперь честный var(--accent) + мягкое внешнее свечение. */
  outline: none; border-color: var(--accent); background: rgba(255, 255, 255, .06);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent), 0 0 18px color-mix(in srgb, var(--accent) 35%, transparent);
}
/* Pulse-glow "текущего редактора" (40-список, п.34) — поле, в котором
   СЕЙЧАС реально печатают текст (не select/checkbox/дата — там нет
   процесса "редактирования" в этом смысле), мягко пульсирует, отличая его
   от просто выбранного (:focus без набора текста) поля. */
textarea:focus, input[type="text"]:focus, input[type="search"]:focus, input:not([type]):focus {
  animation: fx-editing-pulse 1.7s ease-in-out infinite;
}
@keyframes fx-editing-pulse {
  0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent), 0 0 18px color-mix(in srgb, var(--accent) 35%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 30%, transparent), 0 0 26px color-mix(in srgb, var(--accent) 50%, transparent); }
}
input[type=checkbox] { width: auto; accent-color: var(--accent); }
input[type=color] { width: 64px; height: 40px; padding: 2px; cursor: pointer; }
.color-field { display: flex; align-items: center; gap: 10px; margin-top: 5px; }
.color-field input[type=color] { margin-top: 0; }
.color-field .color-hex { font-family: ui-monospace, monospace; font-size: .85rem; color: var(--text-dim); text-transform: uppercase; }

form.card { max-width: 480px; }

select option { background: #14141f; color: var(--text); }

/* ---------- Misc ---------- */

.flash {
  padding: 12px 16px; border-radius: var(--radius-sm); margin-bottom: 18px; font-size: .9rem;
  border: 1px solid transparent; backdrop-filter: blur(var(--blur));
  animation: fx-flash-in .5s var(--ease) both; /* лёгкий bounce-приход вместо мгновенного появления (40-список, п.6) */
}
@keyframes fx-flash-in {
  0% { transform: translateY(-16px) scale(.95); opacity: 0; }
  60% { transform: translateY(3px) scale(1.008); opacity: 1; }
  100% { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .flash { animation: none; } }
.flash-error { background: rgba(255, 84, 112, .12); color: #ff8fa3; border-color: rgba(255, 84, 112, .3); }
.flash-success { background: rgba(56, 224, 122, .1); color: #6bf0a3; border-color: rgba(56, 224, 122, .28); }

.badge { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: .74rem; font-weight: 700; background: rgba(255, 255, 255, .08); color: var(--text-dim); letter-spacing: .02em; }
.badge-trial { background: rgba(255, 200, 74, .15); color: var(--warning); }
.badge-active { background: rgba(56, 224, 122, .15); color: var(--success); }
.badge-past_due, .badge-suspended { background: rgba(255, 84, 112, .15); color: var(--danger); }
.badge-cancelled { background: rgba(255, 255, 255, .08); color: var(--text-faint); }

.inline-form { display: inline; }
.live-dot { color: var(--danger); font-weight: 700; }

/* ---------- Animation hooks (see app.js) ---------- */

.stagger > *, .anim-in {
  opacity: 0;
  translate: 0 14px;
  animation: fadeUp .55s var(--ease) forwards;
}
@keyframes fadeUp { to { opacity: 1; translate: 0 0; } }
.stagger > *:nth-child(1) { animation-delay: .02s; }
.stagger > *:nth-child(2) { animation-delay: .07s; }
.stagger > *:nth-child(3) { animation-delay: .12s; }
.stagger > *:nth-child(4) { animation-delay: .17s; }
.stagger > *:nth-child(5) { animation-delay: .22s; }
.stagger > *:nth-child(n+6) { animation-delay: .26s; }
/* .card-grid/.sport-grid помечаются классом .stagger автоматически через
   JS (app.js — там же выставляется --i на каждого ребёнка), не вручную в
   шаблонах — так добавление новой карточки в любую сетку не требует правки
   вёрстки отдельно под анимацию. */
.card-grid.stagger > *, .sport-grid.stagger > * { animation-delay: calc(var(--i, 0) * 0.06s) !important; }

[data-tilt] { transform-style: preserve-3d; will-change: transform; }

@media (max-width: 640px) {
  main { padding: 0 16px; margin: 20px auto 60px; }
  header.navbar { padding: 12px 16px; }
  h1 { font-size: 1.4rem; }
}
