Files
portal/web/v8/v8_errors.html
T
Дмитрий 887abf444e rebrand(v8.5→Лидерра): дизайн-handoff Платона v8 Forest + Лидпоток→Лидерра
Получен handoff-пакет liderra_v8_handoff/ от дизайнера Платона
(kpd9363@gmail.com) от 07.05.2026 — v8 Forest. Заказчик 08.05 решил
применить только в части дизайна, имени, логотипа. Функционал, состав
страниц и правила (CTO-11, click-wrap, SSO break-glass, 14 статусов
воронки) — без изменений (источник — ТЗ v8.5/schema v8.5).

Что сделано:

- Массовая замена Лидпоток→Лидерра (с учётом падежей: Лидерры/Лидерре)
  в 33 файлах (449 вхождений) — все .md/.sql/.json/.toml/.yml/.txt/.html,
  кроме исторических упоминаний внутри liderra_v8_handoff/
- Удалён docs/brandbook.md v1.1 — заменён на BRANDBOOK_v2.md из handoff
- Скопированы 13 концептов liderra_v8_handoff/concepts/v8_*.html в
  web/v8/. Удалены старые web/01-login.html, 02-dashboard.html,
  03-deals.html, index.html (палитра v1.1 deprecated)
- CLAUDE.md v1.0→v1.1: §0 (BRANDBOOK_v2 + DEVELOPER_HANDOFF в источниках),
  §2 (палитра Forest, Inter+JBM, Lucide), §5 п.6 (anti-pattern Inter
  снят — в Forest Inter наш основной шрифт), §6 (13 концептов в web/v8/)
- Реестр Открытые_вопросы_v8_3.md v1.12→v1.13: добавлена запись о
  ребрендинге + 4 точечных расхождений handoff vs ТЗ (статусы воронки,
  click-wrap чекбоксы, SSO fallback, axe violations)
- package.json/package-lock.json: name lidpotok→liderra

4 расхождения handoff vs ТЗ (НЕ применены, источник истины — ТЗ/schema):

1. 14 «обобщённых» статусов в BRANDBOOK_v2 §3.6 ≠ 14 slug'ов в
   schema.sql:2076 (совпадает 2 из 14: «Переговоры», «Оплачено»).
   Источник — schema/ТЗ §6.4 (реселлерская модель из аудита crm.bp-gr.ru,
   6 системных + 8 настраиваемых статусов).
2. 3-й click-wrap в v8_login.html («маркетинг-опционально») ≠ ТЗ §1.5/§4.1
   («согласие на ПДн», обязательное, OPEN-Ж-3).
3. SSO в v8_admin.html («локальный 2FA fallback») ≠ ТЗ OPEN-И-13
   (break-glass super_admin, локальный 2FA выключен).
