Files
APAW/landing/index.html
Deploy Bot dbbf4c32e1 feat(landing): add state API service with real-fit score drill-down
- Add apaw-state-api Flask service (landing/api/server.py) that serves
  agent fit scores, best models, and explanations from real-fit.db
- Add nginx proxy rule: /api/state → apaw-state-api:8080
- Add fit-score drill-down modal (click heatmap cell → score breakdown
  + explanation) in api.js, styles.css, and index.html
- Add real-fit-recalc.py script for offline score recalculation from
  stored SQLite responses
- Add real-fit-engine.py (evaluation engine) and sync-dashboard-data.py
- Add Dockerfile ENTRYPOINT + entrypoint.sh for landing container
- Add docker-compose.ollama.yml for local Ollama inference
- Update kilo.jsonc command models and agent-versions.json
- Regenerate index.standalone.html with latest dashboard data
- Add .gitignore entries for __pycache__, runtime data, and backups
2026-05-27 19:53:40 +01:00

438 lines
29 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">
<meta name="theme-color" content="#0a0a0f">
<title>APAW — Автономный AI-конвейер самоулучшающегося кода</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./assets/styles.css">
</head>
<body>
<!-- SCROLL TOP -->
<button class="scroll-top" aria-label="Наверх" onclick="window.scrollTo({top:0,behavior:'smooth'})">&uarr;</button>
<!-- HERO -->
<header class="hero" id="top">
<nav class="nav">
<div class="logo"><span class="logo__icon">&#x27A4;</span><span class="logo__text">APAW</span></div>
<button class="nav__toggle" aria-label="Меню" aria-expanded="false" onclick="this.setAttribute('aria-expanded',this.getAttribute('aria-expanded')==='true' ? 'false' : 'true')">
<span></span><span></span><span></span>
</button>
<div class="nav__actions">
<button class="theme-toggle" id="themeToggle" aria-label="Переключить тему" title="Переключить тему">
<span class="theme-toggle__icon" id="themeIcon">&#9728;</span>
<span class="theme-toggle__label" id="themeLabel">Светло</span>
</button>
</div>
<div class="nav__links">
<a href="#workflow">Пайплайн</a>
<a href="#agents">Агенты</a>
<a href="#evolution">Эволюция</a>
<a href="#skills">Скиллы</a>
<a href="#pricing">Тарифы</a>
<a href="#contact" class="nav__link nav__link--cta">Начать</a>
</div>
</nav>
<div class="hero__content">
<div class="hero__badge">Gitea-Nervous-System v2.0 <span class="hero__sep">/</span> 30+ агента <span class="hero__sep">/</span> Self-improving</div>
<h1 class="hero__title">Автономный AI-конвейер<br><span class="gradient">самоулучшающегося кода</span></h1>
<p class="hero__subtitle">
Распределенная система из <strong>30+ специализированных AI-агентов</strong>, оркестрируемых через Gitea.<br>
От идеи до продакшена — полностью автоматически. Самоанализ, саморевью, самоэволюция.
</p>
<div class="hero__actions">
<a href="#pricing" class="btn btn--primary">Подключить доступ</a>
<a href="#workflow" class="btn btn--ghost">Посмотреть пайплайн</a>
</div>
<div class="hero__stats">
<div class="stat"><div class="stat__value">30+</div><div class="stat__label">AI-агентов</div></div>
<div class="stat"><div class="stat__value">11</div><div class="stat__label">LLM-моделей</div></div>
<div class="stat"><div class="stat__value">8/10</div><div class="stat__label">Оценка кода</div></div>
<div class="stat"><div class="stat__value">0.91</div><div class="stat__label">Фитнес-скор</div></div>
<div class="stat"><div class="stat__value">15+</div><div class="stat__label">Скиллов</div></div>
</div>
</div>
</header>
<!-- VALUE PROPS -->
<section class="trusted">
<p class="trusted__text">Поддерживает стеки разработки</p>
<div class="trusted__logos">
<span>Gitea</span><span>Docker</span><span>TypeScript</span><span>Go</span>
<span>PHP / Laravel</span><span>Python / Django / FastAPI</span>
<span>Vue / Nuxt</span><span>React / Next.js</span><span>Flutter</span><span>WordPress</span>
</div>
</section>
<!-- WORKFLOW STATES -->
<section id="workflow" class="workflow">
<div class="container">
<h2 class="section__title">Пайплайн <span class="gradient">от задачи до релиза</span></h2>
<p class="section__subtitle">Каждая задача проходит через 8 фаз с автоматической сменой статус-лейблов.<br>Оркестратор выбирает агента под каждую фазу, исходя из типа задачи и состояния.</p>
<div class="states">
<div class="state-card"><div class="state-card__num">1</div><div class="state-card__meta"><div class="state-card__state">status: new</div><div class="state-card__agent">@RequirementRefiner</div></div><p class="state-card__desc">Превращает идею или баг-репорт в строгую User Story с критериями приемки.</p></div>
<div class="state-arrow">&darr;</div>
<div class="state-card"><div class="state-card__num">2</div><div class="state-card__meta"><div class="state-card__state">status: planned</div><div class="state-card__agent">@HistoryMiner</div></div><p class="state-card__desc">Ищет дубли в git-истории и прошлых решениях, чтобы избежать регрессий и повторной работы.</p></div>
<div class="state-arrow">&darr;</div>
<div class="state-card"><div class="state-card__num">3</div><div class="state-card__meta"><div class="state-card__state">status: researching</div><div class="state-card__agent">@SystemAnalyst</div></div><p class="state-card__desc">Проектирует спецификации, схемы данных, API-контракты и архитектуру до написания кода.</p></div>
<div class="state-arrow">&darr;</div>
<div class="state-card"><div class="state-card__num">4</div><div class="state-card__meta"><div class="state-card__state">status: designed</div><div class="state-card__agent">@SdetEngineer</div></div><p class="state-card__desc">Пишет тесты по методологии TDD — до кода. Mock'и, fixtures, edge cases — всё сразу.</p></div>
<div class="state-arrow">&darr;</div>
<div class="state-card"><div class="state-card__num">5</div><div class="state-card__meta"><div class="state-card__state">status: testing</div><div class="state-card__agent">@LeadDeveloper</div></div><p class="state-card__desc">Пишет код, чтобы тесты прошли. Backend, frontend, Go, PHP, Python — подключается узкоспециализированный агент.</p></div>
<div class="state-arrow">&darr;</div>
<div class="state-card"><div class="state-card__num">6</div><div class="state-card__meta"><div class="state-card__state">status: implementing</div><div class="state-card__agent">@CodeSkeptic → @TheFixer</div></div><p class="state-card__desc">Адверсариальное ревью по SOLID, безопасности и performance. Если есть замечания — TheFixer дорабатывает итеративно.</p></div>
<div class="state-arrow">&darr;</div>
<div class="state-card"><div class="state-card__num">7</div><div class="state-card__meta"><div class="state-card__state">status: releasing</div><div class="state-card__agent">@ReleaseManager</div></div><p class="state-card__desc">Создает PR, мержит в dev, fast-forward в main, ставит semver-теги и готовит релиз.</p></div>
<div class="state-arrow">&darr;</div>
<div class="state-card"><div class="state-card__num">8</div><div class="state-card__meta"><div class="state-card__state">status: evaluated</div><div class="state-card__agent">@Evaluator + @PromptOptimizer</div></div><p class="state-card__desc">Оценивает эффективность агентов (1-10) и фитнес-скор (0.0-1.0). Score < 7 триггерит оптимизацию промптов.</p></div>
</div>
</div>
</section>
<!-- AGENT TABLE -->
<section id="agents" class="agents">
<div class="container">
<h2 class="section__title">Агентная <span class="gradient">матрица</span></h2>
<p class="section__subtitle">30+ агентов, каждый со своей LLM, ролью и статусом. Модель подбирается под задачу — не наоборот.</p>
<div class="table-wrap">
<div class="table-hint"><span class="table-hint__icon">&harr;</span> прокрутите вправо, чтобы увидеть всё</div>
<table class="agent-table">
<thead>
<tr><th>Агент</th><th>Категория</th><th>Роль</th><th>Модель</th><th>Fit</th><th>Вызывается</th></tr>
</thead>
<tbody id="agent-tbody">
<!-- Dynamic: loaded from /api/state via api.js -->
</tbody>
</table>
</div>
<h3 class="section__subheading">GNS-2 Event Format — как агенты общаются</h3>
<div class="code-block">
<pre><code><span class="token-comment">&lt;!-- GNS_EVENT: {
"type": "subagent_result",
"agent": "lead-developer",
"invocation_id": "AGENT-113-003",
"parent_id": "orch-113-001",
"depth": 1,
"budget": {"remaining": 3800},
"state_changes": {
"labels_add": ["phase::drafting-spec"],
"assignee": "system-analyst"
},
"next_agent": "system-analyst",
"timestamp": "2026-05-23T09:00:00Z"
} --&gt;</span>
<span class="token-punct">{</span>
<span class="token-attr">"type"</span><span class="token-punct">:</span> <span class="token-str">"subagent_result"</span><span class="token-punct">,</span>
<span class="token-attr">"agent"</span><span class="token-punct">:</span> <span class="token-str">"lead-developer"</span><span class="token-punct">,</span>
<span class="token-attr">"invocation_id"</span><span class="token-punct">:</span> <span class="token-str">"AGENT-113-003"</span><span class="token-punct">,</span>
<span class="token-attr">"budget"</span><span class="token-punct">:</span> <span class="token-punct">{</span><span class="token-str">"remaining"</span><span class="token-punct">:</span> <span class="token-num">3800</span><span class="token-punct">}</span><span class="token-punct">,</span>
<span class="token-attr">"state_changes"</span><span class="token-punct">:</span> <span class="token-punct">{</span>
<span class="token-str">"labels_add"</span><span class="token-punct">:</span> <span class="token-punct">[</span><span class="token-str">"phase::drafting-spec"</span><span class="token-punct">]</span><span class="token-punct">,</span>
<span class="token-str">"assignee"</span><span class="token-punct">:</span> <span class="token-str">"system-analyst"</span>
<span class="token-punct">}</span><span class="token-punct">,</span>
<span class="token-attr">"next_agent"</span><span class="token-punct">:</span> <span class="token-str">"system-analyst"</span><span class="token-punct">,</span>
<span class="token-attr">"timestamp"</span><span class="token-punct">:</span> <span class="token-str">"2026-05-23T09:00:00Z"</span>
<span class="token-punct">}</span></code></pre>
</div>
<p class="section__caption">Каждый агент при входе читает checkpoint из issue body, при выходе — пишет структурированный результат с machine-readable footer в Gitea.</p>
</div>
</section>
<!-- ANALYTICS HIERARCHY -->
<section id="analytics" class="analytics">
<div class="container">
<h2 class="section__title">Аналитическая <span class="gradient">иерархия</span></h2>
<p class="section__subtitle">Живая дистрибуция агентов по моделям и категориям. Обновляется автоматически из реальных конфигов <code>.kilo/agents/</code>.</p>
<div class="analytics__grid">
<!-- Model tree -->
<div class="analytics__card" id="model-hierarchy">
<h3 class="analytics__card-title">Модели → Категории → Агенты</h3>
<div class="analytics__tree" id="model-tree">
<!-- Dynamic: loaded from /api/state via api.js -->
</div>
</div>
<!-- Category breakdown -->
<div class="analytics__card" id="category-breakdown">
<h3 class="analytics__card-title">Дистрибуция по категориям</h3>
<div class="analytics__bars" id="category-bars">
<!-- Dynamic -->
</div>
</div>
<!-- Fit score distribution -->
<div class="analytics__card" id="fit-distribution">
<h3 class="analytics__card-title">Fit-score распределение</h3>
<div class="analytics__heatmap" id="fit-heatmap">
<!-- Dynamic -->
</div>
</div>
<!-- Commands summary -->
<div class="analytics__card" id="commands-summary">
<h3 class="analytics__card-title">Команды — модели и режимы</h3>
<div class="analytics__table-wrap">
<table class="analytics__table">
<thead>
<tr><th>Команда</th><th>Модель</th><th>Score</th></tr>
</thead>
<tbody id="command-analytics-tbody">
<!-- Dynamic -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<!-- EVOLUTION -->
<section id="evolution" class="evolution">
<div class="container">
<h2 class="section__title">Агентная <span class="gradient">эволюция</span></h2>
<p class="section__subtitle">APAW не просто выполняет задачи — она учится на результатах. Модель под каждого агента подбирается по фитнес-матрице, а промпты оптимизируются автоматически.</p>
<div class="evolution__grid">
<article class="evo-card">
<div class="evo-card__title">Model Benchmarks</div>
<div class="evo-card__value">11 LLM</div>
<div class="evo-card__desc">SWE-bench + IFEval скоры для каждой модели. Kimi K2.6 ведет с IF 91, Qwen3-Coder 480B — SWE-bench 66.5%.</div>
</article>
<article class="evo-card">
<div class="evo-card__title">Agent × Model Matrix</div>
<div class="evo-card__value">30×11</div>
<div class="evo-card__desc">Каждая пара Agent × Model имеет fit-score. Оркестратор выбирает лучшую модель под конкретную задачу, а не использует один LLM на всех.</div>
</article>
<article class="evo-card">
<div class="evo-card__title">Self-Improvement Cycle</div>
<div class="evo-card__value">closed-loop</div>
<div class="evo-card__desc">Pipeline → Evaluator (score 1-10) → Pipeline Judge (fitness 0.0-1.0) → если fitness < 0.70 PromptOptimizer улучшает промпты повтор.</div>
</article>
<article class="evo-card">
<div class="evo-card__title">Fitness Formula</div>
<div class="evo-card__value">0.91 avg</div>
<div class="evo-card__desc">fitness = (pass_rate × 0.50) + (gates_rate × 0.25) + (efficiency × 0.25). Покрытие тестов, билд, линт, типы — всё в формуле.</div>
</article>
</div>
<h3 class="section__subheading">Flow: Bidirectional Data Flow</h3>
<div class="flow">
<div class="flow-step"><div class="flow-step__num">1</div><div class="flow-step__body"><div class="flow-step__name">/research models</div><div class="flow-step__desc">Сбор свежих benchmark-данных из провайдеров</div></div></div>
<div class="flow-step"><div class="flow-step__num">2</div><div class="flow-step__body"><div class="flow-step__name">sync-model-research.ts</div><div class="flow-step__desc">Применение рекомендаций: capability-index.yaml → kilo-meta.json → kilo.jsonc</div></div></div>
<div class="flow-step"><div class="flow-step__num">3</div><div class="flow-step__body"><div class="flow-step__name">sync-agents.js --fix</div><div class="flow-step__desc">Пропагация frontmatter в .kilo/agents/*.md, KILO_SPEC.md, AGENTS.md</div></div></div>
<div class="flow-step"><div class="flow-step__num">4</div><div class="flow-step__body"><div class="flow-step__name">Dashboard rebuild</div><div class="flow-step__desc">research-dashboard.html с 6 вкладками: Обзор, Groq, Модели, Матрица, Рекомендации, Анализ профита</div></div></div>
<div class="flow-step flow-step--loop"><div class="flow-step__num">5</div><div class="flow-step__body"><div class="flow-step__name">/research models</div><div class="flow-step__desc">Цикл повторяется — система постоянно самоулучшается</div></div></div>
</div>
</div>
</section>
<!-- SKILLS -->
<section id="skills" class="skills">
<div class="container">
<h2 class="section__title">Доменные <span class="gradient">скиллы</span></h2>
<p class="section__subtitle">15+ модулей специализированных знаний, которые агенты подгружают под конкретную задачу — не держат в контексте постоянно.</p>
<div class="skills__grid">
<div class="skill-chip"><div class="skill-chip__cat">Containerization</div><div class="skill-chip__items">docker-compose, docker-swarm, docker-security, docker-monitoring</div></div>
<div class="skill-chip"><div class="skill-chip__cat">Node.js</div><div class="skill-chip__items">nodejs-express-patterns, nodejs-auth-jwt, nodejs-security-owasp</div></div>
<div class="skill-chip"><div class="skill-chip__cat">Databases</div><div class="skill-chip__items">postgresql-patterns, sqlite-patterns, clickhouse-patterns</div></div>
<div class="skill-chip"><div class="skill-chip__cat">PHP</div><div class="skill-chip__items">php-laravel-patterns, php-symfony-patterns, php-wordpress-patterns, php-modular-architecture, php-security, php-testing</div></div>
<div class="skill-chip"><div class="skill-chip__cat">Python</div><div class="skill-chip__items">python-django-patterns, python-fastapi-patterns</div></div>
<div class="skill-chip"><div class="skill-chip__cat">Frontend</div><div class="skill-chip__items">nextjs-patterns, react-patterns, vue-nuxt-patterns</div></div>
<div class="skill-chip"><div class="skill-chip__cat">Process</div><div class="skill-chip__items">planning-patterns, memory-systems, tool-use, research-cycle</div></div>
<div class="skill-chip"><div class="skill-chip__cat">Go</div><div class="skill-chip__items">go-modules, go-concurrency, go-testing, go-security</div></div>
<div class="skill-chip"><div class="skill-chip__cat">Integration</div><div class="skill-chip__items">gitea-workflow, quality-controller, scoped-labels, agent-logging</div></div>
<div class="skill-chip"><div class="skill-chip__cat">Domains</div><div class="skill-chip__items">ecommerce, booking, blog, fix-workflow</div></div>
</div>
<h3 class="section__subheading">Docker-нативность: никаких установок на хост</h3>
<div class="code-block">
<pre><code><span class="token-comment"># Visual testing — Playwright в контейнере</span>
docker compose -f docker/docker-compose.web-testing.yml run --rm visual-tester
<span class="token-comment"># Architect indexer — перед любой задачей</span>
docker compose -f docker/docker-compose.architect.yml run --rm architect-indexer
<span class="token-comment"># Evolution dashboard — обновляется автоматически</span>
bun run sync:evolution && bun run evolution:dashboard</code></pre>
</div>
<p class="section__caption">Все браузерные инструменты (Playwright, скриншоты, E2E) запускаются в контейнерах. Хост остаётся чистым.</p>
</div>
</section>
<!-- PRICING -->
<section id="pricing" class="pricing">
<div class="container">
<h2 class="section__title">Тарифы <span class="gradient">доступа</span></h2>
<p class="section__subtitle">Выберите подписку, которая соответствует масштабу вашей команды.</p>
<div class="pricing__grid">
<div class="pricing-card">
<h3 class="pricing-card__name">Developer</h3>
<div class="pricing-card__price"><span class="pricing-card__amount">35</span><span class="pricing-card__currency">&euro;</span><span class="pricing-card__period">/месяц</span></div>
<p class="pricing-card__tag">Для индивидуальных разработчиков и небольших проектов</p>
<ul class="pricing-card__list">
<li><span class="pricing-card__check">&#10003;</span>1 активный пайплайн</li>
<li><span class="pricing-card__check">&#10003;</span>Все базовые агенты (Core + QA)</li>
<li><span class="pricing-card__check">&#10003;</span>Gitea-интеграция</li>
<li><span class="pricing-card__check">&#10003;</span>Git-операции + semver</li>
<li><span class="pricing-card__check">&#10003;</span>Доступ к 15+ доменным скиллам</li>
<li><span class="pricing-card__check">&#10003;</span>Email-поддержка</li>
</ul>
<a href="#contact" class="btn btn--outline btn--full">Выбрать Developer</a>
</div>
<div class="pricing-card pricing-card--featured">
<div class="pricing-card__badge">Популярный</div>
<h3 class="pricing-card__name">Team</h3>
<div class="pricing-card__price"><span class="pricing-card__amount pricing-card__amount--hl">200</span><span class="pricing-card__currency">&euro;</span><span class="pricing-card__period">/месяц</span></div>
<p class="pricing-card__tag">Для команд, продуктовых компаний и агентств</p>
<ul class="pricing-card__list">
<li><span class="pricing-card__check">&#10003;</span><strong>Безлимитные</strong> параллельные пайплайны</li>
<li><span class="pricing-card__check">&#10003;</span><strong>Все</strong> 30+ агентов включая DevOps и Cognitive</li>
<li><span class="pricing-card__check">&#10003;</span>Приоритетная очередь + выделенный orchestrator</li>
<li><span class="pricing-card__check">&#10003;</span>Docker-контейнеры для тестирования и деплоя</li>
<li><span class="pricing-card__check">&#10003;</span>Визуальное тестирование (Playwright, скриншоты)</li>
<li><span class="pricing-card__check">&#10003;</span>Agent Evolution Dashboard + API доступ</li>
<li><span class="pricing-card__check">&#10003;</span>Персональный менеджер</li>
<li><span class="pricing-card__check">&#10003;</span>Priority SLAB</li>
</ul>
<a href="#contact" class="btn btn--primary btn--full">Выбрать Team</a>
</div>
</div>
</div>
</section>
<!-- CTA -->
<section id="contact" class="cta">
<div class="container">
<h2 class="section__title section__title--light">Готовы автоматизировать<br>разработку?</h2>
<p class="section__subtitle section__subtitle--light">Подключите APAW к своему проекту и получите доступ к автономному AI-конвейеру с самоулучшением уже сегодня.</p>
<div class="cta__actions">
<a href="mailto:hello@apaw.dev" class="btn btn--light btn--lg">hello@apaw.dev</a>
<span class="cta__or">или</span>
<a href="#" class="btn btn--outline-light btn--lg" onclick="event.preventDefault();alert('Свяжитесь через hello@apaw.dev — Telegram интеграция скоро!');return false;">Написать в Telegram</a>
</div>
<div class="cta__bottom">
<a href="#pricing" class="btn btn--primary btn--lg">Начать с APAW</a>
</div>
</div>
</section>
<!-- FOOTER -->
<footer class="footer">
<div class="container">
<div class="footer__grid">
<div class="footer__col">
<div class="logo"><span class="logo__icon">&#x27A4;</span><span class="logo__text">APAW</span></div>
<p class="footer__desc">Self-improving code pipeline.<br>Built by agents, for agents.</p>
</div>
<div class="footer__col">
<h4>Продукт</h4>
<a href="#workflow">Пайплайн</a>
<a href="#agents">Агенты</a>
<a href="#evolution">Эволюция</a>
<a href="#skills">Скиллы</a>
<a href="#pricing">Тарифы</a>
</div>
<div class="footer__col">
<h4>Сообщество</h4>
<a href="https://git.softuniq.eu" target="_blank" rel="noopener">Gitea</a>
<a href="#" onclick="event.preventDefault();alert('Документация скоро будет доступна.');return false;">Документация</a>
<a href="#" onclick="event.preventDefault();alert('Статус и логи будут доступны.');return false;">Статус</a>
</div>
</div>
<div class="footer__bottom">
<span>&copy; 2026 APAW. All rights reserved.</span>
<span class="footer__made">Made with &hearts; by agents</span>
</div>
</div>
</footer>
<!-- Fit Score Drill-down Modal -->
<div id="fit-modal" class="modal" role="dialog" aria-modal="true" aria-label="Детали fit-score" tabindex="-1" aria-hidden="true">
<div class="modal__overlay"></div>
<div class="modal__content">
<button class="modal__close" aria-label="Закрыть">&times;</button>
<div class="modal__header">
<h3 class="modal__title" id="modal-agent-name">Agent</h3>
<span class="modal__model" id="modal-model">model</span>
</div>
<div class="modal__score-row">
<span class="modal__score" id="modal-score">0</span>
<span class="modal__score-label">Fit Score</span>
</div>
<div class="modal__breakdown" id="modal-breakdown"></div>
<div class="modal__section">
<h4>Почему такой скор?</h4>
<p class="modal__explanation" id="modal-explanation"></p>
</div>
</div>
</div>
<script>
/* ===== THEME TOGGLE ===== */
(function() {
const root = document.documentElement;
const btn = document.getElementById('themeToggle');
const icon = document.getElementById('themeIcon');
const label = document.getElementById('themeLabel');
const meta = document.querySelector('meta[name="theme-color"]');
const KEY = 'apaw-theme';
const ICONS = { dark: '\u2600', light: '\u263E' };
const LABELS = { dark: '\u0421\u0432\u0435\u0442\u043B\u043E', light: '\u0422\u0435\u043C\u043D\u043E' };
const META = { dark: '#0a0a0f', light: '#f8f9fb' };
function apply(theme) {
root.setAttribute('data-theme', theme);
if (meta) meta.setAttribute('content', META[theme]);
if (icon) icon.textContent = ICONS[theme];
if (label) label.textContent = LABELS[theme];
if (btn) btn.setAttribute('aria-pressed', theme === 'light');
}
const saved = localStorage.getItem(KEY);
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const initial = saved || (prefersDark ? 'dark' : 'light');
apply(initial);
if (btn) {
btn.addEventListener('click', () => {
const next = root.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
apply(next);
localStorage.setItem(KEY, next);
});
}
})();
/* ===== SCROLL TOP ===== */
const topBtn = document.querySelector('.scroll-top');
window.addEventListener('scroll', () => {
topBtn.classList.toggle('is-visible', window.scrollY > 400);
});
/* ===== MOBILE NAV ===== */
const toggle = document.querySelector('.nav__toggle');
const links = document.querySelector('.nav__links');
if (toggle) {
toggle.addEventListener('click', () => {
links.classList.toggle('is-open');
});
links.querySelectorAll('a').forEach(a => {
a.addEventListener('click', () => {
links.classList.remove('is-open');
toggle.setAttribute('aria-expanded', 'false');
});
});
}
</script>
<script src="assets/api.js"></script>
</body>
</html>