Брендбук HOMIN

Згенеровано з живих джерел проєкту: кольори з 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
ТокенЗначенняКонтраст до #0D0B0EWCAG
--ink#F6EEE417.05AAA
--ink-2#CDBFB210.91AAA
--muted#9A8F866.2AA
--ember-2#FF7A457.58AAA
--bronze-2#C4A06F8.03AAA
--ok#7FD6A411.25AAA

Світла тема

--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
ТокенЗначенняКонтраст до #E7EAF4WCAG
--ink#17162A14.75AAA
--ink-2#45445C7.82AAA
--muted#62657A4.77AA
--ember-2#AE441A4.8AA
--bronze-2#7C5E3C4.96AA
--ok#1B71455.0AA

Заливки й сяйва

Ці токени в світлій темі не фарбують текст — вони живуть у градієнтах кнопок, межах і сяйві. Відношення до тла наведене довідково: вимога 4,5:1 до них не застосовується, доки ними не почнуть писати.

ТокенЗначенняКонтраст до #E7EAF4Роль
--ember#D9732B2.71заливка, не текст
--ember-3#FFC48A1.29заливка, не текст
--bronze#A8845A2.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,03emUnbounded
Заголовок розділуclamp(24px, 3.4vw, 38px)500 · −0,02emUnbounded
Заголовок картки17–22px600 · −0,01emUnbounded
Основний текст15–16px400 · 0Manrope
Підпис12–13,5px400–500 · 0Manrope
Мітка11–12px600 · +0,06em, великіManrope
Код, число, id11–14px400–500 · 0JetBrains Mono
1Вага 700 в Unbounded — лише в лого-локапах. У заголовках 500: на 700 трекінг «розʼїжджається» на кирилиці.
2Числа, ідентифікатори й курси — завжди моно, щоб колонки не стрибали при перерахунку валюти.
3Міжрядковий: 1,6 для тексту, 1,15–1,25 для заголовків, 1 для чисел у картках.
4Фолбек — system-ui. Сторінка читомa ще до того, як приїде шрифт.

Звідки й на яких умовах

ШрифтЛіцензіяПідключення
UnboundedSIL Open Font License 1.1Google Fonts, ваги 300/500/700
ManropeSIL Open Font License 1.1Google Fonts, ваги 400/500/600/700
JetBrains MonoSIL Open Font License 1.1Google Fonts, ваги 400/500
Playfair DisplaySIL Open Font License 1.1не підключається: знак уже переведений у контури wm-playfair-85.svg
·Усі чотири під OFL — вільні для комерційного використання, вбудовування й самостійного хостингу.
!Google Fonts — єдина зовнішня залежність усього сайту. Все інше вшите в сторінку. Для повної автономності шрифти доведеться покласти поруч.

Сітка й форма

Одна колонка 980 px тримає весь сайт. Ширші значення трапляються лише там, де блок навмисно виходить за колонку — таблиця порівняння й сцена з роєм.

Контейнери

ШиринаВикористань
1480px2
1400px1
1180px1
1100px3
980px10

Точки зламу

ШиринаПравил у CSSЩо змінюється
1180px1бокова панель згортається
1100px3сітка тарифів 3 → 2
980px9головна точка: колонки → стовпчик, шапка → док
900px1таблиця порівняння отримує прокрутку
860px1картка голосу втрачає бічний підпис
700px1студія переходить у вертикальний режим
640px1«можна/не можна» в один стовпчик
600px1чипи переносяться
560px2мінімальний макет: усе в один стовпчик

Радіуси

РадіусВикористаньРоль
999px44пігулки, перемикачі, круглі кнопки
50%36аватари, іскри рою
22px17великі картки й панелі
18px7картка голосу, демо-блоки
16px10вкладені картки
14px15малі картки, textarea
12px9чипи, поля вводу
10px10input, select
6px7фокусне кільце
4px6смуга діапазону
2px5тонкі індикатори

Тінь і глибина

