diff --git a/CLAUDE.md b/CLAUDE.md index ff36a75..9c37ae7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,12 +88,39 @@ src/controllers/ PropVfxC.ts - Day 6: завантажує дизайнерські quarks.art VFX-префаби (JSON з temp/, sparks/dust/debris) для хіту/знищення крейтів + WeaponHudC.ts - Day 8: статична юайка внизу-зліва — восьмикутна + плита (weaponHudBgSrc, ZombiePunk_Icon-Bottom.webp з + temp/) + іконка бити (weaponIconBatSrc, Tool_2.webp) + + хардкоджений текст "LVL 0" (нема системи рівнів + зброї в проєкті — суто візуал під референс). Іконка + завжди бита, не стежить за поточною екіпірованою + зброєю (pistol/bat) — окреме, свідомо не зроблене + рішення + ZombieInvasionHudC.ts - Day 8: топ-центр "ZOMBIE INVASION" — заголовок + + таймер-бар. Голова зомбі (zombieHeadIconSrc, + REF_1.png з temp/ — файл НАСПРАВДІ webp, не png, + розширення в джерелі помилкове) їде з + right:100% -> left:0% за TIMER_DURATION_S (120с) + через CSS transition (JS лише раз перемикає + значення); окремий .zombie-invasion__fill росте + 0%->100% width з right:0 в ТОЙ САМИЙ момент/тривалість + — їх ліва межа завжди математично збігається з + позицією голови, без пофреймової синхронізації. + Останні WARNING_FRACTION=0.05 (6с) — pulse+червоний + флеш через setTimeout+клас (CSS keyframes самі + крутяться в infinite, ніхто їх не зупиняє після + заповнення — свідомо, за проханням користувача). + Немає дизайнерського ассету під сам бар/трек — + чистий CSS pill, лише голова — реальна картинка ThreeC.ts/CameraC.ts - базовий сетап three.js/камери від SDK resources/meshes/ - ZombiePunk_Map.glb, ZombiePunk_Character.glb resources/images/ - resource_counter_bg.webp (Tool_15.webp з temp/, - дизайнерський плейт лічильника), images.ts - (icon_wood.webp/iconWoodSrc видалені — стали - осиротілими після заміни на resourceCounterBgSrc) + дизайнерський плейт лічильника), weapon_hud_bg.webp, + weapon_icon_bat.webp, zombie_head_icon.webp, + images.ts (icon_wood.webp/iconWoodSrc видалені — + стали осиротілими після заміни на resourceCounterBgSrc) +resources/fonts/ - PassionOne-Black.otf (з temp/) — див. "gameFont" + нижче resources/vfx/ - VFX_Lootable_Hit.json, VFX_Lootable_Destroy.json (quarks.art префаби, копії з temp/, реально використовуються з коду — див. PropVfxC) @@ -1159,3 +1186,323 @@ canvas-rect гарантовано збігається з тим, що бачи ресурс в різних сценах польоту — тримати спільну константу базового розміру (`export`), а не дублювати магічне число, інакше вони розходяться, як тут. + +## Day 8 (продовження): WeaponHudC, ZombieInvasionHudC, InstallBanner-рестайл, шрифт, ротація (2026-08-13) + +Усе нижче — **ще не закомічено** (тільки `4a22a9b`/`63609ee` в git; це +робота вже після другого коміту). `npm run export` вже прогонявся раз +успішно (з побічним авто-встановленням `three.quarks`, бо в `export.cjs` +своя мережева воркфлоу — див. окремий output_export_browser_en.html). + +### `WeaponHudC.ts`/`ZombieInvasionHudC.ts` — нові статичні/напівстатичні юайки +Реалізовані аналогічно `HudC` (окремий контролер на юайку, `init()` з +`TestSceneC`). Ассети: `ZombiePunk_Icon-Bottom.webp`+`Tool_2.webp` для +зброї, `REF_1.png` (**насправді webp**, розширення джерела — брехня, +перевірено по сирих байтах RIFF/WEBP заголовку) для голови зомбі. + +**ZombieInvasionHudC пройшов кілька раундів фідбеку**: +1. Спочатку `.zombie-invasion__bar` мав суцільний зелений `background` + (статично "повний" від t=0) — користувач: "WARNING_FRACTION чомусь + почався тільки коли шкала повністю заповнилась". Причина НЕ в + таймінгу (`114000мс з 120000мс` — точно останні 5%, перевірено), а в + тому, що шкала і так ЗАВЖДИ виглядала на 100% — різницю "майже" vs + "повна" було неможливо побачити. **Фікс**: реальна заливка + (`.zombie-invasion__track` з `overflow:hidden` + `.zombie-invasion__fill`, + росте `width:0%->100%` з `right:0`, стартує в ТОЙ САМИЙ тік з тією ж + тривалістю, що й голова — їхні межі математично завжди збігаються, без + пофреймової синхронізації). +2. Голова мала бути БІЛЬШОЮ за саму шкалу (`0.24 * width` проти + `0.15 * width` висоти бару) — але `.zombie-invasion__bar` мав + `overflow:hidden` для заливки, що обрізало б і голову. **Фікс**: голова + винесена сиблінгом `.zombie-invasion__track` (не її дитиною) — обрізає + тільки заливку/флеш, не голову. + +### InstallBanner (SDK-компонент) — свідомо НЕ переписаний, лише CSS-рестайл +Круглий портрет персонажа + кнопка "INSTALL" знизу — це **не наша** +юайка, а SDK `InstallBanner` (`node_modules/@hitplay/playable_template`, +підключений в `beforeResourcesLoadedCb.ts`) з реальним кліком +(`AnalyticsC.sendDownload()` -> `Template.end()`) і власною +конфіг-системою (`src/configUIParams/installBanner.ts`, `upload_file` ++ X/Y/scale-відсотки окремо для portrait/landscape — реальні значення це +**треті числа** в кожному `values: [min, max, default]`). Користувач +попросив "уніфікувати" з іншими юайками — явно спитав через +`AskUserQuestion`, чи переписувати логіку самому, чи тільки CSS — +**вибрав тільки CSS-рестайл** (безпечний варіант). Додано лише +адитивні властивості (`filter: drop-shadow`, `-webkit-text-stroke` на +`.bannerButtonText`) — нічого, що конфліктувало б з інжектованим SDK-стилем. + +**⚠️ Знахідка: purgecss мовчки вирізає `.bannerButton`, але не `.bannerLogo`/`.bannerButtonText`** +При білді (не тільки в dev) `purgecss` скановує лише `src/**/*.{js,ts,tsx}` ++ `**/*.html` на предмет "чи цей клас реально використовується" — а +`InstallBanner`'s класи призначаються в JS всередині ВЖЕ мінифікованого +SDK dist-бандла (не в нашому `src/`). Емпірично перевірено в реальному +білд-виводі (`export/index.html`): `.bannerLogo{filter:...}` і +`.bannerButtonText{...}` виживають, а `.bannerButton{filter:...}` — ні, +навіть як окреме правило. Ймовірна причина: мінифікатор пакета +де-дублював рядок `"bannerButton"` (4 похідні: сам + +`Holder`/`Img`/`Text`) на спільний префікс, тож він більше не існує +цілим рядком у бандлі — `"bannerLogo"` (лише 2 похідні) не досяг порогу +для такої оптимізації. Поза нашим контролем (сторонній пакет) — просто +не писати CSS-правила під `.bannerButton` окремо, вони будуть мертвим кодом. + +### `gameFont` нарешті став реальним шрифтом +`font-family: gameFont, sans-serif` використовувався в `main.css`/`ui.css` +з самого початку проєкту, але `gameFont` НІКОЛИ не був заведений через +`@font-face` — тихо відкатувався на дефолтний sans-serif усюди. Користувач: +"весь юай має використовувати шрифт PassionOne-Black, він у нас є в +temp". Додано `@font-face` в `main.css` (`src/resources/fonts/PassionOne-Black.otf`) +під іменем `gameFont` — усі існуючі посилання почали резолвитись сами, +без жодної іншої зміни. Технічний нюанс: для шрифтів (на відміну від +`images.ts`/`ConvertToBase64WhenRelease`-паттерна) НЕ треба нічого +особливого — `assetsInlineLimit: 100000000` в `vite.config.js` +(через `defineConfigTemplate`) вже інлайнить будь-який `url()` в CSS як +base64 і в dev, і в білді. Перевірено прямо в білд-виводі (не на око). + +### `--lvh`/`--lvw` замість сирого `vh`/`vw`/`vmin` — ротація/адаптивність +Користувач (Day 8 чекліст, "All UI elements must be adaptive to changing +device rotation"): розвернув екран — нічого не змінилось. **Знахідка**: +SDK має `OrientationC` (`node_modules/@hitplay/playable_template`), який +під лок-режимом орієнтації обгортає сторінку в +`transform: rotate(-90deg)...`, щоб контент лишався візуально портретним +навіть на фізично горизонтальному екрані. Сирі `vh`/`vw`/`vmin` +**ігнорують transform батьків** і завжди рахуються від фізичного +(неповернутого) вьюпорта — тож будь-який розмір у `vh` стає неправильним +щойно лок реально спрацює. `OrientationC` для цього тримає `--lvw`/`--lvh` +("логічні" версії, коректно стежать за поворотом; в Adaptive-режимі без +локу — просто дорівнюють фізичним vw/vh, тобто нічого не коштують) — +той самий підхід, що вже й в `InstallBanner.ts` (`--lvw` в коментарях +"physical vw doesn't follow the orientation lock"). Усі `--*-width` в +`ui.css` тепер `calc(var(--lvh, 100vh) * ratio)`. + +**⚠️ Важливо**: дефолт в `src/configUIParams/orientation.ts` — **"Adaptive"**, +НЕ лок. У цьому режимі `--lvh == vh` і фікс вище нічого не змінює +візуально прямо зараз — це радше "правильно про запас", коли/якщо +проєкт зрештою поставлять на Portrait Lock (майже неминуче для +вертикального плейбла). Коли користувач після цього фіксу все одно +сказав "нічого не змінилось, юайка так само зосереджена в центрі" — +попросив **медіа-правила** замість покладання на `--lvh`/лок-механізм. +**Фінал**: кожна з трьох юайок (`resource-hud`/`weapon-hud`/`zombie-invasion`) +тепер має власний `@media (orientation: landscape) { ... }` блок одразу +після portrait-правила, що перевизначає `top`/`left`/`right`/`bottom` і +`--*-width` — незалежно від `OrientationC`/локу, реагує на реальне +співвідношення сторін вьюпорта. Значення в landscape-блоках зараз лише +дублюють portrait як стартову точку — **ще НЕ підтверджені користувачем +візуально**, він вже почав підкручувати `.resource-hud`'s landscape +`--hud-width` (з `0.09` на `0.19` — бачив у diff, не сам робив). + +### Стан на кінець цієї сесії +- Working tree: `src/configUIParams/installBanner.ts` (можливо просто + line-ending, не досліджував — не моя зміна), `TestSceneC.ts`, + `main.css`, `ui.css`, `images.ts` — модифіковані; `WeaponHudC.ts`, + `ZombieInvasionHudC.ts`, `resources/fonts/`, три нові `.webp` — нові. + **Не закомічено.** +- `stats.html` — як завжди, build-артефакт, свідомо не в git. +- Наступний крок, якщо буде запит: підтвердити/підкрутити landscape + `@media`-значення разом з користувачем, коли він реально побачить це + на розгорнутому екрані. + +## Health bar для дестройбл-елементів (2026-08-13) + +Референс-скріншот: прямокутний хелз бар з бордером, синя заливка HP, +чорним — вже відняте попередніми ударами, білим — відняте саме поточним +ударом (з коротким затуханням у чорне), число нанесеного урону над баром. +Нехай 10 урону за удар, 100хп у цілого ящика. + +### `BreakablePropC` — перехід з дискретного лічильника хітів на HP +`HITS_PER_STAGE`/`hitsOnStage` повністю видалені. `BreakableProp` тепер +несе `currentHp`/`maxHp` (`MAX_HP = 100`, `export`-нутий, щоб `HealthBarC` +рахував той самий `0..1` дріб без дублювання числа) + `startStageNumber`/ +`totalPossibleStages` — реальні номери з `_SN` у назвах стейдж-нод (не +вигадані), з яких рахується, скільки HP пошкоджений з коробки крейт вже +має "по дефолту" (пошкоджені S2/S3-старт крейти стартують не з 100). +`hit()` тепер: відняти `HIT_DAMAGE`, повідомити `HealthBarC.onHit(...)`, +знищити на HP<=0 (раніше — "просунувся за останній стейдж"), інакше — +перевести `stageIndex` на `Math.floor((1 - currentHp/maxHp) * +totalPossibleStages)` (з відступом на `startStageNumber`), змаплений у +межі власного (можливо короткого) масиву стейджів. + +### `HealthBarC.ts` — новий контролер, world-tracking DOM-бари +Одна мапа `BreakableProp -> BarInstance`, живиться з `BreakablePropC.hit()` +через `onHit`/`onDestroyed` (аналогічний DI-паттерн до `ResourceC.setFlyTarget`). + +**Позиціювання**: `position:fixed` + сирі px з `canvasRect` (та ж техніка, +що й `HudC.getWorldAnchorPosition`, лише в протилежний бік — тепер +винесена в `src/utils/screenWorldProjection.ts`, обидва напрямки). Точка +прив'язки — реальний `Box3().setFromObject(prop.root)` (верх+центр X/Z), +не вгаданий офсет. + +**Три кольори — чистий CSS, без пофреймової JS-анімації**: `.health-bar__white` +й `.health-bar__blue` — два накладені div. На кожен хіт: білий миттєво +(`transition-duration: 0s`) стрибає на ПОПЕРЕДНІЙ `%`, форс-reflow через +`offsetWidth`, потім і білий (з `transition-delay`), і синій одразу +виставляються на ПОТОЧНИЙ `%` — синій доїжджає без затримки, білий +"зависає" на старому значенні й наздоганяє з лагом, показуючи чорний трек +під собою в проміжку. Ніякого `requestAnimationFrame`/tween — сама +CSS-transition timing і є ефектом. + +**Авто-приховування**: `idleTime` рахується щокадру, `>= HIDE_DELAY_S` (2с) +без нового хіта — `is-hiding` клас + `FADE_OUT_MS` (250мс) opacity-transition, +потім видалення з DOM. Скидається на 0 при кожному `onHit`. `onDestroyed` +видаляє бар негайно (порожній бар не має лишатись висіти). + +**Числа урону — кілька раундів фідбеку, фінал**: +- Спочатку плавали вгору з затуханням — користувач попросив натомість + різку появу непрозорого числа (`-webkit-text-stroke`, не text-shadow-глов) + з різким скейл-панчем до 1.5x + ротейтом і поверненням, і тільки ПОТІМ + затуханням (не рухом). +- Ротейт — послідовно звужувався: спершу довільний напрямок/кут, потім + користувач уточнив "тільки по годинниковій стрілці, до 2 години дня" + (тобто 0..60°, `--damage-rotate`, рандомізується в JS на кожен спавн, + читається в keyframe через `var()`). +- **Баг знайдений і виправлений**: затухання на спокої (force=0, у + контексті джойстика — не плутати з цим, тут йдеться про сам панч) + показувало прозору прогалину замість базового кольору, бо альфа + інтерполювалась до 0 — фікс: альфа фіксована, змінюється лише + RGB-канал (той самий принцип пізніше повторився і в джойстику, див. + нижче). +- Тривалість/розмір/швидкість анімації (920мс, +20% розмір шрифту, + expo-out крива "швидкий старт — повільний кінець") — прямі числові + правки за проханням користувача. + +### `src/css/health-bar.css` — новий файл +Слідує тому ж house convention, що й `ui.css` (одна `--health-bar-width` +змінна, `--lvh` не сирий `vh`) — **окремо зазначено користувачем не +чіпати**: файл (як і `ui.css`) свідомо не отримав landscape +`@media`-блоку і досі на `vh`, не `rem`/`%` — відомий, прийнятий +відступ від Day 8 чекліста, відкладений на потім. + +## PayZoneC: політ депозиту тепер з голови гравця, не з юайки (2026-08-13) + +Був: політ ресурсу в Pay Zone стартував з `HudC.getWorldAnchorPosition()` +(іконка лічильника). Користувач попросив: **з персонажа**, летить від +голови вгору й плавно спускається в **центр** зони (не з юайки взагалі). + +`PlayerC.getHeadWorldPosition()` — новий публічний метод, `object.position ++ (0, PLAYER_HEIGHT, 0)` — `PLAYER_HEIGHT` вже був реальним заміряним +розміром рига (`PLAYER_SIZE.y`), не вигаданим числом, тож верх голови +рахується без нової магічної константи. `PayZoneC.playDepositFlight` +тепер цілиться в геометричний центр `zoneBounds` (не в pivot самого +маркер-ноду — не гарантовано, що він точно в центрі footprint-у), і +лишень до прямого lerp додає `Math.sin(t * Math.PI) * +DEPOSIT_FLIGHT_ARC_HEIGHT` (той самий трюк дуги, що вже в `ResourceC`) — +вгору-і-плавно-вниз замість прямої лінії. + +**Знайдено й виправлено**: `DEPOSIT_FLIGHT_SCALE_MULTIPLIER` був +підкручений (`0.3`) під СТАРИЙ політ, де перспективна компенсація +(`perspectiveCompensationScale`, camera-distance ratio) відчутно +збільшувала розмір за час польоту, бо старт був близько до камери (HUD), +а кінець далеко. Тепер старт — голова гравця, приблизно та ж дистанція +до камери, що й Pay Zone (камера жорстко слідує за гравцем) — компенсація +майже не працює, і `0.3` робив токен практично невидимим. Підняли до +`1.3`. + +## Джойстик: редизайн під референс + два реальні баги (2026-08-13) + +Референс: прозора середина, градієнтний бордер, помаранчева точка пальця +з підсвіткою навколо. `JoystickC.init()` (SDK-обгортка nipplejs) приймає +будь-які опції nipplejs напряму, включно з `color: {back, front}` — +кожен рядок йде прямо в `background` відповідного div, підтримує +градієнти й навіть `var()`/`calc()` (живі CSS-змінні всередині inline +style реально оновлюються). + +**Фінальна реалізація** (`beforeResourcesLoadedCb.ts` + `main.css`): +- `back` (кільце) — `conic-gradient(from var(--joy-angle), ...)`, базовий + колір однотонний темний, підсвітка (тон точки пальця) лише у широкій + дузі навпроти пальця, з `calc()`-інтерполяцією RGB-каналів (не альфи!) + за `--joy-force`. +- `front` (точка) — суцільний плоский колір, без градієнта/тіні. +- Форма кільця (прозора середина) — `mask-image: radial-gradient(...)` + з **абсолютними px**, не відсотками — щоб зміна `size` (радіус зони) не + розтягувала товщину бордера пропорційно. +- Розмір точки пальця (`.front`) — фіксований px-оверрайд, повністю + відв'язаний від `size` (nipplejs хардкодить точку як `size/2`, тож + збільшення радіуса зони інакше тягнуло за собою і точку). +- `JoystickC.onJoysticMove` — окрема підписка (SDK підтримує кілька + делегатів на подію, `PlayerC` підписаний окремо на той самий івент для + руху) ставить `--joy-angle`/`--joy-force` на `instance.ui.el` щокадру. + +**⚠️ Знайдений і виправлений баг: підсвітка на 90° проти стрілки від +реального положення пальця.** `Math.atan2(y, x)` на скрін-координатах (y +вниз) і CSS `conic-gradient(from Xdeg, ...)` обидва рахують по +годинниковій, але з різною точкою нуля: atan2 — схід (3 година), +conic-gradient — північ (12 година). Фікс — плюс 90° один раз, не свап +знаку і не переписування напрямку градієнта. + +**⚠️ Знайдений і виправлений баг: підсвітка не зупинялась на межі +зони, а продовжувала змінюватись, коли палець вели далі за край.** +Трасування в самому nipplejs (`Collection.ts`): `force` рахується з +СИРОЇ, необмеженої дистанції дотику (`dist / size`), тоді як +`frontPosition` (сама видима точка й кут вище) — з ОБМЕЖЕНОЇ. За краєм +кільця `force` продовжує необмежено зростати, хоча точка вже візуально +запінена на місці — усі `calc()` розраховані на `force` в межах `0..1`. +Це ж переповнення й давало жовтуватий відтінок (RGB-канали переїжджали +за цільовий колір). Фікс — один `Math.min(Math.max(force, 0), 1)` перед +записом у CSS-змінну, а не clamp у кожному calc() окремо. + +Розмір: `size` (радіус зони) підняли на +50%, потім усі елементи +(зона+точка+товщина кільця) зменшені на -30% від того — фінальні числа: +`size: 105`, точка `35px`/`-17.5px` margin, кільце `43.4/44.8/46.9/48.3px`. + +## Камера: `CAMERA_OFFSET` підкручений (2026-08-13) + +Користувач попросив камеру ближче, показавши референс-кут. `CAMERA_OFFSET` +в `TestSceneC.ts` з `(0, 9, -9)` (45° нахил, дистанція ~12.7) на +`(0, 8, -6)` — коротша дистанція, трохи інше (положче) співвідношення +Y:Z. Порадив користувачу для самостійного підбору: сам `CAMERA_OFFSET` +(єдиний вектор, що визначає і дистанцію, і кут — `CameraFollowC.init()` +ставить позицію й раз викликає `lookAt`, більше кут не змінюється) і +`camera_fov_p` в редакторі конфігів (дефолт 60, діапазон 30-150) — +окремий, незалежний важіль "наближення" через FOV. **Важливо**: +`camera_position_p`/`camera_rotation_p` слайдери в редакторі — мертві, +`CameraFollowC` перезаписує їх одразу при старті. + +## Day 9-10: рефактор і чистка (2026-08-13) + +Бриф прямо просив "думай як розробник, що передає код іншому" — без +зміни логіки/поведінки гри, лише структура/дублювання/коментарі. +Погоджений план (`EnterPlanMode`/`ExitPlanMode`) виконаний фазами, кожна +верифікована окремим `npx vite build`: + +- **Мертвий код прибрано**: `PayZoneC.getTargetWorldPosition()` (0 + викликів), увесь `PhysicsC.PhysicsObjPair`/`pair`-поле/ + `disablePhysicsPair()` (клас був повністю нежиттєздатним — єдине + місце, що присвоювало `pair`, саме закомічене); 2 прототипні + `console.log` (`PayZoneC`, `ResourceC`). `SparkleFxC`'s `console.debug` + на виснаження пулу — свідомо залишений (реальна діагностика + деградованого режиму, не сміттєвий лог). +- **4 нові утиліти в `src/utils/`** — прибрали дублювання, знайдене між + файлами (не новий functionality, лише перенесення існуючої формули + без зміни): + - `perspectiveScale.ts` — camera-distance-ratio компенсація, була + незалежно написана і в `ResourceC` (`toTarget`), і в `PayZoneC` + (`playDepositFlight`). + - `screenWorldProjection.ts` — world<->screen NDC-математика, була в + `HudC` (screen->world, unproject) і `HealthBarC` (world->screen, + project) — обидва незалежно читали `canvasRect`. + - `tweenChain.ts` — той самий "TweenC.add() обидві ланки окремо" + гочтя й бойлерплейт, дослівно повторений у `BreakablePropC` і + `SparkleFxC`. + - `flashOverlayMaterial.ts` — адитивний флеш-матеріал (клон + white + additive overlay), дослівно повторений у `BreakablePropC` і + `ResourceC`, разом з дубльованою константою `FLASH_COLOR`. +- **Коментарі підчищені** у всіх 16 контролерах, `beforeResourcesLoadedCb.ts` + і всіх 3 CSS-файлах — прибрана вся хронологія/цитати чату/"Day N" + посилання, лишились тільки короткі WHY-нотатки на дійсно неочевидні + речі (усі гочті з цього файлу — TweenC-чейн, glTF dot-stripping, + nipplejs force-переповнення, atan2/conic-gradient зсув, MeshBasicMaterial + білий-тінт-no-op — лишились, просто по одному разу в коді, не по + два-три). Додана одна нова коротка нота в `ui.css` — `.bannerButton` + реально мертвий у білді (purgecss-квірк), раніше це знав тільки цей + файл. +- **Свідомо НЕ зроблено**: домен-розбиття `src/controllers/` на + підпапки (gameplay/vfx/ui/core) — розглянуто і відхилено: жодної + зовнішньої конвенції під це нема (SDK сам плаский), `resources/` — + єдиний прецедент підпапок в проєкті, і той групує по типу ассету, не + по домену. Переїзд 16 файлів заради нуля функціональної вигоди — + саме "overengineering", проти якого прямо просив бриф. +- Два CSS-пункти з попереднього Day 8 аудиту (rem/% замість vh, + відсутній landscape-медіазапит у `health-bar.css`) — **свідомо не + торкались**, за проханням користувача відкласти. + +Перевірено виключно `npx vite build` після кожної фази (усі 883 модулі, +без помилок) — жодного self-testing у браузері, як завжди в цьому проєкті. diff --git a/stats.html b/stats.html new file mode 100644 index 0000000..67d297d --- /dev/null +++ b/stats.html @@ -0,0 +1,4949 @@ + + + + + + + + Rollup Visualizer + + + +
+ + + + +