Основи

Рух

У продукті вже рухалося 46 файлів із 88, і жодне число там ніхто не обирав: 12 різних тривалостей і крива ease на 817 значеннях з 818 - тобто значення, яке дістається, коли криву не називав ніхто. Цей етап руху не додає, він його називає: три роботи, три тривалості, три криві. Усе на сторінці живе і читає токени з tokens.css.

Три роботи, і четвертої не буває

Спершу називається робота, потім береться анімація. Момент, для якого жодна з трьох не називається, в інвентар не потрапляє: рух без роботи коштує увагу на кожному відкритті екрана, а виглядає як старанність.

ЗВ'ЯЗОК - «звідки це взялось?» · --dur-slow + --ease-enter + --move-md
Панель виходить з-під кнопки, яка її відкрила, і повертається туди ж. Напрямок і є повідомлення: якби вона приїхала з іншого краю, людина не зв'язала б її з натиском.
СТАТУС - «воно ще працює?» · рівний цикл, без прискорення

Швидкість, що не змінюється, каже «живе» і нічого не обіцяє. Цикл, який прискорюється, читається як «майже готово» - обіцянка, якої продукт дати не може: скільки триває зовнішня перевірка, він не знає. Береться лише там, де процес довший за ~300 мс: миготіння на швидкій відповіді гірше за нерухомість.

У цього циклу ще немає токена, і це відкрите питання кроку 2

Три тривалості описують ПЕРЕХІД, а не цикл: пульс на 150 або 330 мс це миготіння. Сьогодні skeleton.css несе 1.1s, а два обертуни .9s - літералами, що контракт називає дефектом. Рішення про четвертий токен за власником, і поки воно не ухвалене, тут стоїть те саме число, що в системі.
ВІДПОВІДЬ - «мене почули?» · --dur-fast + --move-sm

Відгук, повільніший за ~150 мс, уже не відповідь: він читається як роздум інтерфейсу. Тому --dur-fast стоїть на верхньому краї цього вікна, а не за ним.

Тривалості: три, і кожна це РОБОТА, а не розмір

Три об'єкти рушають одночасно від одного натиску. Порівняння тут не оформлення, а єдиний спосіб побачити різницю: окремо жодна з трьох не читається ні швидкою, ні повільною.

натисни - і всі три поїдуть разом
--dur-fast · 150ms · відповідь
150
--dur-base · 220ms · перехід усередині компонента
220
--dur-slow · 330ms · поява елемента
330

ТокенЗначенняРоботаЗвідки число
--dur-fast150msвідповідьвласна більшість продукту, взята, а не замінена: 92 зі 133 оголошень і 26 083 з 42 303 обчислених значень уже стоять тут. .14s і .12s згортаються в неї
--dur-base220msперехід у компонентітеж наявне значення (6 оголошень), але робота змінюється: сьогодні це ціна ПОЯВИ (шухляда, шит, тост), відсьогодні поява піднімається на щабель вище
--dur-slow330msпоява елементаєдине число, якого в переписі не було, і це знахідка, а не виняток: продукт живе в смузі 120-280 мс, де немає щабля, що читається як прибуття. Ніщо не сповільнюється - одинадцять організмів зі станом open сьогодні не мають переходу взагалі. 330 це 1.5x від базової, тобто те саме відношення, у якому базова стоїть до швидкої
Криві: три, і кожна розв'язана, а не пригадана

Тривалість у всіх трьох однакова, тож видно рівно форму. Референс кроку 1 назвав родину (power_.out) і не дав чотирьох чисел - тому числа підігнані до функції родини приладом tools/ease-fit.mjs. Пригадана з таблиці крива це вигадане число, лише на одну таблицю далі.

натисни - і всі три поїдуть разом, на --dur-slow
--ease-standard · більшість переходів
st
--ease-enter · те, що ПРИБУВАЄ
en
--ease-exit · те, що ЙДЕ
ex

