feat: LLM-блоки на страницах цветов и типографики + docs/LLM_CONTEXT.md

- Создан компонент components/llm/LlmBlock.tsx (use client):
  LlmBlock, LlmSection, LlmTable, LlmRules — переиспользуемый
  паттерн для LLM-спецификаций на всех страницах брендбука

- /foundation/colors: добавлен раздел «LLM-спецификация» (v2.1)
  · таблица фирменных цветов Oracal (7 шт.)
  · таблица цветов сайта oclinica.ru (11 шт., +3 новых: коралловый,
    светло-жёлтый, светло-зелёный)
  · таблица контрастности WCAG 2.1
  · правила применения ✓/✕
  · кнопка «Скопировать» — plain text для LLM

- /foundation/typography: добавлен раздел «LLM-спецификация» (v2.0)
  · таблица шрифтов DINPro vs Fira Sans
  · шкала DINPro (6 стилей)
  · шкала Fira Sans (11 стилей, включая letter-spacing)
  · применение по носителям + правила ✓/✕

- docs/LLM_CONTEXT.md: создан сводный машиночитаемый файл бренда
  Версия 2.1: все цвета, типографика, логотип, оффлайн, CSS-vars,
  правила — единый контекст для AI при работе с брендом клиники

- docs/TZ.md: добавлено требование ФТ-03-LLM
- docs/SPRINTS.md: задачи LLM-блоков в Sprint 3–8

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AR 15 M4
2026-03-22 19:59:58 +05:00
parent 6648f16bcb
commit 0198947c4e
6 changed files with 923 additions and 6 deletions
+175
View File
@@ -0,0 +1,175 @@
"use client";
import { useState } from "react";
interface LlmBlockProps {
/** Путь страницы, например "/foundation/colors" */
path: string;
/** Версия данных, например "v2.1" */
version: string;
/** Плоский текст для копирования */
specText: string;
/** Содержимое блока — таблицы, правила */
children: React.ReactNode;
}
/**
* LlmBlock — переиспользуемый блок LLM-спецификации.
* Добавляется в конец каждой страницы брендбука, содержащей дизайн-стандарты.
* Требование: ФТ-03-LLM (TZ.md) · docs/LLM_CONTEXT.md
*/
export function LlmBlock({ path, version, specText, children }: LlmBlockProps) {
const [copied, setCopied] = useState(false);
function handleCopy(e: React.MouseEvent) {
e.preventDefault();
navigator.clipboard.writeText(specText);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
return (
<details
open
className="rounded-xl overflow-hidden"
style={{
border: "2px dashed var(--brand-053m)",
}}
>
{/* Заголовок */}
<summary
className="flex items-center justify-between px-5 py-3 cursor-pointer select-none list-none"
style={{ background: "rgba(126,207,202,0.07)" }}
>
<div className="flex items-center gap-2 min-w-0">
<span
className="shrink-0 text-[10px] font-bold px-1.5 py-0.5 rounded tracking-wider"
style={{ background: "var(--brand-053m)", color: "#fff" }}
>
LLM
</span>
<span className="font-semibold text-sm" style={{ color: "var(--bb-text)" }}>
LLM-спецификация
</span>
<span
className="text-xs truncate hidden sm:inline"
style={{ color: "var(--bb-text-muted)" }}
>
· машиночитаемые данные · docs/LLM_CONTEXT.md
</span>
</div>
<div className="flex items-center gap-3 shrink-0 ml-3">
<span
className="text-[10px] font-mono hidden md:inline"
style={{ color: "var(--bb-text-muted)" }}
>
{path} · {version}
</span>
<button
onClick={handleCopy}
className="text-xs px-3 py-1 rounded font-medium transition-colors shrink-0"
style={{
background: copied ? "#d1fae5" : "var(--brand-053m)",
color: copied ? "#065f46" : "#fff",
}}
>
{copied ? "✓ Скопировано" : "Скопировать"}
</button>
</div>
</summary>
{/* Содержимое */}
<div
className="px-5 py-5 space-y-6 border-t"
style={{
borderColor: "var(--brand-053m)",
borderStyle: "dashed",
background: "var(--bb-content-bg)",
}}
>
{children}
</div>
</details>
);
}
/* ─── Утилиты для содержимого блока ──────────────────────────── */
/** Заголовок подсекции внутри LLM-блока */
export function LlmSection({ title }: { title: string }) {
return (
<p
className="text-[10px] font-semibold uppercase tracking-widest pb-1 border-b"
style={{ color: "var(--bb-text-muted)", borderColor: "var(--bb-border)" }}
>
{title}
</p>
);
}
/** Компактная таблица для LLM-блока */
export function LlmTable({
headers,
rows,
}: {
headers: string[];
rows: (string | React.ReactNode)[][];
}) {
return (
<div className="overflow-x-auto">
<table className="w-full text-xs border-collapse font-mono">
<thead>
<tr style={{ background: "var(--bb-sidebar-bg)" }}>
{headers.map((h) => (
<th
key={h}
className="text-left px-2 py-1.5 font-medium border whitespace-nowrap"
style={{ color: "var(--bb-text-muted)", borderColor: "var(--bb-border)" }}
>
{h}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, ri) => (
<tr key={ri} style={{ borderTop: `1px solid var(--bb-border)` }}>
{row.map((cell, ci) => (
<td
key={ci}
className="px-2 py-1 border"
style={{
borderColor: "var(--bb-border)",
color: "var(--bb-text-muted)",
maxWidth: "240px",
}}
>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
/** Список правил ✓ / ✕ */
export function LlmRules({ rules }: { rules: { ok: boolean; text: string }[] }) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-0.5">
{rules.map((r) => (
<div key={r.text} className="flex items-start gap-1.5 py-0.5 text-xs font-mono">
<span
style={{ color: r.ok ? "#059669" : "#dc2626", fontWeight: 700, flexShrink: 0 }}
>
{r.ok ? "✓" : "✕"}
</span>
<span style={{ color: "var(--bb-text-muted)" }}>{r.text}</span>
</div>
))}
</div>
);
}