4. Заявление «axe-core 4.10.2 — 0 violations» в README handoff — локально
   Pa11y 9.1.1 + axe нашёл 81 violation на 10/13 HTML (преимущественно
   color-contrast на декоративных separator'ах с --ink-disabled).
   Чисто: settings/errors/palette_options.

Что НЕ включено в коммит:
- лендинг/TZ_landing_v1_0.md — untracked, не моя работа в этой сессии
- .tmp/ — gitignored

Что осталось (для следующих сессий):
- Возможное переименование GitHub-репо CoralMinister/lidpotok → liderra
  (отдельное решение заказчика)
- Опционально: обратная связь Платону по 4 расхождениям handoff vs ТЗ

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 07:11:58 +03:00

325 lines
15 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">
<title>v8 · Экраны ошибок — Лидерра</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
:root {
--bg:#F6F3EC; --surface:#FFFDFA; --sunken:#F0EDE4;
--hairline:#D9D5CD;
--ink:#081319; --ink-2:#343C41; --ink-3:#66635C;
--accent:#0F6E56; --accent-tint:#E1EEEA; --accent-deep:#084635;
--side-icon-act:#32C8A9;
--st-fail:#6C60C4;
--st-new:#B94837;
--r-sm:6px; --r-md:10px; --r-lg:14px;
--font-ui:'Inter',system-ui,sans-serif;
--font-mono:'JetBrains Mono',ui-monospace,monospace;
}
body { background:var(--bg); color:var(--ink); font-family:var(--font-ui); font-feature-settings:'cv11','ss01'; -webkit-font-smoothing:antialiased; font-variation-settings:'opsz' 14; min-height:100vh; }
button { font-family:inherit; }
a { color:inherit; text-decoration:none; }
a:focus-visible, button:focus-visible {
outline:2px solid var(--accent); outline-offset:2px; border-radius:var(--r-sm);
}
/* Tab bar — for review only, not part of error pages themselves */
.review-bar { position:sticky; top:0; z-index:50; background:#012019; padding:10px 16px; display:flex; align-items:center; gap:14px; border-bottom:1px solid #1A3A30; }
.review-bar .label { font-size:11px; font-weight:600; letter-spacing:0.06em; color:#7A8C87; font-family:var(--font-mono); }
.review-bar .tabs { display:flex; gap:2px; }
.review-bar .tab { height:26px; padding:0 12px; border:none; background:transparent; color:#B1C2BD; font-family:inherit; font-size:12px; font-weight:500; cursor:pointer; border-radius:4px; }
.review-bar .tab:hover { background:#0A2A22; color:#fff; }
.review-bar .tab.active { background:#fff; color:#012019; }
/* Error page shell */
.error-page {
min-height: calc(100vh - 47px);
display: flex; flex-direction: column; align-items: center; justify-content: center;
padding: 60px 24px 80px;
text-align: center;
position: relative;
}
.error-page .top-brand {
position: absolute; top: 24px; left: 28px;
display: flex; align-items: center; gap: 10px;
font-weight: 600;
font-size: 14.5px;
letter-spacing: -0.01em;
font-variation-settings:'opsz' 18;
color: var(--ink);
}
.error-page .top-brand .mark {
width: 22px; height: 22px;
border-radius: 4px;
background: var(--ink);
display: inline-flex; align-items: center; justify-content: center;
flex-shrink: 0; overflow: hidden;
}
.error-page .top-brand .mark svg { width:100%; height:100%; display:block; }
.error-page .top-brand .dot { color: var(--accent); }
.err-code {
font-family: var(--font-mono);
font-feature-settings: 'tnum';
font-size: 130px;
font-weight: 600;
letter-spacing: -0.04em;
line-height: 1;
color: var(--ink);
margin: 0 0 8px;
display: inline-flex; align-items: baseline; gap: 0;
position: relative;
}
.err-code .accent {
color: var(--accent);
}
.err-code::after {
content: '';
position: absolute;
left: 50%; bottom: -10px; transform: translateX(-50%);
width: 36px; height: 2px;
background: var(--accent);
border-radius: 2px;
}
.err-title {
font-size: 26px;
font-weight: 600;
font-variation-settings:'opsz' 26;
letter-spacing: -0.02em;
line-height: 1.2;
margin: 28px 0 10px;
}
.err-desc {
font-size: 14px;
color: var(--ink-3);
line-height: 1.55;
max-width: 480px;
margin: 0 auto 28px;
letter-spacing: -0.005em;
}
.err-actions { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.btn { display:inline-flex; align-items:center; gap:7px; height:38px; padding:0 18px; border-radius:var(--r-sm); border:1px solid var(--hairline); background:var(--surface); font-size:13px; font-weight:500; color:var(--ink); cursor:pointer; font-family:inherit; letter-spacing:-0.005em; }
.btn:hover { border-color:#BFB9AB; background:var(--sunken); }
.btn-primary { background:var(--accent); color:#fff; border-color:var(--accent); }
.btn-primary:hover { background:var(--accent-deep); border-color:var(--accent-deep); }
.btn svg { width:13px; height:13px; stroke-width:1.7; }
.err-id {
font-family: var(--font-mono);
font-size: 11px;
color: var(--ink-3);
letter-spacing: -0.005em;
margin-top: 28px;
display: inline-flex; align-items: center; gap: 8px;
}
.err-id .copy-btn { width:18px; height:18px; border:none; background:none; color:var(--ink-3); border-radius:3px; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; }
.err-id .copy-btn:hover { background:var(--sunken); color:var(--ink); }
.err-id .copy-btn svg { width:11px; height:11px; }
.err-help {
font-size: 12.5px;
color: var(--ink-3);
margin-top: 18px;
letter-spacing: -0.005em;
}
.err-help a { color: var(--accent); font-weight: 500; }
.err-help a:hover { text-decoration: underline; text-underline-offset: 3px; }
/* Status decoration block */
.err-illust {
width: 100%; max-width: 320px;
margin: 0 auto 8px;
display: flex; align-items: center; justify-content: center;
}
/* status indicator (for 500) */
.status-list {
margin-top: 30px;
display: flex; gap: 14px; align-items: center;
font-size: 11.5px;
color: var(--ink-3);
}
.status-list .item { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-mono); font-feature-settings:'tnum'; }
.status-list .dot { width: 7px; height: 7px; border-radius: 50%; position: relative; }
.status-list .dot::after { content:''; position:absolute; inset:-1px; border-radius:50%; border:1px solid rgba(10,19,25,0.10); }
.status-list .ok .dot { background: var(--accent); }
.status-list .deg .dot { background: var(--st-fail); }
.status-list .down .dot { background: var(--st-new); }
/* page switcher */
.error-page { display: none; }
.error-page.active { display: flex; }
/* error 403 — special */
#err-403 .err-code .accent { color: var(--st-fail); }
#err-403 .err-code::after { background: var(--st-fail); }
/* error 500 */
#err-500 .err-code .accent { color: var(--st-new); }
#err-500 .err-code::after { background: var(--st-new); }
@media (max-width: 768px) {
.err-code { font-size: 88px; }
.err-title { font-size: 22px; }
.error-page { padding: 80px 18px 40px; }
.error-page .top-brand { top: 14px; left: 16px; }
}
</style>
</head>
<body>
<!-- Review-only tabs to switch between error states -->
<nav class="review-bar" aria-label="Переключение между состояниями ошибок (только для review)">
<span class="label">PREVIEW</span>
<div class="tabs" role="tablist">
<button type="button" class="tab active" data-tab="404" role="tab" aria-selected="true">404 · не найдено</button>
<button type="button" class="tab" data-tab="403" role="tab" aria-selected="false">403 · нет доступа</button>
<button type="button" class="tab" data-tab="500" role="tab" aria-selected="false">500 · сервис недоступен</button>
</div>
</nav>
<!-- ===== 404 ===== -->
<main id="err-404" class="error-page active" role="main">
<a href="#" class="top-brand">
<span class="mark" aria-hidden="true">
<svg viewBox="0 0 48 48"><path d="M16 14 L16 34 L32 34" stroke="#fff" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/><circle cx="32" cy="34" r="3.5" fill="#0F6E56"/></svg>
</span>
<span>Лидерра<span class="dot">.</span></span>
</a>
<div class="err-illust" aria-hidden="true">
<svg width="240" height="120" viewBox="0 0 240 120" fill="none">
<path d="M20 90 Q60 60 120 60 T220 90" stroke="#D9D5CD" stroke-width="1.5" stroke-dasharray="3 4"/>
<circle cx="120" cy="60" r="3" fill="#0F6E56"/>
<path d="M115 30 L120 60 M125 30 L120 60" stroke="#0F6E56" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<h1 class="err-code">4<span class="accent">0</span>4</h1>
<h2 class="err-title">Страница не найдена</h2>
<p class="err-desc">
Похоже, такой страницы нет — её удалили, переименовали или вы ввели адрес с опечаткой.
Все рабочие экраны Лидерра доступны через дашборд.
</p>
<div class="err-actions">
<a href="/" class="btn btn-primary">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
На дашборд
</a>
<button type="button" class="btn" onclick="history.back()">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
Назад
</button>
</div>
<p class="err-help">Что-то не так? Напишите в <a href="mailto:support@liderra.app">support@liderra.app</a></p>
</main>
<!-- ===== 403 ===== -->
<main id="err-403" class="error-page" role="main">
<a href="#" class="top-brand">
<span class="mark" aria-hidden="true">
<svg viewBox="0 0 48 48"><path d="M16 14 L16 34 L32 34" stroke="#fff" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/><circle cx="32" cy="34" r="3.5" fill="#0F6E56"/></svg>
</span>
<span>Лидерра<span class="dot">.</span></span>
</a>
<div class="err-illust" aria-hidden="true">
<svg width="180" height="120" viewBox="0 0 180 120" fill="none">
<rect x="50" y="50" width="80" height="60" rx="6" stroke="#D9D5CD" stroke-width="1.5"/>
<path d="M62 50 L62 36 a28 28 0 0 1 56 0 L118 50" stroke="#6C60C4" stroke-width="2" stroke-linecap="round" fill="none"/>
<circle cx="90" cy="78" r="5" fill="#6C60C4"/>
<line x1="90" y1="83" x2="90" y2="92" stroke="#6C60C4" stroke-width="2.5" stroke-linecap="round"/>
</svg>
</div>
<h1 class="err-code">4<span class="accent">0</span>3</h1>
<h2 class="err-title">У вас нет доступа</h2>
<p class="err-desc">
Эта страница принадлежит другому тенанту, либо ваша роль не позволяет её увидеть.
Если вы считаете, что это ошибка — обратитесь к администратору вашей команды или в поддержку.
</p>
<div class="err-actions">
<a href="/" class="btn btn-primary">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
На дашборд
</a>
<a href="mailto:support@liderra.app" class="btn">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
Написать в поддержку
</a>
</div>
<div class="err-id">
<span>Запрос</span>
<span style="color:var(--ink-2);font-weight:500">REQ-3F8A2-0007</span>
<button type="button" class="copy-btn" aria-label="Скопировать ID запроса">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
</button>
</div>
</main>
<!-- ===== 500 / 503 ===== -->
<main id="err-500" class="error-page" role="main">
<a href="#" class="top-brand">
<span class="mark" aria-hidden="true">
<svg viewBox="0 0 48 48"><path d="M16 14 L16 34 L32 34" stroke="#fff" stroke-width="4.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/><circle cx="32" cy="34" r="3.5" fill="#0F6E56"/></svg>
</span>
<span>Лидерра<span class="dot">.</span></span>
</a>
<div class="err-illust" aria-hidden="true">
<svg width="220" height="120" viewBox="0 0 220 120" fill="none">
<path d="M20 90 L40 60 L60 80 L80 50 L100 70 L120 40" stroke="#D9D5CD" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<path d="M120 40 L140 90" stroke="#B94837" stroke-width="2.2" stroke-linecap="round"/>
<circle cx="140" cy="90" r="4" fill="#B94837"/>
<text x="155" y="94" font-family="JetBrains Mono, monospace" font-size="11" fill="#B94837" font-weight="600">503</text>
</svg>
</div>
<h1 class="err-code">5<span class="accent">0</span>0</h1>
<h2 class="err-title">Что-то пошло не так</h2>
<p class="err-desc">
Внутренняя ошибка — мы уже занимаемся. Команда получила уведомление.
Большинство сбоев чинятся за 5–10 минут. Можно вернуться через минуту, или открыть страницу статуса.
</p>
<div class="err-actions">
<button type="button" class="btn btn-primary" onclick="location.reload()">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
Попробовать снова
</button>
<a href="https://status.liderra.app" class="btn">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><polyline points="3 17 9 11 13 15 21 7"/><polyline points="14 7 21 7 21 14"/></svg>
Статус сервиса
</a>
</div>
<div class="status-list" aria-label="Состояние ключевых интеграций">
<span class="item ok"><span class="dot" aria-hidden="true"></span>API · OK</span>
<span class="item deg"><span class="dot" aria-hidden="true"></span>Telegram · деградация</span>
<span class="item ok"><span class="dot" aria-hidden="true"></span>YooKassa · OK</span>
</div>
<div class="err-id">
<span>Инцидент</span>
<span style="color:var(--ink-2);font-weight:500">INC-2026-0507-0034</span>
<button type="button" class="copy-btn" aria-label="Скопировать ID инцидента">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
</button>
</div>
</main>
<script>
// Tab switching for review preview only
const tabs = Array.from(document.querySelectorAll('.review-bar .tab'));
const pages = Array.from(document.querySelectorAll('.error-page'));
function setTab(id) {
tabs.forEach(t => {
const isA = t.dataset.tab === id;
t.classList.toggle('active', isA);
t.setAttribute('aria-selected', isA ? 'true' : 'false');
});
pages.forEach(p => p.classList.toggle('active', p.id === 'err-' + id));
}
tabs.forEach(t => t.addEventListener('click', () => setTab(t.dataset.tab)));
</script>
</body>
</html>