ТокенЗначенняДе вживаєтьсяПохибка підгонки
--ease-standardcubic-bezier(.48, .04, .52, .96)стан змінюється, і жоден кінець руху не важливіший за іншийpower1.inOut, 0.36%
--ease-entercubic-bezier(.33, 1, .67, 1)поява: енергія витрачена на старті, тож око приходить, коли елемент уже осідає і вже читаєтьсяpower2.out, 0.19%
--ease-exitcubic-bezier(.33, 0, .67, 0)зникнення: рушає повільно, щоб було видно ЩО йде, далі швидко, бо дивитись до кінця нема навіщодзеркало, 0.19%

Пружин і відскоків немає, і це рішення з джерелом

back.out(1.7) (перельот) і elastic зняті за назвою: voice.md, Принцип 5, забороняє святкування, а таблиця тону ставить фазу 8 - очікування після оплати, дно емоційної кривої - на цільову емоцію нуль. Перельот це маленьке святкування, доставлене рівно туди, де людина його найменше хоче.
Дистанції: дві, і стеля в них це відношення
натисни - обидва підіймуться одночасно
--move-sm · 2px
sm
--move-md · 10px
md

--move-sm 2px - підйом під курсором; у системі вже 10 оголошень translateY(-2px) і два -1px, що згортаються сюди. --move-md 10px - шлях елемента, що прибуває: тост уже стартує з translateY(10px). Рух, довший за півтори висоти елемента, перестає читатись як зв'язок і починає читатись як подорож.

Старт за екраном це НЕ ця дистанція

translateY(100%) і translateX(-100%) у шухляди та шита це відношення до власного розміру елемента. Записане довжиною, воно зламається тієї миті, коли елемент змінить розмір.
Поверхні: те, чого звичайний перехід не бачить узагалі

Головна знахідка кроку 4, і вона більша за будь-яке число з переписом. Кожна поверхня цього продукту з'являється перемиканням display. display - дискретна властивість: у неї немає середини, тож інтерполювати нема чого, і перехід не бачить її взагалі. Компонент міг чесно читати --dur-slow, пройти перекличку кроку 3 і все одно виникати в одному кадрі.

ЧислоЩо воно означає
20поверхонь у системі, які з'являються перемиканням display. Рахує tools/motion.mjs --surfaces, з двох боків: клас може ДАВАТИ видимість (дев'ятнадцять) або ЗАБИРАТИ її (банер cookie - і саме його прилад не бачив, поки читав один бік)
+1панель спліт-в'ю тренера, яку перемикає javascript через атрибут hidden. Прилад читає css і цю поверхню не бачить; вона порахована рукою, і це сказано, щоб «20» ніколи не читалось як «усі»
0@starting-style і transition-behavior: allow-discrete у системі до цього кроку
20 з 20несуть пару після кроку 4. Перевірку перевірено з іншого боку: пару знято з галочки меню, лічильник почервонів на ній одній - перевірка, яка ніколи не падала, не показана робочою
живий приклад: та сама техніка, ті самі токени

Поверхнязатемнення проявляється, картка піднімається на --move-md

Обидва елементи стоять на display: none у спокої, як у продукті. Приберіть @starting-style - і вони почнуть виникати, не змінивши більше нічого.

Тиха деградація, і межа виміру названа

Браузер, який не знає цього правила, викидає його і показує поверхню миттєво - тобто рівно так, як показував учора. Заміряно цієї сесії: Chrome 151 розбирає @starting-style, allow-discrete, @view-transition і view-transition-name. Safari і Firefox у цій сесії не міряні, і це записано, а не припущено.
Стани екрана: рух пишеться з таблиці тону

Спершу структурна відповідь, бо вона змінює саме питання. У цьому продукті стан екрана - це ОКРЕМИЙ ДОКУМЕНТ: goal-empty.html, goal-error.html, goal-loading.html. У сірому корпусі таких 16 порожніх, 13 помилок і 16 завантажень; у кольоровому 14, 11 і 13. Жоден javascript не перемикає стан усередині сторінки. Тобто «перехід між станами екрана» звичайним переходом не робиться взагалі - це навігація, і вона лежить у секції нижче. Усередині документа станів рівно два: панель спліт-в'ю тренера і повідомлення валідації під полем.

