Compare commits
8 Commits
sprint/2
...
0570b50d9f
| Author | SHA1 | Date | |
|---|---|---|---|
| 0570b50d9f | |||
| 789f436be1 | |||
| 5298f8c1cd | |||
| ff3f6acc03 | |||
| 39ecd72fde | |||
| a715503ca5 | |||
| 657cc26d3e | |||
| f03599abcf |
@@ -247,7 +247,7 @@ function ContrastRow({ pair }: { pair: typeof CONTRAST_PAIRS[0] }) {
|
||||
|
||||
/* ─── Экспорт токенов ──────────────────────────────────────────────── */
|
||||
function exportTokens() {
|
||||
const tokens: Record<string, Record<string, string>> = { colors: {} };
|
||||
const tokens: Record<string, Record<string, unknown>> = { colors: {} };
|
||||
BRAND_COLORS.forEach(c => {
|
||||
const key = c.oracal !== "—" ? `brand-${c.oracal.toLowerCase()}` : "brand-white";
|
||||
const { r, g, b } = hexToRgb(c.hex);
|
||||
|
||||
@@ -132,31 +132,119 @@ export default function LogoPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 1. Иерархия */}
|
||||
{/* 1. Логотип */}
|
||||
<Section
|
||||
id="hierarchy"
|
||||
title="Иерархия и версии"
|
||||
subtitle="Клиника использует два варианта логотипа в зависимости от контекста применения."
|
||||
title="Логотип"
|
||||
subtitle="Единый логотип клиники. Применяется на всех носителях."
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
<LogoCard
|
||||
src="/logo/logo-transparent.png"
|
||||
alt="Основной логотип Клиника УХО ГОРЛО НОС им. проф. Е.Н. Оленевой"
|
||||
label="Основной логотип"
|
||||
description="Локальные версии по направлениям (ЛОР, аллергология и др.). Применяется в точках контакта с клиентами, на лендингах и сайтах направлений."
|
||||
tag="Точки контакта с клиентом"
|
||||
/>
|
||||
<LogoCard
|
||||
src="/logo/logo-transparent.png"
|
||||
alt="Общий логотип сети клиник"
|
||||
label="Общий логотип"
|
||||
description="Версия сети клиник. Применяется для онлайн и оффлайн коммуникаций с клиентами, во внутренней документации. Допустимо на общем сайте."
|
||||
tag="Сеть клиник · Документация · Сайт"
|
||||
/>
|
||||
<LogoCard
|
||||
src="/logo/logo-transparent.png"
|
||||
alt="Логотип Клиника ухо, горло, нос им. проф. Е.Н. Оленевой"
|
||||
label="Логотип клиники"
|
||||
description="Применяется в точках контакта с клиентами, на лендингах и сайтах направлений. Применяется для онлайн и оффлайн коммуникаций с клиентами, во внутренней документации."
|
||||
tag="Онлайн · Оффлайн · Документация"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{/* 2. Символика знака */}
|
||||
<Section
|
||||
id="symbol"
|
||||
title="Символика знака"
|
||||
subtitle="О том, что стоит за формой логотипа."
|
||||
>
|
||||
{/* Главный тезис */}
|
||||
<div
|
||||
className="rounded-xl border p-6 mb-6"
|
||||
style={{ borderColor: "var(--bb-border)", background: "var(--bb-sidebar-bg)" }}
|
||||
>
|
||||
<p className="text-sm leading-relaxed" style={{ color: "var(--bb-text)" }}>
|
||||
В знаке клиники — три округлых элемента, расположенных с равной дистанцией от центра.
|
||||
Это создаёт ощущение симметрии, порядка и движения, но без завершённой формы (звезды или круга).
|
||||
</p>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
{[
|
||||
"Сохраняет баланс и лёгкость",
|
||||
"Намекает на естественность и органику",
|
||||
"Не замыкает символ — оставляет «дыхание», открытость",
|
||||
].map(item => (
|
||||
<div key={item} className="flex items-start gap-2.5">
|
||||
<div
|
||||
className="w-1.5 h-1.5 rounded-full mt-1.5 shrink-0"
|
||||
style={{ background: "var(--brand-053m)" }}
|
||||
/>
|
||||
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Три значения */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3 mb-6">
|
||||
{[
|
||||
{
|
||||
num: "1",
|
||||
title: "Незамкнутая симметрия",
|
||||
text: "Это процесс, а не результат. Знак говорит: «мы развиваемся, мы живые, мы не идеальны, но гармоничны».",
|
||||
},
|
||||
{
|
||||
num: "2",
|
||||
title: "Три элемента",
|
||||
text: "Классическая структура уха–горла–носа. Триада равновесия, ритм дыхания, символ доверия и открытости.",
|
||||
},
|
||||
{
|
||||
num: "3",
|
||||
title: "Отсутствие замкнутости",
|
||||
text: "Нет барьера — есть приглашение. Открытая форма передаёт заботу, доступность и человечность.",
|
||||
},
|
||||
].map(item => (
|
||||
<div
|
||||
key={item.num}
|
||||
className="rounded-xl border p-5"
|
||||
style={{ borderColor: "var(--bb-border)" }}
|
||||
>
|
||||
<div
|
||||
className="w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold mb-3"
|
||||
style={{ background: "#e0f5f4", color: "var(--brand-073m)" }}
|
||||
>
|
||||
{item.num}
|
||||
</div>
|
||||
<p className="font-medium text-sm mb-2" style={{ color: "var(--bb-text)" }}>
|
||||
{item.title}
|
||||
</p>
|
||||
<p className="text-sm leading-relaxed" style={{ color: "var(--bb-text-muted)" }}>
|
||||
{item.text}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Почему не звезда */}
|
||||
<div
|
||||
className="rounded-xl border p-5 flex gap-4"
|
||||
style={{ borderColor: "#e0f5f4", background: "#f8fffe" }}
|
||||
>
|
||||
<div
|
||||
className="w-8 h-8 rounded-full flex items-center justify-center shrink-0 mt-0.5"
|
||||
style={{ background: "#e0f5f4" }}
|
||||
>
|
||||
<span className="text-base leading-none">○</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-sm mb-1.5" style={{ color: "var(--bb-text)" }}>
|
||||
Почему нет законченной звезды
|
||||
</p>
|
||||
<p className="text-sm leading-relaxed" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Звезда — символ завершённости и сакрального порядка. Знак клиники — символ жизни
|
||||
и взаимодействия. Он ближе по духу к живой биоморфной форме (капли, клетки, лепестки),
|
||||
чем к идеальной математической фигуре. Такой дизайн передаёт не «власть формы»,
|
||||
а заботу, мягкость и человечность.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* 2. Цветовые варианты */}
|
||||
{/* 3. Цветовые варианты */}
|
||||
<Section
|
||||
id="variants"
|
||||
title="Цветовые варианты"
|
||||
@@ -237,8 +325,7 @@ export default function LogoPage() {
|
||||
{/* 4. Минимальные размеры */}
|
||||
<Section
|
||||
id="sizes"
|
||||
title="Минимальные размеры"
|
||||
subtitle="Размеры логотипа для размещения на форме сотрудников."
|
||||
title="Размеры логотипа для размещения на форме сотрудников"
|
||||
>
|
||||
<div className="overflow-hidden rounded-xl border" style={{ borderColor: "var(--bb-border)" }}>
|
||||
<table className="w-full text-sm">
|
||||
|
||||
@@ -31,79 +31,6 @@ function Section({
|
||||
);
|
||||
}
|
||||
|
||||
/* Компонент бейджа (масштабированный макет) */
|
||||
function BadgeMockup({
|
||||
variant,
|
||||
name,
|
||||
role,
|
||||
}: {
|
||||
variant: "light" | "dark";
|
||||
name: string;
|
||||
role: string;
|
||||
}) {
|
||||
const isDark = variant === "dark";
|
||||
/* 70×30 мм → пропорция 7:3. Отображаем в 280×120px */
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div
|
||||
className="rounded-lg flex items-center px-5 gap-4"
|
||||
style={{
|
||||
width: 280,
|
||||
height: 120,
|
||||
background: isDark ? "var(--brand-073m)" : "var(--brand-081m)",
|
||||
border: isDark ? "none" : "1px solid #d1d5db",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{/* Логотип */}
|
||||
<Image
|
||||
src="/logo/logo-transparent.png"
|
||||
alt="Логотип"
|
||||
width={72}
|
||||
height={26}
|
||||
className="object-contain shrink-0"
|
||||
style={{
|
||||
filter: isDark
|
||||
? "brightness(0) invert(1)"
|
||||
: "brightness(0) sepia(1) saturate(2) hue-rotate(330deg) brightness(0.45)",
|
||||
}}
|
||||
/>
|
||||
{/* Разделитель */}
|
||||
<div
|
||||
className="self-stretch w-px mx-1"
|
||||
style={{ background: isDark ? "rgba(255,255,255,0.25)" : "rgba(92,46,14,0.2)" }}
|
||||
/>
|
||||
{/* Текст */}
|
||||
<div>
|
||||
<p
|
||||
className="font-bold leading-tight"
|
||||
style={{
|
||||
fontFamily: "'DINPro', Arial, sans-serif",
|
||||
fontSize: 13,
|
||||
color: isDark ? "#ffffff" : "#5c2e0e",
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
<p
|
||||
className="leading-tight mt-0.5"
|
||||
style={{
|
||||
fontFamily: "'DINPro', Arial, sans-serif",
|
||||
fontSize: 10,
|
||||
color: isDark ? "rgba(255,255,255,0.7)" : "rgba(92,46,14,0.7)",
|
||||
}}
|
||||
>
|
||||
{role}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
|
||||
{isDark ? "Тёмный вариант (серо-голубой)" : "Светлый вариант (бежевый)"}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BadgesPage() {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-8 py-10">
|
||||
@@ -120,24 +47,76 @@ export default function BadgesPage() {
|
||||
Бейджи сотрудников
|
||||
</h1>
|
||||
<p className="text-base max-w-2xl" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Именные бейджи для идентификации сотрудников клиники. Размер 70×30 мм.
|
||||
Два цветовых варианта в зависимости от должности.
|
||||
Именные бейджи для идентификации сотрудников клиники. Размер 70×30 мм,
|
||||
магнитное крепление. Белый фон, чёрный текст.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Размеры и технические требования */}
|
||||
{/* Фотографии */}
|
||||
<Section
|
||||
title="Образцы бейджей"
|
||||
subtitle="Фотографии реальных бейджей из брендбука клиники."
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
{/* Лицевая сторона */}
|
||||
<div>
|
||||
<div
|
||||
className="rounded-xl overflow-hidden border mb-4"
|
||||
style={{ borderColor: "var(--bb-border)" }}
|
||||
>
|
||||
<Image
|
||||
src="/offline/badges/badge-2.jpeg"
|
||||
alt="Лицевая сторона бейджа: Лебединская Елена Александровна, врач оториноларинголог"
|
||||
width={690}
|
||||
height={347}
|
||||
className="w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<p className="font-medium text-sm mb-1" style={{ color: "var(--bb-text)" }}>
|
||||
Лицевая сторона
|
||||
</p>
|
||||
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Белый фон, скруглённые углы. ФИО — крупный шрифт,
|
||||
должности — мелкий. Металлическая рамка.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Оборотная сторона */}
|
||||
<div>
|
||||
<div
|
||||
className="rounded-xl overflow-hidden border mb-4"
|
||||
style={{ borderColor: "var(--bb-border)" }}
|
||||
>
|
||||
<Image
|
||||
src="/offline/badges/badge-1.jpeg"
|
||||
alt="Оборотная сторона бейджа с магнитным креплением"
|
||||
width={657}
|
||||
height={369}
|
||||
className="w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<p className="font-medium text-sm mb-1" style={{ color: "var(--bb-text)" }}>
|
||||
Оборотная сторона
|
||||
</p>
|
||||
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Чёрный пластик, магнитное крепление (CAUTION MAGNETIC).
|
||||
Не требует проколов в одежде.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Технические параметры */}
|
||||
<Section
|
||||
title="Технические параметры"
|
||||
subtitle="Единый стандарт для всех сотрудников клиники."
|
||||
>
|
||||
<div
|
||||
className="grid grid-cols-2 gap-4 sm:grid-cols-4 mb-6"
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4 mb-6">
|
||||
{[
|
||||
{ label: "Ширина", value: "70 мм" },
|
||||
{ label: "Высота", value: "30 мм" },
|
||||
{ label: "Материал", value: "ПВХ / металл" },
|
||||
{ label: "Крепление", value: "Булавка / клипса" },
|
||||
{ label: "Материал", value: "Металл / ПВХ" },
|
||||
{ label: "Крепление", value: "Магнитное" },
|
||||
].map(({ label, value }) => (
|
||||
<div
|
||||
key={label}
|
||||
@@ -155,29 +134,10 @@ export default function BadgesPage() {
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Варианты */}
|
||||
<Section
|
||||
title="Варианты бейджей"
|
||||
subtitle="Макет бейджа (масштаб: 4× от реального размера 70×30 мм)."
|
||||
>
|
||||
<div className="flex flex-wrap gap-8 items-start">
|
||||
<BadgeMockup
|
||||
variant="light"
|
||||
name="Иванова А.В."
|
||||
role="Врач-оториноларинголог"
|
||||
/>
|
||||
<BadgeMockup
|
||||
variant="dark"
|
||||
name="Петров К.С."
|
||||
role="Главный врач"
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Состав текста */}
|
||||
<Section
|
||||
title="Состав текста на бейдже"
|
||||
subtitle="Строгий порядок элементов. Шрифт — DINPro."
|
||||
subtitle="Строгий порядок элементов."
|
||||
>
|
||||
<div
|
||||
className="overflow-hidden rounded-xl border"
|
||||
@@ -186,7 +146,7 @@ export default function BadgesPage() {
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr style={{ background: "var(--bb-sidebar-bg)" }}>
|
||||
{["Элемент", "Содержание", "Шрифт / Размер", "Позиция"].map(h => (
|
||||
{["Элемент", "Содержание", "Оформление"].map(h => (
|
||||
<th
|
||||
key={h}
|
||||
className="text-left px-5 py-3 font-medium"
|
||||
@@ -199,11 +159,10 @@ export default function BadgesPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{[
|
||||
["Логотип", "Логотип клиники", "PNG / SVG", "Левая часть, по центру по высоте"],
|
||||
["Разделитель", "Вертикальная линия", "1px, 40% прозрачность", "Между логотипом и текстом"],
|
||||
["ФИО", "Фамилия И.О.", "DINPro Bold 13px", "Правая часть, верхняя строка"],
|
||||
["Должность", "Полное название должности", "DINPro Regular 10px", "Правая часть, нижняя строка"],
|
||||
].map(([el, content, font, pos]) => (
|
||||
["ФИО", "Фамилия Имя Отчество", "Крупный шрифт, первая строка"],
|
||||
["Должность", "Основная должность", "Мелкий шрифт, вторая строка"],
|
||||
["Специализация", "Специализация / учёная степень (если есть)", "Мелкий шрифт, третья строка"],
|
||||
].map(([el, content, style]) => (
|
||||
<tr
|
||||
key={el}
|
||||
className="border-t"
|
||||
@@ -211,60 +170,33 @@ export default function BadgesPage() {
|
||||
>
|
||||
<td className="px-5 py-3 font-medium" style={{ color: "var(--bb-text)" }}>{el}</td>
|
||||
<td className="px-5 py-3" style={{ color: "var(--bb-text-muted)" }}>{content}</td>
|
||||
<td className="px-5 py-3 font-mono text-xs" style={{ color: "var(--bb-text-muted)" }}>{font}</td>
|
||||
<td className="px-5 py-3" style={{ color: "var(--bb-text-muted)" }}>{pos}</td>
|
||||
<td className="px-5 py-3" style={{ color: "var(--bb-text-muted)" }}>{style}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Цветовые варианты */}
|
||||
<Section
|
||||
title="Применение вариантов"
|
||||
subtitle="Выбор варианта зависит от должности сотрудника."
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
{[
|
||||
{
|
||||
variant: "Светлый (бежевый)",
|
||||
color: "#c4a882",
|
||||
usage: "Медицинский персонал, санитарки, технический персонал",
|
||||
oracal: "081M",
|
||||
},
|
||||
{
|
||||
variant: "Тёмный (серо-голубой)",
|
||||
color: "#5b7b87",
|
||||
usage: "Административный персонал, менеджеры, главный врач",
|
||||
oracal: "073M",
|
||||
},
|
||||
].map(item => (
|
||||
<div
|
||||
key={item.variant}
|
||||
className="flex gap-4 p-4 rounded-xl border"
|
||||
style={{ borderColor: "var(--bb-border)" }}
|
||||
>
|
||||
<div
|
||||
className="w-8 h-8 rounded-lg shrink-0 mt-0.5"
|
||||
style={{ background: item.color }}
|
||||
/>
|
||||
<div>
|
||||
<p className="font-medium text-sm mb-1" style={{ color: "var(--bb-text)" }}>
|
||||
{item.variant}
|
||||
</p>
|
||||
<p className="text-xs mb-1.5" style={{ color: "var(--bb-text-muted)" }}>
|
||||
{item.usage}
|
||||
</p>
|
||||
<span
|
||||
className="text-[10px] font-mono px-1.5 py-0.5 rounded"
|
||||
style={{ background: "#f3f4f6", color: "#374151" }}
|
||||
>
|
||||
Oracal {item.oracal}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* Пример из фото */}
|
||||
<div
|
||||
className="mt-4 rounded-xl border p-4 flex items-start gap-3"
|
||||
style={{ borderColor: "var(--bb-border)", background: "var(--bb-sidebar-bg)" }}
|
||||
>
|
||||
<div
|
||||
className="w-1.5 self-stretch rounded-full shrink-0"
|
||||
style={{ background: "var(--brand-053m)" }}
|
||||
/>
|
||||
<div>
|
||||
<p className="text-xs font-medium mb-1" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Пример из брендбука
|
||||
</p>
|
||||
<p className="text-sm font-semibold" style={{ color: "var(--bb-text)" }}>
|
||||
Лебединская Елена Александровна
|
||||
</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: "var(--bb-text-muted)" }}>
|
||||
врач оториноларинголог · ведущий хирург · кандидат медицинских наук
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
|
||||
@@ -47,92 +47,113 @@ export default function UniformPage() {
|
||||
Форма сотрудников
|
||||
</h1>
|
||||
<p className="text-base max-w-2xl" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Фирменная медицинская одежда сотрудников клиники. Бежевый костюм
|
||||
с логотипом клиники на левой стороне груди.
|
||||
Фирменная медицинская одежда сотрудников клиники. Два цветовых варианта:
|
||||
бежевый с коричневым логотипом и синий с белым логотипом.
|
||||
Логотип размещается на левой стороне груди.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Описание костюма */}
|
||||
{/* Фотографии вариантов */}
|
||||
<Section
|
||||
title="Описание комплекта"
|
||||
subtitle="Стандартная форма для всех сотрудников клиники."
|
||||
title="Варианты формы"
|
||||
subtitle="Фотографии реальной формы сотрудников с логотипом клиники."
|
||||
>
|
||||
<div
|
||||
className="rounded-xl border p-6"
|
||||
style={{ borderColor: "var(--bb-border)", background: "var(--bb-sidebar-bg)" }}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<p className="font-medium text-sm mb-3" style={{ color: "var(--bb-text)" }}>
|
||||
Состав формы
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{["Медицинский костюм (куртка + брюки)", "Цвет: бежевый (Oracal 081M)", "Материал: медицинская ткань", "Логотип вышит или нанесён термопечатью"].map(item => (
|
||||
<li key={item} className="flex items-start gap-2 text-sm" style={{ color: "var(--bb-text-muted)" }}>
|
||||
<span style={{ color: "var(--brand-053m)" }}>•</span> {item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
{/* Бежевый вариант */}
|
||||
<div>
|
||||
<div
|
||||
className="rounded-xl overflow-hidden border mb-4"
|
||||
style={{ borderColor: "var(--bb-border)" }}
|
||||
>
|
||||
<Image
|
||||
src="/offline/uniform/uniform-1.jpeg"
|
||||
alt="Бежевая форма сотрудника клиники с коричневым логотипом"
|
||||
width={742}
|
||||
height={990}
|
||||
className="w-full object-cover"
|
||||
style={{ maxHeight: 480, objectPosition: "top" }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-sm mb-3" style={{ color: "var(--bb-text)" }}>
|
||||
Цветовая схема
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 rounded-lg border mb-1"
|
||||
style={{ background: "#c4a882", borderColor: "var(--bb-border)" }} />
|
||||
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>081M</p>
|
||||
<p className="text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}>#c4a882</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 rounded-lg border mb-1"
|
||||
style={{ background: "#5c2e0e", borderColor: "var(--bb-border)" }} />
|
||||
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>080M</p>
|
||||
<p className="text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}>#5c2e0e</p>
|
||||
</div>
|
||||
<p className="font-medium text-sm mb-1" style={{ color: "var(--bb-text)" }}>
|
||||
Бежевый вариант
|
||||
</p>
|
||||
<p className="text-sm mb-3" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Основная форма для медицинского персонала. Логотип — тёмно-коричневый.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div
|
||||
className="w-4 h-4 rounded"
|
||||
style={{ background: "#c4a882", border: "1px solid #e5e7eb" }}
|
||||
/>
|
||||
<span className="text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Oracal 081M
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ color: "var(--bb-text-muted)" }}>·</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div
|
||||
className="w-4 h-4 rounded"
|
||||
style={{ background: "#5c2e0e", border: "1px solid #e5e7eb" }}
|
||||
/>
|
||||
<span className="text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Логотип 080M
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Синий вариант */}
|
||||
<div>
|
||||
<div
|
||||
className="rounded-xl overflow-hidden border mb-4"
|
||||
style={{ borderColor: "var(--bb-border)" }}
|
||||
>
|
||||
<Image
|
||||
src="/offline/uniform/uniform-2.jpeg"
|
||||
alt="Синяя форма сотрудника клиники с белым логотипом"
|
||||
width={580}
|
||||
height={773}
|
||||
className="w-full object-cover"
|
||||
style={{ maxHeight: 480, objectPosition: "top" }}
|
||||
/>
|
||||
</div>
|
||||
<p className="font-medium text-sm mb-1" style={{ color: "var(--bb-text)" }}>
|
||||
Синий вариант
|
||||
</p>
|
||||
<p className="text-sm mb-3" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Альтернативный вариант. Логотип — белый инвертированный.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div
|
||||
className="w-4 h-4 rounded"
|
||||
style={{ background: "#4a90c4", border: "1px solid #e5e7eb" }}
|
||||
/>
|
||||
<span className="text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Синий медицинский
|
||||
</span>
|
||||
</div>
|
||||
<span style={{ color: "var(--bb-text-muted)" }}>·</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div
|
||||
className="w-4 h-4 rounded"
|
||||
style={{ background: "#ffffff", border: "1px solid #e5e7eb" }}
|
||||
/>
|
||||
<span className="text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}>
|
||||
Логотип белый
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Логотип на форме */}
|
||||
{/* Размеры логотипа */}
|
||||
<Section
|
||||
title="Размещение логотипа"
|
||||
title="Размеры логотипа для размещения на форме сотрудников"
|
||||
subtitle="Логотип располагается на левой стороне груди. Размер зависит от размера одежды."
|
||||
>
|
||||
{/* Визуализация размещения */}
|
||||
<div
|
||||
className="rounded-xl border p-8 mb-6 flex flex-col items-center justify-center"
|
||||
style={{ background: "#c4a882", borderColor: "transparent", minHeight: 200 }}
|
||||
>
|
||||
<div className="relative w-64 h-48 flex items-center justify-center">
|
||||
{/* Силуэт куртки (упрощённая схема) */}
|
||||
<div className="absolute inset-0 rounded-xl opacity-20"
|
||||
style={{ border: "2px dashed #5c2e0e" }} />
|
||||
{/* Зона логотипа — левая грудь */}
|
||||
<div className="absolute top-6 left-10 flex flex-col items-center gap-2">
|
||||
<Image
|
||||
src="/logo/logo-transparent.png"
|
||||
alt="Логотип на форме"
|
||||
width={100}
|
||||
height={36}
|
||||
className="object-contain"
|
||||
style={{ filter: "brightness(0) sepia(1) saturate(2) hue-rotate(330deg) brightness(0.45)" }}
|
||||
/>
|
||||
<div className="border border-dashed rounded px-1"
|
||||
style={{ borderColor: "#5c2e0e50" }}>
|
||||
<p className="text-xs" style={{ color: "#5c2e0e80" }}>← Левая грудь</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 text-sm" style={{ color: "rgba(92,46,14,0.7)" }}>
|
||||
Схема размещения логотипа (превью)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Таблица размеров */}
|
||||
<div
|
||||
className="overflow-hidden rounded-xl border"
|
||||
style={{ borderColor: "var(--bb-border)" }}
|
||||
@@ -178,7 +199,7 @@ export default function UniformPage() {
|
||||
{[
|
||||
{ ok: true, text: "Носить комплект в полном составе" },
|
||||
{ ok: true, text: "Поддерживать чистоту и опрятность формы" },
|
||||
{ ok: true, text: "Логотип — только тёмно-коричневый на бежевом" },
|
||||
{ ok: true, text: "Логотип только в утверждённых цветовых вариантах" },
|
||||
{ ok: false, text: "Носить форму без логотипа" },
|
||||
{ ok: false, text: "Изменять цвет или материал формы" },
|
||||
{ ok: false, text: "Добавлять сторонние нашивки и знаки" },
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import type { NextConfig } from "next";
|
||||
import path from "path";
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
turbopack: {
|
||||
root: path.resolve(__dirname, "../.."),
|
||||
},
|
||||
...(isDev && {
|
||||
turbopack: {
|
||||
root: path.resolve(__dirname, "../.."),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 303 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 224 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
+156
@@ -0,0 +1,156 @@
|
||||
# Деплой — oclinica-brandbook
|
||||
|
||||
## Текущее состояние
|
||||
|
||||
| Сервис | Статус | URL | Платформа |
|
||||
|------------|-------------|----------------------------------------|---------------|
|
||||
| Фронтенд | ✅ Активен | https://web-oclinica.vercel.app | Vercel Hobby |
|
||||
| Бэкенд | локально | http://localhost:3001 | Docker Compose |
|
||||
| База данных | локально | localhost:5433 | PostgreSQL 16 |
|
||||
|
||||
---
|
||||
|
||||
## Фронтенд — Vercel
|
||||
|
||||
### Первоначальная настройка (уже выполнена)
|
||||
|
||||
```bash
|
||||
# 1. Установить Vercel CLI
|
||||
npm install -g vercel
|
||||
|
||||
# 2. Войти в аккаунт (однократно, открывает браузер)
|
||||
vercel login
|
||||
|
||||
# 3. Первый деплой из директории apps/web
|
||||
cd apps/web
|
||||
vercel --yes
|
||||
```
|
||||
|
||||
### Деплой обновлений
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
vercel --prod --yes
|
||||
```
|
||||
|
||||
Деплой занимает ~30 секунд. После завершения изменения сразу доступны по адресу:
|
||||
**https://web-oclinica.vercel.app**
|
||||
|
||||
### Как это работает
|
||||
|
||||
- Vercel автоматически определяет Next.js и использует pnpm для сборки
|
||||
- Каждый `vercel --prod` создаёт новый immutable deployment и привязывает его к production URL
|
||||
- Предыдущие деплои остаются доступны по уникальным preview URL
|
||||
- Логи билда: https://vercel.com/oclinica/web
|
||||
|
||||
### Ограничения Vercel Hobby (бесплатный план)
|
||||
|
||||
| Параметр | Лимит |
|
||||
|-----------------------|-------------------------|
|
||||
| Bandwidth | 100 GB / месяц |
|
||||
| Builds | 6000 минут / месяц |
|
||||
| Serverless Functions | 100 GB-hours / месяц |
|
||||
| Тип использования | Только некоммерческие |
|
||||
|
||||
Для брендбука (внутренний инструмент) лимитов более чем достаточно.
|
||||
|
||||
---
|
||||
|
||||
## Локальная разработка
|
||||
|
||||
### Запуск фронтенда
|
||||
|
||||
```bash
|
||||
# Из корня monorepo
|
||||
pnpm dev
|
||||
|
||||
# Или только фронтенд
|
||||
cd apps/web && pnpm dev
|
||||
```
|
||||
|
||||
Доступен на: http://localhost:3001
|
||||
|
||||
### Запуск бэкенда + БД
|
||||
|
||||
```bash
|
||||
# Запустить PostgreSQL
|
||||
docker compose up -d
|
||||
|
||||
# Запустить NestJS
|
||||
cd apps/api && pnpm dev
|
||||
```
|
||||
|
||||
### .env файлы
|
||||
|
||||
```bash
|
||||
# Скопировать и заполнить
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Содержимое `.env.example`:
|
||||
```
|
||||
DATABASE_URL="postgresql://brandbook:brandbook@localhost:5433/brandbook"
|
||||
API_PORT=3001
|
||||
NEXT_PUBLIC_API_URL=http://localhost:3001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Автодеплой через Gitea Actions (планируется в Sprint 12)
|
||||
|
||||
Для автоматического деплоя при пуше в ветку `main` создать файл
|
||||
`.gitea/workflows/deploy-frontend.yml`:
|
||||
|
||||
```yaml
|
||||
name: Deploy Frontend to Vercel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'apps/web/**'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: apps/web/pnpm-lock.yaml
|
||||
|
||||
- name: Install Vercel CLI
|
||||
run: npm install -g vercel
|
||||
|
||||
- name: Deploy to Vercel
|
||||
run: cd apps/web && vercel --prod --yes --token ${{ secrets.VERCEL_TOKEN }}
|
||||
```
|
||||
|
||||
**Настройка:**
|
||||
1. Получить Vercel Token: https://vercel.com/account/tokens
|
||||
2. Добавить в Gitea: Settings → Secrets → `VERCEL_TOKEN`
|
||||
|
||||
---
|
||||
|
||||
## Хостинг бэкенда (планируется к Sprint 11)
|
||||
|
||||
Бэкенд (NestJS + PostgreSQL) потребуется для экспериментальной секции (Sprint 11).
|
||||
Варианты для рассмотрения:
|
||||
|
||||
| Платформа | PostgreSQL | Бесплатно | Карта |
|
||||
|-----------|-----------|-----------|-------|
|
||||
| Railway | ✅ | $5 кредит / месяц | нужна |
|
||||
| Supabase | ✅ | ✅ (PostgreSQL managed) | нет |
|
||||
| Fly.io | ✅ | ✅ ограниченно | нет |
|
||||
| VPS клиники | ✅ | ✅ (если есть) | нет |
|
||||
|
||||
Рекомендация: **Supabase** для БД (бесплатно, managed PostgreSQL) + **Railway** или VPS для NestJS.
|
||||
+12
-6
@@ -88,6 +88,7 @@
|
||||
- Страница `/offline/print` — макеты визитки (лицо/оборот) и листовки А5, Telegram-бот
|
||||
- Sidebar: убраны «скоро» для Цветов, Типографики и всех 5 страниц Оффлайн
|
||||
- Версия обновлена до **Sprint 2 · v0.2.0**
|
||||
- **Деплой на Vercel:** https://web-oclinica.vercel.app (production, бесплатно)
|
||||
|
||||
### Технические решения Sprint 2
|
||||
- Страница «Цвета» — `"use client"` для clipboard API и экспорта JSON
|
||||
@@ -252,20 +253,25 @@
|
||||
|
||||
---
|
||||
|
||||
## Sprint 12 — Деплой, полировка, документация
|
||||
## Sprint 12 — Полировка, финальный деплой и документация
|
||||
|
||||
**Цель:** Финальный релиз и публикация.
|
||||
**Цель:** Финальный релиз. Фронтенд уже живёт на Vercel с Sprint 2, Sprint 12 — финальная полировка и production-готовность бэкенда.
|
||||
|
||||
### Задачи
|
||||
- [ ] BE + FE: Полный smoke-тест всего брендбука
|
||||
- [ ] FE: Мобильная адаптация — финальная проверка всех страниц
|
||||
- [ ] FE: Accessibility-аудит (WCAG AA)
|
||||
- [ ] Деплой: настройка CI/CD, публикация на сервере
|
||||
- [ ] Docs: Создание `docs/deployment.md`
|
||||
- [ ] Docs: Обновление README.md финальными инструкциями
|
||||
- [ ] Деплой BE: выбрать и настроить хостинг для NestJS + PostgreSQL
|
||||
- [ ] Деплой: настроить автоматический деплой через Gitea Actions → Vercel (при пуше в `main`)
|
||||
- [ ] Docs: Обновление `docs/DEPLOY.md` финальными инструкциями
|
||||
- [ ] Design: Финальный ревью брендбука
|
||||
|
||||
**Результат спринта:** Брендбук задеплоен и доступен по URL.
|
||||
### Текущий статус деплоя
|
||||
- **Фронтенд:** https://web-oclinica.vercel.app (Vercel Hobby, задеплоен в Sprint 2)
|
||||
- **Команда деплоя:** `cd apps/web && vercel --prod --yes`
|
||||
- **Бэкенд:** локально (Docker Compose), хостинг выбирается в Sprint 12
|
||||
|
||||
**Результат спринта:** Брендбук полностью готов, оба сервиса задеплоены, автодеплой настроен.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+34
-19
@@ -37,9 +37,10 @@
|
||||
| Краткое название | oclinica-brandbook |
|
||||
| Сайт клиники | https://oclinica.ru |
|
||||
| Тип системы | Веб-приложение (Living Styleguide) |
|
||||
| Режим работы | Локальная разработка + деплой на сервер |
|
||||
| Режим работы | Локальная разработка + Vercel (preview + production) |
|
||||
| Аудитория | Внутренние дизайнеры клиники, внешние подрядчики |
|
||||
| Хостинг | TBD — будет прописан отдельно |
|
||||
| Хостинг (фронтенд) | Vercel Hobby (бесплатно) — https://web-oclinica.vercel.app |
|
||||
| Хостинг (бэкенд + БД) | TBD — уточняется при переходе к Sprint 11 (экспериментальная секция) |
|
||||
|
||||
---
|
||||
|
||||
@@ -393,17 +394,18 @@
|
||||
|
||||
| Слой | Технология | Версия | Обоснование |
|
||||
|-------------------|-----------------------|----------|--------------------------------------------------|
|
||||
| Фронтенд | Next.js (App Router) | 15.x | SSR/SSG, оптимизация, экосистема React |
|
||||
| Бэкенд | NestJS | 10.x | Типизированный Node.js фреймворк, DI, модули |
|
||||
| Фронтенд | Next.js (App Router) | 16.x | SSR/SSG, оптимизация, экосистема React |
|
||||
| Бэкенд | NestJS | 11.x | Типизированный Node.js фреймворк, DI, модули |
|
||||
| База данных | PostgreSQL | 16.x | Надёжная реляционная БД, JSON-поля для атрибутов |
|
||||
| ORM | Prisma | 5.x | Type-safe запросы, миграции, seed |
|
||||
| Стилизация | CSS Modules | — | Изоляция стилей, нет рантайм-зависимостей |
|
||||
| ORM | Prisma | 7.x | Type-safe запросы, миграции, seed |
|
||||
| Стилизация | Tailwind CSS | 4.x | Utility-first, CSS-переменные, нет рантайм-overhead |
|
||||
| Дизайн-токены | CSS Custom Properties | — | Нативно поддерживается всеми браузерами |
|
||||
| Шрифт (бренд) | DINPro | — | Фирменный шрифт бренда, оффлайн-носители |
|
||||
| Шрифт (веб) | Fira Sans | — | Google Fonts, кириллица, веса 300 и 400, сайт |
|
||||
| Шрифт (веб) | Fira Sans | — | Google Fonts, кириллица, веса 300/400/500/600 |
|
||||
| Авторизация | JWT + httpOnly cookie | — | Безопасное хранение токена |
|
||||
| Пакетный менеджер | pnpm | 9.x | Monorepo workspaces, скорость |
|
||||
| Контейнеризация | Docker + Compose | — | Единообразное окружение dev/prod |
|
||||
| Пакетный менеджер | pnpm | 10.x | Monorepo workspaces, скорость |
|
||||
| Контейнеризация | Docker + Compose | — | Единообразное окружение локальной разработки |
|
||||
| Хостинг фронтенда | Vercel | — | Нативная поддержка Next.js, бесплатный Hobby-план |
|
||||
|
||||
---
|
||||
|
||||
@@ -480,16 +482,29 @@ oclinica-brandbook/
|
||||
| Docker | >= 24 |
|
||||
| Docker Compose | >= 2 |
|
||||
|
||||
### 12.2 Production-сервер
|
||||
### 12.2 Деплой фронтенда (Vercel)
|
||||
|
||||
> TBD — параметры хостинга будут прописаны отдельно.
|
||||
Фронтенд (`apps/web`) деплоится на Vercel Hobby (бесплатно).
|
||||
|
||||
Минимальные ожидаемые требования:
|
||||
- ОС: Ubuntu 22.04+
|
||||
- RAM: 2 GB
|
||||
- Disk: 20 GB
|
||||
- PostgreSQL 16 (или managed database)
|
||||
- Node.js 20 LTS
|
||||
**Production URL:** https://web-oclinica.vercel.app
|
||||
|
||||
**Команда деплоя** (из директории `apps/web`):
|
||||
```bash
|
||||
vercel --prod --yes
|
||||
```
|
||||
|
||||
**Требования:**
|
||||
- Vercel CLI установлен глобально: `npm install -g vercel`
|
||||
- Выполнен `vercel login` (однократно)
|
||||
|
||||
**Деплой занимает ~30 секунд.** После команды изменения сразу доступны по production URL.
|
||||
|
||||
### 12.3 Бэкенд и база данных
|
||||
|
||||
> TBD — параметры хостинга бэкенда (NestJS + PostgreSQL) будут определены к Sprint 11,
|
||||
> когда потребуется работающий API для экспериментальной секции.
|
||||
|
||||
Варианты для рассмотрения: Railway, Render, VPS клиники.
|
||||
|
||||
---
|
||||
|
||||
@@ -511,11 +526,11 @@ oclinica-brandbook/
|
||||
| № | Вопрос | Ответственный | Срок |
|
||||
|----|--------------------------------------------------------------------------------------|---------------|----------|
|
||||
| ОВ-01 | Доступен ли JSON API или REST API на oclinica.ru? Каков формат ответов? | Клиника | Sprint 1 |
|
||||
| ОВ-02 | Параметры хостинга для production-деплоя | Клиника | TBD |
|
||||
| ОВ-02 | ~~Параметры хостинга для production-деплоя~~ **Закрыт:** фронтенд — Vercel Hobby (https://web-oclinica.vercel.app); бэкенд — TBD к Sprint 11 | — | Частично закрыт |
|
||||
| ОВ-03 | Нужна ли страница «Заболевание» как отдельный тип, или это подвид страницы «Услуга»? | Клиника | Sprint 9 |
|
||||
| ОВ-04 | Список иконок — какую стороннюю библиотеку утвердить? (Lucide, Heroicons, и др.) | Совместно | Sprint 2 |
|
||||
| ОВ-05 | ~~Нужен ли раздел «Логотип» в v1.0 или ждём вектор?~~ **Закрыт:** страница логотипа реализуется в Sprint 1 с PNG-версией; вектор будет добавлен позже | — | Закрыт |
|
||||
| ОВ-06 | HEX-эквиваленты цветов Oracal (053M, 073M, 066M, 050M, 081M, 080M) для использования в токенах | Совместно | Sprint 2 |
|
||||
| ОВ-06 | ~~HEX-эквиваленты цветов Oracal~~ **Закрыт:** приблизительные HEX зафиксированы в Sprint 2 и подтверждены как рабочие (053M=#7ecfca, 073M=#5b7b87, 066M=#5bb5ad, 050M=#1b4c72, 081M=#c4a882, 080M=#5c2e0e). Точная калибровка — при получении физических образцов. | — | Закрыт |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user