/* ОТКЛИК ПЕРЕХОДА — «СЧЁТ-ВСТУПЛЕНИЕ» (09.10.2026, правка Сармата №1).
   Пара к nav-feel.js: там «когда показывать и когда гасить», здесь только вид.

   ЗАДАЧА. Приложение многостраничное: между нажатием и новой страницей у живых
   людей 1,5–2 с (из России дольше), и всё это время старый экран стоит как ни в
   чём не бывало. Пружина (spring.css) отвечает на само нажатие, а после
   отпускания была тишина — человек не знает, принят ли тап, и жмёт второй раз.
   Запрещено Сарматом: крутилка-кругляшок («значит всё плохо работает») и надпись
   «Загрузка…» («по-деревенски»).

   ЧТО ВЗЯТО ИЗ ЕГО ИДЕИ «ОТСЧЁТА», А ЧТО НЕТ. Цифры 3-2-1 / «иди» не берём: они
   обещают время, которым мы не управляем (в Европе страница приходит раньше
   «двух» — счёт обрывается; из России позже «иди» — сигнал «вперёд» в пустоту),
   а счёт секунд заставляет следить за часами, и ожидание тянется дольше. Берём
   суть — ритм и предвкушение — и язык самого продукта: на экране песни перед
   строкой уже идёт счёт вступления из трёх точек. Здесь тот же счёт на три доли
   в темпе 120 BPM (доля 500 мс, обычный темп k-pop): РАЗ — в момент тапа, ДВА —
   через полсекунды, ТРИ — на первой секунде, ровно на пороге «поток мысли не
   прерван» (NN/g, 1 с). После третьей доли счёт не уходит в «четыре» и не говорит
   «иди»: третья доля ДЕРЖИТСЯ (фермата) и медленно дотягивается, пока страница
   не придёт сама. Обещания по времени нет, обрыва в пустоту нет.

   ОДНА СИСТЕМА В ДВУХ МЕСТАХ, ОДИН СЧЁТ.
   1. СЛЕД — на самой кнопке, там, где палец и глаза (место внимания). Кнопка
      загорается сиреневым ободком, от точки касания расходится свет, и на каждую
      долю от пальца уходит волна. Три волны — три доли. Потом кнопка просто
      держит свет: «принято, едем».
   2. НИТЬ — тонкая полоса сверху из трёх отрезков, те же три доли: первый
      заполняется сразу, второй на «два», третий на «три» тянется к концу и не
      доходит, пока страница не пришла. Тот же язык, что у полосы жеста «назад»
      (swipe-back, 3 px сверху, сирень → металл): «верхняя нить = идём между
      экранами». По нити видно и переход, начатый не пальцем (location.href из
      кода, таймер после задания).
   3. ПРИХОД — новая страница проявляется и садится на место за 280 мс
      (кросс-документный View Transition). Где он не поддержан, правило
      @view-transition браузер просто не читает — ничего не мигает и не ломается.

   ПОЧЕМУ ТАК ПО ТЕХНИКЕ.
   · Анимируются только transform и opacity — всё на композиторе, кадр не ждёт
     главного потока, который в момент ухода со страницы как раз занят.
   · Оба слоя position:fixed и pointer-events:none: вёрстку не двигают, кнопку
     не блокируют — можно нажать что-то ещё.
   · Свет поверх кнопки — отдельный слой в <body>, а не ::before/::after самой
     кнопки: псевдоэлементы у кнопок продукта уже заняты (счёт, рельсы, блики), а
     разметку экранов перерисовывают через innerHTML.
   · Гашение — отдельным классом .out поверх .on: снятие .on сбросило бы
     анимации, и нить на глазах схлопнулась бы в ноль, а не растаяла.
   · Цвета — свои токены, как у pull-refresh: файл подключён к четырём страницам с
     разными наборами переменных. Светлая тема — это СНЯТЫЙ data-theme (ловушка,
     на ней уже горели), поэтому база :root — светлая, тёмная поверх неё.
     Контраст нити к фону не ниже 3:1 (WCAG 1.4.11): в тёмной сирень #C79EE8 —
     8,5:1, шампань #F3D9AC — 13,7:1; в светлой #8E5FD6 и #B07A35 — 3,8 и 3,2 к
     серебру #EDEFF4 и 3,6 и 4,3 к тёмной хромовой плите шапки. */