Стан (voice.md)НосійТривалістьКриваАмплітудаНапрямокЗвірка
Порожній
чому порожньо + дія-вихід
.empty / .emptybox, і #clDetailEmpty у спліті--dur-base--ease-enter–без зсувусходиться. Зсув знизу читається як «щось вантажиться», а нічого не вантажиться - тому проявлення без руху
Помилка
що сталося + що робити
.pm-e під полем; .wf-toast.error--dur-fast · --dur-base--ease-standard--move-smвгору, 2pxбуло розійшлось, виправлено. Тост помилки приходив тим самим розмахом, що й «Адресу збережено»
Завантаження
мовчить або каже коротко
.skpulse; .auth-spin / .co-spin--dur-cycle--ease-cycle–рівний повторсходиться. «Рівний повтор» і фізика обертання вимагають того самого - linear
Успіх
факт + наступний крок, без святкування
.wf-toast (ok)--dur-slow вхід · --dur-base вихід--ease-enter / --ease-exit--move-mdзнизу вгорусходиться, без пружини й відскоку. Вихід укоротився з іншої причини - див. нижче
OOS / наявністьavailability.css, restock-note.css––––руху немає навмисно. Це позначки, а не події: вони не приходять і не йдуть
Небезпечна дія.ceov + діалог підтвердження--dur-slow--ease-enter / --ease-exit--move-mdпідйом у центрсходиться, і однаковість тут навмисна. Принцип 4 - спокійно й упевнено: діалог видалення приходить так само, як будь-який інший, бо драма в русі це тиск

Одна анімація виявилась обрізаною числом із файлу, якого етап не чіпає

wireframes/_nav.js:1260 знімає вузол тоста через 250мс після класу .out. Крок 3 поставив вихід на --dur-slow, тобто 330 - тост зникав за 80мс до кінця власного згасання, і жоден перепис css цього побачити не міг. Сірий корпус заморожений, тож рухається число всередині системи: 220 вміщається в 250. Це єдине місце етапу, де число ЗЗОВНІ системи вирішило число всередині неї.

І одна розбіжність, яка виявилась не в анімації, а в тексті

microcopy.md тримає рядок «Вітаємо у Stack! Ви увійшли 🎉». У продукті стоїть wfToast('ok', 'Ви увійшли') - без вітання і без емодзі. Редакція продукту правильна за тоном (Принцип 4: без святкування), редакція інвентаря - ні, і жоден продуктовий рядок не існує у двох редакціях. Знайдено звіркою тону, полагоджено в microcopy.md і пересобрано в voice.html тим самим кроком.
Патерни: один, і власного переходу він не отримує

Патернів у системі рівно один - action-row.css, ряд із двох дій, який стоїть на 70 сірих екранах під 53 різними іменами контейнера. Він не отримує власного переходу композиції, і це відповідь, а не пропуск.

Чому. Перехід патерна відповідає на «що з'являється, звідки і в якому порядку». Ряд дій ніколи не з'являється сам: він приходить разом із блоком, який його тримає - підвалом діалогу, ногою картки, порожнім станом, - і в того блока вже є своя робота ЗВ'ЯЗОК. Дати ряду власний вихід означало б анімувати момент, для якого не називається жодна з трьох робіт. Рух, який на ньому видно, приходить із button.css: стан в одному файлі, рух в іншому - той самий клас, що icon.css на кроці 3.

Зате спліт-в'ю - найчистіший ЗВ'ЯЗОК у продукті

