Files
vkus-ai-v4/Start data/sequence_diagrams_business.html
T

406 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>