feat: redesign home page — Material glassmorphism, staggered animations, custom typography
This commit is contained in:
+173
-53
@@ -1045,31 +1045,37 @@ async def home_page():
|
||||
statuses = await fetch_servers_status()
|
||||
|
||||
servers_html = ""
|
||||
for s in statuses:
|
||||
for i, s in enumerate(statuses):
|
||||
chk = s.get("checks", {})
|
||||
cpu = chk.get("CPU", {}).get("value")
|
||||
ram = chk.get("RAM", {}).get("value")
|
||||
disk = chk.get("Disk /", {}).get("value")
|
||||
net_raw = chk.get("Net ↓↑", {}).get("value", "")
|
||||
server_name = s.get("server_name", s["name"].upper())
|
||||
delay = 0.3 + i * 0.15
|
||||
|
||||
service_icons = ""
|
||||
svc_tags = ""
|
||||
for key in ("Caddy", "Minecraft", "Bio site", "Main site", "3x-UI"):
|
||||
val = chk.get(key, {}).get("value", "")
|
||||
if val:
|
||||
dot = "🟢" if "🟢" in val else ("🔴" if "🔴" in val or "🔴" in str(val) else ("🟡" if "🟡" in val else ""))
|
||||
service_icons += f'<span title="{key}: {val}" style="font-size:14px;margin-right:4px">{dot or "●"}</span>'
|
||||
if "🟢" in val or "RUN" in val or "OK" in val or "200" in val:
|
||||
cl, st = "ok", "🟢"
|
||||
elif "🔴" in val or "DOWN" in val or "502" in val or "503" in val or "error" in val.lower():
|
||||
cl, st = "err", "🔴"
|
||||
else:
|
||||
cl, st = "warn", "🟡"
|
||||
svc_tags += f'<span class="svc {cl}" title="{key}: {val}">{st} {key}</span>'
|
||||
|
||||
servers_html += f'''
|
||||
<div class="server">
|
||||
<div class="srv-head"><span class="flag">{get_flag_emoji(s.get("country",""))}</span> {server_name}</div>
|
||||
<div class="srv-body">
|
||||
<div class="metric"><span class="ml">CPU</span><div class="mb"><div class="mf" style="width:{cpu if cpu is not None else 0}%"></div></div><span class="mv">{f"{cpu:.1f}%" if cpu is not None else "—"}</span></div>
|
||||
<div class="metric"><span class="ml">RAM</span><div class="mb"><div class="mf" style="width:{ram if ram is not None else 0}%"></div></div><span class="mv">{f"{ram:.1f}%" if ram is not None else "—"}</span></div>
|
||||
<div class="metric"><span class="ml">DISK</span><div class="mb"><div class="mf" style="width:{disk if disk is not None else 0}%"></div></div><span class="mv">{f"{disk:.1f}%" if disk is not None else "—"}</span></div>
|
||||
<div class="metric"><span class="ml">NET</span><div class="mb" style="background:none;padding:0"><span style="font-size:12px;color:#aaa;font-family:monospace">{net_raw}</span></div></div>
|
||||
<div class="s-card" style="--d: {delay}s">
|
||||
<div class="s-head"><span class="flag">{get_flag_emoji(s.get("country",""))}</span> {server_name}</div>
|
||||
<div class="s-metrics">
|
||||
<div class="m"><span class="m-l">CPU</span><div class="m-t"><div class="m-f" style="--w: {cpu if cpu is not None else 0}%"></div></div><span class="m-v">{f"{cpu:.1f}%" if cpu is not None else "—"}</span></div>
|
||||
<div class="m"><span class="m-l">RAM</span><div class="m-t"><div class="m-f" style="--w: {ram if ram is not None else 0}%"></div></div><span class="m-v">{f"{ram:.1f}%" if ram is not None else "—"}</span></div>
|
||||
<div class="m"><span class="m-l">DSK</span><div class="m-t"><div class="m-f" style="--w: {disk if disk is not None else 0}%"></div></div><span class="m-v">{f"{disk:.1f}%" if disk is not None else "—"}</span></div>
|
||||
<div class="m"><span class="m-l">NET</span><div class="m-t" style="background:none;align-items:center"><span class="m-net">{net_raw}</span></div></div>
|
||||
</div>
|
||||
{f'<div class="srv-svc">{service_icons}</div>' if service_icons else ''}
|
||||
{f'<div class="s-svc">{svc_tags}</div>' if svc_tags else ''}
|
||||
</div>'''
|
||||
|
||||
return HTMLResponse(content=f'''<!DOCTYPE html>
|
||||
@@ -1077,67 +1083,181 @@ async def home_page():
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>{title}</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {{
|
||||
--bg: #080c18;
|
||||
--surface: rgba(255,255,255,0.03);
|
||||
--border: rgba(255,255,255,0.06);
|
||||
--border-hover: rgba(255,255,255,0.12);
|
||||
--text: #fff;
|
||||
--text-sec: rgba(255,255,255,0.5);
|
||||
--text-ter: rgba(255,255,255,0.3);
|
||||
--accent: #6C63FF;
|
||||
--green: #10b981;
|
||||
--amber: #f59e0b;
|
||||
--rose: #f43f5e;
|
||||
--card-rad: 16px;
|
||||
}}
|
||||
* {{ margin:0; padding:0; box-sizing:border-box }}
|
||||
body{{font-family:Roboto,sans-serif;background:linear-gradient(135deg,#1a1a2e,#16213e);min-height:100vh;color:#fff}}
|
||||
.container{{max-width:800px;margin:0 auto;padding:30px 16px}}
|
||||
.header{{text-align:center;margin-bottom:24px}}
|
||||
.header h1{{font-size:26px;font-weight:500;margin-bottom:6px}}
|
||||
.header p{{color:#888;font-size:14px}}
|
||||
.stats{{display:flex;gap:12px;justify-content:center;margin-bottom:28px;flex-wrap:wrap}}
|
||||
.stat{{background:rgba(255,255,255,0.04);border-radius:12px;padding:14px 22px;text-align:center;border:1px solid rgba(255,255,255,0.06)}}
|
||||
.stat .n{{font-size:26px;font-weight:500}}
|
||||
.stat .l{{font-size:12px;color:#888;margin-top:2px}}
|
||||
.servers{{display:grid;gap:12px}}
|
||||
.server{{background:rgba(255,255,255,0.04);border-radius:14px;padding:14px 16px;border:1px solid rgba(255,255,255,0.06)}}
|
||||
.srv-head{{font-size:14px;font-weight:500;margin-bottom:10px}}
|
||||
.srv-head .flag{{font-size:18px;margin-right:6px}}
|
||||
.srv-body{{display:grid;gap:7px}}
|
||||
.metric{{display:grid;grid-template-columns:42px 1fr 52px;align-items:center;gap:10px}}
|
||||
.ml{{font-size:12px;color:#888;font-weight:500}}
|
||||
.mb{{height:8px;background:rgba(255,255,255,0.08);border-radius:4px;overflow:hidden}}
|
||||
.mf{{height:100%;border-radius:4px;background:linear-gradient(90deg,#4CAF50,#8BC34A);transition:width .5s}}
|
||||
.mv{{font-size:11px;color:#888;text-align:right;font-family:monospace}}
|
||||
.srv-svc{{margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.05)}}
|
||||
.info{{margin-top:28px;background:rgba(255,255,255,0.03);border-radius:14px;padding:16px;border:1px solid rgba(255,255,255,0.06)}}
|
||||
.info h3{{font-size:14px;font-weight:500;margin-bottom:8px}}
|
||||
.info p{{font-size:12px;color:#888;line-height:1.7}}
|
||||
.info p span{{color:#aaa}}
|
||||
.btns{{text-align:center;margin-top:24px}}
|
||||
.btn{{display:inline-block;padding:11px 24px;background:#4CAF50;color:#fff;text-decoration:none;border-radius:10px;font-size:13px;margin:4px;transition:.3s}}
|
||||
.btn:hover{{background:#45a049}}
|
||||
.btn.da{{background:#E91E63}}
|
||||
.btn.da:hover{{background:#c2185b}}
|
||||
.footer{{text-align:center;margin-top:28px;color:#444;font-size:12px}}
|
||||
html {{ scroll-behavior:smooth }}
|
||||
body {{
|
||||
font-family:'Plus Jakarta Sans',sans-serif;
|
||||
background:var(--bg);
|
||||
color:var(--text);
|
||||
min-height:100vh;
|
||||
overflow-x:hidden;
|
||||
}}
|
||||
body::before {{
|
||||
content:'';
|
||||
position:fixed;inset:0;
|
||||
background:
|
||||
radial-gradient(ellipse 80% 50% at 10% 0%, rgba(108,99,255,0.08) 0%, transparent 70%),
|
||||
radial-gradient(ellipse 60% 40% at 90% 100%, rgba(16,185,129,0.06) 0%, transparent 70%);
|
||||
pointer-events:none;
|
||||
z-index:0;
|
||||
}}
|
||||
.container {{ position:relative; z-index:1; max-width:720px; margin:0 auto; padding:48px 20px 32px }}
|
||||
|
||||
/* Header */
|
||||
.header {{ text-align:center; margin-bottom:36px; animation:fadeUp .7s ease-out forwards }}
|
||||
.header h1 {{
|
||||
font-size:32px; font-weight:800; letter-spacing:-.03em;
|
||||
background:linear-gradient(135deg,#fff 30%,rgba(255,255,255,.6));
|
||||
-webkit-background-clip:text; -webkit-text-fill-color:transparent;
|
||||
background-clip:text;
|
||||
}}
|
||||
.header p {{ color:var(--text-sec); font-size:15px; margin-top:6px; font-weight:400 }}
|
||||
|
||||
/* Stats row */
|
||||
.stats {{ display:flex; gap:12px; justify-content:center; margin-bottom:36px; animation:fadeUp .7s .1s ease-out both }}
|
||||
.stat {{
|
||||
flex:1; max-width:180px;
|
||||
background:var(--surface); border:1px solid var(--border);
|
||||
border-radius:var(--card-rad); padding:18px 16px; text-align:center;
|
||||
backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
|
||||
transition:border-color .3s, transform .3s, box-shadow .3s;
|
||||
}}
|
||||
.stat:hover {{ border-color:var(--border-hover); transform:translateY(-2px); box-shadow:0 8px 40px rgba(108,99,255,0.08) }}
|
||||
.stat .n {{ font-size:28px; font-weight:700; letter-spacing:-.02em }}
|
||||
.stat .l {{ font-size:12px; color:var(--text-sec); margin-top:4px; font-weight:500 }}
|
||||
|
||||
/* Server cards */
|
||||
.servers {{ display:grid; gap:14px }}
|
||||
.s-card {{
|
||||
background:var(--surface); border:1px solid var(--border);
|
||||
border-radius:var(--card-rad); padding:18px 20px;
|
||||
backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
|
||||
transition:border-color .4s, transform .4s, box-shadow .4s;
|
||||
animation:fadeUp .6s ease-out var(--d,0s) both;
|
||||
}}
|
||||
.s-card:hover {{ border-color:var(--border-hover); transform:translateY(-3px); box-shadow:0 12px 48px rgba(108,99,255,0.06) }}
|
||||
.s-head {{ font-size:15px; font-weight:600; margin-bottom:14px; display:flex; align-items:center; gap:8px }}
|
||||
.s-head .flag {{ font-size:20px; line-height:1 }}
|
||||
.s-metrics {{ display:grid; gap:10px }}
|
||||
.m {{ display:grid; grid-template-columns:40px 1fr 50px; align-items:center; gap:10px }}
|
||||
.m-l {{ font-size:11px; font-weight:600; color:var(--text-sec); letter-spacing:.04em }}
|
||||
.m-t {{ height:6px; background:rgba(255,255,255,0.06); border-radius:3px; overflow:hidden; display:flex }}
|
||||
.m-f {{
|
||||
width:0; height:100%; border-radius:3px;
|
||||
background:linear-gradient(90deg,var(--accent),var(--green));
|
||||
animation:fillBar .9s cubic-bezier(.4,0,.2,1) .4s forwards;
|
||||
}}
|
||||
.m-v {{ font-family:'JetBrains Mono',monospace; font-size:11px; color:var(--text-sec); text-align:right }}
|
||||
.m-net {{ font-family:'JetBrains Mono',monospace; font-size:11px; color:var(--text-ter); white-space:nowrap }}
|
||||
.s-svc {{ margin-top:12px; padding-top:12px; border-top:1px solid var(--border); display:flex; gap:8px; flex-wrap:wrap }}
|
||||
.svc {{ font-size:11px; padding:3px 8px; border-radius:6px; background:rgba(255,255,255,0.04); font-weight:500 }}
|
||||
.svc.ok {{ color:var(--green) }}
|
||||
.svc.err {{ color:var(--rose) }}
|
||||
.svc.warn {{ color:var(--amber) }}
|
||||
|
||||
/* Tech info */
|
||||
.info {{
|
||||
margin-top:28px;
|
||||
background:var(--surface); border:1px solid var(--border);
|
||||
border-radius:var(--card-rad); padding:20px;
|
||||
backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
|
||||
animation:fadeUp .6s .5s ease-out both;
|
||||
transition:border-color .3s;
|
||||
}}
|
||||
.info:hover {{ border-color:var(--border-hover) }}
|
||||
.info h3 {{ font-size:14px; font-weight:600; margin-bottom:12px; color:var(--text) }}
|
||||
.info-grid {{ display:grid; grid-template-columns:1fr 1fr; gap:8px 16px }}
|
||||
.info-grid span {{ font-size:12px; color:var(--text-sec); line-height:1.8; display:flex; align-items:center; gap:6px }}
|
||||
.info-grid span::before {{ content:''; display:inline-block; width:4px; height:4px; border-radius:2px; background:var(--accent); flex-shrink:0 }}
|
||||
|
||||
/* Buttons */
|
||||
.btns {{ display:flex; justify-content:center; gap:10px; margin-top:28px; animation:fadeUp .6s .6s ease-out both }}
|
||||
.btn {{
|
||||
display:inline-flex; align-items:center; gap:6px;
|
||||
padding:12px 28px; border-radius:12px;
|
||||
font-size:14px; font-weight:600; text-decoration:none;
|
||||
transition:transform .3s, box-shadow .3s;
|
||||
cursor:pointer;
|
||||
}}
|
||||
.btn:hover {{ transform:translateY(-2px); box-shadow:0 8px 32px rgba(108,99,255,0.2) }}
|
||||
.btn.da {{ background:linear-gradient(135deg,#f43f5e,#e11d48); color:#fff }}
|
||||
|
||||
/* Footer */
|
||||
.footer {{ text-align:center; margin-top:36px; color:var(--text-ter); font-size:12px; animation:fadeUp .6s .7s ease-out both }}
|
||||
|
||||
/* Keyframes */
|
||||
@keyframes fadeUp {{
|
||||
from {{ opacity:0; transform:translateY(16px) }}
|
||||
to {{ opacity:1; transform:translateY(0) }}
|
||||
}}
|
||||
@keyframes fillBar {{
|
||||
from {{ width:0% }}
|
||||
to {{ width:var(--w) }}
|
||||
}}
|
||||
|
||||
/* Responsive */
|
||||
@media(max-width:520px) {{
|
||||
.container {{ padding:32px 14px }}
|
||||
.header h1 {{ font-size:24px }}
|
||||
.stat {{ max-width:none; padding:14px 12px }}
|
||||
.stat .n {{ font-size:22px }}
|
||||
.s-card {{ padding:14px }}
|
||||
.m {{ grid-template-columns:34px 1fr 44px; gap:8px }}
|
||||
.info-grid {{ grid-template-columns:1fr }}
|
||||
.btns {{ flex-direction:column; align-items:center }}
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<div class="header">
|
||||
<h1>⚡ {title}</h1>
|
||||
<p>Быстрый и надёжный VPN-сервис</p>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="n">{total}</div><div class="l">👥 Клиентов</div></div>
|
||||
<div class="stat"><div class="n">{len(servers)}</div><div class="l">🌍 Локаций</div></div>
|
||||
<div class="stat"><div class="n">{total}</div><div class="l">Пользователей</div></div>
|
||||
<div class="stat"><div class="n">{len(servers)}</div><div class="l">Локаций</div></div>
|
||||
</div>
|
||||
|
||||
<div class="servers">{servers_html}</div>
|
||||
|
||||
<div class="info">
|
||||
<h3>🔧 Технические детали</h3>
|
||||
<p>
|
||||
<span>🔒</span> Шифрование: VLESS + XTLS Vision / Reality<br>
|
||||
<span>⚡</span> Протоколы: TCP, WebSocket, gRPC<br>
|
||||
<span>🛡️</span> DDoS-защита на всех серверах<br>
|
||||
<span>📡</span> Каналы: 1-10 Gbit/s<br>
|
||||
<span>🔐</span> Zero-log политика — трафик не логируется<br>
|
||||
<span>🌐</span> Поддержка Happ (iOS/Android/Desktop)
|
||||
</p>
|
||||
<div class="info-grid">
|
||||
<span>VLESS + XTLS Vision / Reality</span>
|
||||
<span>TCP, WebSocket, gRPC</span>
|
||||
<span>DDoS-защита на всех серверах</span>
|
||||
<span>Каналы 1–10 Gbit/s</span>
|
||||
<span>Zero-log политика</span>
|
||||
<span>Поддержка Happ (iOS/Android)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btns">
|
||||
<a href="{da_url}" class="btn da">❤️ Поддержать проект</a>
|
||||
</div>
|
||||
|
||||
<div class="footer">© 2026 {title}</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>''')
|
||||
|
||||
Reference in New Issue
Block a user