Деталь клієнта приходить З БОКУ СПИСКУ, бо саме там щойно були око й палець тренера: --move-md ліворуч, --dur-base, і лише всередині двох воріт спліта. І без проявлення - крок 10.5 переводить фокус на ім'я клієнта тієї ж миті, тож панель непрозора кожен кадр руху: скрінрідер не має права читати те, чого око ще не бачить. Рух не приходить пізніше за фокус.
Перехід між екранами: розвилка, і рішення власника

Кожен екран продукту - окремий html-документ. «Деталь виїжджає зі списку» звичайним transition не робиться взагалі: сторінка вивантажується разом зі своїми стилями. Три чесні виходи, і мовчки не береться жоден.

ГілкаЩо цеРішення
Aне анімуємо міждокументні переходи; робота ЗВ'ЯЗОК лишається за тим, що відбувається всередині документарекомендація кроку 4, не взята
Bодин запис @view-transition { navigation: auto } у base.cssУХВАЛЕНО власником
Cокреме рішення під конкретний флоуне взята

Рекомендацією була A, і причина була не обережність, а власне правило пака: гілка B береться, «лише якщо в інвентарі є конкретна пара екранів з роботою ЗВ'ЯЗОК», а інвентар моментів такої пари не називає - він називає поверхні всередині документа і чотири кроки сесії. Власник обрав B; отже цей момент прийшов із РІШЕННЯ, а не з корпусу, і платить за це двома речами: власним рядком в інвентарі та критикою на кроці 6, якої пак вимагає для вигляду, якого раніше не було.

живий приклад: це справжня навігація, а не демо

Перейти на «Адаптив» Перейти на «Геометрію»

Сторінки стенда вантажать ту саму system/index.css, тож запис із base.css діє й тут. У браузері без підтримки обидва посилання просто перейдуть, як переходили.

Крок 6: гілка B купувала МЕХАНІЗМ, а значення успадковувала

Один запис купує кросфейд і більше нічого - криву й тривалість браузер підставляє свої. Зняте зсередини живої навігації в Chrome 151, ДО правки: -ua-view-transition-fade-out, -ua-view-transition-fade-in, -ua-view-transition-group-anim-root і -ua-mix-blend-mode-plus-lighter, усі 250мс, крива ease. 250 - це не 150, не 220 і не 330, а ease - рівно те значення, заради видалення якого етап існує: перепис рахує його на 817 із 818 функцій і називає «значенням, яке оголошення отримує, коли криву не назвали». Воно повернулося в повний зріст на моменті, який власник обрав особисто.

У кривої два написання, і перше зняття взяло не те

CSS-анімація несе linear на ЕФЕКТІ (це дефолт) і справжню animation-timing-function на КОЖНОМУ кадрі. Перший запис у лог критики сказав «linear» - правдиве зняття не тієї половини. Рендериться кадрова, тобто ease. Виправлено в логу, а не тихо, і motion.mjs --view тепер друкує обидва написання поруч.
Псевдоелементбулосталочому саме цей токен
::view-transition-old(root)250мс ease--dur-slow · --ease-exitте, що ПІШЛО. Та сама читка напрямку, що в двадцяти поверхнях кроку 4
::view-transition-new(root)250мс ease--dur-slow · --ease-enterте, що ПРИЙШЛО. Цілий документ - це ПОЯВА, а поява бере верхню сходинку тих самих сходів
::view-transition-group(root)250мс ease--dur-slow · --ease-standardморфить КОРОБКУ знімка, а не прозорість; на двох однакових документах морфити нічого, тож крива симетрична

330 замість 250 - це на 80мс повільніше за дефолт браузера, і це сказано вголос: альтернатива - другі сходи заради одного моменту.

Перепис цього знайти НЕ МІГ, і це дорожча половина знахідки

Кожне інше питання motion.mjs ставиться ЕЛЕМЕНТУ. Ці чотири анімації живуть на дереві псевдоелементів, яке браузер будує на початок навігації і руйнує на її кінець: у документі в спокої його немає, тож querySelectorAll('*') не дістає до нього принципово, а не випадково. Зелений лічильник не міг почервоніти - він міг лише мовчати. Питає режим motion.mjs --view: він ставить слухача pagereveal ДО першого рядка скриптів вхідного документа, чекає ready переходу і читає document.getAnimations().

