v0.0.01b
This commit is contained in:
921
.ref/account.html
Normal file
921
.ref/account.html
Normal file
@@ -0,0 +1,921 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MaleniaVPN · Account</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600&family=Tenor+Sans&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #faf7f2;
|
||||
--surface: #f5f0e8;
|
||||
--fg: #1a1610;
|
||||
--muted: #3d3428;
|
||||
--accent: #c9a84c;
|
||||
--font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
|
||||
--font-body: 'Tenor Sans', 'Helvetica Neue', Arial, sans-serif;
|
||||
--radius: 8px;
|
||||
--baseline: 8px;
|
||||
--hairline: rgba(26, 22, 16, 0.10);
|
||||
--hairline-strong: rgba(26, 22, 16, 0.18);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Grain overlay */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9998;
|
||||
opacity: 0.035;
|
||||
mix-blend-mode: multiply;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* Custom cursor */
|
||||
.cursor-dot, .cursor-ring {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.cursor-dot {
|
||||
width: 5px; height: 5px;
|
||||
background: var(--fg);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.cursor-ring {
|
||||
width: 30px; height: 30px;
|
||||
border: 1px solid var(--muted);
|
||||
border-radius: 50%;
|
||||
opacity: 0.4;
|
||||
transition: width 0.25s, height 0.25s, border-color 0.25s, opacity 0.25s, transform 0.08s linear;
|
||||
}
|
||||
.cursor-ring.is-active {
|
||||
width: 52px; height: 52px;
|
||||
border-color: var(--accent);
|
||||
opacity: 0.9;
|
||||
}
|
||||
@media (hover: none), (pointer: coarse) {
|
||||
.cursor-dot, .cursor-ring { display: none; }
|
||||
* { cursor: auto !important; }
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
body, button, a, [data-cursor] { cursor: none; }
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.shell {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 64px 32px 96px;
|
||||
}
|
||||
|
||||
/* Topbar */
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 32px;
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
}
|
||||
.logo {
|
||||
font-family: var(--font-display);
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.35em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fg);
|
||||
}
|
||||
.logo .e { color: var(--accent); }
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: 28px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.nav a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
padding: 4px 0;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.nav a:hover { color: var(--fg); }
|
||||
.nav a.is-active::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0; right: 0; bottom: -2px;
|
||||
height: 1px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
/* Hero / identity */
|
||||
.hero {
|
||||
padding: 56px 0 48px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
}
|
||||
.kicker {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.28em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.hero-title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
font-size: 44px;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--fg);
|
||||
}
|
||||
.hero-sub {
|
||||
margin-top: 14px;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Profile stat strip — graphics-led */
|
||||
.profile {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
}
|
||||
.stat {
|
||||
padding: 40px 24px;
|
||||
border-right: 1px solid var(--hairline);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-height: 168px;
|
||||
justify-content: center;
|
||||
}
|
||||
.stat:last-child { border-right: none; }
|
||||
.stat-icon {
|
||||
width: 34px; height: 34px;
|
||||
color: var(--muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.stat:hover .stat-icon { color: var(--accent); }
|
||||
.stat-label {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.26em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.stat-value {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
font-size: 26px;
|
||||
line-height: 1.1;
|
||||
color: var(--fg);
|
||||
letter-spacing: -0.01em;
|
||||
word-break: break-word;
|
||||
}
|
||||
.stat-value.is-mono { font-family: var(--font-body); font-size: 15px; letter-spacing: 0.04em; }
|
||||
.stat-value.is-empty {
|
||||
color: var(--muted);
|
||||
opacity: 0.45;
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.stat-value .accent { color: var(--accent); }
|
||||
|
||||
/* Subscription access link */
|
||||
.subscription-link {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
background: color-mix(in srgb, var(--surface) 86%, var(--accent) 14%);
|
||||
}
|
||||
.subscription-emblem {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 50%;
|
||||
color: var(--fg);
|
||||
}
|
||||
.subscription-emblem svg { width: 18px; height: 18px; }
|
||||
.subscription-copy { min-width: 0; }
|
||||
.subscription-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.subscription-url {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: var(--fg);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.02em;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.subscription-url strong {
|
||||
color: var(--fg);
|
||||
font-weight: 400;
|
||||
}
|
||||
.copy-link {
|
||||
appearance: none;
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
border: 1px solid var(--fg);
|
||||
border-radius: var(--radius);
|
||||
background: var(--fg);
|
||||
color: var(--bg);
|
||||
font-family: var(--font-body);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
transition: background 0.25s, color 0.25s, border-color 0.25s, transform 0.15s;
|
||||
}
|
||||
.copy-link:hover { background: var(--accent); color: var(--fg); border-color: var(--accent); }
|
||||
.copy-link:active { transform: translateY(1px); }
|
||||
.copy-link:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.35);
|
||||
}
|
||||
|
||||
/* Devices section */
|
||||
.devices-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
padding: 48px 0 24px;
|
||||
}
|
||||
.devices-title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
font-size: 28px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.devices-count {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.26em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.devices-count strong {
|
||||
color: var(--fg);
|
||||
font-weight: 400;
|
||||
font-family: var(--font-display);
|
||||
font-size: 16px;
|
||||
margin-right: 4px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.device-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1px;
|
||||
background: var(--hairline);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
.device {
|
||||
background: var(--surface);
|
||||
padding: 28px 28px 24px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 22px;
|
||||
position: relative;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.device:hover { background: color-mix(in srgb, var(--surface) 80%, var(--fg) 4%); }
|
||||
|
||||
.device-glyph {
|
||||
flex: 0 0 auto;
|
||||
width: 48px; height: 48px;
|
||||
border: 1px solid var(--hairline-strong);
|
||||
border-radius: var(--radius);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
}
|
||||
.device-glyph svg { width: 22px; height: 22px; }
|
||||
|
||||
.device-meta {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.device-model {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.2;
|
||||
color: var(--fg);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.device-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 14px;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.device-tags .tag-client {
|
||||
color: var(--fg);
|
||||
}
|
||||
.device-tags .sep {
|
||||
width: 1px;
|
||||
background: var(--hairline-strong);
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.device-delete {
|
||||
flex: 0 0 auto;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: 1px solid var(--hairline-strong);
|
||||
color: var(--muted);
|
||||
width: 36px; height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: border-color 0.25s, color 0.25s, background 0.25s, transform 0.25s;
|
||||
}
|
||||
.device-delete:hover {
|
||||
border-color: var(--fg);
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
}
|
||||
.device-delete:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.35);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.device-delete svg { width: 14px; height: 14px; }
|
||||
|
||||
/* Empty devices state */
|
||||
.devices-empty {
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
padding: 72px 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.devices-empty-glyph {
|
||||
width: 44px; height: 44px;
|
||||
margin: 0 auto 20px;
|
||||
color: var(--muted);
|
||||
opacity: 0.55;
|
||||
}
|
||||
.devices-empty-title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
color: var(--fg);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.devices-empty-sub {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Footer rule */
|
||||
.foot {
|
||||
margin-top: 64px;
|
||||
padding-top: 28px;
|
||||
border-top: 1px solid var(--hairline);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Verification modal */
|
||||
.modal-veil {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(26, 22, 16, 0.32);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.25s;
|
||||
padding: 24px;
|
||||
}
|
||||
.modal-veil.is-open {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.modal {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--hairline-strong);
|
||||
border-radius: var(--radius);
|
||||
max-width: 420px;
|
||||
width: 100%;
|
||||
padding: 36px 32px 28px;
|
||||
text-align: center;
|
||||
transform: translateY(8px);
|
||||
transition: transform 0.3s cubic-bezier(.2,.7,.2,1);
|
||||
box-shadow: 0 24px 60px -20px rgba(26, 22, 16, 0.35);
|
||||
}
|
||||
.modal-veil.is-open .modal { transform: translateY(0); }
|
||||
|
||||
.modal-glyph {
|
||||
width: 52px; height: 52px;
|
||||
margin: 0 auto 20px;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--accent);
|
||||
}
|
||||
.modal-glyph svg { width: 22px; height: 22px; }
|
||||
|
||||
.modal-kicker {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.28em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.modal-title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
line-height: 1.25;
|
||||
color: var(--fg);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.modal-body {
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
line-height: 1.55;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
.modal-body strong {
|
||||
color: var(--fg);
|
||||
font-weight: 400;
|
||||
font-family: var(--font-display);
|
||||
font-size: 16px;
|
||||
}
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn {
|
||||
appearance: none;
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
padding: 13px 22px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--hairline-strong);
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
transition: background 0.25s, color 0.25s, border-color 0.25s, transform 0.15s;
|
||||
min-height: 44px;
|
||||
}
|
||||
.btn:hover { background: var(--surface); }
|
||||
.btn:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.35);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--fg);
|
||||
color: var(--bg);
|
||||
border-color: var(--fg);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--accent);
|
||||
color: var(--fg);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background: var(--fg);
|
||||
color: var(--bg);
|
||||
border-color: var(--fg);
|
||||
}
|
||||
.btn:active { transform: translateY(1px); }
|
||||
|
||||
/* Fade-up */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
transition: opacity 0.7s cubic-bezier(.2,.7,.2,1), transform 0.7s cubic-bezier(.2,.7,.2,1);
|
||||
}
|
||||
.reveal.is-in { opacity: 1; transform: translateY(0); }
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 16px);
|
||||
background: var(--fg);
|
||||
color: var(--bg);
|
||||
padding: 13px 22px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
z-index: 10001;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
box-shadow: 0 12px 30px -10px rgba(26, 22, 16, 0.4);
|
||||
}
|
||||
.toast.is-show { opacity: 1; transform: translate(-50%, 0); }
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 860px) {
|
||||
.profile { grid-template-columns: repeat(2, 1fr); }
|
||||
.stat:nth-child(2) { border-right: none; }
|
||||
.stat:nth-child(1), .stat:nth-child(2) { border-bottom: 1px solid var(--hairline); }
|
||||
.device-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 560px) {
|
||||
.shell { padding: 40px 20px 64px; }
|
||||
.topbar { padding-bottom: 24px; }
|
||||
.nav { gap: 18px; }
|
||||
.nav a { font-size: 10px; }
|
||||
.hero { padding: 40px 0 36px; }
|
||||
.hero-title { font-size: 34px; }
|
||||
.profile { grid-template-columns: 1fr; }
|
||||
.stat { border-right: none; border-bottom: 1px solid var(--hairline); min-height: 132px; padding: 32px 20px; }
|
||||
.stat:last-child { border-bottom: none; }
|
||||
.subscription-link { grid-template-columns: auto minmax(0, 1fr); gap: 16px; padding: 20px; }
|
||||
.subscription-url { font-size: 12px; }
|
||||
.copy-link { grid-column: 1 / -1; width: 100%; }
|
||||
.devices-head { padding: 40px 0 20px; }
|
||||
.device { padding: 22px 20px; gap: 16px; }
|
||||
.device-model { font-size: 18px; }
|
||||
.device-delete { width: 34px; height: 34px; }
|
||||
.foot { flex-direction: column; gap: 10px; text-align: center; }
|
||||
.modal { padding: 28px 24px 24px; }
|
||||
.modal-title { font-size: 21px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cursor-ring" aria-hidden="true"></div>
|
||||
<div class="cursor-dot" aria-hidden="true"></div>
|
||||
|
||||
<main class="shell">
|
||||
<!-- Topbar -->
|
||||
<header class="topbar" data-od-id="topbar">
|
||||
<div class="logo" data-od-id="brand">Mal<span class="e">e</span>nia</div>
|
||||
<nav class="nav" data-od-id="primary-nav">
|
||||
<a href="#" class="is-active">Account</a>
|
||||
<a href="#">Devices</a>
|
||||
<a href="#">Billing</a>
|
||||
<a href="#">Support</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Hero identity -->
|
||||
<section class="hero reveal" data-od-id="account-hero">
|
||||
<div class="kicker">Your account</div>
|
||||
<h1 class="hero-title">Eleanor Ashworth</h1>
|
||||
<p class="hero-sub">Member since March 2023 · Plan: Solitude</p>
|
||||
</section>
|
||||
|
||||
<!-- Profile stat strip -->
|
||||
<section class="profile reveal" data-od-id="profile-stats" aria-label="Account summary">
|
||||
<!-- Username -->
|
||||
<div class="stat" data-od-id="stat-username">
|
||||
<div class="stat-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%">
|
||||
<circle cx="12" cy="8" r="4"/>
|
||||
<path d="M4 20c0-3.6 3.6-6 8-6s8 2.4 8 6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-label">Username</div>
|
||||
<div class="stat-value">e.ashworth</div>
|
||||
</div>
|
||||
|
||||
<!-- Telegram ID -->
|
||||
<div class="stat" data-od-id="stat-telegram">
|
||||
<div class="stat-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%">
|
||||
<path d="M21 4L3 11l5 2 2 6 3-4 4 3 4-14z"/>
|
||||
<path d="M8 13l8-6"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-label">Telegram ID</div>
|
||||
<div class="stat-value is-mono">@eashworth</div>
|
||||
</div>
|
||||
|
||||
<!-- Device count -->
|
||||
<div class="stat" data-od-id="stat-device-count">
|
||||
<div class="stat-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%">
|
||||
<rect x="3" y="4" width="18" height="12" rx="1.5"/>
|
||||
<path d="M9 20h6M12 16v4"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-label">Devices linked</div>
|
||||
<div class="stat-value"><span id="device-count">4</span> <span class="accent">/</span> <span style="font-family:var(--font-body); font-size:14px; color:var(--muted); letter-spacing:0.04em">of 6</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Subscription expiry -->
|
||||
<div class="stat" data-od-id="stat-expiry">
|
||||
<div class="stat-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%">
|
||||
<rect x="3" y="5" width="18" height="16" rx="1.5"/>
|
||||
<path d="M3 9h18M8 3v4M16 3v4"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-label">Renews</div>
|
||||
<div class="stat-value">14 Mar 2026</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="subscription-link reveal" data-od-id="subscription-link" aria-label="Subscription link">
|
||||
<div class="subscription-emblem" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10 13a5 5 0 0 0 7.1.1l2-2a5 5 0 0 0-7.1-7.1l-1.1 1.1"/>
|
||||
<path d="M14 11a5 5 0 0 0-7.1-.1l-2 2A5 5 0 0 0 12 20l1.1-1.1"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="subscription-copy">
|
||||
<div class="subscription-label">Subscription link</div>
|
||||
<span class="subscription-url" id="subscription-url">https://malenia.network/sub/<strong>mle_7f2a9c</strong></span>
|
||||
</div>
|
||||
<button class="copy-link" id="copy-subscription-link" data-od-id="copy-subscription-link" data-cursor type="button">Copy link</button>
|
||||
</section>
|
||||
|
||||
<!-- Devices -->
|
||||
<section data-od-id="devices-section">
|
||||
<div class="devices-head reveal">
|
||||
<h2 class="devices-title">Connected devices</h2>
|
||||
<div class="devices-count" data-od-id="devices-count"><strong id="devices-count-num">4</strong>active</div>
|
||||
</div>
|
||||
|
||||
<div class="device-grid reveal" data-od-id="device-grid" id="device-grid">
|
||||
<!-- devices injected by JS -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="foot" data-od-id="footer">
|
||||
<span>MaleniaVPN · Solitude plan</span>
|
||||
<span>Sign out</span>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<!-- Verification modal -->
|
||||
<div class="modal-veil" id="modal-veil" data-od-id="delete-verify-modal" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-hidden="true">
|
||||
<div class="modal" role="document">
|
||||
<div class="modal-glyph" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="modal-kicker">Confirm removal</div>
|
||||
<h3 class="modal-title" id="modal-title">Remove this device?</h3>
|
||||
<p class="modal-body">Disconnecting <strong id="modal-device-name">—</strong> will end its VPN session immediately. You can reconnect it later from the device.</p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn" id="modal-cancel" data-od-id="modal-cancel">Keep it</button>
|
||||
<button class="btn btn-danger" id="modal-confirm" data-od-id="modal-confirm">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
// ----- Device data -----
|
||||
const devices = [
|
||||
{ id: 'd1', os: 'macos', model: 'MacBook Pro 14"', client: 'Malenia 2.4', joined: 'Joined 2d ago' },
|
||||
{ id: 'd2', os: 'ios', model: 'iPhone 15 Pro', client: 'Malenia 2.4', joined: 'Joined 11d ago' },
|
||||
{ id: 'd3', os: 'android', model: 'Pixel 8', client: 'OpenVPN', joined: 'Joined 28d ago' },
|
||||
{ id: 'd4', os: 'windows', model: 'Surface Laptop 5', client: 'Malenia 2.4', joined: 'Joined 3mo ago' }
|
||||
];
|
||||
|
||||
// ----- OS glyphs (monoline SVG) -----
|
||||
const glyphs = {
|
||||
macos: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="13" rx="1.5"/><path d="M1 20h22M9 20l1-3h4l1 3"/></svg>',
|
||||
ios: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="2.5" width="10" height="19" rx="2"/><path d="M11 5.5h2"/></svg>',
|
||||
android: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 14a7 7 0 0 1 14 0v5H5z"/><path d="M5 14L3 12M19 14l2-2M8 9l-1.5-2M16 9l1.5-2M9 8.5h6"/></svg>',
|
||||
windows: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5l8-1v8H3zM12 4l9-1v10h-9zM3 13h8v6l-8-1zM12 13h9v8l-9-1z"/></svg>',
|
||||
linux: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="13" rx="6" ry="7"/><path d="M9 9.5l1 1M15 9.5l-1 1M10 17h4"/></svg>'
|
||||
};
|
||||
const osLabels = { macos: 'macOS', ios: 'iOS', android: 'Android', windows: 'Windows', linux: 'Linux' };
|
||||
|
||||
// ----- Render -----
|
||||
const grid = document.getElementById('device-grid');
|
||||
function renderDevices() {
|
||||
grid.innerHTML = '';
|
||||
if (devices.length === 0) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'devices-empty';
|
||||
empty.setAttribute('data-od-id', 'devices-empty');
|
||||
empty.innerHTML = `
|
||||
<div class="devices-empty-glyph">${glyphs.macos}</div>
|
||||
<div class="devices-empty-title">No devices linked</div>
|
||||
<div class="devices-empty-sub">Install MaleniaVPN on a device to connect it to your account.</div>
|
||||
`;
|
||||
grid.appendChild(empty);
|
||||
} else {
|
||||
devices.forEach((d, i) => {
|
||||
const card = document.createElement('article');
|
||||
card.className = 'device reveal';
|
||||
card.setAttribute('data-od-id', 'device-' + d.id);
|
||||
card.innerHTML = `
|
||||
<div class="device-glyph" aria-hidden="true">${glyphs[d.os] || glyphs.linux}</div>
|
||||
<div class="device-meta">
|
||||
<h3 class="device-model">${d.model}</h3>
|
||||
<div class="device-tags">
|
||||
<span>${osLabels[d.os] || d.os}</span>
|
||||
<span class="sep"></span>
|
||||
<span class="tag-client">${d.client}</span>
|
||||
<span class="sep"></span>
|
||||
<span>${d.joined}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="device-delete" data-od-id="device-delete-${d.id}" data-cursor aria-label="Remove ${d.model}">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/></svg>
|
||||
</button>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => card.classList.add('is-in'), 40 * i);
|
||||
});
|
||||
});
|
||||
}
|
||||
document.getElementById('device-count').textContent = devices.length;
|
||||
document.getElementById('devices-count-num').textContent = devices.length;
|
||||
bindDeleteButtons();
|
||||
}
|
||||
|
||||
// ----- Delete flow -----
|
||||
const veil = document.getElementById('modal-veil');
|
||||
const modalName = document.getElementById('modal-device-name');
|
||||
const cancelBtn = document.getElementById('modal-cancel');
|
||||
const confirmBtn = document.getElementById('modal-confirm');
|
||||
const toast = document.getElementById('toast');
|
||||
let pendingId = null;
|
||||
|
||||
function bindDeleteButtons() {
|
||||
grid.querySelectorAll('.device-delete').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.getAttribute('data-od-id').replace('device-delete-', '');
|
||||
const d = devices.find(x => x.id === id);
|
||||
if (!d) return;
|
||||
pendingId = id;
|
||||
modalName.textContent = d.model;
|
||||
openModal();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
veil.classList.add('is-open');
|
||||
veil.setAttribute('aria-hidden', 'false');
|
||||
document.body.style.overflow = 'hidden';
|
||||
setTimeout(() => cancelBtn.focus(), 60);
|
||||
}
|
||||
function closeModal() {
|
||||
veil.classList.remove('is-open');
|
||||
veil.setAttribute('aria-hidden', 'true');
|
||||
document.body.style.overflow = '';
|
||||
pendingId = null;
|
||||
}
|
||||
function showToast(msg) {
|
||||
toast.textContent = msg;
|
||||
toast.classList.add('is-show');
|
||||
setTimeout(() => toast.classList.remove('is-show'), 2200);
|
||||
}
|
||||
|
||||
// ----- Subscription link -----
|
||||
const subscriptionUrl = document.getElementById('subscription-url');
|
||||
const copySubscriptionLink = document.getElementById('copy-subscription-link');
|
||||
copySubscriptionLink.addEventListener('click', async () => {
|
||||
const link = subscriptionUrl.textContent.trim();
|
||||
try {
|
||||
await navigator.clipboard.writeText(link);
|
||||
showToast('Subscription link copied');
|
||||
} catch (error) {
|
||||
const fallback = document.createElement('textarea');
|
||||
fallback.value = link;
|
||||
fallback.setAttribute('readonly', '');
|
||||
fallback.style.position = 'fixed';
|
||||
fallback.style.opacity = '0';
|
||||
document.body.appendChild(fallback);
|
||||
fallback.select();
|
||||
document.execCommand('copy');
|
||||
fallback.remove();
|
||||
showToast('Subscription link copied');
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener('click', closeModal);
|
||||
confirmBtn.addEventListener('click', () => {
|
||||
if (!pendingId) return;
|
||||
const idx = devices.findIndex(x => x.id === pendingId);
|
||||
if (idx > -1) {
|
||||
const removed = devices.splice(idx, 1)[0];
|
||||
renderDevices();
|
||||
showToast(removed.model + ' disconnected');
|
||||
}
|
||||
closeModal();
|
||||
});
|
||||
veil.addEventListener('click', (e) => { if (e.target === veil) closeModal(); });
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && veil.classList.contains('is-open')) closeModal();
|
||||
});
|
||||
|
||||
// ----- Reveal observer -----
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach(en => {
|
||||
if (en.isIntersecting) {
|
||||
en.target.classList.add('is-in');
|
||||
io.unobserve(en.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.12 });
|
||||
document.querySelectorAll('.reveal').forEach((el, i) => {
|
||||
el.style.transitionDelay = (i * 80) + 'ms';
|
||||
io.observe(el);
|
||||
});
|
||||
|
||||
// ----- Custom cursor -----
|
||||
const dot = document.querySelector('.cursor-dot');
|
||||
const ring = document.querySelector('.cursor-ring');
|
||||
let mx = 0, my = 0, rx = 0, ry = 0;
|
||||
const fine = window.matchMedia('(hover: hover) and (pointer: fine)').matches;
|
||||
if (fine) {
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
mx = e.clientX; my = e.clientY;
|
||||
dot.style.left = mx + 'px'; dot.style.top = my + 'px';
|
||||
});
|
||||
function tick() {
|
||||
rx += (mx - rx) * 0.18; ry += (my - ry) * 0.18;
|
||||
ring.style.left = rx + 'px'; ring.style.top = ry + 'px';
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
document.addEventListener('mouseover', (e) => {
|
||||
const t = e.target.closest('a, button, [data-cursor]');
|
||||
ring.classList.toggle('is-active', !!t);
|
||||
});
|
||||
} else {
|
||||
dot.style.display = 'none'; ring.style.display = 'none';
|
||||
}
|
||||
|
||||
// ----- Init -----
|
||||
renderDevices();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1212
.ref/calculator.html
Normal file
1212
.ref/calculator.html
Normal file
File diff suppressed because it is too large
Load Diff
467
.ref/landing.html
Normal file
467
.ref/landing.html
Normal file
@@ -0,0 +1,467 @@
|
||||
<html lang="ru"><head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Malenia — Свобода в сети</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=Tenor+Sans&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<style>
|
||||
:root {
|
||||
--gold: #C9A84C;
|
||||
--gold-light: #E8CA7A;
|
||||
--gold-dim: #8B6914;
|
||||
--cream: #FAF7F2;
|
||||
--warm-white: #F5F0E8;
|
||||
--ink: #1A1610;
|
||||
--ink-soft: #3D3428;
|
||||
--ink-muted: #7A6E5F;
|
||||
--border: rgba(201,168,76,0.25);
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html { cursor: none; }
|
||||
|
||||
body {
|
||||
font-family: 'Tenor Sans', sans-serif;
|
||||
background: var(--cream);
|
||||
color: var(--ink);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
/* ── Custom cursor ─────────────────────────────────── */
|
||||
#cursor-dot,
|
||||
#cursor-ring {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: opacity .3s;
|
||||
}
|
||||
#cursor-dot {
|
||||
width: 6px; height: 6px;
|
||||
background: var(--gold);
|
||||
border-radius: 50%;
|
||||
}
|
||||
#cursor-ring {
|
||||
width: 32px; height: 32px;
|
||||
border: 1px solid var(--gold);
|
||||
border-radius: 50%;
|
||||
transition: width .25s cubic-bezier(.4,0,.2,1),
|
||||
height .25s cubic-bezier(.4,0,.2,1),
|
||||
opacity .3s;
|
||||
}
|
||||
body.hovering #cursor-ring {
|
||||
width: 48px; height: 48px;
|
||||
border-color: var(--gold-light);
|
||||
}
|
||||
|
||||
/* ── Noise overlay ─────────────────────────────────── */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
opacity: .035;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
background-repeat: repeat;
|
||||
background-size: 180px;
|
||||
}
|
||||
|
||||
/* ── Layout ────────────────────────────────────────── */
|
||||
.page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
/* ── Header ────────────────────────────────────────── */
|
||||
header {
|
||||
padding: 2.5rem 4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: 'Cormorant Garamond', serif;
|
||||
font-weight: 300;
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: .35em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
}
|
||||
.logo span { color: var(--gold); }
|
||||
|
||||
.header-rule {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
margin: 0 2rem;
|
||||
}
|
||||
|
||||
.header-tag {
|
||||
font-size: .65rem;
|
||||
letter-spacing: .25em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
/* ── Hero ──────────────────────────────────────────── */
|
||||
.hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 6rem 2rem;
|
||||
gap: 3.5rem;
|
||||
}
|
||||
|
||||
.emblem {
|
||||
position: relative;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
opacity: 0;
|
||||
animation: fadeUp .9s .1s forwards;
|
||||
}
|
||||
.emblem svg { width: 100%; height: 100%; }
|
||||
|
||||
.quote-block {
|
||||
max-width: 640px;
|
||||
opacity: 0;
|
||||
animation: fadeUp .9s .25s forwards;
|
||||
}
|
||||
|
||||
.quote-text {
|
||||
font-family: 'Cormorant Garamond', serif;
|
||||
font-size: clamp(2rem, 5vw, 3.4rem);
|
||||
font-weight: 300;
|
||||
line-height: 1.25;
|
||||
color: var(--ink);
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
.quote-text em {
|
||||
font-style: italic;
|
||||
color: var(--gold-dim);
|
||||
}
|
||||
|
||||
.quote-author {
|
||||
margin-top: 1.25rem;
|
||||
font-size: .7rem;
|
||||
letter-spacing: .3em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
/* ── Divider ───────────────────────────────────────── */
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
opacity: 0;
|
||||
animation: fadeUp .9s .4s forwards;
|
||||
}
|
||||
.divider::before,
|
||||
.divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
.divider-diamond {
|
||||
width: 6px; height: 6px;
|
||||
background: var(--gold);
|
||||
transform: rotate(45deg);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Buttons ───────────────────────────────────────── */
|
||||
.cta-group {
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
animation: fadeUp .9s .55s forwards;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
padding: .85rem 2rem;
|
||||
font-family: 'Tenor Sans', sans-serif;
|
||||
font-size: .78rem;
|
||||
letter-spacing: .18em;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
border-radius: 0;
|
||||
cursor: none;
|
||||
transition: color .25s, background .25s, border-color .25s;
|
||||
overflow: hidden;
|
||||
}
|
||||
.btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--gold);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform .35s cubic-bezier(.4,0,.2,1);
|
||||
z-index: 0;
|
||||
}
|
||||
.btn:hover::after { transform: scaleX(1); }
|
||||
.btn > * { position: relative; z-index: 1; }
|
||||
|
||||
.btn-primary {
|
||||
background: var(--ink);
|
||||
color: var(--cream);
|
||||
border: 1px solid var(--ink);
|
||||
}
|
||||
.btn-primary:hover { color: var(--ink); border-color: var(--gold); }
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--ink);
|
||||
border: 1px solid var(--gold);
|
||||
}
|
||||
.btn-secondary::after { background: var(--ink); }
|
||||
.btn-secondary:hover { color: var(--cream); }
|
||||
|
||||
.btn-icon {
|
||||
width: 18px; height: 18px;
|
||||
fill: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Footer ────────────────────────────────────────── */
|
||||
footer {
|
||||
padding: 2rem 4rem;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.footer-copy {
|
||||
font-size: .65rem;
|
||||
letter-spacing: .2em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.footer-dot {
|
||||
width: 4px; height: 4px;
|
||||
background: var(--gold);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* ── Animations ────────────────────────────────────── */
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(22px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ── Responsive ────────────────────────────────────── */
|
||||
@media (max-width: 640px) {
|
||||
header { padding: 1.75rem 1.5rem; }
|
||||
.header-rule { display: none; }
|
||||
.header-tag { display: none; }
|
||||
.hero { padding: 4rem 1.5rem; gap: 2.5rem; }
|
||||
footer { padding: 1.5rem; flex-direction: column; align-items: flex-start; }
|
||||
.btn { padding: .8rem 1.5rem; font-size: .72rem; }
|
||||
.cta-group { flex-direction: column; align-items: stretch; }
|
||||
.btn { justify-content: center; }
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Cursor -->
|
||||
<div id="cursor-dot" style="left: 233px; top: 539px;"></div>
|
||||
<div id="cursor-ring" style="left: 233px; top: 539px;"></div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<a class="logo" href="#">Mal<span>e</span>nia</a>
|
||||
<div class="header-rule"></div>
|
||||
<span class="header-tag">Свобода. Практика. Простота.</span>
|
||||
</header>
|
||||
|
||||
<!-- Hero -->
|
||||
<main class="hero">
|
||||
|
||||
<!-- Emblem -->
|
||||
<div class="emblem" aria-hidden="true">
|
||||
<svg viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- outer ring -->
|
||||
<circle cx="45" cy="45" r="42" stroke="#C9A84C" stroke-width=".8" stroke-dasharray="3 4"></circle>
|
||||
<!-- inner ring -->
|
||||
<circle cx="45" cy="45" r="32" stroke="#C9A84C" stroke-width=".5" opacity=".5"></circle>
|
||||
<!-- diamond -->
|
||||
<path d="M45 18 L72 45 L45 72 L18 45 Z" stroke="#C9A84C" stroke-width=".9" fill="none"></path>
|
||||
<!-- center dot -->
|
||||
<circle cx="45" cy="45" r="3.5" fill="#C9A84C"></circle>
|
||||
<!-- cardinal marks -->
|
||||
<line x1="45" y1="3" x2="45" y2="12" stroke="#C9A84C" stroke-width=".8"></line>
|
||||
<line x1="45" y1="78" x2="45" y2="87" stroke="#C9A84C" stroke-width=".8"></line>
|
||||
<line x1="3" y1="45" x2="12" y2="45" stroke="#C9A84C" stroke-width=".8"></line>
|
||||
<line x1="78" y1="45" x2="87" y2="45" stroke="#C9A84C" stroke-width=".8"></line>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Quote -->
|
||||
<div class="quote-block">
|
||||
<p class="quote-text" id="quote-text">Верните себе<br><em>свободный интернет.</em></p>
|
||||
<p class="quote-author">malenia.space</p>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="divider" aria-hidden="true">
|
||||
<div class="divider-diamond"></div>
|
||||
</div>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="cta-group">
|
||||
|
||||
<a class="btn btn-primary" href="https://t.me/maleniavpn" target="_blank" rel="noopener">
|
||||
<!-- Telegram icon -->
|
||||
<svg class="btn-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.265 2.428a1.523 1.523 0 0 0-1.547-.232L1.91 9.9a1.523 1.523 0 0 0 .089 2.844l4.498 1.498 1.815 5.784a.761.761 0 0 0 1.295.3l2.617-2.818 4.898 3.674a1.523 1.523 0 0 0 2.389-.98l3.047-16.762a1.523 1.523 0 0 0-.293-1.012zM9.94 13.88l-.98 3.267-.98-3.143L18.5 6.04z"></path>
|
||||
</svg>
|
||||
<span>Канал</span>
|
||||
</a>
|
||||
|
||||
<a class="btn btn-secondary" href="https://t.me/maleniasupportbot" target="_blank" rel="noopener">
|
||||
<!-- Bot icon -->
|
||||
<svg class="btn-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2a2 2 0 0 1 2 2 2 2 0 0 1-1 1.732V7h3a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3v-8a3 3 0 0 1 3-3h3V5.732A2 2 0 0 1 10 4a2 2 0 0 1 2-2zm-3 9a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm6 0a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"></path>
|
||||
</svg>
|
||||
<span>Поддержка</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<span class="footer-copy">© 2026 Malenia</span>
|
||||
<div class="footer-dot"></div>
|
||||
<span class="footer-copy">malenia.space</span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ── Quotes ─────────────────────────────────────────── */
|
||||
const quotes = [
|
||||
"Верните себе\nсвободный интернет.",
|
||||
"Свобода должна\nбыть доступной.",
|
||||
"Интернет без границ —\nне привилегия, а норма.",
|
||||
"Ваш трафик.\nВаши правила.",
|
||||
"Тишина в сети —\nэто тоже выбор.",
|
||||
"Открытый мир\nначинается здесь.",
|
||||
"Без цензуры.\nБез компромиссов.",
|
||||
"Простота —\nлучшая форма свободы.",
|
||||
"Один клик\nмежду вами и миром.",
|
||||
"Malenia —\nсвобода в форме привычки.",
|
||||
"Будьте везде.\nОставайтесь невидимым.",
|
||||
"Приватность — это право,\nа не роскошь.",
|
||||
"Интернет таким,\nкаким он задумывался.",
|
||||
"Ваши данные —\nтолько ваше дело.",
|
||||
"Закрытых дверей\nбольше не существует.",
|
||||
"Любой сайт.\nИз любой точки мира.",
|
||||
"Оставьте след в истории,\nа не в браузере.",
|
||||
"Malenia —\nключ к открытой сети.",
|
||||
"Фильтры придумали они.\nРешение выбрали вы.",
|
||||
"Безопасность, которую\nвы не замечаете.",
|
||||
"Сотрите цифровые границы.\nОстаньтесь собой.",
|
||||
"Скорость без компромиссов.\nСеть без преград.",
|
||||
"Включили и забыли.\nВы в безопасности.",
|
||||
"Блокировки иллюзорны.\nСвобода реальна.",
|
||||
"Никакой слежки.\nТолько вы и интернет.",
|
||||
"Доступ к информации —\nваше базовое право.",
|
||||
"Malenia.\nИнтернет снова ваш.",
|
||||
"Контроль над сетью\nв ваших руках.",
|
||||
"Невидимый щит\nдля вашей свободы.",
|
||||
"Весь мир на экране.\nБез лишних вопросов.",
|
||||
"Никогда не поздно\nвыбрать свободу.",
|
||||
"Привычка к запретам —\nповод проснуться.",
|
||||
"Откройте глаза.\nСтен больше нет.",
|
||||
"Вспомните, как выглядит\nнастоящая сеть.",
|
||||
"Запреты стали нормой.\nПора это менять.",
|
||||
"Свобода начинается\nс вашего решения.",
|
||||
"Выход из спящего режима.\nСделайте первый шаг.",
|
||||
"Лучший момент для\nсвободы — сейчас.",
|
||||
"Иллюзия границ рушится\nв один клик.",
|
||||
"Хватит мириться с рамками.\nМир шире, чем кажется.",
|
||||
"Не ждите свободы.\nВключите её.",
|
||||
"Цифровая амнезия окончена.\nВерните своё.",
|
||||
"Осознанность начинается\nтам, где кончается цензура.",
|
||||
"Первый шаг к свободе —\nперестать верить в стены.",
|
||||
"Malenia.\nВаше цифровое пробуждение."
|
||||
];
|
||||
|
||||
function renderQuote(raw) {
|
||||
// italic the second line if two lines exist
|
||||
const lines = raw.split('\n');
|
||||
if (lines.length === 2) {
|
||||
return `${lines[0]}<br><em>${lines[1]}</em>`;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
const el = document.getElementById('quote-text');
|
||||
const chosen = quotes[Math.floor(Math.random() * quotes.length)];
|
||||
el.innerHTML = renderQuote(chosen);
|
||||
|
||||
/* ── Custom cursor ──────────────────────────────────── */
|
||||
const dot = document.getElementById('cursor-dot');
|
||||
const ring = document.getElementById('cursor-ring');
|
||||
let mx = -100, my = -100, rx = -100, ry = -100;
|
||||
|
||||
document.addEventListener('mousemove', e => { mx = e.clientX; my = e.clientY; });
|
||||
|
||||
(function loop() {
|
||||
rx += (mx - rx) * .12;
|
||||
ry += (my - ry) * .12;
|
||||
dot.style.left = mx + 'px';
|
||||
dot.style.top = my + 'px';
|
||||
ring.style.left = rx + 'px';
|
||||
ring.style.top = ry + 'px';
|
||||
requestAnimationFrame(loop);
|
||||
})();
|
||||
|
||||
document.querySelectorAll('a, button').forEach(el => {
|
||||
el.addEventListener('mouseenter', () => document.body.classList.add('hovering'));
|
||||
el.addEventListener('mouseleave', () => document.body.classList.remove('hovering'));
|
||||
});
|
||||
|
||||
document.addEventListener('mouseleave', () => {
|
||||
dot.style.opacity = '0';
|
||||
ring.style.opacity = '0';
|
||||
});
|
||||
document.addEventListener('mouseenter', () => {
|
||||
dot.style.opacity = '1';
|
||||
ring.style.opacity = '1';
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body></html>
|
||||
561
.ref/login.html
Normal file
561
.ref/login.html
Normal file
@@ -0,0 +1,561 @@
|
||||
<!doctype html>
|
||||
<html lang="en"><head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MaleniaVPN · Sign In</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600&family=Tenor+Sans&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg: #faf7f2;
|
||||
--surface: #f5f0e8;
|
||||
--fg: #1a1610;
|
||||
--muted: #3d3428;
|
||||
--accent: #c9a84c;
|
||||
--font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
|
||||
--font-body: 'Tenor Sans', 'Helvetica Neue', Arial, sans-serif;
|
||||
--radius: 8px;
|
||||
--baseline: 8px;
|
||||
--hairline: rgba(26, 22, 16, 0.10);
|
||||
--hairline-strong: rgba(26, 22, 16, 0.18);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Grain overlay */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9998;
|
||||
opacity: 0.035;
|
||||
mix-blend-mode: multiply;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* Custom cursor */
|
||||
.cursor-dot, .cursor-ring {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.cursor-dot {
|
||||
width: 5px; height: 5px;
|
||||
background: var(--fg);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.cursor-ring {
|
||||
width: 30px; height: 30px;
|
||||
border: 1px solid var(--muted);
|
||||
border-radius: 50%;
|
||||
opacity: 0.4;
|
||||
transition: width 0.25s, height 0.25s, border-color 0.25s, opacity 0.25s, transform 0.08s linear;
|
||||
}
|
||||
.cursor-ring.is-active {
|
||||
width: 52px; height: 52px;
|
||||
border-color: var(--accent);
|
||||
opacity: 0.9;
|
||||
}
|
||||
@media (hover: none), (pointer: coarse) {
|
||||
.cursor-dot, .cursor-ring { display: none; }
|
||||
* { cursor: auto !important; }
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
body, button, a, input, [data-cursor] { cursor: none; }
|
||||
}
|
||||
|
||||
/* Shell */
|
||||
.shell {
|
||||
max-width: 460px;
|
||||
margin: 0 auto;
|
||||
padding: 56px 32px 48px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Topbar — logo + kicker anchored to the top, together */
|
||||
.topbar {
|
||||
text-align: center;
|
||||
}
|
||||
.topbar .kicker {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.logo {
|
||||
font-family: var(--font-display);
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.35em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.logo .e { color: var(--accent); }
|
||||
|
||||
/* Emblem — geometric mark above the form */
|
||||
.emblem {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 28px;
|
||||
color: var(--accent);
|
||||
}
|
||||
.emblem svg { width: 44px; height: 44px; display: block; }
|
||||
|
||||
/* Card — centered in remaining space */
|
||||
.card {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.kicker {
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.28em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 500;
|
||||
font-size: 36px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.01em;
|
||||
text-align: center;
|
||||
color: var(--fg);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.sub {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 22px;
|
||||
}
|
||||
.field {
|
||||
position: relative;
|
||||
}
|
||||
.field-label {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.26em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.field-input {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--hairline-strong);
|
||||
padding: 8px 36px 12px 0;
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
color: var(--fg);
|
||||
letter-spacing: 0.02em;
|
||||
transition: border-color 0.3s;
|
||||
outline: none;
|
||||
}
|
||||
.field-input::placeholder {
|
||||
color: var(--muted);
|
||||
opacity: 0.45;
|
||||
font-style: italic;
|
||||
}
|
||||
.field-input:focus {
|
||||
border-color: var(--fg);
|
||||
}
|
||||
.field-input:focus + .field-line {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
.field-line {
|
||||
position: absolute;
|
||||
left: 0; right: 0; bottom: 0;
|
||||
height: 1px;
|
||||
background: var(--accent);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.field-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 12px;
|
||||
color: var(--muted);
|
||||
opacity: 0.5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: opacity 0.25s, color 0.25s;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
.field-icon svg { width: 18px; height: 18px; display: block; }
|
||||
.field-icon:hover { opacity: 0.9; color: var(--fg); }
|
||||
.field-icon:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Row: remember + forgot */
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.check {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.check input {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 16px; height: 16px;
|
||||
border: 1px solid var(--hairline-strong);
|
||||
border-radius: 3px;
|
||||
background: transparent;
|
||||
position: relative;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.check input:checked {
|
||||
background: var(--fg);
|
||||
border-color: var(--fg);
|
||||
}
|
||||
.check input:checked::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 4px; top: 1px;
|
||||
width: 5px; height: 9px;
|
||||
border: solid var(--bg);
|
||||
border-width: 0 1.5px 1.5px 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.check input:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.link {
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.02em;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: color 0.25s, border-color 0.25s;
|
||||
}
|
||||
.link:hover { color: var(--fg); border-bottom-color: var(--hairline-strong); }
|
||||
.link:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Primary action */
|
||||
.btn {
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
padding: 16px 28px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--fg);
|
||||
background: var(--fg);
|
||||
color: var(--bg);
|
||||
cursor: pointer;
|
||||
transition: background 0.25s, color 0.25s, border-color 0.25s, transform 0.08s;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.btn:hover {
|
||||
background: var(--muted);
|
||||
border-color: var(--muted);
|
||||
}
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.btn:active { transform: translateY(1px); }
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Divider */
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin: 32px 0 22px;
|
||||
color: var(--muted);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.28em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.divider::before,
|
||||
.divider::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--hairline);
|
||||
}
|
||||
|
||||
/* Secondary — telegram */
|
||||
.btn-ghost {
|
||||
width: 100%;
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
padding: 15px 28px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--hairline-strong);
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
transition: border-color 0.25s, background 0.25s, transform 0.08s;
|
||||
}
|
||||
.btn-ghost:hover {
|
||||
border-color: var(--fg);
|
||||
background: var(--surface);
|
||||
}
|
||||
.btn-ghost:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.btn-ghost:active { transform: translateY(1px); }
|
||||
.btn-ghost svg { width: 16px; height: 16px; }
|
||||
|
||||
/* Sign-up hint */
|
||||
.signup {
|
||||
text-align: center;
|
||||
margin-top: 36px;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.signup .link { color: var(--fg); }
|
||||
|
||||
/* Error / status */
|
||||
.status {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--muted);
|
||||
margin-top: 16px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
.status:empty { display: none; }
|
||||
.status.is-error { color: var(--fg); opacity: 1; }
|
||||
.status.is-info { opacity: 1; }
|
||||
|
||||
/* Footer */
|
||||
.foot {
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
opacity: 0.6;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
/* Reveal animation */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
transition: opacity 0.7s ease, transform 0.7s ease;
|
||||
}
|
||||
.reveal.is-in {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 480px) {
|
||||
.shell { padding: 40px 22px 36px; }
|
||||
.title { font-size: 30px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cursor-dot"></div>
|
||||
<div class="cursor-ring"></div>
|
||||
|
||||
<div class="shell">
|
||||
<header class="topbar reveal" data-od-id="topbar">
|
||||
<a href="malenia-vpn-account.html" class="logo" data-od-id="logo" aria-label="MaleniaVPN home">
|
||||
Mal<span class="e">e</span>nia
|
||||
</a>
|
||||
<div class="kicker">Private access</div>
|
||||
</header>
|
||||
|
||||
<main class="card">
|
||||
<h1 class="title reveal" data-od-id="login-title">Sign in</h1>
|
||||
<p class="sub reveal">Enter your credentials to resume your session.</p>
|
||||
|
||||
<form class="form reveal" data-od-id="login-form" novalidate="">
|
||||
<div class="field" data-od-id="field-username">
|
||||
<label class="field-label" for="username">Username</label>
|
||||
<input class="field-input" id="username" name="username" type="text" autocomplete="username" placeholder="your handle" spellcheck="false" required="">
|
||||
<span class="field-line"></span>
|
||||
</div>
|
||||
|
||||
<div class="field" data-od-id="field-password">
|
||||
<label class="field-label" for="password">Password</label>
|
||||
<input class="field-input" id="password" name="password" type="password" autocomplete="current-password" placeholder="••••••••" required="">
|
||||
<span class="field-line"></span>
|
||||
<button type="button" class="field-icon" id="toggle-pass" data-od-id="toggle-password" aria-label="Show password" aria-pressed="false">
|
||||
<svg id="eye-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row" data-od-id="login-options">
|
||||
<label class="check">
|
||||
<input type="checkbox" id="remember" name="remember">
|
||||
<span>Keep me signed in</span>
|
||||
</label>
|
||||
<a href="#" class="link" data-od-id="forgot-password">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn" data-od-id="btn-signin">Sign in</button>
|
||||
|
||||
<div class="status" id="status" role="status" aria-live="polite"></div>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<p class="signup reveal" data-od-id="signup-hint">
|
||||
No account yet? <a href="#" class="link" data-od-id="signup-link">Create one</a>
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<footer class="foot reveal" data-od-id="footer">
|
||||
Protected by MaleniaVPN
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
// ----- Password visibility toggle -----
|
||||
const toggle = document.getElementById('toggle-pass');
|
||||
const pass = document.getElementById('password');
|
||||
const eye = document.getElementById('eye-icon');
|
||||
toggle.addEventListener('click', () => {
|
||||
const show = pass.type === 'password';
|
||||
pass.type = show ? 'text' : 'password';
|
||||
toggle.setAttribute('aria-pressed', String(show));
|
||||
toggle.setAttribute('aria-label', show ? 'Hide password' : 'Show password');
|
||||
eye.innerHTML = show
|
||||
? '<path d="M3 3l18 18M10.5 10.7a3 3 0 0 0 4 4M9.4 5.2A10.6 10.6 0 0 1 12 5c6.5 0 10 7 10 7a17.3 17.3 0 0 1-3.2 4.1M6.1 6.2A17.3 17.3 0 0 0 2 12s3.5 7 10 7c1.4 0 2.7-.3 3.8-.7"/>'
|
||||
: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" /><circle cx="12" cy="12" r="3" />';
|
||||
});
|
||||
|
||||
// ----- Form submit (demo) -----
|
||||
const form = document.querySelector('.form');
|
||||
const status = document.getElementById('status');
|
||||
const btn = form.querySelector('.btn');
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const u = document.getElementById('username').value.trim();
|
||||
const p = document.getElementById('password').value;
|
||||
status.classList.remove('is-error', 'is-info');
|
||||
if (!u || !p) {
|
||||
status.textContent = 'Username and password are required.';
|
||||
status.classList.add('is-error');
|
||||
return;
|
||||
}
|
||||
status.textContent = 'Verifying…';
|
||||
status.classList.add('is-info');
|
||||
btn.disabled = true;
|
||||
setTimeout(() => {
|
||||
btn.disabled = false;
|
||||
status.classList.remove('is-info');
|
||||
status.textContent = 'Unable to reach server. Check your connection.';
|
||||
status.classList.add('is-error');
|
||||
}, 1400);
|
||||
});
|
||||
|
||||
// ----- Reveal observer -----
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach((en) => {
|
||||
if (en.isIntersecting) {
|
||||
en.target.classList.add('is-in');
|
||||
io.unobserve(en.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.12 });
|
||||
document.querySelectorAll('.reveal').forEach((el, i) => {
|
||||
el.style.transitionDelay = (i * 70) + 'ms';
|
||||
io.observe(el);
|
||||
});
|
||||
|
||||
// ----- Custom cursor -----
|
||||
const dot = document.querySelector('.cursor-dot');
|
||||
const ring = document.querySelector('.cursor-ring');
|
||||
let mx = 0, my = 0, rx = 0, ry = 0;
|
||||
const fine = window.matchMedia('(hover: hover) and (pointer: fine)').matches;
|
||||
if (fine) {
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
mx = e.clientX; my = e.clientY;
|
||||
dot.style.left = mx + 'px'; dot.style.top = my + 'px';
|
||||
});
|
||||
function tick() {
|
||||
rx += (mx - rx) * 0.18; ry += (my - ry) * 0.18;
|
||||
ring.style.left = rx + 'px'; ring.style.top = ry + 'px';
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
tick();
|
||||
document.addEventListener('mouseover', (e) => {
|
||||
const t = e.target.closest('a, button, input, [data-cursor]');
|
||||
ring.classList.toggle('is-active', !!t);
|
||||
});
|
||||
} else {
|
||||
dot.style.display = 'none'; ring.style.display = 'none';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>
|
||||
Reference in New Issue
Block a user