РівеньЗначенняДе
Базовиймежа --line, без тінікартки в потоці
Піднятий0 30px 90px rgba(0,0,0,.35)модалка, док, шухляда
Світла тема0 1px 2px rgba(60,50,120,.05), 0 10px 30px rgba(60,50,120,.08)та сама глибина, але тінь холодна — чорна на світлому читається брудом
Акцентний0 0 0 1px rgba(255,178,90,.4), 0 14px 50px var(--glow)кнопка дії під курсором
·Крок відступів — 4 px. Проміжки в сітках: 8 · 10 · 12 · 14 · 22 · 26.
·Вертикальний ритм секції: 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РазівДе
14тонкі службові позначки всередині інших іконок
1.6108основна вага — на ній намальована більшість набору
1.78поодинокі іконки з густішою геометрією
1.855другий за поширеністю крок, для дрібніших сіток
1.913найщільніші фігури, щоб не злипались
221хрестик закриття шухляди — навмисно важчий за решту

Разом 209 обведень у наборі. Нова іконка починається з 1.6; інша вага — лише коли на своїй сітці вона інакше злипається.

Можна

  • Малювати в 24 px і перевіряти в 20 px
  • Тримати currentColor — колір задає текст навколо
  • Округлі кінці: набір читається як одна рука
  • Давати aria-label, якщо поруч немає тексту

Не можна

  • Тягнути бібліотеку заради однієї іконки
  • Заливати фігуру — набір лінійний
  • Вішати колір усередині <svg> — зламає світлу тему
  • Масштабувати іконку разом із обведенням: лінія потовщає

Рух

1Єдина крива --ease: cubic-bezier(.2,.7,.2,1) — швидкий вихід, мʼяке приземлення.
2Мікровідгук 150–250 мс, поява блоку 400–450 мс, сценні переходи 900–1300 мс.
3prefers-reduced-motion вимикає рій, пульсацію й переходи — але не приховує контент і не глушить звук.
4Рій іскор — фірмовий рух. Реагує на swarm.stress(), swarm.onset() та енергію звуку.
ПодіяТривалістьКрива
Наведення, натискання150–250 мс--ease
Поява картки, розкриття400–450 мс--ease
Перехід сцени, шухляда900–1300 мс--ease
Спалах рядка в партитуріприбитий до звукубез кривої — часова мітка
Пульс рою на долю≈ 180 мс згасаннялінійне згасання

Доступність

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

ЩоЯк зробленоПеревірка
Фокусoutline:2px solid var(--ember);outline-offset:3px;border-radius:6pxєдине правило на всі сторінки
Контраст текстуусі текстові токени ≥ AA на найгіршій зупинці градієнтаpython3 src/audit_contrast.py — 22 прогони, 0 порушень
Рухprefers-reduced-motion вимикає рій і переходиконтент і звук лишаються
Клавіатурапорядок джерела збігається з візуальнимжодного tabindex > 0
Ціль дотикумінімум 40 px висоти, кнопки першого екрана 60 px
Мова документаlang="uk" у розмітціне оновлюється при перемиканні — див. відкриті питання
Пошук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#7C5E3C3,89 → 4,96
--ember-2 (світла)#B5481D#AE441A4,46 → 4,80
--ok (світла)#2E9A63#1B71452,95 → 5,00
.icard h3 small, .mock .call--ember--ember-23,26 → 5,07
.dw b i — наголошена літера--ember--ember-23,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 ₴
Englishenповний обсяг інтерфейсуUSD $
Polskiplповний обсяг інтерфейсу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 · навчання, IVREvelina — confident, calm3rWBcFHu7rpPUEJQYEqDСадо́к вишне́вий ко́ло ха́ти, хрущі́ над ви́шнями гуду́ть.Тарас Шевченко2.96 с2.93 с

Рядок кожної персони — цитата з української поезії, дібрана під її регістр. Він же служить контрольним текстом: згенероване повертається через stt й звіряється зі сценарієм.

·Голос добирається під портрет за статтю, віком і регістром, а не навпаки — фото знайти легше, ніж тембр.
·Один voice_id не використовується двічі. Перевіряється тестом.
·Верес і Калина — серія Neo: нейтральніші, для навчання та IVR.

