ШІ-веброзробка для малого бізнесу — Технічна специфікація
Назва бренду — Factory (використовується всюди в UI: навбар,
<title>, JSON-LD — більше неPLACEHOLDER). Продакшн-домен — ще не відомий: деплой на Railway, кожен сервіс отримує власний згенерований*.up.railway.app-домен (розділ 14);NEXT_PUBLIC_SITE_URL—PLACEHOLDERдоки цей домен не згенеровано. Логотип/фірмовий знак ще не визначено.
1. Overview
Багатосторінковий маркетинговий сайт для технологічної компанії, яка розробляє професійні вебсайти для малого бізнесу (масажисти, стоматологи, дитячі студії, тренери, локальні спеціалісти) за допомогою спеціалізованого ШІ-агента, побудованого на 10+ роках досвіду веброзробки. Сайт — премium/технологічний, українською мовою, і одночасно виконує три задачі: (1) генерує ліди через головний CTA «Отримати сайт», (2) пояснює методологію (людина-розробник + спеціалізований ШІ + перевірка) так, щоб не читатись як типовий «AI-білдер», (3) додає шар довіри через дослідницький (PhD) контекст, стек технологій і команду.
Сайт складається з чотирьох навігаційних маршрутів: головна (/, 11 активних секцій — розділ 7.1), /research, /technology, /team. Додатково є один утилітарний маршрут поза навігацією — /spec: рендерить актуальний текст цієї специфікації у спливаючому вікні, відкривається кнопкою в секції 8 «Це наш продукт» (розділ 7.1). Ціноутворення — секція на головній (#pricing), без окремого маршруту (обґрунтування — розділ 4). «Готово» означає: усі активні секції головної та три навігаційні під-сторінки реалізовані згідно копірайту нижче, форма ліда реально працює (надсилає повідомлення в Telegram через окремий Elysia-бекенд — нічого не персистується в базі даних), демо бронювання та демо інтерфейсу правок — статичні/клієнтські макети без бекенду, шейдер у hero працює і коректно деградує, сайт проходить перевірки продуктивності/доступності зі skill frontend.
Порівняно з початковою версією брифу: дві секції прибрані з головної повністю — AI development system flow та Technology тизер (компоненти й ексклюзивні контент-константи прибрані з кодової бази; повна сторінка
/technologyне постраждала). «Website examples» (портфоліо) з початкового брифу в продукті більше немає — не планується й не описується в цьому документі. Замість видалених секцій з'явилась нова секція 8 «Це наш продукт» — самореферентний доказ методології з кнопкою, що відкриває цю специфікацію. Див. розділ 15 «Assumptions».
2. Goals & Non-Goals
Goals
- Донести головне позиціювання: 10 років досвіду → інженерні знання → спеціалізований ШІ-агент → продукти, що виходять швидко, під наглядом розробника.
- Провести нетехнічного власника малого бізнесу через шлях: «я можу отримати нормальний сайт без великих витрат» → «можна додати онлайн-запис» → «за цим стоять реальні розробники й технологія» → «хочу обговорити свій сайт».
- Дати технічному відвідувачу (розробник, дослідник) достатньо деталей (стек, методологія, дослідження), щоб сприйняти систему серйозно.
- Зібрати реальний лід через CTA «Отримати сайт» / «Обговорити проєкт» (єдина функціональна форма на сайті).
- Показати онлайн-запис і інтерфейс збору правок як переконливі, візуально якісні макети — без побудови реального бекенду для них.
- Тримати ціноутворення максимально прозорим: розробка безкоштовна за акцією, хостинг 600 грн/міс, домен окремо.
Non-Goals
- Не будувати реальну систему онлайн-запису для самого сайту компанії (це продукт, який компанія продає клієнтам, а не функціонал власного маркетингового сайту).
- Не будувати авторизацію, CRM або адмін-панель для перегляду лідів — кожен лід приходить окремим повідомленням у Telegram-чат розробника; немає ні бази даних, ні скрипту тріажу, ні live-дашборду. Історія лідів — це історія переписки в Telegram, не окрема система запису.
- Не робити багатомовність — сайт лише українською.
- Не робити блог/CMS, не приймати оплату на самому сайті.
- Не вигадувати статистику, клієнтів, відгуки, нагороди чи цифри продуктивності, яких не було надано.
- Не будувати реальний scheduling для «Обговорити сайт з розробником» — це проста зовнішня лінк-кнопка на
nedoshev.dev/meet.
3. Users & Key Flows
A. Власник малого бізнесу (нетехнічний), головний сценарій
Заходить на / → бачить hero (обіцянка + CTA) → прокручує довіру/проблему/рішення → дивиться 6 кроків розробки та демо інтерфейсу правок → бачить демо онлайн-запису, впізнає свій випадок (масаж/групові заняття/тощо) → переглядає приклади сайтів для своєї ніші → бачить розділ ціноутворення (безкоштовно за акцією + хостинг) → читає FAQ → тисне «Отримати сайт» → заповнює форму ліда (ім'я, контакт, тип бізнесу, коментар) → бачить підтвердження.
B. Власник, який хоче спочатку поговорити з людиною
У фінальному CTA або в контактному блоці бачить «Обговорити сайт з розробником» → клік відкриває зовнішнє посилання nedoshev.dev/meet в новій вкладці (жодної форми на самому сайті).
C. Технічний/академічний відвідувач (розробник, дослідник, потенційний партнер) Заходить через hero → зацікавлений формулюванням «перетворили досвід на ШІ-агента» → переходить у навбарі на «Технології» → дивиться повний стек по категоріях → переходить на «Дослідження» → читає обидва research-картки, розуміє гіпотези й методологію → переходить на «Команда» → бачить, хто стоїть за продуктом → або надсилає лід, або йде з довірою до бренду.
D. Повторний відвідувач, що порівнює пропозиції
Отримує пряме посилання на /#pricing (наприклад, від розробника в переписці) → одразу бачить структуру ціни без проходження всієї сторінки заново → переходить до FAQ або форми ліда.
4. Scope
Тип застосунку: комбінація — багатосторінковий маркетинговий/лендинг-сайт (основний обсяг) + один реальний бекенд-ендпоінт для форми ліда (мінімальний, без бази даних узагалі — ендпоінт лише пересилає повідомлення в Telegram). Немає адмін-панелі, немає реального booking-бекенду.
| Частина | Skill(и) |
|---|---|
| Усі 4 сторінки, вся верстка/дизайн/секції | frontend (обов'язково — SKILL.md, а перед кожною секцією відповідний references/*.md) |
| Hero-шейдер (фон) | frontend/references/animated-backgrounds.md — за драбиною витрат зупинитись на OGL/WebGL, оскільки ефект — один шейдер (потік/спотворення), а не мультипрохідна сцена; ескалація до vgpu (WebGPU) — опційна, лише якщо піксель-шейдер OGL не тягне бажаний «інженерний» ефект (композиція кількох шарів, ping-pong буфери) |
| Демо онлайн-запису (секція 6) | frontend/references/booking-section.md для дизайну секції; appointments skill НЕ використовується — це презентаційний макет із хардкод-даними, без слот-логіки, без БД |
| Демо інтерфейсу збору правок (частина секції 5) | Немає окремого skill — простий клієнтський стан (React), лише вигляд |
| FAQ, Pricing, Testimonials-подібні секції | frontend/references/sections.md, cards.md, testimonials-section.md (якщо колись з'являться реальні відгуки — зараз їх немає, розділ пропущено, див. Assumptions) |
| Форма ліда «Отримати сайт» / «Обговорити проєкт» (єдиний реальний бекенд) | backend + elysiajs + bun skills (Elysia-сервіс) + Telegram Bot API (fetch напряму, без SDK/skill — немає бази даних узагалі) + captcha skill (honeypot + timing на формі) |
| Адмін-панель | Не потрібна — немає бази даних, яку можна було б тріажити; кожен лід — окреме повідомлення в Telegram |
Pricing: анchor чи окремий маршрут? Анкор #pricing на головній. Обґрунтування, узгоджене з конвенціями frontend skill: ціна — одна проста модель (одна «пропозиція», не порівняння тарифів), не потребує окремого маршруту чи SEO-цінності асинхронної сторінки; секція вже входить у наратив головної (sections.md — «Objection handling» перед FAQ). Інші сторінки (/research, /technology, /team) за потреби посилаються глибоким лінком на /#pricing, а не дублюють вміст.
5. Tech Stack
| Шар | Вибір | Обґрунтування |
|---|---|---|
| Runtime | Bun | Фіксовано проєктом (CLAUDE.md/bun skill) |
| Структура репозиторію | Bun workspaces monorepo — корінь із package.json (workspaces: ["frontend", "backend"]), один bun install на весь репозиторій, один bun run dev піднімає фронтенд і бекенд паралельно (bun run --filter '*' --parallel dev) |
Два незалежні пакети (frontend/, backend/) з окремими білдами, але спільним встановленням залежностей і єдиною dev-командою; build/lint/typecheck/test так само агреговані через --filter '*' --if-present |
| Фронтенд-фреймворк | Next.js (App Router), TypeScript | Фіксовано frontend skill; 4 навігаційні маршрути + утилітарний /spec природно лягають на App Router (маршрути /, /research, /technology, /team — у групі (site) зі спільним layout-ом навбару/футера; /spec — поза групою, без навбару/футера, див. розділ 7.1 секцію 8) |
| Рендеринг | Static export (output: 'export') |
Увесь контент — статичний маркетинг; єдина динамічна дія (форма ліда) — це fetch() до окремого Elysia-сервісу, тож сервер Next.js не потрібен |
| Стилі | Tailwind CSS v4 (@theme у globals.css) |
Фіксовано skill; усі токени кольору з розділу 10 живуть тут |
| Типографіка | next/font — Geist Sans (display/body) + Geist Mono (числа, мітки кроків, технічні деталі) |
Геометричний, техно-нейтральний грощеск, що читається як «інженерний продукт», без ліцензійних ризиків; моно-шрифт підсилює «технічні деталі UI», яких просить бриф |
| Анімація/хореографія скролу | GSAP + ScrollTrigger | Стандарт skill для reveal/pin |
| Компонентна анімація | Звичайні CSS-переходи (Tailwind transition-*) |
motion/react не додавали — усі ховери/демо-стани (booking, change-request, nav CTA) покриваються CSS-переходами; зайва залежність прибрана під час аудиту JS-бюджету (розділ 12) |
| Плавний скрол | Lenis | Стандарт skill, вимикається на prefers-reduced-motion і <768px |
| Hero-фон | OGL (WebGL, ~12kb) | Один шейдер-ефект (потік/шум/спотворення на курсор), чорно-золота палітра з токенів; статичний постер до готовності контексту; graceful fallback, якщо WebGL недоступний |
Markdown → HTML (лише для /spec) |
marked |
Рендерить цю специфікацію в HTML на етапі білду (Server Component, fs.readFileSync + marked.parse) — нуль байтів у клієнтському бандлі; використовується виключно на утилітарній сторінці /spec, не на маркетингових сторінках |
| Форми | react-hook-form + zod | Валідація форми ліда на клієнті; та ж zod-схема повторюється на бекенді |
| Бот-захист форми | captcha skill (honeypot + timing, Turnstile — лише якщо з'явиться спам) |
Форма ліда — єдина публічна форма запису в БД на сайті |
| Бекенд (лише для форми ліда) | Bun + ElysiaJS, окремий процес/контейнер | Фіксовано backend skill — статичний фронтенд не має власного сервера |
| База даних | Немає жодної. | Ліди нічого не персистують — POST /leads лише пересилає повідомлення в Telegram і повертає успіх/помилку; замінило Firestore, щоб не тримати окрему БД + сервіс-акаунт заради єдиної форми на сайті |
| Сповіщення про ліди | Telegram Bot API, прямий fetch (Bun-нативний, без SDK) |
POST https://api.telegram.org/bot<token>/sendMessage з chat_id+text; src/lib/telegram.ts |
| Іконки | Lucide React, stroke-width={1.5}, лише де іконка — афорданс (гамбургер, зовнішнє посилання, соцмережі у футері) |
rules/icons.md — за замовчуванням без іконок |
| Хостинг | Власна керована інфраструктура компанії (Docker + Linux + reverse proxy + CDN) — див. розділ 14 | Автентична деталь: власний сайт компанії розгорнутий на тій самій інфраструктурі, яку вона продає клієнтам (розділ «Технологія», категорія Infrastructure) |
6. Information Architecture
| Маршрут | Призначення | Ключові компоненти | Дані |
|---|---|---|---|
/ |
Головна — повна історія позиціювання, 11 активних секцій (розділ 7.1) | Hero, TrustSection, ProblemSection, SolutionSection, HowItWorks (+ change-request demo), BookingDemo, ResearchTeaser, BuildProofSection, PricingSection, FaqSection, FinalCta (форма ліда) |
Хардкод-контент (типізовані константи, розділ 8) + один реальний POST до /leads |
/research |
Повний опис дослідницької (PhD) методології + картки досліджень (наразі 7, не 2 — реальні публікації з реальними посиланнями, більше не TBD) |
ResearchIntro, ResearchCard × N (мапиться по масиву, не фіксована кількість) |
Хардкод-контент; посилання на публікації здебільшого реальні (див. розділ 16 щодо решти) |
/technology |
Повний технологічний стек по категоріях | TechCategoryBlock × 5 (Frontend/Backend/Database/Infrastructure/AI) |
Хардкод-контент |
/team |
Картки ролей — наразі 5: Founder/Lead Developer, Founder/Backend Engineer, і троє під роллю «Consultants / Advisors» (безпека, UI/UX, юридичні питання) | TeamMemberCard × N (мапиться по масиву) |
Хардкод-контент, усі імена реальні — жодного PLACEHOLDER не лишилось (розділ 16 оновлено) |
/spec |
Утилітарний, поза навігацією. Рендерить цю специфікацію (Markdown → HTML на етапі білду) у мінімальному layout без навбару/футера — призначений для відкриття у спливаючому вікні (window.open) кнопкою в секції 8 головної, не для прямого переходу з меню |
Server Component, читає docs/specs/ai-web-development-studio.md через fs відносно кореня монорепо, конвертує marked-ом; SpecCloseButton (client, закриває вікно або веде на /) |
Немає — рендериться напряму з файлу специфікації, єдине джерело правди |
Навігація (усі сторінки): Головна · Як це працює · Дослідження · Технології · Команда, CTA праворуч — «Отримати сайт». «Як це працює» з будь-якої під-сторінки веде на /#how-it-works; «Головна», «Дослідження», «Технології», «Команда» — прямі маршрути. /spec у навігації немає навмисно. CTA навбару на під-сторінках веде на /#get-site (форма ліда живе лише на головній).
Nav CTA — не дублювати CTA hero. Поки hero у в'юпорті, навбар не показує кнопку «Отримати сайт» (вона й так уже показана великою в hero) — IntersectionObserver на #hero (з rootMargin, що дорівнює висоті навбару) перемикає видимість/aria-hidden/tabIndex кнопки, коли hero йде за навбар. На сторінках без hero (/research, /technology, /team, /spec) кнопка показана завжди. Деталі патерну і важливий hydration-нюанс (початковий стан не можна обчислювати з document у useState-ініціалізаторі — інакше SSR/клієнт розходяться і React не перемальовує невдалий hydration-мисматч) задокументовані в frontend/references/navbar.md → «Don't show the same CTA twice at once».
7. Sections & Content
7.1 Головна сторінка — 11 активних секцій по порядку
1. Hero
- H1 (verbatim): «Ми перетворили 10 років досвіду у веброзробці та створенні інструментів для розробників на спеціалізованого ШІ-агента.»
- Підзаголовок (verbatim): «Він допомагає нам швидко створювати швидкі, легкі та професійні вебсайти з онлайн-записом для малого бізнесу.»
- Primary CTA: «Отримати сайт» (веде на
#get-site). Secondary CTA (текстове посилання, не друга кнопка): «Як це працює» (веде на#how-it-works). - Фон: інтерактивний WebGL-шейдер (OGL) — переважно чорний (
--background-950), з тонкими золотими деталями (--primary-500/--accent-500), органічний потік/шум (simplex/curl noise), демпфоване (lerp 0.06–0.1) слідування за курсором, ніякого «AI-блоба». Поверх шейдера — легкий DOM/SVG-оверлей: стилізована, напівпрозора панель-каркас («wireframe» інтерфейсу сайту), що ледь помітно змінюється/«добудовується» (opacity/position transitions на кількох прямокутниках-плейсхолдерах), щоб дослівно натякнути на «інтерфейс сайту, що генерується всередині інженерного середовища», без будь-яких людиноподібних роботів чи мозків. - Статичний постер (CSS-градієнт у тонах
--background-950→--primary-900) рендериться першим кадром; канвас проявляється після ініціалізації WebGL.prefers-reduced-motionі відсутність WebGL → лишається постер. - H1 — LCP-елемент, видимий без JS.
2. Trust / Expertise
- Головна теза: «10+ років у веброзробці» — команда будувала UI-бібліотеки, вебзастосунки, інструменти розробки, frontend-інфраструктуру, backend-системи, бази даних, продуктивні застосунки.
- Формат: три колонки без карток (
cards.md— «plain columns» варіант), хедлайн + одне речення на кожну, без іконок, без вигаданих цифр/клієнтів/нагород — суто якісне формулювання. - Приклад копірайту (можна редагувати збережучи зміст): «Десять років у інженерії», «UI-бібліотеки та вебзастосунки, з якими працювали тисячі рядків production-коду», «Frontend, backend, бази даних і безпека — не окремі підрядники, а одна команда».
3. Problem
- Пояснює традиційні варіанти для малого бізнесу: агенції (дорого), конструктори сайтів (прості, але вимагають кількох підписок/інтеграцій), DIY (забирає час власника).
- Перехідний рядок (verbatim): «Ми автоматизували найдорожчу частину процесу.»
- Ідея: людська експертиза визначає систему, ШІ виконує повторювану реалізацію, розробник-людина перевіряє результат.
- Формат: три короткі блоки (агенції/конструктори/DIY) → окремий, більший рядок з переходом, візуально відокремлений (напр. на темнішій підповерхні).
4. Solution
- Коротко презентує саму компанію-систему як відповідь на розділ 3: людина визначає вимоги й стандарти → спеціалізований ШІ-агент виконує реалізацію → розробник перевіряє. Місток до секції 5.
5. How development works (критична секція) — 6 кроків
Формат: великі легкі номери 01–06 (без іконок, cards.md/sections.md — «Steps»), з'єднувальна тонка лінія між кроками на десктопі, вертикальний стек на мобільному.
- 01 — Розмова з розробником. Клієнт спілкується напряму з розробником; разом визначають бізнес-цілі, потрібні сторінки, послуги, контент, візуальний напрям, функціонал, вимоги до онлайн-запису, інтеграції.
- 02 — Формування вимог. Вимоги структуруються у чіткий технічний документ.
- 03 — Спеціалізований ШІ-агент. Агент отримує вимоги й розробляє продукт, використовуючи спеціалізовані навички та інженерні знання.
- 04 — Перевірка розробником. Згенерований результат перевіряє досвідчений розробник; ШІ самостійно не вирішує, що робота завершена — розробник валідує і може запросити зміни.
- 05 — Клієнтська перевірка. Клієнт переглядає сайт; якщо потрібні зміни, формує структурований список правок.
- 06 — Запуск. Після затвердження сайт розгортається і підключається до домену клієнта.
5a. Демо-макет інтерфейсу правок (вкладена частина кроку 05, лише візуальна, без реальної логіки)
- Показує браузерний фрейм сторінки-прикладу; клік на елемент (напр. «Кнопка Записатися») відкриває коментар-пін з текстом-прикладом: «Зробити кнопку трохи більшою та змінити текст на «Записатися на масаж».» Праворуч/знизу — панель, що збирає такі коментарі у структурований список правок (2–3 приклади заздалегідь заповнені).
- Реалізація: 2–3 заздалегідь визначені hotspot-точки на статичному макеті сторінки; клік перемикає локальний React-стан (показати/сховати пін і рядок у списку). Жодного реального збереження, жодного бекенду — цей інтерфейс «вже існує окремо і буде інтегрований пізніше» (цитата з брифу).
6. Online booking (демо, ключова можливість продукту)
- Крок 1 «Оберіть послугу»: приклад карток — «Класичний масаж · 60 хв · 800 грн», «Спортивний масаж · 60 хв · 1000 грн».
- Крок 2 «Оберіть дату» → календар-приклад.
- Крок 3 «Вільний час»:
10:00, 11:30, 14:00, 15:30, 17:00. - Крок 4 «Ваші дані»: поля «Ім'я», «Телефон» (лише в макеті — нічого нікуди не надсилається). Поле «Телефон» — з UA-маскою (
099 999 99 99/+380 099 999 99 99, автовизначення з першого введеного символу —0чи+/380),frontend/references/input-masks.md. - CTA кроку: «Записатися».
- Далі — пояснювальний блок про адаптивність системи: індивідуальні спеціалісти, салони, клініки, терапевти, масажисти, дитячі студії, групові заняття.
- Приклад групового заняття: «Малювання для дітей», «Субота · 11:00», лічильник місць «8 / 12 місць», CTA «Записатися на заняття».
- Реалізація: повністю клієнтський демо-компонент (
useState, крокова навігація), дані — типізовані константи (розділ 8), доступний з клавіатури, ніякого мережевого виклику. - Поруч або нижче — вторинний контактний варіант: «Обговорити сайт з розробником» → зовнішнє посилання
https://nedoshev.dev/meet(target="_blank" rel="noopener"), без будь-якої системи запису на самому сайті.
AI development system — видалена повністю Секція «Вимоги → Спеціалізовані skills → AI development agent → Code generation → Automated checks → Developer review → Production» та її ексклюзивна контент-константа видалені з кодової бази (компонент, імпорт, дані) — рішення власника продукту, не юридичне. Секція 8 нижче («Це наш продукт») тепер несе аналогічну функцію (довести, що методологія реальна), іншим способом — прямим доказом, а не діаграмою.
7. Research (тизер)
- Заголовок (verbatim): «Від досліджень до production».
- Пояснення: методологія розробки пов'язана з поточним PhD-дослідженням того, як структуровані знання, семантичний пошук і LLM можуть покращити розробку ПЗ та генерацію UI. Комерційний продукт — також реальне тестове середовище для дослідження.
- Картки: тизер показує перші 4 публікації масиву (
RESEARCH_PAPERS.slice(0, 4)), під ними рядок «Та інші», якщо публікацій більше — вже так: масив зараз містить 7 реальних публікацій (не 2, як у початковому брифі), здебільшого з реальними зовнішніми посиланнями. Кількість карток у тизері й на/researchбільше не хардкодиться на «2» ніде в коді — обидві сторінки мапляться по довжині масиву. - CTA: «Переглянути всі дослідження» →
/research.
8. Це наш продукт (build proof) — НОВА секція
- Headline (verbatim, зміст обов'язковий): «Цей сайт — живий приклад нашого процесу.»
- Body: пояснює, що сайт розроблений тим самим процесом, що описаний в секції 5 (розмова → вимоги → генерація → перевірка розробником), і що на це пішло приблизно 30 хвилин.
- Верстка: асиметрична 12-колонкова сітка — текст+CTA у
md:col-span-7, велике мono-число~30+ підпис «хв» уmd:col-span-4 md:col-start-9(той самий патерн великих mono-чисел, що й у прайсингу). - CTA-кнопка: «Переглянути технічну специфікацію» → відкриває
/specу спливаючому вікні (window.open('/spec', 'site-spec', 'width=880,height=900,resizable=yes,scrollbars=yes')), не в модалці й не в тому ж вікні. Реалізація:<a href="/spec" target="_blank" rel="noopener noreferrer">зonClick, що викликаєwindow.openі робитьpreventDefault()лише якщо попап справді відкрився (popup !== null) — якщо блокувальник попапів це заблокував, посилання деградує до звичайної нової вкладки замість того, щоб мовчки нічого не робити. /spec— окремий маршрут (розділ 6, 7.5 нижче), не в навігації, без навбару/футера.
9. Pricing (id="pricing")
- «Розробка сайту»:
~~2 000 грн~~ → БЕЗКОШТОВНО (за акцією). - «Хостинг»:
600 грн / місяць— включає хостинг, SSL, деплой, керування інфраструктурою, базове обслуговування, доступність. - «Домен»: «Оплачується окремо».
- Верстка: як прайс-картка (
cards.md— pricing card conventions), цінаtabular-numsвеликим кеглем, закреслена стара ціна семантично через<s>, а не лише CSS-декорацію. - Обов'язково читається однозначно: немає великої передоплати за розробку.
10. FAQ — точні пари питання/відповідь (не більше і не менше, порядок збережено; формат <details>/<summary>, максимум 6 зі sections.md тут навмисно перевищено, бо контент фіксований клієнтом):
- Сайт справді безкоштовний? — Розробка сайту зараз доступна без оплати за акцією. Ви сплачуєте хостинг та домен.
- Що входить у сайт? — Landing pages, послуги, ціни, галерея, контакти, форми, адаптивний дизайн, базове SEO та онлайн-запис — залежно від вимог проєкту.
- Чи можна додати онлайн-запис? — Так.
- Чи можна записуватися на групові заняття? — Так, система може працювати не лише з індивідуальними записами, а й з груповими заняттями та обмеженою кількістю місць.
- Чи можу я використовувати власний домен? — Так.
- Хто створює сайт? — Розробку прискорює спеціалізований AI-агент, але результат проходить перевірку розробником.
- Чи можу я попросити зміни? — Так. Для цього передбачений зручний процес формування документа зі змінами.
- Що відбувається після запуску? — Сайт розміщується на керованій інфраструктурі, а клієнт сплачує щомісячний хостинг.
11. Final CTA (id="get-site")
- Повторює «Отримати сайт» великим кеглем, full-bleed інвертована поверхня (
footer-section.md— «statement footer» патерн, але це не сам футер — окрема секція перед ним). - Тут же живе єдина реальна форма-лід: поля «Ім'я» (required), «Телефон або email» (required, хоча б один спосіб зв'язку), «Тип бізнесу» (select з варіантами з брифу + «Інше», optional), «Коментар» (textarea, optional). Прихований honeypot + timestamp-поле за
captchaskill. Кнопка сабміту: «Отримати сайт». Поруч — вторинне текстове посилання «Обговорити сайт з розробником» →nedoshev.dev/meet. - «Телефон або email» — комбіноване поле, тож не може взяти на себе жорстку телефонну маску безумовно: маска (
099 999 99 99/+380 099 999 99 99) застосовується, лише поки значення ще виглядає як телефон (цифри/+/дужки/тире/пробіли); щойно з'являється буква чи@— маскування вимикається саме тому й email вводиться звичайним текстом. Деталі патерну —frontend/references/input-masks.md. - Після успішного сабміту — inline-підтвердження (без редіректу): «Дякуємо! Ми зв'яжемось найближчим часом» (робочий текст, редагований копірайтером пізніше).
7.2 /research
- Вступ: пояснює, що дослідницька методологія (PhD) → перевіряється на реальних production-сайтах компанії. Тон: сучасна дослідницька лабораторія / технічний архів публікацій, не університетський шаблон.
- Картки (детальніше, ніж тизер на головній), кожна з полями: заголовок, рік, короткий опис, дослідницьке питання, релевантність до технології компанії, зовнішнє посилання на публікацію. Масив тепер містить 7 реальних публікацій, не 2 гіпотетичні з початкового брифу — сторінка мапиться по довжині масиву (
RESEARCH_PAPERS.map), нову публікацію можна додати без зміни коду сторінки. Усі 7 наразі мають і реальний рік, і реальний зовнішнійpublicationUrl— жодногоTBD/nullне лишилось; тип (year: number | 'TBD',publicationUrl: string | null) досі допускає обидва як запасний варіант для майбутнього запису без підтверджених даних, просто наразі жоден існуючий запис їх не використовує. - CTA нижче карток: «Обговорити технологію» →
/#get-site. - Це найбільш «живий» контент сайту — оновлювати масив
RESEARCH_PAPERSуsrc/lib/content.tsбезпосередньо при появі нової публікації, специфікацію після цього синхронізувати (розділ 16).
7.3 /technology
Повна версія тизера з головної, по категоріях, кожна — окремий блок (не картка-з-іконкою, а текстовий блок + список технологій моно-шрифтом):
- Frontend — Next.js, React, TypeScript, Tailwind CSS.
- Backend — Bun, Elysia.
- Database — PostgreSQL.
- Infrastructure — Docker, Linux, reverse proxy, CDN, автоматичний деплой, моніторинг, бекапи.
- AI — Large Language Models, спеціалізовані skills для розробки, семантичний пошук, retrieval документації, автоматична генерація коду, автоматичні перевірки якості.
- Примітка (як і в тизері): стек може змінюватися в часі; не кожна технологія використовується в кожному проєкті.
- Тон: демонструє інженерну компетентність, не перевантажує нетехнічного відвідувача — короткі описові речення під кожною категорією, без глибокого туторіалу.
7.4 /team
Картки ролей, cards.md — plain grid (md:grid-cols-3), без stock-фото, без вигаданих досягнень. Наразі 5 карток, не 3 — команда додала «Consultants / Advisors» як третю категорію ролі, окрім засновників:
- Максим — Founder / Lead Developer. Фокус: web architecture, frontend engineering, UI/UX design, AI-assisted development, developer tooling, research.
- Олександр — Founder / Backend Engineer. Фокус: backend architecture, databases, distributed systems, monitoring and observability, secure development practices.
- Максим — Consultants / Advisors (кібербезпека). Фокус: cybersecurity, data protection, secure development practices.
- Ліза — Consultants / Advisors (UI/UX). Фокус: UI/UX design.
- Діана — Consultants / Advisors (юридичні питання). Фокус: legal aspects, intellectual property protection, legal compliance.
Усі 5 імен реальні — жодного PLACEHOLDER не лишилось (розділ 16 оновлено відповідно). При md:grid-cols-3 і 5 картках останній рядок — 2 картки, не 3; це прийнятно, спеціально сітку під непарну кількість не переробляли. Жодних біографій чи досягнень понад дані вище не вигадувати — якщо додається новий консультант/розробник, дотримуватись того самого мінімального формату (ім'я, роль, 1 речення опису, список фокус-областей).
7.5 /spec (утилітарний, поза навігацією)
- Не маркетингова сторінка — рендерить цю специфікацію (файл
docs/specs/ai-web-development-studio.md, той самий, що ви зараз читаєте) у HTML на етапі білду, для перегляду в спливаючому вікні з кнопки секції 8 головної (розділ 7.1). - Немає навбару/футера (окремий route-layout поза групою
(site), розділ 6) — лише мінімальний хедер із міткою «ТЕХНІЧНА СПЕЦИФІКАЦІЯ» і кнопкою «Закрити» (SpecCloseButton, client-компонент): якщо вікно маєwindow.openerабо порожню історію — викликаєwindow.close(); інакше веде на/як звичайне посилання (next/link). - Рендеринг:
fs.readFileSyncшляхуpath.join(process.cwd(), "src", "content", "spec.md")у Server Component (без"use client") →marked.parse(markdown, { gfm: true })→dangerouslySetInnerHTML. Уся трансформація відбувається під часnext build, у клієнтський бандлmarkedне потрапляє. - Стилізація — клас
.spec-docуglobals.css: заголовки/параграфи/списки/таблиці/код-блоки у темній палітрі сайту, код і заголовки таблиць — Geist Mono, посилання —--primary-400. - Не читає канонічний файл напряму — читає синхронізовану копію, і це навмисно, не забута обіцянка «без дублікатів». Початковий дизайн читав
../docs/specs/ai-web-development-studio.md(батьківську щодоfrontend/теку) — працювало локально, але падало на Railway зENOENT:rootDirectory: "frontend"(розділ 14) білдить цей пакет ізольовано, без доступу до сусідніх тек монорепо. Реальний фікс:frontend/src/content/spec.md— копія, яку читає ця сторінка;frontend/sync-spec.ts(виконується як частинаbun run build, доnext build) перезаписує її з кореневогоdocs/specs/ai-web-development-studio.md, коли той видимий (будь-який локальний білд з повним чекаутом монорепо) — і тихо нічого не робить, коли ні (ізольований білд Railway, лишається остання закомічена копія). Канонічний файл для редагування людиною — і надалі корінь репозиторію;frontend/src/content/spec.md— лише білд-артефакт, який синхронізується автоматично, доки перед комітом виконувався хоча б один локальний білд.
8. Data Model
Сайт повністю статичний — немає жодної персистентності. Форма ліда не пише в жодну базу даних; POST /leads пересилає дані в Telegram і забуває про них. Лід «зберігається» рівно настільки, наскільки Telegram зберігає історію чату.
Lead (вхідні дані форми — тип LeadInput, спільний для zod-схеми фронтенду й бекенду, розділ 9)
| Поле | Тип | Опис |
|---|---|---|
name |
string | Ім'я з форми |
contact |
string | Телефон або email (одне поле, вільний формат — валідація на непорожність) |
businessType |
string (опційно) | Тип бізнесу (опційний select/free text) |
message |
string (опційно) | Коментар (опційний) |
source |
'get-site' | 'discuss-project' |
Який CTA/форма ініціювали лід |
page |
string | Шлях сторінки, з якої надіслано (/, /technology, …) |
Ці ж поля (крім пасток бот-захисту) форматуються в текст одного Telegram-повідомлення — formatLeadMessage() у backend/src/lib/telegram.ts. Немає ні id, ні status, ні createdAt — нічого з цього не потрібне, коли немає запису для ідентифікації/тріажу/датування.
Поля-пастки бот-захисту (captcha skill) подорожують у тому ж запиті, але ніколи нікуди не пересилаються: company_website (honeypot, має бути порожнім), renderedAt (timestamp рендеру форми).
Контентні типи (build-time константи, не персистуються, живуть у коді фронтенду)
type Service = { name: string; durationMinutes: number; priceUah: number }
type TimeSlot = { time: string; available: boolean }
type ClassSession = { name: string; day: string; time: string; seatsTaken: number; seatsTotal: number }
type ResearchPaper = { title: string; year: number | 'TBD'; description: string; researchQuestion: string; relevance: string; publicationUrl: string | null /* most entries now have a real URL — 'TBD'/null is the exception, not the default */ }
type TeamMember = { name: string | null /* null → render as visible PLACEHOLDER; no entry currently uses null */; role: string; focus: string[]; description?: string }
type TechCategory = { name: string; items: string[] }
type FaqItem = { question: string; answer: string }
Немає жодної бази даних, схеми міграцій чи ORM на всьому сайті.
9. API / Integrations
POST /leads (Elysia-бекенд, окремий origin від статичного сайту)
Request body (zod, спільна схема з react-hook-form):
{
name: string, // min 2
contact: string, // min 1 — телефон або email, вільний формат
businessType?: string,
message?: string,
source: 'get-site' | 'discuss-project',
page: string,
company_website: string, // honeypot — має лишатись порожнім
renderedAt: number, // timestamp рендеру форми
}
Response: { id: "ok" } на успіх (буквально той самий літерал і для реального надсилання, і для фейкового успіху при спрацюванні honeypot/timing, за captcha skill — немає реального id, бо немає запису, який він міг би ідентифікувати), реальна помилка з кодом 4xx/5xx через onError Elysia лише при валідній, але невдалій операції.
Ендпоінт: валідація zod → перевірка honeypot/timing (captcha skill) → POST в Telegram Bot API (src/lib/telegram.ts, sendLeadNotification). Помилки: 500 якщо TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID не задані; 502 якщо сам HTTP-запит до Telegram впав (мережа) або Telegram відповів не-2xx (наприклад, бот не запущений отримувачем — «chat not found»).
Зовнішні інтеграції
- Telegram Bot API (
https://api.telegram.org/bot<token>/sendMessage) — єдиний спосіб, яким лід взагалі кудись потрапляє. Прямийfetch, без бібліотеки-обгортки. Бот має бути заздалегідь запущений отримувачем (/startу чаті з ним) — інакше Telegram відхиляє надсилання, і чат/user id для цього не допомагає. https://nedoshev.dev/meet— просте вихідне посилання («Обговорити сайт з розробником» / «Поговорити з розробником»), без API-виклику,target="_blank" rel="noopener noreferrer".- Посилання на публікації досліджень на
/research— усі 7 записів мають реальний зовнішній URL (розділ 7.2/16), більше неPLACEHOLDER.
Env vars (лише назви, без значень)
Frontend (.env статичного Next.js-застосунку):
NEXT_PUBLIC_API_BASE_URL— базовий URL Elysia-бекенду для сабміту форми ліда.
Backend (Elysia-сервіс, .env, ніколи не в браузері):
TELEGRAM_BOT_TOKEN— токен бота від @BotFather.TELEGRAM_CHAT_ID— куди слати ліди: особистий user id (дізнатись через @userinfobot) або id групи/каналу; бот має бути заздалегідь запущений/доданий туди.CORS_ORIGIN— реальний деплой-origin статичного фронтендуTURNSTILE_SECRET_KEY— лише якщо пізніше з'явиться потреба ескалації бот-захисту (не потрібен на старті)
Frontend, лише якщо буде додано Turnstile:
NEXT_PUBLIC_TURNSTILE_SITE_KEY
10. Design Direction
Токени кольору (точні значення з брифу, використовувати як є)
--text-50: #f5f3ef; --text-100: #ebe7e0; --text-200: #d7cec1; --text-300: #c3b6a2; --text-400: #af9d83;
--text-500: #9c8563; --text-600: #7c6a50; --text-700: #5d503c; --text-800: #3e3528; --text-900: #1f1b14; --text-950: #100d0a;
--background-50: #f7f3ee; --background-100: #eee8dd; --background-200: #ded1ba; --background-300: #cdba98; --background-400: #bda375;
--background-500: #ac8b53; --background-600: #8a7042; --background-700: #675432; --background-800: #453821; --background-900: #221c11; --background-950: #110e08;
--primary-50: #f8f4ec; --primary-100: #f2e9d9; --primary-200: #e4d3b4; --primary-300: #d7be8e; --primary-400: #caa868;
--primary-500: #bd9242; --primary-600: #977535; --primary-700: #715828; --primary-800: #4b3a1b; --primary-900: #261d0d; --primary-950: #130f07;
--secondary-50: #faf5eb; --secondary-100: #f5ead6; --secondary-200: #ebd5ad; --secondary-300: #e0c085; --secondary-400: #d6ab5c;
--secondary-500: #cc9633; --secondary-600: #a37829; --secondary-700: #7a5a1f; --secondary-800: #523c14; --secondary-900: #291e0a; --secondary-950: #140f05;
--accent-50: #fbf5e9; --accent-100: #f8ebd3; --accent-200: #f1d7a7; --accent-300: #eac37b; --accent-400: #e2af50;
--accent-500: #db9b24; --accent-600: #af7c1d; --accent-700: #845d15; --accent-800: #583e0e; --accent-900: #2c1f07; --accent-950: #161004;
Ці значення йдуть у Tailwind v4 @theme як CSS custom properties один-в-один (не переводити в OKLCH — брифом задано саме ці hex-значення, зберегти дослівно).
Ролі використання (default):
| Роль | Токен |
|---|---|
| Фон за замовчуванням | --background-950 |
| Основний текст | --text-50 |
| Другорядний текст | --text-300 / --text-400 |
| Основний інтерактивний (кнопки, посилання за замовчуванням) | --primary-500 (hover → --primary-400/--primary-600) |
| Акцент/підсвітка (одне слово в заголовку, glow навколо ключового UI, focus ring) | --accent-500 |
| Тонкі рамки | --background-800 / --primary-900 |
--secondary-* |
Резервна, рідкісна тональна варіація (напр. м'який радіальний glow позаду hero, або дуже тонкий wash між секціями) — ніколи як другий конкуруючий акцент для CTA |
Хоча в брифі задано п'ять іменованих шкал (text/background/primary/secondary/accent), primary/secondary/accent — один і той самий золотий відтінок на різних щаблях яскравості/насиченості, тож за формулою frontend/references/colors.md це рахується як один акцентний колір, розділений на семантичні ролі, а не «п'ять кольорів». Домен-калібрування — строгий кінець (dev-tools/premium software/research lab): один акцент, щільність <5% пікселів, золото зʼявляється вибірково (кнопки, фокус-кільце, підсвітка ключового слова, glow біля шейдера) — домінує темна мова, як і вимагає бриф.
Типографіка
- Display/body: Geist Sans (
next/font/google, variable) — геометричний, нейтрально-технічний grotesque. - Технічні деталі/числа/мітки кроків/лейбли FAQ-нумерації: Geist Mono.
- Три розміри: display (H1
text-7xl→text-9xl/text-[10rem]на найбільших екранах,leading-[0.95],tracking-[-0.03em]), body (16–18px), micro (12–14px, uppercase tracking для міток на кшталт01 —).
Ритм і компонування
- Дві вертикальні паузи для всього сайту: стандартна секція
py-24 md:py-32 lg:py-40, тіснаpy-16 md:py-20(proof-стрічка, розділювачі). - 12-колонкова сітка, асиметричне використання (заголовок
col-span-7, підтекст зміщений уcol-start-9), а не centered-все. - Одна скругленість на весь сайт:
rounded-2xl, тонкі рамки (border1px, ~8–12% opacity), без акцентних смуг на заокруглених картках (rules/card-borders.md). - Без чіпів/eyebrow над заголовками (
rules/headers.md) — нумеровані мітки (01 —,02 —) дозволені лише коли несуть інформацію (номер кроку/секції), і використовуються послідовно. - Іконки — виняток, не дефолт (
rules/icons.md): лише афордансні (гамбургер, зовнішнє посилання, соцмережі футера, чек/риска в pricing).
Візуальна мова
- Поєднання: преміум software-компанія, експериментальна інженерна лабораторія, дизайн-студія, дослідницький AI-проєкт.
- Так: чорні поверхні, теплі золоті акценти, великий типографічний контраст, щедрий whitespace, тонкі рамки, вишукані мікро-взаємодії, шейдерні ефекти, технічні UI-деталі (моно-шрифт, wireframe-панелі, кодоподібні елементи), якісні продуктові mockup'и.
- Ні: типові SaaS-градієнти, фіолетово-синя «AI»-естетика, роботи, мозки, generic neural-network графіка, надмірний glassmorphism, мультяшні ілюстрації, stock-фото розробників, generic startup-шаблони.
- Hero-оверлей — єдине місце, де візуальний «продукт» показаний прямо (wireframe-панелі); все інше — типографіка + простір.
Тон голосу
Впевнений, технічний, розумний, сучасний, прямий, доступний. НЕ корпоративний, не зверхній, не надто академічний, не hype-driven, не «написаний ШІ-стартапом». Уникати фраз на кшталт «революційна технологія, яка змінить світ» — конкретні інженерні переваги замість хайпу. Кожен розділ копірайту вище написаний із цим орієнтиром; координатор/coder не повинен додавати маркетингові суперлативи понад задане.
11. Responsive & Accessibility Requirements
- Брейкпоінти: 360px (мінімальний тест), 768px, 1024px, 1280px, 1536px+ — mobile-first.
- Hero-шейдер: на мобільному — або спрощена/призупинена версія ефекту, або статичний постер за замовчуванням, якщо профілювання на реальному пристрої не підтвердить стабільні 60fps; завжди пауза при
visibilitychange/поза viewport (IntersectionObserver), DPR capped на 2. - Демо онлайн-запису та демо інтерфейсу правок: на мобільному — вертикальний степер замість горизонтального макету, повна клавіатурна керованість, hotspot-точки в change-request демо мають видиму, а не лише hover, точку взаємодії на тач-пристроях.
- Research/technology картки — стек в один стовпець нижче
md. - Навбар: fixed, прозорий зверху → суцільний після скролу; мобільне меню — full-screen, focus trap, закриття по
Escape, повернення фокусу на тригер,aria-expandedсинхронізований, skip-link першим фокусованим елементом. - Форма ліда: усі поля мають
<label>, видимий custom focus ring, помилки валідації анонсуються (aria-describedby), мінімум 44px touch target на кнопках. - FAQ — нативні
<details>/<summary>, керовані клавіатурою за замовчуванням. - Контраст: перевірити кожну пару текст/фон з реального токен-набору вручну (secondary text на
--background-950— переконатись, що--text-300/--text-400проходять 4.5:1 для body-розміру;--text-500/--text-600вважати придатними лише для великого тексту (24px+) або рамок, не для body-копірайту). prefers-reduced-motion: усі GSAP/Lenis/CSS-transition ефекти вимкнені (глобальний@mediaуglobals.cssобнуляє тривалість анімацій/переходів для всіх елементів), увесь контент повністю видимий одразу (ніякогоopacity: 0, що ніколи не стане 1), шейдер — статичний постер.- Соцмережі футера (якщо додані) — іконки з
aria-label, максимум 4.
12. Performance & SEO
- JS-бюджет: <150kb gzip на сторінку (GSAP+ScrollTrigger+Lenis ≈55kb, OGL ≈12kb, форма — react-hook-form+zod, легкі;
markedне рахується — використовується лише на етапі білду, не потрапляє в клієнтський бандл). - LCP < 2.5s: H1 рендериться без залежності від WebGL; шейдер лениво монтується, статичний постер — перший видимий кадр фону.
- Зображення (якщо/коли додаються — наразі сайт не показує жодного продуктового скріншоту): сучасний формат (WebP/AVIF), явні
width/height,loading="lazy"нижче згину. - Кожна з 4 навігаційних сторінок — унікальні
<title>/meta description, OG-зображення, favicon, семантичні landmarks, один<h1>на сторінку./spec— окремий<title>,robots: { index: false, follow: false }, і не вsitemap.ts(не рекламна сторінка, індексувати не потрібно). - Структуровані дані:
OrganizationJSON-LD на головній (назва компанії — «Factory», реальна, неPLACEHOLDER; посилання —NEXT_PUBLIC_SITE_URL,PLACEHOLDERдоки Railway не згенерує домен фронтенду, розділ 14/16; опис) — не додаватиLocalBusiness, бо сама компанія не є локальним бізнесом (нею є її клієнти).ScholarlyArticle/citation-схему на/researchдосі не додано — початкова причина (немає реальних URL публікацій) більше не діє, усі 7 записів мають реальнийpublicationUrl(розділ 7.2/16), тож це тепер відкрите питання «чи варто», а не заблоковане відсутніми даними; поля журналу/DOI/афіліації, яких реально немає в даних, і надалі не вигадувати. - Аналітика: у брифі не вказано провайдера — не додавати конкретний сервіс без підтвердження (розділ 15/16).
- SEO-ключові фрази: природна українська, орієнтована на запити на кшталт «сайт для масажиста», «сайт для стоматологічної клініки», «онлайн-запис для малого бізнесу» — органічно в копірайт секцій 6–7, не keyword-stuffing.
- Lighthouse: performance/accessibility/best-practices/SEO усі ≥90 на throttled mobile перед здачею.
13. Build Plan
Кроки 1–20 нижче — початкова послідовність побудови «з нуля» і досі коректна для повторного білду. Кроки 21–24 додані пізніше й відображають те, чого початковий план не міг передбачити (монорепо, self-referential секція, видалення секцій, зміна плану деплою з subpath на Railway).
- Скелет проєкту.
bunx create-next-app(TS, Tailwind, App Router,src/),next.config.tsзoutput: 'export'. Верифікація:bun run devпіднімається без помилок. - Токени та шрифти.
@theme-блок з усіма кольоровими шкалами розділу 10 дослівно, підключення Geist Sans/Mono черезnext/font. Верифікація: тестова сторінка показує коректні кольори/шрифти. - Глобальний layout. Навбар (
navbar.md) + футер (footer-section.md, sitemap-варіант — є куди йти: 4 маршрути + дослідження/технології/команда), 4 порожні маршрути (/,/research,/technology,/team). Верифікація: навігація між усіма 4 сторінками працює, мобільне меню відповідає a11y-вимогам розділу 11. (Оновлено кроком 23 — ці 4 маршрути тепер у групі(site)зі спільним layout-ом, а не в кореневому.) - Скелет головної. Секції як плоский семантичний HTML без стилів/анімації (build order
frontend/SKILL.md). Верифікація: контент читається згори вниз у правильному порядку. - Hero + шейдер. Верстка hero (
hero-section.md) + OGL-шейдер + DOM-оверлей wireframe-панелі. Верифікація: H1 видимий без JS, постер рендериться миттєво, шейдер degrades gracefully без WebGL, пауза поза viewport/на прихованій вкладці,prefers-reduced-motion→ статика.id="hero"на секції — потрібен кроку 23 для nav CTA reveal. - Trust / Problem / Solution. Стилізація трьох секцій. Верифікація: контраст, відсутність вигаданих цифр.
- How it works + change-request демо. 6 кроків + вкладений hotspot-демо. Верифікація: демо клікабельне з клавіатури, працює на тач (без hover-залежності), жодних мережевих викликів.
- Booking демо. Клієнтський степер (послуга → дата → час → дані → підтвердження) + блок про адаптивність + приклад групового заняття. Верифікація: повністю keyboard-operable, дані — лише в React state, нічого не надсилається нікуди.
AI system flow + Research/Technology тизери.AI system flow видалено повністю кроком 21. Research/Technology тизери лишаються (див. кроки нижче) — Research тизер тепер мапиться по масиву довільної довжини, не фіксовано на «2».- Pricing. Верифікація: закреслена стара ціна семантична (
<s>), ієрархія «безкоштовно / 600 грн / домен окремо» читається за 3 секунди погляду. ЦінаБЕЗКОШТОВНОмаєflex-wrapіwhitespace-nowrapна закресленій сумі — без цього текст переповнює картку на вузьких в'юпортах (реальний баг, знайдений і виправлений після першого білду). - FAQ. Усі 8 пар дослівно,
<details>/<summary>. Верифікація: клавіатурна навігація по акордеону. - Final CTA + форма ліда. Форма з honeypot+timing полями (
captchaskill), клієнтська zod-валідація,fetch()наNEXT_PUBLIC_API_BASE_URL, вторинне посилання наnedoshev.dev/meet. Верифікація (поки бекенд-заглушка): форма валідовується, показує стан помилки/успіху, honeypot-поле поза tab-порядком. /research. Картки, що мапляться по масивуRESEARCH_PAPERS(не фіксована кількість). Верифікація: контент відповідає розділу 7.2./technology. П'ять категорій повністю. Верифікація: усі перелічені технології присутні, без зайвого маркетингового опису./team. Картки, що мапляться по масивуTEAM_MEMBERS(не фіксована кількість — зараз 5). Верифікація: жодних вигаданих досягнень.- Анімаційний прохід. GSAP scroll-reveal на всіх секціях, Lenis, ховер-стани (звичайні CSS-переходи —
motion/reactне додавали, розділ 5), glow навколо ключового UI (animations.mddefaults: 500–700ms,power3.out, стагер 60–80ms). Верифікація:prefers-reduced-motionвимикає все, контент лишається повністю видимим. - Бекенд-сервіс.
bun create elysia app(окремий пакет),POST /leadsз zod-валідацією, honeypot/timing (captchaskill), CORS обмежений наCORS_ORIGIN. Верифікація:bun testна валідатори + ручнийcurlпроти локального інстансу. - Telegram-сповіщення.
src/lib/telegram.ts— прямийfetchдо Telegram Bot API (sendLeadNotification,formatLeadMessage), без SDK/бази даних;TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_IDзадокументовані в.env.example. Верифікація:bun testна форматування повідомлення й на всі гілки помилок (не налаштовано → 500, Telegram відповів не-2xx → 502, мережа впала → 502) через мокнутийfetch; вручну — реальний бот, реальний чат, реальне повідомлення доходить. - Адаптив + продуктивність + a11y + SEO прохід. 360px→1920px без горизонтального скролу, Lighthouse ≥90 по всіх метриках, meta/OG/favicon на всіх 4 сторінках, JSON-LD
Organizationна головній. Верифікація: чек-лист розділів 11–12 пройдений повністю. - Наскрізна перевірка деплою. Статичний фронтенд збирається (
bun run build,output: 'export'), бекенд розгорнутий окремо,NEXT_PUBLIC_API_BASE_URLвказує на реальний бекенд. Верифікація: end-to-end сабміт форми на staging-оточенні доставляє реальне повідомлення в Telegram-чат. - Прибрати дві секції головної. AI system flow — повне видалення компонента (
components/home/ai-system-flow.tsx) і контент-константи (AI_SYSTEM_FLOWзcontent.ts). Technology тизер — повне видалення компонента (components/home/technology-teaser.tsx);TECH_CATEGORIES/TECH_STACK_NOTEне видаляти — їх ще використовує повна сторінка/technology. Верифікація:bun run lint/buildчисті (немає імпортів мертвих файлів),/technologyяк і раніше рендерить п'ять категорій. - Build proof секція. Новий компонент
BuildProofSection(components/home/build-proof-section.tsx) на місці видаленого AI system flow — headline «Цей сайт — живий приклад нашого процесу.», mono-число~30 хв, кнопка, що відкриває/specу попапі (розділ 7.1 секція 8). Верифікація: клік по кнопці в реальному браузері (не тількиnext devчерез статик-сервер зі сторонніми особливостями trailing slash) відкриває саме/spec, а не directory listing чи 404. /specпопап-сторінка + реструктуризація маршрутів. Існуючі 4 навігаційні маршрути перенесені в групуsrc/app/(site)/зі своїмlayout.tsx(навбар+футер+<main>); кореневийlayout.tsxлишає тільки<html>/<body>, шрифти, JSON-LD,SmoothScroll. Новийsrc/app/spec/page.tsx(Server Component, читає файл специфікації черезfs, конвертуєmarked-ом) — поза групою(site), без навбару/футера. На той момент планувався subpath-деплой (nedoshev.dev/factory) — доданоbasePathуnext.config.tsчерезNEXT_PUBLIC_BASE_PATH, іsrc/lib/base-path.tsдля єдиного rawwindow.open/<a>. Рішення пізніше змінено на розділ 24/14 (Railway, кожен сервіс — власний корінь-домен):basePath-плумбінг лишився в коді (нешкідливий,undefinedза замовчуванням), але production-білд його більше не встановлює. Верифікація:bun run buildгенерує окрему статичну сторінкуspec.htmlз коректним<h1>і таблицями; nav CTA reveal (розділ 6) далі коректно шукає#hero, знайдений лише на/.- Деплой на Railway.
.railway/railway.ts(Infrastructure as Code — розділ 14) визначає два сервіси (backend,frontend) черезrootDirectory; фронтенд отримавserve-static.ts(Bun-нативний статичний сервер,bun run start:static) замістьnext start;SITE_URL/NEXT_PUBLIC_BASE_PATHцентралізовано вsrc/lib/site-url.ts,.env.productionіз захардкодженим субшляхом видалено. Верифікація:bun run buildбезNEXT_PUBLIC_BASE_PATHдає кореневі (не/factory-префіксовані) посилання;bun run start:staticкоректно віддає/,/team,/spec,/sitemap.xml, статичні асети (усі200) і справжній404для неіснуючих шляхів; виміряний RSS процесу — ~23MB в стані спокою.
14. Deployment
Платформа — Railway, не власна reverse-proxy інфраструктура з розділу 5/13. Рішення змінилось: замість субшляху nedoshev.dev/factory за власним Nginx/Caddy, кожен сервіс отримує власний згенерований Railway-домен (*.up.railway.app) і живе як окремий Railway-сервіс. NEXT_PUBLIC_BASE_PATH/src/lib/base-path.ts лишаються в коді (не видалені — нешкідливі, якщо колись субшлях-деплой знадобиться десь ще), але production-білд для Railway їх не встановлює: frontend/.env.production видалено, basePath за замовчуванням undefined (корінь).
- Конфігурація — Infrastructure as Code, не
railway.json. Railway задеприкейтив Config as Code (railway.json/railway.toml) для нових сервісів; конфігурація живе в.railway/railway.ts(TypeScript DSL —defineRailway,project,service,github,preserve), застосовується командамиrailway config plan(перегляд) →railway config apply. - Два сервіси, монорепо через
rootDirectory. Коженservice(...)в.railway/railway.tsвказуєsource: github(REPO, { rootDirectory: "backend" | "frontend" })— Railway трактує підпапку як незалежний корінь проєкту (свійbun install, без залежності від кореневого workspace-лока;frontend/backendі так не мають одне до одногоworkspace:*-залежностей, тож це не проблема). Передумова: цей репозиторій ще не є git-репозиторієм —github()-джерело вимагає реального GitHub-репо;REPOв.railway/railway.ts— плейсхолдер<github-owner>/<github-repo>, замінити післяgit init+ push. - Railpack (білдер під капотом Railway) — окрема, друга точка відмови.
rootDirectoryв.railway/railway.ts— налаштування рівня Railway; сам Railpack робить власне автовизначення install/build/start і не завжди коректно поважає цей скоуп для Bun/npm workspace — реальний симптом:✖ No start command detected, у лозіFound workspace with 2 packages(Railpack піднявся до кореневогоpackage.json, побачивworkspaces, і шукав"start"там, де його немає — в корені є лишеdev/build/lint/typecheck/test). Виправлено окремимrailpack.jsonв кожній підпапці сервісу (backend/railpack.json,frontend/railpack.json) з явнимdeploy.startCommand— обходить автовизначення Railpack повністю, незалежно від того, чиrootDirectoryв.railway/railway.tsспрацював. Три різні файли, три різні шари:.railway/railway.ts(Railway IaC), задеприкейтованийrailway.json/railway.toml(не використовується), іrailpack.json(сам білдер) — помилка з брендуванням Railpack виправляється вrailpack.json, не в.railway/railway.ts. railway up— третя, окрема точка відмови, якщо деплоїти так, а не через GitHub/IaC. Фактичний метод деплою в цьому проєкті —railway upз CLI, неrailway config apply. Задокументована поведінка Railway:railway up, запущений без явного шляху зсередини підпапки (cd backend && railway up), все одно архівує й деплоїть з кореня проєкту, ігноруючи те, що cwd — вжеbackend/. Це та сама причинаFound workspace with 2 packages, навіть колиrailpack.jsonуже на місці. Виправлення — явний шлях +--path-as-root:railway up ./backend --path-as-root --service backendіrailway up ./frontend --path-as-root --service frontend(з кореня репозиторію).- Бекенд:
build: "bun install",start: "bun run start"(==bun run src/index.ts) — той самий long-running Bun+Elysia процес, слухаєprocess.env.PORT, який Railway підставляє сам (код уже сумісний, окремих змін не було потрібно). - Фронтенд — без
next start.build: "bun install && bun run build",start: "bun run start:static"— новий скриптfrontend/serve-static.ts, що обслуговуєout/черезBun.serve/Bun.fileнапряму (той самийtry_files-подібний resolve: чистий шлях →.html-файл →/index.html), без Next.js-рантайму на етапі видачі. Виміряно: ~23MB RSS в стані спокою — на порядок менше за типовийnext start. - CORS/API URL через reference-змінні.
CORS_ORIGINбекенду —"https://${{frontend.RAILWAY_PUBLIC_DOMAIN}}",NEXT_PUBLIC_API_BASE_URLфронтенду —"https://${{backend.RAILWAY_PUBLIC_DOMAIN}}"; Railway резолвить їх у реальний домен на момент старту/білду. Порядок деплою важливий: бекенд спершу (щоб його домен уже існував, коли фронтенд це значення запікає в статичний білд), фронтенд — другим. NEXT_PUBLIC_SITE_URL— єдине значення без авто-резолву. Це власний домен фронтенду, якого не існує до першого деплою цього ж сервісу (курка-яйце) — встановити вручну одразу після генерації домену (railway domainабо кнопка в дашборді) і передеплоїти (build-time значення, запікається вsitemap.xml/OG-теги/JSON-LDOrganization.url, розділ 12).- Секрети —
preserve(), не літерали у файлі.TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_IDвстановлюються окремо (railway variables --set ... --service backend, або дашборд) до першогоrailway config apply; в.railway/railway.tsвони —preserve(), тобто «лишити те, що вже встановлено», щоб реальний токен ніколи не потрапляв у закомічений файл. - Мінімальні ресурси. Ліміти CPU/RAM — не частина IaC DSL, лише повзунки в дашборді (Settings → Resource Limits, окремо на сервіс) — Railway й так масштабує вниз до фактичного споживання, ліміт лише стеля; виставити мінімум на обох сервісах. Головний внесок у низьке споживання — сама архітектура (статичний фронтенд без Next-рантайму + один Bun-процес без бази даних), не сам ліміт.
- CI/деплой: Railway деплоїть автоматично при push у гілку, під'єднану до сервісу (стандартна GitHub-інтеграція) — окремий CI-провайдер не потрібен.
15. Assumptions
- Firebase/Firestore замінено на пряме сповіщення в Telegram — свідома зміна архітектури, не тимчасовий обхідний шлях. Замість бази даних + сервіс-акаунт +
firestore.rulesзаради єдиної форми на сайті:POST /leadsпросто пересилає повідомлення через Telegram Bot API. Наслідок: немає історії лідів, яку можна прочитати з коду — лід існує рівно як повідомлення в Telegram-чаті; немає ні статусів (new/contacted/archived), ні можливості query/фільтру, ні way відновити лід, якщо повідомлення видалили. Якщо колись знадобиться і сповіщення, і персистентність/аналітика — це означатиме повернення бази даних поруч із Telegram, не заміну одного на інше. - Одержувач лідів — один Telegram-чат (
TELEGRAM_CHAT_ID), не список; кілька отримувачів означало б або групу/канал у Telegram (простіше, той самий код), або розсилку в кілька чатів (код довелося б міняти — наразі не реалізовано, бо не було потреби). - Назва бренду — Factory, реальна й остаточна, не робоча назва-заглушка; використовується усюди в коді (навбар,
<title>, JSON-LD) без жодногоPLACEHOLDER-маркування. Логотип/фірмовий знак все ще не визначено. - «Отримати сайт» веде до реальної форми ліда у Final CTA (секція 11); це єдина форма, що реально пише в базу.
- CTA навбару на під-сторінках (
/research,/technology,/team,/spec) веде на/#get-site, а не дублює форму на кожній сторінці. - Пейджинг для
/researchне додано — тизер на головній обмежує показ до перших 4 записів масиву (slice(0, 4)) плюс рядок «Та інші»; повна сторінка/researchпоказує всі без пагінації, навіть коли масив зростає далі за 7. - Демо онлайн-запису і демо інтерфейсу правок — повністю клієнтські, без будь-якого мережевого виклику; жоден
appointments/admin-panelskill не підключається. - Аналітика і CI-провайдер так і не вибрані — не додаються за замовчуванням; місце залишене для подальшого рішення (env var, якщо буде обрано провайдера).
- Типографіка (Geist Sans/Mono) обрана як нейтральний, технічно-виглядаючий варіант, що відповідає «строгому» домен-калібруванню з
frontendskill; бренд може замінити на власну гарнітуру пізніше без зміни структури токенів. - Hero-шейдер реалізовано через OGL (WebGL), а не
vgpu(WebGPU) — ефект достатньо описаний як один прохід шуму/спотворення, що не вимагає мультипрохідної композиції; ескалація доvgpuзалишена як опція, якщо реалізований OGL-варіант не досягне бажаної якості. LocalBusinessstructured data навмисно не використано (компанія не є локальним бізнесом); замість цього — базовийOrganization.- AI system flow і Technology тизер видалені повністю (компоненти й ексклюзивні контент-константи прибрані з кодової бази) — власник продукту вирішив, що вони не несуть достатньої цінності поруч із новою секцією 8. «Website examples» (портфоліо) з початкового брифу в продукті більше немає взагалі — не документується й не планується в цьому файлі; якщо колись з'явиться знову, це буде новий розділ, а не відновлення старого.
- Секція 8 «Це наш продукт» — розміщена після Research-тизера і перед Pricing, а не одразу після секції 5 (How it works), хоча вона й доводить саме твердження з секції 5. Рішення: вона працює краще як «капстоун», що закриває розділ довіри (trust → research → self-proof) безпосередньо перед комерційною пропозицією (pricing), ніж як проміжна вставка в середині наративу про сам процес розробки.
- «~30 хвилин» — вказано буквально з реквеста, не вимірювалось секундоміром під час фактичної розробки цього сайту; якщо точний час коли-небудь виміряють, оновити константу в копірайті, а не залишати неточне число тільки тому що воно вже написане.
/specяк спливаюче вікно, не модалка. Обраноwindow.openзамість модального діалогу в тому самому документі, бо специфікація — довгий документ (кілька тисяч рядків HTML), якому потрібен власний скрол/масштабування/друк незалежно від сторінки, що його відкрила; модалка поверх іншого контенту для документа такого розміру гірше на слабких пристроях і плутає навігацію назад/вперед у браузері.markedзамість@tailwindcss/typographyдля стилізації/spec— специфікація має малий і передбачуваний набір Markdown-конструкцій (заголовки, списки, таблиці, код), тож рукописний.spec-docCSS-блок уglobals.cssдає повний контроль над темною палітрою без додавання plugin-залежності, що вплинула б на весь інший Tailwind-конфіг.basePathлишився в коді (next.config.ts,src/lib/base-path.ts), але не використовується за замовчуванням — субшлях-деплой (nedoshev.dev/factory) замінено на Railway з окремими згенерованими доменами на сервіс (розділ 14);frontend/.env.production(де раніше живNEXT_PUBLIC_BASE_PATH=/factory) видалено. Плумбінг не видалявся з коду навмисно — нешкідливий (basePath: undefinedза замовчуванням = корінь), і знадобиться знову без змін, якщо колись субшлях-деплой десь ще стане потрібним.SITE_URLцентралізовано вsrc/lib/site-url.tsзамість трьох окремих однакових констант (layout.tsx,sitemap.ts,robots.ts) — читаєNEXT_PUBLIC_SITE_URL, фолбекhttps://example.com, доки Railway не згенерує реальний домен фронтенду.
16. Open Questions
Шість пунктів, що були тут раніше, тепер вирішені: імена команди більше не PLACEHOLDER (5 реальних людей на /team, розділ 7.4); усі 7 публікацій дослідження мають реальні рік і publicationUrl (розділ 7.2); назва бренду (Factory) підтверджена (розділ 5/14); «Website examples» (портфоліо) більше не в продукті; TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID — реальні значення вже в backend/.env. Актуальний список того, що ще потребує реального рішення:
- Цей репозиторій ще не
git init-нутий —.railway/railway.ts'sgithub()-джерело вимагає реального GitHub-репо;REPOтам — плейсхолдер<github-owner>/<github-repo>. Блокує будь-який Railway-деплой, поки не виправлено. NEXT_PUBLIC_SITE_URL— залишитьсяPLACEHOLDER(https://example.com), доки Railway не згенерує домен фронтенд-сервісу; встановити й передеплоїти після (розділ 14).- Ліміти ресурсів на Railway — не частина
.railway/railway.ts(дашборд-only); виставити мінімум на обох сервісах вручну після першого деплою. - Логотип/фірмовий знак — назва бренду (Factory) підтверджена, візуальний знак — ще ні.
- CI-провайдер і аналітика — окремий CI не потрібен (Railway деплоїть на push), але аналітика так і не обрана; жодних сторонніх скриптів не додано за замовчуванням (розділ 12).