І це фальсифіковано, перш ніж у це повірили

Із закоментованим перевизначенням той самий прогін друкує п'ять рядків ПОЗА РЕЄСТРОМ на 250мс ease. Перевірка кривої навмисне суворіша за «чи є таке написання в таблиці», бо linear у таблиці Є - це --ease-cycle, - і сама лише звірка з реєстром пропустила б дефолт. Кожен псевдоелемент звіряється з кривою, якої вимагає його РОЛЬ. Плюс підміна токена: --dur-slow: 7.77s -> усі п'ять поїхали на 7770мс, тобто читають токен, а не літерал, що збігся.

Що перехід НЕСЕ: нічого, крім самої сторінки

view-transition-name має 0 оголошень у дереві, тож перетікає один знімок сторінки в інший. Спадкоємність елемента (фото картки стає фото картки товару) тут НЕ береться, і причина - обмеження, а не смак: ім'я мусить бути унікальним у документі, а лістинг малює 12 карток, і 12 дублікатів змушують Chrome пропустити перехід ЦІЛКОМ, а не наполовину. Унікальні поіменні імена не можуть прийти ані з файлу компонента, ані з файлу екрана, бо етап 11 забороняє там рух. Це замовлення на систему, і воно в backlog.md.

Токен-оверрайд сюди не дістає, і це закрито поіменно

Кросфейд браузера - це анімація, а не перехід, тож перевизначення --dur-* його не бачить. У base.css стоїть власний блок prefers-reduced-motion, який гасить ::view-transition-old/new/group поіменно. Навігація не чіпається: наступний екран приходить, зникає лише рух - стан не має права зникати разом із ним.

Рух на точці адаптиву - НІ, і виняток теж НІ

Дефолт пака - не анімувати перебудову сітки: точка спрацьовує, коли людина тягне край вікна і на вміст не дивиться. Пак дозволяє виняток для поверхні, якої на вузькій ширині не існувало - у нас це рейка фільтрів .frail, рейка категорій .hrail і права панель спліта. Виняток не взято, і причина сильніша за дефолт: усі три приходять ЛИШЕ через медіазапит, тобто від зміни розміру вікна, а не від дії людини на них. Анімувати нема чого повідомляти.
Ціна кадру

Дешево анімуються рівно дві властивості - transform і opacity: браузер віддає їх композитору і не чіпає ані розкладку, ані малювання. Усе інше або змушує перерахувати РОЗКЛАДКУ щокадру, або перемалювати площину.

БулоСталоЧому
.sw i · transition: left, left: 21px / 3pxtransform: translateX(var(--sw-travel))розкладка щокадру -> композитор. І хід став відношенням: 44 - 20 - 3*2 = 18, тобто рівно 21 - 3 - два числа були одним фактом, записаним двічі. Заміряно: 21 -> 3, хід -18, до пікселя те саме
.co-sw::after · transition: lefttransform: translateX(var(--cosw-travel))38 - 18 - 2*2 = 16, тобто 18 - 2. Заміряно на checkout.html: 2 -> 18, хід +16
.ck-tog i · transition: lefttransform: translateX(var(--ckt-travel))40 - 18 - 2*2 = 18 проти padding-box треку. Пікселями не доводиться: банер cookie стоїть на одній сторінці всього дерева - wireframes/system.html, у замороженому корпусі, - тож цей файл сьогодні не малює нічого. Доказ буде на етапі 12
.wfh-meta · transition: max-heightлишаєтьсяі це відповідь із виміром, а не виняток. Шапка sticky, тобто займає місце в потоці, і весь сенс згортання в тому, щоб вона стала нижчою - жоден transform місця не звільняє

Що коштує єдина розкладкова анімація, у числах самого браузера

