/* Тонкие анимации (engine/static/fx.js). Каждый эффект работает, только если fx.js поставил класс fx-<имя> на <html>
   (то есть motion.fx.<имя> = true и --motion-enabled = 1). Покой = конечный вид; .fx-play проигрывает путь к нему. */
@layer components {
  .fx-focus [data-fx="focus"].fx-play { animation: fx-focus var(--motion-duration-focus) var(--motion-easing-out) both; }
  @keyframes fx-focus { from { filter: blur(7px); opacity: .4; } to { filter: none; opacity: 1; } }

  .fx-marker mark[data-fx="marker"] {
    background-color: transparent; color: inherit; background-image: linear-gradient(var(--highlight), var(--highlight));
    background-repeat: no-repeat; background-size: 100% 100%; padding: 0 .12em; border-radius: 2px;
  }
  .fx-marker [data-fx="marker"].fx-play { animation: fx-mark var(--motion-duration-draw) var(--motion-easing-out) both; }
  @keyframes fx-mark { from { background-size: 0% 100%; } to { background-size: 100% 100%; } }

  .fx-note [data-fx="note"].fx-play { animation: fx-write var(--motion-duration-draw) steps(14, end) both; }
  @keyframes fx-write { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }

  /* штамп: поворот покоя задаёт компонент через --fx-stamp-rotate (по умолчанию −7°) */
  .fx-stamp [data-fx="stamp"].fx-play { animation: fx-stamp calc(var(--motion-duration-draw) * .47) var(--motion-easing-settle) both; }
  @keyframes fx-stamp {
    0% { transform: rotate(-2deg) scale(1.9); opacity: 0; }
    70% { opacity: 1; }
    100% { transform: rotate(var(--fx-stamp-rotate, -7deg)) scale(1); }
  }

  /* картинка аналогии: штрихи [data-flow] бегут, только пока картинка на экране */
  .fx-analogy [data-fx="analogy"] [data-flow] { stroke-dasharray: 5 5; }
  .fx-analogy [data-fx="analogy"].fx-run [data-flow] { animation: fx-flow 1.2s linear infinite; }
  @keyframes fx-flow { to { stroke-dashoffset: -10; } }

  .fx-progress [data-fx="progress"] { transform-origin: 0 50%; transform: scaleX(0); will-change: transform; }
  .fx-toc [data-fx="toc"] a { transition: color var(--motion-duration-base) var(--motion-easing-out), border-color var(--motion-duration-base) var(--motion-easing-out); }
  .fx-pupil [data-fx="pupil"] { transition: r var(--motion-duration-draw) var(--motion-easing-out); }

  /* 3D-кнопка «вдавливается» в свою тень (shadow.hard-button) */
  .fx-press [data-press] { transition: transform var(--motion-duration-fast) ease, box-shadow var(--motion-duration-fast) ease; }
  .fx-press [data-press]:active { transform: translate(3px, 3px); box-shadow: none; }

  /* подсказка источника у [n]: data-cite-title ставит разметка статьи */
  .fx-cite [data-cite-title] { position: relative; }
  .fx-cite [data-cite-title]::after {
    content: attr(data-cite-title); position: absolute; left: 50%; bottom: calc(100% + 6px); z-index: 5; width: max-content; max-width: 16rem;
    transform: translate(-50%, 4px); opacity: 0; pointer-events: none; font: 400 .8rem/1.4 var(--font-body, inherit); color: var(--text);
    background: var(--surface-2); border: var(--border-frame-width, 2px) solid var(--frame); border-radius: .6rem; padding: .45rem .6rem;
    white-space: normal; text-align: left; transition: opacity var(--motion-duration-fast), transform var(--motion-duration-fast);
  }
  .fx-cite [data-cite-title]:hover::after, .fx-cite [data-cite-title]:focus-visible::after { opacity: 1; transform: translate(-50%, 0); }

  /* ═══ DS-6a: каталог эффектов из VROOM/AVITOMA (docs/design/EFFECTS.md), сведён в один приём на модуль ═══ */

  /* стекло — VROOM .dz-vg-glass: градиентный фон, блик сверху (::before), 1px радужная рамка (::after, mask-composite).
     Статичный приём — класс fx-glass просто включает правило, IntersectionObserver не нужен. */
  /* --fx-k-glass — ползунок «Сила» примерочной (docs/design/EFFECTS.md): множитель k=значение/100, ставит
     fx-preview.js на <html>; на обычных страницах переменная не задана, fallback «, 1» = поведение не меняется.
     Сильнее — глубже блюр, заметнее блик/рамка; слабее — почти незаметное стекло (k≈0). */
  .fx-glass [data-fx="glass"] {
    position: relative; isolation: isolate;
    /* фон — доля прозрачности от --fx-k-glass (color-mix, предел 100% у k⩾1): на витрине цель «.bento .card»
       несёт СВОЙ непрозрачный фон (sites/ophthalmolenka/static/style.css) — без этого смешивания «Сила» не
       меняла бы прозрачность фона вовсе (жалоба Романа «ничего не происходит», 02.10.2026). !important —
       собственный фон карточки объявлен ВНЕ @layer (cascade layers всегда ниже обычных правил, спецификация
       не помогает), поднимаем важностью: внутри @layer она всё равно побеждает любое обычное правило. */
    background: color-mix(in srgb, var(--glass-bg) calc(min(1, var(--fx-k-glass, 1)) * 100%), transparent) !important;
    border: var(--glass-border); color: var(--glass-text);
    /* то же самое — у собственного backdrop-filter карточки (style.css, вне @layer) без !important «Сила»
       блюр вообще не трогала бы: сайт показывал бы только фиксированный блюр карточки. */
    backdrop-filter: blur(calc(var(--glass-blur) * var(--fx-k-glass, 1))) saturate(calc(var(--glass-saturate) * var(--fx-k-glass, 1))) !important;
    -webkit-backdrop-filter: blur(calc(var(--glass-blur) * var(--fx-k-glass, 1))) saturate(calc(var(--glass-saturate) * var(--fx-k-glass, 1))) !important;
    box-shadow: var(--glass-highlight), var(--shadow-deep);
  }
  .fx-glass [data-fx="glass"]::before {
    content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 0;
    background: linear-gradient(180deg, color-mix(in srgb, var(--glass-text) 8%, transparent), transparent);
    mix-blend-mode: screen; opacity: calc(.9 * var(--fx-k-glass, 1));
    /* ореол СВЕРХ k=1 (max(0,k−1)) — растёт без потолка, в отличие от фона/блика/рамки (color-mix и opacity
       упираются в «полностью непрозрачно» уже на k=1). Без него рост с k=100% до k=200% был НЕМОНОТОННЫМ по
       доле изменённых пикселей (замер 02.10.2026: 0↔100 — 27.66%, 0↔200 — 23.80%): дальнейший рост блюра сверх
       уже предельно непрозрачного фона только сглаживает картинку (меньше резких границ — меньше точек выше
       порога диффа), а не добавляет новых отличий. На ::before (не ::after) — у ::after есть mask-composite,
       который обрезает и box-shadow по той же маске (первая попытка дала почти ноль прироста — замер 23.83%,
       тот же порядок, что без ореола). При k=1 — 0 0 0px 0px (невидимо, как и было). */
    box-shadow: 0 0 calc(max(0px, 22px * (var(--fx-k-glass, 1) - 1))) calc(max(0px, 5px * (var(--fx-k-glass, 1) - 1))) var(--glass-text);
  }
  .fx-glass [data-fx="glass"]::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px; pointer-events: none; z-index: 0;
    background: var(--glass-frame-gradient); opacity: var(--fx-k-glass, 1);
    -webkit-mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0); -webkit-mask-composite: xor;
    mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0); mask-composite: exclude;
  }
  .fx-glass [data-fx="glass"] > * { position: relative; z-index: 1; }
  /* нет backdrop-filter (или color-mix) — непрозрачный запасной фон, рамка/блик убираем (нечего размывать) */
  @supports not (backdrop-filter: blur(1px)) {
    .fx-glass [data-fx="glass"] { background: var(--glass-bg-fallback); backdrop-filter: none; -webkit-backdrop-filter: none; }
    .fx-glass [data-fx="glass"]::before, .fx-glass [data-fx="glass"]::after { content: none; }
  }
  /* мобильный/грубый указатель — блюр слабее (дешевле на слабом GPU) */
  @media (max-width: 700px), (pointer: coarse) {
    .fx-glass [data-fx="glass"] {
      backdrop-filter: blur(calc(var(--glass-blur-mobile) * var(--fx-k-glass, 1))) saturate(calc(var(--glass-saturate) * var(--fx-k-glass, 1))) !important;
      -webkit-backdrop-filter: blur(calc(var(--glass-blur-mobile) * var(--fx-k-glass, 1))) saturate(calc(var(--glass-saturate) * var(--fx-k-glass, 1))) !important;
    }
  }

  /* цветные пятна за стеклом — AVITOMA Aurora (дрейф x/y/scale) + кинематика VROOM dz-drift-*; крутится
     только пока видно (fx.js переключает fx-run через тот же IntersectionObserver, что и analogy) */
  .fx-aurora [data-fx="aurora"] { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
  .fx-aurora [data-fx="aurora"] i {
    position: absolute; width: 55%; aspect-ratio: 1; border-radius: 50%; font-style: normal;
    filter: blur(calc(var(--aurora-blur) * var(--fx-k-aurora, 1))); opacity: 0; transform: scale(1);
  }
  .fx-aurora [data-fx="aurora"] i:nth-child(1) { top: -18%; left: -12%; background: radial-gradient(circle, var(--aurora-color-1), transparent 60%); }
  .fx-aurora [data-fx="aurora"] i:nth-child(2) { top: -8%; right: -14%; background: radial-gradient(circle, var(--aurora-color-2), transparent 60%); animation-delay: 3s; }
  .fx-aurora [data-fx="aurora"] i:nth-child(3) { bottom: -22%; left: 22%; background: radial-gradient(circle, var(--aurora-color-3), transparent 62%); animation-delay: 6s; }
  .fx-aurora [data-fx="aurora"].fx-run i { animation: fx-aurora var(--aurora-duration) ease-in-out infinite; }
  @keyframes fx-aurora {
    0%, 100% { opacity: calc(var(--aurora-opacity) * var(--fx-k-aurora, 1)); transform: scale(1); }
    50% {
      opacity: calc(1 * var(--fx-k-aurora, 1));
      transform: translate(calc(6% * var(--fx-k-aurora, 1)), calc(-8% * var(--fx-k-aurora, 1))) scale(calc(1 + .12 * var(--fx-k-aurora, 1)));
    }
  }
  /* на мобиле — только одно пятно (дешевле: меньше слоёв с filter:blur) */
  @media (max-width: 700px) { .fx-aurora [data-fx="aurora"] i:nth-child(n+2) { display: none; } }

  /* появление снизу — заменяет .reveal (VROOM dz-reveal / AVITOMA FadeIn): покой = скрыто, .fx-play проигрывает
     путь к конечному виду один раз при появлении; data-fx-stagger на элементе — индекс для лесенки задержек */
  .fx-reveal [data-fx="reveal"] { opacity: 0; transform: translateY(calc(28px * var(--fx-k-reveal, 1))); }
  .fx-reveal [data-fx="reveal"].fx-play {
    animation: fx-reveal var(--motion-duration-base) var(--motion-easing-out) both;
    animation-delay: calc(var(--motion-stagger) * var(--fx-stagger, 0));
  }
  @keyframes fx-reveal {
    from { opacity: 0; transform: translateY(calc(28px * var(--fx-k-reveal, 1))); }
    to { opacity: 1; transform: translateY(0); }
  }

  /* дыхание/пульс — сведение VROOM dz-breathe + AVITOMA pulse-glow/StatusDot/glowPulse в один приём; не для
     текста (WCAG 2.2.2 — мигающий текст); крутится только пока видно */
  .fx-pulse [data-fx="pulse"].fx-run { animation: fx-pulse var(--pulse-duration) ease-in-out infinite; }
  @keyframes fx-pulse {
    /* k² (не k) — цель маленькая (иконка), линейного множителя было мало, чтобы доля изменённых пикселей в
       рамке цели прошла приёмку (замер 02.10.2026: 2.97 % при линейном k); при k=1 даёт тот же множитель 1,
       что и раньше (1² = 1) — вид по умолчанию не меняется. */
    0%, 100% { transform: scale(1); opacity: .88; filter: drop-shadow(0 0 0 transparent); }
    50% {
      /* k³ (не k²) — цель маленькая (иконка ~28px), k² давал только 4.54 % на приёмке (ниже порога 5 %). */
      transform: scale(calc(1 + (var(--pulse-scale) - 1) * var(--fx-k-pulse, 1) * var(--fx-k-pulse, 1) * var(--fx-k-pulse, 1)));
      opacity: 1;
      filter: drop-shadow(0 0 calc(var(--pulse-glow) * var(--fx-k-pulse, 1) * var(--fx-k-pulse, 1) * var(--fx-k-pulse, 1)) var(--pulse-color));
    }
  }

  /* бегущий блик — VROOM dz-shimmer, но одноразовый (появление + hover), не бесконечный (правило «≤1 заметное
     событие на экран») */
  .fx-shimmer [data-fx="shimmer"] { position: relative; overflow: hidden; isolation: isolate; }
  .fx-shimmer [data-fx="shimmer"]::after {
    content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
    background: linear-gradient(110deg, transparent 25%, var(--shimmer-color) 50%, transparent 75%);
    background-size: 200% 100%; background-position: -150% 0; opacity: 0;
    /* полагаться на один только цвет-токен опасно: на кнопке, чей фон близок к shimmer-color (например
       .btn-white внутри data-scope="inverse", где role.text тоже белый), обычное смешивание почти не меняет
       и без того белую поверхность (жалоба Романа 30.09.2026 «Блик не видно»). difference гарантированно даёт
       видимый контраст независимо от фона кнопки. */
    mix-blend-mode: difference;
  }
  .fx-shimmer [data-fx="shimmer"].fx-play::after,
  .fx-shimmer [data-fx="shimmer"]:hover::after,
  .fx-shimmer [data-fx="shimmer"]:focus-visible::after { animation: fx-shimmer var(--shimmer-duration) ease-in-out both; }
  @keyframes fx-shimmer {
    0% { background-position: -150% 0; opacity: 0; }
    15%, 85% { opacity: calc(1 * var(--fx-k-shimmer, 1)); }
    100% { background-position: 150% 0; opacity: 0; }
  }

  /* диагональный луч по карточке-герою — VROOM dz-vg-ray; только пока видно, одна на страницу (см. EFFECTS.md) */
  .fx-ray [data-fx="ray"] { position: relative; overflow: hidden; isolation: isolate; }
  .fx-ray [data-fx="ray"]::after {
    content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1; opacity: 0;
    /* 2.10.2026: прозрачность упирается в 1 уже при k=1 — выше 100 % сила расширяет полосу луча (k=1: 18 %/82 %, как было) */
    background: linear-gradient(100deg, transparent calc(30% - 12% * var(--fx-k-ray, 1)), var(--ray-color) 50%,
      transparent calc(70% + 12% * var(--fx-k-ray, 1)));
    transform: translateX(-120%) skewX(-18deg);
  }
  .fx-ray [data-fx="ray"].fx-run::after { animation: fx-ray var(--ray-duration) linear infinite; }
  @keyframes fx-ray {
    0% { transform: translateX(-120%) skewX(-18deg); opacity: 0; }
    10%, 90% { opacity: calc(1 * var(--fx-k-ray, 1)); }
    100% { transform: translateX(120%) skewX(-18deg); opacity: 0; }
  }
}
@media (prefers-reduced-motion: reduce) { [data-fx], [data-fx] * { animation: none !important; transition: none !important; } }
/* тему «для слабовидящих» могут включить уже после загрузки, когда fx.js работает: гасим всё движение и тут */
[data-theme="low-vision"] [data-fx], [data-theme="low-vision"] [data-fx] * { animation: none !important; transition: none !important; }
/* витрина /_design/ форсирует эффекты у себя кнопкой «выключить движение» (docs/design/SHOWCASE.md) — тот же
   приём, что у low-vision выше, но по явному атрибуту, а не по теме/системной настройке */
[data-motion-preview="off"] [data-fx], [data-motion-preview="off"] [data-fx] * { animation: none !important; transition: none !important; }
