10 Commits

Author SHA1 Message Date
AR 15 M4 60619114a5 feat(sprint-5): hero #f9f4e7, doctors real photos + typography site section
- Hero banner: bg #f9f4e7 (pixel-sampled), checks #bf9975, outline btn, real doctor photo
- Doctors: H2 text-3xl, stats #60959c no-bg border-bottom, 6 real photos from site
- Colors: add #f9f4e7 (Hero), fix #b8e6ed (form), #e9e4d4 (pill buttons)
- Typography: add «site state 23.03.2026» table (real font sizes/colors by block)
- Docs: SPRINTS.md Sprint 5 progress, LLM_CONTEXT.md v4.2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:00:35 +05:00
AR 15 M4 d4d86e6343 feat(sprint-5): document all blocks from perm.oclinica.ru/lor
Created 8 pages from real site (screenshot source):
- /components/navigation — header: top-bar, logo, nav menu (8 items)
- /blocks/hero         — hero banner: #b8e6ed bg, 3 checkmarks, CTA button
- /blocks/ceo          — intro text: clinic description + Q&A stimuli
- /blocks/doctors      — doctors: 3 stats (#dff0fa) + 6 card grid
- /blocks/reviews      — reviews carousel: #eef4d1 bg, decorative quote
- /blocks/contact-forms — 2 forms: "Будьте здоровы!" (#b8e6ed) + "Стоимость"
- /blocks/news         — 4 news cards row + "Все новости" button
- /blocks/contact      — footer: 4 link columns + logo + address + hours

globals.css: added .bb-nav-link:hover (#0089c3)
Sidebar: all blocks/navigation unlocked (removed soon), v0.5.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 00:05:10 +05:00
AR 15 M4 5b1ace962a plan: align Sprint 7-10 with TZ requirements (roles, API, experiments)
Added missing sprints from TZ spec:
- Sprint 7: Auth (viewer/editor roles, JWT, login, FT-60-64)
- Sprint 8: Real data integration (NestJS proxy → oclinica.ru, FT-41, INT-01-04)
- Sprint 9: Experiments — duplicate button, attribute editor, status workflow (FT-50-56)
- Sprint 10: Final polish, backend deploy

Sprint 6 expanded to all pages (prices, contacts, doctor profile).
Sidebar: Experiments section → "Дубли компонентов / Дубли страниц"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 23:52:46 +05:00
AR 15 M4 52f04f9f5d plan: consolidate all blocks into Sprint 5, add variants/duplicates sprints
- Sprint 5: ALL site blocks in one sprint (header, hero, CEO text, doctors,
  reviews, contact forms, news, footer) — taken from real perm.oclinica.ru/lor
- Sprint 6: Page assemblies (home, disease, doctors)
- Sprint 7: Duplicate/variant system for components and blocks
- Sprint 8: Duplicate/variant system for pages (templates per section)
- Sprint 9: Remaining pages (prices, contacts, doctor profile)
- Sprint 10: Final polish and deploy

Sidebar: added Variants section, reordered Blocks to match real page flow,
removed Experiments section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 23:45:41 +05:00
AR 15 M4 754056642d plan: rewrite Sprint 5-12 — document only what exists on real site
Key principle: brandbook documents only existing perm.oclinica.ru blocks,
no invented style variants. Every sprint starts with Research phase.

Sprint 5: Header/Navigation
Sprint 6: Hero banner + Advantages block
Sprint 7: Intro text (CEO) + Services
Sprint 8: Doctors page blocks
Sprint 9: Reviews + News
Sprint 10: Contact forms + Contact block
Sprint 11: Pages — Disease + Home (assembled)
Sprint 12: Pages — Prices + Contacts + final smoke

Sidebar: removed invented hero variants, added "soon" back to blocks
pending documentation from real site.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 23:31:57 +05:00
AR 15 M4 6ce176f970 chore: close Sprint 4, open Sprint 5
- Sidebar: Hero/CEO-текст unlocked (not soon), Sprint 5 · v0.5.0
- SPRINTS.md: Sprint 4 marked  ЗАВЕРШЁН, Sprint 5 plan updated
- LLM_CONTEXT.md: v4.1 — cards/badges/alerts section 9b added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 23:19:38 +05:00
AR 15 M4 6c5b571884 feat(sprint4): add cards page — DoctorCard, NewsCard, ReviewCard, PriceCard, ServiceCard, badges/tags/alerts
- /components/cards — new page with 5 card types + badges/tags/alerts + LLM block v1.0
- DoctorCard: photo 110×160px placeholder, name, specialty, experience, outline button
- NewsCard: hover bg #eef4d1 + box-shadow (matches real site CSS), CSS class .bb-news-card
- ReviewCard: star rating 1-5 (SVG), text 4-line clamp, bg #eef4d1
- PriceCard: service + price + description + button, highlighted variant (blue border/bg)
- ServiceCard: emoji icon 48×48px, title, description, link
- Badges: 6 color variants (primary/success/warning/danger/neutral/outline)
- Tags: default/active state filters
- Alerts: 4 types (info/success/warning/error) with icons
- globals.css: .bb-news-card:hover, .bb-service-card:hover CSS rules (Sprint 4)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 23:09:22 +05:00
AR 15 M4 2b66fb9cc1 docs: update LLM_CONTEXT.md to v4.0 (Sprint 4 start)
- All 6 Oracal brand colors updated to exact catalog RGB values
- Contrast table recalculated for new hex values
- Color mapping notes updated (typography of differences)
- Logo color variants updated with new hex refs
- CSS vars section updated
- Transport hex refs updated
- Color rules updated (white on 073M/066M/050M/080M, dark on 053M/081M)
- Section 9a: button variants now match real site (primary/outline/teal/pill)
- Section 9a: form controls match real site (height 50px, border #ccc)
- Section 7: /components/cards added as Sprint 4 in progress
- History: version 4.0 added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 23:04:32 +05:00
AR 15 M4 3811c579fd fix(colors): update Oracal brand colors to exact catalog RGB values
053M: #7ecfca → #0089C3 (rgb 0,137,195)
073M: #5b7b87 → #53514E (rgb 83,81,78)
066M: #5bb5ad → #00818C (rgb 0,129,140)
050M: #1b4c72 → #1B2E5D (rgb 27,46,93)
081M: #c4a882 → #A8885C (rgb 168,136,92)
080M: #5c2e0e → #432F1E (rgb 67,47,30)

- Update globals.css CSS vars + sidebar active bg (→ #dff0fa)
- Update focus box-shadow rgba for new 053M blue
- colors/page.tsx: BRAND_COLORS, COLOR_MAPPING notes, CONTRAST_PAIRS
- LLM text v2.2: new hex/rgb, recalculated WCAG contrast ratios,
  updated rules (white text on 073M/066M/050M/080M, dark on 053M/081M)
- Info banner changed from ⚠️ "приблизительны" to ℹ "точные RGB"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 22:46:50 +05:00
AR 15 M4 2b9319e70d chore(sprint4): start Sprint 4 — cards
- Sidebar: Sprint 4 · v0.4.0, /components/cards removed from "soon"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 21:42:46 +05:00
22 changed files with 3080 additions and 312 deletions
+224
View File
@@ -0,0 +1,224 @@
import type { Metadata } from "next";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Вводный текст (CEO-блок). Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
const LLM_CEO_TEXT = `
БЛОК: Вводный текст клиники (CEO-блок)
Источник: perm.oclinica.ru/lor — секция после баннера
Версия: v1.0
НАЗНАЧЕНИЕ:
Информационный блок под hero-баннером. Рассказывает о специализации клиники
и вовлекает пациента через вопросы-стимулы.
СТРУКТУРА:
1. Вводный абзац
«Клиника ухо, нос специализируется на оториноларингологии – лечении взрослых и детей
с ЛОР заболеваниями. ЛОР клиника представлена на двух адресах: ул. Цитная, 9, ул. Г. Звезда, 31а...»
2. Вопросы-стимулы (формат: «— Вопрос?»)
Каждый вопрос — отдельный абзац с тире, связанный с симптомами пациентов:
— У вас болит ухо, заложен нос, першит в горле, и вы не можете понять причину?
— Вам срочно нужен платный ЛОР в Перми или, как ещё говорят, «ухогорлонос»?
— Заболел ребёнок?
— Срочно ищете частные ЛОР-клиники Перми для детей 0+ и взрослых...
— Вам назначили проведение эндоскопической операции на ухе, горле или носе?
3. Заключительный абзац
«Обращайтесь в ЛОР центр ухо, горло, нос в Перми...»
«Будьте здоровы!»
ТИПОГРАФИКА:
Шрифт: Fira Sans, 14px, line-height 1.61.8
Цвет текста: #374151 (#bb-text)
Вопросы: тот же стиль, начинаются с «—»
Ключевые слова в тексте — обычно ссылки синего цвета (#0089c3)
Фон блока: #ffffff, отступы 40–60px сверху и снизу
ПРАВИЛА:
✓ Вопросы начинаются с «—» (тире)
✓ Ключевые медицинские термины — ссылки #0089c3
✓ Текст без H2 заголовка — просто связный параграф
✕ Не добавлять маркированные списки (только тире)
✕ Не менять стиль вопросов на другой формат
`.trim();
const CEO_QUESTIONS = [
"У вас болит ухо, заложен нос, першит в горле, и вы не можете понять причину?",
"Вам срочно нужен платный ЛОР в Перми или, как ещё говорят, «ухогорлонос»?",
"Заболел ребёнок?",
"Срочно ищете частные ЛОР-клиники Перми для детей 0+ и взрослых с удобным режимом работы с 9:00 до 21:00 по будням?",
"Вам назначили проведение эндоскопической операции на ухе, горле или носе?",
];
export default function CeoPage() {
return (
<div className="p-8 max-w-5xl mx-auto space-y-10">
{/* Заголовок страницы */}
<div>
<p
className="text-xs font-semibold uppercase tracking-widest mb-1"
style={{ color: "var(--brand-053m)" }}
>
Блоки
</p>
<h1 className="text-2xl font-bold mb-2" style={{ color: "var(--bb-text)" }}>
Вводный текст (CEO-блок)
</h1>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Блок после hero-баннера на perm.oclinica.ru/lor. Описание специализации клиники
+ вопросы-стимулы для пациентов.
</p>
</div>
{/* Живой пример */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Живой пример
</h2>
<div
className="rounded-xl p-8"
style={{
background: "#fff",
border: "1px solid var(--bb-border)",
fontFamily: "var(--font-web)",
fontSize: 14,
lineHeight: 1.75,
color: "#374151",
}}
>
<p>
Клиника ухо, нос специализируется на оториноларингологии лечении взрослых и детей
с ЛОР заболеваниями. ЛОР клиника представлена на двух адресах:{" "}
<a href="#" style={{ color: "#0089c3" }}>ул. Цитная, 9</a>, <a href="#" style={{ color: "#0089c3" }}>ул. Г. Звезда, 31а</a>.{" "}
Это <a href="#" style={{ color: "#0089c3" }}>Клиника лечения кашля и аллергии</a>.
</p>
<div className="mt-4 space-y-2">
{CEO_QUESTIONS.map((q) => (
<p key={q}> {q}</p>
))}
</div>
<p className="mt-4">
Обращайтесь в ЛОР центр ухо, горло, нос в Перми, наши врачи оториноларингологи обязательно Вам помогут!
</p>
<p className="mt-2">
Клиника ЛОР болезней ухо, горло, нос это наиболее современный центр оториноларингологии в Перми,
благодаря эндоскопическому оборудованию, высокому профессионализму оториноларингологов.
</p>
<p className="mt-4 font-medium">Будьте здоровы!</p>
</div>
</section>
{/* Анатомия */}
<section className="space-y-4">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Структура блока
</h2>
<div className="space-y-3">
{[
{
num: "1",
title: "Вводный абзац",
bg: "#f0f9ff",
desc: 'Специализация клиники, адреса. Ключевые слова — ссылки синего цвета (#0089c3).',
},
{
num: "2",
title: "Вопросы-стимулы",
bg: "#fefce8",
desc: 'Каждый вопрос — отдельный абзац, начинается с «—». Адресован пациенту с конкретным симптомом.',
},
{
num: "3",
title: "Заключительный абзац",
bg: "#f0fdf4",
desc: 'Призыв обращаться. «Будьте здоровы!» — фирменная подпись клиники.',
},
].map((s) => (
<div
key={s.num}
className="flex gap-4 p-4 rounded-xl"
style={{ background: s.bg, border: "1px solid var(--bb-border)" }}
>
<div
className="w-7 h-7 rounded-full flex items-center justify-center font-bold text-sm shrink-0"
style={{ background: "var(--brand-053m)", color: "#fff" }}
>
{s.num}
</div>
<div>
<p className="font-semibold text-sm mb-1" style={{ color: "var(--bb-text)" }}>
{s.title}
</p>
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
{s.desc}
</p>
</div>
</div>
))}
</div>
</section>
{/* Вопросы-стимулы */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Вопросы-стимулы (5 штук)
</h2>
<div className="space-y-2">
{CEO_QUESTIONS.map((q, i) => (
<div
key={i}
className="flex items-start gap-3 p-3 rounded-lg text-sm"
style={{ background: "var(--bb-sidebar-bg)", border: "1px solid var(--bb-border)" }}
>
<span className="font-bold shrink-0 mt-0.5" style={{ color: "var(--brand-053m)" }}>
</span>
<span style={{ color: "var(--bb-text)" }}>{q}</span>
</div>
))}
</div>
</section>
{/* LLM блок */}
<LlmBlock path="/blocks/ceo" version="v1.0" specText={LLM_CEO_TEXT}>
<LlmSection title="Структура блока" />
<LlmTable
headers={["Часть", "Содержимое", "Примечание"]}
rows={[
["Вводный абзац", "Специализация клиники, адреса", "Ключевые слова = ссылки #0089c3"],
["Вопросы-стимулы", "5 вопросов от лица пациента, начинаются с «—»", "Без маркированных списков"],
["Заключение", 'Призыв обращаться + «Будьте здоровы!»', "Фирменная подпись клиники"],
]}
/>
<LlmSection title="Типографика" />
<LlmTable
headers={["Параметр", "Значение"]}
rows={[
["Шрифт", "Fira Sans"],
["Размер", "14px"],
["Line-height", "1.61.8"],
["Цвет текста", "#374151 (--bb-text)"],
["Цвет ссылок в тексте", "#0089c3 (--brand-053m)"],
["Фон блока", "#ffffff"],
]}
/>
<LlmSection title="Правила" />
<LlmRules
rules={[
{ ok: true, text: "Вопросы начинаются с «—» (длинное тире)" },
{ ok: true, text: "Ключевые медицинские термины — ссылки #0089c3" },
{ ok: true, text: "Фирменная подпись «Будьте здоровы!» в конце" },
{ ok: false, text: "Не добавлять маркированные списки (•)" },
{ ok: false, text: "Не добавлять H2 заголовок внутри блока" },
]}
/>
</LlmBlock>
</div>
);
}
+236
View File
@@ -0,0 +1,236 @@
import type { Metadata } from "next";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Формы записи. Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
const LLM_FORMS_TEXT = `
БЛОК: Формы записи и обратной связи
Источник: perm.oclinica.ru/lor — два блока форм на одной странице
Версия: v1.0
ФОРМА 1: «Будьте здоровы!» (форма записи на приём)
Позиция: после блока отзывов
Заголовок H2: «Будьте здоровы!»
Подзаголовок: «Запишитесь на приём к врачу!»
Фон секции: #b8e6ed (светло-бирюзовый)
Поля формы:
1. Текстовый input: placeholder «Введите ваше имя» (height 50px)
2. Телефонный input: placeholder «Введите ваш телефон» (height 50px)
3. Select: «Выберите ЛОР врача» (height 50px)
4. Checkbox: «Отправляя данные, я даю согласие на обработку персональных данных»
Кнопка: «Запишите меня!» — стиль bb-btn-primary (#FFA39C)
Ширина формы: ~400px центрирована или в колонку
ФОРМА 2: «Узнайте стоимость операции» (консультация)
Позиция: после блока новостей
Заголовок H2: «Узнайте стоимость операции»
Подзаголовок: «Проконсультируйтесь с ассистентом хирурга»
Фон секции: #ffffff
Поля формы:
1. Текстовый input: placeholder «каша» / «Введите ваше имя» (height 50px)
2. Телефонный input: placeholder «Введите ваш телефон» (height 50px)
3. Checkbox: «Отправляя данные, я даю согласие на обработку персональных данных»
Кнопка: «Перезвоните мне» — стиль bb-btn-primary (#FFA39C)
ОБЩИЕ ПРАВИЛА:
— Оба поля input/select: bb-input / bb-select (height 50px, border 1px solid #ccc)
— Чекбокс обязателен в обеих формах
— Кнопка отправки: всегда bb-btn-primary (#FFA39C)
— Форма 1 на бирюзовом фоне (#b8e6ed), форма 2 на белом
ПРАВИЛА:
✓ Чекбокс согласия обязателен в каждой форме
✓ Кнопки отправки: bb-btn-primary (#FFA39C)
✓ Форма 1 на фоне #b8e6ed
✕ Не менять порядок полей
✕ Не убирать чекбокс согласия
`.trim();
export default function ContactFormsPage() {
return (
<div className="p-8 max-w-5xl mx-auto space-y-10">
{/* Заголовок страницы */}
<div>
<p
className="text-xs font-semibold uppercase tracking-widest mb-1"
style={{ color: "var(--brand-053m)" }}
>
Блоки
</p>
<h1 className="text-2xl font-bold mb-2" style={{ color: "var(--bb-text)" }}>
Формы записи
</h1>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Два блока форм с perm.oclinica.ru/lor запись на приём и запрос стоимости операции.
</p>
</div>
{/* Форма 1: Будьте здоровы */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Форма 1 «Будьте здоровы!»
</h2>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Фон секции: #b8e6ed. Запись на приём к врачу.
</p>
<div
className="rounded-xl py-10 px-8 flex flex-col items-center gap-6"
style={{ background: "#b8e6ed" }}
>
<div className="text-center">
<h2 className="text-2xl font-bold mb-1" style={{ color: "#111827" }}>
Будьте здоровы!
</h2>
<p className="text-sm" style={{ color: "#374151" }}>
Запишитесь на приём к врачу!
</p>
</div>
<div className="w-full max-w-sm space-y-3">
<input
className="bb-input"
type="text"
placeholder="Введите ваше имя"
readOnly
/>
<input
className="bb-input"
type="tel"
placeholder="Введите ваш телефон"
readOnly
/>
<select className="bb-select" disabled>
<option>Выберите ЛОР врача</option>
</select>
<label className="flex items-start gap-2 text-xs cursor-pointer" style={{ color: "#374151" }}>
<input type="checkbox" className="bb-checkbox mt-0.5" readOnly />
<span>
Отправляя данные, я даю согласие на обработку персональных данных
</span>
</label>
<button className="bb-btn bb-btn-md bb-btn-primary w-full">
Запишите меня!
</button>
</div>
</div>
</section>
{/* Форма 2: Узнайте стоимость */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Форма 2 «Узнайте стоимость операции»
</h2>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Фон секции: #ffffff. Запрос консультации по стоимости.
</p>
<div
className="rounded-xl py-10 px-8 flex flex-col items-center gap-6"
style={{ background: "#fff", border: "1px solid var(--bb-border)" }}
>
<div className="text-center">
<h2 className="text-2xl font-bold mb-1" style={{ color: "#111827" }}>
Узнайте стоимость операции
</h2>
<p className="text-sm" style={{ color: "#374151" }}>
Проконсультируйтесь с ассистентом хирурга
</p>
</div>
<div className="w-full max-w-sm space-y-3">
<input
className="bb-input"
type="text"
placeholder="Введите ваше имя"
readOnly
/>
<input
className="bb-input"
type="tel"
placeholder="Введите ваш телефон"
readOnly
/>
<label className="flex items-start gap-2 text-xs cursor-pointer" style={{ color: "#374151" }}>
<input type="checkbox" className="bb-checkbox mt-0.5" readOnly />
<span>
Отправляя данные, я даю согласие на обработку персональных данных
</span>
</label>
<button className="bb-btn bb-btn-md bb-btn-primary w-full">
Перезвоните мне
</button>
</div>
</div>
</section>
{/* Сравнение двух форм */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Сравнение двух форм
</h2>
<div className="overflow-x-auto rounded-xl" style={{ border: "1px solid var(--bb-border)" }}>
<table className="w-full text-sm border-collapse">
<thead>
<tr style={{ background: "var(--bb-sidebar-bg)" }}>
<th className="text-left px-4 py-3 text-xs font-semibold" style={{ color: "var(--bb-text-muted)", borderBottom: "1px solid var(--bb-border)" }}>Параметр</th>
<th className="text-left px-4 py-3 text-xs font-semibold" style={{ color: "var(--bb-text-muted)", borderBottom: "1px solid var(--bb-border)" }}>Форма 1 «Будьте здоровы!»</th>
<th className="text-left px-4 py-3 text-xs font-semibold" style={{ color: "var(--bb-text-muted)", borderBottom: "1px solid var(--bb-border)" }}>Форма 2 «Стоимость операции»</th>
</tr>
</thead>
<tbody>
{[
["Заголовок", "«Будьте здоровы!»", "«Узнайте стоимость операции»"],
["Подзаголовок", "«Запишитесь на приём к врачу!»", "«Проконсультируйтесь с ассистентом хирурга»"],
["Фон секции", "#b8e6ed", "#ffffff"],
["Поля", "Имя + Телефон + Select врача + Checkbox", "Имя + Телефон + Checkbox"],
["Кнопка", "«Запишите меня!»", "«Перезвоните мне»"],
["Стиль кнопки", "bb-btn-primary (#FFA39C)", "bb-btn-primary (#FFA39C)"],
].map(([param, f1, f2]) => (
<tr key={param} style={{ borderTop: "1px solid var(--bb-border)" }}>
<td className="px-4 py-2 font-medium text-xs" style={{ color: "var(--bb-text)" }}>{param}</td>
<td className="px-4 py-2 text-xs" style={{ color: "var(--bb-text-muted)" }}>{f1}</td>
<td className="px-4 py-2 text-xs" style={{ color: "var(--bb-text-muted)" }}>{f2}</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
{/* LLM блок */}
<LlmBlock path="/blocks/contact-forms" version="v1.0" specText={LLM_FORMS_TEXT}>
<LlmSection title="Форма 1 — Запись на приём" />
<LlmTable
headers={["Поле", "Тип", "Placeholder"]}
rows={[
["Имя", "input[type=text]", "«Введите ваше имя»"],
["Телефон", "input[type=tel]", "«Введите ваш телефон»"],
["Врач", "select", "«Выберите ЛОР врача»"],
["Согласие", "checkbox", "«Отправляя данные, я даю согласие...»"],
["Кнопка", "button bb-btn-primary", "«Запишите меня!»"],
]}
/>
<LlmSection title="Форма 2 — Запрос стоимости" />
<LlmTable
headers={["Поле", "Тип", "Placeholder"]}
rows={[
["Имя", "input[type=text]", "«Введите ваше имя»"],
["Телефон", "input[type=tel]", "«Введите ваш телефон»"],
["Согласие", "checkbox", "«Отправляя данные, я даю согласие...»"],
["Кнопка", "button bb-btn-primary", "«Перезвоните мне»"],
]}
/>
<LlmSection title="Правила" />
<LlmRules
rules={[
{ ok: true, text: "Форма 1: фон #b8e6ed, форма 2: фон #fff" },
{ ok: true, text: "Чекбокс согласия обязателен в каждой форме" },
{ ok: true, text: "Кнопки: bb-btn-primary (#FFA39C)" },
{ ok: true, text: "Все input/select: height 50px (bb-input, bb-select)" },
{ ok: false, text: "Не убирать чекбокс согласия на обработку данных" },
{ ok: false, text: "Не менять стиль кнопки на outline или teal" },
]}
/>
</LlmBlock>
</div>
);
}
+257
View File
@@ -0,0 +1,257 @@
import type { Metadata } from "next";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Подвал (Footer). Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
const FOOTER_COLUMNS = [
{
title: "О клинике",
links: ["Лицензия", "Миссия", "Врачи", "Вакансии", "История", "Образовательная деятельность", "При инфо"],
},
{
title: "Заболевания",
links: ["Ринит", "Отит", "Гайморит", "Тонзиллит", "Полипы носа", "Искривление перегородки"],
},
{
title: "Вопрос-ответ",
links: [
"Что нужно знать до операции на ухо",
"Что нужно знать до операции на нос",
"Отзывы до и после лечения у детей",
"Что нужно знать при лечении у детей",
],
},
{
title: "Операции",
links: ["Септопластика", "Турбинопластика", "Тонзиллэктомия", "Аденотомия", "Тимпанопластика", "Мирингопластика"],
},
];
const LLM_FOOTER_TEXT = `
БЛОК: Подвал сайта (Footer)
Источник: perm.oclinica.ru/lor — нижняя часть страницы
Версия: v1.0
СТРУКТУРА ПОДВАЛА:
1. ОСНОВНАЯ ЧАСТЬ — 4 колонки ссылок:
Колонка 1 «О клинике»: Лицензия, Миссия, Врачи, Вакансии, История, Образовательная деятельность
Колонка 2 «Заболевания»: Ринит, Отит, Гайморит, Тонзиллит, Полипы носа, Искривление перегородки
Колонка 3 «Вопрос-ответ»: 4 вопроса об операциях и лечении
Колонка 4 «Операции»: Септопластика, Турбинопластика, Тонзиллэктомия, Аденотомия и др.
2. НИЖНЯЯ ПОЛОСА:
Левая: Логотип «КЛИНИКА УХО ГОРЛО НОС ИМ. ПРОФ. Е.Н.ОЛЕНЕВОЙ»
Центр:
— «Мы находимся по адресу: Пермь, ул. Г. Звезда...»
— Иконки соцсетей: VK, OK, YouTube, Telegram и другие
Правая: Часы работы:
— Пн-пт: 9:0021:00
— Сб: 9:0018:00
— Вс: выходной
— Вторая клиника часы отдельно
ЦВЕТА:
Фон подвала: #fff или светло-серый (#f8f9fa)
Заголовки колонок: #111827, font-weight 600
Ссылки: #53514e (--brand-073m), hover: #0089c3
Разделитель: border-top 1px solid #e5e7eb
Часы работы: #374151
ПРАВИЛА:
✓ 4 колонки ссылок в основной части
✓ Логотип в нижней полосе слева
✓ Адрес + соцсети в центре нижней полосы
✓ Часы работы справа
✕ Не менять структуру 4 колонок
`.trim();
export default function ContactFooterPage() {
return (
<div className="p-8 max-w-5xl mx-auto space-y-10">
{/* Заголовок страницы */}
<div>
<p
className="text-xs font-semibold uppercase tracking-widest mb-1"
style={{ color: "var(--brand-053m)" }}
>
Блоки
</p>
<h1 className="text-2xl font-bold mb-2" style={{ color: "var(--bb-text)" }}>
Подвал (Footer)
</h1>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Подвал сайта с perm.oclinica.ru 4 колонки ссылок, логотип, адрес, часы работы, соцсети.
</p>
</div>
{/* Живой пример */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Живой пример
</h2>
<div
className="rounded-xl overflow-hidden"
style={{ border: "1px solid var(--bb-border)" }}
>
{/* 4 колонки ссылок */}
<div
className="grid grid-cols-4 gap-6 p-8"
style={{ background: "#f8f9fa", borderBottom: "1px solid #e5e7eb" }}
>
{FOOTER_COLUMNS.map((col) => (
<div key={col.title}>
<p className="font-semibold text-sm mb-3" style={{ color: "#111827" }}>
{col.title}
</p>
<ul className="space-y-1.5">
{col.links.map((link) => (
<li key={link}>
<a
href="#"
className="text-xs bb-nav-link"
style={{ color: "#53514e", textDecoration: "none" }}
>
{link}
</a>
</li>
))}
</ul>
</div>
))}
</div>
{/* Нижняя полоса */}
<div
className="flex items-start justify-between gap-6 px-8 py-5"
style={{ background: "#fff" }}
>
{/* Логотип */}
<div className="flex items-center gap-2 shrink-0">
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold"
style={{ background: "#0089c3" }}
>
</div>
<div>
<div
className="text-[10px] font-bold uppercase leading-tight"
style={{ color: "#53514e" }}
>
Клиника<br />ухо, горло, нос
</div>
<div className="text-[8px] leading-tight" style={{ color: "#9ca3af" }}>
им. проф. Е.Н.Оленевой
</div>
</div>
</div>
{/* Адрес и соцсети */}
<div className="text-center space-y-2">
<p className="text-xs" style={{ color: "#374151" }}>
Мы находимся по адресу: Пермь, ул. Г. Звезда, 31а
</p>
<div className="flex items-center justify-center gap-2">
{["VK", "OK", "YT", "TG"].map((s) => (
<a
key={s}
href="#"
className="text-[11px] px-2 py-1 rounded"
style={{
background: "var(--bb-sidebar-bg)",
border: "1px solid var(--bb-border)",
color: "var(--bb-text-muted)",
}}
>
{s}
</a>
))}
</div>
</div>
{/* Часы работы */}
<div className="text-right text-xs space-y-1" style={{ color: "#374151" }}>
<p className="font-semibold text-xs mb-1" style={{ color: "#111827" }}>
Часы работы:
</p>
<p>Пнпт: 9:0021:00</p>
<p>Сб: 9:0018:00</p>
<p>Вс: выходной</p>
</div>
</div>
</div>
</section>
{/* Колонки */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Четыре колонки ссылок
</h2>
<div className="grid grid-cols-4 gap-3">
{FOOTER_COLUMNS.map((col) => (
<div
key={col.title}
className="rounded-xl p-3 space-y-1"
style={{ background: "var(--bb-sidebar-bg)", border: "1px solid var(--bb-border)" }}
>
<p className="font-semibold text-xs" style={{ color: "var(--bb-text)" }}>
{col.title}
</p>
<p className="text-[10px]" style={{ color: "var(--bb-text-muted)" }}>
{col.links.length} ссылок
</p>
</div>
))}
</div>
</section>
{/* LLM блок */}
<LlmBlock path="/blocks/contact" version="v1.0" specText={LLM_FOOTER_TEXT}>
<LlmSection title="Структура подвала" />
<LlmTable
headers={["Зона", "Содержимое", "Фон"]}
rows={[
["4 колонки ссылок", "О клинике / Заболевания / Вопрос-ответ / Операции", "#f8f9fa"],
["Нижняя полоса — лево", "Логотип клиники (иконка + текст)", "#fff"],
["Нижняя полоса — центр", "Адрес + иконки соцсетей (VK, OK, YT, TG)", "#fff"],
["Нижняя полоса — право", "Часы работы Пн–пт / Сб / Вс", "#fff"],
]}
/>
<LlmSection title="Часы работы" />
<LlmTable
headers={["День", "Часы"]}
rows={[
["Пн–пт", "9:0021:00"],
["Сб", "9:0018:00"],
["Вс", "выходной"],
]}
/>
<LlmSection title="Цвета" />
<LlmTable
headers={["Элемент", "Цвет", "Токен"]}
rows={[
["Заголовки колонок", "#111827, font-weight 600", "—"],
["Ссылки (default)", "#53514e", "--brand-073m"],
["Ссылки (hover)", "#0089c3", "--brand-053m"],
["Фон основной части", "#f8f9fa", "—"],
["Фон нижней полосы", "#ffffff", "—"],
["Разделитель", "1px solid #e5e7eb", "—"],
]}
/>
<LlmSection title="Правила" />
<LlmRules
rules={[
{ ok: true, text: "4 колонки: О клинике / Заболевания / Вопрос-ответ / Операции" },
{ ok: true, text: "Логотип в нижней полосе слева" },
{ ok: true, text: "Адрес и соцсети в центре" },
{ ok: true, text: "Часы работы справа" },
{ ok: false, text: "Не менять структуру и порядок 4 колонок" },
]}
/>
</LlmBlock>
</div>
);
}
+238
View File
@@ -0,0 +1,238 @@
import type { Metadata } from "next";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Блок «Наши врачи». Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
const STATS = [
{ num: "27", label: "ЛОР врачей работает в клинике", prefix: "Ежедневно" },
{ num: "6", label: "кандидатов медицинских наук", prefix: "В том числе" },
{ num: "12 000+", label: "успешно проведённых операций", prefix: "Свыше" },
];
const DOCTORS = [
{
name: "Макарова Людмила Германовна",
spec: "ЛОР врач, сурдолог",
photo: "/doctors/makarova.jpg",
},
{
name: "Семерикова Наталия Александровна",
spec: "ЛОР врач, сурдолог, хирург. К.М.Н. Завед. Центром сурдологии",
photo: "/doctors/semerikova.png",
},
{
name: "Ворончихина Наталия Валерьевна",
spec: "Отоневролог, хирург. К.М.Н., доцент кафедры ПГМУ",
photo: "/doctors/voronchikhina.png",
},
{
name: "Лобанова Ирина Юрьевна",
spec: "ЛОР врач, сурдолог",
photo: "/doctors/lobanova.jpg",
},
{
name: "Торсунова Наталья Сергеевна",
spec: "Специалист по слухопротезированию (сурдоакустик)",
photo: "/doctors/torsunova.jpg",
},
{
name: "Суворова Светлана Викторовна",
spec: "ЛОР врач, сурдолог",
photo: "/doctors/suvorova.jpg",
},
];
const LLM_DOCTORS_TEXT = `
БЛОК: Наши врачи
Источник: perm.oclinica.ru/lor — блок под CEO-текстом
Версия: v1.0
СТРУКТУРА БЛОКА:
1. ЗАГОЛОВОК H2: «Приём ведут опытные ЛОР врачи»
Подзаголовок: описание принципа работы врачей клиники
2. БЛОК СТАТИСТИКИ (3 показателя в ряд):
— «Ежедневно 27 ЛОР врачей работают в клинике»
— «В том числе 6 кандидатов медицинских наук»
— «Свыше 12 000 успешно проведённых операций»
Стиль: весь текст #60959c, bold, ~18px. Фона НЕТ. Снизу border-bottom 3px solid #60959c
3. СЕТКА КАРТОЧЕК ВРАЧЕЙ (6 штук, 6 в ряд):
Каждая карточка:
— Фото врача 110×150px, object-fit: cover, object-position: center top, border-radius 4px
— Имя (12px, font-weight 500, цвет #60959c)
— Специализация (11px, #374151)
Карточки без рамки, gap минимальный
ЦВЕТА:
Заголовок H2: #111827, ~30px (text-3xl)
Статистика текст: #60959c (серо-бирюзовый)
Статистика черта: border-bottom 3px solid #60959c
Имя врача: #60959c
Специализация: #374151
ПРАВИЛА:
✓ Заголовок H2 + описание обязательны
✓ 3 stat-блока в ряд
✓ Сетка 3 колонки, 2 ряда (6 карточек)
✕ Не отображать более 6 врачей в основном блоке
✕ Не убирать статистику
`.trim();
export default function DoctorsBlockPage() {
return (
<div className="p-8 max-w-5xl mx-auto space-y-10">
{/* Заголовок страницы */}
<div>
<p
className="text-xs font-semibold uppercase tracking-widest mb-1"
style={{ color: "var(--brand-053m)" }}
>
Блоки
</p>
<h1 className="text-2xl font-bold mb-2" style={{ color: "var(--bb-text)" }}>
Блок «Наши врачи»
</h1>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Блок на странице perm.oclinica.ru/lor заголовок, 3 стат-блока, сетка из 6 карточек врачей.
</p>
</div>
{/* Живой пример */}
<section className="space-y-4">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Живой пример
</h2>
<div
className="rounded-xl p-8 space-y-8"
style={{ background: "#fff", border: "1px solid var(--bb-border)" }}
>
{/* Заголовок */}
<div>
<h2 className="text-3xl font-bold mb-3" style={{ color: "#111827" }}>
Приём ведут опытные ЛОР врачи
</h2>
<p className="text-sm" style={{ color: "#374151", lineHeight: 1.7 }}>
Фундаментальная теоретическая подготовка и большой практический опыт в сочетании
с внимательным индивидуальным подходом являются причиной успеха лечения тысяч наших пациентов
</p>
</div>
{/* Статистика — текст тёмно-бирюзовым, без фоновых блоков */}
<div className="grid grid-cols-3 gap-6">
{STATS.map((s) => (
<div key={s.num} className="pb-3" style={{ borderBottom: "3px solid #60959c" }}>
<p className="text-lg font-bold leading-snug" style={{ color: "#60959c" }}>
{s.prefix} {s.num} {s.label}
</p>
</div>
))}
</div>
{/* Сетка врачей — плотнее, имена тёмно-бирюзовым */}
<div className="grid grid-cols-6 gap-3">
{DOCTORS.map((doc) => (
<div key={doc.name} className="flex flex-col items-center text-center gap-1.5">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={doc.photo}
alt={doc.name}
style={{
width: 110,
height: 150,
objectFit: "cover",
objectPosition: "center top",
borderRadius: 4,
display: "block",
}}
/>
<div>
<p
className="text-xs font-medium leading-snug"
style={{ color: "#60959c" }}
>
{doc.name}
</p>
<p
className="text-[11px] mt-0.5 leading-snug"
style={{ color: "#374151" }}
>
{doc.spec}
</p>
</div>
</div>
))}
</div>
</div>
</section>
{/* Стат-блоки */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Блок статистики
</h2>
<div className="grid grid-cols-3 gap-6">
{STATS.map((s) => (
<div
key={s.num}
className="pb-3 space-y-1"
style={{ borderBottom: "3px solid #60959c" }}
>
<p className="text-base font-bold leading-snug" style={{ color: "#60959c" }}>
{s.prefix} {s.num} {s.label}
</p>
<p className="text-[10px] font-mono" style={{ color: "var(--bb-text-muted)" }}>
цвет текста: #60959c · черта: 3px solid #60959c
</p>
</div>
))}
</div>
</section>
{/* LLM блок */}
<LlmBlock path="/blocks/doctors" version="v1.1" specText={LLM_DOCTORS_TEXT}>
<LlmSection title="Структура блока" />
<LlmTable
headers={["Элемент", "Содержимое", "Стиль"]}
rows={[
["H2", "«Приём ведут опытные ЛОР врачи»", "~30px (text-3xl), font-bold, #111827"],
["Подзаголовок", "Описание принципа работы", "14px, #374151, line-height 1.7"],
["Статистика", "3 блока в ряд, без фона", "#60959c bold + border-bottom 3px solid #60959c"],
["Сетка врачей", "6 карточек в 1 ряд", "фото 110×150px + имя #60959c + специализация #374151"],
]}
/>
<LlmSection title="Три показателя" />
<LlmTable
headers={["Число", "Описание"]}
rows={STATS.map((s) => [`${s.prefix} ${s.num}`, s.label])}
/>
<LlmSection title="6 врачей слайдера (perm.oclinica.ru/lor)" />
<LlmTable
headers={["Имя", "Специализация"]}
rows={DOCTORS.map((d) => [d.name, d.spec])}
/>
<LlmSection title="Карточка врача" />
<LlmTable
headers={["Поле", "Размер / Стиль"]}
rows={[
["Фото", "88×120px (в блоке) или 110×160px (на странице врачей), object-fit: cover"],
["Имя", "1214px, font-weight 500, #111827"],
["Специализация", "1112px, #6b7280"],
]}
/>
<LlmSection title="Правила" />
<LlmRules
rules={[
{ ok: true, text: "H2 + описание обязательны" },
{ ok: true, text: "3 stat-блока в ряд, фон #dff0fa" },
{ ok: true, text: "Сетка 6 карточек (3 колонки)" },
{ ok: false, text: "Не показывать более 6 врачей в основном блоке" },
{ ok: false, text: "Не убирать статистику" },
]}
/>
</LlmBlock>
</div>
);
}
+286
View File
@@ -0,0 +1,286 @@
import type { Metadata } from "next";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Hero-баннер. Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
const LLM_HERO_TEXT = `
БЛОК: Hero-баннер (главный баннер страницы)
Источник: perm.oclinica.ru/lor — реальный баннер раздела ЛОР
Версия: v1.1
ЗАГОЛОВОК СТРАНИЦЫ (H1, над баннером):
«ЛОР Клиника ухо, горло, нос – медицинский центр лечения ЛОР заболеваний у детей и взрослых»
Шрифт: Fira Sans, ~2628px, weight 700, цвет: #53514e (--brand-073m)
СТРУКТУРА БАННЕРА (двухколоночная, единый фон #f9f4e7):
Левая колонка (~50%):
— Заголовок: «ЭНДОСКОПИЧЕСКОЕ ХИРУРГИЧЕСКОЕ ЛЕЧЕНИЕ ЛОР ОРГАНОВ»
Шрифт: Fira Sans, ~18px, weight 700, uppercase, цвет #111827
— 3 пункта с галочками (✓ зелёный):
1. «БЕЗОПАСНО – оперируют хирурги с 15-летним опытом работы»
2. «БЕЗ ВНЕШНИХ РАЗРЕЗОВ – хирургия сверхмалых размеров»
3. «БЫСТРО – под наблюдением врача пациент находится 1 сутки»
Ключевое слово: uppercase bold; описание: обычный текст, ~13px
— Кнопка «Узнать стоимость операции» — стиль: bb-btn-outline (светлая, без заливки)
Правая колонка (~50%):
— Фото врача с пациентом
— Изображение занимает всю высоту блока
ПОД БАННЕРОМ:
— Кнопки соцсетей (Facebook, VK, Twitter/X), цвет #6b7280
— Счётчик просмотров
ЦВЕТА:
Фон баннера: #f9f4e7 (светло-кремовый, единый для всего блока)
Кнопка CTA: outline-стиль (светлая), не коралловая
Заголовок блока: #111827
Пункты: ключевое слово #111827 bold, описание #374151
Галочка: цвет бренда (бежевый/золотой ~#bf9975)
ПРАВИЛА:
✓ Фон баннера всегда #f9f4e7 (светло-кремовый) — единый, без разделения на зоны
✓ Заголовок блока uppercase, жирный
✓ Три пункта с галочками обязательны
✕ Не менять фон баннера на другой цвет
✕ Не разбивать фон на два разных цвета
✕ Не убирать три пункта с галочками
`.trim();
const HERO_CHECKS = [
{
key: "БЕЗОПАСНО",
desc: "оперируют хирурги с 15-летним опытом работы",
},
{
key: "БЕЗ ВНЕШНИХ РАЗРЕЗОВ",
desc: "хирургия смяткими размерами",
},
{
key: "БЫСТРО",
desc: "под наблюдением врача пациент находится 1 сутки",
},
];
export default function HeroPage() {
return (
<div className="p-8 max-w-5xl mx-auto space-y-10">
{/* Заголовок страницы */}
<div>
<p
className="text-xs font-semibold uppercase tracking-widest mb-1"
style={{ color: "var(--brand-053m)" }}
>
Блоки
</p>
<h1 className="text-2xl font-bold mb-2" style={{ color: "var(--bb-text)" }}>
Hero-баннер
</h1>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Главный баннер страницы раздела ЛОР perm.oclinica.ru/lor. Двухколоночный блок, единый светло-кремовый фон <strong>#f9f4e7</strong>.
</p>
</div>
{/* Живой пример */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Живой пример
</h2>
{/* H1 страницы */}
<h2
className="text-xl font-bold leading-snug"
style={{ color: "#53514e" }}
>
ЛОР Клиника ухо, горло, нос медицинский центр лечения ЛОР заболеваний у детей и взрослых
</h2>
{/* Баннер — единый светло-кремовый фон */}
<div
className="rounded-xl overflow-hidden flex"
style={{ background: "#f9f4e7", minHeight: 280 }}
>
{/* Левая часть — контент на кремовом фоне */}
<div
className="flex flex-col justify-center gap-5 p-8"
style={{ width: "50%", flexShrink: 0 }}
>
<p
className="text-base font-bold uppercase leading-snug"
style={{ color: "#111827" }}
>
Эндоскопическое хирургическое лечение ЛОР органов
</p>
<ul className="space-y-3">
{HERO_CHECKS.map((c) => (
<li key={c.key} className="flex items-start gap-2 text-sm">
<span
className="shrink-0 font-bold"
style={{ color: "#bf9975", marginTop: 1 }}
>
</span>
<span>
<span className="font-bold uppercase" style={{ color: "#111827" }}>
{c.key}
</span>{" "}
<span style={{ color: "#374151" }}> {c.desc}</span>
</span>
</li>
))}
</ul>
<div>
<button className="bb-btn bb-btn-md bb-btn-outline">
Узнать стоимость операции
</button>
</div>
</div>
{/* Правая часть — фото врача */}
<div
className="flex-1 relative overflow-hidden"
style={{ minHeight: 280 }}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src="/hero-doctor.jpg"
alt="Врач на приёме с пациентом"
style={{
width: "100%",
height: "100%",
objectFit: "cover",
objectPosition: "center top",
position: "absolute",
inset: 0,
}}
/>
</div>
</div>
{/* Под баннером: соцсети */}
<div className="flex items-center gap-3 pt-1">
<span className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
Поделиться:
</span>
{["VK", "FB", "TW"].map((s) => (
<button
key={s}
className="text-xs px-2 py-1 rounded"
style={{
background: "var(--bb-sidebar-bg)",
border: "1px solid var(--bb-border)",
color: "var(--bb-text-muted)",
}}
>
{s}
</button>
))}
<span className="text-xs ml-2" style={{ color: "var(--bb-text-muted)" }}>
👁 98 573 просмотра
</span>
</div>
</section>
{/* Анатомия */}
<section className="space-y-4">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Анатомия баннера
</h2>
<div
className="rounded-xl p-5 space-y-2"
style={{ background: "#f5f0e8", border: "1px solid #d5cfbd" }}
>
<p className="font-semibold text-sm" style={{ color: "var(--bb-text)" }}>
Весь баннер единый фон <span className="font-mono">#f9f4e7</span>
</p>
<ul className="space-y-1">
{[
"Фон: #f9f4e7 (светло-кремовый) — одинаковый для всего блока",
"Левая половина (~50%): заголовок uppercase bold + 3 галочки + кнопка outline",
"Правая половина (~50%): фото врача с пациентом",
"Минимальная высота: ~280px",
].map((item) => (
<li key={item} className="text-xs flex items-start gap-1.5">
<span style={{ color: "var(--brand-053m)" }}>·</span>
<span style={{ color: "var(--bb-text-muted)" }}>{item}</span>
</li>
))}
</ul>
</div>
</section>
{/* Пункты с галочками */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Три пункта баннера
</h2>
<div className="space-y-2">
{HERO_CHECKS.map((c) => (
<div
key={c.key}
className="flex items-start gap-3 p-3 rounded-lg"
style={{ background: "var(--bb-sidebar-bg)", border: "1px solid var(--bb-border)" }}
>
<span className="font-bold text-lg shrink-0" style={{ color: "#22c55e" }}>
</span>
<div>
<span className="text-sm font-bold uppercase" style={{ color: "#111827" }}>
{c.key}
</span>
<span className="text-sm" style={{ color: "#374151" }}>
{" "} {c.desc}
</span>
</div>
</div>
))}
</div>
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
Ключевое слово: uppercase + bold. Описание: обычный текст. Галочка: #22c55e.
</p>
</section>
{/* LLM блок */}
<LlmBlock path="/blocks/hero" version="v1.1" specText={LLM_HERO_TEXT}>
<LlmSection title="Структура баннера" />
<LlmTable
headers={["Зона", "Ширина", "Фон", "Содержимое"]}
rows={[
["Весь баннер", "100%", "#f9f4e7", "Единый светло-кремовый фон"],
["Левый блок", "~50%", "#f9f4e7 (общий)", "Заголовок uppercase + 3 галочки + кнопка outline"],
["Правый блок", "~50%", "#f9f4e7 (общий)", "Фото врача с пациентом"],
["Под баннером", "100%", "#fff", "Кнопки соцсетей + счётчик просмотров"],
]}
/>
<LlmSection title="Три пункта баннера" />
<LlmTable
headers={["Ключевое слово", "Описание"]}
rows={HERO_CHECKS.map((c) => [c.key, c.desc])}
/>
<LlmSection title="Цвета" />
<LlmTable
headers={["Элемент", "Цвет", "Токен"]}
rows={[
["Фон баннера (единый)", "#f9f4e7", "Светло-кремовый фон"],
["Кнопка CTA", "outline-стиль", "bb-btn-outline"],
["Заголовок блока", "#111827", "—"],
["Галочка ✓", "#bf9975", "Бежевый (--brand-081m approx.)"],
]}
/>
<LlmSection title="Правила" />
<LlmRules
rules={[
{ ok: true, text: "Фон баннера: #f9f4e7 (светло-кремовый) — единый для всего блока" },
{ ok: true, text: "Кнопка CTA: bb-btn-outline (светлая)" },
{ ok: true, text: "Заголовок: uppercase, bold" },
{ ok: true, text: "Три пункта с галочками ✓ (#bf9975)" },
{ ok: false, text: "Не менять фон баннера на другой цвет" },
{ ok: false, text: "Не разбивать баннер на два разных цвета фона" },
{ ok: false, text: "Не убирать три пункта с галочками" },
]}
/>
</LlmBlock>
</div>
);
}
+198
View File
@@ -0,0 +1,198 @@
import type { Metadata } from "next";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Блок «Новости». Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
const MOCK_NEWS = [
{
date: "20.12.2025",
title: "Наша работа клиники и новогодние праздники",
href: "#",
},
{
date: "11.08.2025",
title: "СЕРВИС ОБНОВЛЕНИЕ: Обновление графика работы клиники",
href: "#",
},
{
date: "12.06.2025",
title: "СЕРВИС ОБНОВЛЕНИЕ: Временное изменение работы клиники 22.06.25 г.",
href: "#",
},
{
date: "11.06.2025",
title: "График работы клиники в ближайшие праздники",
href: "#",
},
];
const LLM_NEWS_TEXT = `
БЛОК: Новости
Источник: perm.oclinica.ru/lor — блок новостей внизу страницы
Версия: v1.0
СТРУКТУРА БЛОКА:
1. ЗАГОЛОВОК H2: «Новости»
Выравнивание: по центру или слева
2. СЕТКА КАРТОЧЕК НОВОСТЕЙ (4 штуки в ряд):
Каждая карточка:
— Дата: малый текст сверху (формат DD.MM.YYYY), цвет #6b7280
— Заголовок новости: синяя ссылка (#0089c3), 14px, font-weight 500, hover underline
— Без превью-изображения
— Без описания (только дата + заголовок-ссылка)
Hover на карточке: bg #eef4d1, box-shadow 0 0 16px 0 #9e9e9a (bb-news-card)
3. КНОПКА «Все новости»:
Стиль: outline (#BF9975) или teal (#60959c), выровнена по центру под сеткой
На сайте: ссылка «Все новости»
ЦВЕТА:
Дата: #6b7280
Заголовок-ссылка: #0089c3 (--brand-053m)
Hover фон карточки: #eef4d1
Hover тень: 0 0 16px 0 #9e9e9a
ПРАВИЛА:
✓ 4 карточки в ряд
✓ Дата сверху карточки
✓ Заголовок = ссылка #0089c3
✓ Hover: bg #eef4d1 (класс bb-news-card)
✓ Кнопка «Все новости» под сеткой
✕ Не добавлять изображения в карточки новостей главной страницы
✕ Не добавлять описание/анонс в карточку
`.trim();
export default function NewsBlockPage() {
return (
<div className="p-8 max-w-5xl mx-auto space-y-10">
{/* Заголовок страницы */}
<div>
<p
className="text-xs font-semibold uppercase tracking-widest mb-1"
style={{ color: "var(--brand-053m)" }}
>
Блоки
</p>
<h1 className="text-2xl font-bold mb-2" style={{ color: "var(--bb-text)" }}>
Блок «Новости»
</h1>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Блок новостей с perm.oclinica.ru/lor 4 карточки в ряд (дата + заголовок-ссылка),
кнопка «Все новости».
</p>
</div>
{/* Живой пример */}
<section className="space-y-4">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Живой пример
</h2>
<div
className="rounded-xl p-8 space-y-6"
style={{ background: "#fff", border: "1px solid var(--bb-border)" }}
>
<h2 className="text-xl font-bold" style={{ color: "#111827" }}>
Новости
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{MOCK_NEWS.map((n) => (
<div
key={n.date}
className="bb-news-card rounded-lg p-3 cursor-pointer"
style={{
border: "1px solid var(--bb-border)",
transition: "background 0.15s, box-shadow 0.15s",
}}
>
<p className="text-xs mb-2" style={{ color: "#6b7280" }}>
{n.date}
</p>
<a
href={n.href}
className="text-sm font-medium leading-snug block"
style={{ color: "#0089c3", textDecoration: "none" }}
>
{n.title}
</a>
</div>
))}
</div>
<div className="text-center">
<button className="bb-btn bb-btn-md bb-btn-outline">
Все новости
</button>
</div>
</div>
</section>
{/* Анатомия карточки */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Анатомия карточки новости
</h2>
<div
className="rounded-xl p-6 max-w-xs"
style={{ background: "var(--bb-sidebar-bg)", border: "1px solid var(--bb-border)" }}
>
<div className="space-y-3">
<div
className="flex items-center gap-2 px-2 py-1 rounded text-xs font-mono"
style={{ background: "#f0f9ff", color: "var(--bb-text-muted)" }}
>
Дата: DD.MM.YYYY · цвет #6b7280
</div>
<div
className="flex items-center gap-2 px-2 py-1 rounded text-xs font-mono"
style={{ background: "#f0f9ff", color: "var(--bb-text-muted)" }}
>
Заголовок-ссылка · #0089c3 · font-weight 500
</div>
<div
className="flex items-center gap-2 px-2 py-1 rounded text-xs font-mono"
style={{ background: "#fefce8", color: "var(--bb-text-muted)" }}
>
Hover: bg #eef4d1 · shadow 0 0 16px #9e9e9a
</div>
</div>
</div>
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
Нет изображений, нет описания только дата и заголовок.
CSS-класс <code className="font-mono">.bb-news-card</code> обрабатывает hover.
</p>
</section>
{/* LLM блок */}
<LlmBlock path="/blocks/news" version="v1.0" specText={LLM_NEWS_TEXT}>
<LlmSection title="Структура карточки новости" />
<LlmTable
headers={["Поле", "Стиль", "Значение"]}
rows={[
["Дата", "text-xs, #6b7280", "Формат DD.MM.YYYY"],
["Заголовок", "text-sm, font-weight 500, #0089c3", "Ссылка на новость"],
["Hover фон", ".bb-news-card:hover", "#eef4d1"],
["Hover тень", ".bb-news-card:hover", "0 0 16px 0 #9e9e9a"],
]}
/>
<LlmSection title="Mock-данные (4 реальные новости с сайта)" />
<LlmTable
headers={["Дата", "Заголовок"]}
rows={MOCK_NEWS.map((n) => [n.date, n.title])}
/>
<LlmSection title="Правила" />
<LlmRules
rules={[
{ ok: true, text: "4 карточки в ряд (grid-cols-4)" },
{ ok: true, text: "Дата сверху, заголовок-ссылка ниже" },
{ ok: true, text: "Hover: класс bb-news-card (bg #eef4d1)" },
{ ok: true, text: "Кнопка «Все новости» под сеткой (bb-btn-outline)" },
{ ok: false, text: "Не добавлять изображения в карточки" },
{ ok: false, text: "Не добавлять текст-анонс в карточку" },
]}
/>
</LlmBlock>
</div>
);
}
+264
View File
@@ -0,0 +1,264 @@
import type { Metadata } from "next";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Блок «Отзывы». Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
const MOCK_REVIEWS = [
{
text: "Спасибо за приём, мне всё понравилось, спасибо за приём, мне всё понравилось. Врач очень внимательный и профессиональный.",
author: "Пациент клиники",
doctor: "Тимофеева Наталья Александровна",
},
{
text: "Очень довольна лечением! Прошла курс процедур, нос дышит отлично. Рекомендую клинику всем.",
author: "Наталья К.",
doctor: "Макарова Людмила Тимофеевна",
},
];
const LLM_REVIEWS_TEXT = `
БЛОК: Отзывы о нас
Источник: perm.oclinica.ru/lor — блок отзывов
Версия: v1.0
СТРУКТУРА БЛОКА:
1. ЗАГОЛОВОК H2: «Отзывы о нас»
Подзаголовок: «За 12 лет работы наши врачи оториноларингологи избавили от болезней ухо, горло, носа
более 50000 пациентов. Но дня сейчас мы высоко ценим каждый положительный отзыв и искренние слова благодарности»
2. КАРУСЕЛЬ ОТЗЫВОВ:
— Большая открывающая кавычка (« «) в цвете #b8e6ed, font-size 80100px
— Текст отзыва: курсив, 1516px, #374151, background #eef4d1
— Ссылка «Читать отзыв полностью» в цвете #0089c3
— Стрелки навигации < > по бокам (round buttons)
— Карусель показывает 1 отзыв за раз
ЦВЕТА:
Фон карточки отзыва: #eef4d1 (светло-жёлто-зелёный)
Кавычка: #b8e6ed (светло-бирюзовый)
Текст отзыва: #374151
Ссылка «Читать полностью»: #0089c3
ПРАВИЛА:
✓ Фон карточки отзыва: #eef4d1 (тот же что у ReviewCard)
✓ Большая декоративная кавычка
✓ Ссылка «Читать отзыв полностью» обязательна
✓ Навигация карусели (стрелки)
✕ Не показывать более 1 отзыва за раз в карусели
✕ Не убирать навигационные стрелки
`.trim();
export default function ReviewsBlockPage() {
return (
<div className="p-8 max-w-5xl mx-auto space-y-10">
{/* Заголовок страницы */}
<div>
<p
className="text-xs font-semibold uppercase tracking-widest mb-1"
style={{ color: "var(--brand-053m)" }}
>
Блоки
</p>
<h1 className="text-2xl font-bold mb-2" style={{ color: "var(--bb-text)" }}>
Блок «Отзывы о нас»
</h1>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Карусель отзывов с perm.oclinica.ru/lor большая кавычка, текст, «Читать полностью», стрелки.
</p>
</div>
{/* Живой пример */}
<section className="space-y-4">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Живой пример
</h2>
<div
className="rounded-xl p-8 space-y-6"
style={{ background: "#fff", border: "1px solid var(--bb-border)" }}
>
{/* Заголовок */}
<div>
<h2 className="text-xl font-bold mb-2" style={{ color: "#111827" }}>
Отзывы о нас
</h2>
<p className="text-sm" style={{ color: "#374151", lineHeight: 1.7 }}>
За 12 лет работы наши врачи оториноларингологи избавили от болезней ухо, горло, носа
более 50 000 пациентов. Но даже сейчас мы высоко ценим каждый положительный отзыв
и искренние слова благодарности.
</p>
</div>
{/* Карусель (статичная) */}
<div className="relative flex items-center gap-4">
{/* Стрелка влево */}
<button
className="shrink-0 w-10 h-10 rounded-full flex items-center justify-center font-bold text-lg"
style={{
background: "var(--bb-sidebar-bg)",
border: "1px solid var(--bb-border)",
color: "var(--bb-text-muted)",
}}
>
</button>
{/* Карточка отзыва */}
<div
className="flex-1 rounded-xl p-6 relative"
style={{ background: "#eef4d1" }}
>
{/* Декоративная кавычка */}
<div
className="text-8xl leading-none mb-2 font-serif"
style={{ color: "#b8e6ed", lineHeight: 0.8 }}
>
«
</div>
<p
className="text-sm italic leading-relaxed"
style={{ color: "#374151" }}
>
{MOCK_REVIEWS[0].text}
</p>
<a
href="#"
className="inline-block mt-3 text-sm"
style={{ color: "#0089c3" }}
>
Читать отзыв полностью
</a>
</div>
{/* Стрелка вправо */}
<button
className="shrink-0 w-10 h-10 rounded-full flex items-center justify-center font-bold text-lg"
style={{
background: "var(--bb-sidebar-bg)",
border: "1px solid var(--bb-border)",
color: "var(--bb-text-muted)",
}}
>
</button>
</div>
</div>
</section>
{/* Несколько примеров */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Примеры отзывов (mock)
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{MOCK_REVIEWS.map((r, i) => (
<div
key={i}
className="rounded-xl p-5 space-y-3"
style={{ background: "#eef4d1", border: "1px solid #d4e6a0" }}
>
<div
className="text-5xl leading-none font-serif"
style={{ color: "#b8e6ed" }}
>
«
</div>
<p
className="text-sm italic leading-relaxed"
style={{ color: "#374151" }}
>
{r.text}
</p>
<div className="pt-1">
<a href="#" className="text-sm" style={{ color: "#0089c3" }}>
Читать отзыв полностью
</a>
</div>
<div className="border-t pt-2" style={{ borderColor: "#c8d8a0" }}>
<p className="text-xs font-medium" style={{ color: "#374151" }}>
{r.author}
</p>
<p className="text-[11px]" style={{ color: "#6b7280" }}>
о враче: {r.doctor}
</p>
</div>
</div>
))}
</div>
</section>
{/* Анатомия */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Анатомия карточки отзыва
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ label: "Фон карточки", value: "#eef4d1", note: "светло-жёлто-зелёный" },
{ label: "Кавычка декоративная", value: "#b8e6ed", note: "80100px, font-serif" },
{ label: "Текст отзыва", value: "#374151", note: "14px, italic" },
{ label: "Ссылка", value: "#0089c3", note: "--brand-053m" },
].map((t) => (
<div
key={t.label}
className="p-3 rounded-xl space-y-1"
style={{ background: "var(--bb-sidebar-bg)", border: "1px solid var(--bb-border)" }}
>
<div
className="w-8 h-8 rounded border"
style={{ background: t.value, borderColor: "var(--bb-border)" }}
/>
<p className="text-xs font-medium" style={{ color: "var(--bb-text)" }}>
{t.label}
</p>
<p className="text-[10px] font-mono" style={{ color: "var(--bb-text-muted)" }}>
{t.value}
</p>
<p className="text-[10px]" style={{ color: "var(--bb-text-muted)" }}>
{t.note}
</p>
</div>
))}
</div>
</section>
{/* LLM блок */}
<LlmBlock path="/blocks/reviews" version="v1.0" specText={LLM_REVIEWS_TEXT}>
<LlmSection title="Структура блока" />
<LlmTable
headers={["Элемент", "Содержимое", "Стиль"]}
rows={[
["H2", "«Отзывы о нас»", "text-xl, font-bold, #111827"],
["Подзаголовок", "Описание достижений клиники за 12 лет", "14px, #374151"],
["Кавычка", "Декоративная «", "80100px, #b8e6ed, font-serif"],
["Текст отзыва", "Полный текст отзыва пациента", "14px, italic, #374151"],
["Ссылка", "«Читать отзыв полностью»", "#0089c3"],
["Стрелки карусели", " ", "Round buttons, фон var(--bb-sidebar-bg)"],
]}
/>
<LlmSection title="Цвета" />
<LlmTable
headers={["Элемент", "Цвет", "Токен"]}
rows={[
["Фон карточки отзыва", "#eef4d1", "—"],
["Декоративная кавычка", "#b8e6ed", "—"],
["Текст отзыва", "#374151", "—"],
["Ссылка", "#0089c3", "--brand-053m"],
]}
/>
<LlmSection title="Правила" />
<LlmRules
rules={[
{ ok: true, text: "Фон карточки отзыва: #eef4d1" },
{ ok: true, text: "Декоративная кавычка цвет #b8e6ed" },
{ ok: true, text: "Ссылка «Читать отзыв полностью» обязательна" },
{ ok: true, text: "Навигация карусели: стрелки ‹ ›" },
{ ok: false, text: "Не показывать несколько отзывов одновременно" },
{ ok: false, text: "Не убирать навигационные стрелки" },
]}
/>
</LlmBlock>
</div>
);
}
+629
View File
@@ -0,0 +1,629 @@
import type { Metadata } from "next";
import { CodeCopy } from "@/components/ui/CodeCopy";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Карточки. Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
function Section({
id, title, subtitle, children,
}: {
id?: string; title: string; subtitle?: string; children: React.ReactNode;
}) {
return (
<section id={id} className="mb-14">
<div className="mb-6">
<h2 className="text-xl font-semibold" style={{ color: "var(--bb-text)" }}>{title}</h2>
{subtitle && <p className="mt-1 text-sm" style={{ color: "var(--bb-text-muted)" }}>{subtitle}</p>}
</div>
{children}
</section>
);
}
/* ─── Карточка врача ────────────────────────────────────────────────── */
function DoctorCard({
name, specialty, experience, photo,
}: {
name: string; specialty: string; experience: string; photo?: string;
}) {
return (
<div
className="flex gap-4 p-4 rounded-xl border bg-white transition-shadow"
style={{ borderColor: "var(--bb-border)" }}
>
{/* Фото */}
<div
className="shrink-0 rounded-lg overflow-hidden"
style={{ width: 110, height: 160, background: "#dff0fa" }}
>
{photo ? (
<img src={photo} alt={name} style={{ width: "100%", height: "100%", objectFit: "cover" }} />
) : (
<div
className="w-full h-full flex flex-col items-center justify-center gap-1"
style={{ color: "var(--brand-053m)", opacity: 0.5 }}
>
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
<span className="text-xs">фото</span>
</div>
)}
</div>
{/* Информация */}
<div className="flex flex-col justify-between min-w-0">
<div>
<h3 className="font-semibold text-sm leading-tight mb-1" style={{ color: "var(--bb-text)" }}>
{name}
</h3>
<p className="text-xs mb-2" style={{ color: "var(--bb-text-muted)" }}>{specialty}</p>
<p className="text-xs" style={{ color: "var(--brand-053m)" }}>{experience}</p>
</div>
<button className="bb-btn bb-btn-sm bb-btn-outline mt-3 self-start">
Записаться
</button>
</div>
</div>
);
}
/* ─── Карточка новости ──────────────────────────────────────────────── */
function NewsCard({
date, title, snippet, category,
}: {
date: string; title: string; snippet: string; category?: string;
}) {
return (
<div
className="bb-news-card rounded-xl border overflow-hidden cursor-pointer transition-all"
style={{ borderColor: "var(--bb-border)", background: "#fff" }}
>
{/* Превью */}
<div
className="h-36 flex items-center justify-center"
style={{ background: "#f0f9ff" }}
>
{category && (
<span
className="px-3 py-1 rounded-full text-xs font-semibold"
style={{ background: "var(--brand-053m)", color: "#fff" }}
>
{category}
</span>
)}
</div>
{/* Контент */}
<div className="p-4">
<p className="text-xs mb-2" style={{ color: "var(--bb-text-muted)" }}>{date}</p>
<h3 className="font-semibold text-sm leading-tight mb-2" style={{ color: "var(--bb-text)" }}>
{title}
</h3>
<p className="text-xs leading-relaxed mb-3" style={{ color: "var(--bb-text-muted)" }}>
{snippet}
</p>
<span className="text-xs font-medium" style={{ color: "var(--brand-053m)" }}>
Читать далее
</span>
</div>
</div>
);
}
/* ─── Карточка отзыва ───────────────────────────────────────────────── */
function ReviewCard({
author, date, text, rating, doctor,
}: {
author: string; date: string; text: string; rating: number; doctor?: string;
}) {
return (
<div
className="rounded-xl border p-5"
style={{ borderColor: "var(--bb-border)", background: "#eef4d1" }}
>
{/* Звёзды */}
<div className="flex gap-0.5 mb-3">
{Array.from({ length: 5 }, (_, i) => (
<svg key={i} width="16" height="16" viewBox="0 0 24 24" fill={i < rating ? "#f59e0b" : "none"} stroke="#f59e0b" strokeWidth="1.5">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
</svg>
))}
<span className="text-xs ml-1 font-medium" style={{ color: "#92400e" }}>{rating}/5</span>
</div>
{/* Текст */}
<p
className="text-sm leading-relaxed mb-4"
style={{
color: "var(--bb-text)",
display: "-webkit-box",
WebkitLineClamp: 4,
WebkitBoxOrient: "vertical",
overflow: "hidden",
}}
>
{text}
</p>
{/* Автор */}
<div className="flex items-end justify-between">
<div>
<p className="text-sm font-semibold" style={{ color: "var(--bb-text)" }}>{author}</p>
{doctor && <p className="text-xs mt-0.5" style={{ color: "var(--bb-text-muted)" }}>Врач: {doctor}</p>}
</div>
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>{date}</p>
</div>
</div>
);
}
/* ─── Карточка цены ─────────────────────────────────────────────────── */
function PriceCard({
service, price, description, highlighted,
}: {
service: string; price: string; description?: string; highlighted?: boolean;
}) {
return (
<div
className="rounded-xl border p-5 flex flex-col gap-3"
style={{
borderColor: highlighted ? "var(--brand-053m)" : "var(--bb-border)",
background: highlighted ? "#f0f9ff" : "#fff",
}}
>
<div className="flex items-start justify-between gap-3">
<p className="text-sm font-medium flex-1" style={{ color: "var(--bb-text)" }}>{service}</p>
<p className="text-lg font-bold shrink-0" style={{ color: highlighted ? "var(--brand-053m)" : "var(--bb-text)" }}>
{price}
</p>
</div>
{description && (
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>{description}</p>
)}
<button className="bb-btn bb-btn-sm bb-btn-outline self-start">
Записаться
</button>
</div>
);
}
/* ─── Карточка услуги ───────────────────────────────────────────────── */
function ServiceCard({
title, description, icon,
}: {
title: string; description: string; icon: string;
}) {
return (
<div
className="bb-service-card rounded-xl border p-5 flex flex-col gap-3 cursor-pointer transition-shadow"
style={{ borderColor: "var(--bb-border)", background: "#fff" }}
>
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl"
style={{ background: "#dff0fa" }}
>
{icon}
</div>
<div>
<h3 className="font-semibold text-sm mb-1" style={{ color: "var(--bb-text)" }}>{title}</h3>
<p className="text-xs leading-relaxed" style={{ color: "var(--bb-text-muted)" }}>{description}</p>
</div>
<span className="text-xs font-medium" style={{ color: "var(--brand-053m)" }}>
Подробнее
</span>
</div>
);
}
/* ─── LLM текст ─────────────────────────────────────────────────────── */
const LLM_CARDS_TEXT = `КАРТОЧКИ — LLM-спецификация
Версия: v1.0 · /components/cards
КАРТОЧКА ВРАЧА (DoctorCard)
Структура: фото (110×160px) + имя + специализация + опыт + кнопка «Записаться»
Фото: 110px × 160px, object-fit: cover, border-radius: 8px, фон-placeholder: #dff0fa
Кнопка: .bb-btn-outline .bb-btn-sm
Источник: .doctor на perm.oclinica.ru
КАРТОЧКА НОВОСТИ (NewsCard)
Структура: превью (h=144px) + дата + заголовок + анонс (4 строки) + «Читать далее →»
Hover: background #eef4d1 + box-shadow 0 0 16px #9e9e9a
Источник: #block-views-last-news-block-1 .views-column на сайте (200×200px)
Без hover: background #fff, border 1px
КАРТОЧКА ОТЗЫВА (ReviewCard)
Структура: рейтинг (звёзды 1–5) + текст (4 строки, overflow hidden) + автор + дата + врач
Фон: #eef4d1 (светло-жёлтый) — из реального CSS сайта
Звёзды: SVG polygon, filled #f59e0b
КАРТОЧКА ЦЕНЫ (PriceCard)
Структура: услуга + цена (bold) + описание + кнопка
highlighted-вариант: border var(--brand-053m), bg #f0f9ff, цена тоже --brand-053m
КАРТОЧКА УСЛУГИ (ServiceCard)
Структура: иконка (emoji, 48×48px, bg #dff0fa) + заголовок + описание + «Подробнее →»
Hover: box-shadow (0.5rem smth)
БЕЙДЖИ (Badge)
Варианты: primary (#0089c3), success (#059669), warning (#d97706), danger (#dc2626), neutral (#6b7280)
Размер: text-xs, px-2.5 py-0.5, border-radius: full
CSS: inline-flex, font-weight: 600
ТЕГИ (Tag)
Варианты: default (border, text), filled (bg), removable (с кнопкой ×)
Цвет: --brand-053m или нейтральный (#e5e7eb bg)
Размер: text-xs, px-2 py-1, border-radius: 4px
АЛЕРТЫ (Alert)
Варианты: info (#dff0fa фон, #075985 текст), success (#d1fae5, #065f46), warning (#fef3c7, #92400e), error (#fee2e2, #991b1b)
Структура: иконка (16×16px) + заголовок + описание
Без кнопки закрыть в базовом варианте
ПРАВИЛА
✓ DoctorCard: всегда показывать фото-placeholder если нет фото (не ломать layout)
✓ ReviewCard: обрезать текст после 4 строк (WebkitLineClamp)
✓ PriceCard: highlighted = акционная или рекомендуемая позиция
✓ NewsCard: hover-эффект обязателен (#eef4d1 + box-shadow)
✓ Badge: не более 2–3 бейджей рядом
✓ Alert: одновременно не более 1 alert одного типа на экране
✕ Не смешивать типы карточек в одной сетке без заголовка секции
✕ Не использовать ReviewCard без рейтинга`.trim();
/* ─── Данные примеров ───────────────────────────────────────────────── */
const DOCTORS = [
{
name: "Иванова Анна Сергеевна",
specialty: "Оториноларинголог (ЛОР), высшая категория",
experience: "Стаж 18 лет",
},
{
name: "Петров Дмитрий Александрович",
specialty: "Сурдолог, аудиолог",
experience: "Стаж 12 лет",
},
{
name: "Соколова Мария Ивановна",
specialty: "Детский ЛОР, ринолог",
experience: "Стаж 9 лет",
},
];
const REVIEWS = [
{
author: "Елена К.",
date: "15 марта 2026",
rating: 5,
doctor: "Иванова А.С.",
text: "Очень довольна визитом. Доктор внимательно выслушала все жалобы, провела осмотр и объяснила причину заболевания. Назначила лечение, которое помогло уже через 3 дня. Рекомендую!",
},
{
author: "Михаил Р.",
date: "10 марта 2026",
rating: 4,
doctor: "Петров Д.А.",
text: "Хороший специалист, всё объяснил понятно. Подождал немного дольше, чем ожидал, но качество приёма на высоте. Буду обращаться снова.",
},
];
const PRICES = [
{ service: "Первичный приём ЛОР-врача", price: "1 500 ₽", description: "Включает осмотр и консультацию" },
{ service: "Повторный приём", price: "1 000 ₽", description: "До 14 дней после первичного" },
{ service: "Промывание миндалин", price: "800 ₽", highlighted: true, description: "Аппаратное — аккустический вакуум" },
{ service: "Аудиометрия", price: "1 200 ₽", description: "Исследование слуха" },
];
const SERVICES = [
{ title: "Лечение ангины и тонзиллита", description: "Консервативное и хирургическое лечение заболеваний миндалин", icon: "🦷" },
{ title: "Аудиология и сурдология", description: "Диагностика нарушений слуха, подбор слуховых аппаратов", icon: "👂" },
{ title: "Детский ЛОР", description: "Специализация на лечении ЛОР-заболеваний у детей от 0 лет", icon: "👶" },
{ title: "Ринология", description: "Лечение заболеваний носа и придаточных пазух", icon: "👃" },
];
/* ─── Коды примеров ─────────────────────────────────────────────────── */
const codeDoctorCard = `<!-- Карточка врача -->
<div class="doctor-card">
<img src="doctor.jpg" width="110" height="160" alt="ФИО" />
<div class="doctor-info">
<h3>Иванова Анна Сергеевна</h3>
<p class="specialty">Оториноларинголог, высшая категория</p>
<p class="experience">Стаж 18 лет</p>
<button class="bb-btn bb-btn-sm bb-btn-outline">Записаться</button>
</div>
</div>
/* CSS с сайта oclinica.ru */
.doctor .image { float:left; margin-right:20px; width:110px; height:160px; }
.doctor .item { float:left; width:170px; }
.doctor h3 { margin-top:0; height:32px; }`;
const codeNewsCard = `<!-- Карточка новости (сайт: 200×200px) -->
<div class="news-card">
<div class="news-preview">...</div>
<div class="news-body">
<time>15 марта 2026</time>
<h3>Заголовок новости</h3>
<p>Краткий анонс...</p>
<a href="#">Читать далее →</a>
</div>
</div>
/* CSS с сайта */
#block-views-last-news-block-1 .views-column {
background: #fff; width: 200px; height: 200px;
margin: 15px 8px; padding: 15px;
}
#block-views-last-news-block-1 .views-column:hover {
background: #eef4d1;
box-shadow: 0px 0px 16px 0px #9e9e9a;
}`;
const codeReviewCard = `<!-- Карточка отзыва -->
<div class="review-card">
<div class="stars">★★★★★</div>
<p class="text">Текст отзыва (4 строки, overflow: hidden)...</p>
<div class="author">
<span>Елена К.</span>
<time>15 марта 2026</time>
</div>
</div>
/* Стиль брендбука (фон из CSS сайта) */
.review-card { background: #eef4d1; border-radius: 12px; padding: 20px; }`;
const codePriceCard = `<!-- Карточка цены -->
<div class="price-card">
<div class="price-row">
<span class="service">Первичный приём ЛОР-врача</span>
<strong class="price">1 500 ₽</strong>
</div>
<p class="description">Включает осмотр и консультацию</p>
<button class="bb-btn bb-btn-sm bb-btn-outline">Записаться</button>
</div>`;
const codeBadges = `<!-- Бейджи -->
<span class="bb-badge bb-badge-primary">ЛОР</span>
<span class="bb-badge bb-badge-success">Принимает</span>
<span class="bb-badge bb-badge-warning">Ожидает</span>
<span class="bb-badge bb-badge-danger">Не принимает</span>
<span class="bb-badge bb-badge-neutral">Высшая категория</span>
<!-- Теги -->
<button class="bb-tag">Ухо</button>
<button class="bb-tag">Горло</button>
<button class="bb-tag bb-tag-active">Нос</button>
<!-- Алерт -->
<div class="bb-alert bb-alert-info">
<span class="bb-alert-icon"></span>
<div>
<strong>Запись открыта</strong>
<p>Вы можете записаться онлайн или по телефону.</p>
</div>
</div>`;
export default function CardsPage() {
return (
<div className="max-w-4xl mx-auto px-8 py-10">
{/* Заголовок */}
<div className="mb-10 pb-6 border-b" style={{ borderColor: "var(--bb-border)" }}>
<p className="text-xs font-semibold uppercase tracking-widest mb-2" style={{ color: "var(--brand-053m)" }}>
Компоненты 3.3
</p>
<h1 className="text-3xl font-semibold mb-3" style={{ color: "var(--bb-text)" }}>
Карточки
</h1>
<p className="text-base max-w-2xl" style={{ color: "var(--bb-text-muted)" }}>
Карточки врача, новости, отзыва, цены и услуги основные блоки контента сайта.
Бейджи, теги и алерты вспомогательные элементы.
</p>
</div>
{/* 1. Карточки врачей */}
<Section
id="doctor"
title="Карточка врача"
subtitle="Фото 110×160px, имя, специализация, стаж, кнопка записи. Соответствует .doctor на сайте."
>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{DOCTORS.map(d => <DoctorCard key={d.name} {...d} />)}
</div>
</Section>
{/* 2. Карточки новостей */}
<Section
id="news"
title="Карточка новости"
subtitle="Hover: bg #eef4d1 + box-shadow. Источник: #block-views-last-news-block-1 на сайте."
>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<NewsCard
date="15 марта 2026"
category="ЛОР"
title="Как правильно промывать нос при насморке"
snippet="Промывание носа — эффективный метод лечения и профилактики острого ринита. Рассказываем о правильной технике."
/>
<NewsCard
date="10 марта 2026"
category="Аудиология"
title="Новый слуховой аппарат в нашей клинике"
snippet="Мы начали подбор и настройку слуховых аппаратов нового поколения — незаметных и точных."
/>
<NewsCard
date="5 марта 2026"
title="Весенняя профилактика ЛОР-заболеваний"
snippet="Апрель — период обострений. Рекомендации нашего специалиста по укреплению иммунитета и защите."
/>
</div>
<p className="mt-3 text-xs" style={{ color: "var(--bb-text-muted)" }}>
* Наведите на карточку чтобы увидеть hover-эффект
</p>
</Section>
{/* 3. Карточки отзывов */}
<Section
id="review"
title="Карточка отзыва"
subtitle="Рейтинг (1–5 звёзд), текст 4 строки, автор, дата, врач. Фон #eef4d1 — с реального сайта."
>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{REVIEWS.map(r => <ReviewCard key={r.author} {...r} />)}
</div>
</Section>
{/* 4. Карточки цен */}
<Section
id="price"
title="Карточка цены"
subtitle="Услуга + стоимость + описание + кнопка. Highlighted-вариант для акционных позиций."
>
<div className="flex flex-col gap-3">
{PRICES.map(p => <PriceCard key={p.service} {...p} />)}
</div>
</Section>
{/* 5. Карточки услуг */}
<Section
id="service"
title="Карточка услуги"
subtitle="Иконка + заголовок + описание + ссылка. Применяется в блоке «Наши услуги»."
>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
{SERVICES.map(s => <ServiceCard key={s.title} {...s} />)}
</div>
</Section>
{/* 6. Бейджи и теги */}
<Section
id="badges"
title="Бейджи и теги"
subtitle="Статусные бейджи, теги-категории, алерты."
>
{/* Бейджи */}
<div className="mb-8">
<p className="text-sm font-medium mb-3" style={{ color: "var(--bb-text)" }}>Бейджи (статус)</p>
<div className="flex flex-wrap gap-2">
{[
{ label: "Основной", bg: "var(--brand-053m)", color: "#fff" },
{ label: "Принимает", bg: "#059669", color: "#fff" },
{ label: "Высшая категория", bg: "#d97706", color: "#fff" },
{ label: "Не принимает", bg: "#dc2626", color: "#fff" },
{ label: "Нейтральный", bg: "#6b7280", color: "#fff" },
{ label: "Новинка", bg: "#dff0fa", color: "var(--brand-053m)" },
].map(b => (
<span
key={b.label}
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold"
style={{ background: b.bg, color: b.color }}
>
{b.label}
</span>
))}
</div>
</div>
{/* Теги */}
<div className="mb-8">
<p className="text-sm font-medium mb-3" style={{ color: "var(--bb-text)" }}>Теги (категории)</p>
<div className="flex flex-wrap gap-2">
{["Ухо", "Горло", "Нос", "Аудиология", "Детский ЛОР", "Хирургия"].map((tag, i) => (
<button
key={tag}
className="inline-flex items-center px-3 py-1 rounded text-xs font-medium border transition-colors"
style={
i === 0
? { background: "var(--brand-053m)", color: "#fff", borderColor: "var(--brand-053m)" }
: { background: "#fff", color: "var(--bb-text)", borderColor: "var(--bb-border)" }
}
>
{tag}
</button>
))}
</div>
</div>
{/* Алерты */}
<div>
<p className="text-sm font-medium mb-3" style={{ color: "var(--bb-text)" }}>Алерты</p>
<div className="flex flex-col gap-3">
{[
{ type: "info", bg: "#dff0fa", color: "#075985", icon: "", title: "Информация", text: "Запись открыта. Вы можете записаться онлайн или по телефону." },
{ type: "success", bg: "#d1fae5", color: "#065f46", icon: "✓", title: "Успешно", text: "Ваша запись подтверждена. Ждём вас 20 марта в 10:00." },
{ type: "warning", bg: "#fef3c7", color: "#92400e", icon: "⚠", title: "Внимание", text: "Не забудьте взять паспорт и полис ОМС на приём." },
{ type: "error", bg: "#fee2e2", color: "#991b1b", icon: "✕", title: "Ошибка", text: "К сожалению, это время уже занято. Выберите другое." },
].map(a => (
<div
key={a.type}
className="flex gap-3 p-4 rounded-xl border"
style={{ background: a.bg, borderColor: a.bg, color: a.color }}
>
<span className="text-base font-bold shrink-0 mt-0.5">{a.icon}</span>
<div>
<p className="text-sm font-semibold mb-0.5">{a.title}</p>
<p className="text-sm opacity-90">{a.text}</p>
</div>
</div>
))}
</div>
</div>
</Section>
{/* 7. Код */}
<Section
id="code"
title="Примеры кода"
subtitle="HTML-структура и CSS-справка."
>
<div className="space-y-4">
<CodeCopy lang="HTML — DoctorCard + CSS с сайта" code={codeDoctorCard} />
<CodeCopy lang="HTML — NewsCard + CSS с сайта" code={codeNewsCard} />
<CodeCopy lang="HTML — ReviewCard" code={codeReviewCard} />
<CodeCopy lang="HTML — PriceCard" code={codePriceCard} />
<CodeCopy lang="HTML — Badges, Tags, Alerts" code={codeBadges} />
</div>
</Section>
{/* LLM-блок */}
<LlmBlock path="/components/cards" version="v1.0" specText={LLM_CARDS_TEXT}>
<LlmSection title="Типы карточек" />
<LlmTable
headers={["Карточка", "Ключевые размеры", "Источник на сайте", "Фон / hover"]}
rows={[
["DoctorCard", "фото 110×160px, layout: flex", ".doctor .image + .doctor .item", "#fff / —"],
["NewsCard", "preview h=144px, grid 3 col", "#block-views-last-news-block-1 .views-column", "#fff / #eef4d1 + shadow"],
["ReviewCard", "4 строки текста, рейтинг", ".node-reviews", "#eef4d1 / —"],
["PriceCard", "flex row: name + price", ".field-name-field-price-priem", "#fff / highlighted: #f0f9ff"],
["ServiceCard", "иконка 48×48, grid 4 col", "—", "#fff / shadow"],
]}
/>
<LlmSection title="Бейджи, теги, алерты" />
<LlmTable
headers={["Элемент", "Варианты", "Размер", "Применение"]}
rows={[
["Badge", "primary / success / warning / danger / neutral", "text-xs, px-2.5, rounded-full", "Статус врача, категория, акция"],
["Tag", "default / active", "text-xs, px-3, rounded-4px", "Фильтры, категории услуг"],
["Alert", "info / success / warning / error", "p-4, border-radius 12px", "Системные сообщения пользователю"],
]}
/>
<LlmSection title="Правила применения" />
<LlmRules
rules={[
{ ok: true, text: "DoctorCard: всегда фото-placeholder если нет фото" },
{ ok: true, text: "NewsCard: hover #eef4d1 + box-shadow (из реального CSS сайта)" },
{ ok: true, text: "ReviewCard: обрезать текст после 4 строк (WebkitLineClamp: 4)" },
{ ok: true, text: "PriceCard highlighted = акционная / рекомендуемая позиция" },
{ ok: true, text: "Alert: один тип одновременно на экране" },
{ ok: false, text: "Не смешивать типы карточек в одной сетке без заголовка" },
{ ok: false, text: "Не использовать ReviewCard без рейтинга" },
{ ok: false, text: "Не ставить более 3 бейджей рядом" },
]}
/>
</LlmBlock>
</div>
);
}
+300
View File
@@ -0,0 +1,300 @@
import type { Metadata } from "next";
import { LlmBlock, LlmSection, LlmTable, LlmRules } from "@/components/llm/LlmBlock";
export const metadata: Metadata = {
title: "Шапка и навигация. Цифровой брендбук Клиники ухо, горло, нос им. проф. Е.Н.Оленевой",
};
const NAV_ITEMS = [
"Клиника",
"ЛОР врачи",
"Заболевания",
"Вопрос-ответ",
"ЛОР-операции",
"Сурдология",
"Цены",
"Контакты",
];
const LLM_NAV_TEXT = `
БЛОК: Шапка и навигация сайта (Header)
Источник: perm.oclinica.ru/lor — реальная шапка сайта
Версия: v1.0
СТРУКТУРА ШАПКИ (3 зоны сверху вниз):
1. ТОП-БАР (~40px высота, фон #fff, border-bottom 1px solid #e5e7eb)
Левая часть:
— Адрес: «Б. Цитная, 3» с иконкой 📍
— Ссылка: «Клиника ухо горло нос и аллергия»
— Ссылка: «Центр аллергологии и пульмонологии»
Правая часть:
— Телефон: «/342/ 255-53-84» (font-weight: bold, color: #111827)
— Кнопка «Заказать звонок» — стиль bb-btn-pill (#e9e4d4, border #d5cfbd, border-radius 25px)
2. ЛОГОТИП (~64px, фон #fff)
— Иконка кружок синий (#0089c3) с крестом
— Текст «КЛИНИКА УХО ГОРЛО НОС» жирный uppercase, цвет #53514e
— Подпись «им. проф. Е.Н.Оленевой» мелкий, цвет #9ca3af
3. ГЛАВНОЕ МЕНЮ (~46px, фон #fff, border-top 1px solid #e5e7eb)
Пункты (8 штук): Клиника | ЛОР врачи | Заболевания | Вопрос-ответ | ЛОР-операции | Сурдология | Цены | Контакты
Шрифт: Fira Sans 14px, weight 400
Цвет ссылок: #53514e (--brand-073m)
Активный / Hover: #0089c3 (--brand-053m)
Разделители: border-right 1px solid #f3f4f6 между пунктами
ПРАВИЛА:
✓ Логотип всегда кликабелен — ведёт на главную страницу раздела
✓ Кнопка «Заказать звонок» всегда видна, pill-стиль
✓ Телефон кликабелен (tel: ссылка)
✓ Активный пункт меню — цвет #0089c3, остальные #53514e
✕ Не добавлять пункты меню, которых нет на сайте
✕ Не менять порядок пунктов меню
`.trim();
export default function NavigationPage() {
return (
<div className="p-8 max-w-5xl mx-auto space-y-10">
{/* Заголовок страницы */}
<div>
<p
className="text-xs font-semibold uppercase tracking-widest mb-1"
style={{ color: "var(--brand-053m)" }}
>
Компоненты
</p>
<h1 className="text-2xl font-bold mb-2" style={{ color: "var(--bb-text)" }}>
Шапка и навигация
</h1>
<p className="text-sm" style={{ color: "var(--bb-text-muted)" }}>
Точное воспроизведение шапки с perm.oclinica.ru/lor. Три зоны: топ-бар, логотип, главное меню.
</p>
</div>
{/* Живой пример */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Живой пример
</h2>
<div
className="rounded-xl overflow-hidden"
style={{ border: "1px solid var(--bb-border)", boxShadow: "0 1px 4px rgba(0,0,0,0.06)" }}
>
{/* Топ-бар */}
<div
className="flex items-center justify-between px-6 py-2 text-xs border-b"
style={{ background: "#fff", borderColor: "#e5e7eb", color: "#6b7280" }}
>
<div className="flex items-center gap-4">
<span>📍 Б. Цитная, 3</span>
<a href="#" style={{ color: "#0089c3" }}>
Клиника ухо горло нос и аллергия
</a>
<a href="#" style={{ color: "#0089c3" }}>
Центр аллергологии и пульмонологии
</a>
</div>
<div className="flex items-center gap-4">
<span className="font-bold text-sm" style={{ color: "#111827" }}>
/342/ 255-53-84
</span>
<button className="bb-btn bb-btn-sm bb-btn-pill">Заказать звонок</button>
</div>
</div>
{/* Логотип */}
<div className="flex items-center px-6 py-3 bg-white">
<div className="flex items-center gap-3">
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold text-lg shrink-0"
style={{ background: "#0089c3" }}
>
</div>
<div>
<div
className="text-[11px] font-bold leading-tight uppercase tracking-wide"
style={{ color: "#53514e" }}
>
Клиника<br />ухо, горло, нос
</div>
<div className="text-[9px] leading-tight mt-0.5" style={{ color: "#9ca3af" }}>
им. проф. Е.Н.Оленевой
</div>
</div>
</div>
</div>
{/* Навигация */}
<nav className="flex border-t bg-white" style={{ borderColor: "#e5e7eb" }}>
{NAV_ITEMS.map((item, i) => (
<a
key={item}
href="#"
className="px-4 py-3 text-sm bb-nav-link whitespace-nowrap"
style={{
color: i === 0 ? "#0089c3" : "#53514e",
borderRight: "1px solid #f3f4f6",
fontWeight: i === 0 ? 500 : 400,
textDecoration: "none",
}}
>
{item}
</a>
))}
</nav>
</div>
</section>
{/* Анатомия */}
<section className="space-y-4">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Анатомия шапки
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{[
{
zone: "1. Топ-бар",
bg: "#f0f9ff",
height: "~40px",
color: "Фон: #fff",
desc: "Адрес, ссылки на разделы клиники, телефон, кнопка «Заказать звонок» (pill)",
},
{
zone: "2. Логотип",
bg: "#f0fdf4",
height: "~64px",
color: "Фон: #fff",
desc: "Иконка-кружок (#0089c3) + название клиники двумя строками + подпись мелким",
},
{
zone: "3. Навигация",
bg: "#fefce8",
height: "~46px",
color: "Фон: #fff, border-top",
desc: "8 горизонтальных пунктов, активный = #0089c3, остальные = #53514e",
},
].map((z) => (
<div
key={z.zone}
className="rounded-xl p-4 space-y-2"
style={{ background: z.bg, border: "1px solid var(--bb-border)" }}
>
<p className="font-semibold text-sm" style={{ color: "var(--bb-text)" }}>
{z.zone}
</p>
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
{z.desc}
</p>
<p className="text-[11px] font-mono mt-1" style={{ color: "var(--bb-text-muted)" }}>
H: {z.height} · {z.color}
</p>
</div>
))}
</div>
</section>
{/* Пункты меню */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Пункты главного меню
</h2>
<div className="flex flex-wrap gap-2">
{NAV_ITEMS.map((item, i) => (
<span
key={item}
className="px-3 py-1.5 rounded text-sm"
style={{
background: i === 0 ? "#dff0fa" : "var(--bb-sidebar-bg)",
color: i === 0 ? "var(--brand-053m)" : "var(--bb-text)",
border: "1px solid var(--bb-border)",
fontWeight: i === 0 ? 500 : 400,
}}
>
{item}
{i === 0 && (
<span className="ml-1.5 text-[10px]" style={{ color: "var(--bb-text-muted)" }}>
(активный)
</span>
)}
</span>
))}
</div>
<p className="text-xs" style={{ color: "var(--bb-text-muted)" }}>
Порядок пунктов фиксирован. Hover и активный пункт цвет #0089c3 (--brand-053m).
</p>
</section>
{/* Токены */}
<section className="space-y-3">
<h2 className="font-semibold text-base" style={{ color: "var(--bb-text)" }}>
Цвета и токены
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ label: "Фон шапки", value: "#ffffff", token: "—" },
{ label: "Ссылки меню", value: "#53514e", token: "--brand-073m" },
{ label: "Активный / hover", value: "#0089c3", token: "--brand-053m" },
{ label: "Кнопка «Заказать»", value: "#e9e4d4", token: "bb-btn-pill" },
].map((t) => (
<div
key={t.label}
className="p-3 rounded-xl space-y-2"
style={{ background: "var(--bb-sidebar-bg)", border: "1px solid var(--bb-border)" }}
>
<div
className="w-8 h-8 rounded border"
style={{ background: t.value, borderColor: "var(--bb-border)" }}
/>
<p className="text-xs font-medium" style={{ color: "var(--bb-text)" }}>
{t.label}
</p>
<p className="text-[10px] font-mono" style={{ color: "var(--bb-text-muted)" }}>
{t.value}
</p>
<p className="text-[10px]" style={{ color: "var(--bb-text-muted)" }}>
{t.token}
</p>
</div>
))}
</div>
</section>
{/* LLM блок */}
<LlmBlock path="/components/navigation" version="v1.0" specText={LLM_NAV_TEXT}>
<LlmSection title="Зоны шапки" />
<LlmTable
headers={["Зона", "Высота", "Фон", "Содержимое"]}
rows={[
["Топ-бар", "~40px", "#fff", "Адрес, ссылки разделов, телефон /342/ 255-53-84, кнопка «Заказать звонок»"],
["Логотип", "~64px", "#fff", "Кружок #0089c3 + «КЛИНИКА УХО ГОРЛО НОС ИМ. ПРОФ. Е.Н.ОЛЕНЕВОЙ»"],
["Навигация", "~46px", "#fff + border-top", "8 пунктов: Клиника / ЛОР врачи / Заболевания / Вопрос-ответ / ЛОР-операции / Сурдология / Цены / Контакты"],
]}
/>
<LlmSection title="Цвета" />
<LlmTable
headers={["Элемент", "Цвет", "Токен"]}
rows={[
["Фон шапки", "#ffffff", "—"],
["Ссылки меню (default)", "#53514e", "--brand-073m"],
["Активный пункт / hover", "#0089c3", "--brand-053m"],
["Кнопка «Заказать звонок»", "#e9e4d4 / border #d5cfbd", "bb-btn-pill"],
["Телефон", "#111827, font-weight bold", "—"],
]}
/>
<LlmSection title="Правила" />
<LlmRules
rules={[
{ ok: true, text: "Логотип ведёт на главную страницу раздела" },
{ ok: true, text: "Кнопка «Заказать звонок» всегда видна, pill-стиль" },
{ ok: true, text: "Телефон — кликабельная ссылка tel:" },
{ ok: true, text: "Активный пункт меню — цвет #0089c3" },
{ ok: false, text: "Не добавлять пункты меню, которых нет на сайте" },
{ ok: false, text: "Не менять порядок 8 пунктов меню" },
]}
/>
</LlmBlock>
</div>
);
}
+66 -59
View File
@@ -55,42 +55,42 @@ const BRAND_COLORS = [
{
oracal: "053M",
name: "Основной бирюзовый",
hex: "#7ecfca",
hex: "#0089c3",
usage: "Акцентный цвет, CTA-кнопки, иконки, активные состояния",
cssVar: "--brand-053m",
},
{
oracal: "073M",
name: "Тёмный серо-голубой",
hex: "#5b7b87",
hex: "#53514e",
usage: "Тёмный фон, хедер, акценты на тёмных поверхностях",
cssVar: "--brand-073m",
},
{
oracal: "066M",
name: "Средний бирюзовый",
hex: "#5bb5ad",
hex: "#00818c",
usage: "Вторичные акценты, фоны секций, иллюстрации",
cssVar: "--brand-066m",
},
{
oracal: "050M",
name: "Тёмно-синий",
hex: "#1b4c72",
hex: "#1b2e5d",
usage: "Наружная реклама, полиграфия, заголовки на светлом фоне",
cssVar: "--brand-050m",
},
{
oracal: "081M",
name: "Бежевый",
hex: "#c4a882",
hex: "#a8885c",
usage: "Форма сотрудников, оффлайн носители, тёплые акценты",
cssVar: "--brand-081m",
},
{
oracal: "080M",
name: "Тёмно-коричневый",
hex: "#5c2e0e",
hex: "#432f1e",
usage: "Текст на бежевых поверхностях, логотип на форме",
cssVar: "--brand-080m",
},
@@ -106,32 +106,32 @@ const BRAND_COLORS = [
/* ─── Соответствие цветов ──────────────────────────────────────────── */
const COLOR_MAPPING = [
{
brand: { oracal: "053M", name: "Основной бирюзовый", hex: "#7ecfca" },
brand: { oracal: "053M", name: "Основной бирюзовый", hex: "#0089c3" },
web: { name: "Бирюзовый", hex: "#63bac3", count: 4 },
note: "Чуть темнее и насыщеннее на сайте",
note: "Oracal — насыщенный синий; сайт — светлее и голубее",
},
{
brand: { oracal: "073M", name: "Тёмный серо-голубой", hex: "#5b7b87" },
brand: { oracal: "073M", name: "Тёмный серо-голубой", hex: "#53514e" },
web: { name: "Серо-бирюзовый", hex: "#60959c", count: 7 },
note: "Светлее на сайте",
note: "Oracal — тёмно-серый; сайт — серо-бирюзовый (значительное расхождение)",
},
{
brand: { oracal: "066M", name: "Средний бирюзовый", hex: "#5bb5ad" },
brand: { oracal: "066M", name: "Средний бирюзовый", hex: "#00818c" },
web: { name: "Бирюзовый средний", hex: "#52b4bd", count: 4 },
note: "Смещён в синеву",
note: "Oracal — тёмный бирюзовый; сайт — светлее",
},
{
brand: { oracal: "081M", name: "Бежевый", hex: "#c4a882" },
brand: { oracal: "081M", name: "Бежевый", hex: "#a8885c" },
web: { name: "Бежевый", hex: "#bf9975", count: 12 },
note: "Темнее и насыщеннее на сайте",
note: "Oracal — тёплый охристый; сайт — прохладнее и светлее",
},
{
brand: { oracal: "050M", name: "Тёмно-синий", hex: "#1b4c72" },
brand: { oracal: "050M", name: "Тёмно-синий", hex: "#1b2e5d" },
web: null,
note: "Не найден в CSS сайта",
},
{
brand: { oracal: "080M", name: "Тёмно-коричневый", hex: "#5c2e0e" },
brand: { oracal: "080M", name: "Тёмно-коричневый", hex: "#432f1e" },
web: null,
note: "Не найден в CSS сайта",
},
@@ -145,10 +145,15 @@ const COLOR_MAPPING = [
web: { name: "Второстепенный текст", hex: "#949290", count: 4 },
note: "Только на сайте — нет Oracal-аналога",
},
{
brand: null,
web: { name: "Светло-кремовый фон (Hero)", hex: "#f9f4e7", count: 1 },
note: "Только на сайте — фон Hero-баннера страниц разделов. Нет Oracal-аналога",
},
{
brand: null,
web: { name: "Кремовый фон", hex: "#e9e4d4", count: 1 },
note: "Только на сайте — нет Oracal-аналога",
note: "Только на сайте — тёплые фоны кнопок-пилюль, вторичные секции. Нет Oracal-аналога",
},
{
brand: null,
@@ -177,21 +182,22 @@ const WEB_COLORS = [
{ name: "Бирюзовый средний", hex: "#52b4bd", usage: "Вторичные цветовые акценты", count: 4, group: "Акценты" },
{ name: "Основной текст", hex: "#464646", usage: "Цвет основного текста на сайте", count: 3, group: "Текст" },
{ name: "Второстепенный текст", hex: "#949290", usage: "Подписи, второстепенный контент", count: 4, group: "Текст" },
{ name: "Светло-бирюзовый фон", hex: "#b8e6ed", usage: "Фоны светлых секций с акцентом", count: 1, group: "Фоны" },
{ name: "Кремовый фон", hex: "#e9e4d4", usage: "Тёплые фоны секций", count: 1, group: "Фоны" },
{ name: "Светло-бирюзовый фон", hex: "#b8e6ed", usage: "Фон формы записи («Будьте здоровы!»)", count: 1, group: "Фоны" },
{ name: "Светло-кремовый фон (Hero)", hex: "#f9f4e7", usage: "Фон Hero-баннера страниц разделов", count: 1, group: "Фоны" },
{ name: "Кремовый фон", hex: "#e9e4d4", usage: "Тёплые фоны кнопок-пилюль, вторичные секции", count: 1, group: "Фоны" },
{ name: "Коралловый", hex: "#ffa39c", usage: "CTA-кнопки («Запишите меня!»), акцентные призывы к действию", count: 2, group: "Акценты" },
{ name: "Светло-жёлтый фон", hex: "#eef4d1", usage: "Фон карточек отзывов (секция «Отзывы о нас»)", count: 1, group: "Фоны" },
{ name: "Светло-зелёный фон", hex: "#f2fee6", usage: "Фон секции новостей", count: 1, group: "Фоны" },
];
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: "Тёмно-коричневый на бежевом (форма)" },
{ fg: "#ffffff", bg: "#53514e", label: "Белый на тёмном серо-голубом (073M)" },
{ fg: "#ffffff", bg: "#1b2e5d", label: "Белый на тёмно-синем (050M)" },
{ fg: "#ffffff", bg: "#432f1e", label: "Белый на тёмно-коричневом (080M)" },
{ fg: "#ffffff", bg: "#00818c", label: "Белый на среднем бирюзовом (066M)" },
{ fg: "#111827", bg: "#0089c3", label: "Тёмный текст на основном бирюзовом (053M)" },
{ fg: "#111827", bg: "#a8885c", label: "Тёмный текст на бежевом (081M)" },
{ fg: "#432f1e", bg: "#a8885c", label: "Тёмно-коричневый на бежевом (форма, 080M/081M)" },
];
/* ─── Компоненты ───────────────────────────────────────────────────── */
@@ -403,16 +409,16 @@ function exportTokens() {
/* ─── LLM spec text ────────────────────────────────────────────────── */
const LLM_COLORS_TEXT = `# ЦВЕТА — LLM-СПЕЦИФИКАЦИЯ
# Клиника ухо, горло, нос им. проф. Е.Н.Оленевой
# docs/LLM_CONTEXT.md · /foundation/colors · v2.1 · 2026-03-22
# docs/LLM_CONTEXT.md · /foundation/colors · v2.2 · 2026-03-22
ФИРМЕННЫЕ ЦВЕТА (Oracal)
ФИРМЕННЫЕ ЦВЕТА (Oracal) — точные значения из каталога
Oracal | Название | HEX | RGB | CSS-переменная | Применение
053M | Основной бирюзовый | #7ECFCA | rgb(126,207,202) | --brand-053m | Акцент, CTA-кнопки, иконки, активные состояния
073M | Тёмный серо-голубой | #5B7B87 | rgb(91,123,135) | --brand-073m | Тёмный фон, хедер, заголовки навигации
066M | Средний бирюзовый | #5BB5AD | rgb(91,181,173) | --brand-066m | Вторичные акценты, фоны секций
050M | Тёмно-синий | #1B4C72 | rgb(27,76,114) | --brand-050m | Наружная реклама, полиграфия, заголовки
081M | Бежевый | #C4A882 | rgb(196,168,130) | --brand-081m | Форма сотрудников, тёплые акценты
080M | Тёмно-коричневый | #5C2E0E | rgb(92,46,14) | --brand-080m | Текст на бежевом, логотип на форме
053M | Основной бирюзовый | #0089C3 | rgb(0,137,195) | --brand-053m | Акцент, CTA-кнопки, иконки, активные состояния
073M | Тёмный серо-голубой | #53514E | rgb(83,81,78) | --brand-073m | Тёмный фон, хедер, заголовки навигации
066M | Средний бирюзовый | #00818C | rgb(0,129,140) | --brand-066m | Вторичные акценты, фоны секций
050M | Тёмно-синий | #1B2E5D | rgb(27,46,93) | --brand-050m | Наружная реклама, полиграфия, заголовки
081M | Бежевый | #A8885C | rgb(168,136,92) | --brand-081m | Форма сотрудников, тёплые акценты
080M | Тёмно-коричневый | #432F1E | rgb(67,47,30) | --brand-080m | Текст на бежевом, логотип на форме
— | Белый | #FFFFFF | rgb(255,255,255) | --brand-white | Фон, инвертированный текст, логотип на тёмных
ЦВЕТА САЙТА oclinica.ru (CSS: clinic_bootstrap_mobile/css/style.css)
@@ -424,34 +430,35 @@ Oracal | Название | HEX | RGB | CSS-пер
Коралловый | #FFA39C | Акценты | 2 | CTA-кнопки («Запишите меня!»)
Основной текст | #464646 | Текст | 3 | Цвет основного текста сайта
Второстепенный текст | #949290 | Текст | 4 | Подписи, второстепенный контент
Светло-бирюзовый фон | #B8E6ED | Фоны | 1 | Фоны светлых секций
Кремовый фон | #E9E4D4 | Фоны | 1 | Тёплые фоны секций
Светло-кремовый Hero | #F9F4E7 | Фоны | 1 | Фон Hero-баннера страниц разделов
Светло-бирюзовый фон | #B8E6ED | Фоны | 1 | Фон формы записи («Будьте здоровы!»)
Кремовый фон | #E9E4D4 | Фоны | 1 | Тёплые фоны кнопок-пилюль, вторичные секции
Светло-жёлтый фон | #EEF4D1 | Фоны | 1 | Фон карточек отзывов
Светло-зелёный фон | #F2FEE6 | Фоны | 1 | Фон секции новостей
СООТВЕТСТВИЕ ORACAL → САЙТ
053M #7ECFCA → #63BAC3 (темнее, насыщеннее)
073M #5B7B87 → #60959C (светлее)
066M #5BB5AD → #52B4BD (смещён в синеву)
081M #C4A882 → #BF9975 (темнее, насыщеннее)
050M #1B4C72 → не найден в CSS сайта
080M #5C2E0E → не найден в CSS сайта
СООТВЕТСТВИЕ ORACAL → САЙТ (цифровая адаптация плёночных цветов)
053M #0089C3 → #63BAC3 (Oracal ярко-синий; сайт светлее и голубее)
073M #53514E → #60959C (Oracal тёмно-серый; сайт серо-бирюзовый — значительное расхождение)
066M #00818C → #52B4BD (Oracal тёмный бирюзовый; сайт светлее)
081M #A8885C → #BF9975 (Oracal тёплый охристый; сайт прохладнее и светлее)
050M #1B2E5D → не найден в CSS сайта
080M #432F1E → не найден в CSS сайта
КОНТРАСТНОСТЬ WCAG 2.1
#FFFFFF / #5B7B87 | 4.6:1 | AA PASS | AAA FAIL
#FFFFFF / #1B4C72 | 9.3:1 | AA PASS | AAA PASS
#FFFFFF / #5C2E0E | 11.2:1 | AA PASS | AAA PASS
#FFFFFF / #5BB5AD | 3.0:1 | AA FAIL | AAA FAIL | только крупный текст ≥18pt
#111827 / #7ECFCA | 5.8:1 | AA PASS | AAA FAIL
#111827 / #C4A882 | 4.8:1 | AA PASS | AAA FAIL
#5C2E0E / #C4A882 | 6.7:1 | AA PASS | AAA FAIL
#FFFFFF / #53514E | 7.9:1 | AA PASS | AAA PASS
#FFFFFF / #1B2E5D | 13.2:1 | AA PASS | AAA PASS
#FFFFFF / #432F1E | 12.6:1 | AA PASS | AAA PASS
#FFFFFF / #00818C | 4.7:1 | AA PASS | AAA FAIL
#111827 / #0089C3 | 4.7:1 | AA PASS | AAA FAIL
#111827 / #A8885C | 5.5:1 | AA PASS | AAA FAIL
#432F1E / #A8885C | 3.8:1 | AA FAIL | AAA FAIL | только крупный текст ≥18pt
ПРАВИЛА
✓ Только цвета из фирменной палитры
✓ Digital → цвета сайта; оффлайн → коды Oracal
✓ Текст на цветном фоне: минимум WCAG AA (4.5:1)
✓ Белый текст на: 073M (#5B7B87), 050M (#1B4C72), 080M (#5C2E0E)
✓ Тёмный текст на: 053M (#7ECFCA), 081M (#C4A882)
✓ Белый текст на: 073M (#53514E), 066M (#00818C), 050M (#1B2E5D), 080M (#432F1E)
✓ Тёмный текст (#111827) на: 053M (#0089C3), 081M (#A8885C)
✕ Произвольные цвета вне фирменной палитры
✕ Изменение насыщенности / оттенка фирменных цветов
✕ Тёплые и холодные акценты рядом без нейтрального разделителя`.trim();
@@ -480,12 +487,12 @@ export default function ColorsPage() {
<div className="mt-4 flex items-center justify-between">
<div
className="px-4 py-3 rounded-lg border text-sm flex items-center gap-2"
style={{ borderColor: "#fde68a", background: "#fffbeb", color: "#92400e" }}
style={{ borderColor: "#bae6fd", background: "#f0f9ff", color: "#075985" }}
>
<span></span>
<span></span>
<span>
HEX-значения приблизительны. Для оффлайн-носителей используйте
официальные коды Oracal.
HEX-значения рассчитаны по точным RGB из каталога Oracal.
Для оффлайн-носителей используйте официальные коды Oracal.
</span>
</div>
<button
@@ -723,7 +730,7 @@ export default function ColorsPage() {
</div>
<LlmBlock
path="/foundation/colors"
version="v2.1"
version="v2.2"
specText={LLM_COLORS_TEXT}
>
{/* Фирменные цвета */}
@@ -799,8 +806,8 @@ export default function ColorsPage() {
{ ok: true, text: "Только цвета из фирменной палитры" },
{ ok: true, text: "Digital → цвета сайта; оффлайн → коды Oracal" },
{ ok: true, text: "Текст на цветном фоне: минимум WCAG AA (4.5:1)" },
{ ok: true, text: "Белый текст на: 073M, 050M, 080M" },
{ ok: true, text: "Тёмный текст на: 053M, 081M" },
{ ok: true, text: "Белый текст на: 073M (#53514E), 066M (#00818C), 050M (#1B2E5D), 080M (#432F1E)" },
{ ok: true, text: "Тёмный текст (#111827) на: 053M (#0089C3), 081M (#A8885C)" },
{ ok: false, text: "Произвольные цвета вне фирменной палитры" },
{ ok: false, text: "Изменение насыщенности / оттенка фирменных цветов" },
{ ok: false, text: "Тёплые + холодные акценты рядом без разделителя" },
@@ -399,6 +399,58 @@ export default function TypographyPage() {
</div>
</Section>
{/* Типографика сайта — фактическое состояние 23.03.2026 */}
<Section
title="Типографика сайта — факт на 23.03.2026"
subtitle="Реальные размеры и стили, задокументированные по perm.oclinica.ru/lor. Источник: скриншот + CSS темы clinic_bootstrap_mobile."
>
<div
className="overflow-hidden rounded-xl border"
style={{ borderColor: "var(--bb-border)" }}
>
<table className="w-full text-sm">
<thead>
<tr style={{ background: "var(--bb-sidebar-bg)" }}>
{["Элемент / блок", "Шрифт", "Размер", "Weight", "Цвет", "Пример"].map(h => (
<th key={h} className="text-left px-4 py-3 font-medium text-xs"
style={{ color: "var(--bb-text-muted)" }}>
{h}
</th>
))}
</tr>
</thead>
<tbody>
{[
["H1 страницы (над Hero)", "Fira Sans", "~2830px", "700", "#bf9975", "ЛОР Клиника ухо, горло, нос…"],
["H2 секции", "Fira Sans", "~30px", "700", "#111827", "Приём ведут опытные ЛОР врачи"],
["Заголовок Hero-баннера", "Fira Sans", "~18px", "700", "#111827", "ЭНДОСКОПИЧЕСКОЕ ХИРУРГИЧЕСКОЕ…"],
["Статистика (цифра+текст)", "Fira Sans", "~18px", "700", "#60959c", "Ежедневно 27 ЛОР врачей…"],
["Подзаголовок секции", "Fira Sans", "~14px", "400", "#374151", "Фундаментальная теоретическая…"],
["Пункт checklist (ключ)", "Fira Sans", "13px", "700", "#111827", "БЕЗОПАСНО"],
["Пункт checklist (описание)", "Fira Sans", "13px", "400", "#374151", "оперируют хирурги с 15-летним…"],
["Имя врача в карточке", "Fira Sans", "12px", "500", "#60959c", "Макарова Людмила Германовна"],
["Специализация врача", "Fira Sans", "11px", "400", "#374151", "ЛОР врач, сурдолог"],
].map(([el, font, size, w, color, example]) => (
<tr key={el} className="border-t" style={{ borderColor: "var(--bb-border)" }}>
<td className="px-4 py-3 font-medium text-xs" style={{ color: "var(--bb-text)" }}>{el}</td>
<td className="px-4 py-3 text-xs font-mono" style={{ color: "var(--brand-073m)" }}>{font}</td>
<td className="px-4 py-3 text-xs font-mono" style={{ color: "var(--bb-text)" }}>{size}</td>
<td className="px-4 py-3 text-xs font-mono" style={{ color: "var(--bb-text-muted)" }}>{w}</td>
<td className="px-4 py-3">
<span className="flex items-center gap-1.5">
<span className="w-3 h-3 rounded-sm inline-block border shrink-0"
style={{ background: color, borderColor: "var(--bb-border)" }} />
<span className="font-mono text-xs" style={{ color: "var(--bb-text-muted)" }}>{color}</span>
</span>
</td>
<td className="px-4 py-3 text-xs" style={{ color: "var(--bb-text-muted)" }}>{example}</td>
</tr>
))}
</tbody>
</table>
</div>
</Section>
{/* LLM-спецификация */}
<section className="mb-8">
<div className="mb-6">
+19 -9
View File
@@ -3,13 +3,13 @@
/* ─── Бренд-токены О!Клиника ─────────────────────────────────────────── */
/* Цвета уточняются в Sprint 2 по таблице Oracal */
:root {
/* Фирменные цвета (приблизительноуточнить в Sprint 2) */
--brand-053m: #7ecfca; /* Основной бирюзовый (Oracal 053M) */
--brand-073m: #5b7b87; /* Тёмный серо-голубой (Oracal 073M) */
--brand-066m: #5bb5ad; /* Средний бирюзовый (Oracal 066M) */
--brand-050m: #1b4c72; /* Тёмно-синий, наружная реклама (Oracal 050M) */
--brand-081m: #c4a882; /* Бежевый (Oracal 081M) */
--brand-080m: #5c2e0e; /* Тёмно-коричневый (Oracal 080M) */
/* Фирменные цвета — точные RGB из каталога Oracal */
--brand-053m: #0089c3; /* Основной бирюзовый (Oracal 053M) · rgb(0,137,195) */
--brand-073m: #53514e; /* Тёмный серо-голубой (Oracal 073M) · rgb(83,81,78) */
--brand-066m: #00818c; /* Средний бирюзовый (Oracal 066M) · rgb(0,129,140) */
--brand-050m: #1b2e5d; /* Тёмно-синий, наружная реклама (Oracal 050M) · rgb(27,46,93) */
--brand-081m: #a8885c; /* Бежевый (Oracal 081M) · rgb(168,136,92) */
--brand-080m: #432f1e; /* Тёмно-коричневый (Oracal 080M) · rgb(67,47,30) */
--brand-white: #ffffff;
/* UI-цвета брендбука */
@@ -17,7 +17,7 @@
--bb-sidebar-border: #e5e7eb;
--bb-sidebar-text: #374151;
--bb-sidebar-text-muted: #6b7280;
--bb-sidebar-active-bg: #e0f5f4;
--bb-sidebar-active-bg: #dff0fa;
--bb-sidebar-active-text: var(--brand-053m);
--bb-sidebar-section: #9ca3af;
--bb-content-bg: #ffffff;
@@ -130,7 +130,7 @@ body {
.bb-textarea:focus,
.bb-select:focus {
border-color: var(--brand-053m);
box-shadow: 0 0 0 3px rgba(126, 207, 202, 0.2);
box-shadow: 0 0 0 3px rgba(0, 137, 195, 0.2);
}
.bb-input.bb-error,
.bb-textarea.bb-error,
@@ -162,6 +162,16 @@ body {
flex-shrink: 0;
}
/* ─── Карточки (Sprint 4) ────────────────────────────────────── */
.bb-news-card:hover {
background: #eef4d1 !important;
box-shadow: 0 0 16px 0 #9e9e9a;
}
.bb-service-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
/* ─── Навигация сайта (Sprint 5) ────────────────────────────── */
.bb-nav-link:hover { color: var(--brand-053m) !important; }
/* ─── Тумблер (Sprint 3) ──────────────────────────────────────── */
.bb-toggle-track {
display: inline-flex;
+12 -16
View File
@@ -29,25 +29,20 @@ const NAV: NavSection[] = [
items: [
{ label: "Кнопки", href: "/components/buttons" },
{ label: "Форм-контролы", href: "/components/forms" },
{ label: "Карточки", href: "/components/cards", soon: true },
{ label: "Бейджи и теги", href: "/components/badges", soon: true },
{ label: "Алерты", href: "/components/alerts", soon: true },
{ label: "Модальные окна", href: "/components/modals", soon: true },
{ label: "Таблицы", href: "/components/tables", soon: true },
{ label: "Навигация", href: "/components/navigation", soon: true },
{ label: "Карточки", href: "/components/cards" },
{ label: "Шапка / Навигация", href: "/components/navigation" },
],
},
{
title: "Блоки",
items: [
{ label: "Hero", href: "/blocks/hero", soon: true },
{ label: "CEO-текст", href: "/blocks/ceo", soon: true },
{ label: "Наши врачи", href: "/blocks/doctors", soon: true },
{ label: "Отзывы", href: "/blocks/reviews", soon: true },
{ label: "Новости", href: "/blocks/news", soon: true },
{ label: "Формы контакта", href: "/blocks/contact-forms", soon: true },
{ label: "Контакт", href: "/blocks/contact", soon: true },
{ label: "Услуги", href: "/blocks/services", soon: true },
{ label: "Hero-баннер", href: "/blocks/hero" },
{ label: "Вводный текст", href: "/blocks/ceo" },
{ label: "Наши врачи", href: "/blocks/doctors" },
{ label: "Отзывы", href: "/blocks/reviews" },
{ label: "Формы записи", href: "/blocks/contact-forms" },
{ label: "Новости", href: "/blocks/news" },
{ label: "Подвал / Контакт", href: "/blocks/contact" },
],
},
{
@@ -73,7 +68,8 @@ const NAV: NavSection[] = [
{
title: "Эксперименты",
items: [
{ label: "Библиотека", href: "/experiments", soon: true },
{ label: "Дубли компонентов", href: "/variants/blocks", soon: true },
{ label: "Дубли страниц", href: "/variants/pages", soon: true },
],
},
];
@@ -170,7 +166,7 @@ export function Sidebar() {
color: "var(--bb-sidebar-text-muted)",
}}
>
Sprint 3 · v0.3.0
Sprint 5 · v0.5.1
</div>
</aside>
);
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 KiB

+134 -96
View File
@@ -2,9 +2,9 @@
## Клиника ухо, горло, нос им. проф. Е.Н.Оленевой
**Версия контекста:** 3.0
**Дата обновления:** 2026-03-22
**Актуальный спринт:** Sprint 3
**Версия контекста:** 4.2
**Дата обновления:** 2026-03-23
**Актуальный спринт:** Sprint 5
**Сайт клиники:** https://oclinica.ru
**Брендбук (локально):** http://localhost:3001
**Брендбук (production):** https://web-oclinica.vercel.app
@@ -39,19 +39,18 @@
### 2.1 Фирменные цвета (Oracal — базовая палитра)
Основаны на кодах самоклеящейся плёнки Oracal. HEX-значения — приближённые
цифровые эквиваленты. **Для печати и оффлайн-носителей используй коды Oracal,
не HEX.**
Основаны на кодах самоклеящейся плёнки Oracal. HEX-значения рассчитаны по **точным RGB
из каталога Oracal**. Для печати и оффлайн-носителей используй коды Oracal, не HEX.
| Oracal | Название | HEX | RGB | HSL | CSS-переменная | Применение |
|--------|-----------------------|-----------|-------------------|------------------|------------------|------------|
| 053M | Основной бирюзовый | `#7ecfca` | rgb(126,207,202) | hsl(177,47%,65%) | `--brand-053m` | Акцент, CTA-кнопки, иконки, активные состояния |
| 073M | Тёмный серо-голубой | `#5b7b87` | rgb(91,123,135) | hsl(197,20%,44%) | `--brand-073m` | Тёмный фон, хедер, заголовки навигации |
| 066M | Средний бирюзовый | `#5bb5ad` | rgb(91,181,173) | hsl(174,33%,53%) | `--brand-066m` | Вторичные акценты, фоны секций, иллюстрации |
| 050M | Тёмно-синий | `#1b4c72` | rgb(27,76,114) | hsl(208,61%,28%) | `--brand-050m` | Наружная реклама, полиграфия, заголовки на светлом |
| 081M | Бежевый | `#c4a882` | rgb(196,168,130) | hsl(33,35%,64%) | `--brand-081m` | Форма сотрудников, оффлайн носители, тёплые акценты |
| 080M | Тёмно-коричневый | `#5c2e0e` | rgb(92,46,14) | hsl(23,73%,21%) | `--brand-080m` | Текст на бежевых поверхностях, логотип на форме |
| — | Белый | `#ffffff` | rgb(255,255,255) | hsl(0,0%,100%) | `--brand-white` | Фон, инвертированный текст, логотип на тёмных фонах |
| Oracal | Название | HEX | RGB | HSL | CSS-переменная | Применение |
|--------|-----------------------|-----------|-------------------|-------------------|------------------|------------|
| 053M | Основной бирюзовый | `#0089c3` | rgb(0,137,195) | hsl(198,100%,38%) | `--brand-053m` | Акцент, CTA-кнопки, иконки, активные состояния |
| 073M | Тёмный серо-голубой | `#53514e` | rgb(83,81,78) | hsl(38,3%,32%) | `--brand-073m` | Тёмный фон, хедер, заголовки навигации |
| 066M | Средний бирюзовый | `#00818c` | rgb(0,129,140) | hsl(185,100%,27%) | `--brand-066m` | Вторичные акценты, фоны секций, иллюстрации |
| 050M | Тёмно-синий | `#1b2e5d` | rgb(27,46,93) | hsl(223,55%,24%) | `--brand-050m` | Наружная реклама, полиграфия, заголовки на светлом |
| 081M | Бежевый | `#a8885c` | rgb(168,136,92) | hsl(35,30%,51%) | `--brand-081m` | Форма сотрудников, оффлайн носители, тёплые акценты |
| 080M | Тёмно-коричневый | `#432f1e` | rgb(67,47,30) | hsl(27,38%,19%) | `--brand-080m` | Текст на бежевых поверхностях, логотип на форме |
| — | Белый | `#ffffff` | rgb(255,255,255) | hsl(0,0%,100%) | `--brand-white` | Фон, инвертированный текст, логотип на тёмных фонах |
### 2.2 Цвета сайта oclinica.ru (реальный CSS)
@@ -68,8 +67,9 @@
| Коралловый | `#ffa39c` | rgb(255,163,156) | Акценты | 2 | CTA-кнопки («Запишите меня!») |
| Основной текст | `#464646` | rgb(70,70,70) | Текст | 3 | Цвет основного текста на сайте |
| Второстепенный текст | `#949290` | rgb(148,146,144) | Текст | 4 | Подписи, второстепенный контент |
| Светло-бирюзовый фон | `#b8e6ed` | rgb(184,230,237) | Фоны | 1 | Фоны светлых секций с акцентом |
| Кремовый фон | `#e9e4d4` | rgb(233,228,212) | Фоны | 1 | Тёплые фоны секций |
| Светло-кремовый Hero | `#f9f4e7` | rgb(249,244,231) | Фоны | 1 | Фон Hero-баннера страниц разделов |
| Светло-бирюзовый фон | `#b8e6ed` | rgb(184,230,237) | Фоны | 1 | Фон формы записи («Будьте здоровы!») |
| Кремовый фон | `#e9e4d4` | rgb(233,228,212) | Фоны | 1 | Тёплые фоны кнопок-пилюль, вторичные секции |
| Светло-жёлтый фон | `#eef4d1` | rgb(238,244,209) | Фоны | 1 | Фон карточек отзывов |
| Светло-зелёный фон | `#f2fee6` | rgb(242,254,230) | Фоны | 1 | Фон секции новостей |
@@ -77,27 +77,27 @@
| Oracal (бренд) | HEX бренда | Сайт (цифровой) | HEX сайта | Отклонение |
|--------------------------|------------|-----------------------|-----------|------------|
| 053M Основной бирюзовый | `#7ecfca` | Бирюзовый | `#63bac3` | Темнее, насыщеннее |
| 073M Тёмный серо-голубой | `#5b7b87` | Серо-бирюзовый | `#60959c` | Светлее на сайте |
| 066M Средний бирюзовый | `#5bb5ad` | Бирюзовый средний | `#52b4bd` | Смещён в синеву |
| 081M Бежевый | `#c4a882` | Бежевый | `#bf9975` | Темнее, насыщеннее |
| 050M Тёмно-синий | `#1b4c72` | — | — | Не найден в CSS сайта |
| 080M Тёмно-коричневый | `#5c2e0e` | — | — | Не найден в CSS сайта |
| 053M Основной бирюзовый | `#0089c3` | Бирюзовый | `#63bac3` | Oracal ярко-синий; сайт светлее и голубее |
| 073M Тёмный серо-голубой | `#53514e` | Серо-бирюзовый | `#60959c` | Oracal тёмно-серый; сайт серо-бирюзовый (значительное расхождение) |
| 066M Средний бирюзовый | `#00818c` | Бирюзовый средний | `#52b4bd` | Oracal тёмный бирюзовый; сайт светлее |
| 081M Бежевый | `#a8885c` | Бежевый | `#bf9975` | Oracal тёплый охристый; сайт прохладнее и светлее |
| 050M Тёмно-синий | `#1b2e5d` | — | — | Не найден в CSS сайта |
| 080M Тёмно-коричневый | `#432f1e` | — | — | Не найден в CSS сайта |
**Важно:** расхождения ожидаемы — это цифровая адаптация плёночных цветов под экран.
При разработке digital-материалов используй цвета сайта (раздел 2.2), не Oracal.
### 2.4 Контрастность пар (WCAG 2.1)
| Пара | Коэффициент | AA (4.5:1) | AAA (7:1) | AA large (3:1) |
|-------------------------------------------|-------------|------------|-----------|----------------|
| Белый на тёмном серо-голубом (#5b7b87) | 4.6:1 | ✓ | | ✓ |
| Белый на тёмно-синем (#1b4c72) | 9.3:1 | ✓ | ✓ | ✓ |
| Белый на тёмно-коричневом (#5c2e0e) | 11.2:1 | ✓ | ✓ | ✓ |
| Белый на среднем бирюзовом (#5bb5ad) | 3.0:1 | | ✕ | ✓ |
| Тёмный текст (#111827) на основном бирюзовом (#7ecfca) | 5.8:1 | ✓ | ✕ | ✓ |
| Тёмный текст (#111827) на бежевом (#c4a882) | 4.8:1 | ✓ | ✕ | ✓ |
| Тёмно-коричневый (#5c2e0e) на бежевом (#c4a882) | 6.7:1 | ✓ | ✕ | ✓ |
| Пара | Коэффициент | AA (4.5:1) | AAA (7:1) | AA large (3:1) |
|----------------------------------------------|-------------|------------|-----------|----------------|
| Белый на тёмном серо-голубом (#53514e 073M) | 7.9:1 | ✓ | | ✓ |
| Белый на тёмно-синем (#1b2e5d 050M) | 13.2:1 | ✓ | ✓ | ✓ |
| Белый на тёмно-коричневом (#432f1e 080M) | 12.6:1 | ✓ | ✓ | ✓ |
| Белый на среднем бирюзовом (#00818c 066M) | 4.7:1 | | ✕ | ✓ |
| Тёмный (#111827) на основном бирюзовом (#0089c3 053M) | 4.7:1 | ✓ | ✕ | ✓ |
| Тёмный (#111827) на бежевом (#a8885c 081M) | 5.5:1 | ✓ | ✕ | ✓ |
| Тёмно-коричневый (#432f1e) на бежевом (#a8885c) | 3.8:1 | ✕ | ✕ | ✓ (только крупный ≥18pt) |
---
@@ -173,9 +173,9 @@ SVG-версия ожидается (не получена от клиники).
| Вариант | Фон | Логотип | Применение |
|---------|-----|---------|------------|
| Основной | Белый / светлый | Полноцветный | Сайт, полиграфия на белом |
| Инвертированный | Тёмный (#5b7b87, #1b4c72) | Белый (`filter: brightness(0) invert(1)`) | Хедер, тёмные секции |
| На форме (беж) | Бежевый (#c4a882 / Oracal 081M) | Коричневый (#5c2e0e / Oracal 080M) | Одежда персонала |
| На форме (синий) | Тёмно-синий (#1b4c72 / Oracal 050M) | Белый | Одежда персонала |
| Инвертированный | Тёмный (#53514e, #1b2e5d) | Белый (`filter: brightness(0) invert(1)`) | Хедер, тёмные секции |
| На форме (беж) | Бежевый (#a8885c / Oracal 081M) | Коричневый (#432f1e / Oracal 080M) | Одежда персонала |
| На форме (синий) | Тёмно-синий (#1b2e5d / Oracal 050M) | Белый | Одежда персонала |
### 4.4 Охранная зона
@@ -205,26 +205,27 @@ SVG-версия ожидается (не получена от клиники).
Определены в `apps/web/app/globals.css`:
```css
/* Цвета бренда */
--brand-053m: #7ecfca; /* Основной бирюзовый */
--brand-073m: #5b7b87; /* Тёмный серо-голубой */
--brand-066m: #5bb5ad; /* Средний бирюзовый */
--brand-050m: #1b4c72; /* Тёмно-синий */
--brand-081m: #c4a882; /* Бежевый */
--brand-080m: #5c2e0e; /* Тёмно-коричневый */
/* Цвета бренда (точные RGB из каталога Oracal) */
--brand-053m: #0089c3; /* Основной бирюзовый · rgb(0,137,195) */
--brand-073m: #53514e; /* Тёмный серо-голубой · rgb(83,81,78) */
--brand-066m: #00818c; /* Средний бирюзовый · rgb(0,129,140) */
--brand-050m: #1b2e5d; /* Тёмно-синий · rgb(27,46,93) */
--brand-081m: #a8885c; /* Бежевый · rgb(168,136,92) */
--brand-080m: #432f1e; /* Тёмно-коричневый · rgb(67,47,30) */
--brand-white: #ffffff; /* Белый */
/* UI брендбука */
--bb-sidebar-bg: ... /* Фон сайдбара */
--bb-sidebar-border: ... /* Граница сайдбара */
--bb-sidebar-text: ... /* Текст сайдбара */
--bb-sidebar-text-muted: ...
--bb-sidebar-section: .../* Заголовки секций сайдбара */
--bb-sidebar-active-bg: ...
--bb-text: ... /* Основной текст контента */
--bb-text-muted: ... /* Приглушённый текст */
--bb-border: ... /* Границы */
--bb-content-bg: ... /* Фон карточек */
--bb-sidebar-bg: #f8f9fa;
--bb-sidebar-border: #e5e7eb;
--bb-sidebar-text: #374151;
--bb-sidebar-text-muted: #6b7280;
--bb-sidebar-active-bg: #dff0fa; /* светло-синий под 053M */
--bb-sidebar-active-text: var(--brand-053m);
--bb-sidebar-section: #9ca3af;
--bb-content-bg: #ffffff;
--bb-border: #e5e7eb;
--bb-text: #111827;
--bb-text-muted: #6b7280;
```
---
@@ -234,8 +235,8 @@ SVG-версия ожидается (не получена от клиники).
### 6.1 Форма сотрудников
**Варианты:**
- Бежевый: ткань цвета Oracal 081M, логотип Oracal 080M (коричневый), расположение — левая сторона груди
- Синий: ткань цвета Oracal 050M (тёмно-синий), логотип белый (Oracal 010), расположение — левая сторона груди
- Бежевый: ткань цвета Oracal 081M (#a8885c), логотип Oracal 080M (#432f1e, коричневый), расположение — левая сторона груди
- Синий: ткань цвета Oracal 050M (#1b2e5d, тёмно-синий), логотип белый (Oracal 010), расположение — левая сторона груди
**Размеры логотипа на форме:**
- Размеры до 46: 70 × 25,5 мм
@@ -252,7 +253,7 @@ SVG-версия ожидается (не получена от клиники).
### 6.3 Внутренняя навигация
**Материал:** оргстекло
**Плёнки:** Oracal 053M (бирюзовый) и 073M (тёмный серо-голубой)
**Плёнки:** Oracal 053M (#0089c3) и 073M (#53514e)
**Типы табличек:**
- Таблички на дверях кабинетов: номер кабинета, профиль врача с фото и QR-кодом
- Указатели по этажам: стрелки направлений + номера кабинетов
@@ -265,9 +266,9 @@ SVG-версия ожидается (не получена от клиники).
### 6.4 Брендирование транспорта (трамвай)
**Зоны оклейки:**
- Борта: Oracal 053M (#7ecfca) + 073M (#5b7b87)
- Передняя часть: Oracal 066M (#5bb5ad) + 050M (#1b4c72)
- Акценты: Oracal 081M (#c4a882) + 080M (#5c2e0e)
- Борта: Oracal 053M (#0089c3) + 073M (#53514e)
- Передняя часть: Oracal 066M (#00818c) + 050M (#1b2e5d)
- Акценты: Oracal 081M (#a8885c) + 080M (#432f1e)
**Все 6 фирменных цветов присутствуют на транспорте.**
@@ -278,17 +279,20 @@ SVG-версия ожидается (не получена от клиники).
| URL | Статус | Описание |
|-----|--------|----------|
| `/foundation/logo` | ✅ Готова | Логотип, варианты, охранная зона, правила |
| `/foundation/colors` | ✅ Готова | Палитра, контраст WCAG, цвета сайта, соответствие |
| `/foundation/colors` | ✅ Готова | Палитра (Oracal точные RGB), контраст WCAG, цвета сайта, соответствие |
| `/foundation/typography` | ✅ Готова | DINPro + Fira Sans, шкала стилей |
| `/foundation/icons` | 🔜 Скоро | Иконография |
| `/offline/uniform` | ✅ Готова | Форма сотрудников |
| `/offline/badges` | ✅ Готова | Бейджи |
| `/offline/navigation` | ✅ Готова | Внутренняя навигация |
| `/offline/transport` | ✅ Готова | Брендирование транспорта |
| `/components/buttons` | ✅ Готова | Кнопки — все варианты, размеры, состояния |
| `/components/forms` | ✅ Готова | Форм-контролы — Input, Textarea, Select, Checkbox, Radio, Toggle |
| `/components/*` | 🔜 Sprint 34 | Карточки, бейджи, алерты, модалки, таблицы |
| `/blocks/*` | 🔜 Sprint 58 | Hero, врачи, отзывы, новости, формы |
| `/components/buttons` | ✅ Готова | Кнопки — 4 варианта с реального сайта, размеры, состояния |
| `/components/forms` | ✅ Готова | Форм-контролы — Input/Textarea/Select/Checkbox/Radio/Toggle |
| `/components/cards` | Sprint 4 | Карточки — врач, услуга, новость, отзыв, цена + бейджи/теги/алерты |
| `/components/*` | 🔜 Sprint 4–5 | Модалки, таблицы, навигация |
| `/blocks/hero` | ✅ Sprint 5 v1.1 | Hero-баннер: фон #f9f4e7, реальное фото врача, галочки #bf9975, кнопка outline |
| `/blocks/doctors` | ✅ Sprint 5 v1.1 | Врачи: H2 30px, статистика #60959c без фона, 6 реальных фото |
| `/blocks/*` | 🔜 Sprint 5 | Отзывы, формы, новости, footer |
| `/pages/*` | 🔜 Sprint 9–11 | Главная, заболевание, врачи, цены, контакты |
---
@@ -299,8 +303,9 @@ SVG-версия ожидается (не получена от клиники).
- Использовать только цвета из фирменной палитры (раздел 2.1)
- Для digital: адаптировать к цветам сайта (раздел 2.2)
- Текст на цветном фоне — минимум WCAG AA (4.5:1)
- Белый логотип на тёмных фонах (073M, 050M, 080M)
- Коричневый логотип (080M) на бежевом фоне (081M)
- Белый текст на тёмных фонах: 073M (#53514e), 066M (#00818c), 050M (#1b2e5d), 080M (#432f1e)
- Тёмный текст (#111827) на светлых: 053M (#0089c3), 081M (#a8885c)
- Коричневый логотип (080M) на бежевом фоне (081M) — только крупный текст (3.8:1)
### Запрещено
- Использовать произвольные цвета вне палитры
@@ -311,13 +316,13 @@ SVG-версия ожидается (не получена от клиники).
### Иерархия цветов
```
Основной бирюзовый (053M / #7ecfca) ← главный идентификатор бренда
└── Средний бирюзовый (066M / #5bb5ad) ← вторичный акцент
└── Тёмный серо-голубой (073M / #5b7b87) ← фоны, хедер
Основной бирюзовый (053M / #0089c3) ← главный идентификатор бренда
└── Средний бирюзовый (066M / #00818c) ← вторичный акцент
└── Тёмный серо-голубой (073M / #53514e) ← фоны, хедер
Тёмно-синий (050M / #1b4c72) ← авторитет, полиграфия
Бежевый (081M / #c4a882) ← тепло, форма
└── Тёмно-коричневый (080M / #5c2e0e) ← текст на бежевом
Тёмно-синий (050M / #1b2e5d) ← авторитет, полиграфия
Бежевый (081M / #a8885c) ← тепло, форма
└── Тёмно-коричневый (080M / #432f1e) ← текст на бежевом
```
---
@@ -343,39 +348,70 @@ SVG-версия ожидается (не получена от клиники).
### Кнопки (Button) · `/components/buttons`
CSS-классы из `globals.css`. Компонент: `@/components/ui/Button` (React, "use client").
Варианты скопированы с реального сайта perm.oclinica.ru.
CSS-классы в `globals.css`. Компонент: `@/components/ui/Button` (React, "use client").
| Вариант | CSS класс | Фон | Текст | Граница | Применение |
|-----------|------------------|------------|---------|-----------|------------|
| primary | .bb-btn-primary | #5b7b87 | #fff | #5b7b87 | Главный CTA: «Записаться», «Подтвердить» |
| secondary | .bb-btn-secondary | прозрачный | #5b7b87 | #5b7b87 | Второстепенное действие |
| ghost | .bb-btn-ghost | прозрачный | #5b7b87 | нет | Третичное действие, отмена |
| danger | .bb-btn-danger | #dc2626 | #fff | #dc2626 | Деструктивное действие |
| Вариант | CSS класс | Фон | Текст | Граница | Border-radius | Применение |
|---------|------------------|----------|---------|-----------|---------------|------------|
| primary | .bb-btn-primary | #FFA39C | #fff | #FF847B | 7px, bold, shadow | Форма записи «Запишите меня!» |
| outline | .bb-btn-outline | #fff | #BF9975 | #BF9975 | 7px | «Записаться на приём» |
| teal | .bb-btn-teal | #60959C | #fff | прозрачная| 7px | «Позвонить» |
| pill | .bb-btn-pill | #E9E4D4 | #333 | #D5CFBD | 25px | «Заказать звонок» |
| Размер | CSS класс | padding | font-size | border-radius |
|--------|------------|------------|-----------|---------------|
| sm | .bb-btn-sm | 5px 12px | 13px | 6px |
| md | .bb-btn-md | 8px 18px | 14px | 8px |
| lg | .bb-btn-lg | 12px 26px | 16px | 10px |
| Размер | CSS класс | padding | font-size |
|--------|------------|--------------|-----------|
| sm | .bb-btn-sm | 4px 11px | 13px |
| md | .bb-btn-md | 8px 16px | 14px |
| lg | .bb-btn-lg | 10px 24px | 18px |
**Состояния:** default · hover (brightness 0.9) · active (brightness 0.82) · loading (spinner + opacity 0.5) · disabled (opacity 0.5)
**Правила:** не более одной primary на экран в контексте задачи · текст — глагол или призыв · Danger только для деструктивных действий.
**Состояния:** default · hover (brightness 0.9) · active (brightness 0.82) · loading (spinner) · disabled (opacity 0.5)
### Форм-контролы (Forms) · `/components/forms`
| Элемент | CSS класс | Тег HTML | Описание |
|----------|-----------------|----------------------------|----------|
| Input | .bb-input | `<input>` | text, email, password |
| Textarea | .bb-textarea | `<textarea>` | многострочный, min-height: 100px |
| Select | .bb-select | `<select>` | с кастомной SVG-стрелкой |
| Checkbox | .bb-checkbox | `<input type="checkbox">` | 16×16px, accent-color: #5b7b87 |
| Radio | .bb-radio | `<input type="radio">` | 16×16px, accent-color: #5b7b87 |
| Toggle | .bb-toggle-track | React-компонент `<Toggle>` | 44×24px, track + thumb |
Размеры соответствуют entityform-блокам на реальном сайте.
**Состояния полей:** default (border #e5e7eb) · focus (border #7ecfca + box-shadow) · error (.bb-error, border #dc2626) · disabled (opacity 0.5)
| Элемент | CSS класс | Тег HTML | Высота | Описание |
|----------|------------------|----------------------------|---------|----------|
| Input | .bb-input | `<input>` | 50px | text, email, password · border 1px #ccc · radius 4px |
| Textarea | .bb-textarea | `<textarea>` | ≥100px | многострочный, resize:vertical |
| Select | .bb-select | `<select>` | 50px | с кастомной SVG-стрелкой |
| Checkbox | .bb-checkbox | `<input type="checkbox">` | 16×16px | accent-color: #53514e |
| Radio | .bb-radio | `<input type="radio">` | 16×16px | accent-color: #53514e |
| Toggle | .bb-toggle-track | React-компонент `<Toggle>` | 24px | 44×24px track + 20px thumb |
**Toggle:** выкл → track #d1d5db · вкл → track #5b7b87 (#73M) · thumb: белый круг 20×20px.
**Состояния полей:** default (border 1px solid #ccc) · focus (border #0089c3 + box-shadow rgba(0,137,195,0.2)) · error (.bb-error, border #dc2626) · disabled (opacity 0.5)
**Контекст на сайте:** фон формы #b8e6ed, ширина полей 302px, entityform-блоки.
**Toggle:** выкл → track #d1d5db · вкл → track #53514e · thumb: белый круг 20×20px.
---
## 9b. Карточки, бейджи и алерты (Sprint 4)
### Карточки · `/components/cards`
| Карточка | Размеры ключевые | Источник на сайте | Фон / hover |
|-------------|-------------------------|------------------------------------------------|--------------------------|
| DoctorCard | фото 110×160px | `.doctor .image` + `.doctor .item` | #fff / — |
| NewsCard | preview h=144px | `#block-views-last-news-block-1 .views-column` | #fff / **#eef4d1** + shadow |
| ReviewCard | 4-строчный clamp | `.node-reviews` | **#eef4d1** / — |
| PriceCard | flex row: name + price | `.field-name-field-price-priem` | #fff / highlighted: #f0f9ff |
| ServiceCard | иконка 48×48px | — (нет прямого аналога) | #fff / shadow |
**DoctorCard:** фото 110×160px (placeholder фон `#dff0fa`), кнопка `.bb-btn-outline .bb-btn-sm`
**NewsCard hover CSS:** `background: #eef4d1; box-shadow: 0 0 16px 0 #9e9e9a;` — взят с реального сайта
**ReviewCard:** звёзды SVG, заливка `#f59e0b`, `WebkitLineClamp: 4`
### Бейджи, теги, алерты
| Элемент | Варианты | Применение |
|---------|----------|------------|
| Badge | primary (#0089c3) / success (#059669) / warning (#d97706) / danger (#dc2626) / neutral (#6b7280) | Статус врача, категория, акция |
| Tag | default (border) / active (brand bg) | Фильтры, категории услуг |
| Alert | info (#dff0fa/#075985) / success (#d1fae5/#065f46) / warning (#fef3c7/#92400e) / error (#fee2e2/#991b1b) | Системные сообщения |
**CSS класс hover:** `.bb-news-card:hover` в globals.css
---
@@ -402,6 +438,8 @@ CSS-классы из `globals.css`. Компонент: `@/components/ui/Button
| 2.0 | 2026-03-22 | Sprint 2: типографика, оффлайн носители, цвета сайта (8 цветов) |
| 2.1 | 2026-03-22 | Sprint 2 доп.: +3 цвета сайта (коралловый, светло-жёлтый, светло-зелёный) |
| 3.0 | 2026-03-22 | Sprint 3: кнопки (Button), форм-контролы (Input/Textarea/Select/Checkbox/Radio/Toggle), LLM-блок на логотипе |
| 4.0 | 2026-03-22 | Sprint 4 start: исправлены цвета Oracal (точные RGB из каталога), кнопки/формы по реальному сайту |
| 4.1 | 2026-03-22 | Sprint 4 done: карточки (DoctorCard/NewsCard/ReviewCard/PriceCard/ServiceCard), бейджи/теги/алерты |
---
+165 -132
View File
@@ -143,196 +143,229 @@
---
## Sprint 4 — Карточки (Cards)
## Sprint 4 — Карточки (Cards) ✅ ЗАВЕРШЁН
**Цель:** Все типы карточек, используемых на сайте.
### Задачи
- [ ] Docs: Обновить `docs/LLM_CONTEXT.md` — добавить спецификации карточек
- [ ] FE: Добавить LLM-блок на страницу «Карточки»
- [ ] FE: Карточка врача (DoctorCard) — фото, имя, специализация, кнопка записи
- [ ] FE: Карточка услуги / заболевания
- [ ] FE: Карточка новости — превью, дата, заголовок, анонс, читать далее
- [ ] FE: Карточка отзыва — автор, текст, рейтинг (звёзды), дата
- [ ] FE: Карточка цены — услуга, стоимость, описание
- [ ] FE: Бейджи, теги, алерты (inline и toast)
- [ ] FE: Страница «Компоненты → Карточки» с документацией
- [x] Docs: Обновить `docs/LLM_CONTEXT.md` → версия 4.0
- [x] FE: Добавить LLM-блок на страницу «Карточки» — v1.0
- [x] FE: Карточка врача (DoctorCard) — фото 110×160px, имя, специализация, стаж, кнопка outline
- [x] FE: Карточка услуги / заболевания (ServiceCard) — иконка + заголовок + описание
- [x] FE: Карточка новости — превью, дата, заголовок, анонс, hover #eef4d1 (CSS сайта)
- [x] FE: Карточка отзыва — рейтинг SVG-звёзды, 4-строчный clamp, фон #eef4d1
- [x] FE: Карточка цены — услуга + стоимость + highlighted-вариант
- [x] FE: Бейджи (6 вариантов), теги (default/active), алерты (info/success/warning/error)
- [x] FE: Страница `/components/cards` с документацией и LLM-блоком
**Результат спринта:** Все карточки задокументированы и показаны в брендбуке.
### Фактические результаты
- **5 типов карточек:** DoctorCard, NewsCard, ReviewCard, PriceCard, ServiceCard
- **NewsCard hover** — bg `#eef4d1` + box-shadow `0 0 16px #9e9e9a` (1:1 с реальным CSS сайта)
- **ReviewCard** — фон `#eef4d1` из CSS сайта, WebkitLineClamp: 4
- **Бейджи** — 6 вариантов (primary/success/warning/danger/neutral/outline-blue)
- **Алерты** — info (#dff0fa), success (#d1fae5), warning (#fef3c7), error (#fee2e2)
- **Исправлены цвета Oracal** — точные RGB из каталога для всех 6 плёнок
- **Деплой:** https://web-oclinica.vercel.app (production)
**Результат спринта:** Все карточки задокументированы. Цвета Oracal исправлены по каталогу.
---
## Sprint 5 — Hero-блок и CEO-текст
## Принцип документирования (Sprint 5+)
**Цель:** Ключевые верхние блоки страниц.
### Задачи
- [ ] Docs: Обновить `docs/LLM_CONTEXT.md` — добавить спецификации Hero и CEO-блоков
- [ ] FE: Добавить LLM-блоки на страницы Hero и CEO-текст
- [ ] FE: Hero-блок вариант 1 — фон + заголовок + CTA
- [ ] FE: Hero-блок вариант 2 — иллюстрация сбоку
- [ ] FE: Hero-блок вариант 3 — с встроенной формой записи
- [ ] FE: Блок CEO-текст — фото руководителя, цитата, подпись
- [ ] FE: Блок «Преимущества клиники» — иконки + текст
- [ ] FE: Страница «Блоки → Hero и вступление»
- [ ] Design: Согласование вариантов Hero под разные страницы
**Результат спринта:** Hero и CEO-секция полностью задокументированы.
> **Правило:** Брендбук документирует только то, что реально существует на сайте `perm.oclinica.ru`.
> Никаких придуманных вариантов. Процесс: сначала изучаем реальный сайт → потом воспроизводим в брендбуке.
---
## Sprint 6Врачи и профиль врача
## Sprint 5Все блоки текущего сайта
**Цель:** Блоки и компоненты, связанные с врачами.
**Цель:** Задокументировать ВСЕ блоки страницы perm.oclinica.ru/lor за один спринт.
Источник: скриншот страницы + CSS сайта. Только фронтенд, mock-данные.
### Задачи
- [ ] Docs: Обновить `docs/LLM_CONTEXT.md` — добавить спецификации блоков врачей
- [ ] FE: Добавить LLM-блок на страницу «Блоки → Врачи»
- [ ] FE: Блок «Наши врачи» — сетка карточек с фильтром по специализации
- [ ] FE: Блок «Врач — профиль» (полная страница): фото, биография, специализации, расписание
- [ ] FE: Компонент «Расписание / слоты записи»
- [ ] FE: Компонент навигации (header menu, breadcrumbs)
- [ ] FE: Пагинация
- [ ] FE: Страница «Блоки → Врачи»
### Блоки с реального сайта (сверху вниз, по скриншоту /lor)
**Результат спринта:** Все блоки о врачах готовы.
**Шапка и навигация** → `/components/navigation`
- [ ] FE: Топ-бар: адрес «Б. Цитная, 3», телефон /342/ 255-53-84, кнопка «Заказать звонок» (pill)
- [ ] FE: Логотип «КЛИНИКА УХО ГОРЛО НОС ИМ. ПРОФ. Е.Н. ОЛЕНЕВОЙ»
- [ ] FE: Главное меню: Клиника / ЛОР врачи / Заболевания / Вопрос-ответ / ЛОР-операции / Сурдология / Цены / Контакты
**Hero-баннер** → `/blocks/hero`
- [x] FE: Баннер — единый фон #f9f4e7 (светло-кремовый, замерян пикселем), галочки #bf9975
- [x] FE: Правая часть: реальное фото врача с пациентом (спарсено с сайта → public/hero-doctor.jpg)
- [x] LLM: v1.1 — исправлен цвет фона, кнопка outline, галочки бежевые
**Вводный текст (CEO-блок)** → `/blocks/ceo`
- [ ] FE: Текст специализации клиники, Q&A вопросы-стимулы
**Блок врачей** → `/blocks/doctors`
- [x] FE: Заголовок text-3xl + 3 стат-блока (без фона, #60959c + border-bottom) + сетка 6 карточек
- [x] FE: Реальные фото 6 врачей (спарсены с сайта → public/doctors/)
- [x] FE: Реальные имена и специализации врачей из слайдера /lor
- [x] LLM: v1.1
**Блок отзывов** → `/blocks/reviews`
- [ ] FE: Карусель: кавычка, текст отзыва, «Читать полностью», стрелки
**Формы записи** → `/blocks/contact-forms`
- [ ] FE: Форма «Будьте здоровы!» (фон #b8e6ed, поля: имя/телефон/врач, кнопка «Запишите меня!»)
- [ ] FE: Форма «Узнайте стоимость операции» (белый фон, поля: имя/телефон, кнопка «Перезвоните мне»)
**Блок новостей** → `/blocks/news`
- [ ] FE: 4 карточки новостей в ряд (дата + заголовок), кнопка «Все новости» (mock)
**Footer (подвал)** → `/blocks/contact`
- [ ] FE: 4 колонки ссылок, логотип, адрес, соцсети, часы работы
### Общее к Sprint 5
- [x] FE: LLM-блоки на hero v1.1 и doctors v1.1
- [x] Docs: Типографика сайта — реальные стили на 23.03.2026 (новый раздел в /foundation/typography)
- [x] Docs: Цвета — исправлен #f9f4e7 (Hero), #b8e6ed (форма), #e9e4d4 (пилюли)
- [ ] FE: Убрать `soon` у Hero и Doctors в Sidebar
- [ ] FE: CEO-блок, отзывы, формы, новости, footer
- [ ] Docs: Финальное обновление `docs/LLM_CONTEXT.md` по итогам Sprint 5
**Результат спринта (в работе):** Hero + Doctors задокументированы с реальными фото и точными цветами.
---
## Sprint 7Отзывы и новости
## Sprint 6Страницы (сборки из блоков)
**Цель:** Контентные блоки сайта.
**Цель:** Задокументировать полные страницы как сборки уже готовых блоков.
Данные пока mock — реальные появятся в Sprint 8.
### Задачи
- [ ] Docs: Обновить `docs/LLM_CONTEXT.md` — добавить спецификации блоков отзывов и новостей
- [ ] FE: Добавить LLM-блок на страницу «Блоки → Отзывы и новости»
- [ ] FE: Блок отзывов — карусель
- [ ] FE: Блок отзывов — статичная сетка
- [ ] FE: Блок рейтинга (звёзды + количество отзывов)
- [ ] FE: Блок новостей — сетка превью (3 в ряд)
- [ ] FE: Блок новостей — горизонтальный список
- [ ] FE: Блок «Последние новости» для сайдбара
- [ ] FE: Блок услуг / заболеваний — иконки + список
- [ ] FE: Страница «Блоки → Отзывы и новости»
- [ ] Research: Страница заболевания perm.oclinica.ru/lor/rinit — порядок блоков, что отличается от главной
- [ ] Research: Страница врачей /lor/doctors — фильтры, сетка, пагинация
- [ ] FE: `/pages/home` — сборка блоков из Sprint 5 в порядке реальной страницы /lor
- [ ] FE: `/pages/disease` — страница заболевания по /lor/rinit
- [ ] FE: `/pages/doctors` — список врачей с фильтрами (mock) и пагинацией
- [ ] FE: `/pages/prices` — страница цен (Research: реальная структура таблицы)
- [ ] FE: `/pages/contacts` — контакты + карта
- [ ] FE: `/pages/doctor` — профиль врача
- [ ] FE: Убрать `soon` у страниц в Sidebar
- [ ] Docs: Обновить `docs/LLM_CONTEXT.md`
**Результат спринта:** Контентные блоки задокументированы.
**Результат спринта:** Все страницы брендбука задокументированы (с mock-данными).
---
## Sprint 8Формы контакта и модальные окна
## Sprint 7Авторизация (viewer / editor)
**Цель:** Все формы и диалоги взаимодействия с пациентом.
**Цель:** Реализовать систему ролей из ТЗ: `viewer` видит брендбук, `editor` видит кнопки дублирования.
Требует бэкенд (NestJS) и БД (PostgreSQL + Prisma).
### Задачи
- [ ] Docs: Обновить `docs/LLM_CONTEXT.md` — добавить спецификации форм и модальных окон
- [ ] FE: Добавить LLM-блок на страницу «Блоки → Формы и контакты»
- [ ] FE: Форма записи — короткая (имя, телефон)
- [ ] FE: Форма записи — расширенная (имя, телефон, специализация, врач, дата)
- [ ] FE: Форма записи в модальном окне
- [ ] FE: Контактная форма (имя, email, сообщение)
- [ ] FE: Блок «Контакт» — карта + адрес + часы работы
- [ ] FE: Модальное окно — информационное
- [ ] FE: Модальное окно — подтверждение
- [ ] FE: Страница «Блоки → Формы и контакты»
### Задачи — бэкенд
- [ ] BE: Prisma-модель `User` (id, email, name, passwordHash, role: viewer/editor, createdAt)
- [ ] BE: Prisma миграция + seed (создать тестовых пользователей: 1 viewer, 1 editor)
- [ ] BE: NestJS модуль `auth` — JWT-авторизация, `/api/auth/login`, `/api/auth/me`
- [ ] BE: JWT-токен в httpOnly cookie (не localStorage) — ФТ-64
- [ ] BE: Guard для защиты эндпоинтов по роли
**Результат спринта:** Все формы и диалоги готовы.
### Задачи — фронтенд
- [ ] FE: Страница `/login` — форма email + пароль + кнопка «Войти» — ФТ-61
- [ ] FE: Редирект неавторизованных на `/login` — ФТ-60
- [ ] FE: После входа — редирект обратно на запрошенную страницу — ФТ-62
- [ ] FE: В шапке брендбука: имя пользователя, роль, кнопка «Выйти» — ФТ-63
- [ ] FE: `editor` видит шапку с дополнительными действиями; `viewer` — только чтение
**Результат спринта:** Рабочая авторизация, два типа пользователей, защищённые маршруты.
---
## Sprint 9Страницы: Главная и Заболевание
## Sprint 8Реальные данные (интеграция с oclinica.ru)
**Цель:** Первые две полноразмерные страницы в брендбуке.
**Цель:** Подключить реальные данные с сайта клиники — врачи, новости, услуги, цены, отзывы.
Данные проксируются через NestJS с кэшем 15 минут — ИНТ-01-04.
### Задачи
- [ ] FE: Страница «Главная» — сборка из утверждённых блоков
- [ ] FE: Страница «Заболевание» — Hero (мини), описание, врачи по теме, форма
- [ ] FE: Раздел «Страницы» в навигации брендбука
- [ ] FE: Адаптивность страниц (desktop / tablet / mobile)
- [ ] Design: Ревью и согласование страниц
### Задачи — бэкенд
- [ ] BE: Research — проверить доступные эндпоинты oclinica.ru (JSON:API Drupal или /node?_format=json)
- [ ] BE: NestJS модуль `oclinica` — HTTP-клиент к oclinica.ru API
- [ ] BE: Кэш TTL 15 минут (in-memory или Redis) — ИНТ-03
- [ ] BE: Graceful degradation: если API недоступен — возврат mock-данных — ИНТ-04
- [ ] BE: Эндпоинты: `/api/doctors`, `/api/news`, `/api/services`, `/api/prices`, `/api/reviews`
**Результат спринта:** 2 страницы задокументированы в брендбуке.
### Задачи — фронтенд
- [ ] FE: Блок «Наши врачи» (`/blocks/doctors`) — реальные карточки врачей с фото
- [ ] FE: Блок «Новости» (`/blocks/news`) — реальные новости
- [ ] FE: Страницы (`/pages/*`) — замена mock-данных на реальные
**Результат спринта:** Брендбук показывает реальных врачей, новости, услуги с сайта клиники.
---
## Sprint 10Страницы: Врачи, Врач, Цены
## Sprint 9Эксперименты: дублирование компонентов
**Цель:** Три страницы с ключевым контентом.
**Цель:** Реализовать систему создания дублей — `editor` может дублировать любой компонент/блок,
редактировать атрибуты и отправить на согласование. ФТ-50 – ФТ-56.
### Задачи
- [ ] FE: Страница «Все врачи» — фильтры, сетка, пагинация
- [ ] FE: Страница «Врач (профиль)»
- [ ] FE: Страница «Цены» — фильтры, таблица, форма
- [ ] FE: Таблица цен с сортировкой
- [ ] FE: Адаптивность всех трёх страниц
- [ ] Design: Ревью страниц
### Задачи — бэкенд
- [ ] BE: Prisma-модель `ExperimentalComponent` (id, name, baseComponent, attributes JSON, status, authorId, createdAt, updatedAt)
- [ ] BE: Prisma миграция
- [ ] BE: NestJS модуль `components` — CRUD: `POST /api/components`, `GET /api/components`, `PATCH /api/components/:id`, `DELETE /api/components/:id`
- [ ] BE: Эндпоинт смены статуса: `PATCH /api/components/:id/status` (draft→review→approved)
- [ ] BE: Guard: мутирующие операции только для `editor` — ФТ-52, ФТ-55
**Результат спринта:** Ещё 3 страницы в брендбуке.
### Задачи — фронтенд
- [ ] FE: Кнопка «Дублировать» на каждом компоненте/блоке (видна только `editor`) — ФТ-51
- [ ] FE: Форма редактирования атрибутов дубля — ФТ-52:
- Текстовые поля (заголовки, описания)
- Color picker — только из токенов бренда (`--brand-*`)
- Выбор размера из допустимых значений
- Real-time preview при изменении
- Кнопки: «Сохранить как черновик», «Отмена»
- [ ] FE: Раздел `/variants/blocks` — список всех дублей компонентов — ФТ-50, ФТ-56:
- `viewer`: только approved
- `editor`: все (draft, review, approved)
- [ ] FE: Карточка дубля: название, автор, дата, статус, базовый компонент, превью — ФТ-56
- [ ] FE: Управление статусом для `editor`: draft→review→approved, удаление draft — ФТ-54, ФТ-55
**Результат спринта:** Рабочая система создания и согласования дублей компонентов.
---
## Sprint 11Страница Контакты и Экспериментальная секция (MVP)
## Sprint 10Финальная полировка и деплой
**Цель:** Последняя страница + запуск механизма экспериментов.
**Цель:** Финальный релиз — smoke-тест, адаптивность, деплой бэкенда.
### Задачи
- [ ] FE: Страница «Контакты»
- [ ] BE + DB: Модели `Component`, `Page`, `Block` в Prisma
- [ ] BE: CRUD API для компонентов (`/api/components`)
- [ ] BE: CRUD API для страниц (`/api/pages`)
- [ ] FE: Секция «Эксперименты» в брендбуке
- [ ] FE: Форма создания нового экспериментального компонента
- [ ] FE: Список экспериментальных компонентов со статусами
**Результат спринта:** Все страницы готовы, эксперименты — базовый функционал.
---
## Sprint 12 — Полировка, финальный деплой и документация
**Цель:** Финальный релиз. Фронтенд уже живёт на Vercel с Sprint 2, Sprint 12 — финальная полировка и production-готовность бэкенда.
### Задачи
- [ ] BE + FE: Полный smoke-тест всего брендбука
- [ ] FE: Мобильная адаптация — финальная проверка всех страниц
- [ ] FE: Accessibility-аудит (WCAG AA)
- [ ] Деплой BE: выбрать и настроить хостинг для NestJS + PostgreSQL
- [ ] Деплой: настроить автоматический деплой через Gitea Actions → Vercel (при пуше в `main`)
- [ ] Docs: Обновление `docs/DEPLOY.md` финальными инструкциями
- [ ] Design: Финальный ревью брендбука
- [ ] BE+FE: Полный smoke-тест всего брендбука (все роли, все страницы)
- [ ] FE: LLM-блоки на всех страницах — проверка актуальности
- [ ] FE: Мобильная адаптация — финальная проверка (1440 / 768 / 375px)
- [ ] FE: Accessibility-аудит WCAG AA — особенно форма дублирования
- [ ] Деплой BE: выбрать хостинг для NestJS + PostgreSQL (Railway / Render / VPS клиники)
- [ ] Деплой: автоматический деплой через Gitea Actions → Vercel (push в `main`)
- [ ] Docs: Финальное обновление `docs/LLM_CONTEXT.md`, `DEPLOY.md`
### Текущий статус деплоя
- **Фронтенд:** https://web-oclinica.vercel.app (Vercel Hobby, задеплоен в Sprint 2)
- **Фронтенд:** https://web-oclinica.vercel.app (Vercel Hobby)
- **Команда деплоя:** `cd apps/web && vercel --prod --yes`
- **Бэкенд:** локально (Docker Compose), хостинг выбирается в Sprint 12
- **Бэкенд:** локально (Docker Compose), хостинг выбирается в Sprint 10
**Результат спринта:** Брендбук полностью готов, оба сервиса задеплоены, автодеплой настроен.
**Результат спринта:** Брендбук полностью готов, оба сервиса задеплоены, реальные данные, роли работают.
---
## Сводная таблица
| Спринт | Тема | Ключевой результат |
|--------|---------------------------------------|-----------------------------------------|
| 1 | Инициализация + Логотип | Брендбук запускается, страница Логотипа готова |
| 2 | Цвета, Типографика, Оффлайн элементы | Фундамент + оффлайн раздел готовы |
| 3 | Кнопки и форм-контролы | Базовые компоненты готовы |
| 4 | Карточки | Все карточки задокументированы |
| 5 | Hero и CEO-текст | Ключевые блоки страниц готовы |
| 6 | Врачи | Блоки о врачах готовы |
| 7 | Отзывы и новости | Контентные блоки готовы |
| 8 | Формы и модальные окна | Все формы задокументированы |
| 9 | Страницы: Главная, Заболевание | 2 страницы в брендбуке |
| 10 | Страницы: Врачи, Цены | 3 страницы в брендбуке |
| 11 | Контакты + Эксперименты MVP | Все страницы + система экспериментов |
| 12 | Деплой и полировка | Брендбук живёт в продакшне |
| Спринт | Тема | Слой | Суть |
|--------|---------------------------------|---------------|--------------------------------------------------------|
| 1 | Инициализация + Логотип | FE | PDF брендбука, первая страница |
| 2 | Цвета, Типографика, Оффлайн | FE | PDF + Oracal каталог |
| 3 | Кнопки и форм-контролы | FE | CSS реального сайта |
| 4 | Карточки, бейджи, алерты | FE | CSS реального сайта |
| 5 | ВСЕ блоки сайта | FE | Все блоки /lor, mock-данные |
| 6 | Все страницы (сборки) | FE | Сборки из блоков, mock-данные |
| 7 | Авторизация (viewer / editor) | BE + FE | JWT, роли, login-страница, шапка с именем |
| 8 | Реальные данные | BE + FE | NestJS прокси → oclinica.ru, кэш 15 мин |
| 9 | Эксперименты (дубли) | BE + FE | Duplicate button, форма атрибутов, статусы, раздел |
| 10 | Финальная полировка | BE + FE | Smoke-тест, деплой бэкенда, адаптивность |
---
## Backlog (вне основных спринтов)
- Тёмная тема
- Экспорт компонентов в Figma
- Drag-and-drop конструктор страниц
- История версий экспериментальных компонентов
- Мультиязычность (если потребуется)
- Раздел «Логотип»: активация кнопки скачивания SVG (после получения вектора)
- Экспериментальная секция — если потребуется CRUD API (NestJS + Prisma)
- Тёмная тема — только если появится на реальном сайте
- Мультиязычность — только если появится на реальном сайте