:root{
  --nf-a:#8E5FD6;                       /* сирень, светлая тема — темнее ради контраста */
  --nf-b:#B07A35;                       /* металл */
  --nf-track:rgba(23,27,38,.12);
  --nf-rim:rgba(142,95,214,.9);
  --nf-halo:0 0 0 3px rgba(142,95,214,.2),0 0 18px 2px rgba(142,95,214,.32);
  --nf-wash:rgba(142,95,214,.16);
  --nf-wash-blend:normal;
  --nf-ring:rgba(142,95,214,.65);
  --nf-glint:rgba(255,255,255,.75);
  --nf-beat:500ms;                      /* доля: 120 BPM */
}
/* Тёмная тема: свет СКЛАДЫВАЕТСЯ с кнопкой (screen) — золото светлеет, тёмная плитка
   наливается сиренью, как от софита; затемнить что-то screen не может в принципе. В
   светлой так нельзя: на белой плитке screen невидим, там обычная сиреневая вуаль.
   Сила света .22 — по контрасту: тёмный заголовок золотой кнопки в самой яркой точке
   пятна держит около 5:1 к фону (при .3 было 4,2:1 — ниже нормы 4,5). */
html[data-theme="dark"]{
  --nf-a:#C79EE8;                       /* сиреневая каёмка знака */
  --nf-b:#F3D9AC;                       /* шампань */
  --nf-track:rgba(199,158,232,.2);
  --nf-rim:rgba(214,182,242,.95);
  --nf-halo:0 0 0 3px rgba(199,158,232,.3),0 0 22px 3px rgba(199,158,232,.5);
  --nf-wash:rgba(222,196,250,.22);
  --nf-wash-blend:screen;
  --nf-ring:rgba(244,232,255,.9);
  --nf-glint:rgba(255,255,255,.7);
}

/* ---------- НИТЬ ---------------------------------------------------------- */
.nf-thread{position:fixed;left:0;right:0;top:env(safe-area-inset-top,0px);height:3px;
  z-index:2147483600;display:flex;gap:3px;padding:0 3px;box-sizing:border-box;
  pointer-events:none;opacity:0;transition:opacity 60ms linear;contain:strict}
.nf-thread.on{opacity:1}
/* Палец уже получил отклик на самой кнопке — общую нить показываем не сразу, а с
   200 мс: переход быстрее этого (кэш, bfcache) обходится без мелькания полосы
   (Material 3: «до 200 мс — без индикатора»; NN/g: мелькнувший индикатор тревожит).
   Счёт при этом идёт с момента тапа: нить появляется уже с полной первой долей.
   Переход из кода, без пальца, — нить сразу, другого отклика у него нет. */
.nf-thread.on.nf-late{transition-delay:200ms}
.nf-thread.on.out{opacity:0;transition-duration:240ms;transition-delay:0s}
.nf-thread>i{flex:1 1 0;position:relative;overflow:hidden;border-radius:2px;background:var(--nf-track)}
/* Один градиент на всю ширину, порезанный на три отрезка: каждому своя треть. */
.nf-thread>i>b{position:absolute;inset:0;transform:scaleX(0);transform-origin:0 50%;
  background:linear-gradient(90deg,var(--nf-a),var(--nf-b));background-size:300% 100%}
.nf-thread>i:nth-child(2)>b{background-position:50% 0}
.nf-thread>i:nth-child(3)>b{background-position:100% 0}
/* РАЗ: сразу. ДВА: через долю. ТРИ: через две доли — и держится.
   --nf-t0 — сколько счёта уже прошло до появления нити. На новой странице, которая
   ещё грузит содержимое, нить продолжает ТОТ ЖЕ счёт с того же места (nav-feel.js
   кладёт сюда время с тапа): отрицательная задержка ставит анимации в середину. */
.nf-thread{--nf-t0:0ms}
.nf-thread.on>i:nth-child(1)>b{animation:nf-seg 200ms cubic-bezier(.2,0,.2,1) calc(0ms - var(--nf-t0)) forwards}
.nf-thread.on>i:nth-child(2)>b{animation:nf-seg 320ms cubic-bezier(.2,0,.2,1) calc(var(--nf-beat) - var(--nf-t0)) forwards}
/* ФЕРМАТА. 8 с к 95% по экспоненте (половина — за первую секунду доли, 84% — к
   третьей): движение замедляется, но не останавливается и до конца не доходит,
   пока страница не придёт. linear() — точки кривой 1−e^(−t/1,4 с); строка выше —
   откат для браузеров без linear(). */
.nf-thread.on>i:nth-child(3)>b{animation:nf-hold 8000ms cubic-bezier(.1,.7,.3,1) calc(var(--nf-beat) * 2 - var(--nf-t0)) forwards;
  animation-timing-function:linear(0, .108 2%, .249 5%, .368 8%, .498 12%, .624 17%, .734 23%,
    .823 30%, .901 40%, .952 52%, .98 66%, .994 82%, 1)}
@keyframes nf-seg{to{transform:scaleX(1)}}
@keyframes nf-hold{to{transform:scaleX(.95)}}
/* ДОЕХАЛИ. Содержимое новой страницы на месте — нить добегает до конца с
   УСКОРЕНИЕМ и тает. Ускорение к концу — единственный ход полосы, который
   воспринимается быстрее ровного (Harrison и др., UIST 2007). Начальное
   положение nav-feel.js переписывает в style.transform с текущего кадра,
   иначе анимация стартовала бы с нуля. */
