Перейти до вмісту

Лілейка — брендбук і дизайн-система

Візуальна система для ProjectK — системи управління пластунським куренем. Версія 1.2 · остання правка: 2026-09-22


0. Швидкий контекст (читати першим, особливо якщо ти AI-агент)

Section titled “0. Швидкий контекст (читати першим, особливо якщо ти AI-агент)”

Продукт. Самохостована система для одного куреня НСОУ «Пласт»: реєстр учасників, планування сходин, точкування, вмілості, документи. Один розробник, малий проєкт, свідомо мінімальний обсяг UI.

Стек. Angular 22 (standalone components, signals + RxJS) · @openng/optimus-ui 2 — сумісний із PrimeNG форк, тож усе нижче про PrimeNG-компоненти, severity й Aura-токени чинне без змін · Tailwind 4 (@import "tailwindcss" + @openng/optimus-ui-tailwindcss) · .NET 10 backend. Фронтенд: Frontend/projectk-frontend.

Дві назви — не помилка:

Назва Де вживається
Лілейка продукт: вітальна сторінка, шапка сайдбара, <title>, листи, розмова з користувачем
ProjectK код: репозиторій, README, Docker, env-змінні, CI, назви пакетів

Ніколи не перейменовуй технічні артефакти на «Лілейка» і ніколи не показуй «ProjectK» користувачу в UI (крім рядка версії).

