<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AXIOM — Access Terminal</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=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
</head>
<body>
<main class="stage">
<!-- ================= ORBITAL SCENE ================= -->
<section class="scene">
<canvas id="scene" aria-hidden="true"></canvas>
<div class="scene-vignette" aria-hidden="true"></div>
<div class="grain" aria-hidden="true"></div>
<header class="scene-top">
<div class="telemetry">
<span class="tel"><span class="tel-k">RELAY</span><span class="tel-v">K-7 · ORBITAL</span></span>
<span class="tel"><span class="tel-k">SIG</span><span class="tel-v" id="telSig">98.6%</span></span>
<span class="tel"><span class="tel-k">UTC</span><span class="tel-v" id="telClock">--:--:--</span></span>
</div>
<p class="scene-manifest">DEEP-FIELD RELAY NETWORK<br>4,812 NODES · HANDSHAKE READY</p>
</header>
<footer class="scene-bottom">
<p class="scene-caption">Every channel routes through the dark relay belt. No traces, no logs, no compromise.</p>
<div class="scene-meta">
<span>REV 7.4.1</span><span id="telLat">LAT 44.21°N</span><span>LON 03.14°E</span>
</div>
</footer>
</section>
<!-- ================= ACCESS TERMINAL ================= -->
<section class="terminal" id="terminal">
<div class="cursor-light" id="cursorLight" aria-hidden="true"></div>
<span class="edge-note">AUTH.GATE 07 — RESTRICTED ACCESS</span>
<header class="term-head">
<div class="brand">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="4.1" stroke="currentColor" stroke-width="1.4"/>
<ellipse cx="12" cy="12" rx="10" ry="4.3" stroke="currentColor" stroke-width="1" opacity=".5" transform="rotate(-24 12 12)"/>
<circle cx="21.1" cy="7.9" r="1.4" fill="#E2A44D"/>
</svg>
<span class="brand-word">AXIOM</span>
</div>
<div class="channel" id="channel"><i class="dot"></i><span id="channelText">SECURE CHANNEL</span></div>
</header>
<div class="term-body">
<div class="term-inner">
<h1 class="rise" style="--d:0">Access Terminal</h1>
<p class="lede rise" style="--d:1">Authenticate to open a private channel with the Axiom deep-field relay network.</p>
<form id="loginForm" novalidate>
<!-- 01 · identifier -->
<div class="field rise" style="--d:2" id="fId">
<div class="field-head">
<label for="identifier"><span class="idx">01</span>IDENTIFIER</label>
<span class="field-msg" id="idMsg" aria-live="polite"></span>
</div>
<div class="field-line">
<input id="identifier" type="text" autocomplete="username" autocapitalize="off"
spellcheck="false" placeholder="operator@axiom.space">
<i class="tick tl" aria-hidden="true"></i><i class="tick br" aria-hidden="true"></i>
<i class="rail" aria-hidden="true"></i>
</div>
</div>
<!-- 02 · passphrase -->
<div class="field rise" style="--d:3" id="fPw">
<div class="field-head">
<label for="password"><span class="idx">02</span>PASSPHRASE</label>
<span class="field-msg" id="pwMsg" aria-live="polite"></span>
</div>
<div class="field-line">
<input id="password" type="password" autocomplete="current-password"
placeholder="••••••••••••">
<button type="button" class="eye" id="eyeBtn" aria-label="Show passphrase" aria-pressed="false">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M2.6 12c1.9-3.6 5.4-5.7 9.4-5.7s7.5 2.1 9.4 5.7c-1.9 3.6-5.4 5.7-9.4 5.7S4.5 15.6 2.6 12Z"/>
<circle cx="12" cy="12" r="2.6"/>
<path class="lash" d="M4.5 19.5 19.5 4.5"/>
</svg>
</button>
<i class="tick tl" aria-hidden="true"></i><i class="tick br" aria-hidden="true"></i>
<i class="rail" aria-hidden="true"></i>
</div>
<!-- strength instrument -->
<div class="strength" id="strength" data-level="0">
<div class="strength-inner">
<div class="strength-row">
<div class="cells" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></div>
<span class="strength-label" id="strengthLabel" role="status" aria-live="polite">—</span>
</div>
<canvas id="wave" aria-hidden="true"></canvas>
<div class="strength-meta">
<span id="mBits">EST. ENTROPY 0 BITS</span>
<span id="mLen">LENGTH 0</span>
<span id="mPool">POOL 0</span>
</div>
<p class="strength-tip" id="strengthTip"></p>
</div>
</div>
</div>
<div class="row-between rise" style="--d:4">
<label class="check">
<input type="checkbox" id="remember">
<span class="box" aria-hidden="true">
<svg viewBox="0 0 14 12"><path d="M2 6.5l3.5 3.5L12 2"/></svg>
</span>
<span class="check-label">Remember this terminal</span>
</label>
<button type="button" class="link" id="forgotLink">Forgot passphrase?</button>
</div>
<div class="rise" style="--d:5">
<button class="btn" id="loginBtn" type="submit">
<span class="fill" aria-hidden="true"></span>
<span class="scan" aria-hidden="true"></span>
<span class="content">
<span id="btnLabel">Open secure channel</span>
<svg class="ic arrow" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12h15M13 5.5 19.5 12 13 18.5"/></svg>
<svg class="ic ic-check" viewBox="0 0 24 24" aria-hidden="true"><path d="M4.5 12.5 9.5 17.5 19.5 6.5"/></svg>
</span>
</button>
</div>
</form>
<div class="term-foot rise" style="--d:6">
<span>NO CLEARANCE YET?</span>
<button type="button" class="link" id="requestLink" style="text-transform:uppercase;letter-spacing:.16em;font-size:9.5px">Request access</button>
</div>
</div>
</div>
<footer class="term-base">
<span>© 2087 AXIOM DYNAMICS</span>
<span>TLS 1.3 · X25519</span>
</footer>
</section>
</main>
<!-- ================= MODAL ================= -->
<div class="overlay" id="overlay" role="dialog" aria-modal="true" aria-labelledby="modalTitle" hidden>
<div class="sheet">
<div class="sheet-head">
<span class="sheet-k" id="modalKicker">RECOVERY PROTOCOL</span>
<button type="button" class="sheet-close" id="modalClose" aria-label="Close dialog">
<svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M2 2l10 10M12 2 2 12"/></svg>
</button>
</div>
<h2 id="modalTitle">Reset passphrase</h2>
<p class="sheet-desc" id="modalDesc"></p>
<form id="modalForm" novalidate>
<div class="field" id="fModal">
<div class="field-head">
<label for="modalInput" id="modalLabel">IDENTIFIER</label>
<span class="field-msg" id="modalMsg" aria-live="polite"></span>
</div>
<div class="field-line">
<input id="modalInput" type="text" autocomplete="email" autocapitalize="off" spellcheck="false" placeholder="operator@axiom.space">
<i class="rail" aria-hidden="true"></i>
</div>
</div>
<button class="btn" id="modalBtn" type="submit">
<span class="fill" aria-hidden="true"></span>
<span class="content"><span id="modalBtnLabel">Send recovery signal</span></span>
</button>
</form>
<div class="sheet-done" id="sheetDone" hidden>
<svg viewBox="0 0 36 36" aria-hidden="true"><circle cx="18" cy="18" r="16"/><path d="M11.5 18.5l4.5 4.5 9-10"/></svg>
<p id="sheetDoneText"></p>
</div>
</div>
</div>
<div class="toasts" id="toasts" aria-live="polite"></div>
</body>
</html>
Giriş yap
Snippet’leri beğenmek ve favorilerine eklemek için ücretsiz hesabınla giriş yap.
/* ============================================================
AXIOM ACCESS TERMINAL — design tokens
============================================================ */
:root{
--bg:#08080A;
--panel:#0B0B0E;
--panel-2:#101014;
--hair:rgba(255,255,255,.09);
--hair-soft:rgba(255,255,255,.05);
--txt:#EDEDEF;
--txt-2:#9A9AA3;
--txt-3:#5E5E67;
--amber:#E2A44D;
--amber-bright:#F2BE6B;
--phos:#4FE0A4;
--ember:#F45B41;
--mono:'IBM Plex Mono',ui-monospace,monospace;
--sans:'Space Grotesk',sans-serif;
--ease:cubic-bezier(.22,1,.36,1);
}
*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
background:var(--bg);color:var(--txt);
font-family:var(--sans);
-webkit-font-smoothing:antialiased;
overflow-x:hidden;
}
::selection{background:rgba(226,164,77,.28);color:#fff}
:focus-visible{outline:1px solid var(--amber);outline-offset:3px;border-radius:2px}
button{font-family:inherit}
/* ============================================================
STAGE LAYOUT
============================================================ */
.stage{
min-height:100dvh;
display:grid;
grid-template-columns:1.15fr 1fr;
}
/* ============================================================
LEFT — ORBITAL SCENE
============================================================ */
.scene{
position:relative;isolation:isolate;
background:#050507;
overflow:hidden;
animation:sceneIn 1.4s ease both;
}
@keyframes sceneIn{from{opacity:0}to{opacity:1}}
.scene canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.scene-vignette{
position:absolute;inset:0;pointer-events:none;z-index:2;
background:radial-gradient(120% 92% at 50% 28%,transparent 52%,rgba(3,3,5,.6) 100%);
}
.grain{
position:absolute;inset:0;pointer-events:none;z-index:3;opacity:.05;mix-blend-mode:overlay;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}
.scene-top,.scene-bottom{position:absolute;left:0;right:0;z-index:4;display:flex;justify-content:space-between;gap:24px}
.scene-top{top:0;padding:clamp(20px,3vw,36px)}
.scene-bottom{bottom:0;padding:clamp(20px,3vw,36px);align-items:flex-end}
.telemetry{display:flex;flex-wrap:wrap;gap:6px 22px;font-family:var(--mono)}
.tel{display:flex;gap:8px;align-items:baseline;font-size:10px;letter-spacing:.12em;white-space:nowrap}
.tel-k{color:var(--txt-3)}
.tel-v{color:var(--txt-2)}
.scene-manifest{
font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;
color:var(--txt-3);text-align:right;line-height:1.9;flex:none;
}
.scene-caption{
max-width:300px;font-size:15px;line-height:1.65;color:var(--txt-2);
letter-spacing:.01em;
}
.scene-meta{
display:flex;gap:20px;font-family:var(--mono);font-size:9.5px;
letter-spacing:.16em;color:var(--txt-3);flex:none;
}
/* ============================================================
RIGHT — TERMINAL PANEL
============================================================ */
.terminal{
position:relative;overflow:hidden;
display:flex;flex-direction:column;
border-left:1px solid var(--hair-soft);
background:
repeating-linear-gradient(90deg,rgba(255,255,255,.026) 0,rgba(255,255,255,.026) 1px,transparent 1px,transparent 92px),
var(--panel);
}
.cursor-light{
position:absolute;left:0;top:0;width:560px;height:560px;
margin:-280px 0 0 -280px;border-radius:50%;pointer-events:none;
opacity:0;transition:opacity .6s ease;will-change:transform;
background:radial-gradient(circle,rgba(226,164,77,.055),rgba(226,164,77,.018) 42%,transparent 66%);
}
@media (hover:hover) and (pointer:fine){ .terminal:hover .cursor-light{opacity:1} }
.edge-note{
position:absolute;right:16px;top:50%;transform:translateY(-50%);
writing-mode:vertical-rl;font-family:var(--mono);font-size:9px;
letter-spacing:.34em;color:var(--txt-3);white-space:nowrap;pointer-events:none;
}
.term-head{
display:flex;justify-content:space-between;align-items:center;
padding:26px clamp(24px,4.5vw,56px) 22px;
border-bottom:1px solid var(--hair-soft);
}
.brand{display:flex;align-items:center;gap:12px;color:var(--txt)}
.brand svg{display:block;color:var(--txt)}
.brand-word{font-size:14px;font-weight:600;letter-spacing:.36em;transform:translateY(1px)}
.channel{display:flex;align-items:center;gap:8px;font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;color:var(--txt-3);transition:color .4s}
.channel .dot{width:5px;height:5px;border-radius:50%;background:var(--amber);animation:softPulse 2.6s ease-in-out infinite;transition:background .4s,box-shadow .4s}
.channel.handshake{color:var(--amber)}
.channel.handshake .dot{animation:blink .55s steps(1) infinite}
.channel.open{color:var(--phos)}
.channel.open .dot{background:var(--phos);box-shadow:0 0 8px rgba(79,224,164,.9);animation:none}
@keyframes softPulse{0%,100%{opacity:1}50%{opacity:.35}}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.15}}
.term-body{
flex:1;display:flex;flex-direction:column;justify-content:center;
padding:44px clamp(24px,4.5vw,56px);
}
.term-inner{width:min(400px,100%);margin-inline:auto}
h1{font-size:clamp(27px,2.4vw,34px);font-weight:600;letter-spacing:-.015em;line-height:1.12}
.lede{margin:13px 0 36px;font-size:14.5px;line-height:1.65;color:var(--txt-2)}
/* entrance choreography */
.rise{animation:rise .8s var(--ease) both;animation-delay:calc(140ms + var(--d)*75ms)}
@keyframes rise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
/* ============================================================
FIELDS
============================================================ */
.field + .field{margin-top:28px}
.field-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:2px}
.field-head label{
font-family:var(--mono);font-size:10px;font-weight:500;
letter-spacing:.2em;color:var(--txt-3);cursor:pointer;
}
.field-head .idx{color:rgba(226,164,77,.65);margin-right:9px}
.field-msg{
font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;
color:var(--ember);text-align:right;min-height:12px;
}
.field-line{
position:relative;display:flex;align-items:center;gap:6px;
padding:9px 0 12px;border-bottom:1px solid var(--hair);
}
.field-line input{
flex:1;min-width:0;background:transparent;border:0;outline:none;
color:var(--txt);font-family:var(--sans);font-size:15px;letter-spacing:.015em;
caret-color:var(--amber);
}
.field-line input::placeholder{color:var(--txt-3);letter-spacing:.02em}
input:-webkit-autofill{
-webkit-box-shadow:0 0 0 60px var(--panel) inset;
-webkit-text-fill-color:var(--txt);
}
@media (pointer:coarse){.field-line input{font-size:16px}}
/* focus rail */
.rail{
position:absolute;left:0;right:0;bottom:-1px;height:1px;background:var(--amber);
transform:scaleX(0);transform-origin:left;transition:transform .45s var(--ease),background .3s;
}
.field-line:focus-within .rail{transform:scaleX(1)}
/* corner brackets */
.tick{position:absolute;width:8px;height:8px;opacity:0;transition:opacity .35s,transform .35s var(--ease);pointer-events:none}
.tick.tl{top:3px;left:-15px;border-left:1px solid rgba(226,164,77,.7);border-top:1px solid rgba(226,164,77,.7);transform:translate(4px,4px)}
.tick.br{bottom:11px;right:-15px;border-right:1px solid rgba(226,164,77,.7);border-bottom:1px solid rgba(226,164,77,.7);transform:translate(-4px,-4px)}
.field-line:focus-within .tick{opacity:1;transform:none}
/* error state */
.field.err .rail{background:var(--ember);transform:scaleX(1)}
.field.err .field-head label{color:var(--txt-2)}
.field.err.shake .field-line{animation:shake .3s ease}
@keyframes shake{25%{transform:translateX(-3px)}55%{transform:translateX(3px)}85%{transform:translateX(-1px)}}
/* eye toggle */
.eye{
flex:none;width:32px;height:32px;display:grid;place-items:center;
background:none;border:0;color:var(--txt-3);cursor:pointer;
transition:color .25s;border-radius:2px;
}
.eye:hover{color:var(--amber-bright)}
.eye svg{width:19px;height:19px;overflow:visible}
.eye .lash{stroke-dasharray:23;stroke-dashoffset:23;transition:stroke-dashoffset .3s var(--ease)}
.eye circle{transition:r .3s var(--ease)}
.eye.revealed .lash{stroke-dashoffset:0}
.eye.revealed circle{r:1.7px}
/* ============================================================
STRENGTH INSTRUMENT
============================================================ */
.strength{
max-height:0;opacity:0;overflow:hidden;transform:translateY(-5px);
transition:max-height .5s var(--ease),opacity .4s ease,transform .5s var(--ease);
}
.strength.on{max-height:230px;opacity:1;transform:none}
.strength-inner{padding-top:16px}
.strength[data-level="1"]{--lvl:#F45B41}
.strength[data-level="2"]{--lvl:#F58A3C}
.strength[data-level="3"]{--lvl:#E7B34C}
.strength[data-level="4"]{--lvl:#B4DE6A}
.strength[data-level="5"]{--lvl:#4FE0A4}
.strength-row{display:flex;align-items:center;gap:14px}
.cells{display:flex;gap:5px;flex:1}
.cells i{
flex:1;height:4px;position:relative;overflow:hidden;
background:rgba(255,255,255,.09);
transition:background .4s,box-shadow .4s;
}
.cells i.filled{
background:var(--lvl);
box-shadow:0 0 9px color-mix(in srgb,var(--lvl) 45%,transparent);
}
.cells i.filled::after{
content:'';position:absolute;inset:0;
background:linear-gradient(105deg,transparent 25%,rgba(255,255,255,.4) 50%,transparent 75%);
transform:translateX(-130%);
animation:sheen 3.2s var(--ease) infinite;
animation-delay:inherit;
}
.cells i:nth-child(2).filled::after{animation-delay:.18s}
.cells i:nth-child(3).filled::after{animation-delay:.36s}
.cells i:nth-child(4).filled::after{animation-delay:.54s}
.cells i:nth-child(5).filled::after{animation-delay:.72s}
@keyframes sheen{0%{transform:translateX(-130%)}45%,100%{transform:translateX(130%)}}
.strength-label{
flex:none;min-width:88px;text-align:right;
font-family:var(--mono);font-size:10px;font-weight:500;
letter-spacing:.14em;color:var(--lvl);transition:color .4s;
}
.strength-label.swap{animation:labelSwap .35s var(--ease)}
@keyframes labelSwap{from{opacity:0;transform:translateY(5px)}}
#wave{display:block;width:100%;height:30px;margin:12px 0 9px}
.strength-meta{display:flex;flex-wrap:wrap;gap:6px 16px;font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;color:var(--txt-3)}
.strength-tip{margin-top:8px;font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;color:var(--lvl);transition:color .4s}
/* ============================================================
ROW / CHECKBOX / LINKS
============================================================ */
.row-between{display:flex;justify-content:space-between;align-items:center;margin:26px 0 22px}
.check{display:flex;align-items:center;gap:10px;cursor:pointer;user-select:none;position:relative}
.check input{position:absolute;opacity:0;width:1px;height:1px}
.check .box{
width:15px;height:15px;border:1px solid rgba(255,255,255,.25);border-radius:2px;
display:grid;place-items:center;
transition:border-color .25s,background .25s;
}
.check .box svg{width:10px;height:9px;overflow:visible}
.check .box path{
stroke:var(--amber);stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round;
stroke-dasharray:18;stroke-dashoffset:18;transition:stroke-dashoffset .28s var(--ease) .05s;
}
.check input:checked + .box{border-color:var(--amber);background:rgba(226,164,77,.08)}
.check input:checked + .box path{stroke-dashoffset:0}
.check input:focus-visible + .box{outline:1px solid var(--amber);outline-offset:2px}
.check-label{font-family:var(--mono);font-size:11px;letter-spacing:.05em;color:var(--txt-2)}
.link{
background:none;border:0;padding:0;cursor:pointer;
font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.05em;
color:var(--txt-2);text-decoration:underline;
text-decoration-color:rgba(255,255,255,.25);text-underline-offset:4px;
transition:color .25s,text-decoration-color .25s;
}
.link:hover{color:var(--amber-bright);text-decoration-color:var(--amber)}
/* ============================================================
LOGIN BUTTON
============================================================ */
.btn{
position:relative;display:flex;justify-content:center;align-items:center;
width:100%;height:54px;border-radius:2px;overflow:hidden;
border:1px solid var(--amber);background:transparent;color:var(--amber);
font-family:var(--mono);font-size:12px;font-weight:500;
letter-spacing:.22em;text-transform:uppercase;cursor:pointer;
transition:color .35s,border-color .35s,transform .15s;
}
.btn .fill{
position:absolute;inset:0;background:var(--amber);
transform:scaleX(0);transform-origin:left;transition:transform .5s var(--ease);
}
.btn:hover:not(.loading):not(.success){color:#0B0B0D}
.btn:hover:not(.loading):not(.success) .fill{transform:scaleX(1)}
.btn:active:not(.loading):not(.success){transform:translateY(1px)}
.btn .content{position:relative;z-index:1;display:flex;align-items:center;gap:11px}
.btn .ic{width:15px;height:15px;overflow:visible}
.btn .ic path{stroke:currentColor;stroke-width:1.9;fill:none;stroke-linecap:round;stroke-linejoin:round}
.btn .arrow{transition:transform .35s var(--ease)}
.btn:hover .arrow{transform:translateX(4px)}
.btn .scan{position:absolute;bottom:0;left:0;height:1px;width:32%;background:var(--amber);opacity:0}
.btn.loading{pointer-events:none;color:var(--txt-2);border-color:rgba(226,164,77,.4)}
.btn.loading .arrow{display:none}
.btn.loading .scan{opacity:.85;animation:scan 1.05s cubic-bezier(.45,0,.55,1) infinite}
@keyframes scan{from{transform:translateX(-120%)}to{transform:translateX(430%)}}
.btn.success{pointer-events:none;border-color:var(--phos);color:var(--phos)}
.btn.success .arrow{display:none}
.btn .ic-check{opacity:0;transition:opacity .3s}
.btn.success .ic-check{opacity:1}
.btn .ic-check path{stroke-dasharray:24;stroke-dashoffset:24;transition:stroke-dashoffset .45s var(--ease) .1s}
.btn.success .ic-check path{stroke-dashoffset:0}
.term-foot{
margin-top:34px;padding-top:20px;border-top:1px solid var(--hair-soft);
display:flex;align-items:center;gap:14px;
font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;color:var(--txt-3);
}
.term-base{
display:flex;justify-content:space-between;gap:16px;
padding:20px clamp(24px,4.5vw,56px) 26px;
border-top:1px solid var(--hair-soft);
font-family:var(--mono);font-size:9px;letter-spacing:.18em;color:var(--txt-3);
}
/* ============================================================
MODAL
============================================================ */
.overlay{
position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
padding:20px;background:rgba(5,5,7,.74);backdrop-filter:blur(7px);
-webkit-backdrop-filter:blur(7px);
opacity:0;transition:opacity .3s ease;
}
.overlay.open{opacity:1}
.sheet{
width:min(432px,100%);position:relative;
background:var(--panel-2);border:1px solid var(--hair);border-radius:3px;
padding:30px 30px 34px;
transform:translateY(16px) scale(.985);transition:transform .4s var(--ease);
}
.overlay.open .sheet{transform:none}
.sheet::before,.sheet::after{content:'';position:absolute;width:12px;height:12px;pointer-events:none}
.sheet::before{top:-5px;left:-5px;border-left:1px solid rgba(226,164,77,.55);border-top:1px solid rgba(226,164,77,.55)}
.sheet::after{bottom:-5px;right:-5px;border-right:1px solid rgba(226,164,77,.55);border-bottom:1px solid rgba(226,164,77,.55)}
.sheet-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:14px}
.sheet-k{font-family:var(--mono);font-size:9.5px;letter-spacing:.26em;color:var(--amber)}
.sheet-close{
width:30px;height:30px;display:grid;place-items:center;background:none;
border:0;color:var(--txt-3);cursor:pointer;transition:color .25s;border-radius:2px;
}
.sheet-close:hover{color:var(--txt)}
.sheet-close svg{width:14px;height:14px}
.sheet h2{font-size:22px;font-weight:600;letter-spacing:-.01em}
.sheet-desc{margin:10px 0 26px;font-size:13.5px;line-height:1.65;color:var(--txt-2)}
.sheet .btn{height:48px;margin-top:24px}
.sheet-done{display:flex;gap:14px;align-items:flex-start;padding:8px 0 4px}
.sheet-done svg{flex:none;width:34px;height:34px;overflow:visible}
.sheet-done circle{stroke:var(--phos);stroke-width:1.4;fill:none;stroke-dasharray:64;stroke-dashoffset:64;animation:draw .6s var(--ease) forwards}
.sheet-done path{stroke:var(--phos);stroke-width:1.9;fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:24;stroke-dashoffset:24;animation:draw .4s var(--ease) .35s forwards}
@keyframes draw{to{stroke-dashoffset:0}}
.sheet-done p{font-size:13.5px;line-height:1.65;color:var(--txt-2);padding-top:4px}
[hidden]{display:none !important}
/* ============================================================
TOASTS
============================================================ */
.toasts{
position:fixed;right:22px;bottom:22px;z-index:80;
display:flex;flex-direction:column;gap:10px;pointer-events:none;
}
.toast{
pointer-events:auto;display:flex;align-items:stretch;gap:12px;
min-width:270px;max-width:370px;
background:#121217;border:1px solid var(--hair);border-radius:2px;
padding:12px 12px 12px 14px;
animation:toastIn .45s var(--ease);
}
.toast.out{animation:toastOut .3s ease forwards}
@keyframes toastIn{from{opacity:0;transform:translateY(10px)}}
@keyframes toastOut{to{opacity:0;transform:translateY(8px)}}
.toast .bar{flex:none;width:2px;background:currentColor}
.toast.ok{color:var(--phos)}
.toast.err{color:var(--ember)}
.toast.info{color:var(--amber)}
.toast p{flex:1;font-family:var(--mono);font-size:11px;line-height:1.55;letter-spacing:.05em;color:var(--txt)}
.t-close{background:none;border:0;padding:0 2px;cursor:pointer;color:var(--txt-3);font-family:var(--mono);font-size:14px;line-height:1.4}
.t-close:hover{color:var(--txt)}
/* ============================================================
RESPONSIVE
============================================================ */
@media (max-width:1280px){ .edge-note{display:none} }
@media (max-width:1020px){
.stage{grid-template-columns:.9fr 1.1fr}
.scene-manifest{display:none}
}
@media (max-width:760px){
.stage{display:block}
.scene{height:clamp(280px,42vh,380px)}
.scene-caption{display:none}
.scene-top{padding:18px 20px}
.scene-bottom{padding:16px 20px}
.telemetry{gap:5px 16px}
.term-head{padding:20px}
.term-body{padding:36px 22px 30px;justify-content:flex-start}
.term-base{padding:16px 20px 22px;flex-wrap:wrap;gap:8px 18px}
.toasts{left:16px;right:16px;bottom:16px}
.toast{max-width:none;min-width:0}
}
@media (prefers-reduced-motion:reduce){
*,*::before,*::after{
animation-duration:.01ms !important;
animation-iteration-count:1 !important;
transition-duration:.15s !important;
}
.rise,.scene{animation:none}
}
(() => {
'use strict';
/* ============================ utils ============================ */
const $ = (s, c = document) => c.querySelector(s);
const $$ = (s, c = document) => [...c.querySelectorAll(s)];
const clamp = (v, a, b) => Math.min(b, Math.max(a, v));
const lerp = (a, b, t) => a + (b - a) * t;
const REDUCED = matchMedia('(prefers-reduced-motion: reduce)').matches;
/* ============================ toasts ============================ */
const Toast = (() => {
const host = $('#toasts');
function show(type, msg) {
const el = document.createElement('div');
el.className = 'toast ' + type;
el.innerHTML = `<span class="bar"></span><p></p><button class="t-close" aria-label="Dismiss">×</button>`;
el.querySelector('p').textContent = msg;
host.appendChild(el);
const kill = () => { el.classList.add('out'); setTimeout(() => el.remove(), 320); };
el.querySelector('.t-close').addEventListener('click', kill);
setTimeout(kill, 4200);
}
return { ok: m => show('ok', m), err: m => show('err', m), info: m => show('info', m) };
})();
/* ============================ password engine ============================ */
const COMMON = new Set(['password','123456','12345678','123456789','1234567890','qwerty',
'qwerty123','abc123','password1','password123','iloveyou','admin','admin123','welcome',
'monkey','dragon','letmein','letmein1','111111','000000','master','sunshine','princess',
'football','baseball','shadow','superman','batman','trustno1','whatever','freedom',
'passw0rd','p@ssw0rd','changeme','default','guest','root','toor','starwars','hello123',
'zaq12wsx','1q2w3e4r','qwe123','654321','123123','121212','666666','888888','abcdef']);
const ROOTS = ['password','qwerty','letmein','welcome','iloveyou','admin','master','monkey',
'dragon','sunshine','princess','football','baseball','superman','trustno','starwars',
'whatever','freedom','changeme','access','login'];
function analyze(pw) {
const len = pw.length;
if (!len) return { len: 0, bits: 0, pool: 0, score: 0, level: 0 };
const lower = /[a-z]/.test(pw), upper = /[A-Z]/.test(pw);
const digit = /[0-9]/.test(pw), sym = /[^A-Za-z0-9]/.test(pw);
const classes = lower + upper + digit + sym;
const pool = (lower ? 26 : 0) + (upper ? 26 : 0) + (digit ? 10 : 0) + (sym ? 32 : 0);
const unique = new Set(pw).size;
const bits = Math.round(len * Math.log2(Math.max(pool, 2)) * (unique / len));
let score = 0;
score += Math.min(len, 16) / 16 * 34; // length (max 34)
score += classes * 8; // character classes (max 32)
score += unique / len * 14; // diversity (max 14)
score -= Math.min(len - unique, len * .4) * 1.2; // repeated characters
let seq = 0; // sequential runs (abc / 321)
for (let i = 2; i < len; i++) {
const a = pw.charCodeAt(i - 2), b = pw.charCodeAt(i - 1), c = pw.charCodeAt(i);
if ((b === a + 1 && c === b + 1) || (b === a - 1 && c === b - 1)) seq++;
}
score -= seq * 3;
let rep = 0; // immediate doubles (aa, 11)
for (let i = 1; i < len; i++) if (pw[i] === pw[i - 1]) rep++;
score -= rep * 2.5;
const norm = pw.toLowerCase();
if (COMMON.has(norm)) score = Math.min(score, 6);
else if (len >= 6) for (const w of ROOTS) if (norm.includes(w)) { score = Math.min(score, 22); break; }
score = clamp(Math.round(score), 0, 100);
const level = score <= 20 ? 1 : score <= 40 ? 2 : score <= 62 ? 3 : score <= 82 ? 4 : 5;
return { len, bits, pool, score, level };
}
const LEVELS = [null,
{ name: 'VERY WEAK', rgb: [244, 91, 65], tip: 'EASILY COMPROMISED — AVOID COMMON PATTERNS' },
{ name: 'WEAK', rgb: [245, 138, 60], tip: 'ADD LENGTH AND CHARACTER VARIETY' },
{ name: 'MEDIUM', rgb: [231, 179, 76], tip: 'ADEQUATE — SYMBOLS WOULD HARDEN IT' },
{ name: 'STRONG', rgb: [180, 222, 106], tip: 'RESISTANT TO BRUTE-FORCE ATTACKS' },
{ name: 'VERY STRONG', rgb: [79, 224, 164], tip: 'EXEMPLARY — SUITABLE FOR ROOT ACCESS' }];
/* ============================ entropy oscilloscope ============================ */
const Wave = (() => {
const cv = $('#wave'), ctx = cv.getContext('2d');
let W = 0, H = 0, hash = 0;
let amp = 2, ampT = 2, spd = .5, spdT = .5;
let col = [154, 154, 163], colT = [154, 154, 163];
let f1 = 1.2, f2 = 2.7, f3 = 4.3, ph1 = 0, ph2 = 0, ph3 = 0;
function resize() {
const r = cv.getBoundingClientRect();
if (!r.width) return;
const dpr = Math.min(devicePixelRatio || 1, 2);
W = r.width; H = r.height;
cv.width = W * dpr; cv.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
new ResizeObserver(resize).observe(cv);
function setSignal(pw, score, rgb) {
colT = rgb;
ampT = 3 + (score / 100) * (H ? H * .5 - 4 : 10);
spdT = .5 + (score / 100) * 2.6;
let h = 2166136261; // FNV-1a — deterministic per password
for (let i = 0; i < pw.length; i++) { h ^= pw.charCodeAt(i); h = Math.imul(h, 16777619) >>> 0; }
hash = h >>> 0;
f1 = 1 + (hash % 260) / 100;
f2 = 2 + ((hash >>> 7) % 420) / 100;
f3 = 3 + ((hash >>> 15) % 700) / 100;
if (REDUCED) { amp = ampT; spd = 0; col = [...colT]; }
}
function draw(dt) {
if (!W) { resize(); if (!W) return; }
const k = 1 - Math.exp(-dt / 220);
amp = lerp(amp, ampT, k); spd = lerp(spd, spdT, k);
col = col.map((c, i) => lerp(c, colT[i], k));
ph1 += dt * .0012 * spd; ph2 += dt * .0017 * spd; ph3 += dt * .0023 * spd;
ctx.clearRect(0, 0, W, H);
ctx.strokeStyle = 'rgba(255,255,255,.07)'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(0, H / 2 + .5); ctx.lineTo(W, H / 2 + .5); ctx.stroke();
const c = `rgb(${col[0] | 0},${col[1] | 0},${col[2] | 0})`;
ctx.beginPath();
for (let x = 0; x <= W; x += 2) {
const n = x / W;
let y = Math.sin(n * Math.PI * 2 * f1 + ph1) * .55
+ Math.sin(n * Math.PI * 2 * f2 + ph2) * .30
+ Math.sin(n * Math.PI * 2 * f3 + ph3) * .15
+ Math.sin(x * .55 + (hash % 97)) * .1 * (amp / (H / 2));
const py = H / 2 + y * amp;
x === 0 ? ctx.moveTo(x, py) : ctx.lineTo(x, py);
}
ctx.strokeStyle = c; ctx.lineWidth = 1.4;
ctx.shadowColor = c; ctx.shadowBlur = 4 + amp * .14;
ctx.stroke(); ctx.shadowBlur = 0;
}
return { draw, setSignal };
})();
/* ============================ strength UI ============================ */
const Strength = (() => {
const block = $('#strength'), cells = $$('.cells i', block);
const labelEl = $('#strengthLabel'), tipEl = $('#strengthTip');
const mBits = $('#mBits'), mLen = $('#mLen'), mPool = $('#mPool');
function setLabel(txt) {
if (labelEl.textContent === txt) return;
labelEl.textContent = txt;
labelEl.classList.remove('swap'); void labelEl.offsetWidth;
labelEl.classList.add('swap');
}
function update(pw) {
const r = analyze(pw);
block.classList.toggle('on', r.len > 0);
if (!r.len) { Wave.setSignal('', 0, [154, 154, 163]); return r; }
block.dataset.level = r.level;
const L = LEVELS[r.level];
cells.forEach((c, i) => c.classList.toggle('filled', i < r.level));
setLabel(L.name);
tipEl.textContent = L.tip;
mBits.textContent = `EST. ENTROPY ${r.bits} BITS`;
mLen.textContent = `LENGTH ${r.len}`;
mPool.textContent = `POOL ${r.pool}`;
Wave.setSignal(pw, r.score, L.rgb);
if (REDUCED) Wave.draw(0);
return r;
}
return { update };
})();
/* ============================ orbital scene ============================ */
const Scene = (() => {
const cv = $('#scene'), ctx = cv.getContext('2d');
let W = 0, H = 0, stars = [], dust = [], meteors = [], pulses = [], atmoBoost = 0;
let m = { x: .5, y: .5 }, mt = { x: .5, y: .5 };
let nextMeteor = 4500 + Math.random() * 7000;
const sats = [{ a: Math.random() * 6.28, v: .00006 }, { a: Math.random() * 6.28, v: .00010 }];
const sprite = document.createElement('canvas'); // soft round star sprite
sprite.width = sprite.height = 32;
(() => {
const s = sprite.getContext('2d');
const g = s.createRadialGradient(16, 16, 0, 16, 16, 16);
g.addColorStop(0, 'rgba(255,255,255,1)');
g.addColorStop(.35, 'rgba(255,255,255,.45)');
g.addColorStop(1, 'rgba(255,255,255,0)');
s.fillStyle = g; s.fillRect(0, 0, 32, 32);
})();
function resize() {
const r = cv.getBoundingClientRect();
W = r.width; H = r.height;
const dpr = Math.min(devicePixelRatio || 1, 2);
cv.width = W * dpr; cv.height = H * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const n = Math.round(W * H / 5200);
stars = Array.from({ length: n }, () => ({
x: Math.random(), y: Math.random(), z: .25 + Math.random() * .75,
tw: Math.random() * 6.28, ts: .4 + Math.random() * 1.6 }));
dust = Array.from({ length: Math.max(12, n / 5 | 0) }, () => ({
x: Math.random(), y: Math.random(),
vx: (Math.random() - .5) * .006, vy: -.002 - Math.random() * .008,
r: .4 + Math.random() * 1.2, a: .06 + Math.random() * .16 }));
}
addEventListener('resize', resize); resize();
addEventListener('pointermove', e => {
mt.x = e.clientX / innerWidth; mt.y = e.clientY / innerHeight;
});
const geo = () => {
const R = Math.max(W, H) * .72;
return { R, cx: W * .40 + (m.x - .5) * 26, cy: H + R * .64 + (m.y - .5) * 16 };
};
const ring = (cx, cy, rx, ry, rot, a) => {
ctx.beginPath(); ctx.ellipse(cx, cy, rx, ry, rot, 0, 6.2832);
ctx.strokeStyle = `rgba(214,218,228,${a})`; ctx.lineWidth = 1; ctx.stroke();
};
const satPos = (s, rx, ry, rot, cx, cy) => {
s.a += s.v;
const ca = Math.cos(s.a), sa = Math.sin(s.a);
const cr = Math.cos(rot), sr = Math.sin(rot);
return { x: cx + rx * ca * cr - ry * sa * sr,
y: cy + rx * ca * sr + ry * sa * cr,
front: rx * ca * sr + ry * sa * cr > 0 };
};
const satDraw = (p, cx, cy, rx, ry, rot) => {
ctx.beginPath(); ctx.ellipse(cx, cy, rx, ry, rot, p.a - .5, p.a);
ctx.strokeStyle = 'rgba(226,164,77,.20)'; ctx.lineWidth = 1; ctx.stroke();
ctx.save(); ctx.shadowColor = 'rgba(242,190,107,.9)'; ctx.shadowBlur = 9;
ctx.fillStyle = 'rgba(244,196,116,.95)';
ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, 6.2832); ctx.fill(); ctx.restore();
};
function draw(dt, now) {
const t = now * .001;
const k = 1 - Math.exp(-dt / 260);
m.x = lerp(m.x, mt.x, k); m.y = lerp(m.y, mt.y, k);
ctx.clearRect(0, 0, W, H);
const { R, cx, cy } = geo();
const rot1 = -.38, rx1 = R * 1.55, ry1 = R * .30;
const rot2 = -.28, rx2 = R * 1.22, ry2 = R * .22;
sats[0].v && 0; // velocities used below
const p1 = satPos({ ...sats[0], a: sats[0].a }, rx1, ry1, rot1, cx, cy); sats[0].a += 0;
// advance angles properly
if (!REDUCED) { sats[0].a += 0; }
// (angles advance inside satPos via s.v — handled above/below)
/* stars */
for (const s of stars) {
const px = s.x * W + (m.x - .5) * -18 * s.z;
const py = s.y * H + (m.y - .5) * -12 * s.z;
const tw = REDUCED ? .8 : .45 + .55 * Math.sin(t * s.ts + s.tw);
const sz = s.z * 1.5 + .4;
ctx.globalAlpha = (0.14 + .5 * s.z) * tw;
ctx.drawImage(sprite, px - sz, py - sz, sz * 2, sz * 2);
}
ctx.globalAlpha = 1;
/* dust */
for (const d of dust) {
if (!REDUCED) { d.x += d.vx * dt * .06; d.y += d.vy * dt * .06; }
if (d.y < -.05) { d.y = 1.05; d.x = Math.random(); }
if (d.x < -.05) d.x = 1.05; if (d.x > 1.05) d.x = -.05;
ctx.fillStyle = `rgba(226,180,120,${d.a})`;
ctx.beginPath(); ctx.arc(d.x * W, d.y * H, d.r, 0, 6.2832); ctx.fill();
}
/* meteors */
if (!REDUCED) {
nextMeteor -= dt;
if (nextMeteor <= 0 && W > 480) {
nextMeteor = 6500 + Math.random() * 9000;
meteors.push({ x: W * (.15 + Math.random() * .7), y: -20,
vx: (Math.random() < .5 ? -1 : 1) * (.22 + Math.random() * .25),
vy: .18 + Math.random() * .16, life: 1 });
}
meteors = meteors.filter(mm => mm.life > 0);
for (const mm of meteors) {
mm.x += mm.vx * dt; mm.y += mm.vy * dt; mm.life -= dt * .0009;
const g = ctx.createLinearGradient(mm.x, mm.y, mm.x - mm.vx * 60, mm.y - mm.vy * 60);
g.addColorStop(0, `rgba(235,240,250,${.75 * mm.life})`);
g.addColorStop(1, 'rgba(235,240,250,0)');
ctx.strokeStyle = g; ctx.lineWidth = 1.1;
ctx.beginPath(); ctx.moveTo(mm.x, mm.y);
ctx.lineTo(mm.x - mm.vx * 60, mm.y - mm.vy * 60); ctx.stroke();
}
}
/* rings + satellites (behind planet pass) */
ring(cx, cy, rx1, ry1, rot1, .07);
ring(cx, cy, rx2, ry2, rot2, .05);
const pos1 = { ...p1 }; const pos2 = satPos(sats[1], rx2, ry2, rot2, cx, cy);
satDraw(pos1, cx, cy, rx1, ry1, rot1);
satDraw(pos2, cx, cy, rx2, ry2, rot2);
/* planet body */
ctx.beginPath(); ctx.arc(cx, cy, R, 0, 6.2832);
const bg = ctx.createRadialGradient(cx - (m.x - .5) * R * .3, cy - R * .45, R * .1, cx, cy, R);
bg.addColorStop(0, '#101117'); bg.addColorStop(.55, '#0B0B0F'); bg.addColorStop(1, '#070709');
ctx.fillStyle = bg; ctx.fill();
/* atmosphere halo */
const ga = .10 + atmoBoost * .28;
const glow = ctx.createRadialGradient(cx, cy, R * .985, cx, cy, R * 1.15);
glow.addColorStop(0, 'rgba(226,164,77,0)');
glow.addColorStop(.12, `rgba(226,164,77,${ga})`);
glow.addColorStop(1, 'rgba(226,164,77,0)');
ctx.beginPath(); ctx.arc(cx, cy, R * 1.15, 0, 6.2832); ctx.fillStyle = glow; ctx.fill();
/* rim light — warm key toward the light source */
const la = Math.atan2(-H * .35 - cy, W * (.5 + (m.x - .5) * .3) - cx);
ctx.save();
ctx.beginPath(); ctx.arc(cx, cy, R + .5, la - 1.15, la + 1.15);
ctx.strokeStyle = `rgba(242,190,107,${.7 + atmoBoost * .3})`;
ctx.lineWidth = 1.6; ctx.shadowColor = 'rgba(232,170,80,.9)'; ctx.shadowBlur = 14 + atmoBoost * 24;
ctx.stroke();
ctx.beginPath(); ctx.arc(cx, cy, R + 1, la - 1.6, la + 1.6);
ctx.strokeStyle = `rgba(226,164,77,${.26 + atmoBoost * .4})`;
ctx.lineWidth = 4; ctx.shadowBlur = 26; ctx.stroke();
/* faint cool rim on the night side */
ctx.beginPath(); ctx.arc(cx, cy, R, la + Math.PI - .8, la + Math.PI + .8);
ctx.strokeStyle = 'rgba(150,180,210,.10)'; ctx.lineWidth = 1; ctx.shadowBlur = 0; ctx.stroke();
ctx.restore();
/* settlement beacon on the limb */
const ba = -Math.PI / 2 - .5;
const bl = REDUCED ? .5 : .2 + .8 * Math.pow(Math.max(0, Math.sin(t * 1.6)), 4);
ctx.save(); ctx.shadowColor = 'rgba(79,224,164,.9)'; ctx.shadowBlur = 8;
ctx.fillStyle = `rgba(79,224,164,${bl})`;
ctx.beginPath();
ctx.arc(cx + Math.cos(ba) * R * 1.005, cy + Math.sin(ba) * R * 1.005, 1.6, 0, 6.2832);
ctx.fill(); ctx.restore();
/* auth-success pulse rings */
for (const p of pulses) {
p.delay -= dt; if (p.delay > 0) continue;
p.r += dt * .28; p.a -= dt * .00075;
if (p.a <= 0) continue;
ctx.beginPath(); ctx.arc(cx, cy, p.r, Math.PI, Math.PI * 2);
ctx.strokeStyle = `rgba(242,190,107,${p.a})`;
ctx.lineWidth = 1.2; ctx.shadowColor = 'rgba(226,164,77,.7)'; ctx.shadowBlur = 10;
ctx.stroke(); ctx.shadowBlur = 0;
}
pulses = pulses.filter(p => p.a > 0);
atmoBoost = Math.max(0, atmoBoost - dt * .0005);
/* front satellite re-draw */
if (pos1.front) satDraw(pos1, cx, cy, rx1, ry1, rot1);
if (pos2.front) satDraw(pos2, cx, cy, rx2, ry2, rot2);
}
function pulse() {
const { R } = geo();
pulses.push({ r: R + 4, a: .55, delay: 0 }, { r: R + 4, a: .4, delay: 240 });
atmoBoost = 1;
}
return { draw, pulse };
})();
/* ============================ cursor light ============================ */
const Light = (() => {
const el = $('#cursorLight'), term = $('#terminal');
let x = 0, y = 0, tx = 0, ty = 0, active = false;
if (matchMedia('(hover:hover) and (pointer:fine)').matches && !REDUCED) {
active = true;
addEventListener('pointermove', e => {
const r = term.getBoundingClientRect();
tx = e.clientX - r.left; ty = e.clientY - r.top;
});
} else el.style.display = 'none';
function update(dt) {
if (!active) return;
const k = 1 - Math.exp(-dt / 170);
x = lerp(x, tx, k); y = lerp(y, ty, k);
el.style.transform = `translate3d(${x}px,${y}px,0)`;
}
return { update };
})();
/* ============================ telemetry ============================ */
(() => {
const clock = $('#telClock'), sig = $('#telSig'), lat = $('#telLat');
const tick = () => clock.textContent = new Date().toISOString().slice(11, 19);
tick(); setInterval(tick, 1000);
setInterval(() => sig.textContent = (97 + Math.random() * 2.9).toFixed(1) + '%', 2400);
let v = 44.21;
setInterval(() => { v = (v + .0001) % 90; lat.textContent = `LAT ${v.toFixed(2)}°N`; }, 1000);
})();
/* ============================ modal ============================ */
const Modal = (() => {
const overlay = $('#overlay'), form = $('#modalForm'), input = $('#modalInput');
const msg = $('#modalMsg'), field = $('#fModal'), done = $('#sheetDone');
const kicker = $('#modalKicker'), title = $('#modalTitle'), desc = $('#modalDesc');
const mLabel = $('#modalLabel'), btnL = $('#modalBtnLabel'), doneT = $('#sheetDoneText');
const V = {
forgot: { k: 'RECOVERY PROTOCOL', t: 'Reset passphrase',
d: 'Enter the identifier tied to your clearance. We will dispatch a recovery signal to your relay inbox.',
btn: 'Send recovery signal', toast: 'RECOVERY SIGNAL DISPATCHED',
done: 'Recovery signal dispatched. Check your relay inbox for further instructions.' },
request: { k: 'CLEARANCE REQUEST', t: 'Request access',
d: 'Axiom is invitation-first. Leave your identifier and we will queue your request for review.',
btn: 'Submit request', toast: 'REQUEST QUEUED — AWAITING REVIEW',
done: 'Request queued. Clearance review typically completes within 48 hours.' }
};
let trigger = null, v = V.forgot, closer = null;
function open(kind, el) {
v = V[kind]; trigger = el;
kicker.textContent = v.k; title.textContent = v.t; desc.textContent = v.d;
btnL.textContent = v.btn; doneT.textContent = v.done;
form.reset(); form.hidden = false; done.hidden = true;
msg.textContent = ''; field.classList.remove('err');
clearTimeout(closer);
overlay.hidden = false;
requestAnimationFrame(() => overlay.classList.add('open'));
setTimeout(() => input.focus(), 90);
document.addEventListener('keydown', onKey);
}
function close() {
overlay.classList.remove('open');
document.removeEventListener('keydown', onKey);
clearTimeout(closer);
setTimeout(() => { overlay.hidden = true; if (trigger) trigger.focus(); }, 320);
}
const onKey = e => { if (e.key === 'Escape') close(); };
$('#modalClose').addEventListener('click', close);
overlay.addEventListener('click', e => { if (e.target === overlay) close(); });
form.addEventListener('submit', e => {
e.preventDefault();
const val = input.value.trim();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(val)) {
msg.textContent = 'A VALID RELAY ADDRESS IS REQUIRED';
field.classList.remove('err'); void field.offsetWidth; field.classList.add('err');
input.focus(); return;
}
msg.textContent = ''; field.classList.remove('err');
form.hidden = true; done.hidden = false;
Toast.ok(v.toast);
closer = setTimeout(close, 3000);
});
input.addEventListener('input', () => { msg.textContent = ''; field.classList.remove('err'); });
return { open };
})();
$('#forgotLink').addEventListener('click', e => Modal.open('forgot', e.currentTarget));
$('#requestLink').addEventListener('click', e => Modal.open('request', e.currentTarget));
/* ============================ login form ============================ */
(() => {
const form = $('#loginForm'), idIn = $('#identifier'), pwIn = $('#password');
const fId = $('#fId'), fPw = $('#fPw'), idMsg = $('#idMsg'), pwMsg = $('#pwMsg');
const btn = $('#loginBtn'), btnLabel = $('#btnLabel');
const channel = $('#channel'), channelText = $('#channelText');
const eye = $('#eyeBtn'), remember = $('#remember');
let busy = false, granted = false;
const setErr = (field, msgEl, msg) => {
msgEl.textContent = msg;
field.classList.toggle('err', !!msg);
if (msg) { field.classList.remove('shake'); void field.offsetWidth; field.classList.add('shake'); }
};
const validId = v => {
v = v.trim();
if (!v) return 'IDENTIFIER REQUIRED';
if (v.includes('@')) return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v) ? '' : 'MALFORMED ADDRESS — CHECK FORMAT';
return /^[\w.\-]{3,32}$/.test(v) ? '' : '3–32 CHARACTERS — LETTERS, DIGITS, . _ -';
};
const setChannel = s => {
channel.className = 'channel' + (s === 'default' ? '' : ' ' + s);
channelText.textContent = s === 'open' ? 'CHANNEL OPEN' : s === 'handshake' ? 'HANDSHAKE' : 'SECURE CHANNEL';
};
const resetGrant = () => {
if (!granted) return;
granted = false;
btn.classList.remove('success');
btnLabel.textContent = 'Open secure channel';
setChannel('default');
};
/* remembered identifier */
try {
const saved = localStorage.getItem('axiom.remember.id');
if (saved) { idIn.value = saved; remember.checked = true; }
} catch (e) {}
/* eye toggle */
eye.addEventListener('click', () => {
const show = pwIn.type === 'password';
pwIn.type = show ? 'text' : 'password';
eye.classList.toggle('revealed', show);
eye.setAttribute('aria-pressed', String(show));
eye.setAttribute('aria-label', show ? 'Hide passphrase' : 'Show passphrase');
pwIn.focus();
});
idIn.addEventListener('input', () => { setErr(fId, idMsg, ''); resetGrant(); });
idIn.addEventListener('blur', () => { const e = validId(idIn.value); if (e) setErr(fId, idMsg, e); });
pwIn.addEventListener('input', () => {
setErr(fPw, pwMsg, '');
Strength.update(pwIn.value);
resetGrant();
});
form.addEventListener('submit', e => {
e.preventDefault();
if (busy || granted) return;
const idErr = validId(idIn.value), pwv = pwIn.value;
const pwErr = !pwv ? 'PASSPHRASE REQUIRED'
: pwv.length < 6 ? `MIN 6 CHARACTERS — ${pwv.length}/6` : '';
setErr(fId, idMsg, idErr); setErr(fPw, pwMsg, pwErr);
if (idErr) { idIn.focus(); return; }
if (pwErr) { pwIn.focus(); return; }
busy = true;
btn.classList.add('loading');
btnLabel.textContent = 'Authenticating';
setChannel('handshake');
setTimeout(() => {
busy = false; granted = true;
btn.classList.remove('loading');
btn.classList.add('success');
btnLabel.textContent = 'Access granted';
setChannel('open');
Scene.pulse();
try {
if (remember.checked) localStorage.setItem('axiom.remember.id', idIn.value.trim());
else localStorage.removeItem('axiom.remember.id');
} catch (e) {}
const name = idIn.value.trim().split('@')[0];
Toast.ok(`CHANNEL OPEN — WELCOME BACK, ${name.toUpperCase()}`);
}, 1450);
});
Strength.update('');
})();
if (!REDUCED) {
let last = performance.now();
(function frame(now) {
const dt = clamp(now - last, 0, 50); last = now;
Scene.draw(dt, now);
Wave.draw(dt);
Light.update(dt);
requestAnimationFrame(frame);
})(last);
} else { Scene.draw(0, 0); }
})();
Bu snippet hakkında
Düz HTML, CSS ve saf JavaScript ile hazırlanmış eksiksiz, sinematik bir giriş ekranı. Sol yarıda <canvas> üzerine çizilen hareketli bir yörünge sahnesi, sağ yarıda ise anlık geri bildirim veren terminal tarzı bir giriş formu, şifre kurtarma penceresi ve bildirim kutucukları yer alır. Hiçbir çatı (framework) kullanmadan zengin giriş sayfaları yapmak için iyi bir örnektir.
Özellikler
- Canvas sahnesi: yörünge halkaları ve hareket eden uydularla bir gezegen;
requestAnimationFramedöngüsüyle çizilir ve pencere boyutuna göre yeniden ölçeklenir. - Parola gücü "osiloskobu": yazdıkça betik, uzunluk ve karakter havuzuna göre entropiyi hesaplar; sonucu bit cinsinden, bir etiketle ve hareketli bir dalga formuyla gösterir.
- Satır içi doğrulama: kullanıcı adı ve parola alanları uyarı penceresi yerine mesajları alanın altında gösterir.
- Parolayı göster/gizle düğmesi, "bu terminali hatırla" seçeneği ve form üzerinde imleci takip eden bir ışık.
- Şifre kurtarma ve erişim talebi için kendi doğrulaması, odak yönetimi ve Escape ile kapanma desteği olan açılır pencere.
- Başarı ve hata mesajları için bildirim kutucukları; ayrıca
prefers-reduced-motiondesteği.
Yapı
CSS, :root içindeki tasarım değişkenleriyle (renkler, yazı tipleri, geçiş eğrileri) başlar ve küçük ekranlarda tek sütuna inen iki sütunlu bir grid kullanır. JavaScript tek bir IIFE içinde toplanmış ve küçük modüllere ayrılmıştır: Toast, Strength, Wave, Scene, Light ve Modal.
Nasıl kullanılır?
- Üç dosyayı kopyalayıp HTML'i aç; herhangi bir derleme adımı gerekmez.
- Formun gönderim işleyicisini kendi kimlik doğrulama adresine bağla (örnek yalnızca isteği taklit eder).
- Metinleri ve
--amber,--phosgibi renk değişkenlerini kendi markana göre düzenle. - Daha hafif bir sayfa gerekiyorsa canvas bölümünü kaldır; form tek başına da çalışır.
Not: Parola gücü göstergesi kullanıcıya geri bildirim vermek için tarayıcıda yapılan bir tahmindir. Parolaları her zaman sunucuda doğrula ve hash'le.
Yorumlar 0
Henüz yorum yok. İlk yorumu sen yaz.