Initial commit: VKUS vitrina MVP (frontend + backend).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 23:13:06 +07:00
commit 862e6b7b25
113 changed files with 50949 additions and 0 deletions
+405
View File
@@ -0,0 +1,405 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ВКУС — диаграммы понятным языком</title>
<style>
:root {
--bg: #f4f4f5; --card: #fff; --border: #d4d4d8; --text: #18181b; --muted: #71717a;
--primary: #1d4ed8; --note-bg: #f8fafc;
}
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, sans-serif; background: var(--bg); color: var(--text); }
header { background: var(--card); border-bottom: 1px solid var(--border); padding: 14px 20px; position: sticky; top: 0; z-index: 20; }
header h1 { margin: 0 0 4px; font-size: 1.15rem; }
header p { margin: 0; color: var(--muted); font-size: 0.85rem; }
nav { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 20px; background: var(--card); border-bottom: 1px solid var(--border); position: sticky; top: 54px; z-index: 19; }
nav button { padding: 7px 12px; border: 1px solid var(--border); border-radius: 5px; background: #fafafa; cursor: pointer; font-size: 0.8rem; }
nav button.active { background: var(--primary); color: #fff; border-color: var(--primary); }
main { padding: 16px 20px 40px; }
section { display: none; }
section.visible { display: block; }
.panel { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px 18px; margin-bottom: 16px; }
.panel h2 { margin: 0 0 4px; font-size: 1.05rem; }
.panel .hint { margin: 0 0 10px; color: var(--muted); font-size: 0.82rem; }
.legend { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 12px; font-size: 0.76rem; color: var(--muted); }
.legend i { display: inline-block; width: 28px; height: 0; vertical-align: middle; margin-right: 4px; border-top: 2px solid #18181b; }
.legend .ret i { border-top: 2px dashed #52525b; }
.legend .fb i { border-top: 2px solid #ea580c; }
.svg-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: 6px; background: #fff; }
svg { display: block; }
</style>
</head>
<body>
<header>
<h1>ВКУС — схемы понятным языком</h1>
<p>Та же логика, что diagrams_viewer.html · без технических терминов на стрелках</p>
</header>
<nav>
<button class="active" data-tab="target">Цель: клиентский путь</button>
<button data-tab="prototype">Прототип сейчас</button>
<button data-tab="ai">Как подключается ИИ</button>
<button data-tab="c4-1">Кто с кем связан</button>
</nav>
<main>
<section id="target" class="visible">
<div class="panel">
<h2>Целевой путь: от вопроса до заявки</h2>
<p class="hint">Как должно работать по клиентскому пути (CJM) · время сверху вниз</p>
<div class="legend">
<span><i></i> действие</span>
<span class="ret"><i></i> ответ (пунктир)</span>
<span class="fb"><i></i> запасной путь</span>
</div>
<div class="svg-scroll" id="seq-target"></div>
</div>
</section>
<section id="prototype">
<div class="panel">
<h2>Прототип: как обрабатывается запрос сейчас</h2>
<p class="hint">Текущая реализация · оранжевые ветки — обход или запасной вариант</p>
<div class="legend">
<span><i></i> действие</span>
<span class="ret"><i></i> ответ (пунктир)</span>
<span class="fb"><i></i> запасной путь</span>
</div>
<div class="svg-scroll" id="seq-prototype"></div>
</div>
</section>
<section id="ai">
<div class="panel">
<h2>Интеграции ИИ: поиск и генерация ответа</h2>
<p class="hint">Как ВКУС обращается к корпоративной платформе ИИ</p>
<div class="legend">
<span><i></i> действие</span>
<span class="ret"><i></i> ответ (пунктир)</span>
<span class="fb"><i></i> запасной путь</span>
</div>
<div class="svg-scroll" id="seq-ai"></div>
</div>
</section>
<section id="c4-1">
<div class="panel">
<h2>Общая картина: кто с кем взаимодействует</h2>
<p class="hint">Контекстная диаграмма (C4 Level 1) · роли систем простым языком</p>
<div class="svg-scroll" id="c4-context"></div>
</div>
</section>
</main>
<script>
(function () {
"use strict";
var STYLES = {
actor: { fill: "#fef9c3", stroke: "#ca8a04" },
core: { fill: "#eff6ff", stroke: "#3b82f6" },
integ: { fill: "#f0fdf4", stroke: "#16a34a" },
ext: { fill: "#fef3c7", stroke: "#d97706" },
fragAlt:{ fill: "rgba(255,247,237,0.92)", stroke: "#ea580c" },
fragOpt:{ fill: "rgba(248,250,252,0.92)", stroke: "#64748b" },
};
function esc(s) {
return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function fragRect(f, msgs, xs, y0, rowH) {
var minP = 999, maxP = -1;
var extra = f.participants || [];
extra.forEach(function (p) { minP = Math.min(minP, p); maxP = Math.max(maxP, p); });
for (var i = f.start; i <= f.end; i++) {
if (!msgs[i]) continue;
minP = Math.min(minP, msgs[i].from, msgs[i].to);
maxP = Math.max(maxP, msgs[i].from, msgs[i].to);
}
if (minP > maxP) return null;
var margin = 62;
return {
x: xs[minP] - margin,
y: y0 + f.start * rowH - 16,
w: xs[maxP] - xs[minP] + margin * 2,
h: (f.end - f.start + 1) * rowH + 8,
};
}
function renderSequence(el, cfg) {
var uid = cfg.id || "s";
var parts = cfg.participants;
var msgs = cfg.messages;
var colW = cfg.colW || 124;
var left = 28;
var top = 62;
var rowH = cfg.rowH || 40;
var headH = 52;
var w = left + 30 + colW * parts.length + 40;
var h = top + headH + msgs.length * rowH + 50;
var xs = parts.map(function (_, i) { return left + 50 + i * colW; });
var y0 = top + headH;
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '">';
svg += '<defs>';
svg += '<marker id="' + uid + '-ar" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#18181b"/></marker>';
svg += '<marker id="' + uid + '-fb" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#ea580c"/></marker>';
svg += '<marker id="' + uid + '-ret" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#52525b"/></marker>';
svg += '</defs>';
parts.forEach(function (p, i) {
var st = STYLES[p.kind] || STYLES.core;
var x = xs[i];
var bw = Math.min(colW - 6, p.kind === "actor" ? 80 : 108);
svg += '<rect x="' + (x - bw / 2) + '" y="10" width="' + bw + '" height="' + headH + '" rx="4" fill="' + st.fill + '" stroke="' + st.stroke + '" stroke-width="1.2"/>';
(p.label || "").split("\n").forEach(function (ln, li) {
svg += '<text x="' + x + '" y="' + (28 + li * 13) + '" text-anchor="middle" font-size="10.5" font-weight="600">' + esc(ln) + '</text>';
});
if (p.sub) {
svg += '<text x="' + x + '" y="' + (headH + 2) + '" text-anchor="middle" font-size="8.5" fill="#71717a">' + esc(p.sub) + '</text>';
}
svg += '<line x1="' + x + '" y1="' + (10 + headH) + '" x2="' + x + '" y2="' + (h - 16) + '" stroke="#d4d4d8" stroke-width="1" stroke-dasharray="5 4"/>';
});
if (cfg.fragments) {
cfg.fragments.forEach(function (f) {
var r = fragRect(f, msgs, xs, y0, rowH);
if (!r) return;
var st = f.kind === "opt" ? STYLES.fragOpt : STYLES.fragAlt;
svg += '<rect x="' + r.x + '" y="' + r.y + '" width="' + r.w + '" height="' + r.h + '" fill="' + st.fill + '" stroke="' + st.stroke + '" stroke-width="1.2" rx="5"/>';
svg += '<text x="' + (r.x + 8) + '" y="' + (r.y + 15) + '" font-size="9.5" font-weight="700" fill="' + st.stroke + '">' + esc(f.label) + '</text>';
});
}
msgs.forEach(function (m, idx) {
var y = y0 + idx * rowH + rowH / 2;
var fi = m.from, ti = m.to;
var xFrom = xs[fi], xTo = xs[ti];
var isRet = m.type === "return";
var isSelf = fi === ti;
var isFb = m.fallback;
var col = isFb ? "#ea580c" : (isRet ? "#52525b" : "#18181b");
var mk = uid + (isFb ? "-fb" : (isRet ? "-ret" : "-ar"));
var dash = isRet ? ' stroke-dasharray="7 5"' : "";
var lines = (m.label || "").split("\n");
if (isSelf) {
var sx = xFrom + 10;
svg += '<path d="M' + sx + ' ' + y + ' h34 v18 h-34" fill="none" stroke="' + col + '" stroke-width="1.4" marker-end="url(#' + mk + ')"/>';
lines.forEach(function (ln, li) {
svg += '<text x="' + (sx + 42) + '" y="' + (y - 4 + li * 12) + '" font-size="9.5" fill="#3f3f46">' + esc(ln) + '</text>';
});
} else {
svg += '<line x1="' + xFrom + '" y1="' + y + '" x2="' + xTo + '" y2="' + y + '" stroke="' + col + '" stroke-width="1.4"' + dash + ' marker-end="url(#' + mk + ')"/>';
var lx = (xFrom + xTo) / 2;
var anchor = "middle";
if (Math.abs(xTo - xFrom) < 90) { lx = Math.max(xFrom, xTo) + 6; anchor = "start"; }
lines.forEach(function (ln, li) {
svg += '<text x="' + lx + '" y="' + (y - 8 + li * 12) + '" text-anchor="' + anchor + '" font-size="9.5" fill="#3f3f46">' + esc(ln) + '</text>';
});
}
if (m.num) {
svg += '<circle cx="' + (left + 4) + '" cy="' + y + '" r="10" fill="#1d4ed8"/>';
svg += '<text x="' + (left + 4) + '" y="' + (y + 4) + '" text-anchor="middle" font-size="9" fill="#fff" font-weight="700">' + m.num + '</text>';
}
});
svg += '</svg>';
el.innerHTML = svg;
}
// ——— ЦЕЛЕВОЙ CJM ———
renderSequence(document.getElementById("seq-target"), {
id: "tgt",
colW: 118,
rowH: 42,
participants: [
{ label: "Сотрудник", kind: "actor" },
{ label: "Экран\nВКУС", sub: "витрина", kind: "core" },
{ label: "Ядро\nсистемы", sub: "оркестратор", kind: "core" },
{ label: "Понимание\nзапроса", sub: "намерения", kind: "core" },
{ label: "База\nзнаний", sub: "инструкции", kind: "core" },
{ label: "Шлюз\nк ИИ", sub: "корп. API", kind: "integ" },
{ label: "Поиск в\nдокументах", sub: "RAG", kind: "integ" },
{ label: "Генерация\nтекста", sub: "LLM", kind: "integ" },
{ label: "Заявки", sub: "Creatio", kind: "ext" },
],
fragments: [
{ label: "не вопрос — ответ без поиска", start: 4, end: 6 },
{ label: "система не в базе — уточнить", start: 7, end: 9 },
{ label: "корпоративный поиск по документам", start: 12, end: 15, kind: "opt" },
{ label: "запасной: только своя база", start: 15, end: 15 },
{ label: "улучшить ответ с помощью ИИ", start: 16, end: 19, kind: "opt" },
{ label: "запасной: шаблон без ИИ", start: 20, end: 20 },
{ label: "помогло — вопрос закрыт", start: 24, end: 26 },
{ label: "не помогло — заявка в поддержку", start: 27, end: 28 },
],
messages: [
{ num: 1, from: 0, to: 1, label: "Описывает\nзадачу" },
{ num: 2, from: 1, to: 2, label: "Передаёт\nвопрос" },
{ num: 3, from: 2, to: 3, label: "Что имел\nв виду?" },
{ num: 4, from: 2, to: 2, label: "Запоминаем\nсессию" },
{ num: 5, from: 3, to: 2, label: "Без поиска\n— короткий ответ", type: "return", fallback: true },
{ num: 6, from: 2, to: 1, label: "Ответ без\nбазы знаний", type: "return" },
{ num: 7, from: 1, to: 0, label: "Показ\nв чате", type: "return" },
{ num: 8, from: 2, to: 3, label: "По какой\nИТ-системе?" },
{ num: 9, from: 3, to: 2, label: "Уточните\nсистему", type: "return", fallback: true },
{ num: 10, from: 2, to: 1, label: "Сообщение\nсотруднику", type: "return" },
{ num: 11, from: 2, to: 4, label: "Поиск инструкций\nи истории" },
{ num: 12, from: 4, to: 4, label: "Сначала\nсвоя база" },
{ num: 13, from: 4, to: 5, label: "Запрос в\nплатформу ИИ" },
{ num: 14, from: 5, to: 6, label: "Поиск по смыслу\nв документах" },
{ num: 15, from: 6, to: 5, label: "Подходящие\nфрагменты", type: "return" },
{ num: 16, from: 4, to: 4, label: "Запасной:\nтолько своя база", fallback: true },
{ num: 17, from: 4, to: 5, label: "Собрать\nответ ИИ" },
{ num: 18, from: 5, to: 7, label: "Формирование\nтекста" },
{ num: 19, from: 7, to: 5, label: "Текст и\nуверенность", type: "return" },
{ num: 20, from: 4, to: 2, label: "Запасной:\nшаблон", type: "return", fallback: true },
{ num: 21, from: 4, to: 2, label: "Найденное +\nчерновик заявки", type: "return" },
{ num: 22, from: 2, to: 1, label: "Ответ, шаги,\nисточники", type: "return" },
{ num: 23, from: 1, to: 0, label: "«Помогло» /\n«Не помогло»" },
{ num: 24, from: 0, to: 1, label: "Помогло" },
{ num: 25, from: 1, to: 2, label: "Оценка\nрезультата" },
{ num: 26, from: 2, to: 1, label: "Вопрос закрыт\nбез заявки", type: "return" },
{ num: 27, from: 2, to: 8, label: "Создание\nзаявки" },
{ num: 28, from: 8, to: 2, label: "Номер\nи срок", type: "return" },
{ num: 29, from: 2, to: 1, label: "Номер заявки\nсотруднику", type: "return" },
],
});
// ——— ПРОТОТИП ———
renderSequence(document.getElementById("seq-prototype"), {
id: "proto",
colW: 128,
rowH: 42,
participants: [
{ label: "Сотрудник", kind: "actor" },
{ label: "Экран\nчата", sub: "витрина", kind: "core" },
{ label: "Обработка\nзапроса", sub: "ядро", kind: "core" },
{ label: "Тип\nреплики", sub: "диалог", kind: "core" },
{ label: "Проверка\nсистемы", sub: "фильтр", kind: "core" },
{ label: "Поиск\nинструкций", sub: "база знаний", kind: "core" },
{ label: "Внешняя\nбаза", sub: "если есть", kind: "integ" },
{ label: "Прошлые\nобращения", sub: "опыт", kind: "core" },
{ label: "Ответ\nс ИИ", sub: "улучшение", kind: "integ" },
],
fragments: [
{ label: "не вопрос — ответ без поиска", start: 3, end: 4 },
{ label: "система не в нашей базе", start: 5, end: 7 },
{ label: "сначала по смыслу, иначе по словам", start: 9, end: 10 },
{ label: "доп. поиск снаружи", start: 11, end: 13, kind: "opt" },
{ label: "похожие случаи коллег", start: 14, end: 15, kind: "opt" },
{ label: "ИИ или готовый шаблон", start: 17, end: 20 },
],
messages: [
{ num: 1, from: 0, to: 1, label: "Пишет\nвопрос" },
{ num: 2, from: 1, to: 2, label: "Передаёт\nв систему" },
{ num: 3, from: 2, to: 3, label: "Что это:\nвопрос или реплика?" },
{ num: 4, from: 3, to: 2, label: "Без поиска\n(спасибо, смена темы)", type: "return", fallback: true },
{ num: 5, from: 2, to: 1, label: "Короткий\nответ", type: "return" },
{ num: 6, from: 2, to: 4, label: "По какой\nИТ-системе?" },
{ num: 7, from: 4, to: 2, label: "Не поддерживаем\n— уточнить", type: "return", fallback: true },
{ num: 8, from: 2, to: 1, label: "Просьба назвать\nсистему", type: "return" },
{ num: 9, from: 2, to: 5, label: "Ищем в\nофициальных материалах" },
{ num: 10, from: 5, to: 5, label: "Сравниваем\nсмысл текста" },
{ num: 11, from: 5, to: 5, label: "Запасной путь:\nпо ключевым словам", fallback: true },
{ num: 12, from: 2, to: 6, label: "Доп. поиск\nво внешней базе" },
{ num: 13, from: 6, to: 2, label: "Найденные\nфрагменты", type: "return" },
{ num: 14, from: 2, to: 2, label: "Остаёмся\nна своей базе", fallback: true },
{ num: 15, from: 2, to: 7, label: "Ищем похожие\nслучаи" },
{ num: 16, from: 7, to: 2, label: "Опыт коллег\nили пусто", type: "return" },
{ num: 17, from: 2, to: 2, label: "Решаем:\nответить или заявка" },
{ num: 18, from: 2, to: 8, label: "Собрать ответ\nс помощью ИИ" },
{ num: 19, from: 8, to: 2, label: "Готовый\nтекст", type: "return" },
{ num: 20, from: 8, to: 2, label: "Текст\n(запасной канал)", type: "return", fallback: true },
{ num: 21, from: 2, to: 2, label: "Ответ\nпо шаблону", fallback: true },
{ num: 22, from: 2, to: 2, label: "Блок «что помогало»\n+ черновик заявки" },
{ num: 23, from: 2, to: 1, label: "Ответ\nдля экрана", type: "return" },
{ num: 24, from: 1, to: 0, label: "Рекомендация\nи похожие случаи", type: "return" },
],
});
// ——— ИИ ———
renderSequence(document.getElementById("seq-ai"), {
id: "ai",
colW: 148,
rowH: 44,
participants: [
{ label: "Слой знаний\nВКУС", sub: "обработка", kind: "core" },
{ label: "Корпоративный\nAPI ИИ", sub: "единая точка", kind: "integ" },
{ label: "Поиск по\nдокументам", sub: "RAG", kind: "integ" },
{ label: "Языковая\nмодель", sub: "LLM", kind: "integ" },
{ label: "Заявки\nCreatio", sub: "поддержка", kind: "ext" },
],
fragments: [
{ label: "поиск в документах или запасной путь", start: 1, end: 4 },
{ label: "генерация ИИ или шаблон", start: 6, end: 9 },
{ label: "мало уверенности — заявка в поддержку", start: 10, end: 10 },
],
messages: [
{ num: 1, from: 0, to: 1, label: "Найти фрагменты\nпо вопросу" },
{ num: 2, from: 1, to: 2, label: "Поиск по смыслу\nв базе документов" },
{ num: 3, from: 2, to: 1, label: "Лучшие\nфрагменты", type: "return" },
{ num: 4, from: 1, to: 0, label: "Список\nисточников", type: "return" },
{ num: 5, from: 0, to: 0, label: "Запасной:\nпоиск по словам", fallback: true },
{ num: 6, from: 0, to: 1, label: "Сформировать\nответ" },
{ num: 7, from: 1, to: 3, label: "Вопрос +\nнайденные тексты" },
{ num: 8, from: 3, to: 1, label: "Ответ и\nуверенность", type: "return" },
{ num: 9, from: 1, to: 0, label: "Готовая\nрекомендация", type: "return" },
{ num: 10, from: 0, to: 0, label: "Запасной:\nшаблон без ИИ", fallback: true },
{ num: 11, from: 0, to: 4, label: "Создать заявку\nв поддержку", fallback: true },
],
});
// ——— C4 Level 1 ———
document.getElementById("c4-context").innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="860" height="340" viewBox="0 0 860 340">' +
'<defs><marker id="c4a" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#52525b"/></marker>' +
'<marker id="c4o" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#ea580c"/></marker></defs>' +
'<rect x="350" y="10" width="160" height="44" rx="4" fill="#fef9c3" stroke="#ca8a04"/>' +
'<text x="430" y="30" text-anchor="middle" font-size="12" font-weight="600">Пользователи</text>' +
'<text x="430" y="46" text-anchor="middle" font-size="9" fill="#71717a">сотрудники компании</text>' +
'<rect x="300" y="72" width="240" height="78" fill="none" stroke="#71717a" stroke-dasharray="5 3"/>' +
'<text x="312" y="88" font-size="10" fill="#71717a">Портал ВКУС — единая точка входа</text>' +
'<rect x="315" y="100" width="95" height="42" rx="3" fill="#eff6ff" stroke="#3b82f6"/>' +
'<text x="362" y="120" text-anchor="middle" font-size="11" font-weight="600">Экран</text>' +
'<text x="362" y="134" text-anchor="middle" font-size="9" fill="#71717a">чат и формы</text>' +
'<rect x="430" y="100" width="95" height="42" rx="3" fill="#eff6ff" stroke="#3b82f6"/>' +
'<text x="477" y="120" text-anchor="middle" font-size="11" font-weight="600">Ядро</text>' +
'<text x="477" y="134" text-anchor="middle" font-size="9" fill="#71717a">логика ответа</text>' +
'<line x1="410" y1="121" x2="430" y2="121" stroke="#52525b" marker-end="url(#c4a)"/>' +
'<text x="420" y="115" text-anchor="middle" font-size="8" fill="#71717a">запрос</text>' +
'<line x1="430" y1="32" x2="362" y2="100" stroke="#52525b" marker-end="url(#c4a)"/>' +
'<text x="380" y="72" font-size="8" fill="#71717a">задаёт вопрос</text>' +
'<rect x="30" y="175" width="390" height="125" fill="none" stroke="#16a34a" stroke-dasharray="5 3"/>' +
'<text x="42" y="192" font-size="10" fill="#16a34a">Корпоративная платформа ИИ — внешний сервис</text>' +
'<rect x="48" y="208" width="108" height="44" rx="3" fill="#f0fdf4" stroke="#16a34a"/>' +
'<text x="102" y="228" text-anchor="middle" font-size="10" font-weight="600">Шлюз ИИ</text>' +
'<text x="102" y="242" text-anchor="middle" font-size="8.5" fill="#71717a">доступ и безопасность</text>' +
'<rect x="172" y="208" width="108" height="44" rx="3" fill="#f0fdf4" stroke="#16a34a"/>' +
'<text x="226" y="228" text-anchor="middle" font-size="10" font-weight="600">Поиск в базе</text>' +
'<text x="226" y="242" text-anchor="middle" font-size="8.5" fill="#71717a">инструкции, документы</text>' +
'<rect x="296" y="208" width="108" height="44" rx="3" fill="#f0fdf4" stroke="#16a34a"/>' +
'<text x="350" y="228" text-anchor="middle" font-size="10" font-weight="600">Генерация</text>' +
'<text x="350" y="242" text-anchor="middle" font-size="8.5" fill="#71717a">текст ответа</text>' +
'<line x1="477" y1="142" x2="102" y2="208" stroke="#52525b" marker-end="url(#c4a)"/>' +
'<text x="280" y="168" font-size="8.5" fill="#71717a">поиск и генерация через платформу</text>' +
'<rect x="560" y="208" width="120" height="44" rx="3" fill="#fef3c7" stroke="#d97706"/>' +
'<text x="620" y="228" text-anchor="middle" font-size="10" font-weight="600">Creatio</text>' +
'<text x="620" y="242" text-anchor="middle" font-size="8.5" fill="#71717a">заявки в поддержку</text>' +
'<line x1="500" y1="130" x2="560" y2="208" stroke="#ea580c" marker-end="url(#c4o)"/>' +
'<text x="545" y="168" font-size="8.5" fill="#ea580c">если не помогло — заявка</text>' +
'</svg>';
document.querySelectorAll("nav button").forEach(function (btn) {
btn.addEventListener("click", function () {
document.querySelectorAll("nav button").forEach(function (b) { b.classList.remove("active"); });
document.querySelectorAll("main section").forEach(function (s) { s.classList.remove("visible"); });
btn.classList.add("active");
document.getElementById(btn.dataset.tab).classList.add("visible");
});
});
})();
</script>
</body>
</html>