Десять правил, які покривають 90% рішень:

  1. Один зелений (#0E6E4E), один теплий (#D9762F), чорнило, поверхня. Більше кольорів не вводити.
  2. Терракота — тільки статуси й акценти. Ніколи не кнопка, не посилання, не «успішно».
  3. Один шрифт в інтерфейсі: Manrope. Рукописний Neucha — тільки логотип і порожні стани.
  4. Рамка 1px, ніколи 2px. Кольорових лівих смужок у картках немає.
  5. Радіуси: 8px інтерактивне, 12px картки й панелі.
  6. Тіні — тільки модалки, дропдауни й скриншоти в маркетингу. Картки живуть на рамці.
  7. Ховер = зміна фону на surface або рамки на primary. Без зсувів, масштабу й анімації розміру.
  8. Порожні стани обовʼязкові й мають голос. Один рукописний рядок, один рядок пояснення, одна дія.
  9. Іконки — pi pi-* з PrimeIcons; коли потрібної там немає — icon-* з Lucide (lucide-static, той самий шрифтовий підхід, приклад: icon-bug). Понад ці два набори нічого не додавати, SVG не малювати.
  10. Емодзі в UI немає. Ніде.

Що НЕ можна змінювати: червона табличка з числом куреня (kurin-number.css, #b30003 / #ff0005) — це пластова відзнака, а не елемент бренду. Див. §7.


Скаутська лілея (fleur-de-lis) у лінійному виконанні, нахилена на 45° вправо. Складники: центральна пелюстка-вістря (терракотова), два бічні завитки, одинарна поперечка й нижнє вістря — зелені (або currentColor). Знак завжди fill="none", тонка лінія з круглими кінцями. Офіційна емблема НСОУ «Пласт» (лілея з тризубом) не використовується і не відтворюється — це власний узагальнений скаутський fleur-de-lis.

Геометрія (viewBox 0 0 100 100, усе всередині <g transform="rotate(45 50 50)">):

Частина Path
Правий завиток M50 44 C54 33 62 28 71 33 C79 38 80 48 73 53 C68 56 62 55 60 51
Лівий завиток M50 44 C46 33 38 28 29 33 C21 38 20 48 27 53 C32 56 38 55 40 51
Поперечка M33 60 H67
Нижнє вістря M50 88 C46 80 46 71 50 65 C54 71 54 80 50 88 Z
Центральна пелюстка (терракота) M50 7 C45 24 45 43 50 57 C55 43 55 24 50 7 Z
Файл Призначення
assets/lileyka-mark.svg основний, двоколірний (зелений + терракотова центральна пелюстка)
assets/lileyka-mark-mono.svg currentColor, весь знак одним кольором — для друку/одноколірних контекстів
assets/lileyka-mark-dark.svg для темного фону (#34C48D + #EE9A5A)
assets/favicon.svg 64px, білий знак на зеленому квадраті r=14, центр терракотою #D9762F
assets/favicon-16.svg 16px, той самий знак, товща лінія для читабельності
  • Форма — скаутський fleur-de-lis, нахил 45°. Попередні варіанти (дво-/трипелюсткова лілея) не відтворювати.
  • Вільне поле навколо знака — висота нижнього вістря (≈ 20% висоти знака).
  • Товщина обведення: 5.5 для основного, 6–6.5 для дрібних фавіконів. Знак завжди fill="none", stroke-linecap="round".
  • Центральна пелюстка терракотова на всіх поверхнях, і на зеленому теж: на фавіконі, банері, у шапці сайдбара й на аватарках знак білий, а центр — той самий #D9762F (у темній темі #EE9A5A). Світлішого «клею» для зеленого немає: колишній #F5C99B не був кольором палітри і розходився з тим, що малює застосунок. Виняток: одноколірна версія (lileyka-mark-mono.svg) лишається в один колір. Джерело правди кольору — клас .lil-mark-center у застосунку (stroke: var(--lil-clay-500)).
  • Нахил 45° уже «вшитий» у файли (rotate(45 50 50)) — додатково не обертати. Не заливати, не додавати тінь чи градієнт. Не ставити в коло, крім аватарки користувача.

Знак + слово «Лілейка» у Neucha, горизонтально, відступ між ними = 0.4× висоти знака, вирівнювання по оптичному центру. Вертикального варіанта немає — не вигадувати.


Роль Hex Де
primary #0E6E4E кнопки, активний пункт меню, посилання, акцентні числа
primary-hover #0B5A40 ховер кнопки
primary-active #084733 натиснута кнопка
primary-50 #EAF3EE фон активного пункту меню, фон іконки-плашки, тег «Admin»
accent #D9762F терракота — див. обмеження нижче
accent-50 #FBE9DC фон тега «Незабаром», банер cold start
accent-700 #A8541B текст на accent-50
ink #101413 заголовки, основний текст
ink-soft #2E3633 текст у пунктах меню
muted #6E7873 підписи, другорядний текст
faint #9AA39F мета-дані, підписи полів, іконки в спокої
line #E4E8E6 усі рамки й розділювачі
surface #F7F9F8 фон сторінки, ховер рядків
paper #FFFFFF картки, панелі, модалки
cream #FBF8F1 тільки маркетинг: OG-image, фон hero-ілюстрації
Роль Hex
primary #34C48D
primary-hover #4FD49E
primary-bg #1D2E27
accent #EE9A5A
accent-bg #2A1F16
ink #E9EDEA
ink-soft #DDE2DF
muted #9BA8A2
faint #6C7570
line #232A27
ground #0D1110
surface #161B19
hover-bg #1D2320

Темна тема вмикається document.documentElement.dataset.theme = 'dark' (darkModeSelector: '[data-theme="dark"]' у preset).

Терракота — вичерпний список місць

Section titled “Терракота — вичерпний список місць”

Дозволено: тег «Незабаром» · крапка «є що модерувати» в сайдбарі · банер cold start · іконка pi-sparkles у «Зроблено пластунами для пластунів» · центральний листок знака · акцентне слово в маркетинговому заголовку («свисти алярми»).

Заборонено: кнопки, посилання, статус «успішно», графіки, фони секцій, hover-стани, рамки карток.

Правило одної плями: на будь-якому екрані терракота зʼявляється не більше ніж в одному місці одночасно. Якщо їх два — одне з них не потрібне.

Виняток — палітра категорій агенди. Категорія заходу (табір, сходини, вишкіл) має власний колір, який Звʼязковий обирає з семи закритих значень у agenda-category-manager.ts (BRAND_COLORS) і який зберігається в базі разом із категорією. Це єдиний hex-список у коді фронтенду: він є даними, а не стилем, і саме тому не зводиться до токенів теми. Розширювати його — лише тут і лише тонами, що тримають контраст із білим текстом бейджа.

Виняток — дошка задач (Задачі / Agenda). У палітрі немає жовтого, а статусна тріада «Зробити → В процесі → Зроблено» потребує теплого середнього тону. Тому на дошці задач warn-теракота закріплена за статусом «В процесі» (тег p-tag severity="warn") і може повторюватися на кількох картках одночасно — це свідомий виняток із правила одної плями, обмежений сторінкою дошки та статус-тегами календаря. Інші статуси: «Зробити» — secondary (сірий), «Зроблено» — info (зелений/primary). Джерело правди — agenda-status.config.ts.

Виняток — синій у календарі. Щоб на календарі події не зливалися із задачами (які фарбуються статусними кольорами сірий/теракота/зелений), події позначаються приглушеним синім тегом. Це єдине місце, де вводиться синій — токени --lil-blue-50 / --lil-blue-700 (світла/темна тема) у lileyka-theme.css, застосовані лише через токени --lil-agenda-event-* (клас .agenda-ev--event на сторінці календаря). Ніде інде синій не використовується.

Плашки на сітці календаря беруть кольори з токенів --lil-agenda-{todo|progress|done|event}-{bg|line|fg} у lileyka-theme.css — по набору на світлу й темну тему. Підкладка й рамка — ті самі сірий/теракота/зелений, що й у тегах статусів на дошці, плюс календарний синій для подій; текст на плашках — чорнило, як числа днів; ними ж фарбуються відповіді RSVP («йду» — зелений, «можливо» — теракота, «не йду» — сірий). Нових плашкових кольорів не вводити — розширювати лише ці токени. Колір категорії на плашці ніколи не є заливкою як є: він замішується в папір за коефіцієнтами --lil-agenda-cat-{bg|line|dot}-mix (світла тема — тінт 14 % під чорнильним текстом, темна — 70 %, майже суцільна смуга: рівно стільки, щоб золото «Таборів» тримало 4.5:1), а сама категорія впізнається по крапці. Без цього сім темних кольорів палітри, дібраних під білий текст бейджа, у світлій темі стояли б суцільними темними плитами поруч із пастельними плашками задач.

severity Значення Колір
success підтверджено, вмілість зарахована зелений з Aura
info «Незабаром», нейтральна мітка #EAF3EE / #0B5A40 — перевизначено на зелений, не синій
warn очікує модерації, cold start терракота
danger ліміт, помилка, видалення червоний з Aura
secondary роль, службова мітка faint

Роль Шрифт Розмір Вага Tracking Токен
h1 маркетинг Manrope 62px (clamp до 40px) 800 −4% — (тільки сайт і вітальна)
h1 в системі Manrope 28px 800 −2.5% --lil-text-h1 · --lil-tracking-h1
h2 Manrope 24px 800 −2% --lil-text-h2 · --lil-tracking-h2
h3 / заголовок картки Manrope 17px 800 −1% --lil-text-h3 · --lil-tracking-h3
body Manrope 15px 400 0 --lil-text-body
UI (кнопки, меню) Manrope 15px 600–700 0 --lil-weight-ui
підпис / мета Manrope 13px 400–600 0 --lil-text-caption
мікропідпис Manrope 11px 700, uppercase +18% --lil-text-micro · --lil-tracking-micro
логотип Neucha 29–50px 400 0 —
порожній стан — заголовок Neucha 24px 400 0 —
код, версія, ключі ui-monospace 11–12px 400 0 —

Line-height: 1.02 для великих заголовків, 1.15 для h2–h3 (--lil-leading-heading), 1.5–1.55 для тексту (--lil-leading-body), 1.45 для дрібного. Вага заголовків — --lil-weight-heading (800).

Шкала живе в lileyka-theme.css як токени, і голі h1 / h2 / h3 в застосунку вже набрані нею. Tailwind-preflight скидає заголовки до розміру тексту, тому раніше кожна сторінка виставляла числа сама — і виставляла свої. Тепер правило одне: розмір і вага заголовка не пишуться в CSS сторінки і не вішаються Tailwind-класами (text-xl, font-bold на заголовку — помилка ревʼю). Сторінка обирає лише роль — заголовок сторінки (§5 «Сторінка») чи заголовок картки (§5 «Картка») — і роль приносить розмір. Виняток — маркетингові h1 на сайті й вітальній.

Перша редакція брендбука називала Karla й Patrick Hand. Жоден із них не має кириличного набору — весь український текст падав на system-ui і виглядав по-різному на кожній ОС. Замінено на Manrope (той самий нейтральний гротеск, ваги 400–800) і Neucha (рукописний), обидва з повною кирилицею.

Neucha при однаковому кеглі дає ascent приблизно на 10% нижчий за Patrick Hand, тож розміри рукописного в таблиці підняті рівно настільки — оптично це той самий рядок.

Шрифти локальні, не з CDN: самохост має підніматись без інтернету.

public/assets/fonts/
├── manrope-var-cyrillic.woff2 ← варіативний, 400–800
├── manrope-var-latin.woff2
├── manrope-var-latin-ext.woff2
├── neucha-400-cyrillic.woff2
└── neucha-400-latin.woff2

@font-face — у секції 0 lileyka-theme.css, з unicode-range від Google Fonts. Кирилиця Manrope додатково в <link rel="preload"> у index.html: нею набрано весь інтерфейс. Разом ~112 kB.

Neucha не використовується ніде, крім логотипу й заголовків порожніх станів. Не в кнопках, не в тегах, не в тултипах, не в заголовках сторінок.


Відступи. Крок 4px. У реальному вжитку: 8 · 12 · 14 · 20 · 28 · 56. Внутрішній відступ картки — 20px (мобільний 16px, --lil-tile-pad), панелі — 24px, секції сторінки — 56px.

Ширина контенту. min(100% - 2rem, 72rem) — у темі це .lil-page з токенів --lil-page-width (72rem), --lil-page-gutter (1rem з кожного боку), --lil-page-block (2rem зверху й знизу, 1rem на мобільному) і --lil-page-gap (20px між плитками). Ширші сторінки — лише .lil-page--wide (84rem, --lil-page-width-wide): реєстр із десятьма колонками. Вужчі — перевизначенням --lil-page-width на сторінці (імпорт, 64rem). Власних width: min(...) у CSS сторінок більше не пишуть.

Радіуси. 8px — кнопки, поля, теги, пункти меню. 12px — картки (.kurin-tile), панелі, модалки. 14px — favicon, hero-ілюстрація. 50% — тільки аватарки й крапки-індикатори.

Рамки. Завжди 1px solid var(--lil-line). Ніяких 2px, ніяких кольорових лівих смужок, ніяких подвійних рамок. Пунктирна рамка (1px dashed) — тільки порожні стани й вимкнені елементи.

Тіні. Дозволено: модалка/дропдаун (0 12px 40px rgba(16,20,19,0.07)), скриншот продукту в маркетингу (0 20px 52px rgba(16,20,19,0.10)). Все інше — без тіні. Наявна тінь .kurin-tile (0 8px 26px -22px …primary…) прибирається.

Ховер. Фон → surface (світла) / hover-bg (темна), або рамка → primary. Перехід background-color 120ms ease, border-color 120ms ease. Без transform, без box-shadow, без зміни розміру.

Вимкнено. opacity: 0.4 + пунктирна іконка. Не сірий текст — прозорість, щоб було видно, що елемент є, але поки не працює.

Анімація. Максимум 200ms, ease. Плавний рух є лише в трьох місцях: пунктирна лінія в порожньому стані (stroke-dashoffset, 1.6s linear infinite), спінер cold start, поява дровера. Логотип на вітальній може мати легке коливання 6s — більше ніде. Прев’ю продукту на вітальній міняє кадр раз на 6 с із переходом 200ms; це зміна стану, а не рух, і за prefers-reduced-motion кадри не гортаються самі.

Діє тільки на маркетингових поверхнях: сайт site/, OG-зображення, презентація, відео. У застосунку чинний §4 без жодних послаблень — усе нижче туди не переноситься.

Правило, з якого випливає решта: кадром керує прокрутка, а не таймер. Темп задає людина. Ніщо не рухається саме собою, доки вона стоїть.

  • Один липкий кадр на секцію, до пʼяти станів. Показуємо один продукт, що змінюється, а не пʼять картинок поспіль. Шостий стан означає, що секцію треба різати надвоє.
  • Перехід між станами — 200ms, ease. Те саме число, що в застосунку; довших переходів немає й тут.
  • Дозволений рух — лише opacity і translate по одній осі, не більше 24px. Без scale, без rotate, без паралакса, без руху фону. Продукт не «прилітає» — він змінюється.
  • Техніка — нативні animation-timeline: view() / scroll(). Бібліотек анімації не додавати: п’ять станів їх не потребують, а кожна з них — це ще один пакет у постачанні.
  • Стовпчик — стан за замовчуванням, а не запасний. За prefers-reduced-motion: reduce і там, де animation-timeline не підтримується, усі кадри показуються один під одним, без липкості. Спершу пишеться стовпчик, липкість накладається зверху.
  • Тінь на скриншоті — за §4: 0 20px 52px rgba(16,20,19,0.10). Рамка лишається 1px.
  • Відео — без звуку, без системного курсора, петля до 6 с. За prefers-reduced-motion замість відео показується постер.

Терракота й тут за §2: один акцент на екран, ніколи не кнопка й не посилання.


Усі — PrimeNG. Нові компонентні бібліотеки не додавати.

Варіант Коли PrimeNG
primary заповнена головна дія екрана, одна на екран <p-button label="Зберегти" />
secondary контурна альтернативна дія severity="secondary"
text третинна, «Скасувати», «Розгорнути» severity="secondary" variant="text"
з іконкою завжди iconPos="left", крім «Відкрити →» icon="pi pi-plus"
icon-only тулбар, дії в рядку таблиці text severity="secondary"

Висота 40px (size="small" 34px), горизонтальний відступ 20–28px. severity="info" для кнопок не використовувати — у поточному коді (kurin-panel) вона стоїть на «Створити» / «Додати учасника»; замінити на дефолтну primary.

Мітка над полем: 13px, faint, вага 600. Поле: рамка line, радіус 8px, висота 40px, фокус — рамка primary + outline: 2px solid primary-50. Помилка — рамка danger + рядок 13px під полем. Форма — display: grid; gap: 1rem, три колонки на десктопі, одна на мобільному (як уже в .profile-form).

Кожен екран застосунку складається з трьох речей, і всі три є в темі:

<div class="lil-page"> <!-- колонка: ширина, поля, відступ між плитками -->
<header class="lil-page-head">
<div>
<p class="lil-page-head__eyebrow">Налаштування куреня</p> <!-- необовʼязково -->
<h1>Реєстр</h1>
<p>12 курінь · УПЮ · 29 у складі</p> <!-- підпис 13px muted -->
</div>
<div class="lil-page-head__actions">…одна primary, решта secondary…</div>
</header>
<section class="kurin-tile">…</section>
<section class="kurin-tile">…</section>
</div>

h1 у шапці — 28px/800 з токенів, без класів. Шапка над плитками, не всередині першої: заголовок належить сторінці, а не її першій таблиці. На ≤768px шапка стає стовпчиком, дії йдуть під заголовок. Виняток — сторінки-профілі (курінь, гурток, картка людини): там шапка сама є плиткою з фото чи числом куреня, і h1 живе в ній — але набирається тими самими токенами.

.kurin-tile — базовий контейнер контенту:

.kurin-tile {
background: var(--p-content-background);
border: 1px solid var(--p-content-border-color);
border-radius: 12px;
box-shadow: none;
padding: var(--lil-tile-pad); /* 20px, на ≤640px — 16px */
}

Шапка картки — .lil-tile-head, один клас на всі плитки й на <ng-template #caption> кожної p-table:

<div class="lil-tile-head">
<div>
<h2>Гуртки</h2> <!-- 17px/800 незалежно від того, h2 це чи h3 -->
<span class="lil-count">3</span> <!-- лічильник — плашка, не частина тексту -->
<p>2 активних</p> <!-- підпис 13px muted -->
</div>
<div class="lil-tile-head__actions">…</div> <!-- праворуч, margin-left: auto -->
</div>

Рівень заголовка — семантичний, не візуальний: плитка, що є розділом сторінки, бере h2; блок усередині плитки — h3. Розмір від цього не міняється, його дає роль. Копій .table-caption, .kv-caption, .leadership-caption і подібних у CSS сторінок немає — це все одна .lil-tile-head.

Хедер: 11px, uppercase, tracking +18%, faint, фон surface, без рамок між колонками. Рядок: висота 52px, розділювач 1px line, ховер — фон surface. Головна колонка — назва 15px/650 + мета 13px muted під нею (як .group-name-cell). Колонка дій — праворуч, фіксована ширина, кнопки icon-only або splitButton. Мобільний — рядок стає flex, хедер display: none (уже реалізовано, лишається).

Порожня таблиця — не p-message, а порожній стан із §6.

Радіус 6px, 11px/700, відступ 3px 9px. Роль користувача — secondary. «Незабаром» — info (зелений, не синій). «Очікує модерації» — warn (терракота). Ліміт CBT — danger при досягненні, інакше info.

Висота 56px, фон paper, нижня рамка 1px line, без тіні. Ліворуч — бургер (pi pi-bars, text secondary). Праворуч — дзвіночок нотифікацій і вихід, gap: 0.35rem (уже в .toolbar-actions). Логотипа в тулбарі немає — він у сайдбарі.

Шлях (p-breadcrumb) — одразу за бургером, gap: 0.75rem (у .toolbar-start), без власного фону й рамки, підписи 13px; на ≤768px ховається. Домик веде в корінь ролі (курінь → /kurin, адмін поза куренем → /panel, інакше картка учасника) з підказкою-назвою місця. Сам корінь і все вище нього в ланцюжку не дублюється — з куреня в адміністрацію виходять кнопкою «До адміністрації».

Крихта, що позначає сутність (гурток, учасник, курінь), підписана її власною назвою у тому ж форматі, що й <title>: г. Соколи, Шевченко Тарас, к. ч. 12 (форматери в page-title.format.ts). Поки назва вантажиться або якщо не завантажилась — лишається загальний підпис маршруту («Гурток», «Картка учасника»).

Крихту, посилання якої нічим заповнити (ментор без гуртка), не показуємо взагалі — шлях будується за наявними даними. Порожній Guid не є даними: перевіряй ключі через isUsableKey.

Шапка — зелений банер: фон primary, padding: 1.25rem, знак 32px білим + «Лілейка» Neucha 31px білим. Замінює <h2>Project K</h2>. Бічну рамку дровера прибрано — на зеленому вона світилась смужкою; відділяє тінь і підкладка.

Пункти: висота 42px, gap: 0.75rem, радіус 8px, 15px/600 ink-soft. Активний — фон primary-50, текст і іконка primary, вага 700. Вимкнений — opacity: 0.4 + пунктирна іконка. Індикатор «є що модерувати» — крапка 7px терракотою праворуч, без числа.

Низ: рядок версії 11px monospace faint, під ним рамка й блок користувача — іконка, e-mail 13px, тег ролі.

Порядок пунктів не змінювати — він залежить від ролей і будується в sidebar-menu.ts.

Фон paper, радіус 12px, тінь 0 12px 40px rgba(16,20,19,0.07), заголовок 20px/800, підкладка rgba(16,20,19,0.32). Дії — праворуч знизу, gap: 0.5rem, порядок: text «Скасувати», потім primary.

Обовʼязковий скрізь, де може не бути даних. Структура:

<div class="lil-empty">
<!-- проста лінія/фігура, stroke: line, 110×66 -->
<div class="lil-empty__title">Тут ще нікого немає</div>
<div class="lil-empty__body">Додай першого юнака — і реєстр почне жити.</div>
<p-button label="Додати учасника" />
</div>

Рамка 1px dashed, радіус 12px, padding: 1.5rem, вирівнювання по центру, gap: 0.875rem. Ілюстрація — обведені прості фігури (кола, прямокутники, лінії) кольором line, одна деталь терракотою. Ніяких намальованих сцен і персонажів.

Наявний компонент лишається за структурою. Кольори: фон accent-50, рамка 1px accent-100, текст accent-700, спінер accent. Заголовок 15px/700, повідомлення 13px.


Формула: рукописний заголовок (без крапки) + рядок пояснення (з крапкою) + одна дія. Заголовок описує стан, а не наказує. Гумор — тихий, пластовий, без жартів про користувача.

Місце Заголовок Пояснення Дія
Реєстр порожній Тут ще нікого немає Додай першого юнака — і реєстр почне жити. Додати учасника
Планування порожнє Календар ще спить Перша сходина в плані — і він прокинеться. Створити планування
Точкування порожнє Точкування чисте Ще ніхто не заробив і не втратив пункта. Поставити точки
Пошук без результатів Нічого не знайшлось Спробуй коротший запит — або перевір гурток. Скинути фільтри
Гуртки порожні Гуртків поки немає Курінь без гуртків — як мандрівка без стежки. Створити гурток
Вмілості порожні Вмілостей ще немає Заявки на вмілості зʼявляться тут. —
Модерація порожня Все розглянуто Заявок на вмілості немає. Можна відпочити. —
Документи (незабаром) Документи в дорозі Статути й накази житимуть тут. Скоро. —
Cold start Сервер ще прокидається Самохост після сну. Дай йому пів хвилини. Пробуємо знову…
403 Сюди тобі не можна Ця сторінка для впорядників. Твоя роль — юнак. На мій профіль
404 Такої стежки немає Можливо, посилання застаріло. На головну
500 Щось пішло не так Ми вже знаємо. Спробуй за хвилину. Оновити
Офлайн Звʼязку немає Перевір інтернет — дані збережені локально. —
Успішне збереження (toast) Збережено — —

Toast-повідомлення не рукописні й не жартівливі: «Збережено», «Учасника додано», «Не вдалося зберегти». Whimsy живе тільки в порожніх станах і на екранах помилок.


Число куреня (kurin-number.css) — біле число на червоному полі #b30003 з рамкою #ff0005, Arial. Це відтворення пластової курінної таблички, а не елемент бренду. Не перефарбовувати в зелений, не змінювати шрифт, не додавати радіус. Єдине, що дозволено — розміри для адаптиву (уже є).

Ілюстрація scouts-main.png — лінійна графіка пластунів, чорна. Лишається чорною на світлому фоні. Не тонувати в зелений, не інвертувати. У темній темі — на світлій підкладці #F7F9F8 з радіусом 16px.


  • Звертання на «ти», не на «Ви». Це система для своїх.
  • Пластова термінологія без пояснень: курінь, гурток, юнак, впорядник, сходина, точкування, вмілість, алярм, станиця, край.
  • Короткі речення. Одна думка — одне речення.
  • Заголовки кнопок — дієслово в інфінітиві: «Додати учасника», «Створити планування», «Зберегти».
  • Не писати «будь ласка», «на жаль», «упс», «ой». Не вигукувати.
  • Не пояснювати очевидне. «Додати учасника» не потребує підпису «натисніть, щоб додати учасника».
  • Технічні терміни в UI українською: не «submit», а «Надіслати». Виняток — назви ролей (Admin) і технічні ключі.

Frontend/projectk-frontend/
├── public/assets/
│ ├── README.md ← маніфест бренд-ассетів (джерело правди)
│ ├── favicon.svg ← <link rel="icon">
│ ├── favicon-16.svg
│ ├── lileyka-mark.svg
│ ├── lileyka-mark-mono.svg
│ ├── lileyka-mark-dark.svg
│ ├── lileyka-banner-1080x288.svg ← простий банер; README бере .github/assets/banners/readme-*.png
│ ├── lileyka-og-1200x630.svg ← og:image
│ └── fonts/ ← Manrope + Neucha, woff2 (див. §3)
└── src/
├── index.html ← favicon + preload кирилиці + <title>Лілейка</title>
├── styles.css ← @import "./lileyka-theme.css";
├── lileyka-theme.css ← @font-face + токени + .lil-* класи
└── app/
├── lileyka-preset.ts ← preset для PrimeNG
└── app.config.ts ← preset: LileykaPreset

Назва системи — «Лілейка», стала: environment.appName у src/environments/*.ts, без runtime-перевизначення. Змінну PROJECTK_APP_NAME прибрано в 1.0 — ліцензія не дозволяє запускати систему під іншою назвою, тож і налаштування для цього бути не повинно.

Не все перекривається через CSS-змінні. PrimeNG вставляє свою тему в <head> після styles.css, тож:

  • Тільки в lileyka-preset.ts: компонентні токени (tag, message — Aura бере info з {sky.*}, а warn з {orange.*}), шкала primitive.borderRadius, semantic.colorScheme.*.text. Змінні --p-info-* / --p-warn-* із теми до них не доходять.
  • Темна схема Aura читає surface навпаки: surface.0 — це колір тексту, 900 — фон контенту, 950 — тло, 700 — рамка. Шкала йде від світлого до темного, інакше буде темний текст на темному тлі.
  • У CSS однокласові селектори (.p-tag, .p-toolbar) потребують префікса :root — інакше програють темі PrimeNG за порядком у каскаді.

app.config.ts:

import { LileykaPreset } from './lileyka-preset';
providePrimeNG({
translation: { /* без змін */ },
theme: {
preset: LileykaPreset,
options: { darkModeSelector: '[data-theme="dark"]' }
}
})

