/* DS-6b (30.09.2026, docs/design/EFFECTS.md) — второй пакет модуля тонких анимаций, отдельный от fx.css/fx.js
   ради бюджета (courier-эффекты за курсором требуют больше кода, чем шесть модулей DS-6a). Подключается тем же
   способом, что и fx.css: только если в tokens.json сайта включён хоть один motion.fx.<ключ DS-6b>, ИЛИ на
   витрине/в примерочной (fx2_v_forced()). Правила как у fx.css: покой = конечный вид, только opacity/transform/
   filter/background-position/background-size, --motion-enabled:0 гасит всё. */
@layer components {
  /* курсорные модули (tilt/glare/depth/magnetic/spotlight) — позицию/угол считает fx2.js (pointer:coarse и узкий
     экран — вообще не навешивает обработчики, см. engine/static/fx2.js), CSS только переходит плавно (transition,
     «пружина» через motion.easing.settle) и рисует статичную часть (градиенты, маски). */
  .fx-tilt [data-fx="tilt"] {
    transform-style: preserve-3d;
    transition: transform var(--motion-duration-base) var(--motion-easing-settle);
    will-change: transform;
  }

  .fx-glare [data-fx="glare"] { position: relative; isolation: isolate; overflow: hidden; }
  .fx-glare [data-fx="glare"]::after {
    content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1; opacity: 0;
    background: radial-gradient(calc(var(--glare-size) * var(--fx-k-glare, 1)) calc(var(--glare-size) * var(--fx-k-glare, 1))
      at var(--glare-x, 50%) var(--glare-y, 50%), color-mix(in srgb, var(--glare-color) 65%, transparent), transparent 70%);
    transition: opacity var(--motion-duration-fast) var(--motion-easing-out);
  }
  .fx-glare [data-fx="glare"]:hover::after, .fx-glare [data-fx="glare"]:focus-within::after {
    opacity: calc(var(--glare-opacity) * var(--fx-k-glare, 1));
  }

  /* глубина: первые три прямых потомка контейнера [data-fx="depth"] (иконка/заголовок/текст по порядку разметки)
     двигаются на разную величину вдоль --depth-x/--depth-y (-1..1, считает fx2.js при наведении на контейнер). */
  .fx-depth [data-fx="depth"] > :nth-child(1) {
    transform: translate(calc(var(--depth-x, 0) * var(--depth-shift-1) * var(--fx-k-depth, 1)),
      calc(var(--depth-y, 0) * var(--depth-shift-1) * var(--fx-k-depth, 1)));
    transition: transform var(--motion-duration-base) var(--motion-easing-settle);
  }
  .fx-depth [data-fx="depth"] > :nth-child(2) {
    transform: translate(calc(var(--depth-x, 0) * var(--depth-shift-2) * var(--fx-k-depth, 1)),
      calc(var(--depth-y, 0) * var(--depth-shift-2) * var(--fx-k-depth, 1)));
    transition: transform var(--motion-duration-base) var(--motion-easing-settle);
  }
  .fx-depth [data-fx="depth"] > :nth-child(3) {
    transform: translate(calc(var(--depth-x, 0) * var(--depth-shift-3) * var(--fx-k-depth, 1)),
      calc(var(--depth-y, 0) * var(--depth-shift-3) * var(--fx-k-depth, 1)));
    transition: transform var(--motion-duration-base) var(--motion-easing-settle);
  }

  /* покачивание без мыши — крутится, только пока видно (fx-run, IntersectionObserver в fx2.js, как aurora/pulse/ray) */
  .fx-sway [data-fx="sway"].fx-run { animation: fx2-sway var(--sway-duration) ease-in-out infinite; }
  /* k² — амплитуда покачивания (2.4° по умолчанию) слишком мала визуально при линейном k (замер 02.10.2026:
     4.07 %); при k=1 множитель 1² = 1 — покой не меняется. */
  @keyframes fx2-sway {
    0%, 100% { transform: rotate(calc(var(--sway-deg) * -1deg * var(--fx-k-sway, 1) * var(--fx-k-sway, 1) * var(--fx-k-sway, 1))); }
    50% { transform: rotate(calc(var(--sway-deg) * 1deg * var(--fx-k-sway, 1) * var(--fx-k-sway, 1) * var(--fx-k-sway, 1))); }
  }

  /* светящаяся рамка бежит по краю — conic-gradient вращается, маска оставляет только тонкую полосу по краю
     (тот же mask-composite:exclude приём, что у glass.frame-gradient в fx.css). Крутится, только пока видно. */
  .fx-borderglow [data-fx="borderglow"] { position: relative; isolation: isolate; }
  /* мягкий ореол box-shadow СВЕРХ k=1 (max(0,k−1)) — у тонкой рамки через ::after/mask-composite:exclude есть
     жёсткий потолок на маленьких целях (кнопка 193×52px): рамка толще ~20px схлопывает content-box в ноль
     (52px высоты − 2×26px ≥ 0), маска перестаёт давать тонкую полосу, и доля изменённых пикселей ПАДАЕТ, а не
     растёт (разбор 02.10.2026: k⁴ — 4.67 %, k⁴+24px — 3.52 %, ХУЖЕ). box-shadow не зависит от content-box
     цели вовсе — ореол сам по себе даёт нужный прирост площади. При k=1 — blur/spread ровно 0px (невидимо,
     как и было); при k=2 — заметный мягкий ореол вокруг всей кнопки. */
  .fx-borderglow [data-fx="borderglow"].fx-run {
    box-shadow: 0 0 calc(max(0px, 22px * (var(--fx-k-borderglow, 1) - 1)))
      calc(max(0px, 7px * (var(--fx-k-borderglow, 1) - 1))) var(--borderglow-color);
  }
  /* !important — цель карты примерочной (кнопки .btn-cyan и т. п.) уже несёт СВОЙ ::after (бегущий блик
     кнопки, sites/ophthalmolenka/static/style.css: `.btn-primary::after,.btn-cyan::after{position;top;
     left:-70%;width:50%;height:100%;background;transform}`, ВНЕ @layer). Один ::after на двоих: без
     !important чужой unlayered left:-70%/width:50%/height:100%/background побеждали по каждому общему
     физическому свойству — рамка рисовалась в полоске 50%×100% с отступом −70% слева (практически за
     пределами кнопки) чужим цветом вместо конуса --borderglow-color (разбор 02.10.2026, замер 1.67 %). */
  .fx-borderglow [data-fx="borderglow"]::after {
    /* top/left/width/height — ПОЛНЫЙ набор переопределений чужих top/left/width/height, right/bottom явно auto
       (не inset:0 — если задать left/right/width/height одновременно, система переопределена: браузер по
       спецификации игнорирует «left» и пересчитывает его сам, рамка «плыла» по горизонтали вместе с ростом
       padding ниже, с разным смещением на каждом k — разбор 02.10.2026, второй замер 0.81 % после первой,
       неполной правки). */
    content: "" !important; position: absolute !important;
    top: 0 !important; left: 0 !important; right: auto !important; bottom: auto !important;
    width: 100% !important; height: 100% !important;
    /* transition:none!important — сайт анимирует «left» своего блика (`transition:left .6s ease`); наш
       important-left:0 выигрывает ЗНАЧЕНИЕ, но чужой transition на то же свойство остаётся объявлен и плавно
       доводит до него секунды полторы — семплы «на k=0/100/200» на самом деле ловили РАЗНЫЕ фазы одного и того
       же затухания к нулю, а не разницу от k (разбор 02.10.2026, третий замер 0.81 %, left менялся от −71px до
       −2.6px по ходу теста независимо от «Силы»). */
    transition: none !important;
    border-radius: inherit; pointer-events: none;
    /* z-index:4 (не 1) — та же причина, что у orbit ниже: содержимое кнопки (текст) непредсказуемо перекрывает
       низкий z-index в этом конкретном стековом контексте (isolation:isolate), рамка почти не была видна даже
       при правильных вычисленных width/opacity/background (разбор 02.10.2026, третий замер 2.77 %). */
    z-index: 4;
    /* k³ (не k⁴/k⁴+24px — обе давали МЕНЬШЕ при проверке: на кнопке высотой 52px padding 56-64px по вертикали
       схлопывает content-box в ноль (52px − 2×56px < 0, CSS обрезает отрицательную высоту), и maska
       content-box/border-box exclude перестаёт давать тонкую рамку — разбор 02.10.2026, k⁴+24px замер 3.52 %,
       ХУЖЕ k⁴ без прибавки 4.67%). k³ — content-box остаётся положительным (52 − 2×16 = 20px) при k=2,
       маска работает штатно; при k=1 — те же 2px, что раньше. */
    padding: calc(var(--borderglow-width) * var(--fx-k-borderglow, 1) * var(--fx-k-borderglow, 1)
      * var(--fx-k-borderglow, 1));
    opacity: 0;
    background: conic-gradient(from 0deg, transparent 0%, var(--borderglow-color) 12%,
      transparent calc(26% + 30% * (var(--fx-k-borderglow, 1) - 1))) !important;
    /* difference — гарантированный контраст с фоном кнопки независимо от того, какого цвета сама кнопка (тот
       же приём, что у shimmer в fx.css: «Блик не видно» на светлом фоне, если цвет блика близок к фону). */
    mix-blend-mode: difference;
    -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-borderglow [data-fx="borderglow"].fx-run::after { animation: fx2-borderglow var(--borderglow-duration) linear infinite; }
  @keyframes fx2-borderglow {
    0% { transform: rotate(0deg); opacity: calc(.9 * var(--fx-k-borderglow, 1)); }
    100% { transform: rotate(360deg); opacity: calc(.9 * var(--fx-k-borderglow, 1)); }
  }

  /* кольцо вокруг иконки/фото — приём AVITOMA GlowSphere.jsx (вращающийся conic-gradient по окружности). Крутится,
     только пока видно. Цель — НЕ голый <svg> (иконка сама по себе): ::before на SVG-элементах не отрисовывается
     в Chromium (computed style есть, пикселей нет — проверено эмпирически 30.09.2026); оборачивайте иконку в
     <span>/<div> или ставьте data-fx="orbit" на родительский контейнер (docs/design/EFFECTS.md, карта dockto). */
  .fx-orbit [data-fx="orbit"] { position: relative; isolation: isolate; }
  /* !important на всех свойствах, которые также объявляет чужой ::before — цель карты примерочной
     (.hero-photo) несёт СВОЙ декоративный блик на ::before (sites/ophthalmolenka/static/style.css:
     `.hero-photo::before{position;inset;background;filter:blur(30px);z-index}`, ВНЕ @layer). У элемента
     только один ::before — кольцо и блик спорят за один и тот же псевдоэлемент; без !important чужой
     unlayered-блик побеждал по каждому общему свойству (в т.ч. фон — кольцо красилось БЛЁКЛЫМ цветом блика
     сайта, а не акцентным --orbit-color, и blur всегда был зафиксирован на 30px независимо от «Силы» —
     разбор 02.10.2026, замер 1.15/1.04 %). */
  .fx-orbit [data-fx="orbit"]::before {
    /* z-index:-1 пряталось за собственным НЕПРОЗРАЧНЫМ фоном контейнера (isolation:isolate создаёт свой стек —
       отрицательный z-index красится ниже border/background самого элемента, а не только ниже его контента;
       заметно только на прозрачных контейнерах вроде .hero-photo). z-index:0 (порядок исходника, до контента)
       — кольцо поверх фона карточки, дети перекрывают его естественным порядком отрисовки. */
    content: "" !important; position: absolute !important; inset: 50% auto auto 50% !important;
    /* z-index:2 (не 0, как раньше) — на «.hero-photo» (ophtha) сама фотография стоит z-index:1 (style.css):
       кольцо с z-index:0 целиком красилось ПОД фотографией (видна только тонкая полоска по краю маски фото —
       практически не видно; разбор 02.10.2026, замер 0.04 %). 2 — заведомо выше любого обычного контента цели. */
    z-index: 2 !important; pointer-events: none;
    /* k² — кольцо тонкое (узкая полоса по маске ниже), площадь растёт как квадрат диаметра: линейный k давал
       слишком маленький прирост площади, чтобы пройти приёмку (замер 02.10.2026: 1.15 %); при k=1 множитель
       1² = 1 — размер кольца (128px) не меняется. */
    width: calc(var(--orbit-size) * var(--fx-k-orbit, 1) * var(--fx-k-orbit, 1));
    height: calc(var(--orbit-size) * var(--fx-k-orbit, 1) * var(--fx-k-orbit, 1));
    border-radius: 50% !important;
    transform: translate(-50%, -50%);
    background: conic-gradient(from 0deg, transparent 0 46%, var(--orbit-color) 62%, transparent 92%) !important;
    -webkit-mask: radial-gradient(circle closest-side, transparent 58%, black 61%, black 72%, transparent 75%);
    mask: radial-gradient(circle closest-side, transparent 58%, black 61%, black 72%, transparent 75%);
    opacity: 0;
    /* мягкое свечение прибавляется только СВЕРХ k=1 (множитель k−1, снизу ограничен нулём) — при k=1 blur(0px),
       вид не меняется; при k>1 кольцо дополнительно расплывается, занимая больше площади цели. */
    filter: blur(calc(max(0px, 6px * (var(--fx-k-orbit, 1) - 1)))) !important;
  }
  .fx-orbit [data-fx="orbit"].fx-run::before {
    animation: fx2-orbit var(--orbit-duration) linear infinite; opacity: calc(.9 * var(--fx-k-orbit, 1));
  }
  @keyframes fx2-orbit { from { transform: translate(-50%, -50%) rotate(0deg); } to { transform: translate(-50%, -50%) rotate(360deg); } }

  /* кнопка тянется к курсору (fx2.js считает translate в радиусе действия, CSS плавно переходит) */
  .fx-magnetic [data-fx="magnetic"] {
    display: inline-block; transition: transform var(--motion-duration-fast) var(--motion-easing-settle);
    will-change: transform;
  }

  /* подсветка фона вокруг курсора на тёмных блоках — fx2.js считает позицию, --spot-opacity включает/гасит плавно */
  .fx-spotlight [data-fx="spotlight"] { position: relative; isolation: isolate; }
  .fx-spotlight [data-fx="spotlight"]::before {
    content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
    background: radial-gradient(calc(var(--spotlight-size) * var(--fx-k-spotlight, 1)) calc(var(--spotlight-size) * var(--fx-k-spotlight, 1))
      at var(--spot-x, 50%) var(--spot-y, 50%), var(--spotlight-color), transparent 70%);
    opacity: var(--spot-opacity, 0); transition: opacity var(--motion-duration-base) var(--motion-easing-out);
  }
  .fx-spotlight [data-fx="spotlight"] > * { position: relative; z-index: 1; }

  /* слой первого экрана — скорость считает fx2.js по прокрутке (data-parallax-speed, по умолчанию 0.3), предел
     смещения — parallax.shift (без layout-сдвигов, только transform). */
  .fx-parallax [data-fx="parallax"] { will-change: transform; }

  /* заголовок проявляется по словам — fx2.js оборачивает каждое слово в .fx-word и проставляет --fx-word-i.
     Длительность перехода делим на k (не меньше .15, чтобы не делить на ноль) — при k=1 длительность та же, что
     раньше (motion-duration-base/1); при k→0 слово всплывает в десятки раз дольше: на застывшем кадре приёмки
     (currentTime фиксирован) видно едва начавшийся переход вместо уже завершённого — без этого финальное
     состояние (opacity:1, translateY(0)) не зависит от k вовсе, и «Сила» выглядела бы так, будто ничего не
     делает (замер 02.10.2026: 0 %, разбор — docs/design/EFFECTS.md). */
  .fx-words [data-fx="words"] .fx-word {
    display: inline-block; opacity: 0; transform: translateY(calc(.4em * var(--fx-k-words, 1)));
    /* 2.10.2026: деление на k делало эффект при k>1 быстрее, то есть НЕзаметнее (замер 100↔200 = 0 %) — сильнее
       = дальше вылет (.4em·k выше) и медленнее проявление; k=1 — как было */
    transition: opacity calc(var(--motion-duration-base) * (.5 + .5 * var(--fx-k-words, 1))) var(--motion-easing-out),
      transform calc(var(--motion-duration-base) * (.5 + .5 * var(--fx-k-words, 1))) var(--motion-easing-out);
    transition-delay: calc(var(--words-stagger) * var(--fx-word-i, 0));
  }
  .fx-words [data-fx="words"].fx-play .fx-word { opacity: 1; transform: translateY(0); }

  /* подчёркивание «рисуется» слева направо при наведении/фокусе — тот же приём, что fx-marker в fx.css, но по
     нижнему краю ссылки, не по всей высоте текста. */
  /* !important на background-image/-repeat/-position — цель карты примерочной (.nav a, класс .mt) попадает под
     собственное правило сайта `.mnav .mt:hover{background:...}` (style.css, ВНЕ @layer): шорткод `background`
     на hover сбрасывает ВСЕ свои подсвойства к начальным (background-image → none), не только background-size —
     без !important на image линия пропадала целиком даже когда background-size был верным числом (разбор
     02.10.2026, второй замер — 0 %, хотя background-size уже корректно менялся). */
  /* k³ (не k) — ссылка в карте примерочной (.nav a) маленькая (84×42px), линейная толщина (2px→4px на k=2)
     занимает <1% рамки цели (замер 02.10.2026: 2.08 % после фикса !important). Кубическая кривая: при k=1 —
     те же 2px, что и раньше (1³=1), при k=2 — 16px (заметная полоса, не тонкая линия). */
  .fx-underline [data-fx="underline"] {
    text-decoration: none;
    background-image: linear-gradient(var(--underline-color), var(--underline-color)) !important;
    background-repeat: no-repeat !important; background-position: 0 100% !important;
    background-size: 0% calc(2px * var(--fx-k-underline, 1) * var(--fx-k-underline, 1) * var(--fx-k-underline, 1));
    transition: background-size var(--underline-duration) var(--motion-easing-out);
    padding-bottom: calc(2px * var(--fx-k-underline, 1) * var(--fx-k-underline, 1) * var(--fx-k-underline, 1));
  }
  /* !important — тот же `.mnav .mt:hover{background:...}` сбрасывает и background-size на hover (той же
     специфичности, cascade layers всегда ниже обычных правил). Без !important подчёркивание на hover пропадало
     целиком — «Сила» было нечем мерить (замер 02.10.2026: 0 %). */
  .fx-underline [data-fx="underline"]:hover, .fx-underline [data-fx="underline"]:focus-visible {
    background-size: 100% calc(2px * var(--fx-k-underline, 1) * var(--fx-k-underline, 1) * var(--fx-k-underline, 1)) !important;
  }

  /* медленный перелив градиента по тексту — только opacity/background-position-совместимые свойства в keyframes */
  .fx-gradienttext [data-fx="gradienttext"] {
    background-image: linear-gradient(90deg, var(--gradienttext-color-1), var(--gradienttext-color-2), var(--gradienttext-color-3), var(--gradienttext-color-1));
    background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent;
    /* сила — насыщенность ПЛЮС контраст перелива: k=0 обесцвечивает и уплощает (почти нет эффекта), k=2 —
       вдвое ярче и контрастнее. Один saturate() почти не меняет яркость (luma) пикселей — приёмка 02.10.2026
       сравнивает кадры в оттенках серого (L-канал), а насыщенность на него влияет слабо; добавленный contrast()
       меняет именно яркостный разброс, который и ловит приёмка. При k=1 — contrast(100%), ровно как раньше. */
    filter: saturate(calc(var(--fx-k-gradienttext, 1) * 100%)) contrast(calc(60% + 40% * var(--fx-k-gradienttext, 1)));
  }
  .fx-gradienttext [data-fx="gradienttext"].fx-run { animation: fx2-gradienttext var(--gradienttext-duration) linear infinite; }
  @keyframes fx2-gradienttext { from { background-position: 0% 0; } to { background-position: 300% 0; } }
  /* нет background-clip:text — обычный цвет текста (первая роль градиента), не прозрачный текст на прозрачном фоне */
  @supports not (background-clip: text) {
    .fx-gradienttext [data-fx="gradienttext"] { background-image: none; color: var(--gradienttext-color-1); }
  }

  /* «плёночная» фактура — SVG-шум инлайном (data: URI, без сетевого запроса), статично, без анимации */
  .fx-grain [data-fx="grain"] { position: relative; isolation: isolate; }
  .fx-grain [data-fx="grain"]::after {
    content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
    opacity: calc(var(--grain-opacity) * var(--fx-k-grain, 1));
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
    /* screen (не overlay) — на почти чёрном фоне overlay математически близок к no-op (overlay(near-0, x) ≈ near-0
       для любого x); screen только высветляет, спекл виден независимо от того, насколько тёмный fond (проверено
       эмпирически 30.09.2026 — с overlay доля изменённых пикселей была 0% даже при opacity 0.14). */
    mix-blend-mode: screen;
  }

  /* конфетти — всплеск разноцветных прямоугольников по событию (fx2.js создаёт и удаляет узлы сам, здесь только
     стартовое/конечное состояние и keyframes: opacity/transform — дешёвые свойства, ≤1.2с, без мигания (1 цикл,
     не infinite). */
  .fx-confetti-piece {
    position: fixed; top: 0; left: 0; width: 8px; height: 14px; pointer-events: none; z-index: 2147483000;
    opacity: 0; border-radius: 2px; will-change: transform, opacity;
  }
  .fx-confetti-piece.fx-run {
    animation: fx2-confetti var(--confetti-duration, 1.1s) cubic-bezier(.2, .7, .2, 1) both;
  }
  @keyframes fx2-confetti {
    0% { opacity: 0; transform: translate(0, 0) rotate(0deg); }
    12% { opacity: 1; }
    100% { opacity: 0; transform: var(--fx-confetti-end, translate(0, 220px) rotate(260deg)); }
  }
}
@media (prefers-reduced-motion: reduce) {
  [data-fx], [data-fx] * { animation: none !important; transition: none !important; }
  .fx-confetti-piece { display: none !important; }
}
[data-theme="low-vision"] [data-fx], [data-theme="low-vision"] [data-fx] * { animation: none !important; transition: none !important; }
[data-motion-preview="off"] [data-fx], [data-motion-preview="off"] [data-fx] * { animation: none !important; transition: none !important; }
