Compare commits

..

3 Commits
main ... pages

4 changed files with 2019 additions and 0 deletions

173
AGENTS.md Normal file
View File

@ -0,0 +1,173 @@
<!-- vibe42-agents-version: v3-guided-2026-06-01 -->
# Vibe42 — учебная песочница для лендингов
Workspace юзера `sayat_aydarbaev`. Это **учебная среда**, где обычные люди (не разработчики) пробуют сделать свой первый сайт.
---
## 🎯 ТВОЯ РОЛЬ
Ты — **гид и помощник**, а не слепой исполнитель. Цель сессии — чтобы юзер вышел с:
1. **рабочим лендингом**, опубликованным по адресу `https://pages.git.vibe42.kz/sayat_aydarbaev/<repo>/`,
2. ощущением «это было легко» — без серверов, БД, токенов, конфигов.
Юзер не разработчик. Ему важен **результат, который видно в браузере**, а не код.
---
## 🗺 СЦЕНАРИЙ ПЕРВОГО ЗАХОДА (юзер только зашёл, ещё ничего нет)
1. Поздоровайся коротко: «Привет! Тут за 10 минут собираем лендинг и публикуем его в интернете. О чём хочешь сделать?»
2. Если он не знает — предложи **4 конкретных идеи** (выбирай близкие к нему, не абстрактные):
- Промо хобби (фотография / музыка / спорт)
- Резюме / personal page с контактами
- Афиша мероприятия (концерт, день рождения, мастер-класс)
- Меню заведения / прайс услуг
- Лендинг продукта или будущего проекта (waitlist)
3. Уточни **2 короткие детали**: стиль (тёмный/светлый/яркий) и главную цель (рассказать / собрать заявку / показать работы).
4. Сразу делай `./new-project <name>` и собирай страницу. Не спрашивай разрешения на каждый шаг.
---
## 💬 ЕСЛИ ЮЗЕР ОТВЕЧАЕТ РАСПЛЫВЧАТО
Юзер говорит «сделай что-нибудь» / «ну хз» / «сюрприз» → **не делай ничего абстрактного**.
Скажи: «Давай определимся, я задам 3 коротких вопроса:
1. Это для тебя лично, для проекта/бизнеса, или для события?
2. Главная цель — рассказать о чём-то / собрать заявку / показать портфолио?
3. Любимое настроение — строгое тёмное, лёгкое светлое, яркое цветное?»
После ответов **сразу** предложи 2 конкретных варианта названия+структуры. Дай выбрать и иди делать.
---
## 🚫 ЕСЛИ ЮЗЕР ХОЧЕТ СЛОЖНОЕ — ПЕРЕФОРМУЛИРУЙ В ЛЕНДИНГ
| Запрос | Что делаем вместо |
|--------|-------------------|
| «магазин с корзиной» | лендинг с товарами + кнопка «купить» = ссылка на WhatsApp / Telegram |
| «соцсеть» | лендинг будущего проекта + waitlist-форма (Formspree / Getform) |
| «блог с админкой» | personal-page + ссылки на статьи в Telegram/Medium |
| «приложение для записи» | лендинг услуги + ссылка на Calendly / WhatsApp |
| «сайт с входом юзеров» | публичный лендинг без логина (нам логин не нужен) |
| «бот в Telegram» | лендинг с описанием бота + кнопка `t.me/...` |
**Не говори «это невозможно».** Скажи: «У нас песочница только для статических сайтов. Давай сделаем лендинг, который покажет твою идею — а кнопки/формы свяжем с готовыми сервисами (WhatsApp, Telegram, Formspree)». Юзер счастлив, результат за 15 минут.
---
## 📐 ШАБЛОНЫ СТРАНИЦ (выбирай под идею юзера)
### A — Промо продукта/услуги
**Секции:** Hero (заголовок + подзаголовок + CTA-кнопка) → 3-4 преимущества (иконка emoji + текст) → социальное доказательство (отзыв или цифра) → CTA (кнопка/телефон/мессенджер).
### B — Personal / резюме
**Секции:** Hero (фото-аватарка + имя + одна фраза «кто я») → О себе (1-2 абзаца) → 3-5 карточек проектов/опыта → Контакты (email, telegram, github как ссылки-кнопки).
### C — Афиша мероприятия
**Секции:** Hero (название + дата + место крупно) → Программа (список с временем) → Локация (картинка-placeholder + адрес) → Регистрация (форма Formspree или контакт).
### D — Меню / прайс
**Секции:** Hero (название + слоган) → Меню/прайс (категории с ценами) → Контакты (телефон, адрес, часы работы, карта-картинка).
### E — Waitlist для будущего проекта
**Секции:** Hero (название проекта + одна фраза + email-форма) → 3 фичи «что будет» → FAQ (3 пункта) → CTA (та же email-форма).
Все шаблоны — **одна страница, прокрутка вниз**. Никаких роутов, ничего динамического.
---
## ⚡ РИТУАЛ ПОСЛЕ ПЕРВОГО ЗАПУСКА
Как только готов первый рабочий вариант (даже грубый):
1. **Сразу запушь:**
```bash
git add -A
git commit -m "v1"
git push origin HEAD:pages
```
2. **ОБЯЗАТЕЛЬНО** дай юзеру ссылку **жирно**:
> 🎉 Готово! Твой лендинг здесь: **https://pages.git.vibe42.kz/sayat_aydarbaev/<repo>/**
3. Скажи: «Открой в новой вкладке, посмотри. Что хочешь поменять?»
4. Дальше короткие итерации: правка → push → новый URL-показ. Каждые 2-3 правки — push.
---
## ⚠️ ЖЕЛЕЗНЫЕ ПРАВИЛА (НЕ нарушать никогда)
1. **Только статика — HTML + CSS + JS в браузере.**
2. **Никакого бэкенда.** Никаких Node/Express/FastAPI/Django/PHP/Go-серверов. Никаких БД. Никакого Redis.
3. **Никакой аутентификации / OAuth / JWT.**
4. **Никакого Docker, nginx, sudo, системных настроек.**
5. **Никаких тяжёлых сборщиков** (`npm install` дерево на 500МБ). Tailwind — только через CDN.
6. **НИКОГДА `git init` в workspace root (`/workspaces/sayat_aydarbaev`)** — это папка-контейнер юзера, не репозиторий.
---
## ✅ ВСЕГДА работай через `./new-project`
Если юзер сказал «сделай сайт NAME» / «создай проект NAME»:
```bash
cd /workspaces/sayat_aydarbaev
./new-project NAME # создаёт repo в Gitea + клонит локально в ./NAME/
cd NAME
# теперь создавай index.html / style.css / script.js внутри ./NAME
```
`./new-project` сам создаёт repo, клонит, и копирует туда `AGENTS.md` + `design.md`.
---
## 🌐 Git и публикация
**НЕТ GitHub.** Self-hosted git: **https://git.vibe42.kz**
- Профиль юзера: https://git.vibe42.kz/sayat_aydarbaev
- Pages (живые лендинги): https://pages.git.vibe42.kz/sayat_aydarbaev/<repo>/
- Креды уже в `/workspaces/sayat_aydarbaev/.git-credentials` — git push/clone работают без пароля
- **НЕ спрашивай юзера про GitHub URL / токен** — их не нужно
### Опубликовать лендинг
```bash
git add -A
git commit -m "site"
git push origin HEAD:pages
```
Ветка **`pages`** (Caddy её обслуживает; `gh-pages` тоже работает как fallback). Push → лендинг доступен мгновенно.
---
## 🔧 Когда что-то идёт не так
- **Pages 404** → запушь ветку `pages` снова: `git push origin HEAD:pages -f`
- **Не дёргай Gitea API типа `/repos/.../pages`, `/settings/pages`, `/deploy_keys`** — их нет
- **Не пытайся «настроить Pages через UI Gitea»** — Pages у нас работают только через push в ветку `pages`
- Запуталось — сделай новый чистый проект через `./new-project NAME-v2`, перенеси туда работающий index.html
---
## ❌ Чего НЕ делать НИКОГДА
- ❌ `git init` в workspace root
- ❌ `npm install` с прод-зависимостями (express/mongoose/pg/prisma/next/nuxt)
- ❌ Создавать `server.js` / `app.py` / `main.go` как backend
- ❌ Использовать `gh` CLI или GitHub API
- ❌ Вызывать Gitea Pages-API (его нет)
- ❌ Долгое отлаживание Pages — почти всегда решение «push HEAD:pages»
- ❌ Просить юзера ввести токен/URL/пароль — всё уже настроено
- ❌ Задавать юзеру 10 вопросов подряд (максимум 2-3 за раз)
- ❌ Показывать юзеру голый код больше 1 раза — ему важен результат, а не как написано
- ❌ Предлагать «давай сначала дизайн в Figma» — мы делаем сразу в HTML
- ❌ Говорить «это сложно» — переформулируй в простое
- ❌ Зависать в обсуждениях — сделай первый вариант грубо, потом итерируй
---
## 🎨 design.md
Рядом лежит `design.md` с готовой палитрой, типографикой и стартер-шаблоном `index.html`. **Начинай с него.** Не выдумывай новые цвета — модифицируй существующие.