Окремі класи, які додає lileyka-theme.css: .lil-wordmark (логотип) · .lil-sidebar-brand (шапка сайдбара) · .lil-empty, .lil-empty__art, .lil-empty__title, .lil-empty__body, .lil-empty__accent, .lil-empty__dash (порожній стан). Плюс перевизначення .kurin-tile, .project-table, а також :root .p-toolbar, :root .p-tag, :root .p-panelmenu.lil-menu.

Розмітку .lil-empty руками не писати — є app-empty-state (src/app/shared/empty-state/):

<app-empty-state art="list" title="Тут ще нікого немає"
body="Додай першого юнака — і реєстр почне жити.">
<p-button label="Додати учасника" icon="pi pi-plus" (onClick)="create()" />
</app-empty-state>

art — trail (типово), list, calendar. Дія проєктується через ng-content: якщо прав немає, просто не передавай нічого. Текст брати з каталогу §6.

Якщо та сама дія вже є в шапці списку — сховати її, поки список порожній. Інакше на екрані дві однакові primary-кнопки, що суперечить §5.

Кольори пунктів меню живуть у navigation.item.* у preset, а не в CSS: PrimeNG фарбує обгортку .p-panelmenu-*-content, тож правило на вкладене посилання дає два накладені прямокутники на ховері.