listing.html, 2006 елементів, Performance.getMetrics: спокій 400мс - 0 layouts · згортання мета-бара - 25 layouts, 1.1мс layout, 1.3мс style · розгортання - 26 · той самий елемент на самій лише opacity - 1 layout, 0.1мс. І другий вимір, який знімає головний аргумент: мета-бар існує лише від 860px, тож слабкий телефон його не рендерить узагалі.

Дороге МАЛЮВАННЯ: 24 оголошення, а не 70. Перепис рахує 70, але 46 із них це color, background і border-color на ховерах - площини вони не навантажують. box-shadow і filter несуть 24 правила.

Списку з двадцяти карток, якого боїться метод, у цьому продукті НЕМАЄ

Перевірено тригер кожного з 24: усі до одного це :hover, :focus-visible або клас вибору .on / [aria-current]. Курсор один, фокус один, вибраний елемент у групі один - одночасно перемальовується один елемент, максимум два на перемиканні вибору. Двадцять карток разом не бувають.

Зате є місце, де одночасно рухається пів документа, і воно не в списку карток

Перемикач теми міняє [data-theme] на корені, тобто колір УСЬОГО документа одразу. Заміряно: listing.html - 2006 елементів, з них 463 з переходом кольору, зміна теми коштує 32.1мс перерахунку стилів в один момент; index.html - 459 і 21.7мс. Цього ніхто не замовляв: переходи писалися під ховер, а перетікання всієї сторінки - побічний ефект 463 правил. Той самий аргумент, яким прибрали transition: all.

Власник обрав придушити. theme.js вішає .uiv-theming на корінь, міняє атрибут і знімає клас через ДВА кадри анімації: перший колбек виконується ДО того, як зміну намальовано, тож зняти клас там означало б усе одно дати переходам стартувати. Під цим класом одне правило гасить переходи на * через !important - тобто рівно те, проти чого цей етап бореться всюди, і саме в цьому суть: воно мусить перебити кожен компонент на довжину одного кадру. Це ЗАСЛІНКА, а не страхувальна сітка: вона живе по 16мс за раз і нічого не ховає, бо аудит іде на сторінці, де ніхто нічого не перемикає.

Екранчерез uivTheme()повз заслінку
listing.html0 переходів у русі через кадр після перемикання390
index.html0263

Заміряно через продуктовий вхід, а не встановленням атрибута руками - перший замір робив саме так і тому не побачив жодної різниці. Права колонка це та сама перевірка, доведена з іншого боку: коли заслінки немає, число не нуль.

will-change: нуль

У всьому дереві його немає жодного разу, і це правильний стан: постійний will-change це витік пам'яті на графіці, а не оптимізація.

Прилад теж мав борг, і крок 4 його оплатив

opacity стояла В ОБОХ списках переписом - і як дороге малювання, і як дешева властивість. Поки продукт майже не користувався прозорістю, це нічого не коштувало; тієї миті, коли крок 4 поставив прозорість на двадцять поверхонь, лічильник «анімує дороге» стрибнув з 73 до 107, і кожен новий випадок був найдешевшою річчю в css. Таблиця ціни кадру на кроці 5 будувалась би на роздутому числі. Прозорість композиться, а не малюється.
Менше анімації - це обов'язок, а не опція

Механізм один: у блоці @media (prefers-reduced-motion: reduce) перевизначаються ті самі токени. Компонент, що читає var(), підкоряється сам - і той, який з'явиться на етапі 12, підкориться теж, нічого про це не знаючи. Саме заради цього тривалості стали токенами, а не звичкою.

перемикач цієї сторінки, а не продукту

Він ставить клас на корінь документа і перевизначає ті самі п'ять токенів - той самий механізм, що й медіазапит. У продукті такого перемикача немає і не буде: перемикач це системне налаштування людини, а для частини людей це не питання смаку, а нудота і мігрень.

Справжня перевірка робиться не цим перемикачем, а емуляцією prefers-reduced-motion: reduce у браузері, обчисленим стилем, на кроці 5. Знак один: більше за 1ms це дефект.

