/* lvl up × PTJ — слой эффектов.
 *
 * Владелец прислал набор приёмов (бегущий блик по тексту, градиентный текст,
 * мерцающая решётка, лучи, кольцо прогресса, матрица глифов, сияющая рамка,
 * размытие к краю, появление списка, печать по букве). Здесь они переведены
 * на наш язык: одна бирюза Системы, срезанные углы, тёмное стекло окна
 * Системы. Никаких новых цветов — только токены app.css.
 *
 * Слой независим от app.js: классы вешает fx.js по разметке, которая уже
 * есть. Убрать всё разом — снять два тега в index.html.
 *
 * ПЛАВНОСТЬ (20.09.2026). Замер под CPU ×6 на 360×740: с эффектами как были —
 * 40 к/с в прокрутке и 26 длинных кадров за три секунды, без них — 59 и 2.
 * Дорого стоило не количество эффектов, а их природа: анимации box-shadow,
 * filter и background-position перерисовывают элемент каждый кадр, а холсты
 * перерисовывались целиком. Теперь всё, что движется вечно, движется через
 * transform и opacity (это телефон делает без перерисовки), холсты
 * перерисовывают только изменившиеся клетки, а размытие «за стеклом»
 * осталось только на шапке и вкладках — и то лишь в полном режиме.
 *
 * Три режима на <html data-fx>: full — всё; lite — без холстов, лучей,
 * бликов и стекла (слабые телефоны, выбирается само); off — только
 * необходимые переходы. Выбор — в Настройках, автоматика — в fx.js.
 *
 * Что где:
 *   .fx-grid        мерцающая решётка на весь фон (холст, fx.js)      full
 *   .fx-rays-top    лучи света из-под шапки                             full
 *   .fx-frame       сияющая рамка (вращается transform-ом)             full
 *   .fx-shine       бегущий блик: имя в шапке, ярлык «Сейчас»          full
 *   .fx-gradient    градиентный текст: редкость, имя карты             full/lite
 *   .fx-in          появление: только opacity и transform              full/lite
 *   .fx-beam        огонёк по кромке шапки и вкладок (transform)       full
 *   .fx-ripple      рябь от нажатия                                     full/lite
 *   .fx-typing      печать по букве в окне Системы                     все
 *   .fx-canvas      матрица глифов: загрузка, разговор, бой            full
 *   .fx-rays        лучи на «Глава закрыта», рождении, вскрытии карты  full
 *   .fx-fade-bottom туман над вкладками — градиент, без размытия       full/lite
 */

/* Старая статичная сетка уступает место мерцающей решётке. */
body::before { display: none; }

/* --- Мерцающая решётка: подложка всего приложения ------------------------ */
.fx-grid {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: -1;
  pointer-events: none;
  opacity: 0.7;
}

/* Экран рождения и «Привязка не выполнена» пускают решётку и лучи под панель. */
#birth.screen, #gate.gate { background: transparent; }
html[data-fx="lite"] #birth.screen, html[data-fx="off"] #birth.screen,
html[data-fx="lite"] #gate.gate, html[data-fx="off"] #gate.gate { background: var(--void); }