.nf-thread.on.nf-done>i>b{animation:nf-seg 220ms cubic-bezier(.5,0,.9,.55) forwards}
.nf-thread.on.nf-done::after{animation:none;opacity:0}
/* Блик по нити, пока держится третья доля: видно, что не зависло. Бежит НАЗАД,
   справа налево, — так полоса воспринимается быстрее (Harrison, Yeo, Hudson,
   CHI 2010: «рёбра», бегущие против хода прогресса). */
.nf-thread::after{content:"";position:absolute;top:0;bottom:0;left:0;width:72px;opacity:0;
  background:linear-gradient(90deg,transparent,var(--nf-glint),transparent)}
.nf-thread.on::after{animation:nf-glint 1400ms cubic-bezier(.45,0,.55,1) calc(var(--nf-beat) * 2 + 200ms - var(--nf-t0)) infinite}
@keyframes nf-glint{
  0%{transform:translateX(100vw);opacity:0}
  15%{opacity:.9}
  85%{opacity:.9}
  100%{transform:translateX(-72px);opacity:0}
}

/* ---------- СЛЕД НА КНОПКЕ ------------------------------------------------ */
/* Размер, место и скругление ставит nav-feel.js по самой кнопке. */
.nf-trace{position:fixed;z-index:2147483500;pointer-events:none;overflow:hidden;
  box-sizing:border-box;opacity:0;transition:opacity 70ms linear;
  box-shadow:inset 0 0 0 2px var(--nf-rim),var(--nf-halo)}
.nf-trace.on{opacity:1}
.nf-trace.on.out{opacity:0;transition-duration:240ms}
/* Свет из точки касания — пятно софита, а не туман: ярче всего под пальцем и сходит
   на нет к дальнему краю. Залить кнопку целиком значит выбелить её, а выбеленная
   кнопка читается как «выключена» — ровно обратный смысл. */
.nf-trace>s{position:absolute;left:var(--nf-x);top:var(--nf-y);width:var(--nf-d);height:var(--nf-d);
  margin:calc(var(--nf-d) / -2) 0 0 calc(var(--nf-d) / -2);border-radius:50%;
  background:radial-gradient(closest-side,var(--nf-wash) 0,var(--nf-wash) 18%,transparent 72%);
  mix-blend-mode:var(--nf-wash-blend);transform:scale(.06)}
.nf-trace.on>s{animation:nf-wash calc(var(--nf-beat) * 2) cubic-bezier(.16,1,.3,1) forwards}
@keyframes nf-wash{to{transform:scale(1)}}
/* Три волны — три доли. Каждая уходит от пальца к краям и гаснет. */
.nf-trace>i{position:absolute;left:var(--nf-x);top:var(--nf-y);width:var(--nf-d);height:var(--nf-d);
  margin:calc(var(--nf-d) / -2) 0 0 calc(var(--nf-d) / -2);border-radius:50%;box-sizing:border-box;
  border:2px solid var(--nf-ring);transform:scale(.08);opacity:0}
.nf-trace.on>i{animation:nf-ring 720ms cubic-bezier(.16,1,.3,1) both}
.nf-trace.on>i:nth-of-type(2){animation-delay:var(--nf-beat)}
.nf-trace.on>i:nth-of-type(3){animation-delay:calc(var(--nf-beat) * 2)}
@keyframes nf-ring{
  0%{transform:scale(.08);opacity:0}
  12%{opacity:1}
  100%{transform:scale(1);opacity:0}
}
/* Пружина ещё отыгрывает отпускание (spring.css): слой повторяет её, иначе
   ободок полсекунды не совпадает с краем кнопки. Ход пружины nav-feel.js
   переписывает в --spring-press самого слоя, с кнопки. */
.nf-trace.nf-spring{animation:sp-press-out var(--spring-dur,420ms) var(--spring,ease-out)}

/* ---------- ПРИХОД НОВОЙ СТРАНИЦЫ ---------------------------------------- */
/* Кросс-документный View Transition: обе страницы подключают этот файл, значит
   обе согласны. Старая уходит быстрее, чем входит новая (выход около 60–70%
   входа), новая проявляется и садится на 12 px вверх — «приехала», а не
   «щёлкнула». Возврат назад (свайп, кнопка «назад») переход не играет: у iOS и
   Android на жест своя анимация, двойная не нужна — отменяет nav-feel.js в
   pageswap. */