РішенняЧому саме так
1ms, а не 0sна нулі деякі браузери не шлють transitionend, і обробник, що чекає кінця руху, чекає вічно. Скорочення руху зламало б поведінку замість того, щоб зняти оздоблення
стан не зникаєелемент, який з'являється, з'являється і при reduce. Зникає ШЛЯХ, а не прибуття - тому дистанції йдуть у нуль, а display і opacity цей блок не чіпає
чому @media тут працює, а на етапі 10 не працювавтам запит ПИТАВ про змінну, а медіаумова обчислюється до каскаду кастомних властивостей - правило не спрацьовувало і помилки не давало. Тут запит нічого не питає: всередині нього змінна ПЕРЕВИЗНАЧАЄТЬСЯ. Питати і присвоювати це різні операції
сітки не буде - рішення власника на кроці 5аудит зелений БЕЗ неї: 5826 рухомих елементів, 0 лишились рухомими. Аргумент ЗА неї - код, який напишуть субагенти на етапі 12; аргумент ПРОТИ - докази цього ж етапу: сітка, яка вже стояла, коштувала чотирьох етапів цифр, і під нею перший прогін аудиту дав би 0 дефектів замість 96. Етап 12 закривається приладом: motion.mjs --reduce тепер гейт, і літерал від субагента зробить його червоним так само, як зробив тут
страхувальної сітки на * немаєна кроці 2 це речення було ХИБНИМ, і виправлене на кроці 5. Воно описувало намір, а не систему: base.css ніс @media (prefers-reduced-motion: reduce){ *{ transition: none !important } } ще з етапу 07, а !important на * б'є будь-яке оголошення в tokens.css. Під ним аудит кроку 5 був би зеленим на порожньому місці. І та сама сітка вписала хибне речення в запис кроку 3: нулі, які повертала емуляція, пояснили як «Chrome обнуляє переходи сам». Chrome такого не робить - це був власний рядок цього репозиторію, і tools/cdp.mjs казав про нього в коментарі з етапу 07. Знято на 11.5 ПЕРЕД першим аудитом, і саме це зробило аудит червоним. Прилад не має ховати дефект, який шукає - а твердження, що приладу нічого не заважає, питається в дерева, а не в наміру
чого оверрайд не дістаєтри записи, і крок 4 закрив два з них. Цикл @keyframes - при reduce його треба ЗАМІНИТИ статикою, а не прискорити (пульс на 1ms це миготіння, тобто гірше): три з трьох закриті у власних файлах. Літерал усередині компонента - у продукті нуль. Перехід між документами - закритий поіменно: кросфейд браузера це анімація, тож base.css має власний блок reduce, який гасить ::view-transition-old/new/group, не чіпаючи саму навігацію. Доказ у браузері - крок 5
токени циклу в цьому блоці НЕМАЄі це рішення, а не пропуск. Перехід на 1ms закінчується раніше, ніж його побачать, - це і значить «руху немає». Цикл на 1ms не закінчується взагалі: він повторюється тисячу разів на секунду
Інвентар моментів

Повний перепис і обидва корпуси - у docs/motion.md. Тут зведення з колонкою стану. Корпусів два, і вони не взаємозамінні: карта флоу ніколи не називає ховер кнопки, тож інвентар, зібраний по екранах, бачить ЗВ'ЯЗОК і СТАТУС повністю і не бачить ВІДПОВІДЬ майже зовсім.

