Unified header: same top-header across landing, login, register, status
- Landing: replaced custom landing-nav with standard top-header - Removed scroll listener for nav (no longer needed) - Header CSS moved to shared.css (single source) - Removed duplicate header CSS from login, register, status - All 4 public pages now have identical header with theme toggle
This commit is contained in:
parent
b7e9eb03ab
commit
27ad078e07
1070
public/landing.html
1070
public/landing.html
File diff suppressed because it is too large
Load Diff
@ -1,142 +1,126 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ru">
|
<html lang="ru" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Вход — WA Dev Tools</title>
|
<title>Вход — WA Dev Tools</title>
|
||||||
<script src="/vendor/tailwind.js"></script>
|
<script src="/vendor/tailwind.js"></script>
|
||||||
<script>
|
<script>
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme: { extend: {
|
theme: { extend: {
|
||||||
fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['Manrope', 'sans-serif'] },
|
fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['Manrope', 'sans-serif'] },
|
||||||
colors: { surface: { 900: '#060c18', 800: '#091020', 700: '#0d1828', 600: '#162040' }, accent: { DEFAULT: '#0054e6', dim: '#0043b8', bright: '#6b9fff' } }
|
colors: { surface: { 900: '#060c18', 800: '#091020', 700: '#0d1828', 600: '#162040' }, accent: { DEFAULT: '#0054e6', dim: '#0043b8', bright: '#6b9fff' } }
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<link href="/vendor/fonts.css" rel="stylesheet">
|
<link href="/vendor/fonts.css" rel="stylesheet">
|
||||||
<link href="/shared.css" rel="stylesheet">
|
<link href="/shared.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* Header */
|
|
||||||
.top-header { position:fixed; top:0; left:0; right:0; height:56px; background:var(--surface-800); border-bottom:1px solid var(--surface-600); display:flex; align-items:center; padding:0 24px; z-index:50; backdrop-filter:blur(12px); }
|
/* Content */
|
||||||
.top-logo { font-size:18px; font-weight:800; color:var(--text-primary); text-decoration:none; display:flex; align-items:center; gap:8px; }
|
.page-body { padding-top:56px; min-height:100vh; display:flex; align-items:center; justify-content:center; }
|
||||||
.top-logo span { color:var(--accent); }
|
.auth-card { max-width:420px; width:100%; padding:20px; }
|
||||||
.top-nav { display:flex; align-items:center; gap:6px; margin-left:32px; }
|
.auth-card-inner { background:var(--surface-700); border:1px solid var(--surface-600); border-radius:16px; padding:36px 32px; box-shadow:0 4px 24px rgba(0,0,0,.1); }
|
||||||
.top-nav a { font-size:13px; color:var(--text-muted); text-decoration:none; padding:6px 12px; border-radius:8px; transition:all .15s; font-weight:500; }
|
.auth-title { font-size:24px; font-weight:800; color:var(--text-primary); text-align:center; }
|
||||||
.top-nav a:hover { color:var(--text-secondary); background:rgba(255,255,255,.04); }
|
.auth-subtitle { font-size:14px; color:var(--text-muted); text-align:center; margin-top:6px; }
|
||||||
.top-right { margin-left:auto; display:flex; align-items:center; gap:8px; }
|
.auth-label { display:block; font-size:13px; color:var(--text-muted); margin-bottom:6px; font-family:'JetBrains Mono',monospace; font-weight:500; }
|
||||||
.btn-ghost { font-size:13px; color:var(--text-muted); text-decoration:none; padding:8px 16px; border-radius:8px; transition:all .15s; font-weight:600; }
|
.auth-input { width:100%; background:var(--select-bg); border:1px solid var(--surface-600); color:var(--select-color); padding:12px 16px; border-radius:10px; font-family:'Manrope',sans-serif; font-size:15px; outline:none; transition:border-color .2s; }
|
||||||
.btn-ghost:hover { color:var(--text-secondary); background:rgba(255,255,255,.04); }
|
.auth-input:focus { border-color:var(--accent); }
|
||||||
.btn-ghost.active { color:var(--accent); background:var(--accent-bg); }
|
.auth-input::placeholder { color:var(--text-muted); opacity:.6; }
|
||||||
.btn-primary-sm { font-size:13px; color:#fff; background:var(--accent); padding:8px 18px; border-radius:8px; text-decoration:none; font-weight:600; transition:background .15s; }
|
.auth-btn { width:100%; padding:14px; background:var(--accent); color:#fff; border:none; border-radius:10px; font-family:'Manrope',sans-serif; font-weight:700; font-size:16px; cursor:pointer; transition:background .2s; }
|
||||||
.btn-primary-sm:hover { background:var(--accent-dim); }
|
.auth-btn:hover { background:var(--accent-dim); }
|
||||||
.theme-btn { width:32px; height:32px; border:none; background:none; cursor:pointer; color:var(--text-muted); display:flex; align-items:center; justify-content:center; border-radius:8px; transition:all .15s; }
|
.auth-btn:disabled { opacity:0.5; cursor:not-allowed; }
|
||||||
.theme-btn:hover { background:rgba(255,255,255,.04); color:var(--text-secondary); }
|
.auth-error { background:rgba(255,82,82,.08); border:1px solid rgba(255,82,82,.25); color:#ff5252; padding:10px 14px; border-radius:8px; font-size:13px; display:none; }
|
||||||
@media(max-width:640px) { .top-nav { display:none; } .top-header { padding:0 16px; } }
|
.auth-error.visible { display:block; }
|
||||||
|
.auth-footer { text-align:center; margin-top:20px; font-size:14px; color:var(--text-muted); }
|
||||||
/* Content */
|
.auth-footer a { color:var(--accent); text-decoration:none; font-weight:600; }
|
||||||
.page-body { padding-top:56px; min-height:100vh; display:flex; align-items:center; justify-content:center; }
|
.auth-footer a:hover { text-decoration:underline; }
|
||||||
.auth-card { max-width:420px; width:100%; padding:20px; }
|
.auth-divider { height:1px; background:var(--surface-600); margin:20px 0; }
|
||||||
.auth-card-inner { background:var(--surface-700); border:1px solid var(--surface-600); border-radius:16px; padding:36px 32px; box-shadow:0 4px 24px rgba(0,0,0,.1); }
|
</style>
|
||||||
.auth-title { font-size:24px; font-weight:800; color:var(--text-primary); text-align:center; }
|
<script>
|
||||||
.auth-subtitle { font-size:14px; color:var(--text-muted); text-align:center; margin-top:6px; }
|
// Theme init (before render)
|
||||||
.auth-label { display:block; font-size:13px; color:var(--text-muted); margin-bottom:6px; font-family:'JetBrains Mono',monospace; font-weight:500; }
|
(function(){
|
||||||
.auth-input { width:100%; background:var(--select-bg); border:1px solid var(--surface-600); color:var(--select-color); padding:12px 16px; border-radius:10px; font-family:'Manrope',sans-serif; font-size:15px; outline:none; transition:border-color .2s; }
|
const s = localStorage.getItem('theme');
|
||||||
.auth-input:focus { border-color:var(--accent); }
|
if (s === '' || (s === null && !window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
.auth-input::placeholder { color:var(--text-muted); opacity:.6; }
|
document.documentElement.classList.remove('dark');
|
||||||
.auth-btn { width:100%; padding:14px; background:var(--accent); color:#fff; border:none; border-radius:10px; font-family:'Manrope',sans-serif; font-weight:700; font-size:16px; cursor:pointer; transition:background .2s; }
|
}
|
||||||
.auth-btn:hover { background:var(--accent-dim); }
|
})();
|
||||||
.auth-btn:disabled { opacity:0.5; cursor:not-allowed; }
|
</script>
|
||||||
.auth-error { background:rgba(255,82,82,.08); border:1px solid rgba(255,82,82,.25); color:#ff5252; padding:10px 14px; border-radius:8px; font-size:13px; display:none; }
|
</head>
|
||||||
.auth-error.visible { display:block; }
|
<body>
|
||||||
.auth-footer { text-align:center; margin-top:20px; font-size:14px; color:var(--text-muted); }
|
|
||||||
.auth-footer a { color:var(--accent); text-decoration:none; font-weight:600; }
|
<header class="top-header">
|
||||||
.auth-footer a:hover { text-decoration:underline; }
|
<a href="/" class="top-logo">
|
||||||
.auth-divider { height:1px; background:var(--surface-600); margin:20px 0; }
|
<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>
|
||||||
</style>
|
WA Dev <span>Tools</span>
|
||||||
<script>
|
</a>
|
||||||
// Theme init (before render)
|
<nav class="top-nav">
|
||||||
(function(){
|
<a href="/">Главная</a>
|
||||||
const s = localStorage.getItem('theme');
|
<a href="/#tools">Инструменты</a>
|
||||||
if (s !== 'dark') {
|
<a href="/#features">Возможности</a>
|
||||||
document.documentElement.classList.remove('dark');
|
</nav>
|
||||||
}
|
<div class="top-right">
|
||||||
})();
|
<button class="theme-btn" id="themeBtn" title="Переключить тему">
|
||||||
</script>
|
<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>
|
||||||
</head>
|
<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>
|
||||||
<body>
|
</button>
|
||||||
|
<a href="/auth/login" class="btn-ghost active">Войти</a>
|
||||||
<header class="top-header">
|
<a href="/auth/register" class="btn-primary-sm">Регистрация</a>
|
||||||
<a href="/" class="top-logo">
|
</div>
|
||||||
<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>
|
</header>
|
||||||
WA Dev <span>Tools</span>
|
|
||||||
</a>
|
<div class="page-body">
|
||||||
<nav class="top-nav">
|
<div class="auth-card">
|
||||||
<a href="/">Главная</a>
|
<div class="auth-card-inner">
|
||||||
<a href="/#tools">Инструменты</a>
|
<div class="auth-title">Вход в аккаунт</div>
|
||||||
<a href="/#features">Возможности</a>
|
<div class="auth-subtitle">Добро пожаловать обратно</div>
|
||||||
</nav>
|
<div id="error" class="auth-error" style="margin-top:20px;"></div>
|
||||||
<div class="top-right">
|
<form id="loginForm" style="display:flex;flex-direction:column;gap:16px;margin-top:24px;" onsubmit="return handleLogin(event)">
|
||||||
<button class="theme-btn" id="themeBtn" title="Переключить тему">
|
<div>
|
||||||
<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>
|
<label class="auth-label">Email</label>
|
||||||
<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>
|
<input class="auth-input" type="email" name="email" required autofocus autocomplete="email" placeholder="you@example.com" />
|
||||||
</button>
|
</div>
|
||||||
<a href="/auth/login" class="btn-ghost active">Войти</a>
|
<div>
|
||||||
<a href="/auth/register" class="btn-primary-sm">Регистрация</a>
|
<label class="auth-label">Пароль</label>
|
||||||
</div>
|
<input class="auth-input" type="password" name="password" required autocomplete="current-password" placeholder="Минимум 6 символов" />
|
||||||
</header>
|
</div>
|
||||||
|
<button class="auth-btn" type="submit" id="submitBtn">Войти</button>
|
||||||
<div class="page-body">
|
</form>
|
||||||
<div class="auth-card">
|
<div class="auth-divider"></div>
|
||||||
<div class="auth-card-inner">
|
<div class="auth-footer">Нет аккаунта? <a href="/auth/register">Зарегистрируйтесь</a></div>
|
||||||
<div class="auth-title">Вход в аккаунт</div>
|
</div>
|
||||||
<div class="auth-subtitle">Добро пожаловать обратно</div>
|
</div>
|
||||||
<div id="error" class="auth-error" style="margin-top:20px;"></div>
|
</div>
|
||||||
<form id="loginForm" style="display:flex;flex-direction:column;gap:16px;margin-top:24px;" onsubmit="return handleLogin(event)">
|
|
||||||
<div>
|
<script>
|
||||||
<label class="auth-label">Email</label>
|
// Theme toggle
|
||||||
<input class="auth-input" type="email" name="email" required autofocus autocomplete="email" placeholder="you@example.com" />
|
const _tb = document.getElementById('themeBtn');
|
||||||
</div>
|
function _ut() {
|
||||||
<div>
|
const d = document.documentElement.classList.contains('dark');
|
||||||
<label class="auth-label">Пароль</label>
|
document.querySelectorAll('.sun-i').forEach(e => e.style.display = d ? 'none' : 'block');
|
||||||
<input class="auth-input" type="password" name="password" required autocomplete="current-password" placeholder="Минимум 6 символов" />
|
document.querySelectorAll('.moon-i').forEach(e => e.style.display = d ? 'block' : 'none');
|
||||||
</div>
|
}
|
||||||
<button class="auth-btn" type="submit" id="submitBtn">Войти</button>
|
_ut();
|
||||||
</form>
|
_tb.addEventListener('click', () => {
|
||||||
<div class="auth-divider"></div>
|
const d = document.documentElement.classList.toggle('dark');
|
||||||
<div class="auth-footer">Нет аккаунта? <a href="/auth/register">Зарегистрируйтесь</a></div>
|
localStorage.setItem('theme', d ? 'dark' : '');
|
||||||
</div>
|
_ut();
|
||||||
</div>
|
});
|
||||||
</div>
|
|
||||||
|
// Login
|
||||||
<script>
|
async function handleLogin(e) {
|
||||||
// Theme toggle
|
e.preventDefault();
|
||||||
const _tb = document.getElementById('themeBtn');
|
const form = e.target, btn = document.getElementById('submitBtn'), err = document.getElementById('error');
|
||||||
function _ut() {
|
err.classList.remove('visible'); btn.disabled = true; btn.textContent = 'Вход...';
|
||||||
const d = document.documentElement.classList.contains('dark');
|
try {
|
||||||
document.querySelectorAll('.sun-i').forEach(e => e.style.display = d ? 'none' : 'block');
|
const res = await fetch('/auth/login', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({email:form.email.value,password:form.password.value}) });
|
||||||
document.querySelectorAll('.moon-i').forEach(e => e.style.display = d ? 'block' : 'none');
|
const data = await res.json();
|
||||||
}
|
if (data.ok) window.location.href = data.redirect || '/dashboard';
|
||||||
_ut();
|
else { err.textContent = data.error; err.classList.add('visible'); }
|
||||||
_tb.addEventListener('click', () => {
|
} catch { err.textContent = 'Ошибка сети'; err.classList.add('visible'); }
|
||||||
const d = document.documentElement.classList.toggle('dark');
|
btn.disabled = false; btn.textContent = 'Войти';
|
||||||
localStorage.setItem('theme', d ? 'dark' : '');
|
}
|
||||||
_ut();
|
</script>
|
||||||
});
|
</body>
|
||||||
|
</html>
|
||||||
// Login
|
|
||||||
async function handleLogin(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const form = e.target, btn = document.getElementById('submitBtn'), err = document.getElementById('error');
|
|
||||||
err.classList.remove('visible'); btn.disabled = true; btn.textContent = 'Вход...';
|
|
||||||
try {
|
|
||||||
const res = await fetch('/auth/login', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({email:form.email.value,password:form.password.value}) });
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.ok) window.location.href = data.redirect || '/dashboard';
|
|
||||||
else { err.textContent = data.error; err.classList.add('visible'); }
|
|
||||||
} catch { err.textContent = 'Ошибка сети'; err.classList.add('visible'); }
|
|
||||||
btn.disabled = false; btn.textContent = 'Войти';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@ -1,151 +1,135 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ru">
|
<html lang="ru" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Регистрация — WA Dev Tools</title>
|
<title>Регистрация — WA Dev Tools</title>
|
||||||
<script src="/vendor/tailwind.js"></script>
|
<script src="/vendor/tailwind.js"></script>
|
||||||
<script>
|
<script>
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme: { extend: {
|
theme: { extend: {
|
||||||
fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['Manrope', 'sans-serif'] },
|
fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['Manrope', 'sans-serif'] },
|
||||||
colors: { surface: { 900: '#060c18', 800: '#091020', 700: '#0d1828', 600: '#162040' }, accent: { DEFAULT: '#0054e6', dim: '#0043b8', bright: '#6b9fff' } }
|
colors: { surface: { 900: '#060c18', 800: '#091020', 700: '#0d1828', 600: '#162040' }, accent: { DEFAULT: '#0054e6', dim: '#0043b8', bright: '#6b9fff' } }
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<link href="/vendor/fonts.css" rel="stylesheet">
|
<link href="/vendor/fonts.css" rel="stylesheet">
|
||||||
<link href="/shared.css" rel="stylesheet">
|
<link href="/shared.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
/* Header */
|
|
||||||
.top-header { position:fixed; top:0; left:0; right:0; height:56px; background:var(--surface-800); border-bottom:1px solid var(--surface-600); display:flex; align-items:center; padding:0 24px; z-index:50; backdrop-filter:blur(12px); }
|
/* Content */
|
||||||
.top-logo { font-size:18px; font-weight:800; color:var(--text-primary); text-decoration:none; display:flex; align-items:center; gap:8px; }
|
.page-body { padding-top:56px; min-height:100vh; display:flex; align-items:center; justify-content:center; }
|
||||||
.top-logo span { color:var(--accent); }
|
.auth-card { max-width:420px; width:100%; padding:20px; }
|
||||||
.top-nav { display:flex; align-items:center; gap:6px; margin-left:32px; }
|
.auth-card-inner { background:var(--surface-700); border:1px solid var(--surface-600); border-radius:16px; padding:36px 32px; box-shadow:0 4px 24px rgba(0,0,0,.1); }
|
||||||
.top-nav a { font-size:13px; color:var(--text-muted); text-decoration:none; padding:6px 12px; border-radius:8px; transition:all .15s; font-weight:500; }
|
.auth-title { font-size:24px; font-weight:800; color:var(--text-primary); text-align:center; }
|
||||||
.top-nav a:hover { color:var(--text-secondary); background:rgba(255,255,255,.04); }
|
.auth-subtitle { font-size:14px; color:var(--text-muted); text-align:center; margin-top:6px; }
|
||||||
.top-right { margin-left:auto; display:flex; align-items:center; gap:8px; }
|
.auth-label { display:block; font-size:13px; color:var(--text-muted); margin-bottom:6px; font-family:'JetBrains Mono',monospace; font-weight:500; }
|
||||||
.btn-ghost { font-size:13px; color:var(--text-muted); text-decoration:none; padding:8px 16px; border-radius:8px; transition:all .15s; font-weight:600; }
|
.auth-input { width:100%; background:var(--select-bg); border:1px solid var(--surface-600); color:var(--select-color); padding:12px 16px; border-radius:10px; font-family:'Manrope',sans-serif; font-size:15px; outline:none; transition:border-color .2s; }
|
||||||
.btn-ghost:hover { color:var(--text-secondary); background:rgba(255,255,255,.04); }
|
.auth-input:focus { border-color:var(--accent); }
|
||||||
.btn-primary-sm { font-size:13px; color:#fff; background:var(--accent); padding:8px 18px; border-radius:8px; text-decoration:none; font-weight:600; transition:background .15s; }
|
.auth-input::placeholder { color:var(--text-muted); opacity:.6; }
|
||||||
.btn-primary-sm:hover { background:var(--accent-dim); }
|
.auth-btn { width:100%; padding:14px; background:var(--accent); color:#fff; border:none; border-radius:10px; font-family:'Manrope',sans-serif; font-weight:700; font-size:16px; cursor:pointer; transition:background .2s; }
|
||||||
.btn-primary-sm.active { box-shadow:0 0 0 2px var(--accent), 0 0 0 4px rgba(0,84,230,.15); }
|
.auth-btn:hover { background:var(--accent-dim); }
|
||||||
.theme-btn { width:32px; height:32px; border:none; background:none; cursor:pointer; color:var(--text-muted); display:flex; align-items:center; justify-content:center; border-radius:8px; transition:all .15s; }
|
.auth-btn:disabled { opacity:0.5; cursor:not-allowed; }
|
||||||
.theme-btn:hover { background:rgba(255,255,255,.04); color:var(--text-secondary); }
|
.auth-error { background:rgba(255,82,82,.08); border:1px solid rgba(255,82,82,.25); color:#ff5252; padding:10px 14px; border-radius:8px; font-size:13px; display:none; }
|
||||||
@media(max-width:640px) { .top-nav { display:none; } .top-header { padding:0 16px; } }
|
.auth-error.visible { display:block; }
|
||||||
|
.auth-footer { text-align:center; margin-top:20px; font-size:14px; color:var(--text-muted); }
|
||||||
/* Content */
|
.auth-footer a { color:var(--accent); text-decoration:none; font-weight:600; }
|
||||||
.page-body { padding-top:56px; min-height:100vh; display:flex; align-items:center; justify-content:center; }
|
.auth-footer a:hover { text-decoration:underline; }
|
||||||
.auth-card { max-width:420px; width:100%; padding:20px; }
|
.auth-divider { height:1px; background:var(--surface-600); margin:20px 0; }
|
||||||
.auth-card-inner { background:var(--surface-700); border:1px solid var(--surface-600); border-radius:16px; padding:36px 32px; box-shadow:0 4px 24px rgba(0,0,0,.1); }
|
.auth-hint { font-size:11px; color:var(--text-muted); margin-top:4px; font-family:'JetBrains Mono',monospace; opacity:.7; }
|
||||||
.auth-title { font-size:24px; font-weight:800; color:var(--text-primary); text-align:center; }
|
</style>
|
||||||
.auth-subtitle { font-size:14px; color:var(--text-muted); text-align:center; margin-top:6px; }
|
<script>
|
||||||
.auth-label { display:block; font-size:13px; color:var(--text-muted); margin-bottom:6px; font-family:'JetBrains Mono',monospace; font-weight:500; }
|
(function(){
|
||||||
.auth-input { width:100%; background:var(--select-bg); border:1px solid var(--surface-600); color:var(--select-color); padding:12px 16px; border-radius:10px; font-family:'Manrope',sans-serif; font-size:15px; outline:none; transition:border-color .2s; }
|
const s = localStorage.getItem('theme');
|
||||||
.auth-input:focus { border-color:var(--accent); }
|
if (s === '' || (s === null && !window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
.auth-input::placeholder { color:var(--text-muted); opacity:.6; }
|
document.documentElement.classList.remove('dark');
|
||||||
.auth-btn { width:100%; padding:14px; background:var(--accent); color:#fff; border:none; border-radius:10px; font-family:'Manrope',sans-serif; font-weight:700; font-size:16px; cursor:pointer; transition:background .2s; }
|
}
|
||||||
.auth-btn:hover { background:var(--accent-dim); }
|
})();
|
||||||
.auth-btn:disabled { opacity:0.5; cursor:not-allowed; }
|
</script>
|
||||||
.auth-error { background:rgba(255,82,82,.08); border:1px solid rgba(255,82,82,.25); color:#ff5252; padding:10px 14px; border-radius:8px; font-size:13px; display:none; }
|
</head>
|
||||||
.auth-error.visible { display:block; }
|
<body>
|
||||||
.auth-footer { text-align:center; margin-top:20px; font-size:14px; color:var(--text-muted); }
|
|
||||||
.auth-footer a { color:var(--accent); text-decoration:none; font-weight:600; }
|
<header class="top-header">
|
||||||
.auth-footer a:hover { text-decoration:underline; }
|
<a href="/" class="top-logo">
|
||||||
.auth-divider { height:1px; background:var(--surface-600); margin:20px 0; }
|
<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>
|
||||||
.auth-hint { font-size:11px; color:var(--text-muted); margin-top:4px; font-family:'JetBrains Mono',monospace; opacity:.7; }
|
WA Dev <span>Tools</span>
|
||||||
</style>
|
</a>
|
||||||
<script>
|
<nav class="top-nav">
|
||||||
(function(){
|
<a href="/">Главная</a>
|
||||||
const s = localStorage.getItem('theme');
|
<a href="/#tools">Инструменты</a>
|
||||||
if (s !== 'dark') {
|
<a href="/#features">Возможности</a>
|
||||||
document.documentElement.classList.remove('dark');
|
</nav>
|
||||||
}
|
<div class="top-right">
|
||||||
})();
|
<button class="theme-btn" id="themeBtn" title="Переключить тему">
|
||||||
</script>
|
<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>
|
||||||
</head>
|
<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>
|
||||||
<body>
|
</button>
|
||||||
|
<a href="/auth/login" class="btn-ghost">Войти</a>
|
||||||
<header class="top-header">
|
<a href="/auth/register" class="btn-primary-sm active">Регистрация</a>
|
||||||
<a href="/" class="top-logo">
|
</div>
|
||||||
<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>
|
</header>
|
||||||
WA Dev <span>Tools</span>
|
|
||||||
</a>
|
<div class="page-body">
|
||||||
<nav class="top-nav">
|
<div class="auth-card">
|
||||||
<a href="/">Главная</a>
|
<div class="auth-card-inner">
|
||||||
<a href="/#tools">Инструменты</a>
|
<div class="auth-title">Создать аккаунт</div>
|
||||||
<a href="/#features">Возможности</a>
|
<div class="auth-subtitle">Бесплатный доступ ко всем инструментам</div>
|
||||||
</nav>
|
<div id="error" class="auth-error" style="margin-top:20px;"></div>
|
||||||
<div class="top-right">
|
<form id="regForm" style="display:flex;flex-direction:column;gap:16px;margin-top:24px;" onsubmit="return handleRegister(event)">
|
||||||
<button class="theme-btn" id="themeBtn" title="Переключить тему">
|
<div>
|
||||||
<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>
|
<label class="auth-label">Имя</label>
|
||||||
<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>
|
<input class="auth-input" type="text" name="name" placeholder="Как вас зовут" autocomplete="name" />
|
||||||
</button>
|
</div>
|
||||||
<a href="/auth/login" class="btn-ghost">Войти</a>
|
<div>
|
||||||
<a href="/auth/register" class="btn-primary-sm active">Регистрация</a>
|
<label class="auth-label">Email</label>
|
||||||
</div>
|
<input class="auth-input" type="email" name="email" required autofocus autocomplete="email" placeholder="you@example.com" />
|
||||||
</header>
|
</div>
|
||||||
|
<div>
|
||||||
<div class="page-body">
|
<label class="auth-label">Пароль</label>
|
||||||
<div class="auth-card">
|
<input class="auth-input" type="password" name="password" required minlength="6" autocomplete="new-password" placeholder="Придумайте пароль" />
|
||||||
<div class="auth-card-inner">
|
<div class="auth-hint">минимум 6 символов</div>
|
||||||
<div class="auth-title">Создать аккаунт</div>
|
</div>
|
||||||
<div class="auth-subtitle">Доступ ко всем инструментам</div>
|
<div>
|
||||||
<div id="error" class="auth-error" style="margin-top:20px;"></div>
|
<label class="auth-label">Подтвердите пароль</label>
|
||||||
<form id="regForm" style="display:flex;flex-direction:column;gap:16px;margin-top:24px;" onsubmit="return handleRegister(event)">
|
<input class="auth-input" type="password" name="password2" required minlength="6" autocomplete="new-password" placeholder="Повторите пароль" />
|
||||||
<div>
|
</div>
|
||||||
<label class="auth-label">Имя</label>
|
<button class="auth-btn" type="submit" id="submitBtn">Зарегистрироваться</button>
|
||||||
<input class="auth-input" type="text" name="name" placeholder="Как вас зовут" autocomplete="name" />
|
</form>
|
||||||
</div>
|
<div class="auth-divider"></div>
|
||||||
<div>
|
<div class="auth-footer">Уже есть аккаунт? <a href="/auth/login">Войти</a></div>
|
||||||
<label class="auth-label">Email</label>
|
</div>
|
||||||
<input class="auth-input" type="email" name="email" required autofocus autocomplete="email" placeholder="you@example.com" />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label class="auth-label">Пароль</label>
|
<script>
|
||||||
<input class="auth-input" type="password" name="password" required minlength="6" autocomplete="new-password" placeholder="Придумайте пароль" />
|
const _tb = document.getElementById('themeBtn');
|
||||||
<div class="auth-hint">минимум 6 символов</div>
|
function _ut() {
|
||||||
</div>
|
const d = document.documentElement.classList.contains('dark');
|
||||||
<div>
|
document.querySelectorAll('.sun-i').forEach(e => e.style.display = d ? 'none' : 'block');
|
||||||
<label class="auth-label">Подтвердите пароль</label>
|
document.querySelectorAll('.moon-i').forEach(e => e.style.display = d ? 'block' : 'none');
|
||||||
<input class="auth-input" type="password" name="password2" required minlength="6" autocomplete="new-password" placeholder="Повторите пароль" />
|
}
|
||||||
</div>
|
_ut();
|
||||||
<button class="auth-btn" type="submit" id="submitBtn">Зарегистрироваться</button>
|
_tb.addEventListener('click', () => {
|
||||||
</form>
|
const d = document.documentElement.classList.toggle('dark');
|
||||||
<div class="auth-divider"></div>
|
localStorage.setItem('theme', d ? 'dark' : '');
|
||||||
<div class="auth-footer">Уже есть аккаунт? <a href="/auth/login">Войти</a></div>
|
_ut();
|
||||||
</div>
|
});
|
||||||
</div>
|
|
||||||
</div>
|
async function handleRegister(e) {
|
||||||
|
e.preventDefault();
|
||||||
<script>
|
const form = e.target, btn = document.getElementById('submitBtn'), err = document.getElementById('error');
|
||||||
const _tb = document.getElementById('themeBtn');
|
err.classList.remove('visible');
|
||||||
function _ut() {
|
if (form.password.value !== form.password2.value) { err.textContent = 'Пароли не совпадают'; err.classList.add('visible'); return; }
|
||||||
const d = document.documentElement.classList.contains('dark');
|
btn.disabled = true; btn.textContent = 'Регистрация...';
|
||||||
document.querySelectorAll('.sun-i').forEach(e => e.style.display = d ? 'none' : 'block');
|
try {
|
||||||
document.querySelectorAll('.moon-i').forEach(e => e.style.display = d ? 'block' : 'none');
|
const res = await fetch('/auth/register', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({name:form.name.value,email:form.email.value,password:form.password.value,password2:form.password2.value}) });
|
||||||
}
|
const data = await res.json();
|
||||||
_ut();
|
if (data.ok) window.location.href = data.redirect || '/dashboard';
|
||||||
_tb.addEventListener('click', () => {
|
else { err.textContent = data.error; err.classList.add('visible'); }
|
||||||
const d = document.documentElement.classList.toggle('dark');
|
} catch { err.textContent = 'Ошибка сети'; err.classList.add('visible'); }
|
||||||
localStorage.setItem('theme', d ? 'dark' : '');
|
btn.disabled = false; btn.textContent = 'Зарегистрироваться';
|
||||||
_ut();
|
}
|
||||||
});
|
</script>
|
||||||
|
</body>
|
||||||
async function handleRegister(e) {
|
</html>
|
||||||
e.preventDefault();
|
|
||||||
const form = e.target, btn = document.getElementById('submitBtn'), err = document.getElementById('error');
|
|
||||||
err.classList.remove('visible');
|
|
||||||
if (form.password.value !== form.password2.value) { err.textContent = 'Пароли не совпадают'; err.classList.add('visible'); return; }
|
|
||||||
btn.disabled = true; btn.textContent = 'Регистрация...';
|
|
||||||
try {
|
|
||||||
const res = await fetch('/auth/register', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({name:form.name.value,email:form.email.value,password:form.password.value,password2:form.password2.value}) });
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.ok) window.location.href = data.redirect || '/dashboard';
|
|
||||||
else { err.textContent = data.error; err.classList.add('visible'); }
|
|
||||||
} catch { err.textContent = 'Ошибка сети'; err.classList.add('visible'); }
|
|
||||||
btn.disabled = false; btn.textContent = 'Зарегистрироваться';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@ -98,6 +98,24 @@ body {
|
|||||||
.wa-btn:disabled { opacity:0.5;cursor:not-allowed }
|
.wa-btn:disabled { opacity:0.5;cursor:not-allowed }
|
||||||
.wa-card { background:var(--surface-700);border:1px solid var(--surface-600);border-radius:12px;padding:20px;transition:background .3s,border-color .3s }
|
.wa-card { background:var(--surface-700);border:1px solid var(--surface-600);border-radius:12px;padding:20px;transition:background .3s,border-color .3s }
|
||||||
|
|
||||||
|
/* Top header (public pages: landing, auth, status) */
|
||||||
|
.top-header { position:fixed;top:0;left:0;right:0;height:56px;background:var(--surface-800);border-bottom:1px solid var(--surface-600);display:flex;align-items:center;padding:0 24px;z-index:50;backdrop-filter:blur(12px) }
|
||||||
|
.top-logo { font-size:18px;font-weight:800;color:var(--text-primary);text-decoration:none;display:flex;align-items:center;gap:8px }
|
||||||
|
.top-logo span { color:var(--accent) }
|
||||||
|
.top-nav { display:flex;align-items:center;gap:6px;margin-left:32px }
|
||||||
|
.top-nav a { font-size:13px;color:var(--text-muted);text-decoration:none;padding:6px 12px;border-radius:8px;transition:all .15s;font-weight:500 }
|
||||||
|
.top-nav a:hover { color:var(--text-secondary);background:rgba(255,255,255,.04) }
|
||||||
|
.top-right { margin-left:auto;display:flex;align-items:center;gap:8px }
|
||||||
|
.btn-ghost { font-size:13px;color:var(--text-muted);text-decoration:none;padding:8px 16px;border-radius:8px;transition:all .15s;font-weight:600 }
|
||||||
|
.btn-ghost:hover { color:var(--text-secondary);background:rgba(255,255,255,.04) }
|
||||||
|
.btn-ghost.active { color:var(--accent);background:var(--accent-bg) }
|
||||||
|
.btn-primary-sm { font-size:13px;color:#fff;background:var(--accent);padding:8px 18px;border-radius:8px;text-decoration:none;font-weight:600;transition:background .15s }
|
||||||
|
.btn-primary-sm:hover { background:var(--accent-dim) }
|
||||||
|
.btn-primary-sm.active { box-shadow:0 0 0 2px var(--accent),0 0 0 4px rgba(0,84,230,.15) }
|
||||||
|
.theme-btn { width:32px;height:32px;border:none;background:none;cursor:pointer;color:var(--text-muted);display:flex;align-items:center;justify-content:center;border-radius:8px;transition:all .15s }
|
||||||
|
.theme-btn:hover { background:rgba(255,255,255,.04);color:var(--text-secondary) }
|
||||||
|
@media(max-width:640px) { .top-nav { display:none } .top-header { padding:0 16px } }
|
||||||
|
|
||||||
/* Scrollbar */
|
/* Scrollbar */
|
||||||
::-webkit-scrollbar { width:6px;height:6px }
|
::-webkit-scrollbar { width:6px;height:6px }
|
||||||
::-webkit-scrollbar-track { background:transparent }
|
::-webkit-scrollbar-track { background:transparent }
|
||||||
|
|||||||
@ -1,144 +1,130 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ru">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Status — WA Dev Tools</title>
|
<title>Status — WA Dev Tools</title>
|
||||||
<script src="/vendor/tailwind.js"></script>
|
<script src="/vendor/tailwind.js"></script>
|
||||||
<script>
|
<script>
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
theme: { extend: {
|
theme: { extend: {
|
||||||
fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['Manrope', 'sans-serif'] },
|
fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['Manrope', 'sans-serif'] },
|
||||||
colors: { surface: { 900: '#060c18', 800: '#091020', 700: '#0d1828', 600: '#162040' }, accent: { DEFAULT: '#0054e6', dim: '#0043b8', bright: '#6b9fff' } }
|
colors: { surface: { 900: '#060c18', 800: '#091020', 700: '#0d1828', 600: '#162040' }, accent: { DEFAULT: '#0054e6', dim: '#0043b8', bright: '#6b9fff' } }
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<link href="/vendor/fonts.css" rel="stylesheet">
|
<link href="/vendor/fonts.css" rel="stylesheet">
|
||||||
<link href="/shared.css" rel="stylesheet">
|
<link href="/shared.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
.top-header { position:fixed; top:0; left:0; right:0; height:56px; background:var(--surface-800); border-bottom:1px solid var(--surface-600); display:flex; align-items:center; padding:0 24px; z-index:50; backdrop-filter:blur(12px); }
|
|
||||||
.top-logo { font-size:18px; font-weight:800; color:var(--text-primary); text-decoration:none; display:flex; align-items:center; gap:8px; }
|
.status-page { padding-top:80px; max-width:680px; margin:0 auto; padding-left:20px; padding-right:20px; padding-bottom:60px; }
|
||||||
.top-logo span { color:var(--accent); }
|
.overall-banner { border-radius:12px; padding:24px 28px; margin-bottom:32px; display:flex; align-items:center; gap:14px; }
|
||||||
.top-nav { display:flex; align-items:center; gap:6px; margin-left:32px; }
|
.overall-banner.operational { background:rgba(34,197,94,.08); border:1px solid rgba(34,197,94,.2); }
|
||||||
.top-nav a { font-size:13px; color:var(--text-muted); text-decoration:none; padding:6px 12px; border-radius:8px; transition:all .15s; font-weight:500; }
|
.overall-banner.degraded { background:rgba(234,179,8,.08); border:1px solid rgba(234,179,8,.2); }
|
||||||
.top-nav a:hover { color:var(--text-secondary); background:rgba(255,255,255,.04); }
|
.overall-banner.outage { background:rgba(239,68,68,.08); border:1px solid rgba(239,68,68,.2); }
|
||||||
.top-right { margin-left:auto; display:flex; align-items:center; gap:8px; }
|
.overall-dot { width:10px; height:10px; border-radius:50%; flex-shrink:0; }
|
||||||
.btn-ghost { font-size:13px; color:var(--text-muted); text-decoration:none; padding:8px 16px; border-radius:8px; transition:all .15s; font-weight:600; }
|
.overall-dot.operational { background:#22c55e; }
|
||||||
.btn-ghost:hover { color:var(--text-secondary); background:rgba(255,255,255,.04); }
|
.overall-dot.degraded { background:#eab308; }
|
||||||
.btn-primary-sm { font-size:13px; color:#fff; background:var(--accent); padding:8px 18px; border-radius:8px; text-decoration:none; font-weight:600; transition:background .15s; }
|
.overall-dot.outage { background:#ef4444; }
|
||||||
.btn-primary-sm:hover { background:var(--accent-dim); }
|
.overall-text { font-size:16px; font-weight:700; color:var(--text-primary); }
|
||||||
.theme-btn { width:32px; height:32px; border:none; background:none; cursor:pointer; color:var(--text-muted); display:flex; align-items:center; justify-content:center; border-radius:8px; transition:all .15s; }
|
.service-list { display:flex; flex-direction:column; }
|
||||||
.theme-btn:hover { background:rgba(255,255,255,.04); color:var(--text-secondary); }
|
.service-row { display:flex; align-items:center; justify-content:space-between; padding:16px 0; border-bottom:1px solid var(--surface-600); }
|
||||||
@media(max-width:640px) { .top-nav { display:none; } .top-header { padding:0 16px; } }
|
.service-row:last-child { border-bottom:none; }
|
||||||
|
.service-name { font-size:15px; font-weight:500; color:var(--text-primary); }
|
||||||
.status-page { padding-top:80px; max-width:680px; margin:0 auto; padding-left:20px; padding-right:20px; padding-bottom:60px; }
|
.service-badge { display:flex; align-items:center; gap:8px; font-size:13px; font-weight:500; }
|
||||||
.overall-banner { border-radius:12px; padding:24px 28px; margin-bottom:32px; display:flex; align-items:center; gap:14px; }
|
.service-badge .dot { width:8px; height:8px; border-radius:50%; }
|
||||||
.overall-banner.operational { background:rgba(34,197,94,.08); border:1px solid rgba(34,197,94,.2); }
|
.service-badge .dot.operational { background:#22c55e; }
|
||||||
.overall-banner.degraded { background:rgba(234,179,8,.08); border:1px solid rgba(234,179,8,.2); }
|
.service-badge .dot.degraded { background:#eab308; }
|
||||||
.overall-banner.outage { background:rgba(239,68,68,.08); border:1px solid rgba(239,68,68,.2); }
|
.service-badge .dot.outage { background:#ef4444; }
|
||||||
.overall-dot { width:10px; height:10px; border-radius:50%; flex-shrink:0; }
|
.service-badge .label { color:var(--text-muted); }
|
||||||
.overall-dot.operational { background:#22c55e; }
|
.uptime-footer { margin-top:32px; text-align:center; color:var(--text-muted); font-size:12px; font-family:'JetBrains Mono',monospace; }
|
||||||
.overall-dot.degraded { background:#eab308; }
|
.uptime-footer .uptime-val { font-size:14px; color:var(--text-secondary); font-weight:600; margin-bottom:4px; }
|
||||||
.overall-dot.outage { background:#ef4444; }
|
</style>
|
||||||
.overall-text { font-size:16px; font-weight:700; color:var(--text-primary); }
|
<script>
|
||||||
.service-list { display:flex; flex-direction:column; }
|
(function(){ if (localStorage.getItem('theme') === 'dark') document.documentElement.classList.add('dark'); })();
|
||||||
.service-row { display:flex; align-items:center; justify-content:space-between; padding:16px 0; border-bottom:1px solid var(--surface-600); }
|
</script>
|
||||||
.service-row:last-child { border-bottom:none; }
|
</head>
|
||||||
.service-name { font-size:15px; font-weight:500; color:var(--text-primary); }
|
<body>
|
||||||
.service-badge { display:flex; align-items:center; gap:8px; font-size:13px; font-weight:500; }
|
|
||||||
.service-badge .dot { width:8px; height:8px; border-radius:50%; }
|
<header class="top-header">
|
||||||
.service-badge .dot.operational { background:#22c55e; }
|
<a href="/" class="top-logo">
|
||||||
.service-badge .dot.degraded { background:#eab308; }
|
<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>
|
||||||
.service-badge .dot.outage { background:#ef4444; }
|
WA Dev <span>Tools</span>
|
||||||
.service-badge .label { color:var(--text-muted); }
|
</a>
|
||||||
.uptime-footer { margin-top:32px; text-align:center; color:var(--text-muted); font-size:12px; font-family:'JetBrains Mono',monospace; }
|
<nav class="top-nav">
|
||||||
.uptime-footer .uptime-val { font-size:14px; color:var(--text-secondary); font-weight:600; margin-bottom:4px; }
|
<a href="/">Главная</a>
|
||||||
</style>
|
<a href="/#tools">Инструменты</a>
|
||||||
<script>
|
<a href="/#features">Возможности</a>
|
||||||
(function(){ if (localStorage.getItem('theme') === 'dark') document.documentElement.classList.add('dark'); })();
|
</nav>
|
||||||
</script>
|
<div class="top-right">
|
||||||
</head>
|
<button class="theme-btn" id="themeBtn" title="Переключить тему">
|
||||||
<body>
|
<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>
|
||||||
<header class="top-header">
|
</button>
|
||||||
<a href="/" class="top-logo">
|
<a href="/auth/login" class="btn-ghost">Войти</a>
|
||||||
<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>
|
<a href="/auth/register" class="btn-primary-sm">Регистрация</a>
|
||||||
WA Dev <span>Tools</span>
|
</div>
|
||||||
</a>
|
</header>
|
||||||
<nav class="top-nav">
|
|
||||||
<a href="/">Главная</a>
|
<div class="status-page">
|
||||||
<a href="/#tools">Инструменты</a>
|
<div id="banner" class="overall-banner operational">
|
||||||
<a href="/#features">Возможности</a>
|
<div class="overall-dot operational" id="bannerDot"></div>
|
||||||
</nav>
|
<div class="overall-text" id="bannerText">All systems operational</div>
|
||||||
<div class="top-right">
|
</div>
|
||||||
<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>
|
<div class="service-list" id="serviceList">
|
||||||
<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>
|
<div style="text-align:center;padding:40px 0;color:var(--text-muted);font-size:13px;">Loading...</div>
|
||||||
</button>
|
</div>
|
||||||
<a href="/auth/login" class="btn-ghost">Войти</a>
|
|
||||||
<a href="/auth/register" class="btn-primary-sm">Регистрация</a>
|
<div class="uptime-footer">
|
||||||
</div>
|
<div class="uptime-val" id="uptimeVal">—</div>
|
||||||
</header>
|
<div id="lastCheck">—</div>
|
||||||
|
</div>
|
||||||
<div class="status-page">
|
</div>
|
||||||
<div id="banner" class="overall-banner operational">
|
|
||||||
<div class="overall-dot operational" id="bannerDot"></div>
|
<script>
|
||||||
<div class="overall-text" id="bannerText">All systems operational</div>
|
const OVERALL = { operational: 'All systems operational', degraded: 'Partial system outage', outage: 'Major system outage' };
|
||||||
</div>
|
const STATUS = { operational: 'Operational', degraded: 'Degraded', outage: 'Outage' };
|
||||||
|
|
||||||
<div class="service-list" id="serviceList">
|
function fmtUptime(s) {
|
||||||
<div style="text-align:center;padding:40px 0;color:var(--text-muted);font-size:13px;">Loading...</div>
|
const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60);
|
||||||
</div>
|
const p = [];
|
||||||
|
if (d) p.push(d + 'd');
|
||||||
<div class="uptime-footer">
|
if (h) p.push(h + 'h');
|
||||||
<div class="uptime-val" id="uptimeVal">—</div>
|
p.push(m + 'm');
|
||||||
<div id="lastCheck">—</div>
|
return 'Uptime: ' + p.join(' ');
|
||||||
</div>
|
}
|
||||||
</div>
|
|
||||||
|
function render(data) {
|
||||||
<script>
|
const b = document.getElementById('banner');
|
||||||
const OVERALL = { operational: 'All systems operational', degraded: 'Partial system outage', outage: 'Major system outage' };
|
b.className = 'overall-banner ' + data.overall;
|
||||||
const STATUS = { operational: 'Operational', degraded: 'Degraded', outage: 'Outage' };
|
document.getElementById('bannerDot').className = 'overall-dot ' + data.overall;
|
||||||
|
document.getElementById('bannerText').textContent = OVERALL[data.overall] || data.overall;
|
||||||
function fmtUptime(s) {
|
|
||||||
const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60);
|
document.getElementById('serviceList').innerHTML = data.services.map(s =>
|
||||||
const p = [];
|
'<div class="service-row"><div class="service-name">' + s.name + '</div>' +
|
||||||
if (d) p.push(d + 'd');
|
'<div class="service-badge"><span class="dot ' + s.status + '"></span>' +
|
||||||
if (h) p.push(h + 'h');
|
'<span class="label">' + (STATUS[s.status] || s.status) + '</span></div></div>'
|
||||||
p.push(m + 'm');
|
).join('');
|
||||||
return 'Uptime: ' + p.join(' ');
|
|
||||||
}
|
document.getElementById('uptimeVal').textContent = fmtUptime(data.uptime);
|
||||||
|
document.getElementById('lastCheck').textContent = 'Last checked: ' + new Date(data.timestamp).toLocaleTimeString();
|
||||||
function render(data) {
|
}
|
||||||
const b = document.getElementById('banner');
|
|
||||||
b.className = 'overall-banner ' + data.overall;
|
async function poll() { try { const r = await fetch('/status/api'); if (r.ok) render(await r.json()); } catch {} }
|
||||||
document.getElementById('bannerDot').className = 'overall-dot ' + data.overall;
|
poll();
|
||||||
document.getElementById('bannerText').textContent = OVERALL[data.overall] || data.overall;
|
setInterval(poll, 30000);
|
||||||
|
|
||||||
document.getElementById('serviceList').innerHTML = data.services.map(s =>
|
// Theme
|
||||||
'<div class="service-row"><div class="service-name">' + s.name + '</div>' +
|
const tb = document.getElementById('themeBtn');
|
||||||
'<div class="service-badge"><span class="dot ' + s.status + '"></span>' +
|
function ut() {
|
||||||
'<span class="label">' + (STATUS[s.status] || s.status) + '</span></div></div>'
|
const d = document.documentElement.classList.contains('dark');
|
||||||
).join('');
|
document.querySelectorAll('.sun-i').forEach(e => e.style.display = d ? 'none' : 'block');
|
||||||
|
document.querySelectorAll('.moon-i').forEach(e => e.style.display = d ? 'block' : 'none');
|
||||||
document.getElementById('uptimeVal').textContent = fmtUptime(data.uptime);
|
}
|
||||||
document.getElementById('lastCheck').textContent = 'Last checked: ' + new Date(data.timestamp).toLocaleTimeString();
|
ut();
|
||||||
}
|
tb.addEventListener('click', () => { const d = document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', d ? 'dark' : ''); ut(); });
|
||||||
|
</script>
|
||||||
async function poll() { try { const r = await fetch('/status/api'); if (r.ok) render(await r.json()); } catch {} }
|
</body>
|
||||||
poll();
|
</html>
|
||||||
setInterval(poll, 30000);
|
|
||||||
|
|
||||||
// Theme
|
|
||||||
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(); });
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user