@view-transition{navigation:auto}
::view-transition-old(root){animation:nf-vt-out 180ms cubic-bezier(.4,0,1,1) both}
::view-transition-new(root){animation:nf-vt-in 280ms cubic-bezier(.16,1,.3,1) both}
@keyframes nf-vt-out{to{opacity:0}}
@keyframes nf-vt-in{from{opacity:0;transform:translateY(12px)}}
/* Нить — отдельным слоем перехода и НЕ тает вместе со старой страницей: держится
   почти весь переход и уходит в последние 80 мс. Новая страница поднимает свою
   нить (продолжение счёта) только когда отработает её скрипт — через кадр-другой
   после первой отрисовки, — и без этой выдержки нить на мгновение исчезала бы. */
.nf-thread{view-transition-name:nf-thread}
::view-transition-group(nf-thread){animation-duration:280ms}
::view-transition-old(nf-thread){animation:nf-vt-hold 280ms linear both}
::view-transition-new(nf-thread){animation:none}
@keyframes nf-vt-hold{0%,72%{opacity:1}100%{opacity:0}}

/* ---------- СЧЁТ НА ЗАГЛУШКЕ НОВОЙ СТРАНИЦЫ ----------------------------- */
/* Замер на проде 09.10: сама страница learn.html приходит быстро, а основное
   ожидание — уже на ней: 1–1,5 с она стоит в заглушке «…» (role=status), пока
   каталог или песня едут из Сеула. Серое многоточие в 14 px на пустом экране
   читалось как «ничего не происходит». Теперь там тот же счёт вступления, что у
   нити и следа: три точки загораются на РАЗ-ДВА-ТРИ (доля --nf-beat), третья —
   шампанью — держится и дышит, пока не пришли данные. Не крутилка и не надпись.
   Только начальная заглушка экрана: у прочих .ls-empty (тексты «пусто», «сбой»)
   нет role=status с подписью «…», их правило не трогает. */
#lsRoot>.ls-empty[role="status"][aria-label="…"]{font-size:0;color:transparent;min-height:42vh;
  display:grid;place-items:center;padding:0}
#lsRoot>.ls-empty[role="status"][aria-label="…"]::before{content:"";width:10px;height:10px;border-radius:50%;
  transform:translateX(-20px);background:var(--nf-track);
  box-shadow:20px 0 0 var(--nf-track),40px 0 0 var(--nf-track);
  animation:nf-count calc(var(--nf-beat) * 3) steps(1,end) forwards,
            nf-breathe 1400ms ease-in-out calc(var(--nf-beat) * 3) infinite}
@keyframes nf-count{
  0%{background:var(--nf-a);box-shadow:20px 0 0 var(--nf-track),40px 0 0 var(--nf-track)}
  33.34%{background:var(--nf-a);box-shadow:20px 0 0 var(--nf-a),40px 0 0 var(--nf-track)}
  66.67%,100%{background:var(--nf-a);box-shadow:20px 0 0 var(--nf-a),40px 0 0 var(--nf-b)}
}
@keyframes nf-breathe{50%{opacity:.55}}

/* ---------- УВАЖЕНИЕ К НАСТРОЙКАМ --------------------------------------- */
/* Меньше движения: отклик остаётся (это не украшение), уходит качание. Нить
   встаёт ступенями по долям, без бега и блика; кнопка просто загорается, без
   волн; новая страница проявляется без сдвига. */
@media (prefers-reduced-motion:reduce){
  .nf-thread.on>i:nth-child(1)>b{animation:nf-seg 1ms calc(0ms - var(--nf-t0)) forwards}
  .nf-thread.on>i:nth-child(2)>b{animation:nf-seg 1ms calc(var(--nf-beat) - var(--nf-t0)) forwards}
  .nf-thread.on>i:nth-child(3)>b{animation:nf-step 1ms calc(var(--nf-beat) * 2 - var(--nf-t0)) forwards}
  .nf-thread.on.nf-done>i>b{animation:nf-seg 1ms forwards}
  .nf-thread.on::after{animation:none}
  .nf-trace.on>s{animation:none;transform:none}
  .nf-trace.on>i{animation:none}
  .nf-trace.nf-spring{animation:none}
  #lsRoot>.ls-empty[role="status"][aria-label="…"]::before{animation:nf-count calc(var(--nf-beat) * 3) steps(1,end) forwards}
  ::view-transition-old(root){animation:nf-vt-out 120ms linear both}
  ::view-transition-new(root){animation:nf-vt-fade 160ms linear both}
}
@keyframes nf-step{to{transform:scaleX(.5)}}
@keyframes nf-vt-fade{from{opacity:0}}
/* Режим высокой контрастности: системные цвета вместо бренда. */
@media (forced-colors:active){
  .nf-thread>i>b{background:Highlight}
  .nf-trace{box-shadow:inset 0 0 0 2px Highlight}
  .nf-trace>s,.nf-trace>i,.nf-thread::after{display:none}
}