МоментКорпусРоботаНосійРівеньТривалістьСтан є?Стан
Панель клієнта у спліт-в'юB1зв'язокcoach-clientsорганізмповільнатаккрок 4
Оверлей і вуаль під поверхнеюB1зв'язокoverlayорганізмповільнатаккрок 4
Шухляда меню · шит фільтрівB1зв'язокnav-drawer, filter-sheetорганізмповільнатаккрок 4
Діалоги: вхід, місто, відгук, каталогB1зв'язокauth-dialog, city-dialog, review-modal, cat-overlayорганізмповільнатаккрок 4
Мега-меню розкриваєтьсяB1зв'язокmega-menuорганізмповільнатаккрок 4
Тост приходить і йдеB1зв'язокtoastорганізмповільнатаккрок 4
Дев'ять станів завантаження з flows.mdB1статусskeleton, paginationатом, молекулациклтаккрок 4, і токен циклу відкритий
Ховер, натиск, фокус на 42 компонентахB2відповідькнопка, поле, чип, посилання, карткаатом → організмшвидкатаккрок 3
Серце на картці, 92 входженняB2відповідьfavouriteатомшвидкалише focusкрок 3
Поточна сторінка в таб-баріB2відповідьtabbarорганізмшвидкатаккрок 3
Іконка, що рухається без жодного стануB2жодноїiconатом-нізнімається на кроці 3
рядків інвентаря 85мають стан 59уже рухаються 45 мають стан і не рухаються 15ПОТРІБЕН СТАН 7

Рівень, названий уголос зараз: організм

34 компоненти, стан мають 24, рухаються 13. Одинадцять зі станом і без руху - це рівно поверхні. Дізнатись про це на кроці 3 було б удвічі дорожче, тому воно сказане тут.

ПОТРІБЕН СТАН - сім рядків, і рішення по них не тут

client-dialog, buy-bar, upsell, toolbar, product-grid, system-page і патерн action-row не несуть жодного інтерактивного стану. Стан, якого немає, це замовлення за п'ятіркою етапу 08, а не домальований клас: поки воно не розміщене, ці рядки анімації не отримують.
Референси характеру

Референс відповідає рівно на одне питання - характер - і лише на момент, уже названий в інвентарі. Тривалість не береться ніколи: чуже число несе чужий контекст. Через півроку питання «звідки взявся цей рух» мусить мати відповідь у браузері, а не в чиїйсь пам'яті - тому таблиця стоїть тут.

Джерело замінено, і заміна записана рядком

motionsites віддав free_prompts_remaining: 0 - три безкоштовні промпти акаунта витрачені раніше, а найрелевантніші результати виявились платними. Власник замінив джерело на greensock/gsap-skills, прочитане живим 21.08.2026. GSAP у продукт не їде і в залежності не заводиться: береться прийом, а не бібліотека, і напрямок перекладу назвало саме джерело - його CustomEase «can use cubic-bezier values (as used in CSS cubic-bezier())».
МоментРоботаДжерелоЩо взятоЩо це дає роботіЩо каже тон
Панель клієнта у спліт-в'юзв'язокскіл gsap-coreродина кривої power_.out; напрямок від краю рейки, а не від краю вікна; мала амплітуданапрямок каже, ЧИЙ це запис; крива віддає енергію на старті, тож око приходить у панель, коли вона вже осідає і вже читаєтьсязбігається. back.out(1.7) і elastic зняті за назвою: Принцип 5, без святкування
Перевірка соцпосилання тренерастатусgsap-core ("none") + gsap-performanceрівний цикл без прискорення; лише opacity; will-change не лишати постійношвидкість, що не змінюється, каже «живе» і нічого не обіцяє - а час зовнішньої перевірки продукт не знаєзбігається. Фаза 8 = емоція 0, не +2; цикл, що прискорюється, це обіцянка
Мега-менюзв'язок, композиціяgsap-core stagger + gsap-timelineчерговість from: "start" від тригера; amount, а не each; перекриття, щоб читалось як одна поверхняпорядок вказує назад на тригер; фіксований розкид тримає появу однаковою, скільки б категорій меню не неслонейтрально, тексту при меню немає. Bounce не береться, Принцип 4
--thorne.com, живий замірнічого не взято, лише число для звіркилідер бенчмарку тримає 2 тривалості і одну обрану криву на 95 елементах зі 110; ми тримали 12 і жодної обраної-