Згенеровано з живих джерел проєкту: кольори з index.css, склад голосів з voice_map.json, радіуси й точки зламу пораховані по всіх стилях, питання — з open_questions.py. Після будь-якої зміни перезапустіть python3 src/tools_brandbook_gen.py.
Знак
Вордмарк набраний Playfair Display 600 з трекінгом +2 %. Бронзовий наголос над «O» —
55 % кегля. Знак існує як вектор wm-playfair-85.svg із
fill="currentColor", тому підключається CSS-маскою й успадковує колір тексту.
Окремих файлів під теми не потрібно — жодного «логотипа для темного фону».
Зліва направо: у кольорі тексту, акцентний, мінімальний розмір 18 px.
Параметр
Значення
Чому саме так
Пропорція
3,734 : 1
ширина вектора до висоти; фіксована
Мінімальна висота
18 px
нижче наголос над «O» зливається з літерою
Вільне поле
≥ висоти «H»
рій іскор не заходить у це поле
Підключення
mask + currentColor
один файл на всі теми й розміри
У пісні
замінює слово «Гомін»
на 8,44 с спалахує разом із рядком
Можна
Масштабувати вільно — вектор без растру
Фарбувати через background-color будь-яким токеном
Ставити на --panel, --bg і на фото з затемненням
У тексті пісні знак заміщає слово й світиться разом із рядком
Не можна
Розтягувати — пропорція фіксована
Прибирати наголос над «O» — це суть знака
Обводити, додавати тінь чи градієнт усередині літер
Писати «HOMIN» текстом там, де є місце для знака
Колір
Дві теми, однакові за функціоналом. Темна — основна; світла вмикається атрибутом
data-theme="light" на <html>. Жоден колір не задається
літералом у компонентах — лише через токен, інакше друга тема ламається мовчки.
Ролі
Група
Токени
Призначення
Тло
--bg, --panel, --panel-2
сторінка, картка, вкладена картка
Скло
--glass … --glass-3
напівпрозорі поверхні поверх тла; три щільності
Лінії
--line, --line-2
межа спокійна і межа активна
Текст
--ink, --ink-2, --muted
основний, вторинний, підпис
Акцент
--ember, --ember-2, --ember-3
дія, гарячий стан, світлий відблиск
Сяйво
--glow, --glow-2
тінь навколо активного акценту, фокусне кільце
Метал
--bronze, --bronze-2
наголос у знаку, службові позначки
Стан
--ok
успіх; єдиний зелений у системі
Темна тема
--bg#0D0B0E
--panel#1B1619
--panel-2#221C20
--ink#F6EEE4
--ink-2#CDBFB2
--muted#9A8F86
--ember#FFB25A
--ember-2#FF7A45
--ember-3#FFE0A8
--bronze#A8845A
--bronze-2#C4A06F
--ok#7FD6A4
Токен
Значення
Контраст до #0D0B0E
WCAG
--ink
#F6EEE4
17.05
AAA
--ink-2
#CDBFB2
10.91
AAA
--muted
#9A8F86
6.2
AA
--ember-2
#FF7A45
7.58
AAA
--bronze-2
#C4A06F
8.03
AAA
--ok
#7FD6A4
11.25
AAA
Світла тема
--bg#EEF0F8
--panel#FFFFFF
--panel-2#F6F5FB
--ink#17162A
--ink-2#45445C
--muted#62657A
--ember#D9732B
--ember-2#AE441A
--ember-3#FFC48A
--bronze#A8845A
--bronze-2#7C5E3C
--ok#1B7145
Токен
Значення
Контраст до #E7EAF4
WCAG
--ink
#17162A
14.75
AAA
--ink-2
#45445C
7.82
AAA
--muted
#62657A
4.77
AA
--ember-2
#AE441A
4.8
AA
--bronze-2
#7C5E3C
4.96
AA
--ok
#1B7145
5.0
AA
Заливки й сяйва
Ці токени в світлій темі не фарбують текст — вони живуть у градієнтах кнопок,
межах і сяйві. Відношення до тла наведене довідково: вимога 4,5:1 до них
не застосовується, доки ними не почнуть писати.
Токен
Значення
Контраст до #E7EAF4
Роль
--ember
#D9732B
2.71
заливка, не текст
--ember-3
#FFC48A
1.29
заливка, не текст
--bronze
#A8845A
2.86
заливка, не текст
Числа рахуються під час збірки за формулою WCAG 2.1 проти найгіршої
зупинки градієнта. Емпірична перевірка — python3 src/audit_contrast.py:
22 прогони, 0 порушень.
Шрифти
Unbounded 300/500/700 · --display
Тут мова оживає, грає, співає.
Manrope 400/500/600/700 · --body
Основний текст, підписи, інтерфейс. Читомий у довгих абзацах і не сперечається
із заголовком.
JetBrains Mono 400/500 · --mono
voice_id · omnihuman-1-5 · 44.659 ₴/$
Шкала
Роль
Розмір
Вага · трекінг
Сімʼя
Титул екрана
clamp(34px, 7vw, 76px)
500 · −0,03em
Unbounded
Заголовок розділу
clamp(24px, 3.4vw, 38px)
500 · −0,02em
Unbounded
Заголовок картки
17–22px
600 · −0,01em
Unbounded
Основний текст
15–16px
400 · 0
Manrope
Підпис
12–13,5px
400–500 · 0
Manrope
Мітка
11–12px
600 · +0,06em, великі
Manrope
Код, число, id
11–14px
400–500 · 0
JetBrains Mono
1Вага 700 в Unbounded — лише в лого-локапах. У заголовках 500: на 700 трекінг «розʼїжджається» на кирилиці.
2Числа, ідентифікатори й курси — завжди моно, щоб колонки не стрибали при перерахунку валюти.
3Міжрядковий: 1,6 для тексту, 1,15–1,25 для заголовків, 1 для чисел у картках.
4Фолбек — system-ui. Сторінка читомa ще до того, як приїде шрифт.
Звідки й на яких умовах
Шрифт
Ліцензія
Підключення
Unbounded
SIL Open Font License 1.1
Google Fonts, ваги 300/500/700
Manrope
SIL Open Font License 1.1
Google Fonts, ваги 400/500/600/700
JetBrains Mono
SIL Open Font License 1.1
Google Fonts, ваги 400/500
Playfair Display
SIL Open Font License 1.1
не підключається: знак уже переведений у контури wm-playfair-85.svg
·Усі чотири під OFL — вільні для комерційного використання, вбудовування й самостійного хостингу.
!Google Fonts — єдина зовнішня залежність усього сайту. Все інше вшите в сторінку. Для повної автономності шрифти доведеться покласти поруч.
Сітка й форма
Одна колонка 980 px тримає весь сайт. Ширші значення трапляються лише там, де
блок навмисно виходить за колонку — таблиця порівняння й сцена з роєм.
·Вертикальний ритм секції: 56 px зверху, 22 px під заголовком.
·Поле сторінки: 22 px на мобільному, 26 px від 980 px.
Компоненти
Живі приклади — намальовані тими самими токенами, що й сайт. Якщо тут щось
виглядає не так, як у продукті, розійшлися токени, а не розмітка.
Кнопки
Клас
Коли
Правило
.btn-ember
єдина головна дія на екрані
градієнт --ember-3 → --ember → --ember-2, текст --ember-ink
.btn-ghost
вторинна дія поруч із головною
скло + межа --line-2
.btn-big
перший екран, мобільний док
висота 60 px, кегль 17
Пігулки, чипи, перемикачі
NeoУкраїнська44,1 кГцuk UA
Поле й картка
Соломіятепла · книги, подкасти
·Фокус ніколи не знімається. :focus-visible — кільце --ember 2 px з відступом 3 px, це єдиний фокусний стиль у системі.
·Поле у фокусі змінює лише колір межі на --ember; розмір і положення не рухаються, щоб макет не смикався.
·Активний стан пігулки — заливка --glass-2 і текст --ink; ніколи не самим лише кольором тексту.
·Натискання: transform:translateY(1px). Одне правило на всі кнопки.
Іконки
Жодної бібліотеки. Кожна іконка намальована вручну як інлайн
<svg> зі stroke="currentColor", тому вона
успадковує колір тексту й працює в обох темах без другого файлу.
Параметр
Значення
Сітка
20 або 24 px
Обведення
currentColor, ніколи не заливка
Кінці й стики
stroke-linecap:round, stroke-linejoin:round
Підпис
aria-label або текст поруч; декоративні — aria-hidden
Розмір у коді
успадковується від кегля, не задається жорстко
Вага лінії
Набір не має однієї ваги — він має основну і кілька винятків.
Порахувати чесно важливіше, ніж записати гарне правило й порушити його:
stroke-width
Разів
Де
1
4
тонкі службові позначки всередині інших іконок
1.6
108
основна вага — на ній намальована більшість набору
1.7
8
поодинокі іконки з густішою геометрією
1.8
55
другий за поширеністю крок, для дрібніших сіток
1.9
13
найщільніші фігури, щоб не злипались
2
21
хрестик закриття шухляди — навмисно важчий за решту
Разом 209 обведень у наборі. Нова іконка починається з
1.6; інша вага — лише коли на своїй сітці вона інакше злипається.
Можна
Малювати в 24 px і перевіряти в 20 px
Тримати currentColor — колір задає текст навколо
Округлі кінці: набір читається як одна рука
Давати aria-label, якщо поруч немає тексту
Не можна
Тягнути бібліотеку заради однієї іконки
Заливати фігуру — набір лінійний
Вішати колір усередині <svg> — зламає світлу тему
Масштабувати іконку разом із обведенням: лінія потовщає
не оновлюється при перемиканні — див. відкриті питання
Пошук
index, follow + canonical на кожній сторінці
robots.txt і sitemap.xml згенеровані
Звук
автозапуск не робиться; музика лише за згодою
браузер інакше глушить трек мовчки
·Колір ніколи не єдиний носій сенсу: стан дублюється заливкою, підписом або позицією.
·Кожне <img> має alt; декоративні полотна рою — порожній alt і aria-hidden.
!Звичайний прогін axe тут бреше. Майже кожна поверхня сайту — градієнт, а на градієнті axe відповідає «incomplete», а не «violation»: на головній так лишалися неперевіреними 83 елементи, і сторінка все одно звітувала «0 порушень». audit_contrast.py спершу сплощує кожен градієнт до найгіршої зупинки й лише тоді міряє.
Що це виявило
Перший чесний прогін дав 59 справжніх порушень — усі у світлій темі,
усі на дрібному тексті. Виправлено зсувом трьох токенів і двома правилами:
Токен або правило
Було
Стало
Контраст
--bronze-2 (світла)
#8F6E47
#7C5E3C
3,89 → 4,96
--ember-2 (світла)
#B5481D
#AE441A
4,46 → 4,80
--ok (світла)
#2E9A63
#1B7145
2,95 → 5,00
.icard h3 small, .mock .call
--ember
--ember-2
3,26 → 5,07
.dw b i — наголошена літера
--ember
--ember-2
3,17 → 5,61
Темна тема була чистою від початку: на темному тлі ті самі токени
дають 6–17:1. Проблема виникає лише там, де дрібний акцентний текст лягає
на світлий градієнт.
Числа й валюти
Ціни зберігаються в доларах і перераховуються на льоту. Базова валюта одна,
решта — похідні, тому нема двох «джерел правди» для однієї ціни.
Валюта
Курс
Звідки
USD $
1,00
база; усі цінники в коді — у доларах
EUR €
1,00
прирівняно до долара — рішення власника, не конвертація
UAH ₴
44.659
НБУ, вшивається під час збірки inject_currency.py
Правила запису
1Гривня від 10 ₴ округлюється до десятків: 1 340 ₴, а не 1 337,42 ₴. Цінник із копійками виглядає як помилка.
2Нижче 10 ₴ копійки лишаються: це поштучні тарифи, і 0,22 ₴ за 1000 символів — не те саме, що 0 ₴.
3Нуль — це 0, без знаків після коми й без округлення.
4Знак валюти: $19 і €19 попереду, 850 ₴ позаду — так пишуть у кожній з мов.
5Валюта за замовчуванням від мови: uk → ₴, pl → €, решта → $. Вибір користувача перекриває це й памʼятається.
6Розряди розділяє нерозривний пробіл, дробову частину — кома.
Джерело правил — src/site/currency.js; перемикач додається
до кожної сторінки під час збірки, а не вписаний у розмітку.
Мови
Мова
Код
Стан
Валюта за замовчуванням
Українська
uk
основна, повний обсяг
UAH ₴
English
en
повний обсяг інтерфейсу
USD $
Polski
pl
повний обсяг інтерфейсу
EUR €
·Кожен рядок інтерфейсу живе у src/i18n_pages_data.py. Тексту в розмітці немає — інакше переклад відстає від правок.
·Імена персон і рядки віршів не перекладаються: це власні назви й цитати з української поезії.
·Голоси й наголоси — українські в усіх трьох мовах. Мова інтерфейсу не змінює те, що звучить.
!Атрибут lang лишається uk після перемикання. Відоме питання — див. розділ нижче.
Голос бренду
Українська, на «ви» до відвідувача, без канцеляриту й без вигуків.
Технічне називаємо технічно: «модель», «кредити», «наголос U+0301».
Можна
«Увімкніть звук — і слухайте»
«Наголос ставиться там, де слово двозначне»
«850 ₴ на місяць» — з валютою й без копійок
«Модель omnihuman-1-5» — назвати точно
Не можна
«Революційна AI-платформа нового покоління»
«Здійснюється можливість налаштування параметрів»
«Всього за 19$!» — окличні знаки й приліплена валюта
«Наша нейромережа» замість назви моделі
·Наголоси ставимо лише там, де слово двозначне. Це не стилістика: зайва позначка ламає просодію. Перевірено — з «іржа́вий» голос читав «і ржавий», без позначки — чисто.
·Бренд у текстах для синтезу пишемо кирилицею Го́мін. Латинське «HOMIN» вимовляється як «Humind» або «Хомін».
·На екрані бренд лишається HOMIN або знаком. Показ і вимова навмисно різні.
·Наголос — комбінований U+0301 після голосної, ніколи не латинська «á». Латиниця всередині кирилиці ламає і синтез, і пошук.
Склад голосів
Персона
Голос ElevenLabs
Рядок
Аудіо
Відео
Соломіятепла · книги, подкасти
Yevheniia — Warm & BrightBn2Tw7ruLKdtFQ6xcEkG
Так, я бу́ду крізь сльо́зи смія́тись, се́ред ли́ха співа́ти пісні́.Леся Українка
4.11 с
4.04 с
Остапвпевнений · реклама, промо
Alex Nekrasov — Confident and Clear9Sj8ugvpK1DmcAXyvi3a
Впере́д, хто не зомлі́в! Ще ме́ту не оси́пав цвіт зів'я́лий!Іван Франко
3.22 с
3.24 с
Мирославановини · дикторська
Ai.NastasiaUrqGMRmIdd73BHEhBvt6
І чужо́му науча́йтесь, й свого́ не цура́йтесь.Тарас Шевченко
2.96 с
2.76 с
Богданпідтримка · колцентр
Yevhen ProfessionalTEyBWD5tAHAWqAGEv6yI
Без наді́ї таки́ сподіва́юсь. Бу́ду жи́ти! Геть, думки́ сумні́!Леся Українка
3.19 с
3.09 с
Яринажвава · соцмережі, ігри
Katerina Boyko — GoldenXx7Usst6zGAQKLjgeyV7
Ене́й був па́рубок мото́рний і хло́пець хоть куди́ коза́к.Іван Котляревський
3.87 с
3.74 с
Левдрама · трейлери, аудіокниги
Borys Borysov — deep narratorYdkhl6Al705Uykb6EPiX
Реве́ та сто́гне Дніпр широ́кий, серди́тий ві́тер завива́.Тарас Шевченко
2.8 с
2.76 с
ВересNeo · новини, навчання
Viacheslav SkorykndzBDh5VK0dFeOP73Guh
Борі́теся — поборе́те! За вас пра́вда, за вас сла́ва.Тарас Шевченко
3.22 с
3.24 с
КалинаNeo · навчання, IVR
Evelina — confident, calm3rWBcFHu7rpPUEJQYEqD
Садо́к вишне́вий ко́ло ха́ти, хрущі́ над ви́шнями гуду́ть.Тарас Шевченко
2.96 с
2.93 с
Рядок кожної персони — цитата з української поезії, дібрана під її регістр.
Він же служить контрольним текстом: згенероване повертається через stt
й звіряється зі сценарієм.
·Голос добирається під портрет за статтю, віком і регістром, а не навпаки — фото знайти легше, ніж тембр.
·Один voice_id не використовується двічі. Перевіряється тестом.
·Верес і Калина — серія Neo: нейтральніші, для навчання та IVR.
Звук бренду
Дві пісні а капела, по два варіанти. На головній звучить letyt_1 —
під нього написана партитура, тому підміна треку без нової карти міток розсинхронізує
весь перший екран.
Трек
Розмір
Роль
letyt_1
479 КБ
гімн, звучить на першому екрані
letyt_2
487 КБ
друга версія «Летить»
nebokray_1
461 КБ
«Небокрай», варіант 1
nebokray_2
446 КБ
«Небокрай», варіант 2
Партитура
Мітка
Час
Що відбувається
Перший рядок
0,36 с
текст зʼявляється, рій отримує перший імпульс
Наголоси
0,36 · 4,14 · 8,44 с
три позначки над заголовком запалюються по черзі
Шари рою
0,36 · 2,50 · 4,14 · 6,36 · 8,44 · 11,54 с
щільність наростає шістьма кроками
Знак
8,44 с
слово «Гомін» у рядку заміщається вордмарком і спалахує
Витримка
14,60 с
довге «летить» — рій розтягується
Аутро
18,12 → 20,00 с
пʼять скет-складів як пʼять пульсів
Кінець
21,36 с
рій повертається у спокій
1Автозапуску немає. Браузер глушить звук без жесту, і трек «грав би» у тиші. Музика вмикається лише за згодою.
2Нижче 75 % висоти екрана гучність падає з 0,62 до 0,22 — музика лишається, але не заважає читати.
3Караоке-рядок веде слово за словом: проспіване лишається світлим, поточне — акцентним. Мітки в src/site/song.js.
4prefers-reduced-motion зупиняє рій, але не звук — це різні потреби.
Медіа
8Портрети1024×1024 PNG, студійна серія
24Голосові записиElevenLabs, mp3 44,1 кГц
2Звукові ефектиElevenLabs Sound Generation
8Аватар-відеоOmniHuman 1.5, 640×640 h264/aac
4ПісніSuno V5_5, а капела
27Іконкиінлайн SVG, без бібліотек
·Відео лежать файлами у /video/; аудіо й портрети вшиті в сторінку як data-URI.
·Аватар-відео — 640×640 H.264/AAC, до 1 МБ. Вихід моделі у HEVC на 7 МБ не показується браузером на половині пристроїв.
Портрети
Вісім портретів — одна серія: одне світло, один клас обʼєктива, одне тло.
Новий персонаж має стати в цей ряд, а не поруч із ним.
Параметр
Значення
Розмір
1024×1024 PNG, квадрат
Кадр
погруддя, очі приблизно на 38 % висоти зверху
Погляд
прямо в камеру
Світло
мʼяке студійне, ключове збоку, тепла заливка
Тло
темне нейтральне з градієнтом, без предметів і текстур
Портрет оживляє omnihuman-1-5, а рухом губ керує власний запис
персони — не синтез «під відео». Тому голос і артикуляція збігаються за побудовою.
1Промт руху один на всіх: The person speaks straight to camera in a warm recording studio, natural micro-expressions and subtle head motion, steady framing.
2Керує кліп __B — персона представляється. Короткий, і кожна повертає його дослівно через stt.
3Подавати хвилями по три. Сім завдань одночасно впираються в ліміт одночасності kie.ai, і частина падає.
4Після рендера — перекодувати в H.264 640×640 і перезаписати video/index.json: збірка бере розміри звідти.
5Кадр не обрізати під круг у розмітці — портрет уже скомпонований під квадрат, і обрізка зʼїдає підборіддя.
Генеративний стек
Завдання
Модель
Примітка
Синтез мовлення
eleven_multilingual_v2
завжди з language_code: "uk"; turbo/flash гірше тримають ґ та апостроф
Розпізнавання
scribe_v1
контроль вимови: транскрипт звіряється зі сценарієм
Зображення
google/nano-banana
портрети, 4 кредити
Редагування
google/nano-banana-edit
консистентність персонажа
Аватар-відео
omnihuman-1-5
без префікса вендора; output_resolution — 720 або 1080, не «1080P»
Загальне відео
bytedance/seedance-2-5
для сцен, не для ліпсинку — губи не збігаються
Музика
Suno V5_5
лише V5_5 керує тривалістю; callBackUrl обовʼязковий
·Усе йде через один вебхук n8n — POST /homin-bridge, поле op обирає гілку. Ключі живуть у креденшелах n8n, не в скриптах.
·Файли вантажаться на kieai.redpandaai.co, не на api.kie.ai — там цього маршруту немає, 404.
·Обмеження — на витратах (Safe-Spend Limits), не на IP: вихідна адреса n8n Cloud не гарантовано стала.
Міст
Уся генерація йде через один вебхук n8n — POST /homin-bridge.
Тіло завжди JSON, поле op обирає гілку, відповідь завжди JSON.
Ключі живуть у креденшелах n8n і ніколи не потрапляють у скрипти чи в сайт.
Операція
Постачальник
Що робить
avatar
kie.ai
omnihuman-1-5 — ліпсинк під власний запис
fetch
мережа
завантажити URL, за потреби зменшити
help
службова
перелік операцій — міст описує себе сам
image
kie.ai
nano-banana — портрет
image_edit
kie.ai
nano-banana-edit — тримає персонажа
kie_status
kie.ai
опитати завдання
lipsync
HeyGen
без креденшела — див. відкриті питання
music
kie.ai
Suno V5_5
music_status
kie.ai
опитати завдання Suno
ping
службова
перевірка живості
sfx
ElevenLabs
звуковий ефект з опису
status
HeyGen
без креденшела — див. відкриті питання
stt
ElevenLabs
розпізнавання — ним звіряється вимова
tts
ElevenLabs
синтез мовлення, до 2500 символів
upload
kie.ai
покласти файл; хост kieai.redpandaai.co
video
kie.ai
seedance-2-5 — сцена, не ліпсинк
voices
ElevenLabs
список доступних голосів
·17 операцій. Міст позначений «Available in MCP», тож ті самі виклики доступні як інструменти агента.
·Будь-яка гілка при збої повертає {ok:false, error}, а не падає: сторона, що викликає, завжди отримує JSON.
·Таймаут 120 с, відповідь до ~4 МБ. Довгі рендери — createTask плюс опитування, не очікування в запиті.
!Файли вантажаться на kieai.redpandaai.co. На api.kie.ai цього маршруту немає — 404, і це легко сплутати з проблемою ключа.
Домени
Середовище
Адреса
Стан
Продакшн
homin.studio
публічний, TLS Let's Encrypt, відкритий для пошуку
З www
www.homin.studio
той самий сайт
Попередній
homin.taskwheels.com
лишається робочим як запасний
Прев'ю
*.vercel.app
закриті Vercel Authentication — кастомних доменів це не стосується
Міст
POST /homin-bridge у n8n Cloud
17 операцій, доступний як MCP-інструмент
1DNS у Cloudflare, не у Vercel: A → 76.76.21.21 у режимі DNS only. Під помаранчевим проксі Vercel не видасть сертифікат — і одного зайвого проксійованого запису на тому самому імені досить, щоб зламати правильний.
4Додати до VOICES у src/index.js та src/studio.js, до REGISTER у src/brandbook_data.py, перезібрати
Додати сторінку
1src/<page>.html|css|js + словник у src/i18n_pages_data.py
2Адреса в artifacts/urls.json і плейсхолдер у tools_urls.py
3Внести назву до PAGES у src/wrap_documents.py, src/inject_currency.py і src/test_suite.py
Додати колір або компонент
1Токен додається у обидва блоки :root — темний і [data-theme="light"]
2Перезапустити генератор брендбука: новий токен зʼявиться у свотчах і в таблиці контрасту. Не пройшов AA — видно одразу
3Жодного кольору літералом у компоненті
Обовʼязково після збірки
!wrap_documents.py — doctype, charset, viewport. Збірка віддає фрагменти без них, і кирилиця розсипається там, де сервер не додає charset сам.
!inject_currency.py — валюти, знаки голосів, партитура. Збірка перезаписує HTML і зносить ці вставки.
!tools_brandbook_gen.py — інакше брендбук описує попередню версію.
!audit_site.py, audit_release.py, test_suite.py — усі три мають дати нуль проблем.
Відкриті питання
Реєстр усього, що свідомо не закрито. Ведеться в src/open_questions.py і друкується сюди, щоб питання не жили лише в листуванні. Разом 20 · відкладено: 3 · відкрите: 8 · рішення: 8 · заблоковане: 1.
заблокованеінфраструктура
homin.studio чекає на A-запис у Cloudflare
Домен і www прив'язані до проєкту Vercel, але DNS зони живе в Cloudflare, а не у Vercel. Vercel просить A homin.studio → 76.76.21.21; зараз ім'я вказує на IP Cloudflare і TLS-рукостискання падає.
Чому
Записи в Cloudflare я змінити не можу — доступу до зони немає, і це не та дія, яку варто робити за власним рішенням.
Наслідок
Поки запису немає, сайт відкривається лише за homin.taskwheels.com.
Що закриє
У Cloudflare → DNS: A homin.studio → 76.76.21.21 і A www.homin.studio → 76.76.21.21, обидва в режимі DNS only (сіра хмарка). Далі vercel certs issue homin.studio.
відкритеголоси
На головній 6 голосів із 8
Масив VOICES у src/index.js містить solomiia, ostap, myroslava, bohdan, yaryna, lev. Верес і Калина є лише в студії.
Чому
Так було в отриманій збірці; портрети, аудіо й відео для всіх восьми готові.
Наслідок
Два повністю готові персонажі не показані на першому екрані.
Що закриє
Додати два записи у VOICES в src/index.js — усе інше вже на місці.
відкритеголоси
Точність ліпсинку оцінена лише візуально
Вісім відео OmniHuman перевірені по кадрах: артикуляція є, вотермарків немає.
Чому
Збіг губ зі звуком неможливо виміряти автоматично — потрібне людське вухо.
Наслідок
Якщо десь розсинхрон — це видно лише при перегляді.
Що закриє
Переглянути всі вісім. Проблемні перегенерувати з іншим seed через python3 src/batch_avatars.py .
відкритедоступ
Форма входу — заглушка на публічному сайті
login.html показує повний інтерфейс входу, але сервера за ним немає: реєстрація й вхід нічого не зберігають.
Чому
Прототип інтерфейсу випередив бекенд. Поки сайт був за паролем, це бачила лише команда.
Наслідок
Відвідувач може спробувати зареєструватися й нічого не отримати.
Що закриє
Або підключити справжню авторизацію, або чесно підписати форму як демонстрацію.
відкритедоступ
Сайт публічний, але сторінки умов немає
Пароль знято, robots переведено в index, follow. При цьому посилання «Умови» й «Конфіденційність» у підвалі кожної сторінки ведуть у «#».
Чому
Сторінку свідомо відкладено — вона наповнюватиметься даними пізніше. Але тепер її бачить не лише команда.
Наслідок
Публічний сайт із неробочими юридичними посиланнями. Для сервісу з тарифами це помітно.
Що закриє
Або зробити legal.html (див. [[legal-page]]), або прибрати посилання з підвалу, доки її немає.
відкритесайт
Атрибут lang не оновлюється при зміні мови
Сайт перемикає тексти EN/PL, але documentElement.lang завжди лишається «uk».
Чому
Механізм i18n змінює вміст, а не атрибут документа.
Наслідок
Валюта за замовчуванням завжди рахується від «uk»; скрінрідери читають EN і PL сторінки українською артикуляцією; групування чисел у цінах теж орієнтується на lang.
Що закриє
У перемикачі мови додати documentElement.setAttribute('lang', LANG).
відкритесайт
Дрібні наголоси на градієнті — постійний ризик
Світла тема лишає мало запасу: --bronze-2 дає 4,96 при потрібних 4,5, --ember-2 — 4,80. Новий дрібний акцентний текст легко виходить за межу.
Чому
Палітра тепла й насичена; темніші відтінки почали б читатися як коричневі, а не як бурштин.
Наслідок
Кожен новий акцентний підпис у світлій темі треба міряти.
Що закриє
Не фарбувати дрібний текст у --ember: у світлій темі для цього є --ember-2 і --bronze-2.
відкритесайт
index 1,2 МБ, studio 1,8 МБ
Збірка вшиває аудіо, портрети й словники як data-URI в один HTML.
Чому
Початковий задум — самодостатній артефакт без зовнішніх файлів.
Наслідок
Повільний перший показ на слабкому зʼєднанні; кеш не перевикористовується між сторінками.
Що закриє
Відео вже винесено у /video/ файлами. Так само можна винести аудіо й портрети — правка audio_js та avatars_js у tools_urls.py.
відкритеінфраструктура
Вихідна адреса n8n Cloud не статична
Виміряна адреса 4.165.103.114 входить у діапазон n8n 4.165.103.112/28, але n8n прямо не гарантує сталість — інстанс може переїхати.
Чому
Білий список у kie.ai знято (ліміт 10 адрес проти ~209 у n8n), натомість стоять Safe-Spend Limits.
Наслідок
Якщо білий список колись увімкнуть знову — запити почнуть падати 401 без видимої причини.
Що закриє
Тримати обмеження на витратах, а не на IP. Перевірити адресу через python3 src/egress_ip.py, якщо зʼявиться 401.
відкладеногенерація
Операції lipsync і status не працюють — немає ключа HeyGen
Ноди HeyGen у мості налаштовані на Custom Auth, але креденшела в акаунті немає.
Чому
HeyGen замінено на OmniHuman 1.5, який дешевший і без водяного знака.
Наслідок
2 з 16 операцій мосту повертають помилку. Решта 14 не залежать від цього.
Що закриє
Або створити креденшел із шаблоном {"headers":{"X-Api-Key":"<ключ>"}}, або прибрати обидві гілки з мосту.
відкладеногенерація
Співаючий аватар не зроблено
Обговорювався рендер, де OmniHuman керується піснею letyt_1.mp3 (21,4 с).
Чому
Вирішено спершу зробити «співочий сайт». Плюс OmniHuman рекомендує аудіо до 15 с — 21,4 с може дати деградацію.
Наслідок
Нічого не блокує; партитура працює без нього.
Що закриє
Пробний рендер на Леві або Остапі, ~110–160 кредитів, і оцінити якість.
відкладеносайт
Немає сторінки умов і політики
tools_urls.py мапить плейсхолдер , але legal.html не існує, тож посилання на всіх 11 сторінках деградує в «#».
Чому
Свідомо відкладено: сторінка наповнюватиметься юридичними даними пізніше.
Наслідок
Кожна сторінка має неробоче посилання в підвалі та боковому меню.
Що закриє
Створити src/legal.html + словник, додати адресу в artifacts/urls.json і назву в PAGES у wrap_documents.py та inject_currency.py.
рішеннябізнес
Євро прирівняне до долара 1:1
Ціна в EUR дорівнює ціні в USD. НБУ на 24.08 дає 52,25 ₴/€ проти 44,66 ₴/$.
Чому
Свідоме рішення власника, не помилка конвертації.
Наслідок
Європейські клієнти платять приблизно на 13 % менше за паритет.
Що закриє
Переглянути, якщо курс піде далі. Правиться одним числом у src/site/currency.js (rates.EUR).
рішеннягенерація
Suno дописала скет у кінці «Летить»
У брифі було «не додавати слів». Suno не додала слів, але доспівала «ба-да-дом» на 18,1–20,4 с.
Чому
Вирішено не обрізати — для а капели звучить доречно.
Наслідок
Партитура використовує цей аутро як пʼять ритмічних імпульсів рою.
Що закриє
Якщо колись прибирати — обрізати трек після 17,2 с і перезняти карту.
рішеннягенерація
callBackUrl для Suno вказує на сам міст
Suno вимагає callBackUrl (422 без нього). Вказано вебхук мосту: роутер бачить запит без op і відповідає 200.
Чому
Так результат не йде на сторонній сервіс, а забирається опитуванням.
Наслідок
У логах n8n зʼявляються виконання без op — це очікувано, не помилка.
Що закриє
Нічого, якщо не знадобиться реальний вебхук-приймач.
рішеннягенерація
Три пісні з чотирьох не звучать на сайті
На головній грає letyt_1. Ще три — letyt_2, nebokray_1, nebokray_2, разом 1,4 МБ — доступні лише зі сторінки /music/.
Чому
Партитура написана під letyt_1 потактово; підміна треку без нової карти міток розсинхронізує весь перший екран.
Наслідок
1,4 МБ у деплої заради сторінки прослуховування. Це свідомий обмін, не забутий файл.
Що закриє
Якщо колись знадобиться другий трек у геро — знімати нову карту в src/site/song.js, а не міняти TRACK.
рішенняголоси
Rachel, Onyx, Polina, Marek — без портретів
Чотири чужі голоси в таблиці порівняння студії отримують генеративний знак, а не фото.
Чому
Це продукти ElevenLabs, OpenAI та Azure. Фотореалістичне обличчя для чужого продукту вводило б в оману.
Наслідок
Нічого; знак малюється детерміновано з seed кожного голосу.
Що закриє
Якщо потрібна впізнаваність — логотипи провайдерів замість абстракції.
рішеннясайт
axe не може перевірити контраст на градієнтах
verify_all.py звітує «0 порушень», але на головній 83 перевірки контрасту axe позначає «incomplete» — тло градієнтне, і колір під текстом не визначається.
Чому
Це поведінка axe, а не помилка сайту. Тому доданий audit_contrast.py: він сплощує кожен градієнт до найгіршої зупинки й лише тоді міряє.
Наслідок
Без цього кроку «0 порушень» нічого не доводить. Перший чесний прогін знайшов 59 справжніх провалів у світлій темі.
Що закриє
Запускати python3 src/audit_contrast.py після кожної зміни кольорів; має давати 0 порушень у 22 прогонах.
рішеннясайт
Іконки мають шість ваг лінії, а не одну
Документація стверджувала «одна лінія 1,6–1,9 px». Насправді в наборі 1 · 1,6 · 1,7 · 1,8 · 1,9 · 2, де 1,6 переважає, а 2 — лише хрестик закриття шухляди.
Чому
На різних сітках однакова вага виглядає по-різному; винятки зроблені свідомо, просто не були записані.
Наслідок
Нічого візуально. Але правило, яке не збігається з набором, збиває того, хто малює наступну іконку.
Що закриє
Нічого. Брендбук тепер друкує реальний розподіл, а не бажаний.
рішеннясайт
Сирі рендери відвантажувались у продакшн
Дев'ять вихідних файлів OmniHuman і покинута спроба Seedance — 24 МБ — лежали поруч із вісьмома робочими кліпами по 1,1 МБ разом і потрапляли в деплой, бо .vercelignore не згадував .mp4.
Чому
Оригінали потрібні як джерело для перекодування, тому їх лишено на диску, але внесено до .vercelignore.
Наслідок
Було: 37 МБ у деплої. Стало: 12,9 МБ. Файли на місці.
Що закриє
Нові рендери називати за схемою _omnihuman.mp4 — маска їх підхопить сама.
Порядок — за станом: заблоковане, відкрите, відкладене, рішення. Нове питання — один запис у реєстрі, сторінка перезбирається сама.
20 розділів · 25 токенів темної теми, 28 світлої · 8 голосів · 26 аудіо · 8 відео. Сторінка не редагується вручну — правиться джерело, а не вона.