189 lines
14 KiB
HTML
189 lines
14 KiB
HTML
<!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>
|
||
<link href="/vendor/tailwind.min.css" rel="stylesheet">
|
||
<script>window.WA_TOOL_ID = 'home';</script>
|
||
<script src="/shared.js?v=3"></script>
|
||
<link href="/vendor/fonts.css" rel="stylesheet">
|
||
<link href="/shared.css" rel="stylesheet">
|
||
<style>
|
||
.dash-header { position:fixed; top:0; left:56px; right:0; height:52px; background:var(--surface-800); border-bottom:1px solid var(--surface-600); display:flex; align-items:center; padding:0 24px; z-index:40; backdrop-filter:blur(12px); }
|
||
.dash-logo { font-size:16px; font-weight:800; color:var(--text-primary); text-decoration:none; display:flex; align-items:center; gap:8px; flex-shrink:0; }
|
||
.dash-logo span { color:var(--accent); }
|
||
.dash-right { margin-left:auto; display:flex; align-items:center; gap:12px; }
|
||
.dash-user { font-size:13px; color:var(--text-muted); font-family:'JetBrains Mono',monospace; }
|
||
.dash-logout { color:var(--text-muted); transition:color .15s; }
|
||
.dash-logout:hover { color:#ff5252; }
|
||
@media(max-width:768px) { .dash-header { left:0; } .dash-logo svg { display:none; } }
|
||
|
||
@keyframes fadeUp { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:translateY(0); } }
|
||
.fade-up { animation: fadeUp .4s ease-out forwards; }
|
||
|
||
.tool-tile { display:flex; flex-direction:column; gap:10px; padding:18px; border-radius:14px; border:1px solid var(--surface-600); background:var(--surface-800); color:inherit; text-decoration:none; transition:all .2s; }
|
||
.tool-tile:hover { transform:translateY(-2px); border-color:rgba(0,84,230,.3); box-shadow:0 8px 24px rgba(0,0,0,.1); background:var(--surface-700); }
|
||
.tile-icon { width:40px; height:40px; border-radius:10px; display:flex; align-items:center; justify-content:center; flex-shrink:0; transition:background .2s; }
|
||
.tool-tile:hover .tile-icon { filter:brightness(1.2); }
|
||
.tile-title { font-size:14px; font-weight:600; color:var(--text-primary); }
|
||
.tile-desc { font-size:11px; color:var(--text-muted); font-family:'JetBrains Mono',monospace; line-height:1.4; }
|
||
|
||
.cat-header { display:flex; align-items:center; gap:10px; margin-bottom:14px; margin-top:32px; }
|
||
.cat-header:first-child { margin-top:0; }
|
||
.cat-icon { width:32px; height:32px; border-radius:8px; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
|
||
.cat-title { font-size:16px; font-weight:700; color:var(--text-primary); }
|
||
.cat-count { font-size:11px; color:var(--text-muted); font-family:'JetBrains Mono',monospace; background:var(--surface-600); padding:2px 8px; border-radius:6px; }
|
||
.cat-desc { font-size:12px; color:var(--text-muted); margin-left:auto; font-family:'JetBrains Mono',monospace; }
|
||
@media(max-width:640px) { .cat-desc { display:none; } }
|
||
</style>
|
||
</head>
|
||
<body class="font-sans antialiased">
|
||
|
||
<header class="dash-header">
|
||
<a href="/dashboard" class="dash-logo">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><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>
|
||
<div class="dash-right">
|
||
<span class="dash-user" id="headerUser"></span>
|
||
<a href="/auth/logout" class="dash-logout" title="Выйти">
|
||
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"/></svg>
|
||
</a>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="main-content">
|
||
<div class="tool-container relative z-10 max-w-5xl mx-auto" style="padding-top:72px;">
|
||
|
||
<div class="fade-up" style="margin-bottom:28px;">
|
||
<h1 style="font-size:1.75rem;font-weight:800;color:var(--text-primary);letter-spacing:-0.025em;" id="greeting">Добро пожаловать</h1>
|
||
<p id="toolsCount" style="font-size:14px;color:var(--text-secondary);margin-top:8px;line-height:1.6;max-width:600px;">
|
||
Инструменты для веб-разработки: сжатие картинок и видео, форматирование кода, тестирование API и многое другое.
|
||
</p>
|
||
</div>
|
||
|
||
<div id="toolsGrid"></div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Category icons (SVG paths)
|
||
const CAT_ICONS = {
|
||
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: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"/>',
|
||
utils: '<path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17l-5.645 3.232a.75.75 0 01-1.09-.791l1.078-6.285L1.347 7.06a.75.75 0 01.416-1.28l6.31-.917L10.89.598a.75.75 0 011.341 0l2.816 5.266 6.31.917a.75.75 0 01.416 1.28l-4.416 4.266 1.078 6.285a.75.75 0 01-1.09.791L12 15.17z"/>',
|
||
};
|
||
|
||
// Tool icons — match sidebar icons from shared.js
|
||
const TOOL_ICONS = {
|
||
compress: '<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"/>',
|
||
placeholder: '<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 01-1.59.659H9.06a2.25 2.25 0 01-1.591-.659L5 14.5m14 0V17a2.25 2.25 0 01-2.25 2.25H7.25A2.25 2.25 0 015 17v-2.5"/>',
|
||
svgeditor: '<path stroke-linecap="round" stroke-linejoin="round" d="M9.53 16.122a3 3 0 00-5.78 1.128 2.25 2.25 0 01-2.4 2.245 4.5 4.5 0 008.4-2.245c0-.399-.078-.78-.22-1.128zm0 0a15.998 15.998 0 003.388-1.62m-5.043-.025a15.994 15.994 0 011.622-3.395m3.42 3.42a15.995 15.995 0 004.764-4.648l3.876-5.814a1.151 1.151 0 00-1.597-1.597L14.146 6.32a15.996 15.996 0 00-4.649 4.763m3.42 3.42a6.776 6.776 0 00-3.42-3.42"/>',
|
||
editor: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"/>',
|
||
video: '<path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z"/>',
|
||
favicon: '<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6z"/>',
|
||
formatter: '<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"/>',
|
||
sanitizer: '<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"/>',
|
||
converter: '<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21L3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"/>',
|
||
regex: '<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"/>',
|
||
parser: '<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m9.86-2.06a4.5 4.5 0 00-6.364-6.364L6.257 6.514a4.5 4.5 0 001.242 7.244"/>',
|
||
httpclient: '<path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"/>',
|
||
redirects: '<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"/>',
|
||
password: '<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"/>',
|
||
md: '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"/>',
|
||
pdf: '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"/>',
|
||
};
|
||
|
||
function svgIcon(pathHtml, size) {
|
||
return `<svg width="${size||20}" height="${size||20}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">${pathHtml}</svg>`;
|
||
}
|
||
|
||
function esc(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
||
|
||
// Load tools from API
|
||
fetch('/api/tools')
|
||
.then(r => r.json())
|
||
.then(categories => {
|
||
const grid = document.getElementById('toolsGrid');
|
||
let totalTools = 0;
|
||
const delays = ['', 'fade-up', 'fade-up', 'fade-up', 'fade-up'];
|
||
let catIdx = 0;
|
||
|
||
categories.forEach(cat => {
|
||
if (!cat.tools || !cat.tools.length) return;
|
||
catIdx++;
|
||
totalTools += cat.tools.length;
|
||
const color = cat.color || '#0054e6';
|
||
const bgColor = color + '1a'; // 10% opacity hex
|
||
|
||
// Category header
|
||
const header = document.createElement('div');
|
||
header.className = 'cat-header fade-up';
|
||
header.style.animationDelay = (catIdx * 0.05) + 's';
|
||
header.innerHTML = `
|
||
<div class="cat-icon" style="background:${bgColor};color:${color}">
|
||
${svgIcon(CAT_ICONS[cat.slug] || CAT_ICONS.utils, 18)}
|
||
</div>
|
||
<span class="cat-title">${esc(cat.title)}</span>
|
||
<span class="cat-count">${cat.tools.length}</span>
|
||
<span class="cat-desc">${esc(cat.description || '')}</span>
|
||
`;
|
||
grid.appendChild(header);
|
||
|
||
// Tools grid
|
||
const tilesWrap = document.createElement('div');
|
||
tilesWrap.className = 'grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 fade-up';
|
||
tilesWrap.style.animationDelay = (catIdx * 0.05) + 's';
|
||
|
||
cat.tools.forEach(tool => {
|
||
const iconPath = TOOL_ICONS[tool.slug] || TOOL_ICONS.compress;
|
||
const a = document.createElement('a');
|
||
a.href = tool.path;
|
||
a.className = 'tool-tile';
|
||
a.innerHTML = `
|
||
<div class="tile-icon" style="background:${bgColor};color:${color}">${svgIcon(iconPath)}</div>
|
||
<div>
|
||
<div class="tile-title">${esc(tool.title)}</div>
|
||
<div class="tile-desc">${esc(tool.description || '')}</div>
|
||
</div>
|
||
`;
|
||
tilesWrap.appendChild(a);
|
||
});
|
||
|
||
grid.appendChild(tilesWrap);
|
||
});
|
||
|
||
// Update count
|
||
document.getElementById('toolsCount').textContent =
|
||
totalTools + ' инструментов для веб-разработки: сжатие картинок и видео, форматирование кода, тестирование API и многое другое.';
|
||
})
|
||
.catch(() => {
|
||
document.getElementById('toolsGrid').innerHTML = '<p style="color:var(--text-muted)">Не удалось загрузить инструменты</p>';
|
||
});
|
||
|
||
// User greeting
|
||
fetch('/auth/me').then(r => r.ok ? r.json() : null).then(user => {
|
||
if (!user) return;
|
||
const name = user.name || user.email.split('@')[0];
|
||
document.getElementById('greeting').textContent = 'Привет, ' + name;
|
||
document.getElementById('headerUser').textContent = name;
|
||
}).catch(() => {});
|
||
</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 -->
|
||
</body>
|
||
</html>
|