10. Чекліст ревʼю екрана

Section titled “10. Чекліст ревʼю екрана”
  • Терракота зʼявляється не більше ніж один раз
  • Neucha тільки в логотипі або порожньому стані
  • Усі рамки 1px line, тіней немає (крім модалки)
  • Радіуси 8px / 12px, інших немає
  • Одна primary-кнопка на екран
  • Є порожній стан для кожного списку
  • Немає емодзі й намальованих SVG-сцен
  • Іконки — тільки pi pi-*
  • Кольори взяті з токенів, hex у компонентах немає (крім числа куреня)
  • Темна тема перевірена: контраст тексту ≥ 4.5:1
  • «ProjectK» не показано користувачу (крім рядка версії)
  • Тексти на «ти», без «будь ласка» й «упс»

11. Довідник для агента: типові задачі

Section titled “11. Довідник для агента: типові задачі”

«Додай новий екран.» <div class="lil-page"> → <header class="lil-page-head"> з голим h1 → плитки <section class="kurin-tile">, кожна з .lil-tile-head (§5 «Сторінка», «Картка»). Жодних font-size на заголовках і жодних text-*/font-* Tailwind-класів на них — розмір дає роль. Дані — p-table styleClass="project-table" або dl.identity-grid. Обовʼязково порожній стан із каталогу §6.

«Додай кнопку.» Одна primary на екран. Решта — severity="secondary" або variant="text". severity="info" не використовувати.

«Зроби це помітнішим.» Спочатку вага шрифту й розмір, потім primary, і лише в крайньому разі терракота — і тільки якщо її на екрані більше немає.

«Додай іконку.» Знайти в PrimeIcons (pi pi-*). Якщо нема — обійтися без іконки. Не малювати SVG, не додавати бібліотеку.

«Додай нову сутність у меню.» Тільки через buildItems() у sidebar-menu.ts, з перевіркою прав через PermissionService. Порядок наявних пунктів не змінювати.

Чого не робити без запиту: не додавати градієнти, не вводити третій колір, не міняти шрифт, не додавати анімації понад §4, не перефарбовувати число куреня, не додавати емодзі, не переписувати scouts-main.png.