/* --- Лучи из-под шапки: один слой, движется только transform и opacity --- */
.fx-rays-top {
  position: fixed;
  top: 0; left: -25%; right: -25%;
  height: 58vh;
  z-index: -1;
  pointer-events: none;
  background:
    conic-gradient(from 180deg at 50% -6%,
      transparent 0deg,   rgba(79, 227, 216, 0.22) 5deg,  transparent 11deg,
      transparent 28deg,  rgba(79, 227, 216, 0.16) 33deg, transparent 40deg,
      transparent 58deg,  rgba(79, 227, 216, 0.26) 63deg, transparent 70deg,
      transparent 96deg,  rgba(79, 227, 216, 0.18) 100deg, transparent 108deg,
      transparent 130deg, rgba(79, 227, 216, 0.24) 136deg, transparent 144deg,
      transparent 160deg, rgba(79, 227, 216, 0.16) 166deg, transparent 174deg);
  -webkit-mask: linear-gradient(to bottom, #000 10%, transparent 92%);
          mask: linear-gradient(to bottom, #000 10%, transparent 92%);
  transform-origin: 50% 0;
  will-change: transform, opacity;
  animation: fx-rays-top 11s ease-in-out infinite alternate;
}
/* Экран боя закрывает игру целиком и не пускает решётку — лучи ему свои,
   тем же рисунком, через собственный слой. */
.fight > .fx-rays-top { position: absolute; z-index: 0; }
.fight > :not(.fx-rays-top) { position: relative; z-index: 1; }
@keyframes fx-rays-top {
  from { transform: rotate(-5deg) scaleX(1);    opacity: 0.4; }
  50%  { transform: rotate(0deg)  scaleX(1.08); opacity: 0.6; }
  to   { transform: rotate(5deg)  scaleX(1);    opacity: 0.45; }
}

/* --- Бегущий блик по тексту --------------------------------------------- */
/* Единственная анимация с перерисовкой, которая осталась: текст короткий,
   элементов один-два на экран, и замер её не заметил. */
.fx-shine {
  background: linear-gradient(110deg,
    var(--sys) 0%, var(--sys) 38%, #FFFFFF 50%, var(--sys) 62%, var(--sys) 100%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: fx-shine 4s linear infinite;
  opacity: 1;
}
@keyframes fx-shine { from { background-position: 110% 0; } to { background-position: -110% 0; } }

/* --- Градиентный текст: статичный, красится один раз ----------------------- */
.fx-gradient {
  background: linear-gradient(90deg, var(--sys), var(--upgrade), var(--gold));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* --- Сияющая рамка --------------------------------------------------------
   fx.js кладёт в панель <i class="fx-frame"><i></i></i>. Внешний — кольцо
   толщиной 2 px по контуру панели (маска «рамка минус середина», тот же
   срез угла через clip-path: inherit). Внутренний — квадрат вдвое больше
   панели с коническим градиентом, который ВРАЩАЕТСЯ transform-ом: телефон
   крутит его на видеокарте, не перерисовывая градиент. Прежняя версия
   анимировала угол градиента через @property и перерисовывала рамку каждый
   кадр. */
.fx-border { position: relative; isolation: isolate; }
.fx-frame {
  position: absolute; inset: 0;
  padding: 2px;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  clip-path: inherit;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}
.fx-frame > i {
  position: absolute;
  left: 50%; top: 50%;
  width: 300%; padding-top: 300%;
  margin: -150% 0 0 -150%;
  background: conic-gradient(from 0deg,
    transparent 0deg,
    var(--fx-color, var(--sys)) 30deg, #FFFFFF 55deg, var(--fx-color, var(--sys)) 80deg,
    transparent 120deg,
    transparent 180deg,
    var(--fx-color, var(--sys)) 210deg, #FFFFFF 235deg, var(--fx-color, var(--sys)) 260deg,
    transparent 300deg);
  will-change: transform;
  animation: fx-spin 4.5s linear infinite;
}
@keyframes fx-spin { to { transform: rotate(360deg); } }

/* Панель «Сейчас» дышит: свечение — отдельный слой, меняется только его
   прозрачность. Прежде анимировался box-shadow самой панели. */
.panel-sys.fx-border { border-color: var(--sys); }
.fx-glow {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 0;
  box-shadow: inset 0 0 44px -14px var(--sys);
  clip-path: inherit;
  will-change: opacity;
  animation: fx-breathe 2.8s ease-in-out infinite alternate;
}
@keyframes fx-breathe { from { opacity: 0.35; } to { opacity: 1; } }

/* --- Появление: только opacity и transform --------------------------------- */
.fx-in { animation: fx-in 0.36s cubic-bezier(0.22, 1, 0.36, 1) both; }
.fx-in:nth-child(2)  { animation-delay: 0.04s; }
.fx-in:nth-child(3)  { animation-delay: 0.08s; }
.fx-in:nth-child(4)  { animation-delay: 0.12s; }
.fx-in:nth-child(5)  { animation-delay: 0.16s; }
.fx-in:nth-child(6)  { animation-delay: 0.20s; }
.fx-in:nth-child(7)  { animation-delay: 0.24s; }
.fx-in:nth-child(8)  { animation-delay: 0.28s; }
.fx-in:nth-child(n+9) { animation-delay: 0.32s; }
@keyframes fx-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* --- Кнопка действия: пульс свечения и пробегающий блик ------------------ */
.btn, .tile, .tab { position: relative; overflow: hidden; }
/* Пульс — отдельный слой с готовой тенью, дышит прозрачностью. */
.btn-primary::before {
  content: "";
  position: absolute; inset: 0;
  box-shadow: inset 0 0 14px 0 rgba(79, 227, 216, 0.35);
  pointer-events: none;
  will-change: opacity;
  animation: fx-breathe 3s ease-in-out infinite alternate;
}
/* Блик — узкая полоса, бежит transform-ом слева направо и ждёт паузу. */
.btn-primary::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 0;
  width: 45%;
  background: linear-gradient(105deg,
    transparent 0%, rgba(255, 255, 255, 0.28) 40%, rgba(255, 255, 255, 0.55) 50%,
    rgba(255, 255, 255, 0.28) 60%, transparent 100%);
  transform: translateX(-120%);
  pointer-events: none;
  will-change: transform;
  animation: fx-sweep 6s ease-in-out infinite;
}
@keyframes fx-sweep {
  0%   { transform: translateX(-120%); }
  25%  { transform: translateX(340%); }
  100% { transform: translateX(340%); }
}

/* Текущий шаг главы дышит рамкой — слоем, а не тенью самого шага. */
.step-now { position: relative; }
.step-now::after {
  content: "";
  position: absolute; inset: 0;
  box-shadow: inset 0 0 14px -4px rgba(79, 227, 216, 0.5);
  pointer-events: none;
  will-change: opacity;
  animation: fx-breathe 2.4s ease-in-out infinite alternate;
}

/* --- Рябь от нажатия ------------------------------------------------------- */
.fx-ripple {
  position: absolute;
  width: 12px; height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(79, 227, 216, 0.65), rgba(79, 227, 216, 0.15) 60%, transparent 70%);
  pointer-events: none;
  will-change: transform, opacity;
  animation: fx-ripple 0.65s ease-out forwards;
  z-index: 2;
}
@keyframes fx-ripple {
  from { transform: scale(1); opacity: 1; }
  to   { transform: scale(28); opacity: 0; }
}

/* --- Огонёк по кромке шапки и вкладок: узкая полоса едет transform-ом ---- */
.fx-beam {
  position: absolute;
  left: 0;
  width: 34%;
  height: 2px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent, var(--sys) 35%, #FFFFFF 50%, var(--sys) 65%, transparent);
  transform: translateX(-100%);
  will-change: transform;
  animation: fx-beam 5s linear infinite;
  z-index: 3;
}
.hud > .fx-beam  { bottom: 0; }
.tabs > .fx-beam { top: 0; }
@keyframes fx-beam { from { transform: translateX(-100%); } to { transform: translateX(300%); } }

/* --- Полосы здоровья и сил: блик пробегает по заполненной части ----------- */
.meter-fill::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 0;
  width: 40%;
  background: linear-gradient(100deg,
    transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%);
  transform: translateX(-120%);
  will-change: transform;
  animation: fx-sweep 6s ease-in-out infinite;
}

/* --- Активная вкладка светится: статично, без анимации фильтра ------------ */
.tab.is-active .tab-icon {
  filter: drop-shadow(0 0 5px var(--sys)) drop-shadow(0 0 10px rgba(79, 227, 216, 0.45));
}

/* --- Туман над вкладками: градиент, без размытия --------------------------
   Размытие «за стеклом» здесь пересчитывалось на каждый кадр прокрутки и
   было самой дорогой вещью на экране, а глазу давало почти ничего. */
.fx-fade-bottom {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(var(--tabs-h) + var(--safe-bottom));
  height: 72px;
  pointer-events: none;
  z-index: 5;
  background: linear-gradient(to bottom, rgba(11, 18, 25, 0), rgba(11, 18, 25, 0.9));
}

/* --- Печать по букве: реплики Системы --------------------------------------- */
.fx-typing::after {
  content: "▍";
  color: var(--sys);
  animation: fx-caret 0.8s steps(1) infinite;
  margin-left: 1px;
}
@keyframes fx-caret { 50% { opacity: 0; } }

/* --- Холст матрицы глифов: подложка загрузки и окна Системы --------------- */
.fx-canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  z-index: 0;
  opacity: 0.6;
}
.fight-scene .fx-canvas { opacity: 0.4; }
#scene .scene-body { position: relative; z-index: 1; }

