wa-dev-tools/public/landing.html

1662 lines
37 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru" >
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WA Dev Tools — инструменты для веб-разработчика</title>
<meta name="description" content="Сжатие картинок, форматирование кода, HTTP-клиент и ещё 10 инструментов для веб-разработчиков — всё в браузере, " />
<link href="/vendor/tailwind.min.css" rel="stylesheet">
<link href="/vendor/fonts.css" rel="stylesheet">
<link href="/shared.css" rel="stylesheet">
<style>
/* ── Resets & base ── */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* ── Animations ── */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(28px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes glow-pulse {
0%, 100% { opacity: 0.45; }
50% { opacity: 0.70; }
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-8px); }
}
.animate-fade-in { animation: fadeIn 0.7s ease both; }
.animate-slide-up { animation: slideUp 0.7s ease both; }
.delay-100 { animation-delay: 0.10s; }
.delay-200 { animation-delay: 0.20s; }
.delay-300 { animation-delay: 0.30s; }
.delay-400 { animation-delay: 0.40s; }
.delay-500 { animation-delay: 0.50s; }
.delay-600 { animation-delay: 0.60s; }
/* Intersection-observer driven reveal */
.reveal {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
}
.reveal-delay-1 { transition-delay: 0.10s; }
.reveal-delay-2 { transition-delay: 0.20s; }
.reveal-delay-3 { transition-delay: 0.30s; }
.reveal-delay-4 { transition-delay: 0.40s; }
/* ── Hero ── */
.hero {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 80px 20px 60px;
position: relative;
overflow: hidden;
}
/* Glow blobs */
.hero-glow-1 {
position: absolute;
top: -10%;
left: 50%;
transform: translateX(-50%);
width: min(700px, 120vw);
height: min(700px, 120vw);
background: radial-gradient(ellipse at center, rgba(0, 84, 230, 0.18) 0%, transparent 65%);
pointer-events: none;
animation: glow-pulse 5s ease-in-out infinite;
}
.hero-glow-2 {
position: absolute;
bottom: 0;
right: -15%;
width: 420px;
height: 420px;
background: radial-gradient(ellipse at center, rgba(107, 159, 255, 0.08) 0%, transparent 60%);
pointer-events: none;
animation: glow-pulse 7s ease-in-out infinite reverse;
}
.hero-glow-3 {
position: absolute;
bottom: 10%;
left: -10%;
width: 360px;
height: 360px;
background: radial-gradient(ellipse at center, rgba(0, 67, 184, 0.10) 0%, transparent 60%);
pointer-events: none;
animation: glow-pulse 6s ease-in-out infinite 1s;
}
/* Grid lines overlay */
.hero-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(0, 84, 230, 0.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 84, 230, 0.04) 1px, transparent 1px);
background-size: 60px 60px;
mask-image: radial-gradient(ellipse at center, black 20%, transparent 75%);
pointer-events: none;
}
.hero-logo {
font-family: 'JetBrains Mono', monospace;
font-size: clamp(11px, 1.5vw, 13px);
font-weight: 700;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: 28px;
}
.hero-logo span {
color: var(--accent-bright);
}
.hero-headline {
font-family: 'Manrope', sans-serif;
font-size: clamp(2rem, 5.5vw, 3.75rem);
font-weight: 800;
line-height: 1.15;
letter-spacing: -0.03em;
color: var(--text-primary);
max-width: 820px;
margin: 0 auto 20px;
}
.hero-headline .accent { color: var(--accent); }
.hero-sub {
font-family: 'Manrope', sans-serif;
font-size: clamp(1rem, 2vw, 1.2rem);
line-height: 1.65;
color: var(--text-secondary);
max-width: 600px;
margin: 0 auto 44px;
}
.hero-cta-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
@media (min-width: 480px) {
.hero-cta-group { flex-direction: row; justify-content: center; }
}
.btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 15px 32px;
background: var(--accent);
color: #fff;
border: none;
border-radius: 12px;
font-family: 'Manrope', sans-serif;
font-weight: 700;
font-size: 16px;
text-decoration: none;
cursor: pointer;
transition: background 0.2s, transform 0.2s, box-shadow 0.2s;
box-shadow: 0 0 0 0 rgba(0, 84, 230, 0);
}
.btn-primary:hover {
background: var(--accent-dim);
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(0, 84, 230, 0.35);
}
.btn-primary:active {
transform: translateY(0);
}
.btn-secondary {
display: inline-flex;
align-items: center;
font-family: 'Manrope', sans-serif;
font-size: 15px;
color: var(--text-secondary);
text-decoration: none;
transition: color 0.2s;
}
.btn-secondary:hover { color: var(--text-primary); }
.btn-secondary .link-text {
border-bottom: 1px solid var(--surface-600);
padding-bottom: 1px;
}
/* Count badge in hero */
.hero-badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--surface-800);
border: 1px solid var(--surface-600);
border-radius: 999px;
padding: 6px 16px 6px 10px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--text-muted);
margin-bottom: 28px;
}
.hero-badge .dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-bright);
box-shadow: 0 0 6px var(--accent-bright);
animation: glow-pulse 2s ease-in-out infinite;
}
/* ── Scroll hint ── */
.scroll-hint {
position: absolute;
bottom: 28px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
letter-spacing: 0.15em;
text-transform: uppercase;
animation: none;
}
/* ── Section common ── */
.section {
padding: 80px 20px;
}
.section-inner {
max-width: 1120px;
margin: 0 auto;
}
.section-label {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--accent-bright);
margin-bottom: 12px;
}
.section-title {
font-family: 'Manrope', sans-serif;
font-size: clamp(1.6rem, 4vw, 2.4rem);
font-weight: 800;
letter-spacing: -0.025em;
color: var(--text-primary);
margin: 0 0 48px;
line-height: 1.2;
}
/* ── Category cards ── */
.cat-grid {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
@media (min-width: 640px) {
.nav-link { display:inline-flex; }
.cat-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
.cat-grid { grid-template-columns: repeat(4, 1fr); }
}
.cat-card {
background: var(--surface-800);
border: 1px solid var(--surface-600);
border-radius: 16px;
padding: 28px 24px;
display: flex;
flex-direction: column;
gap: 16px;
transition: border-color 0.25s, transform 0.25s, box-shadow 0.25s;
}
.cat-card:hover {
border-color: rgba(0, 84, 230, 0.5);
transform: translateY(-4px);
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(0, 84, 230, 0.15);
}
.cat-icon-wrap {
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--accent-bg);
display: flex;
align-items: center;
justify-content: center;
color: var(--accent-bright);
flex-shrink: 0;
}
.cat-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.cat-title {
font-family: 'Manrope', sans-serif;
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
}
.cat-badge {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
font-weight: 700;
color: var(--accent-bright);
background: rgba(107, 159, 255, 0.12);
border: 1px solid rgba(107, 159, 255, 0.2);
border-radius: 999px;
padding: 2px 10px;
white-space: nowrap;
}
.cat-desc {
font-family: 'Manrope', sans-serif;
font-size: 14px;
line-height: 1.6;
color: var(--text-secondary);
flex: 1;
}
.cat-tools {
display: flex;
flex-direction: column;
gap: 6px;
padding-top: 4px;
border-top: 1px solid var(--surface-600);
}
.cat-tool-item {
display: flex;
align-items: center;
gap: 8px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--text-muted);
}
.cat-tool-item::before {
content: '';
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--accent-bright);
opacity: 0.5;
flex-shrink: 0;
}
/* ── Advantages ── */
.adv-section {
background: var(--surface-800);
border-top: 1px solid var(--surface-600);
border-bottom: 1px solid var(--surface-600);
}
.adv-grid {
display: grid;
grid-template-columns: 1fr;
gap: 0;
}
@media (min-width: 640px) {
.adv-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
.adv-grid { grid-template-columns: repeat(4, 1fr); }
}
.adv-card {
padding: 36px 28px;
display: flex;
flex-direction: column;
gap: 12px;
border-right: 1px solid var(--surface-600);
border-bottom: 1px solid var(--surface-600);
}
.adv-card:last-child { border-right: none; }
@media (min-width: 640px) {
.adv-card:nth-child(2) { border-right: none; }
.adv-card:nth-child(3) { border-bottom: none; }
.adv-card:nth-child(4) { border-right: none; border-bottom: none; }
}
@media (min-width: 1024px) {
.adv-card:nth-child(2) { border-right: 1px solid var(--surface-600); }
.adv-card:nth-child(3) { border-bottom: 1px solid var(--surface-600); }
.adv-card:last-child { border-right: none; border-bottom: none; }
/* last row has no bottom border */
.adv-card { border-bottom: none; }
}
.adv-icon-wrap {
width: 44px;
height: 44px;
border-radius: 10px;
background: var(--accent-bg);
display: flex;
align-items: center;
justify-content: center;
color: var(--accent-bright);
}
.adv-title {
font-family: 'Manrope', sans-serif;
font-size: 17px;
font-weight: 700;
color: var(--text-primary);
}
.adv-desc {
font-family: 'Manrope', sans-serif;
font-size: 14px;
line-height: 1.65;
color: var(--text-secondary);
}
/* ── Final CTA ── */
.cta-section {
text-align: center;
padding: 100px 20px;
}
.cta-glow {
position: relative;
display: inline-block;
}
.cta-glow::before {
content: '';
position: absolute;
inset: -40px -80px;
background: radial-gradient(ellipse at center, rgba(0, 84, 230, 0.12) 0%, transparent 65%);
pointer-events: none;
}
.cta-headline {
font-family: 'Manrope', sans-serif;
font-size: clamp(1.8rem, 4vw, 2.8rem);
font-weight: 800;
letter-spacing: -0.025em;
color: var(--text-primary);
margin: 0 0 16px;
}
.cta-sub {
font-family: 'Manrope', sans-serif;
font-size: clamp(0.95rem, 1.8vw, 1.1rem);
line-height: 1.65;
color: var(--text-secondary);
margin: 0 auto 40px;
max-width: 480px;
}
/* ── Footer ── */
.landing-footer {
padding: 28px 32px;
border-top: 1px solid var(--surface-600);
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
.footer-copy {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--text-muted);
}
.footer-links {
display: flex;
gap: 20px;
}
.footer-link {
font-family: 'Manrope', sans-serif;
font-size: 13px;
color: var(--text-muted);
text-decoration: none;
transition: color 0.2s;
}
.footer-link:hover { color: var(--text-secondary); }
/* ── Divider line with accent ── */
.section-divider {
width: 40px;
height: 3px;
background: var(--accent);
border-radius: 2px;
margin-bottom: 16px;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
</style>
<script>
// Theme init (before render)
(function(){
const s = localStorage.getItem('theme');
if (s === '' || (s === null && !window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.remove('dark');
}
})();
</script>
</head>
<body>
<!-- ════════════════════════════════════════════
NAV
════════════════════════════════════════════ -->
<header class="top-header">
<a href="/" class="top-logo">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--accent)"><path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5"/></svg>
WA Dev <span>Tools</span>
</a>
<nav class="top-nav">
</nav>
<div class="top-right">
<button class="theme-btn" id="themeBtn" title="Переключить тему">
<svg class="sun-i" width="16" height="16" fill="currentColor" viewBox="0 0 20 20"><path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"/></svg>
<svg class="moon-i" width="16" height="16" fill="currentColor" viewBox="0 0 20 20"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/></svg>
</button>
<a href="/auth/login" class="btn-ghost">Войти</a>
<a href="/auth/register" class="btn-primary-sm">Регистрация</a>
</div>
</header>
<!-- ════════════════════════════════════════════
HERO
════════════════════════════════════════════ -->
<section class="hero">
<div class="hero-glow-1"></div>
<div class="hero-glow-2"></div>
<div class="hero-glow-3"></div>
<div class="hero-grid"></div>
<div style="position:relative;z-index:1;width:100%;">
<div class="hero-badge animate-fade-in">
<span class="dot"></span>
<span id="hero-badge-text">14 инструментов для разработчиков</span>
</div>
<div class="hero-logo animate-slide-up delay-100">
WA Dev <span>Tools</span>
</div>
<h1 class="hero-headline animate-slide-up delay-200" id="hero-headline">
Инструменты для<br>
<span class="accent">веб-разработчика</span>
</h1>
<p class="hero-sub animate-slide-up delay-300" id="hero-sub">
Сжатие картинок, форматирование кода, HTTP-клиент и ещё 10 инструментов —
всё в браузере, без установки и регистрации карты.
</p>
<div class="hero-cta-group animate-slide-up delay-400">
<a href="/auth/register" class="btn-primary">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 5H7a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-6"/>
<path d="M17 3h4v4"/>
<path d="M10 14 21 3"/>
</svg>
Начать работу
</a>
<a href="/auth/login" class="btn-secondary">
<span class="link-text">Уже есть аккаунт? Войти</span>
</a>
</div>
</div>
<a href="#tools" class="scroll-hint" aria-label="Прокрутить вниз">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"/>
</svg>
<span>Подробнее</span>
</a>
</section>
<!-- ════════════════════════════════════════════
CATEGORIES
════════════════════════════════════════════ -->
<section class="section" id="tools">
<div class="section-inner">
<div class="reveal">
<p class="section-label">Что внутри</p>
<div class="section-divider"></div>
<h2 class="section-title">Инструменты</h2>
</div>
<div class="cat-grid">
<!-- Изображения -->
<div class="cat-card reveal reveal-delay-1">
<div class="cat-icon-wrap">
<!-- Heroicons: photo -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0023.25 18.75V5.25A2.25 2.25 0 0021 3H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z"/>
<path d="M3 9h18"/>
<circle cx="9" cy="6" r="1" fill="currentColor" stroke="none"/>
<path d="M3 15l4-4 4 4 3-3 5 5"/>
</svg>
</div>
<div>
<div class="cat-header">
<span class="cat-title">Изображения</span>
<span class="cat-badge">4 инструмента</span>
</div>
</div>
<p class="cat-desc">Сжатие, редактирование, SVG и placeholder-генератор</p>
<ul class="cat-tools">
<li class="cat-tool-item">Сжатие картинок</li>
<li class="cat-tool-item">Placeholder-генератор</li>
<li class="cat-tool-item">SVG-редактор</li>
<li class="cat-tool-item">Фоторедактор</li>
</ul>
</div>
<!-- Код -->
<div class="cat-card reveal reveal-delay-2">
<div class="cat-icon-wrap">
<!-- Heroicons: code-bracket -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="m8 6-4 6 4 6"/>
<path d="m16 6 4 6-4 6"/>
<path d="m14 4-4 16"/>
</svg>
</div>
<div>
<div class="cat-header">
<span class="cat-title">Код</span>
<span class="cat-badge">3 инструмента</span>
</div>
</div>
<p class="cat-desc">Форматирование, очистка и конвертация кода между форматами</p>
<ul class="cat-tools">
<li class="cat-tool-item">Форматирование кода</li>
<li class="cat-tool-item">HTML Sanitizer</li>
<li class="cat-tool-item">Конвертер форматов</li>
</ul>
</div>
<!-- Веб -->
<div class="cat-card reveal reveal-delay-3">
<div class="cat-icon-wrap">
<!-- Heroicons: globe-alt -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"/>
<path d="M3.6 9h16.8M3.6 15h16.8"/>
<path d="M12 3a14.4 14.4 0 0 1 0 18M12 3a14.4 14.4 0 0 0 0 18"/>
</svg>
</div>
<div>
<div class="cat-header">
<span class="cat-title">Веб</span>
<span class="cat-badge">3 инструмента</span>
</div>
</div>
<p class="cat-desc">HTTP-клиент, парсер страниц и анализ редиректов</p>
<ul class="cat-tools">
<li class="cat-tool-item">HTTP-клиент</li>
<li class="cat-tool-item">Парсер статей</li>
<li class="cat-tool-item">Redirect-анализатор</li>
</ul>
</div>
<!-- Утилиты -->
<div class="cat-card reveal reveal-delay-4">
<div class="cat-icon-wrap">
<!-- Heroicons: wrench-screwdriver -->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M11.42 15.17 17.25 21A2.652 2.652 0 0 0 21 17.25l-5.877-5.877"/>
<path d="M11.42 15.17l-2.496-2.496a3.5 3.5 0 0 1-4.95-4.95l2.496 2.496"/>
<path d="m6.496 10.22 4.258-4.257a3.5 3.5 0 0 1 4.95 4.95l-4.258 4.257"/>
</svg>
</div>
<div>
<div class="cat-header">
<span class="cat-title">Утилиты</span>
<span class="cat-badge">2 инструмента</span>
</div>
</div>
<p class="cat-desc">Генератор паролей и Markdown-просмотрщик с превью</p>
<ul class="cat-tools">
<li class="cat-tool-item">Генератор паролей</li>
<li class="cat-tool-item">Markdown Viewer</li>
</ul>
</div>
</div>
</div>
</section>
<!-- ════════════════════════════════════════════
ADVANTAGES
════════════════════════════════════════════ -->
<!-- ════════════════════════════════════════════
FINAL CTA
════════════════════════════════════════════ -->
<section class="cta-section">
<div class="cta-glow reveal">
<p class="section-label" style="margin-bottom:16px;">Начните прямо сейчас</p>
<h2 class="cta-headline">Готовы начать?</h2>
<p class="cta-sub">
Зарегистрируйтесь и получите доступ ко всем инструментам.
</p>
<a href="/auth/register" class="btn-primary" style="font-size:17px;padding:17px 40px;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<line x1="19" y1="8" x2="19" y2="14"/>
<line x1="22" y1="11" x2="16" y2="11"/>
</svg>
Создать аккаунт
</a>
</div>
</section>
<!-- ════════════════════════════════════════════
FOOTER
════════════════════════════════════════════ -->
<footer class="landing-footer">
<span class="footer-copy">&copy; 2024&ndash;2026 WA Dev Tools</span>
<nav class="footer-links">
<a href="/auth/login" class="footer-link">Войти</a>
<a href="/auth/register" class="footer-link">Регистрация</a>
</nav>
</footer>
<script>
/* ── Theme toggle ── */
const tb = document.getElementById('themeBtn');
function ut() {
const d = document.documentElement.classList.contains('dark');
document.querySelectorAll('.sun-i').forEach(e => e.style.display = d ? 'none' : 'block');
document.querySelectorAll('.moon-i').forEach(e => e.style.display = d ? 'block' : 'none');
}
ut();
tb.addEventListener('click', () => {
const d = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', d ? 'dark' : '');
ut();
});
/* ── Intersection observer for reveal animations ── */
const revealEls = document.querySelectorAll('.reveal');
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
revealEls.forEach((el) => observer.observe(el));
} else {
// Fallback for old browsers
revealEls.forEach((el) => el.classList.add('visible'));
}
</script>
<!-- Yandex.Metrika counter -->
<script type="text/javascript">
(function(m,e,t,r,i,k,a){
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=108185982', 'ym');
ym(108185982, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", referrer: document.referrer, url: location.href, accurateTrackBounce:true, trackLinks:true});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/108185982" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
<script>
// Show user info if authenticated
fetch('/auth/me').then(r => r.ok ? r.json() : null).then(user => {
if (!user) return;
const right = document.querySelector('.top-right');
if (!right) return;
const loginBtn = right.querySelector('[href="/auth/login"]');
const regBtn = right.querySelector('[href="/auth/register"]');
if (loginBtn) loginBtn.remove();
if (regBtn) regBtn.remove();
const userEl = document.createElement('a');
userEl.href = '/dashboard';
userEl.className = 'btn-ghost';
userEl.textContent = user.name || user.email;
userEl.style.color = 'var(--accent)';
right.appendChild(userEl);
const logoutBtn = document.createElement('a');
logoutBtn.href = '/auth/logout';
logoutBtn.className = 'btn-ghost';
logoutBtn.textContent = 'Выйти';
right.appendChild(logoutBtn);
}).catch(() => {});
</script>
</body>
</html>
<script>
/* ── Dynamic content from API ── */
(async function() {
try {
const [settingsRes, toolsRes, advRes] = await Promise.all([
fetch('/api/settings'),
fetch('/api/tools'),
fetch('/api/content/advantages'),
]);
if (settingsRes.ok) {
const s = await settingsRes.json();
const headline = document.getElementById('hero-headline');
if (headline && s.hero_headline) {
const parts = s.hero_headline.split(' ');
const last = parts.pop();
headline.innerHTML = parts.join(' ') + '<br><span class="accent">' + last + '</span>';
}
const sub = document.getElementById('hero-sub');
if (sub && s.hero_description) sub.textContent = s.hero_description;
const badge = document.getElementById('hero-badge-text');
if (badge && s.site_tagline) badge.textContent = s.site_tagline;
}
if (toolsRes.ok) {
const cats = await toolsRes.json();
const grid = document.querySelector('.cat-grid');
if (grid && cats.length) {
grid.innerHTML = cats.map((cat, i) => {
const toolsList = cat.tools.map(t => '<li class="cat-tool-item">' + t.title + '</li>').join('');
return '<div class="cat-card reveal visible reveal-delay-' + (i+1) + '">' +
'<div class="cat-icon-wrap" style="color:' + (cat.color || '#0054e6') + '">' +
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">' + ({images:'<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5A2.25 2.25 0 0023.25 18.75V5.25A2.25 2.25 0 0021 3H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z"/>',code:'<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5"/>',web:'<circle cx="12" cy="12" r="9"/><path d="M3.6 9h16.8M3.6 15h16.8"/><path d="M12 3a14.4 14.4 0 0 1 0 18M12 3a14.4 14.4 0 0 0 0 18"/>',utils:'<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/>'}[cat.slug] || '<rect x="3" y="3" width="18" height="18" rx="3"/>') + '</svg>' +
'</div><div><div class="cat-header"><span class="cat-title">' + cat.title + '</span>' +
'<span class="cat-badge">' + cat.tools.length + ' инстр.</span></div></div>' +
'<p class="cat-desc">' + (cat.description || '') + '</p>' +
'<ul class="cat-tools">' + toolsList + '</ul></div>';
}).join('');
}
}
if (advRes.ok) {
const blocks = await advRes.json();
const advGrid = document.querySelector('.adv-grid');
if (advGrid && blocks.length) {
advGrid.innerHTML = blocks.map((b, i) =>
'<div class="adv-card reveal visible reveal-delay-' + i + '">' +
'<div class="adv-icon-wrap"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z"/></svg></div>' +
'<div class="adv-title">' + b.title + '</div>' +
'<p class="adv-desc">' + b.body + '</p></div>'
).join('');
}
}
} catch(e) { /* silent — static content stays as fallback */ }
})();
</script>