860
checklist.html Normal file
View File

@ -0,0 +1,860 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Чек-лист проверки | Цифровой инспектор БиОТ</title>
<style>
:root {
--ink: #0F1218;
--cyan: #00E5FF;
--white: #fff;
--gray-500: #5B6573;
--gray-100: #F2F4F7;
--gray-800: #1A1D25;
--gray-700: #252833;
--gray-600: #3A3E4A;
--red: #FF4D4D;
--green: #00C853;
--amber: #FFB300;
--red-bg: rgba(255,77,77,0.08);
--green-bg: rgba(0,200,83,0.08);
--amber-bg: rgba(255,179,0,0.08);
}
*{box-sizing:border-box;margin:0;padding:0}
body {
font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
color: var(--ink);
background: #f0f2f5;
-webkit-font-smoothing: antialiased;
}
/* ===== TOP BAR ===== */
.topbar {
background: var(--ink);
color: var(--white);
padding: 12px 20px;
display: flex;
align-items: center;
gap: 12px;
position: sticky;
top: 0;
z-index: 100;
}
.topbar .back {
color: var(--cyan);
text-decoration: none;
font-size: 20px;
line-height: 1;
}
.topbar .title { font-weight: 700; font-size: 16px; flex: 1 }
.topbar .save {
color: var(--cyan);
font-size: 14px;
font-weight: 600;
text-decoration: none;
}
/* ===== HEADER CARD ===== */
.header-card {
background: var(--white);
margin: 16px 16px 0;
border-radius: 14px;
padding: 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.header-card .field {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 0;
border-bottom: 1px solid #f0f2f5;
}
.header-card .field:last-child { border-bottom: none }
.header-card .field .label {
font-size: 13px;
color: var(--gray-500);
width: 110px;
flex-shrink: 0;
}
.header-card .field .value {
font-size: 15px;
font-weight: 600;
flex: 1;
}
.header-card .field select, .header-card .field input {
flex: 1;
border: 1px solid #e0e3e8;
border-radius: 8px;
padding: 8px 12px;
font-size: 14px;
font-family: inherit;
background: #fafbfc;
color: var(--ink);
}
.progress-bar {
margin: 16px 16px 0;
background: var(--white);
border-radius: 14px;
padding: 14px 20px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
display: flex;
align-items: center;
gap: 12px;
}
.progress-bar .track {
flex: 1;
height: 6px;
background: #e5e7eb;
border-radius: 3px;
overflow: hidden;
}
.progress-bar .fill {
height: 100%;
background: var(--cyan);
border-radius: 3px;
transition: width 0.3s;
}
.progress-bar .pct {
font-size: 13px;
font-weight: 700;
color: var(--cyan);
min-width: 45px;
text-align: right;
}
/* ===== TABS ===== */
.tabs {
display: flex;
margin: 16px 16px 0;
background: var(--white);
border-radius: 14px;
padding: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
overflow-x: auto;
gap: 2px;
}
.tab {
flex: 1;
min-width: 90px;
padding: 10px 8px;
border: none;
border-radius: 11px;
background: transparent;
font-size: 12px;
font-weight: 600;
color: var(--gray-500);
cursor: pointer;
white-space: nowrap;
transition: all 0.15s;
font-family: inherit;
text-align: center;
}
.tab.active {
background: var(--ink);
color: var(--white);
}
.tab .tab-emoji { display: block; font-size: 18px; margin-bottom: 2px }
/* ===== CHECKLIST ===== */
.checklist {
margin: 12px 16px 80px;
display: none;
}
.checklist.active { display: block }
.check-item {
background: var(--white);
border-radius: 12px;
padding: 16px;
margin-bottom: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
transition: box-shadow 0.15s;
}
.check-item.selected { box-shadow: 0 0 0 2px var(--cyan); }
.check-item.has-violation { box-shadow: 0 0 0 1px rgba(255,77,77,0.3); }
.check-item .item-header {
display: flex;
align-items: flex-start;
gap: 10px;
}
.check-item .item-num {
width: 26px;
height: 26px;
background: #f0f2f5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
color: var(--gray-500);
flex-shrink: 0;
margin-top: 1px;
}
.check-item .item-text { flex: 1; font-size: 14px; font-weight: 500 }
.check-item .item-note {
margin-top: 4px;
font-size: 12px;
color: var(--gray-500);
}
/* Status buttons */
.status-row {
display: flex;
gap: 8px;
margin-top: 12px;
}
.status-btn {
flex: 1;
padding: 10px 8px;
border: 2px solid #e5e7eb;
border-radius: 10px;
background: var(--white);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
font-family: inherit;
text-align: center;
}
.status-btn:hover { border-color: #d1d5db }
.status-btn.pass.active {
border-color: var(--green);
background: var(--green-bg);
color: var(--green);
}
.status-btn.fail.active {
border-color: var(--red);
background: var(--red-bg);
color: var(--red);
}
.status-btn.na.active {
border-color: var(--gray-500);
background: #f0f2f5;
color: var(--gray-500);
}
/* Violation form */
.violation-form {
display: none;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f0f2f5;
}
.violation-form.show { display: block }
.violation-form .form-row {
margin-bottom: 10px;
}
.violation-form label {
display: block;
font-size: 12px;
font-weight: 600;
color: var(--gray-500);
margin-bottom: 4px;
}
.violation-form textarea, .violation-form input, .violation-form select {
width: 100%;
border: 1px solid #e0e3e8;
border-radius: 8px;
padding: 8px 12px;
font-size: 14px;
font-family: inherit;
background: #fafbfc;
color: var(--ink);
resize: vertical;
}
.violation-form textarea:focus, .violation-form input:focus, .violation-form select:focus {
outline: none;
border-color: var(--cyan);
}
.violation-form .photo-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px dashed #d1d5db;
border-radius: 8px;
background: #fafbfc;
font-size: 13px;
color: var(--gray-500);
cursor: pointer;
font-family: inherit;
}
.violation-form .auto-fill-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid rgba(0,229,255,0.3);
border-radius: 8px;
background: rgba(0,229,255,0.06);
font-size: 13px;
font-weight: 600;
color: var(--cyan);
cursor: pointer;
font-family: inherit;
margin-top: 8px;
}
.risk-options { display: flex; gap: 8px }
.risk-opt {
flex: 1;
padding: 8px;
border: 2px solid #e5e7eb;
border-radius: 8px;
text-align: center;
cursor: pointer;
font-size: 12px;
font-weight: 600;
font-family: inherit;
background: var(--white);
transition: all 0.15s;
}
.risk-opt.low.active { border-color: var(--green); background: var(--green-bg); color: var(--green) }
.risk-opt.mid.active { border-color: var(--amber); background: var(--amber-bg); color: var(--amber) }
.risk-opt.high.active { border-color: var(--red); background: var(--red-bg); color: var(--red) }
/* ===== BOTTOM BAR ===== */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--white);
border-top: 1px solid #e5e7eb;
padding: 12px 20px;
display: flex;
gap: 10px;
z-index: 100;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
}
.bottom-bar .btn {
flex: 1;
padding: 14px;
border: none;
border-radius: 10px;
font-weight: 700;
font-size: 15px;
cursor: pointer;
font-family: inherit;
transition: all 0.15s;
}
.bottom-bar .btn-preview {
background: var(--white);
border: 2px solid var(--ink);
color: var(--ink);
}
.bottom-bar .btn-generate {
background: var(--ink);
color: var(--white);
}
.bottom-bar .btn-generate:hover { background: #2a2d35 }
.bottom-bar .btn-preview:hover { background: #f0f2f5 }
/* ===== MODAL ===== */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.6);
z-index: 200;
align-items: center;
justify-content: center;
padding: 20px;
}
.modal-overlay.show { display: flex }
.modal {
background: var(--white);
border-radius: 16px;
padding: 28px;
max-width: 700px;
width: 100%;
max-height: 85vh;
overflow-y: auto;
}
.modal h3 { font-size: 20px; font-weight: 700; margin-bottom: 6px }
.modal .modal-meta { font-size: 13px; color: var(--gray-500); margin-bottom: 20px }
.modal table { width: 100%; border-collapse: collapse; font-size: 13px }
.modal th {
background: #f3f4f6;
padding: 8px 10px;
text-align: left;
font-weight: 600;
font-size: 11px;
color: var(--gray-500);
text-transform: uppercase;
}
.modal td {
padding: 8px 10px;
border-bottom: 1px solid #f3f4f6;
}
.modal .close-btn {
margin-top: 20px;
width: 100%;
padding: 12px;
background: #f0f2f5;
border: none;
border-radius: 10px;
font-weight: 600;
font-size: 15px;
cursor: pointer;
font-family: inherit;
}
.risk-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 4px; vertical-align: middle }
.risk-dot.red { background: var(--red) }
.risk-dot.amber { background: var(--amber) }
.modal .empty-msg { text-align: center; color: var(--gray-500); padding: 40px 0; font-size: 15px }
@media (min-width: 768px) {
.checklist-container { max-width: 768px; margin: 0 auto }
}
.toast {
position: fixed;
top: 80px;
left: 50%;
transform: translateX(-50%);
background: var(--ink);
color: var(--white);
padding: 12px 24px;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
z-index: 300;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.toast.show { opacity: 1 }
</style>
</head>
<body>
<div class="topbar">
<a href="index.html" class="back">+</a>
<span class="title">Новая проверка</span>
<a href="#" class="save" onclick="saveChecklist()">Сохранить</a>
</div>
<div style="max-width:768px; margin:0 auto">
<!-- Header -->
<div class="header-card">
<div class="field">
<span class="label">Объект</span>
<select><option>Цех №3</option><option>Складской комплекс</option><option>Административный корпус</option><option>Строительная площадка</option></select>
</div>
<div class="field">
<span class="label">Подразделение</span>
<select><option>Производственный участок</option><option>Ремонтная служба</option><option>Энергоцех</option><option>Транспортный цех</option></select>
</div>
<div class="field">
<span class="label">Ответственный</span>
<input type="text" placeholder="ФИО ответственного лица">
</div>
<div class="field">
<span class="label">Направление</span>
<select id="mainDirection"><option>Комплексная проверка</option><option>Охрана труда и ТБ</option><option>Пожарная безопасность</option><option>Электробезопасность</option><option>Транспортная безопасность</option><option>Охрана здоровья</option><option>Выездная проверка</option></select>
</div>
<div class="field">
<span class="label">Дата проверки</span>
<input type="date" value="2026-06-03">
</div>
</div>
<!-- Progress -->
<div class="progress-bar">
<span style="font-size:12px;color:var(--gray-500);white-space:nowrap">Пройдено:</span>
<div class="track"><div class="fill" id="progressFill" style="width:0%"></div></div>
<span class="pct" id="progressPct">0%</span>
</div>
<!-- Tabs -->
<div class="tabs" id="tabBar">
<button class="tab active" data-tab="1"><span class="tab-emoji">&#9888;</span>ОТ и ТБ</button>
<button class="tab" data-tab="2"><span class="tab-emoji">&#128293;</span>Пожарная</button>
<button class="tab" data-tab="3"><span class="tab-emoji">&#9889;</span>Электро</button>
<button class="tab" data-tab="4"><span class="tab-emoji">&#128739;</span>Транспорт</button>
<button class="tab" data-tab="5"><span class="tab-emoji">&#10084;</span>Здоровье</button>
<button class="tab" data-tab="6"><span class="tab-emoji">&#128506;</span>Выездные</button>
</div>
<!-- Checklist sections -->
<div id="checklistContainer"></div>
</div>
<!-- Bottom bar -->
<div class="bottom-bar">
<button class="btn btn-preview" onclick="showPreview()">Предпросмотр</button>
<button class="btn btn-generate" onclick="generateOrder()">Сформировать указание</button>
</div>
<!-- Modal -->
<div class="modal-overlay" id="modalOverlay" onclick="if(event.target===this)closeModal()">
<div class="modal" id="modalContent"></div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<script>
// ===== CHECKLIST DATA =====
const checklistSections = {
1: {
title: 'Охрана труда и ТБ',
items: [
{id:'1.1', text:'Наличие защитных ограждений на вращающихся и движущихся механизмах', note:'Пункт 1.2 Правил ОТ'},
{id:'1.2', text:'Состояние проходов, проездов и эвакуационных путей', note:'Свободны, не загромождены'},
{id:'1.3', text:'Наличие знаков безопасности и сигнальной разметки', note:'ГОСТ 12.4.026'},
{id:'1.4', text:'Применение средств индивидуальной защиты работниками', note:'Каски, очки, перчатки, спецобувь'},
{id:'1.5', text:'Наличие ограждений при работе на высоте (выше 1.3м)', note:'Страховочные системы, перила'},
{id:'1.6', text:'Состояние лестниц, подмостей и средств подмащивания', note:'Исправность, бирки, даты испытаний'},
{id:'1.7', text:'Исправность и своевременное освидетельствование грузоподъемных механизмов', note:'Краны, тельферы, стропы'},
{id:'1.8', text:'Наличие нарядов-допусков на огневые, газоопасные и высотные работы', note:'Оформлены, подписаны, сроки'},
{id:'1.9', text:'Проведение инструктажей (вводный, первичный, повторный, внеплановый)', note:'Записи в журналах, подписи'},
{id:'1.10', text:'Наличие и ведение журналов по охране труда', note:'Журнал инструктажа, журнал выдачи СИЗ, журнал НС'}
]
},
2: {
title: 'Пожарная безопасность',
items: [
{id:'2.1', text:'Наличие и состояние первичных средств пожаротушения (огнетушители)', note:'Не просрочены, опломбированы, доступны'},
{id:'2.2', text:'Доступность пожарных гидрантов и рукавов', note:'Не загромождены, укомплектованы'},
{id:'2.3', text:'Наличие планов эвакуации и указателей выходов', note:'Актуальные, освещённые, на видных местах'},
{id:'2.4', text:'Состояние эвакуационных выходов и путей', note:'Не заперты, свободны, освещены'},
{id:'2.5', text:'Исправность автоматической пожарной сигнализации и оповещения', note:'Датчики дыма, сирены, проверки'},
{id:'2.6', text:'Проведение противопожарных инструктажей', note:'Записи в журнале, подписи, периодичность'},
{id:'2.7', text:'Состояние электропроводки и электрооборудования', note:'Отсутствие скруток, повреждений изоляции'},
{id:'2.8', text:'Наличие мест для курения, оборудованных по нормам', note:'Урны, знаки, удалённость от строений'}
]
},
3: {
title: 'Электробезопасность',
items: [
{id:'3.1', text:'Наличие защитного заземления и зануления оборудования', note:'Визуальная целостность, протоколы замеров'},
{id:'3.2', text:'Состояние распределительных щитов и шкафов', note:'Закрыты, промаркированы, чистота'},
{id:'3.3', text:'Наличие предупреждающих знаков и плакатов на электроустановках', note:'«Осторожно! Электрическое напряжение»'},
{id:'3.4', text:'Наличие и сроки испытания диэлектрических средств защиты', note:'Перчатки, боты, коврики, штампы'},
{id:'3.5', text:'Наличие у персонала группы допуска по электробезопасности', note:'Удостоверения, сроки, соответствие работам'},
{id:'3.6', text:'Состояние изоляции кабелей и проводов', note:'Отсутствие повреждений, провисаний, скруток'},
{id:'3.7', text:'Наличие однолинейных схем электроснабжения', note:'Актуальные, на рабочих местах'}
]
},
4: {
title: 'Транспортная безопасность',
items: [
{id:'4.1', text:'Проведение предрейсовых медицинских осмотров водителей', note:'Журнал, подписи, штампы'},
{id:'4.2', text:'Техническое состояние транспортных средств перед выездом', note:'Тормоза, рулевое, фары, шины'},
{id:'4.3', text:'Наличие и оформление путевых листов', note:'Заполнены, отметки механика и медика'},
{id:'4.4', text:'Наличие допусков на управление спецтехникой', note:'Удостоверения, категории, сроки'},
{id:'4.5', text:'Состояние грузозахватных приспособлений на транспорте', note:'Стропы, цепи, крюки — бирки, испытания'},
{id:'4.6', text:'Проведение инструктажей водителей и механизаторов', note:'Записи, подписи, периодичность'}
]
},
5: {
title: 'Охрана здоровья',
items: [
{id:'5.1', text:'Проведение периодических медицинских осмотров работников', note:'График, заключения, допуски'},
{id:'5.2', text:'Наличие и укомплектованность аптечек первой помощи', note:'Сроки годности, опись, доступность'},
{id:'5.3', text:'Санитарное состояние производственных и бытовых помещений', note:'Чистота, уборка, дезинфекция'},
{id:'5.4', text:'Наличие питьевой воды и условия для приёма пищи', note:'Куллеры, столовая/комната приёма пищи'},
{id:'5.5', text:'Состояние систем вентиляции и кондиционирования', note:'Работают, чистые, проверки'},
{id:'5.6', text:'Освещённость рабочих мест', note:'Нормы, исправность светильников, замеры'},
{id:'5.7', text:'Параметры микроклимата на рабочих местах', note:'Температура, влажность, сквозняки'}
]
},
6: {
title: 'Выездные проверки',
items: [
{id:'6.1', text:'Состояние мест производства работ на выездном объекте', note:'Ограждения, порядок, безопасность'},
{id:'6.2', text:'Наличие ограждений опасных зон на выездных объектах', note:'Котлованы, проёмы, высотные участки'},
{id:'6.3', text:'Безопасность складирования материалов и конструкций', note:'Устойчивость, проходы, высота штабелей'},
{id:'6.4', text:'Наличие первичных средств пожаротушения на выездном объекте', note:'Огнетушители, ящики с песком'},
{id:'6.5', text:'Соблюдение технологии и проекта производства работ', note:'ППР на месте, соответствие выполняемых работ'},
{id:'6.6', text:'Фиксация GPS-координат места проверки', note:'Широта/долгота, фото объекта'}
]
}
};
// ===== STATE =====
const state = {};
Object.keys(checklistSections).forEach(sec => {
checklistSections[sec].items.forEach(item => {
state[item.id] = { status: null, description:'', photo:'', risk:'', deadline:'', requirement:'', measure:'' };
});
});
let currentTab = 1;
// ===== RENDER =====
function renderChecklist(sectionId) {
const sec = checklistSections[sectionId];
let html = '';
sec.items.forEach((item, idx) => {
const st = state[item.id];
const hasViolation = st.status === 'fail';
const selected = st.status !== null;
html += `
<div class="check-item ${selected ? 'selected' : ''} ${hasViolation ? 'has-violation' : ''}" id="item-${item.id}">
<div class="item-header">
<div class="item-num">${idx + 1}</div>
<div>
<div class="item-text">${item.text}</div>
${item.note ? `<div class="item-note">${item.note}</div>` : ''}
</div>
</div>
<div class="status-row">
<button class="status-btn pass ${st.status==='pass'?'active':''}" onclick="setStatus('${item.id}','pass')">Соответствует</button>
<button class="status-btn fail ${st.status==='fail'?'active':''}" onclick="setStatus('${item.id}','fail')">Не соответствует</button>
<button class="status-btn na ${st.status==='na'?'active':''}" onclick="setStatus('${item.id}','na')">Не применяется</button>
</div>
<div class="violation-form ${hasViolation ? 'show' : ''}" id="form-${item.id}">
<div class="form-row">
<label>Описание нарушения</label>
<textarea rows="2" placeholder="Опишите нарушение..." onchange="updateField('${item.id}','description',this.value)">${st.description}</textarea>
</div>
<div class="form-row">
<label>Фото нарушения</label>
<button class="photo-btn" onclick="alert('В боевой версии: камера / галерея')">+ Прикрепить фото</button>
</div>
<div class="form-row">
<label>Категория риска</label>
<div class="risk-options">
<div class="risk-opt low ${st.risk==='low'?'active':''}" onclick="setRisk('${item.id}','low',this)">Низкий</div>
<div class="risk-opt mid ${st.risk==='mid'?'active':''}" onclick="setRisk('${item.id}','mid',this)">Средний</div>
<div class="risk-opt high ${st.risk==='high'?'active':''}" onclick="setRisk('${item.id}','high',this)">Высокий</div>
</div>
</div>
<div class="form-row">
<label>Срок устранения</label>
<input type="date" value="${st.deadline}" onchange="updateField('${item.id}','deadline',this.value)">
</div>
<div class="form-row">
<label>Ответственное лицо</label>
<input type="text" placeholder="Должность и ФИО" value="${st.measure}" onchange="updateField('${item.id}','measure',this.value)">
</div>
<button class="auto-fill-btn" onclick="autoFillViolation('${item.id}','${item.text}')">+ ИИ: автозаполнение</button>
</div>
</div>`;
});
document.getElementById('checklistContainer').innerHTML = html;
}
function setStatus(id, status) {
if (state[id].status === status) {
state[id].status = null;
} else {
state[id].status = status;
}
if (status !== 'fail') {
state[id].description = '';
state[id].risk = '';
state[id].deadline = '';
state[id].measure = '';
}
renderChecklist(currentTab);
updateProgress();
}
function setRisk(id, risk, el) {
state[id].risk = risk;
renderChecklist(currentTab);
}
function updateField(id, field, val) {
state[id][field] = val;
}
function autoFillViolation(id, itemText) {
const autoData = getAutoViolation(itemText);
state[id].description = autoData.description;
state[id].requirement = autoData.requirement;
state[id].measure = autoData.measure;
state[id].risk = autoData.risk;
state[id].deadline = autoData.deadline;
renderChecklist(currentTab);
showToast('+ ИИ: запись сформирована');
}
function getAutoViolation(text) {
// Simulated AI auto-fill based on checklist item
const map = {
'ограждений': { description:'Отсутствует защитное ограждение вращающихся/движущихся механизмов', requirement:'Установить защитное ограждение согласно требованиям правил ОТ', measure:'Начальник участка', risk:'high', deadline:'2026-06-15' },
'СИЗ': { description:'Работники не применяют средства индивидуальной защиты', requirement:'Обеспечить применение СИЗ согласно нормам выдачи', measure:'Мастер участка', risk:'mid', deadline:'2026-06-10' },
'высоте': { description:'Работы на высоте выполняются без страховочной системы', requirement:'Выполнять работы на высоте только с применением страховочной привязи', measure:'Начальник участка', risk:'high', deadline:'2026-06-12' },
'огнетушител': { description:'Огнетушитель не прошёл своевременную перезарядку', requirement:'Выполнить перезарядку огнетушителя', measure:'Руководитель объекта', risk:'mid', deadline:'2026-06-10' },
'эвакуац': { description:'Эвакуационные выходы загромождены / заперты', requirement:'Обеспечить свободный доступ к эвакуационным выходам', measure:'Руководитель объекта', risk:'high', deadline:'2026-06-08' },
'заземл': { description:'Отсутствует или нарушено защитное заземление оборудования', requirement:'Восстановить защитное заземление согласно ПУЭ', measure:'Главный энергетик', risk:'high', deadline:'2026-06-14' },
'проводк': { description:'Выявлены повреждения изоляции электропроводки', requirement:'Заменить повреждённые участки электропроводки', measure:'Электрик участка', risk:'high', deadline:'2026-06-11' },
'медосмотр': { description:'У работников отсутствуют отметки о прохождении медосмотра', requirement:'Организовать прохождение медицинского осмотра', measure:'Специалист по ОТ', risk:'mid', deadline:'2026-06-20' },
'путев': { description:'Путевые листы оформлены с нарушениями / отсутствуют', requirement:'Обеспечить правильное оформление путевых листов', measure:'Механик гаража', risk:'low', deadline:'2026-06-09' },
'инструктаж': { description:'Пропущен срок проведения инструктажа', requirement:'Провести внеплановый инструктаж с записью в журнале', measure:'Мастер участка', risk:'mid', deadline:'2026-06-07' },
'знаков': { description:'Отсутствуют знаки безопасности в установленных местах', requirement:'Установить знаки безопасности согласно ГОСТ', measure:'Начальник участка', risk:'mid', deadline:'2026-06-17' },
'аптеч': { description:'Аптечка не укомплектована / просрочены медикаменты', requirement:'Укомплектовать аптечку согласно нормам', measure:'Специалист по ОТ', risk:'low', deadline:'2026-06-10' },
'вентиляц': { description:'Система вентиляции не обеспечивает нормативный воздухообмен', requirement:'Провести ремонт/очистку системы вентиляции', measure:'Главный механик', risk:'mid', deadline:'2026-06-21' },
'GPS': { description:'Не зафиксированы GPS-координаты места проверки', requirement:'Включить геолокацию и зафиксировать координаты', measure:'Инспектор', risk:'low', deadline:'2026-06-04' }
};
for (const [key, val] of Object.entries(map)) {
if (text.toLowerCase().includes(key.toLowerCase())) return val;
}
return { description:'Нарушение требований безопасности', requirement:'Устранить нарушение в соответствии с нормативными требованиями', measure:'Ответственный руководитель', risk:'mid', deadline:'2026-06-17' };
}
// ===== TAB SWITCHING =====
document.getElementById('tabBar').addEventListener('click', function(e) {
const tab = e.target.closest('.tab');
if (!tab) return;
currentTab = parseInt(tab.dataset.tab);
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
renderChecklist(currentTab);
});
// ===== PROGRESS =====
function updateProgress() {
let total = 0;
let checked = 0;
Object.keys(checklistSections).forEach(sec => {
checklistSections[sec].items.forEach(item => {
total++;
if (state[item.id].status !== null) checked++;
});
});
const pct = total > 0 ? Math.round(checked / total * 100) : 0;
document.getElementById('progressFill').style.width = pct + '%';
document.getElementById('progressPct').textContent = pct + '%';
}
// ===== GET VIOLATIONS =====
function getViolations() {
const violations = [];
Object.keys(checklistSections).forEach(sec => {
checklistSections[sec].items.forEach(item => {
const st = state[item.id];
if (st.status === 'fail') {
const riskLabel = {low:'Низкий', mid:'Средний', high:'Высокий'};
violations.push({
id: item.id,
num: violations.length + 1,
section: checklistSections[sec].title,
text: item.text,
description: st.description || item.text,
risk: st.risk || 'mid',
riskLabel: riskLabel[st.risk] || 'Средний',
deadline: st.deadline || '—',
responsible: st.measure || '—',
measure: (st.description || item.text).length > 80 ? (st.description || item.text) : 'Устранить нарушение согласно нормативным требованиям'
});
}
});
});
return violations;
}
// ===== PREVIEW =====
function showPreview() {
const violations = getViolations();
const objName = document.querySelector('.header-card select').value;
const dept = document.querySelectorAll('.header-card select')[1].value;
const date = document.querySelector('.header-card input[type="date"]').value;
let html = `<h3>Предпросмотр нарушений</h3>`;
html += `<div class="modal-meta">Объект: ${objName} &middot; Подразделение: ${dept} &middot; Дата: ${date}</div>`;
if (violations.length === 0) {
html += `<div class="empty-msg">Нарушений не выявлено</div>`;
} else {
html += `<table>
<tr><th></th><th>Нарушение</th><th>Риск</th><th>Срок</th><th>Ответственный</th></tr>`;
violations.forEach(v => {
html += `<tr>
<td>${v.num}</td>
<td>${v.description.length > 80 ? v.description.substring(0,80)+'...' : v.description}</td>
<td><span class="risk-dot ${v.risk==='high'?'red':v.risk==='mid'?'amber':''}"></span>${v.riskLabel}</td>
<td>${v.deadline}</td>
<td>${v.responsible}</td>
</tr>`;
});
html += `</table>`;
}
html += `<button class="close-btn" onclick="closeModal()">Закрыть</button>`;
document.getElementById('modalContent').innerHTML = html;
document.getElementById('modalOverlay').classList.add('show');
}
function generateOrder() {
const violations = getViolations();
if (violations.length === 0) {
showToast('+ Нет нарушений для формирования указания');
return;
}
const objName = document.querySelector('.header-card select').value;
const dept = document.querySelectorAll('.header-card select')[1].value;
const date = document.querySelector('.header-card input[type="date"]').value;
const responsible = document.querySelector('.header-card input[type="text"]').value || '—';
let html = `<h3>Указание по безопасности и охране труда</h3>`;
html += `<div class="modal-meta">
Номер: ${new Date().getTime().toString().slice(-6)} &middot;
Объект: ${objName} &middot;
Подразделение: ${dept} &middot;
Дата: ${date} &middot;
Проверяющий: ${responsible}
</div>`;
html += `<table>
<tr><th></th><th>Выявленное нарушение</th><th>Корректирующее мероприятие</th><th>Ответственный</th><th>Срок</th></tr>`;
violations.forEach(v => {
html += `<tr>
<td>${v.num}</td>
<td>${v.description}</td>
<td>${v.measure}</td>
<td>${v.responsible}</td>
<td>${v.deadline}</td>
</tr>`;
});
html += `</table>
<div style="display:flex;justify-content:space-between;margin-top:16px;padding-top:12px;border-top:1px solid #e5e7eb;font-size:13px;color:var(--gray-500)">
<span>Подпись проверяющего: ____________</span>
<span>Подпись руководителя: ____________</span>
</div>
<div style="text-align:right;margin-top:12px;font-size:11px;color:var(--gray-500)">
<span style="background:var(--ink);color:var(--white);padding:4px 8px;border-radius:4px;font-family:monospace">QR-код</span>
&nbsp;Проверка подлинности
</div>`;
html += `<button class="close-btn" onclick="closeModal()">Закрыть</button>`;
html += `<button class="btn" style="width:100%;margin-top:8px;background:var(--cyan);color:var(--ink);border:none;padding:12px;border-radius:10px;font-weight:700;cursor:pointer;font-family:inherit" onclick="alert('В боевой версии: экспорт в PDF / Word')">Скачать PDF</button>`;
document.getElementById('modalContent').innerHTML = html;
document.getElementById('modalOverlay').classList.add('show');
}
function closeModal() {
document.getElementById('modalOverlay').classList.remove('show');
}
function saveChecklist() {
showToast('+ Чек-лист сохранён');
}
function showToast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2000);
}
// ===== INIT =====
renderChecklist(1);
updateProgress();
</script>
</body>
</html>