Звук бренду

Дві пісні а капела, по два варіанти. На головній звучить letyt_1 — під нього написана партитура, тому підміна треку без нової карти міток розсинхронізує весь перший екран.

ТрекРозмірРоль
letyt_1479 КБгімн, звучить на першому екрані
letyt_2487 КБдруга версія «Летить»
nebokray_1461 КБ«Небокрай», варіант 1
nebokray_2446 КБ«Небокрай», варіант 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 % висоти зверху
Поглядпрямо в камеру
Світломʼяке студійне, ключове збоку, тепла заливка
Тлотемне нейтральне з градієнтом, без предметів і текстур
Одягоднотонний, без логотипів і дрібного візерунка
Модельgoogle/nano-banana, доводка google/nano-banana-edit

Аватар-відео

Портрет оживляє 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_resolution720 або 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 і ніколи не потрапляють у скрипти чи в сайт.

ОпераціяПостачальникЩо робить
avatarkie.aiomnihuman-1-5 — ліпсинк під власний запис
fetchмережазавантажити URL, за потреби зменшити
helpслужбоваперелік операцій — міст описує себе сам
imagekie.ainano-banana — портрет
image_editkie.ainano-banana-edit — тримає персонажа
kie_statuskie.aiопитати завдання
lipsyncHeyGenбез креденшела — див. відкриті питання
musickie.aiSuno V5_5
music_statuskie.aiопитати завдання Suno
pingслужбоваперевірка живості
sfxElevenLabsзвуковий ефект з опису
statusHeyGenбез креденшела — див. відкриті питання
sttElevenLabsрозпізнавання — ним звіряється вимова
ttsElevenLabsсинтез мовлення, до 2500 символів
uploadkie.aiпокласти файл; хост kieai.redpandaai.co
videokie.aiseedance-2-5 — сцена, не ліпсинк
voicesElevenLabsсписок доступних голосів
·17 операцій. Міст позначений «Available in MCP», тож ті самі виклики доступні як інструменти агента.
·Будь-яка гілка при збої повертає {ok:false, error}, а не падає: сторона, що викликає, завжди отримує JSON.
·Таймаут 120 с, відповідь до ~4 МБ. Довгі рендери — createTask плюс опитування, не очікування в запиті.
!Файли вантажаться на kieai.redpandaai.co. На api.kie.ai цього маршруту немає — 404, і це легко сплутати з проблемою ключа.

Домени

СередовищеАдресаСтан
Продакшнhomin.studioпублічний, TLS Let's Encrypt, відкритий для пошуку
З wwwwww.homin.studioтой самий сайт
Попереднійhomin.taskwheels.comлишається робочим як запасний
Прев'ю*.vercel.appзакриті Vercel Authentication — кастомних доменів це не стосується
МістPOST /homin-bridge у n8n Cloud17 операцій, доступний як MCP-інструмент
1DNS у Cloudflare, не у Vercel: A → 76.76.21.21 у режимі DNS only. Під помаранчевим проксі Vercel не видасть сертифікат — і одного зайвого проксійованого запису на тому самому імені досить, щоб зламати правильний.
2Сертифікат випускається явно: vercel certs issue homin.studio www.homin.studio. Автоматична верифікація тут зависає — перевірено двічі.
3python3 src/finish_domain.py робить обидва кроки й розрізняє чотири стани DNS замість «працює / не працює».
·.vercelignore тримає на місці 111 службових файлів: скрипти збірки, чернетки лого, сирі рендери, звіти перевірок. Відвантажується 90, разом 12,9 МБ.

Як масштабувати

Додати голос

1Портрет 1024×1024 у тій самій студійній естетиці → avatars/<id>.png
2Внести до VOICE_MAP у src/regen_voice_samples.py і рядок вірша у src/gen_voice_lines.py
3python3 src/gen_voice_lines.pysrc/verify_pronunciation.pysrc/batch_avatars.py <id>
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 відео. Сторінка не редагується вручну — правиться джерело, а не вона.