/* --- Лучи: «Глава закрыта», рождение, вскрытие карты ----------------------- */
/* #reveal уже fixed на весь экран — ему только overflow; лист карточки
   становится опорой для лучей сам. */
.sheet-body.fx-rays { position: relative; }
.fx-rays { overflow: hidden; }
.fx-rays::before {
  content: "";
  position: absolute; inset: -40% -20% auto -20%;
  height: 160%;
  background:
    conic-gradient(from 180deg at 50% 0%,
      transparent 0deg,   rgba(79, 227, 216, 0.30) 5deg,   transparent 11deg,
      transparent 28deg,  rgba(79, 227, 216, 0.24) 33deg,  transparent 40deg,
      transparent 58deg,  rgba(79, 227, 216, 0.32) 63deg,  transparent 70deg,
      transparent 96deg,  rgba(79, 227, 216, 0.26) 100deg, transparent 108deg,
      transparent 130deg, rgba(79, 227, 216, 0.30) 136deg, transparent 144deg,
      transparent 160deg, rgba(79, 227, 216, 0.22) 166deg, transparent 174deg);
  -webkit-mask: linear-gradient(to bottom, #000, transparent 88%);
          mask: linear-gradient(to bottom, #000, transparent 88%);
  pointer-events: none;
  will-change: transform, opacity;
  animation: fx-rays 7s ease-in-out infinite alternate;
  z-index: 0;
}
.fx-rays > :not(.reveal-burst) { position: relative; z-index: 1; }
@keyframes fx-rays { from { transform: rotate(-4deg); opacity: 0.5; } to { transform: rotate(4deg); opacity: 0.8; } }

/* Вскрытая карта парит и покачивается после выезда. */
#reveal:not([hidden]) .reveal-card {
  animation:
    card-in 0.62s cubic-bezier(0.2, 0.9, 0.25, 1) both,
    fx-float 4.5s 0.7s ease-in-out infinite alternate;
}
@keyframes fx-float {
  from { translate: 0 0;    rotate: y -7deg; }
  to   { translate: 0 -10px; rotate: y 7deg; }
}
#reveal:not([hidden]) .reveal-burst { animation: burst 0.7s ease-out both, fx-burst-breathe 3s 0.7s ease-in-out infinite alternate; }
@keyframes fx-burst-breathe { from { opacity: 0.55; scale: 1; } to { opacity: 0.95; scale: 1.15; } }

/* --- Кольцо прогресса: доля здоровья на карточке, глава в шапке ---------- */
.fx-ring { width: 44px; height: 44px; transform: rotate(-90deg); }
.fx-ring circle { fill: none; stroke-width: 4; }
.fx-ring .track { stroke: var(--line-soft); }
.fx-ring .bar { stroke: var(--sys); stroke-linecap: butt;
  transition: stroke-dashoffset 0.7s cubic-bezier(0.2, 0.8, 0.2, 1); }

/* --- Счётчик чисел: пока число бежит, оно чуть подсвечено ----------------- */
.fx-ticking { color: var(--sys); text-shadow: 0 0 10px rgba(79, 227, 216, 0.6); transition: color 0.3s, text-shadow 0.3s; }

/* --- Лёгкий режим: слабые телефоны и выбор в Настройках --------------------
   Ничего вечного: без холстов, лучей, бликов, рамок и пульса. Появление и
   рябь остаются — они короткие и на transform/opacity. */
html[data-fx="lite"] .fx-grid, html[data-fx="lite"] .fx-canvas, html[data-fx="lite"] .fx-rays-top,
html[data-fx="lite"] .fx-beam, html[data-fx="lite"] .fx-frame, html[data-fx="lite"] .fx-glow,
html[data-fx="lite"] .fx-rays::before, html[data-fx="lite"] .btn-primary::before,
html[data-fx="lite"] .btn-primary::after, html[data-fx="lite"] .meter-fill::after,
html[data-fx="lite"] .step-now::after {
  display: none !important;
}
html[data-fx="lite"] .fx-shine {
  animation: none; background: none; color: var(--sys); -webkit-text-fill-color: initial;
}
html[data-fx="lite"] .tab.is-active .tab-icon { filter: none; }
html[data-fx="lite"] #reveal:not([hidden]) .reveal-card { animation: card-in 0.62s cubic-bezier(0.2, 0.9, 0.25, 1) both; }
html[data-fx="lite"] #reveal:not([hidden]) .reveal-burst { animation: burst 0.7s ease-out both; }

/* --- Выключено: как lite, и без появления с рябью --------------------------- */
html[data-fx="off"] .fx-grid, html[data-fx="off"] .fx-canvas, html[data-fx="off"] .fx-rays-top,
html[data-fx="off"] .fx-beam, html[data-fx="off"] .fx-frame, html[data-fx="off"] .fx-glow,
html[data-fx="off"] .fx-rays::before, html[data-fx="off"] .btn-primary::before,
html[data-fx="off"] .btn-primary::after, html[data-fx="off"] .meter-fill::after,
html[data-fx="off"] .step-now::after, html[data-fx="off"] .fx-ripple, html[data-fx="off"] .fx-fade-bottom {
  display: none !important;
}
html[data-fx="off"] .fx-shine {
  animation: none; background: none; color: var(--sys); -webkit-text-fill-color: initial;
}
html[data-fx="off"] .fx-in { animation: none; }
html[data-fx="off"] .tab.is-active .tab-icon { filter: none; }
html[data-fx="off"] #reveal:not([hidden]) .reveal-card { animation: card-in 0.4s ease-out both; }
html[data-fx="off"] #reveal:not([hidden]) .reveal-burst { animation: none; opacity: 0.55; }

@media (prefers-reduced-motion: reduce) {
  .fx-shine, .fx-in, .fx-rays::before, .fx-typing::after, .fx-rays-top,
  .fx-frame > i, .fx-glow, .btn-primary::before, .btn-primary::after, .step-now::after,
  .fx-beam, .meter-fill::after, #reveal:not([hidden]) .reveal-card,
  #reveal:not([hidden]) .reveal-burst {
    animation: none !important;
  }
  .fx-grid, .fx-canvas { display: none; }
}
