561 lines
15 KiB
HTML
561 lines
15 KiB
HTML
<!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> |