110
design.md Normal file
View File

@ -0,0 +1,110 @@
<!-- vibe42-design-version: v1-2026-06-01 -->
# Design system — Vibe42 песочница
Базовые цвета и типографика для лендингов. Можно отклоняться, но начинай с этого.
## Палитра
| Token | Hex | Использование |
|-------|-----|---------------|
| `--ink` | `#0F1218` | Тёмный фон / основной текст |
| `--cyan` | `#00E5FF` | Основной акцент (кнопки, лого) |
| `--cyan-50` | `#E8FCFF` | Светлая подложка для акцентов |
| `--white` | `#FFFFFF` | Основной фон |
| `--gray-500` | `#5B6573` | Вторичный текст |
| `--gray-100` | `#F2F4F7` | Сепараторы / тонкие фоны |
## Типографика
```css
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
```
| Уровень | Размер | Вес | line-height |
|---------|--------|-----|-------------|
| h1 (hero) | 56px | 800 | 1.05 |
| h2 (section) | 36px | 700 | 1.15 |
| h3 | 22px | 700 | 1.3 |
| body | 17px | 400 | 1.6 |
| small | 14px | 400 | 1.5 |
На мобиле — h1 уменьши до 36px, h2 до 28px.
## Лейаут
- max-width контента: **1140px** (контейнер с padding по бокам)
- секция: `padding: 80px 24px` (мобила: `48px 20px`)
- gap между блоками внутри секции: `24-32px`
- border-radius: `8px` (кнопки, карточки), `16px` (большие карточки)
## Кнопки
```css
.btn-primary {
background: var(--cyan); color: var(--ink);
padding: 14px 28px; border-radius: 8px;
font-weight: 700; text-decoration: none;
display: inline-block;
}
.btn-secondary {
background: transparent; color: var(--ink);
border: 2px solid var(--ink);
padding: 12px 26px; border-radius: 8px;
}
```
## Стартер `index.html`
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Мой проект</title>
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7}
*{box-sizing:border-box;margin:0;padding:0}
body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--white)}
.container{max-width:1140px;margin:0 auto;padding:80px 24px}
.hero{background:var(--ink);color:var(--white)}
.hero h1{font-size:56px;font-weight:800;line-height:1.05;margin-bottom:24px}
.hero p{font-size:20px;color:#9aa3b2;max-width:600px;margin-bottom:32px}
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none}
.btn:hover{background:#1be5ff}
.section h2{font-size:36px;font-weight:700;margin-bottom:24px}
.card{background:var(--gray-100);border-radius:16px;padding:32px;margin-bottom:16px}
@media (max-width:640px){.hero h1{font-size:36px}.section h2{font-size:28px}.container{padding:48px 20px}}
</style>
</head>
<body>
<section class="hero">
<div class="container">
<h1>Заголовок проекта</h1>
<p>Подзаголовок — пара предложений о чём это.</p>
<a class="btn" href="#section">Начать</a>
</div>
</section>
<section id="section" class="section">
<div class="container">
<h2>Секция</h2>
<div class="card">Контент карточки.</div>
<div class="card">Контент карточки.</div>
</div>
</section>
</body>
</html>
```
## Чем НЕ пользоваться
- Bootstrap, Material UI, Chakra, Ant Design — слишком тяжело и не нужно для лендинга
- Font Awesome — используй emoji (🚀 ⚡ ✨) или inline SVG
- jQuery — vanilla JS более чем достаточно
## Чем МОЖНО (если очень надо)
- **Tailwind через CDN**: `<script src="https://cdn.tailwindcss.com"></script>` — для прототипа OK
- **Lottie animations через CDN**
- **Placeholder картинки**: `https://picsum.photos/800/600`, `https://placehold.co/600x400`
- **Шрифты Google Fonts через `<link>`** в head

876
index.html Normal file
View File

@ -0,0 +1,876 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Цифровой инспектор БиОТ — автоматизация проверок безопасности и охраны труда</title>
<style>
:root {
--ink: #0F1218;
--cyan: #00E5FF;
--cyan-50: #E8FCFF;
--white: #fff;
--gray-500: #5B6573;
--gray-100: #F2F4F7;
--gray-800: #1A1D25;
--gray-700: #252833;
--gray-600: #3A3E4A;
--red: #FF4D4D;
--green: #00C853;
--amber: #FFB300;
}
*{box-sizing:border-box;margin:0;padding:0}
body {
font: 17px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
color: var(--white);
background: var(--ink);
-webkit-font-smoothing: antialiased;
}
.container { max-width: 1140px; margin: 0 auto; padding: 0 24px }
/* ===== Hero ===== */
.hero {
padding: 100px 0 80px;
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: -40%;
right: -15%;
width: 600px;
height: 600px;
background: radial-gradient(circle, rgba(0,229,255,0.06) 0%, transparent 70%);
pointer-events: none;
}
.hero .container { position: relative; z-index: 1 }
.hero .badge {
display: inline-block;
background: rgba(0,229,255,0.1);
border: 1px solid rgba(0,229,255,0.2);
color: var(--cyan);
padding: 6px 16px;
border-radius: 100px;
font-size: 14px;
font-weight: 600;
margin-bottom: 24px;
}
.hero h1 {
font-size: 56px;
font-weight: 800;
line-height: 1.05;
margin-bottom: 24px;
letter-spacing: -0.02em;
}
.hero h1 span { color: var(--cyan) }
.hero p {
font-size: 20px;
color: #9aa3b2;
max-width: 620px;
margin-bottom: 36px;
}
.highlight-box {
display: inline-flex;
align-items: center;
gap: 12px;
background: rgba(0,229,255,0.05);
border: 1px solid rgba(0,229,255,0.15);
border-radius: 12px;
padding: 12px 20px;
margin-bottom: 36px;
font-size: 15px;
color: #d1d5db;
}
.highlight-box .timer {
font-weight: 800;
font-size: 20px;
color: var(--cyan);
white-space: nowrap;
}
.hero .cta-row {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 48px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--cyan);
color: var(--ink);
padding: 14px 28px;
border-radius: 8px;
font-weight: 700;
font-size: 16px;
text-decoration: none;
border: none;
cursor: pointer;
transition: background 0.2s, transform 0.15s;
}
.btn:hover { background: #1be5ff; transform: translateY(-1px) }
.btn-outline {
background: transparent;
color: var(--cyan);
border: 2px solid rgba(0,229,255,0.3);
padding: 12px 26px;
border-radius: 8px;
font-weight: 700;
font-size: 16px;
text-decoration: none;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
}
.btn-outline:hover { border-color: var(--cyan); background: rgba(0,229,255,0.05) }
.hero-stats {
display: flex;
gap: 40px;
padding-top: 32px;
border-top: 1px solid rgba(255,255,255,0.08);
flex-wrap: wrap;
}
.hero-stats .stat .num {
font-size: 28px;
font-weight: 800;
color: var(--cyan);
line-height: 1;
}
.hero-stats .stat .label {
font-size: 13px;
color: #6b7280;
margin-top: 4px;
}
/* ===== Sections ===== */
.section { padding: 80px 0 }
.section-label {
display: inline-block;
font-size: 13px;
font-weight: 700;
color: var(--cyan);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 12px;
}
.section h2 {
font-size: 36px;
font-weight: 700;
line-height: 1.15;
margin-bottom: 16px;
}
.section .subtitle {
color: #9aa3b2;
font-size: 18px;
max-width: 600px;
margin-bottom: 48px;
}
/* ===== Direction cards ===== */
.directions { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px }
.dir-card {
background: var(--gray-800);
border: 1px solid rgba(255,255,255,0.05);
border-radius: 14px;
padding: 28px;
display: flex;
gap: 16px;
transition: border-color 0.2s;
}
.dir-card:hover { border-color: rgba(0,229,255,0.2) }
.dir-card .dir-icon {
width: 48px; height: 48px;
background: rgba(0,229,255,0.1);
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 22px;
flex-shrink: 0;
}
.dir-card h3 { font-size: 17px; font-weight: 700; margin-bottom: 4px }
.dir-card p { color: #6b7280; font-size: 14px }
/* ===== Process steps ===== */
.big-steps { display: flex; flex-direction: column; gap: 24px }
.big-step {
display: grid;
grid-template-columns: auto 1fr;
gap: 24px;
background: var(--gray-800);
border: 1px solid rgba(255,255,255,0.05);
border-radius: 16px;
padding: 32px;
transition: border-color 0.2s;
}
.big-step:hover { border-color: rgba(0,229,255,0.15) }
.big-step .step-num {
width: 44px; height: 44px;
background: var(--cyan);
color: var(--ink);
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-weight: 800;
font-size: 18px;
flex-shrink: 0;
}
.big-step .step-body h3 { font-size: 19px; font-weight: 700; margin-bottom: 6px }
.big-step .step-body .step-subtitle { color: var(--cyan); font-size: 13px; font-weight: 600; margin-bottom: 12px; text-transform: uppercase; letter-spacing: 0.05em }
.big-step .step-body p { color: #9aa3b2; font-size: 15px; margin-bottom: 12px }
.big-step .step-body ul {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.big-step .step-body ul li {
background: rgba(0,229,255,0.06);
border: 1px solid rgba(0,229,255,0.1);
border-radius: 8px;
padding: 6px 14px;
font-size: 13px;
color: #d1d5db;
}
/* ===== Doc preview block ===== */
.doc-block {
background: var(--gray-800);
border: 1px solid rgba(255,255,255,0.05);
border-radius: 16px;
padding: 40px;
}
.doc-block h3 { font-size: 20px; font-weight: 700; margin-bottom: 24px }
.doc-preview {
background: var(--white);
color: var(--ink);
border-radius: 12px;
padding: 24px 32px;
font-size: 13px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.doc-preview .doc-header {
font-weight: 700;
font-size: 14px;
margin-bottom: 6px;
}
.doc-preview .doc-meta {
font-size: 11px;
color: #6b7280;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #e5e7eb;
}
.doc-preview table { width: 100%; border-collapse: collapse }
.doc-preview th {
background: #f3f4f6;
padding: 8px 12px;
text-align: left;
font-weight: 600;
font-size: 12px;
color: #6b7280;
}
.doc-preview td {
padding: 8px 12px;
border-bottom: 1px solid #f3f4f6;
font-size: 12px;
}
.risk-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px }
.risk-dot.high { background: var(--red) }
.risk-dot.mid { background: var(--amber) }
/* ===== AI section ===== */
.ai-section {
background: linear-gradient(135deg, var(--gray-800) 0%, var(--ink) 100%);
border: 1px solid rgba(0,229,255,0.08);
border-radius: 24px;
padding: 60px;
}
.ai-flow {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
justify-content: center;
margin-top: 40px;
}
.ai-step {
background: var(--gray-700);
border-radius: 14px;
padding: 24px;
text-align: center;
min-width: 160px;
flex: 1;
}
.ai-step .ai-icon { font-size: 32px; margin-bottom: 12px }
.ai-step h4 { font-size: 15px; font-weight: 700; margin-bottom: 4px }
.ai-step p { font-size: 13px; color: #6b7280 }
.ai-arrow {
font-size: 24px;
color: var(--cyan);
flex-shrink: 0;
}
.ai-result {
background: rgba(0,229,255,0.08);
border: 1px solid rgba(0,229,255,0.2);
border-radius: 14px;
padding: 24px;
flex: 1.5;
min-width: 280px;
}
.ai-result h4 { font-size: 15px; font-weight: 700; color: var(--cyan); margin-bottom: 12px }
.ai-result .ai-row {
display: flex;
justify-content: space-between;
padding: 6px 0;
font-size: 13px;
border-bottom: 1px solid rgba(255,255,255,0.05);
color: #9aa3b2;
}
.ai-result .ai-row span:last-child { color: #d1d5db; font-weight: 600; text-align: right }
.timer-badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: rgba(0,229,255,0.12);
border: 1px solid rgba(0,229,255,0.25);
color: var(--cyan);
padding: 10px 20px;
border-radius: 100px;
font-weight: 700;
font-size: 15px;
margin-top: 24px;
}
/* ===== Dashboard mockup ===== */
.dash-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 14px;
margin-bottom: 24px;
}
.dash-card {
background: var(--gray-700);
border-radius: 12px;
padding: 22px 20px;
}
.dash-card .val {
font-size: 30px;
font-weight: 800;
color: var(--cyan);
line-height: 1;
margin-bottom: 4px;
}
.dash-card .lbl { font-size: 12px; color: #6b7280 }
.dash-card.crit .val { color: var(--red) }
.dash-card.warn .val { color: var(--amber) }
.dash-card.ok .val { color: var(--green) }
.dir-bars { display: flex; flex-direction: column; gap: 10px }
.dir-bar {
display: flex;
align-items: center;
gap: 14px;
}
.dir-bar .dir-name { width: 180px; font-size: 13px; color: #9aa3b2; flex-shrink: 0 }
.dir-bar .bar-track {
flex: 1;
height: 10px;
background: var(--gray-700);
border-radius: 5px;
overflow: hidden;
}
.dir-bar .bar-fill { height: 100%; border-radius: 5px; background: var(--cyan) }
.dir-bar .bar-val { font-size: 13px; font-weight: 700; color: var(--white); width: 30px; text-align: right }
.dir-bar .bar-fill.w2 { width: 46%; background: #2196F3 }
.dir-bar .bar-fill.w3 { width: 35%; background: var(--amber) }
.dir-bar .bar-fill.w4 { width: 19%; background: #9C27B0 }
.dir-bar .bar-fill.w5 { width: 13%; background: var(--green) }
/* ===== Waitlist ===== */
.waitlist {
background: linear-gradient(135deg, var(--gray-800) 0%, var(--ink) 100%);
border: 1px solid rgba(0,229,255,0.1);
border-radius: 24px;
padding: 60px;
text-align: center;
}
.waitlist h2 { font-size: 32px; margin-bottom: 12px }
.waitlist p { color: #9aa3b2; margin-bottom: 32px }
.waitlist form {
display: flex;
gap: 12px;
max-width: 480px;
margin: 0 auto;
}
.waitlist input {
flex: 1;
padding: 14px 20px;
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.1);
background: var(--gray-700);
color: var(--white);
font-size: 16px;
outline: none;
transition: border-color 0.2s;
}
.waitlist input:focus { border-color: var(--cyan) }
.waitlist input::placeholder { color: #6b7280 }
/* ===== FAQ ===== */
.faq-list { max-width: 700px }
.faq-item {
border-bottom: 1px solid rgba(255,255,255,0.06);
padding: 24px 0;
}
.faq-item summary {
font-weight: 700;
font-size: 17px;
cursor: pointer;
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
}
.faq-item summary::-webkit-details-marker { display: none }
.faq-item summary::after {
content: '+';
font-size: 22px;
color: var(--cyan);
font-weight: 400;
transition: transform 0.2s;
}
.faq-item[open] summary::after { content: '\2212' }
.faq-item p { color: #9aa3b2; font-size: 15px; margin-top: 12px; padding-right: 40px }
/* ===== Footer ===== */
.footer {
padding: 48px 0;
border-top: 1px solid rgba(255,255,255,0.06);
color: #6b7280;
font-size: 14px;
}
.footer .container {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 20px;
}
.footer a { color: #9aa3b2; text-decoration: none }
.footer a:hover { color: var(--cyan) }
/* ===== Mobile ===== */
@media (max-width: 768px) {
.hero { padding: 60px 0 48px }
.hero h1 { font-size: 34px }
.hero p { font-size: 17px }
.hero-stats { gap: 20px }
.section { padding: 48px 0 }
.section h2 { font-size: 28px }
.big-step { grid-template-columns: 1fr; gap: 16px }
.doc-block { padding: 24px }
.doc-preview { overflow-x: auto; padding: 16px }
.ai-section { padding: 32px 24px; border-radius: 16px }
.ai-flow { flex-direction: column }
.ai-arrow { transform: rotate(90deg) }
.waitlist { padding: 32px 24px; border-radius: 16px }
.waitlist form { flex-direction: column }
.waitlist h2 { font-size: 24px }
.dir-bar .dir-name { width: 120px; font-size: 12px }
.dash-grid { grid-template-columns: repeat(2, 1fr) }
}
@media (max-width: 480px) {
.hero h1 { font-size: 28px }
.hero .cta-row { flex-direction: column; align-items: stretch }
.btn, .btn-outline { text-align: center; justify-content: center }
.directions { grid-template-columns: 1fr }
}
</style>
</head>
<body>
<!-- ===== HERO ===== -->
<section class="hero">
<div class="container">
<div class="badge">Продукт в разработке</div>
<h1><span>Цифровой инспектор БиОТ</span><br>проверки безопасности — быстро и без бумаги</h1>
<p>Мобильная и веб-система для проверок по охране труда, пожарной, электро-, транспортной безопасности и охране здоровья. С автоматическим формированием предписаний по требованиям законодательства РК.</p>
<div class="highlight-box">
<span class="timer">10-15 мин</span>
<span>на оформление проверки вместо 1-2 часов</span>
</div>
<div class="cta-row">
<a href="checklist.html" class="btn">Открыть чек-лист</a>
<a href="#process" class="btn-outline">Как работает</a>
</div>
<div class="hero-stats">
<div class="stat"><div class="num">6</div><div class="label">направлений проверок</div></div>
<div class="stat"><div class="num">PDF + Word</div><div class="label">экспорт документа</div></div>
<div class="stat"><div class="num">ИИ-модуль</div><div class="label">автораспознавание</div></div>
</div>
</div>
</section>
<!-- ===== DIRECTIONS ===== -->
<section class="section">
<div class="container">
<div class="section-label">Направления проверок</div>
<h2>6 направлений — одна система</h2>
<p class="subtitle">Инспектор выбирает направление, объект и подразделение — система ведёт по чек-листу.</p>
<div class="directions">
<div class="dir-card">
<div class="dir-icon">&#9888;</div>
<div>
<h3>Охрана труда и ТБ</h3>
<p>СИЗ, ограждения, работа на высоте, грузоподъёмные механизмы, документация</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128293;</div>
<div>
<h3>Пожарная безопасность</h3>
<p>Огнетушители, гидранты, планы эвакуации, датчики, противопожарное состояние</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#9889;</div>
<div>
<h3>Электробезопасность</h3>
<p>Заземление, изоляция, распределительные щиты, допуски персонала</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128739;</div>
<div>
<h3>Транспортная безопасность</h3>
<p>Техсостояние транспорта, предрейсовые осмотры, путевые листы, допуски</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#10084;</div>
<div>
<h3>Охрана здоровья</h3>
<p>Медосмотры, санитарное состояние, аптечки, условия труда на рабочих местах</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128506;</div>
<div>
<h3>Выездные проверки</h3>
<p>GPS-координаты, проверка мест производства работ, рейтинг объектов по риску</p>
</div>
</div>
</div>
</div>
</section>
<!-- ===== PROCESS ===== -->
<section class="section" id="process" style="background: var(--gray-800); border-top:1px solid rgba(255,255,255,0.04); border-bottom:1px solid rgba(255,255,255,0.04)">
<div class="container">
<div class="section-label">Бизнес-процесс</div>
<h2>4 этапа — от проверки до отчёта</h2>
<p class="subtitle">Система ведёт инспектора по шагам, исключая ручное оформление.</p>
<div class="big-steps">
<div class="big-step">
<div class="step-num">1</div>
<div class="step-body">
<div class="step-subtitle">На объекте</div>
<h3>Инспектор заполняет чек-лист</h3>
<p>Выбирает объект, подразделение, ответственное лицо, направление, дату. Проходит пункты чек-листа: соответствует / не соответствует / не применяется.</p>
<ul>
<li>Фото нарушения</li>
<li>Категория риска</li>
<li>Срок устранения</li>
<li>Ответственный</li>
<li>Нормативная ссылка</li>
</ul>
</div>
</div>
<div class="big-step">
<div class="step-num">2</div>
<div class="step-body">
<div class="step-subtitle">Автоматически</div>
<h3>Формирование записи о нарушении</h3>
<p>Инспектор фиксирует факт — система сама формулирует нарушение, требование и корректирующее мероприятие по корпоративному шаблону.</p>
<ul>
<li>Формулировка нарушения</li>
<li>Нормативное требование</li>
<li>Корректирующее мероприятие</li>
<li>Категория риска</li>
</ul>
</div>
</div>
</div>
</div>
</section>
<!-- ===== DOCUMENT AUTOGENERATION ===== -->
<section class="section">
<div class="container">
<div class="section-label">Этап 3</div>
<h2>Автоматическое «Указание по БиОТ»</h2>
<p class="subtitle">Форма по корпоративному шаблону или утверждённому образцу организации, в соответствии с законодательством РК.</p>
<div class="doc-block">
<h3>Предпросмотр формируемого документа</h3>
<div class="doc-preview">
<div class="doc-header">Указание по безопасности и охране труда № 12/06</div>
<div class="doc-meta">Объект: Цех №3 &middot; Подразделение: Производственный участок &middot; Проверяющий: Иванов А.С. &middot; Дата: 03.06.2026</div>
<table>
<tr><th></th><th>Выявленное нарушение</th><th>Корректирующее мероприятие</th><th>Ответственный</th><th>Срок</th></tr>
<tr>
<td>1</td>
<td><span class="risk-dot high"></span>Работы на высоте без страховочной привязи</td>
<td>Обеспечить применение страховочных систем</td>
<td>Начальник участка</td>
<td>15.06.2026</td>
</tr>
<tr>
<td>2</td>
<td><span class="risk-dot high"></span>Огнетушитель не прошёл своевременную перезарядку</td>
<td>Выполнить перезарядку огнетушителя</td>
<td>Руководитель объекта</td>
<td>10.06.2026</td>
</tr>
<tr>
<td>3</td>
<td><span class="risk-dot mid"></span>Отсутствует запись в журнале инструктажа</td>
<td>Внести запись и провести инструктаж</td>
<td>Мастер участка</td>
<td>20.06.2026</td>
</tr>
</table>
<div style="display:flex; justify-content: space-between; margin-top: 16px; padding-top: 12px; border-top: 1px solid #e5e7eb; font-size: 11px; color: #6b7280;">
<span>Подпись проверяющего: ___________</span>
<span>Подпись руководителя: ___________</span>
<span style="background:#000;color:#fff;padding:4px 8px;border-radius:4px;font-family:monospace">QR-код</span>
</div>
</div>
</div>
</div>
</section>
<!-- ===== AI MODULE ===== -->
<section class="section">
<div class="container">
<div class="ai-section">
<div class="section-label" style="color: var(--cyan)">Самая ценная функция</div>
<h2>ИИ-модуль: фото → готовая запись</h2>
<p class="subtitle" style="margin: 0 auto 0 0">Инспектор делает снимок нарушения — система сама определяет тип, формулировку и предлагает строку для указания.</p>
<div class="ai-flow">
<div class="ai-step">
<div class="ai-icon">&#128247;</div>
<h4>Фото нарушения</h4>
<p>Инспектор фотографирует нарушение на объекте</p>
</div>
<div class="ai-arrow">&#10132;</div>
<div class="ai-step">
<div class="ai-icon">&#129504;</div>
<h4>Распознавание</h4>
<p>ИИ определяет тип нарушения по снимку</p>
</div>
<div class="ai-arrow">&#10132;</div>
<div class="ai-result">
<h4>Автоматически предложено:</h4>
<div class="ai-row"><span>Нарушение</span><span>Работник без страховочной привязи</span></div>
<div class="ai-row"><span>Мероприятие</span><span>Организовать контроль применения СИЗ</span></div>
<div class="ai-row"><span>Категория риска</span><span style="color:var(--red)">Высокий</span></div>
<div class="ai-row"><span>Нормативная ссылка</span><span>Правила ОТ при работе на высоте &sect;12</span></div>
<div class="timer-badge" style="margin-top:16px">&#9889; Готово к вставке в Указание</div>
</div>
</div>
</div>
</div>
</section>
<!-- ===== DASHBOARD ===== -->
<section class="section" style="background: var(--gray-800); border-top:1px solid rgba(255,255,255,0.04); border-bottom:1px solid rgba(255,255,255,0.04)">
<div class="container">
<div class="section-label">Этап 4</div>
<h2>Дашборд руководителя</h2>
<p class="subtitle">Аналитика в реальном времени по всем объектам, подразделениям и направлениям.</p>
<div class="dash-grid">
<div class="dash-card"><div class="val">142</div><div class="lbl">Проверок за месяц</div></div>
<div class="dash-card crit"><div class="val">37</div><div class="lbl">Нарушений</div></div>
<div class="dash-card ok"><div class="val">82%</div><div class="lbl">Устранено</div></div>
<div class="dash-card warn"><div class="val">5</div><div class="lbl">Просрочено</div></div>
<div class="dash-card crit"><div class="val">3</div><div class="lbl">Критических</div></div>
</div>
<div style="margin-bottom:8px; font-size:13px; font-weight:700; color:#9aa3b2">Нарушения по направлениям</div>
<div class="dir-bars">
<div class="dir-bar">
<span class="dir-name">Охрана труда и ТБ</span>
<span class="bar-track"><span class="bar-fill" style="width:100%"></span></span>
<span class="bar-val">48</span>
</div>
<div class="dir-bar">
<span class="dir-name">Электробезопасность</span>
<span class="bar-track"><span class="bar-fill w2"></span></span>
<span class="bar-val">22</span>
</div>
<div class="dir-bar">
<span class="dir-name">Пожарная безопасность</span>
<span class="bar-track"><span class="bar-fill w3"></span></span>
<span class="bar-val">17</span>
</div>
<div class="dir-bar">
<span class="dir-name">Транспортная безопасность</span>
<span class="bar-track"><span class="bar-fill w4"></span></span>
<span class="bar-val">9</span>
</div>
<div class="dir-bar">
<span class="dir-name">Охрана здоровья</span>
<span class="bar-track"><span class="bar-fill w5"></span></span>
<span class="bar-val">6</span>
</div>
</div>
</div>
</section>
<!-- ===== TECH STACK ===== -->
<section class="section">
<div class="container">
<div class="section-label">Технологии</div>
<h2>Стек и план первого релиза</h2>
<p class="subtitle">MVP за 1-2 месяца: мобильное приложение, сервер, база данных, ИИ-модуль.</p>
<div class="directions">
<div class="dir-card">
<div class="dir-icon">&#128241;</div>
<div><h3>Flutter</h3><p>Мобильное приложение Android + iOS. Один код, две платформы, офлайн-режим.</p></div>
</div>
<div class="dir-card">
<div class="dir-icon">&#9881;</div>
<div><h3>FastAPI + Python</h3><p>API-сервер, генерация PDF/Word, хранение фото, авторизация по ролям.</p></div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128451;</div>
<div><h3>PostgreSQL</h3><p>Чек-листы, проверки, нарушения, пользователи, объекты, подразделения, дашборд.</p></div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128101;</div>
<div><h3>3 роли</h3><p>Инспектор (чек-листы), Руководитель (аналитика), Администратор (настройка системы).</p></div>
</div>
</div>
</div>
</section>
<!-- ===== WHAT YOU GET ===== -->
<section class="section" style="background: var(--gray-800); border-top:1px solid rgba(255,255,255,0.04); border-bottom:1px solid rgba(255,255,255,0.04)">
<div class="container">
<h2 style="text-align:center; margin-bottom: 48px">Что вы получаете</h2>
<div class="directions">
<div class="dir-card">
<div class="dir-icon">&#128196;</div>
<div>
<h3>Чек-листы по 6 направлениям</h3>
<p>Структурированы под требования РК. Настраиваются под ваше предприятие.</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128196;</div>
<div>
<h3>Указание по БиОТ</h3>
<p>Автоформирование по вашему корпоративному шаблону. PDF, Word, QR-код, эл. подпись.</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128200;</div>
<div>
<h3>Аналитическая панель</h3>
<p>Дашборд, графики по направлениям, KPI подразделений, карта выездных проверок.</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#129504;</div>
<div>
<h3>ИИ-распознавание</h3>
<p>Фото нарушения → готовая строка для указания. Оформление за 10 минут вместо 2 часов.</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128276;</div>
<div>
<h3>Контроль сроков</h3>
<p>Уведомления о просрочках, рейтинг ответственных, индекс безопасности подразделений.</p>
</div>
</div>
<div class="dir-card">
<div class="dir-icon">&#128274;</div>
<div>
<h3>Корпоративная авторизация</h3>
<p>Роли: Инспектор, Руководитель, Администратор. Доступ по корпоративному логину.</p>
</div>
</div>
</div>
</div>
</section>
<!-- ===== WAITLIST ===== -->
<section class="section" id="waitlist">
<div class="container">
<div class="waitlist">
<h2>Хотите попробовать первым?</h2>
<p>Оставьте контакты — сообщим о запуске и предоставим ранний доступ.</p>
<form action="https://formspree.io/f/YOUR_FORM_ID" method="POST">
<input type="email" name="email" placeholder="Email" required>
<button type="submit" class="btn">Отправить</button>
</form>
</div>
</div>
</section>
<!-- ===== FAQ ===== -->
<section class="section">
<div class="container">
<div class="section-label">FAQ</div>
<h2>Частые вопросы</h2>
<div class="faq-list">
<details class="faq-item">
<summary>Когда будет готова первая версия?</summary>
<p>MVP — 1-2 месяца. Включает: авторизация, чек-листы по 6 направлениям, фотофиксация, автоформирование «Указания по БиОТ», экспорт PDF, история проверок.</p>
</details>
<details class="faq-item">
<summary>Можно ли использовать нашу форму Указания по БиОТ?</summary>
<p>Да, система настраивается под утверждённый корпоративный шаблон или форму, применяемую в организации в соответствии с законодательством РК.</p>
</details>
<details class="faq-item">
<summary>Работает ли без интернета на объекте?</summary>
<p>Да, офлайн-режим: все данные сохраняются на устройстве и синхронизируются с сервером при появлении сети.</p>
</details>
<details class="faq-item">
<summary>Как работает ИИ-распознавание нарушений?</summary>
<p>Инспектор делает фото нарушения, система определяет тип, формулирует запись, предлагает корректирующее мероприятие, категорию риска и нормативную ссылку. Инспектор подтверждает — запись попадает в Указание.</p>
</details>
<details class="faq-item">
<summary>Какие роли есть в системе?</summary>
<p>Инспектор — проводит проверки, Руководитель — видит дашборд и аналитику, Администратор — настраивает чек-листы, шаблоны и доступы.</p>
</details>
</div>
</div>
</section>
<footer class="footer">
<div class="container">
<span>2026 &middot; Цифровой инспектор БиОТ</span>
<a href="#waitlist">Оставить заявку</a>
</div>
</footer>
</body>
</html>