Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>SLICK — Login</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| background: #0d0d0d; | |
| color: #e0e0e0; | |
| font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Courier New', monospace; | |
| height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .login-box { | |
| background: #1a1a1a; | |
| border: 1px solid #2a2a2a; | |
| border-radius: 16px; | |
| padding: 2.5rem; | |
| width: 90%; | |
| max-width: 380px; | |
| text-align: center; | |
| } | |
| h1 { | |
| color: #ffb300; | |
| font-size: 1.5rem; | |
| letter-spacing: 0.3em; | |
| margin-bottom: 0.5rem; | |
| } | |
| .subtitle { | |
| color: #555; | |
| font-size: 0.7rem; | |
| margin-bottom: 2rem; | |
| letter-spacing: 0.1em; | |
| } | |
| input { | |
| width: 100%; | |
| background: #0d0d0d; | |
| border: 1px solid #333; | |
| color: #e0e0e0; | |
| padding: 0.9rem 1rem; | |
| font-family: inherit; | |
| font-size: 0.8rem; | |
| border-radius: 8px; | |
| outline: none; | |
| margin-bottom: 1rem; | |
| text-align: center; | |
| letter-spacing: 0.05em; | |
| } | |
| input:focus { border-color: #ffb300; } | |
| input::placeholder { color: #444; } | |
| button { | |
| width: 100%; | |
| background: #ffb300; | |
| color: #0d0d0d; | |
| border: none; | |
| padding: 0.9rem; | |
| font-family: inherit; | |
| font-size: 0.85rem; | |
| font-weight: bold; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| letter-spacing: 0.05em; | |
| } | |
| button:hover { background: #ffc107; } | |
| button:disabled { background: #333; color: #666; cursor: not-allowed; } | |
| .error { | |
| color: #f44336; | |
| font-size: 0.75rem; | |
| margin-top: 0.8rem; | |
| min-height: 1.2rem; | |
| } | |
| .status-dot { | |
| display: inline-block; | |
| width: 8px; height: 8px; | |
| border-radius: 50%; | |
| background: #ffb300; | |
| margin-right: 0.4rem; | |
| animation: pulse 2s ease-in-out infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.3; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="login-box"> | |
| <h1><span class="status-dot"></span>S L I C K</h1> | |
| <div class="subtitle">REMOTE RELAY</div> | |
| <input type="password" id="token-input" placeholder="Paste access token" autocomplete="off" autofocus> | |
| <button id="login-btn" onclick="doLogin()">AUTHENTICATE</button> | |
| <div class="error" id="error-msg"></div> | |
| </div> | |
| <script> | |
| const input = document.getElementById('token-input'); | |
| const btn = document.getElementById('login-btn'); | |
| const errorMsg = document.getElementById('error-msg'); | |
| input.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') doLogin(); | |
| }); | |
| async function doLogin() { | |
| const token = input.value.trim(); | |
| if (!token) return; | |
| btn.disabled = true; | |
| btn.textContent = 'VERIFYING...'; | |
| errorMsg.textContent = ''; | |
| try { | |
| const res = await fetch('/api/login', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ token }) | |
| }); | |
| const data = await res.json(); | |
| if (data.ok) { | |
| window.location.href = '/'; | |
| } else { | |
| errorMsg.textContent = 'Invalid token. Try again.'; | |
| btn.disabled = false; | |
| btn.textContent = 'AUTHENTICATE'; | |
| input.value = ''; | |
| input.focus(); | |
| } | |
| } catch (e) { | |
| errorMsg.textContent = 'Connection error: ' + e.message; | |
| btn.disabled = false; | |
| btn.textContent = 'AUTHENTICATE'; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |