# Day 8: UI — резорс-каунтер (2026-08-12) Референс — скріншот іншої гри ("Zombie Invasion") з двома стековани лічильниками (дерево + метал); у нас лише один тип ресурсу, тож зроблено тільки один лічильник, без плейсхолдера під другий (за проханням користувача, поки не підтверджено, що другий ресурс дійсно буде). ## Реальний дизайнерський ассет замість CSS-пілла Користувач: "Tool_15 знайди цей файл в temp. Це юай для каунтера ресурсу." `temp/Tool_15.webp` (184×71, темна округла плата з діагональною деревʼяною планкою, вже вбудованою в саму картинку) — скопійований у `src/resources/images/resource_counter_bg.webp`, підключений через `images.ts` (`resourceCounterBgSrc`, той самий `ConvertToBase64WhenRelease`- паттерн). Стара кругла іконка-рамка + окремий `` (з `icon_wood.webp`) прибрані повністю — планка вже частина фонового зображення. `icon_wood.webp`/`iconWoodSrc` після цього стали осиротілими і видалені. ## Стилі винесені в `ui.css`, живлені реальним аспектом і CSS-змінною Користувач: "Всі стилі для юай мають бути в css файлі, у нас є відповідний файл вже" — весь inline `document.createElement("style")` з `HudC.ts` прибраний, стилі перенесені в `src/css/ui.css` (вже підключений в `index.html`). `HudC.ts` лишає в JS тільки `holder.style.backgroundImage` (шлях до ассету відомий лише в runtime через build-пайплайн, статичний CSS-файл цього не може). Розмір плейта: спочатку `width`+`height` окремо в `vh` (руками підібрана пропорція 184:71) — користувач: "не подобається, що задана і висота і ширина, давай контролювати через аспект рейтіо" -> `aspect-ratio: 184/71` замість другого числа. Потім: CSS custom property `--hud-width` на `.resource-hud`, і `font-size: calc(var(--hud-width) * 0.175)` замість фіксованого `vh` — користувач змінював `width`, шрифт не слідував; тепер один параметр (`--hud-width`) керує і розміром плати, і шрифтом (успадковується вниз по DOM). ## Дві CSS-анімації замість JS-коду (Day 8 технічна вимога) "Use built-in animations/transitions instead of changing elements through code" — (1) `.resource-hud` грає `resource-hud-enter` (fade+slide) один раз при монтуванні, самостійно, без JS-таймингу; (2) `.resource-hud__plate` (НЕ сам `.resource-hud` — окремо, дивись нижче чому) грає `resource-hud-bump` при кожній зміні числа — `HudC.update()` лише toggle-ить клас `is-bumping` (remove -> force reflow через `offsetWidth` -> add), сам keyframe рахує браузер. **⚠️ Знахідка: анімацію не можна вішати на ТОЙ САМИЙ елемент, що вже має іншу** Спочатку bump-анімація була на `.resource-hud__count` (сам текст) — користувач: "Анімація має бути на всю юайку не тільки на каунтер". Але просто перенести на `.resource-hud` (той самий елемент, що вже грає `resource-hud-enter`) НЕ можна: перемикання класу, що змінює `animation-name` (навіть тимчасово, назад до того самого значення), ЗАВЖДИ перезапускає анімацію — тобто кожен збір ресурсу знову проганяв би fade+slide-in ("з'явлення") поверх бампу. **Фікс**: два вкладені div — зовнішній `.resource-hud` (позиція/розмір + enter-анімація, статична), внутрішній `.resource-hud__plate` (фон+число разом, bump-анімація) — дві незалежні `animation`-лінії на двох елементах. ## `getWorldAnchorPosition()` — справжня screen-to-world проєкція Раніше (Day "HUD-лічильник"): фіктивний `Object3D`, дитина камери, з вгаданим локальним офсетом (`WORLD_ANCHOR_LOCAL_OFFSET`, підбирався на око, документований як "не точний screen-to-world розрахунок"). Користувач: "Треба дістати позицію правого края юайки і направляти ресурси саме туди." **Фінал**: `getBoundingClientRect()` правого краю `.resource-hud__plate` (реальний DOM), конвертація в NDC відносно `ThreeC.renderer.domElement.getBoundingClientRect()` (canvas, НЕ `window.innerWidth/Height` — SDK рендерить у letterboxed 9:16 бокс, canvas-rect гарантовано збігається з тим, що бачить камера), `new Vector3(ndcX,ndcY,0.5).unproject(camera)` -> напрямок від камери -> точка на фіксованій `ANCHOR_DISTANCE=2.5` вздовж цього променя. Рахується ЖИВО при кожному викликі (не раз при init) — автоматично лишається коректним при resize/зміні орієнтації, без окремого resize- слухача. `Object3D`/`WORLD_ANCHOR_LOCAL_OFFSET`-підхід видалений повністю.