diff --git a/apps/web/app/foundation/colors/page.tsx b/apps/web/app/foundation/colors/page.tsx
new file mode 100644
index 0000000..0988078
--- /dev/null
+++ b/apps/web/app/foundation/colors/page.tsx
@@ -0,0 +1,415 @@
+"use client";
+
+import { useState, useCallback } from "react";
+import type { Metadata } from "next";
+
+/* ─── Утилиты конвертации ──────────────────────────────────────────── */
+function hexToRgb(hex: string): { r: number; g: number; b: number } {
+ const m = /^#([0-9a-f]{6})$/i.exec(hex);
+ if (!m) return { r: 0, g: 0, b: 0 };
+ return {
+ r: parseInt(m[1].slice(0, 2), 16),
+ g: parseInt(m[1].slice(2, 4), 16),
+ b: parseInt(m[1].slice(4, 6), 16),
+ };
+}
+
+function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
+ r /= 255; g /= 255; b /= 255;
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
+ let h = 0, s = 0;
+ const l = (max + min) / 2;
+ if (max !== min) {
+ const d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
+ case g: h = ((b - r) / d + 2) / 6; break;
+ case b: h = ((r - g) / d + 4) / 6; break;
+ }
+ }
+ return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
+}
+
+function luminance(r: number, g: number, b: number): number {
+ const a = [r, g, b].map(v => {
+ v /= 255;
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+ });
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
+}
+
+function contrastRatio(hex1: string, hex2: string): number {
+ const { r: r1, g: g1, b: b1 } = hexToRgb(hex1);
+ const { r: r2, g: g2, b: b2 } = hexToRgb(hex2);
+ const l1 = luminance(r1, g1, b1);
+ const l2 = luminance(r2, g2, b2);
+ const lighter = Math.max(l1, l2);
+ const darker = Math.min(l1, l2);
+ return Math.round(((lighter + 0.05) / (darker + 0.05)) * 10) / 10;
+}
+
+/* ─── Данные цветов ────────────────────────────────────────────────── */
+const BRAND_COLORS = [
+ {
+ oracal: "053M",
+ name: "Основной бирюзовый",
+ hex: "#7ecfca",
+ usage: "Акцентный цвет, CTA-кнопки, иконки, активные состояния",
+ cssVar: "--brand-053m",
+ },
+ {
+ oracal: "073M",
+ name: "Тёмный серо-голубой",
+ hex: "#5b7b87",
+ usage: "Тёмный фон, хедер, акценты на тёмных поверхностях",
+ cssVar: "--brand-073m",
+ },
+ {
+ oracal: "066M",
+ name: "Средний бирюзовый",
+ hex: "#5bb5ad",
+ usage: "Вторичные акценты, фоны секций, иллюстрации",
+ cssVar: "--brand-066m",
+ },
+ {
+ oracal: "050M",
+ name: "Тёмно-синий",
+ hex: "#1b4c72",
+ usage: "Наружная реклама, полиграфия, заголовки на светлом фоне",
+ cssVar: "--brand-050m",
+ },
+ {
+ oracal: "081M",
+ name: "Бежевый",
+ hex: "#c4a882",
+ usage: "Форма сотрудников, оффлайн носители, тёплые акценты",
+ cssVar: "--brand-081m",
+ },
+ {
+ oracal: "080M",
+ name: "Тёмно-коричневый",
+ hex: "#5c2e0e",
+ usage: "Текст на бежевых поверхностях, логотип на форме",
+ cssVar: "--brand-080m",
+ },
+ {
+ oracal: "—",
+ name: "Белый",
+ hex: "#ffffff",
+ usage: "Фон, инвертированный текст, логотип на тёмных фонах",
+ cssVar: "--brand-white",
+ },
+];
+
+const CONTRAST_PAIRS = [
+ { fg: "#ffffff", bg: "#5b7b87", label: "Белый на тёмном серо-голубом" },
+ { fg: "#ffffff", bg: "#1b4c72", label: "Белый на тёмно-синем" },
+ { fg: "#ffffff", bg: "#5c2e0e", label: "Белый на тёмно-коричневом" },
+ { fg: "#ffffff", bg: "#5bb5ad", label: "Белый на среднем бирюзовом" },
+ { fg: "#111827", bg: "#7ecfca", label: "Тёмный текст на основном бирюзовом" },
+ { fg: "#111827", bg: "#c4a882", label: "Тёмный текст на бежевом" },
+ { fg: "#5c2e0e", bg: "#c4a882", label: "Тёмно-коричневый на бежевом (форма)" },
+];
+
+/* ─── Компоненты ───────────────────────────────────────────────────── */
+function CopyBadge({ value, label }: { value: string; label: string }) {
+ const [copied, setCopied] = useState(false);
+ const copy = useCallback(() => {
+ navigator.clipboard.writeText(value);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ }, [value]);
+ return (
+
+ {copied ? "✓" : "⎘"} {value}
+
+ );
+}
+
+function ColorCard({ color }: { color: typeof BRAND_COLORS[0] }) {
+ const { r, g, b } = hexToRgb(color.hex);
+ const { h, s, l } = rgbToHsl(r, g, b);
+ const isLight = l > 55;
+
+ return (
+
+ {/* Свотч */}
+
+ {color.oracal !== "—" && (
+
+ Oracal {color.oracal}
+
+ )}
+
+
+ {/* Информация */}
+
+
+ {color.name}
+
+
+ {color.usage}
+
+
+ {/* Коды */}
+
+
+
+
+
+
+
+
+ );
+}
+
+function ContrastRow({ pair }: { pair: typeof CONTRAST_PAIRS[0] }) {
+ const ratio = contrastRatio(pair.fg, pair.bg);
+ const aa = ratio >= 4.5;
+ const aaa = ratio >= 7;
+ const aaLarge = ratio >= 3;
+
+ return (
+
+ {/* Превью */}
+
+ Aa Бб Вв
+
+
+ {/* Описание */}
+
+
+ {pair.label}
+
+
+
+ {pair.fg} / {pair.bg}
+
+
+
+
+ {/* Ratio и бейджи */}
+
+
+ {ratio}:1
+
+
+ {[
+ { label: "AA", pass: aa },
+ { label: "AAA", pass: aaa },
+ { label: "AA large", pass: aaLarge },
+ ].map(({ label, pass }) => (
+
+ {pass ? "✓" : "✕"} {label}
+
+ ))}
+
+
+
+ );
+}
+
+/* ─── Экспорт токенов ──────────────────────────────────────────────── */
+function exportTokens() {
+ const tokens: Record> = { colors: {} };
+ BRAND_COLORS.forEach(c => {
+ const key = c.oracal !== "—" ? `brand-${c.oracal.toLowerCase()}` : "brand-white";
+ const { r, g, b } = hexToRgb(c.hex);
+ const { h, s, l } = rgbToHsl(r, g, b);
+ tokens.colors[key] = {
+ oracal: c.oracal,
+ name: c.name,
+ hex: c.hex.toUpperCase(),
+ rgb: `rgb(${r}, ${g}, ${b})`,
+ hsl: `hsl(${h}, ${s}%, ${l}%)`,
+ cssVar: c.cssVar,
+ };
+ });
+ const blob = new Blob([JSON.stringify(tokens, null, 2)], { type: "application/json" });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url; a.download = "oclinica-brand-tokens.json"; a.click();
+ URL.revokeObjectURL(url);
+}
+
+/* ─── Страница ─────────────────────────────────────────────────────── */
+export default function ColorsPage() {
+ return (
+
+
+ {/* Заголовок */}
+
+
+ Фундамент → 1.3
+
+
+ Цвета
+
+
+ Фирменная цветовая палитра основана на кодах плёнки Oracal. HEX-значения
+ подобраны как ближайшие эквиваленты. Для точной печати используйте коды Oracal.
+
+
+
+
+ ⚠️
+
+ HEX-значения приблизительны. Для оффлайн-носителей используйте
+ официальные коды Oracal.
+
+
+
+ ↓ Экспорт JSON
+
+
+
+
+ {/* 1. Палитра */}
+
+
+
+ Фирменные цвета
+
+
+ Нажмите на значение, чтобы скопировать его в буфер обмена.
+
+
+
+ {BRAND_COLORS.map(c => (
+
+ ))}
+
+
+
+ {/* 2. Контрастность */}
+
+
+
+ Контрастность пар (WCAG)
+
+
+ Проверка соответствия требованиям доступности для основных комбинаций цветов.
+ AA = 4.5:1 для обычного текста, 3:1 для крупного (>18pt).
+
+
+
+ {CONTRAST_PAIRS.map(pair => (
+
+ ))}
+
+
+
+ {/* 3. Применение */}
+
+
+
+ Правила применения
+
+
+
+ {[
+ {
+ icon: "✓",
+ color: "#d1fae5",
+ textColor: "#065f46",
+ title: "Используйте фирменные цвета",
+ text: "Только цвета из палитры обеспечивают узнаваемость бренда на всех носителях.",
+ },
+ {
+ icon: "✓",
+ color: "#d1fae5",
+ textColor: "#065f46",
+ title: "Соблюдайте контрастность",
+ text: "Текст на цветном фоне должен соответствовать минимум AA по WCAG 2.1.",
+ },
+ {
+ icon: "✕",
+ color: "#fee2e2",
+ textColor: "#991b1b",
+ title: "Не смешивайте произвольно",
+ text: "Не используйте фирменные цвета с посторонними цветами без согласования.",
+ },
+ {
+ icon: "✕",
+ color: "#fee2e2",
+ textColor: "#991b1b",
+ title: "Не изменяйте насыщенность",
+ text: "Осветление, затемнение или изменение оттенка недопустимо без необходимости.",
+ },
+ ].map(item => (
+
+
+ {item.icon}
+
+
+
+ {item.title}
+
+
+ {item.text}
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/web/app/foundation/typography/page.tsx b/apps/web/app/foundation/typography/page.tsx
new file mode 100644
index 0000000..a88571a
--- /dev/null
+++ b/apps/web/app/foundation/typography/page.tsx
@@ -0,0 +1,349 @@
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "Типографика | Брендбук О!Клиника",
+};
+
+/* ─── Шкала типографики ────────────────────────────────────────────── */
+const DIN_SCALE = [
+ { token: "h1", size: "40px / 2.5rem", weight: "700", lh: "1.2", sample: "Клиника ухо, горло, нос" },
+ { token: "h2", size: "32px / 2rem", weight: "700", lh: "1.25", sample: "Наши специалисты" },
+ { token: "h3", size: "24px / 1.5rem", weight: "700", lh: "1.3", sample: "Запись на приём" },
+ { token: "h4", size: "20px / 1.25rem",weight: "700", lh: "1.35", sample: "Расписание врачей" },
+ { token: "h5", size: "16px / 1rem", weight: "700", lh: "1.4", sample: "Услуги и цены" },
+ { token: "h6", size: "14px / 0.875rem",weight:"700", lh: "1.4", sample: "Контакты клиники" },
+];
+
+const FIRA_SCALE = [
+ { token: "h1", size: "40px / 2.5rem", weight: "600", lh: "1.2", sample: "Клиника ухо, горло, нос" },
+ { token: "h2", size: "32px / 2rem", weight: "600", lh: "1.25", sample: "Наши специалисты" },
+ { token: "h3", size: "24px / 1.5rem", weight: "600", lh: "1.3", sample: "Запись на приём" },
+ { token: "h4", size: "20px / 1.25rem", weight: "500", lh: "1.35", sample: "Расписание врачей" },
+ { token: "h5", size: "16px / 1rem", weight: "500", lh: "1.4", sample: "Услуги и цены" },
+ { token: "h6", size: "14px / 0.875rem",weight: "500", lh: "1.4", sample: "Контакты клиники" },
+ { token: "body", size: "16px / 1rem", weight: "400", lh: "1.6", sample: "ЛОР-клиника предоставляет полный спектр услуг по диагностике и лечению заболеваний уха, горла и носа." },
+ { token: "body-sm", size: "14px / 0.875rem",weight: "400", lh: "1.6", sample: "Запись по телефону или через форму на сайте. Работаем без выходных." },
+ { token: "caption", size: "12px / 0.75rem", weight: "400", lh: "1.5", sample: "Лицензия № ЛО-77-01-018234 от 12.01.2022" },
+ { token: "label", size: "12px / 0.75rem", weight: "500", lh: "1.4", sample: "СПЕЦИАЛИЗАЦИЯ ВРАЧА" },
+ { token: "overline", size: "10px / 0.625rem",weight: "600", lh: "1.4", sample: "ФУНДАМЕНТ → 1.4" },
+];
+
+/* ─── Компоненты ───────────────────────────────────────────────────── */
+function Section({
+ title,
+ subtitle,
+ children,
+}: {
+ title: string;
+ subtitle?: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ {children}
+
+ );
+}
+
+function UsageBadge({ label, color }: { label: string; color: string }) {
+ return (
+
+ {label}
+
+ );
+}
+
+function TypeRow({
+ token,
+ size,
+ weight,
+ lh,
+ sample,
+ fontFamily,
+}: {
+ token: string;
+ size: string;
+ weight: string;
+ lh: string;
+ sample: string;
+ fontFamily: string;
+}) {
+ const [sizeVal] = size.split(" / ");
+ const pxSize = parseInt(sizeVal);
+
+ return (
+
+ {/* Токен */}
+
+
+ {token}
+
+
+
+ {/* Образец */}
+
+ {sample}
+
+
+ {/* Метаданные */}
+
+
+ {size}
+
+
+ w{weight} · lh{lh}
+
+
+
+ );
+}
+
+/* ─── Страница ─────────────────────────────────────────────────────── */
+export default function TypographyPage() {
+ return (
+
+
+ {/* Заголовок */}
+
+
+ Фундамент → 1.4
+
+
+ Типографика
+
+
+ Бренд использует два шрифта с чётким разделением по контексту применения.
+ Никогда не смешивайте их в одном носителе без необходимости.
+
+
+
+ {/* Карточки шрифтов */}
+
+
+ Шрифты бренда
+
+
+ {/* DINPro */}
+
+
+
+
+ DINPro
+
+
+ Бренд-шрифт · не Google Fonts
+
+
+
+
+
+ Aa Бб Вв
+
+
+ Применяется на: форме сотрудников, бейджах, вывесках, транспорте,
+ полиграфии. Лицензионный шрифт — передаётся дизайнером.
+
+
+ --font-brand
+
+
+
+ {/* Fira Sans */}
+
+
+
+
+ Fira Sans
+
+
+ Веб-шрифт · Google Fonts
+
+
+
+
+
+ Aa Бб Вв
+
+
+ Применяется на: сайте oclinica.ru, в цифровом брендбуке, онлайн-материалах.
+ Подключён через next/font/google (кириллица + латиница).
+
+
+ --font-web
+
+
+
+
+
+ {/* Таблица применения */}
+
+
+ Правило применения
+
+
+
+
+
+ {["Носитель", "Шрифт"].map(h => (
+
+ {h}
+
+ ))}
+
+
+
+ {[
+ ["Сайт, цифровые материалы, брендбук", "Fira Sans"],
+ ["Форма сотрудников, бейджи", "DINPro"],
+ ["Вывески, таблички, навигация", "DINPro"],
+ ["Брендирование транспорта", "DINPro"],
+ ["Визитки, листовки, полиграфия", "DINPro"],
+ ["Telegram-бот, пуш-уведомления", "Fira Sans (системный)"],
+ ].map(([media, font]) => (
+
+ {media}
+
+ {font}
+
+
+ ))}
+
+
+
+
+
+ {/* DINPro Scale */}
+
+
+ {DIN_SCALE.map(row => (
+
+ ))}
+
+
+
+ {/* Fira Sans Scale */}
+
+
+ {FIRA_SCALE.map(row => (
+
+ ))}
+
+
+
+ {/* Живой пример */}
+
+
+
+ Наши специалисты
+
+
+ Запись к ЛОР-врачу без очереди и ожидания
+
+
+ В нашей клинике работают специалисты высшей категории с опытом от 15 лет.
+ Диагностика и лечение заболеваний уха, горла, носа и смежных органов.
+
+
+ Лицензия № ЛО-77-01-018234 · Москва, ул. Примерная, д. 1
+
+
+
+
+
+ );
+}
diff --git a/apps/web/app/offline/badges/page.tsx b/apps/web/app/offline/badges/page.tsx
new file mode 100644
index 0000000..b749a2c
--- /dev/null
+++ b/apps/web/app/offline/badges/page.tsx
@@ -0,0 +1,273 @@
+import type { Metadata } from "next";
+import Image from "next/image";
+
+export const metadata: Metadata = {
+ title: "Бейджи | Брендбук О!Клиника",
+};
+
+function Section({
+ title,
+ subtitle,
+ children,
+}: {
+ title: string;
+ subtitle?: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ {children}
+
+ );
+}
+
+/* Компонент бейджа (масштабированный макет) */
+function BadgeMockup({
+ variant,
+ name,
+ role,
+}: {
+ variant: "light" | "dark";
+ name: string;
+ role: string;
+}) {
+ const isDark = variant === "dark";
+ /* 70×30 мм → пропорция 7:3. Отображаем в 280×120px */
+ return (
+
+
+ {/* Логотип */}
+
+ {/* Разделитель */}
+
+ {/* Текст */}
+
+
+ {name}
+
+
+ {role}
+
+
+
+
+ {isDark ? "Тёмный вариант (серо-голубой)" : "Светлый вариант (бежевый)"}
+
+
+ );
+}
+
+export default function BadgesPage() {
+ return (
+
+
+ {/* Заголовок */}
+
+
+ Оффлайн элементы → 6.2
+
+
+ Бейджи сотрудников
+
+
+ Именные бейджи для идентификации сотрудников клиники. Размер 70×30 мм.
+ Два цветовых варианта в зависимости от должности.
+
+
+
+ {/* Размеры и технические требования */}
+
+
+ {[
+ { label: "Ширина", value: "70 мм" },
+ { label: "Высота", value: "30 мм" },
+ { label: "Материал", value: "ПВХ / металл" },
+ { label: "Крепление", value: "Булавка / клипса" },
+ ].map(({ label, value }) => (
+
+
+ {value}
+
+
+ {label}
+
+
+ ))}
+
+
+
+ {/* Варианты */}
+
+
+ {/* Состав текста */}
+
+
+
+
+
+ {["Элемент", "Содержание", "Шрифт / Размер", "Позиция"].map(h => (
+
+ {h}
+
+ ))}
+
+
+
+ {[
+ ["Логотип", "Логотип клиники", "PNG / SVG", "Левая часть, по центру по высоте"],
+ ["Разделитель", "Вертикальная линия", "1px, 40% прозрачность", "Между логотипом и текстом"],
+ ["ФИО", "Фамилия И.О.", "DINPro Bold 13px", "Правая часть, верхняя строка"],
+ ["Должность", "Полное название должности", "DINPro Regular 10px", "Правая часть, нижняя строка"],
+ ].map(([el, content, font, pos]) => (
+
+ {el}
+ {content}
+ {font}
+ {pos}
+
+ ))}
+
+
+
+
+
+ {/* Цветовые варианты */}
+
+
+ {[
+ {
+ variant: "Светлый (бежевый)",
+ color: "#c4a882",
+ usage: "Медицинский персонал, санитарки, технический персонал",
+ oracal: "081M",
+ },
+ {
+ variant: "Тёмный (серо-голубой)",
+ color: "#5b7b87",
+ usage: "Административный персонал, менеджеры, главный врач",
+ oracal: "073M",
+ },
+ ].map(item => (
+
+
+
+
+ {item.variant}
+
+
+ {item.usage}
+
+
+ Oracal {item.oracal}
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/web/app/offline/navigation/page.tsx b/apps/web/app/offline/navigation/page.tsx
new file mode 100644
index 0000000..b8f02ac
--- /dev/null
+++ b/apps/web/app/offline/navigation/page.tsx
@@ -0,0 +1,252 @@
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "Внутренняя навигация | Брендбук О!Клиника",
+};
+
+function Section({
+ title,
+ subtitle,
+ children,
+}: {
+ title: string;
+ subtitle?: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ {children}
+
+ );
+}
+
+/* Макет настенной таблички */
+function SignMockup({
+ type,
+ text,
+ subtext,
+ bgColor,
+ textColor,
+ accentColor,
+ size,
+}: {
+ type: string;
+ text: string;
+ subtext?: string;
+ bgColor: string;
+ textColor: string;
+ accentColor: string;
+ size: string;
+}) {
+ return (
+
+
+ {/* Цветовая полоса */}
+
+
+
+ {text}
+
+ {subtext && (
+
+ {subtext}
+
+ )}
+
+
+
+
+ );
+}
+
+export default function NavigationPage() {
+ return (
+
+
+ {/* Заголовок */}
+
+
+ Оффлайн элементы → 6.3
+
+
+ Внутренняя навигация
+
+
+ Система навигационных табличек и указателей внутри клиники.
+ Единый стиль с фирменными цветами и шрифтом DINPro.
+
+
+
+ {/* Шаблоны табличек */}
+
+
+ {/* Технические требования */}
+
+
+
+
+
+ {["Параметр", "Значение"].map(h => (
+
+ {h}
+
+ ))}
+
+
+
+ {[
+ ["Основной материал", "ПВХ 3мм / ПС зеркальный / акрил"],
+ ["Покрытие фона", "Oracal плёнка (053M / 073M / 050M)"],
+ ["Шрифт", "DINPro Bold / Regular (DXF для фрезеровки)"],
+ ["Крепление", "Двусторонний скотч / шурупы с дистанционным держателем"],
+ ["Толщина букв (фрезеровка)", "3 мм от основы"],
+ ["Минимальный размер текста", "10 мм по высоте"],
+ ].map(([param, value]) => (
+
+
+ {param}
+
+
+ {value}
+
+
+ ))}
+
+
+
+
+
+ {/* Цвета Oracal */}
+
+
+ {[
+ { code: "053M", hex: "#7ecfca", name: "Акцент / полоса" },
+ { code: "073M", hex: "#5b7b87", name: "Фон указателей" },
+ { code: "050M", hex: "#1b4c72", name: "Фон запрещающих" },
+ { code: "081M", hex: "#c4a882", name: "Акцент на тёмном" },
+ ].map(c => (
+
+
+
+
+ Oracal {c.code}
+
+
+ {c.name}
+
+
+ {c.hex}
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/web/app/offline/print/page.tsx b/apps/web/app/offline/print/page.tsx
new file mode 100644
index 0000000..bfbe2b5
--- /dev/null
+++ b/apps/web/app/offline/print/page.tsx
@@ -0,0 +1,331 @@
+import type { Metadata } from "next";
+import Image from "next/image";
+
+export const metadata: Metadata = {
+ title: "Печатные материалы | Брендбук О!Клиника",
+};
+
+function Section({
+ title,
+ subtitle,
+ children,
+}: {
+ title: string;
+ subtitle?: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ {children}
+
+ );
+}
+
+/* Макет визитки */
+function BusinessCardMockup({ side }: { side: "front" | "back" }) {
+ /* 90×50 мм → пропорция 9:5. Отображаем 288×160px */
+ if (side === "front") {
+ return (
+
+ {/* Верхняя бирюзовая полоса */}
+
+
+
+ {/* Левая колонка с логотипом */}
+
+
+
+
+ {/* Правая колонка с данными */}
+
+
+ Иванова Анна Викторовна
+
+
+ Врач-оториноларинголог
+
+
+ {["+7 (495) 000-00-00", "oclinica.ru"].map(line => (
+
+ {line}
+
+ ))}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
+
+/* Макет листовки */
+function LeafletMockup() {
+ /* А5 = 148×210 мм → пропорция ≈ 1:1.42. Отображаем 180×256px */
+ return (
+
+ {/* Шапка */}
+
+
+
+
+ {/* Тело */}
+
+ {/* Плейсхолдер фото */}
+
+ {/* Заголовок */}
+
+
+ {/* Текст-заглушка */}
+ {[100, 90, 95, 80].map((w, i) => (
+
+ ))}
+ {/* CTA-кнопка */}
+
+
+ ЗАПИСАТЬСЯ НА ПРИЁМ
+
+
+
+
+ );
+}
+
+export default function PrintPage() {
+ return (
+
+
+ {/* Заголовок */}
+
+
+ Оффлайн элементы → 6.5
+
+
+ Печатные материалы
+
+
+ Листовки, визитки и digital-каналы клиники. Все материалы следуют
+ единому фирменному стилю с использованием шрифта DINPro и цветов Oracal.
+
+
+
+ {/* Визитки */}
+
+
+
+
+
+
+
+ {["Элемент", "Содержание"].map(h => (
+ {h}
+ ))}
+
+
+
+ {[
+ ["Лицевая — логотип", "Белый инвертированный на тёмно-серо-голубом фоне"],
+ ["Лицевая — ФИО", "DINPro Bold, 10pt, белый"],
+ ["Лицевая — должность", "DINPro Regular, 8pt, белый 70%"],
+ ["Лицевая — контакты", "DINPro Regular, 7pt, белый 60%"],
+ ["Оборотная", "Логотип тёмно-коричневый на бежевом фоне (081M)"],
+ ["Размер", "90 × 50 мм, 4+4 офсет или цифровая печать"],
+ ].map(([el, content]) => (
+
+ {el}
+ {content}
+
+ ))}
+
+
+
+
+
+ {/* Листовки */}
+
+
+
+
+
+ Структура листовки
+
+
+ {[
+ { zone: "Шапка", desc: "Логотип белый на тёмно-серо-голубом (073M)" },
+ { zone: "Фото", desc: "Фото врача или процедуры, 70% ширины" },
+ { zone: "Заголовок", desc: "DINPro Bold, тёмно-синий (050M), 18–22pt" },
+ { zone: "Текст", desc: "DINPro Regular, 9–10pt, серый #374151" },
+ { zone: "CTA-кнопка", desc: "Основной бирюзовый (053M), DINPro Bold белый" },
+ ].map(({ zone, desc }) => (
+
+ ))}
+
+
+
+
+
+ {/* Telegram-бот */}
+
+
+
+ {[
+ { icon: "🤖", title: "Запись на приём", desc: "Выбор врача, даты и времени через бот" },
+ { icon: "📋", title: "Результаты анализов", desc: "Уведомления и просмотр результатов" },
+ { icon: "💬", title: "Напоминания", desc: "Напоминание о предстоящем визите" },
+ ].map(({ icon, title, desc }) => (
+
+ ))}
+
+
+ В Telegram-боте используется системный шрифт Telegram. Логотип и цвета бренда
+ применяются в аватаре, приветственном изображении и кнопках меню.
+
+
+
+
+
+ );
+}
diff --git a/apps/web/app/offline/transport/page.tsx b/apps/web/app/offline/transport/page.tsx
new file mode 100644
index 0000000..c63ce26
--- /dev/null
+++ b/apps/web/app/offline/transport/page.tsx
@@ -0,0 +1,262 @@
+import type { Metadata } from "next";
+import Image from "next/image";
+
+export const metadata: Metadata = {
+ title: "Брендирование транспорта | Брендбук О!Клиника",
+};
+
+function Section({
+ title,
+ subtitle,
+ children,
+}: {
+ title: string;
+ subtitle?: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ {children}
+
+ );
+}
+
+/* Макет трамвая (упрощённый силуэт) */
+function TramMockup() {
+ return (
+
+ {/* Кузов трамвая */}
+
+ {/* Верхняя полоса — бирюзовая */}
+
+
+ {/* Основная бежевая полоса */}
+
+
+ {/* Нижняя полоса — серо-голубая */}
+
+
+ {/* Логотип по центру бежевой полосы */}
+
+
+
+
+ {/* Окна (декоративные) */}
+ {[80, 180, 280, 380, 460].map(x => (
+
+ ))}
+
+ {/* Колёса (декоративные) */}
+ {[60, 200, 360, 500].map(x => (
+
+ ))}
+
+
+
+ Схема цветового решения (превью, не финальный макет)
+
+
+ );
+}
+
+export default function TransportPage() {
+ return (
+
+
+ {/* Заголовок */}
+
+
+ Оффлайн элементы → 6.4
+
+
+ Брендирование транспорта
+
+
+ Схема нанесения фирменной символики на трамвай и другой общественный транспорт.
+ Трёхполосная структура с логотипом клиники по центру.
+
+
+
+ {/* Макет */}
+
+
+ {/* Цветовая схема */}
+
+
+
+
+
+ {["Зона", "Цвет", "Oracal", "Высота", "Содержимое"].map(h => (
+
+ {h}
+
+ ))}
+
+
+
+ {[
+ ["Верхняя полоса", "#7ecfca", "053M", "~17% высоты", "Декоративная"],
+ ["Центральная полоса", "#c4a882", "081M", "~48% высоты", "Логотип клиники (по центру)"],
+ ["Нижняя полоса", "#5b7b87", "073M", "~35% высоты", "Декоративная / контактная информация"],
+ ].map(([zone, hex, oracal, height, content]) => (
+
+
+ {zone}
+
+
+
+
+
+ {oracal}
+
+ {height}
+ {content}
+
+ ))}
+
+
+
+
+
+ {/* Логотип на транспорте */}
+
+
+ {[
+ { label: "Минимальная длина логотипа", value: "600 мм" },
+ { label: "Отступ от краёв борта", value: "≥ 400 мм" },
+ { label: "Вертикальное выравнивание", value: "По центру полосы" },
+ { label: "Цвет логотипа", value: "Тёмно-коричневый (080M)" },
+ ].map(({ label, value }) => (
+
+ ))}
+
+
+
+
+
⚠️
+
+ Финальные макеты для производства предоставляются дизайнером в форматах AI / PDF с
+ реальными размерами конкретного транспортного средства.
+
+
+
+
+ );
+}
diff --git a/apps/web/app/offline/uniform/page.tsx b/apps/web/app/offline/uniform/page.tsx
new file mode 100644
index 0000000..062c12b
--- /dev/null
+++ b/apps/web/app/offline/uniform/page.tsx
@@ -0,0 +1,207 @@
+import type { Metadata } from "next";
+import Image from "next/image";
+
+export const metadata: Metadata = {
+ title: "Форма сотрудников | Брендбук О!Клиника",
+};
+
+function Section({
+ title,
+ subtitle,
+ children,
+}: {
+ title: string;
+ subtitle?: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ {title}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ {children}
+
+ );
+}
+
+export default function UniformPage() {
+ return (
+
+
+ {/* Заголовок */}
+
+
+ Оффлайн элементы → 6.1
+
+
+ Форма сотрудников
+
+
+ Фирменная медицинская одежда сотрудников клиники. Бежевый костюм
+ с логотипом клиники на левой стороне груди.
+
+
+
+ {/* Описание костюма */}
+
+
+
+
+
+ Состав формы
+
+
+ {["Медицинский костюм (куртка + брюки)", "Цвет: бежевый (Oracal 081M)", "Материал: медицинская ткань", "Логотип вышит или нанесён термопечатью"].map(item => (
+
+ • {item}
+
+ ))}
+
+
+
+
+
+
+
+ {/* Логотип на форме */}
+
+ {/* Визуализация размещения */}
+
+
+ {/* Силуэт куртки (упрощённая схема) */}
+
+ {/* Зона логотипа — левая грудь */}
+
+
+
+ Схема размещения логотипа (превью)
+
+
+
+ {/* Таблица размеров */}
+
+
+
+
+ {["Размер одежды", "Длина логотипа", "Высота логотипа", "Расположение"].map(h => (
+
+ {h}
+
+ ))}
+
+
+
+ {[
+ ["До 46 р. включительно", "70 мм", "25,5 мм", "Левая сторона груди"],
+ ["От 48 р.", "90 мм", "32,8 мм", "Левая сторона груди"],
+ ].map(([size, w, h, pos]) => (
+
+ {size}
+ {w}
+ {h}
+ {pos}
+
+ ))}
+
+
+
+
+
+ {/* Правила */}
+
+
+ {[
+ { ok: true, text: "Носить комплект в полном составе" },
+ { ok: true, text: "Поддерживать чистоту и опрятность формы" },
+ { ok: true, text: "Логотип — только тёмно-коричневый на бежевом" },
+ { ok: false, text: "Носить форму без логотипа" },
+ { ok: false, text: "Изменять цвет или материал формы" },
+ { ok: false, text: "Добавлять сторонние нашивки и знаки" },
+ ].map(item => (
+
+
+ {item.ok ? "✓" : "✕"}
+
+
+ {item.text}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/web/components/layout/Sidebar.tsx b/apps/web/components/layout/Sidebar.tsx
index 429f157..f8c382a 100644
--- a/apps/web/components/layout/Sidebar.tsx
+++ b/apps/web/components/layout/Sidebar.tsx
@@ -19,8 +19,8 @@ const NAV: NavSection[] = [
title: "Фундамент",
items: [
{ label: "Логотип", href: "/foundation/logo" },
- { label: "Цвета", href: "/foundation/colors", soon: true },
- { label: "Типографика", href: "/foundation/typography", soon: true },
+ { label: "Цвета", href: "/foundation/colors" },
+ { label: "Типографика", href: "/foundation/typography" },
{ label: "Иконография", href: "/foundation/icons", soon: true },
],
},
@@ -64,11 +64,11 @@ const NAV: NavSection[] = [
{
title: "Оффлайн элементы",
items: [
- { label: "Форма сотрудников", href: "/offline/uniform", soon: true },
- { label: "Бейджи", href: "/offline/badges", soon: true },
- { label: "Навигация", href: "/offline/navigation", soon: true },
- { label: "Транспорт", href: "/offline/transport", soon: true },
- { label: "Печать", href: "/offline/print", soon: true },
+ { label: "Форма сотрудников", href: "/offline/uniform" },
+ { label: "Бейджи", href: "/offline/badges" },
+ { label: "Навигация", href: "/offline/navigation" },
+ { label: "Транспорт", href: "/offline/transport" },
+ { label: "Печать", href: "/offline/print" },
],
},
{
@@ -171,7 +171,7 @@ export function Sidebar() {
color: "var(--bb-sidebar-text-muted)",
}}
>
- Sprint 1 · v0.1.0
+ Sprint 2 · v0.2.0
);
diff --git a/docs/SPRINTS.md b/docs/SPRINTS.md
index 4a58168..1091079 100644
--- a/docs/SPRINTS.md
+++ b/docs/SPRINTS.md
@@ -53,30 +53,47 @@
---
-## Sprint 2 — Цвета, типографика и оффлайн элементы (Brand Foundation)
+## Sprint 2 — Цвета, типографика и оффлайн элементы (Brand Foundation) ✅ ЗАВЕРШЁН
**Цель:** Секция «Фундамент бренда» — цвета, оба шрифта, а также первый оффлайн-раздел.
### Задачи — цвета
-- [ ] Design: Зафиксировать HEX-эквиваленты всех цветов бренда (053M, 073M, 066M, 050M, 081M, 080M)
-- [ ] FE: Создать CSS-переменные / дизайн-токены для всей цветовой палитры
-- [ ] FE: Страница «Цвета» — палитра с кодами Oracal/Pantone, HEX, RGB, HSL, копирование в клик
-- [ ] FE: Проверка контрастности пар цветов (WCAG AA/AAA)
-- [ ] FE: Экспорт токенов в JSON (Figma-compatible)
+- [x] Design: Зафиксировать HEX-эквиваленты всех цветов бренда (053M, 073M, 066M, 050M, 081M, 080M)
+- [x] FE: Создать CSS-переменные / дизайн-токены для всей цветовой палитры
+- [x] FE: Страница «Цвета» — палитра с кодами Oracal/Pantone, HEX, RGB, HSL, копирование в клик
+- [x] FE: Проверка контрастности пар цветов (WCAG AA/AAA)
+- [x] FE: Экспорт токенов в JSON (Figma-compatible)
### Задачи — типографика
-- [ ] FE: Страница «Типографика» — раздел DINPro (бренд/оффлайн) + раздел Fira Sans (веб)
-- [ ] FE: Шкала размеров для обоих шрифтов: h1–h6, body, caption, label, overline
-- [ ] FE: Чёткое указание: где DINPro, где Fira Sans
-- [ ] FE: Живые примеры текста обоими шрифтами
+- [x] FE: Страница «Типографика» — раздел DINPro (бренд/оффлайн) + раздел Fira Sans (веб)
+- [x] FE: Шкала размеров для обоих шрифтов: h1–h6, body, caption, label, overline
+- [x] FE: Чёткое указание: где DINPro, где Fira Sans
+- [x] FE: Живые примеры текста обоими шрифтами
### Задачи — оффлайн элементы (справочный раздел)
-- [ ] FE: Раздел «Оффлайн элементы» в навигации брендбука
-- [ ] FE: Страница «Форма сотрудников» — фото, правила размещения логотипа, таблица размеров
-- [ ] FE: Страница «Бейджи» — размеры 70×30 мм, варианты, состав текста
-- [ ] FE: Страница «Внутренняя навигация» — шаблоны табличек, материалы, цвета Oracal
-- [ ] FE: Страница «Брендирование транспорта» — макет трамвая, цветовая схема
-- [ ] FE: Страница «Печатные материалы» — листовки, визитки, Telegram-бот
+- [x] FE: Раздел «Оффлайн элементы» в навигации брендбука
+- [x] FE: Страница «Форма сотрудников» — схема размещения логотипа, таблица размеров
+- [x] FE: Страница «Бейджи» — размеры 70×30 мм, варианты (светлый/тёмный), состав текста
+- [x] FE: Страница «Внутренняя навигация» — шаблоны табличек, материалы, цвета Oracal
+- [x] FE: Страница «Брендирование транспорта» — макет трамвая, цветовая схема
+- [x] FE: Страница «Печатные материалы» — листовки, визитки, Telegram-бот
+
+### Фактические результаты
+- Страница `/foundation/colors` — 7 цветов с HEX/RGB/HSL/CSS-var и копированием, WCAG-контраст 7 пар, экспорт JSON
+- Страница `/foundation/typography` — DINPro (оффлайн) + Fira Sans (веб), таблица применения, полные шкалы, живой пример
+- Страница `/offline/uniform` — схема формы, таблица размеров логотипа, правила использования
+- Страница `/offline/badges` — макеты бейджей 70×30 мм (светлый/тёмный), состав текста, применение
+- Страница `/offline/navigation` — 4 шаблона табличек, технические требования, цвета Oracal
+- Страница `/offline/transport` — CSS-макет трамвая с трёхполосной схемой, таблица зон
+- Страница `/offline/print` — макеты визитки (лицо/оборот) и листовки А5, Telegram-бот
+- Sidebar: убраны «скоро» для Цветов, Типографики и всех 5 страниц Оффлайн
+- Версия обновлена до **Sprint 2 · v0.2.0**
+
+### Технические решения Sprint 2
+- Страница «Цвета» — `"use client"` для clipboard API и экспорта JSON
+- WCAG relative luminance вычисляется на клиенте, без зависимостей
+- DINPro отображается с фоллбэком Arial (лицензионный шрифт)
+- Макеты (бейджи, трамвай, визитки) — чистый CSS/Tailwind без внешних зависимостей
**Результат спринта:** Разделы «Цвета», «Типографика» и «Оффлайн элементы» полностью готовы.