v0.0.01b
This commit is contained in:
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