Spaces:
Running
Running
| <html lang="en" class="dark"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>IronChronicle | Secure Login</title> | |
| <link rel="icon" type="image/x-icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛡️</text></svg>"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script> | |
| tailwind.config = { | |
| darkMode: 'class', | |
| theme: { | |
| extend: { | |
| colors: { | |
| military: { | |
| 500: '#22c55e', | |
| 600: '#16a34a', | |
| 800: '#14532d', | |
| 900: '#052e16', | |
| 950: '#020617', | |
| } | |
| }, | |
| fontFamily: { | |
| mono: ['"Courier Prime"', 'Courier', 'monospace'], | |
| sans: ['"Inter"', 'sans-serif'], | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); | |
| @import url('https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap'); | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: linear-gradient(135deg, #020617 0%, #0f172a 50%, #020617 100%); | |
| } | |
| .login-container { | |
| background: rgba(15, 23, 42, 0.8); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid rgba(34, 197, 94, 0.2); | |
| } | |
| .input-field { | |
| background: rgba(0, 0, 0, 0.4); | |
| border: 1px solid rgba(71, 85, 105, 0.6); | |
| transition: all 0.3s ease; | |
| } | |
| .input-field:focus { | |
| border-color: #22c55e; | |
| box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1); | |
| outline: none; | |
| } | |
| .login-btn { | |
| background: linear-gradient(135deg, #22c55e, #16a34a); | |
| transition: all 0.3s ease; | |
| } | |
| .login-btn:hover { | |
| background: linear-gradient(135deg, #16a34a, #15803d); | |
| box-shadow: 0 4px 20px rgba(34, 197, 94, 0.4); | |
| transform: translateY(-2px); | |
| } | |
| .login-btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .shield-icon { | |
| filter: drop-shadow(0 0 20px rgba(34, 197, 94, 0.5)); | |
| } | |
| .scan-line { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: linear-gradient(90deg, transparent, #22c55e, transparent); | |
| animation: scan 3s linear infinite; | |
| } | |
| @keyframes scan { | |
| 0% { transform: translateY(0); opacity: 0; } | |
| 10% { opacity: 1; } | |
| 90% { opacity: 1; } | |
| 100% { transform: translateY(400px); opacity: 0; } | |
| } | |
| .error-shake { | |
| animation: shake 0.5s ease-in-out; | |
| } | |
| @keyframes shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 20%, 60% { transform: translateX(-10px); } | |
| 40%, 80% { transform: translateX(10px); } | |
| } | |
| .loading-spinner { | |
| border: 2px solid rgba(255, 255, 255, 0.2); | |
| border-top: 2px solid #22c55e; | |
| border-radius: 50%; | |
| width: 20px; | |
| height: 20px; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .footer-text { | |
| font-family: 'Courier Prime', monospace; | |
| font-size: 0.7rem; | |
| color: rgba(100, 116, 139, 0.6); | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen flex items-center justify-center p-4"> | |
| <div class="w-full max-w-md"> | |
| <!-- Logo Header --> | |
| <div class="text-center mb-8"> | |
| <div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-slate-900 border border-military-500/30 shield-icon mb-4"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path> | |
| </svg> | |
| </div> | |
| <h1 class="text-3xl font-bold text-white tracking-wider">IRON<span class="text-military-500">CHRONICLE</span></h1> | |
| <p class="text-slate-500 text-sm mt-2 font-mono">MILITARY NARRATIVE SYSTEM v4.2.0</p> | |
| </div> | |
| <!-- Login Container --> | |
| <div class="login-container rounded-2xl p-8 relative overflow-hidden"> | |
| <div class="scan-line"></div> | |
| <h2 class="text-xl font-semibold text-white text-center mb-6">SECURE ACCESS</h2> | |
| <form id="login-form" class="space-y-5"> | |
| <!-- Username Field --> | |
| <div> | |
| <label for="username" class="block text-sm font-medium text-slate-400 mb-2">Username</label> | |
| <div class="relative"> | |
| <i data-feather="user" class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-500"></i> | |
| <input | |
| type="text" | |
| id="username" | |
| name="username" | |
| class="input-field w-full pl-10 pr-4 py-3 rounded-lg text-white placeholder-slate-500" | |
| placeholder="Enter username" | |
| autocomplete="username" | |
| > | |
| </div> | |
| </div> | |
| <!-- Password Field --> | |
| <div> | |
| <label for="password" class="block text-sm font-medium text-slate-400 mb-2">Password</label> | |
| <div class="relative"> | |
| <i data-feather="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-500"></i> | |
| <input | |
| type="password" | |
| id="password" | |
| name="password" | |
| class="input-field w-full pl-10 pr-4 py-3 rounded-lg text-white placeholder-slate-500" | |
| placeholder="Enter password" | |
| autocomplete="current-password" | |
| > | |
| </div> | |
| </div> | |
| <!-- Error Message --> | |
| <div id="error-message" class="hidden bg-red-900/30 border border-red-500/50 rounded-lg p-3 text-red-400 text-sm text-center"></div> | |
| <!-- Submit Button --> | |
| <button | |
| type="submit" | |
| id="login-btn" | |
| class="login-btn w-full py-3 rounded-lg text-white font-semibold flex items-center justify-center gap-2" | |
| > | |
| <i data-feather="shield" class="w-5 h-5"></i> | |
| <span>SECURE LOGIN</span> | |
| </button> | |
| </form> | |
| <!-- System Info --> | |
| <div class="mt-6 pt-6 border-t border-slate-700"> | |
| <div class="grid grid-cols-2 gap-4 text-center"> | |
| <div class="footer-text"> | |
| <div class="text-slate-500">ENCRYPTION</div> | |
| <div class="text-military-500">AES-256-GCM</div> | |
| </div> | |
| <div class="footer-text"> | |
| <div class="text-slate-500">NODE</div> | |
| <div class="text-military-500">EU-WEST-4</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="text-center mt-6"> | |
| <p class="footer-text"> | |
| RESTRICTED ACCESS AUTHORIZED PERSONNEL ONLY<br> | |
| © 2024 IRONCHRONICLE SYSTEM | |
| </p> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize Feather Icons | |
| document.addEventListener('DOMContentLoaded', () => { | |
| feather.replace(); | |
| // Credentials (in production, use proper backend authentication) | |
| const API_BASE_URL = window.location.hostname === 'localhost' | |
| ? 'http://localhost:3000/api' | |
| : 'https://api.ironchronicle.secure/api'; | |
| const loginForm = document.getElementById('login-form'); | |
| const errorMessage = document.getElementById('error-message'); | |
| const loginBtn = document.getElementById('login-btn'); | |
| loginForm.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const username = document.getElementById('username').value.trim(); | |
| const password = document.getElementById('password').value; | |
| // Reset error | |
| errorMessage.classList.add('hidden'); | |
| // Show loading state | |
| loginBtn.disabled = true; | |
| loginBtn.innerHTML = '<div class="loading-spinner"></div><span>AUTHENTICATING...</span>'; | |
| feather.replace(); | |
| try { | |
| // Production API call | |
| const response = await fetch(`${API_BASE_URL}/auth/login`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'X-Request-ID': crypto.randomUUID() | |
| }, | |
| body: JSON.stringify({ username, password }) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok && data.token) { | |
| // Store secure session | |
| sessionStorage.setItem('ironchronicle_token', data.token); | |
| sessionStorage.setItem('ironchronicle_user', data.user.username); | |
| sessionStorage.setItem('ironchronicle_clearance', data.user.clearance); | |
| sessionStorage.setItem('ironchronicle_session_id', data.sessionId); | |
| showToast('Authentication successful. Establishing secure connection...', 'success'); | |
| // Redirect with token | |
| setTimeout(() => { | |
| window.location.href = `index.html?sid=${data.sessionId}`; | |
| }, 800); | |
| } else { | |
| throw new Error(data.message || 'Authentication failed'); | |
| } | |
| } catch (error) { | |
| errorMessage.textContent = error.message || 'System unavailable. Contact administrator.'; | |
| errorMessage.classList.remove('hidden'); | |
| // Security shake effect | |
| const container = document.querySelector('.login-container'); | |
| container.classList.add('error-shake'); | |
| setTimeout(() => container.classList.remove('error-shake'), 500); | |
| // Log failed attempt (in production, send to SIEM) | |
| console.warn(`Failed login attempt: ${username} at ${new Date().toISOString()}`); | |
| } finally { | |
| loginBtn.disabled = false; | |
| loginBtn.innerHTML = '<i data-feather="shield" class="w-5 h-5"></i><span>SECURE LOGIN</span>'; | |
| feather.replace(); | |
| } | |
| }); | |
| // Check for existing valid session | |
| const existingToken = sessionStorage.getItem('ironchronicle_token'); | |
| if (existingToken) { | |
| // Validate token silently | |
| fetch(`${API_BASE_URL}/auth/verify`, { | |
| headers: { 'Authorization': `Bearer ${existingToken}` } | |
| }).then(r => { | |
| if (r.ok) window.location.href = 'index.html'; | |
| }).catch(() => { | |
| sessionStorage.clear(); // Clear invalid session | |
| }); | |
| } | |
| // Toast notification function | |
| window.showToast = function(message, type = 'info') { | |
| const toast = document.createElement('div'); | |
| const bgColor = type === 'success' ? 'bg-military-600' : type === 'error' ? 'bg-red-600' : 'bg-slate-700'; | |
| toast.className = `fixed bottom-4 left-1/2 transform -translate-x-1/2 ${bgColor} text-white px-6 py-3 rounded-lg shadow-lg flex items-center gap-2 text-sm font-medium animate-fade-in`; | |
| toast.innerHTML = `<i data-feather="${type === 'success' ? 'check-circle' : 'alert-circle'}" class="w-4 h-4"></i><span>${message}</span>`; | |
| document.body.appendChild(toast); | |
| feather.replace(); | |
| setTimeout(() => { | |
| toast.style.opacity = '0'; | |
| toast.style.transform = 'translateX(-50%) translateY(20px)'; | |
| toast.style.transition = 'all 0.3s ease'; | |
| setTimeout(() => toast.remove(), 300); | |
| }, 3000); | |
| }; | |
| }); | |
| </script> | |
| </body> | |
| </html> |