Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Secure Password Manager</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script> | |
| <style> | |
| :root { | |
| --primary-color: #6366f1; | |
| --primary-hover: #4f46e5; | |
| --bg-color: #0f172a; | |
| --card-bg: #1e293b; | |
| --text-main: #f1f5f9; | |
| --text-muted: #94a3b8; | |
| --success: #10b981; | |
| --danger: #ef4444; | |
| --border-radius: 12px; | |
| --transition: all 0.3s ease; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background-color: var(--bg-color); | |
| color: var(--text-main); | |
| line-height: 1.6; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* Header */ | |
| header { | |
| background-color: var(--card-bg); | |
| padding: 1rem 2rem; | |
| border-bottom: 1px solid #334155; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 1.5rem; | |
| font-weight: 700; | |
| color: var(--text-main); | |
| } | |
| .brand i { | |
| color: var(--primary-color); | |
| } | |
| .brand span { | |
| color: var(--primary-color); | |
| font-weight: 300; | |
| } | |
| .anycoder-link { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| border: 1px solid var(--text-muted); | |
| padding: 5px 10px; | |
| border-radius: 6px; | |
| transition: var(--transition); | |
| } | |
| .anycoder-link:hover { | |
| border-color: var(--primary-color); | |
| color: var(--primary-color); | |
| box-shadow: 0 0 10px rgba(99, 102, 241, 0.3); | |
| } | |
| /* Main Layout */ | |
| main { | |
| flex: 1; | |
| padding: 2rem; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| width: 100%; | |
| } | |
| .dashboard-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 2rem; | |
| } | |
| h1 { | |
| font-size: 2rem; | |
| font-weight: 600; | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: var(--border-radius); | |
| cursor: pointer; | |
| font-size: 1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| transition: var(--transition); | |
| } | |
| .btn-primary:hover { | |
| background-color: var(--primary-hover); | |
| transform: translateY(-1px); | |
| } | |
| /* Stats Grid */ | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 1.5rem; | |
| margin-bottom: 2rem; | |
| } | |
| .stat-card { | |
| background-color: var(--card-bg); | |
| padding: 1.5rem; | |
| border-radius: var(--border-radius); | |
| border: 1px solid #334155; | |
| } | |
| .stat-label { | |
| color: var(--text-muted); | |
| font-size: 0.9rem; | |
| margin-bottom: 0.5rem; | |
| } | |
| .stat-value { | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| color: var(--text-main); | |
| } | |
| /* Password Grid */ | |
| .password-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
| gap: 1.5rem; | |
| } | |
| .password-card { | |
| background-color: var(--card-bg); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| border: 1px solid #334155; | |
| transition: var(--transition); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .password-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.3); | |
| border-color: var(--primary-color); | |
| } | |
| .card-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| } | |
| .site-name { | |
| font-size: 1.25rem; | |
| font-weight: 600; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .site-icon { | |
| width: 32px; | |
| height: 32px; | |
| background-color: rgba(99, 102, 241, 0.2); | |
| color: var(--primary-color); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 8px; | |
| } | |
| .card-actions { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .btn-icon { | |
| background: none; | |
| border: none; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| padding: 5px; | |
| border-radius: 6px; | |
| transition: var(--transition); | |
| font-size: 0.9rem; | |
| } | |
| .btn-icon:hover { | |
| color: var(--primary-color); | |
| background-color: rgba(99, 102, 241, 0.1); | |
| } | |
| .btn-delete:hover { | |
| color: var(--danger); | |
| background-color: rgba(239, 68, 68, 0.1); | |
| } | |
| .credential-row { | |
| margin-bottom: 1rem; | |
| } | |
| .label { | |
| font-size: 0.75rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-muted); | |
| margin-bottom: 4px; | |
| display: block; | |
| } | |
| .input-group { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| input[type="text"], input[type="password"] { | |
| flex: 1; | |
| background-color: #0f172a; | |
| border: 1px solid #334155; | |
| color: var(--text-main); | |
| padding: 10px; | |
| border-radius: 6px; | |
| font-family: inherit; | |
| } | |
| input:focus { | |
| outline: none; | |
| border-color: var(--primary-color); | |
| } | |
| .toggle-password { | |
| background-color: #334155; | |
| border: none; | |
| color: var(--text-muted); | |
| padding: 0 12px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .toggle-password:hover { | |
| color: var(--text-main); | |
| background-color: #475569; | |
| } | |
| .btn-copy { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| padding: 0 15px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 0.85rem; | |
| transition: var(--transition); | |
| } | |
| .btn-copy:hover { | |
| background-color: var(--primary-hover); | |
| } | |
| /* Modal */ | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| backdrop-filter: blur(4px); | |
| z-index: 100; | |
| justify-content: center; | |
| align-items: center; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| .modal-content { | |
| background-color: var(--card-bg); | |
| padding: 2rem; | |
| border-radius: var(--border-radius); | |
| width: 90%; | |
| max-width: 500px; | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5); | |
| animation: slideUp 0.3s ease; | |
| } | |
| .modal-header { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 2rem; | |
| } | |
| .close-btn { | |
| background: none; | |
| border: none; | |
| font-size: 1.5rem; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| } | |
| .form-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-muted); | |
| } | |
| .form-group input { | |
| width: 100%; | |
| padding: 10px; | |
| border-radius: 6px; | |
| border: 1px solid #334155; | |
| background-color: #0f172a; | |
| color: white; | |
| } | |
| .modal-footer { | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 1rem; | |
| margin-top: 2rem; | |
| } | |
| .btn-secondary { | |
| background-color: transparent; | |
| border: 1px solid #334155; | |
| color: var(--text-main); | |
| padding: 10px 20px; | |
| border-radius: var(--border-radius); | |
| cursor: pointer; | |
| } | |
| .btn-secondary:hover { | |
| border-color: var(--text-muted); | |
| } | |
| /* Toast Notification */ | |
| .toast { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| background-color: var(--success); | |
| color: white; | |
| padding: 1rem 2rem; | |
| border-radius: var(--border-radius); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| transform: translateY(100px); | |
| transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55); | |
| z-index: 200; | |
| } | |
| .toast.show { | |
| transform: translateY(0); | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| @keyframes slideUp { | |
| from { transform: translateY(20px); opacity: 0; } | |
| to { transform: translateY(0); opacity: 1; } | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .password-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| header { | |
| flex-direction: column; | |
| gap: 1rem; | |
| text-align: center; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="fa-solid fa-lock"></i> | |
| Secure<span>Vault</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| Built with anycoder | |
| </a> | |
| </header> | |
| <main> | |
| <div class="dashboard-header"> | |
| <h1>My Credentials</h1> | |
| <button class="btn-primary" onclick="openModal()"> | |
| <i class="fa-solid fa-plus"></i> Add Account | |
| </button> | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <div class="stat-label">Total Passwords</div> | |
| <div class="stat-value" id="total-count">0</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Weak Passwords</div> | |
| <div class="stat-value" id="weak-count" style="color: var(--danger)">0</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-label">Last Updated</div> | |
| <div class="stat-value" id="last-updated">-</div> | |
| </div> | |
| </div> | |
| <div class="password-grid" id="password-container"> | |
| <!-- Password cards will be injected here via JS --> | |
| </div> | |
| <div style="text-align: center; margin-top: 4rem; color: var(--text-muted);"> | |
| <p><i class="fa-solid fa-circle-check"></i> All data is stored locally in your browser. No data leaves your device.</p> | |
| </div> | |
| </main> | |
| <!-- Add Account Modal --> | |
| <div class="modal" id="addModal"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h2>Add New Credential</h2> | |
| <button class="close-btn" onclick="closeModal()">×</button> | |
| </div> | |
| <form id="addForm"> | |
| <div class="form-group"> | |
| <label for="siteName">Website / Service Name</label> | |
| <input type="text" id="siteName" placeholder="e.g. Gmail, iCloud, Netflix" required> | |
| </div> | |
| <div class="form-group"> | |
| <label for="username">Username / Email</label> | |
| <input type="text" id="username" placeholder="e.g. user@example.com" required> | |
| </div> | |
| <div class="form-group"> | |
| <label for="password">Password</label> | |
| <div class="input-group"> | |
| <input type="password" id="password" placeholder="Enter password" required> | |
| <button type="button" class="toggle-password" onclick="togglePasswordVisibility('password')"> | |
| <i class="fa-solid fa-eye"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="category">Category</label> | |
| <select id="category" style="width: 100%; padding: 10px; border-radius: 6px; border: 1px solid #334155; background-color: #0f172a; color: white;"> | |
| <option value="email">Email</option> | |
| <option value="social">Social Media</option> | |
| <option value="finance">Finance</option> | |
| <option value="other">Other</option> | |
| </select> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn-secondary" onclick="closeModal()">Cancel</button> | |
| <button type="submit" class="btn-primary">Save Credential</button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div class="toast" id="toast"> | |
| <i class="fa-solid fa-check-circle"></i> | |
| <span id="toastMessage">Password copied to clipboard!</span> | |
| </div> | |
| <script> | |
| // Mock Data - In a real app, this would be encrypted localStorage or IndexedDB | |
| let passwords = [ | |
| { id: 1, site: 'iCloud', username: 'user@example.com', password: 'icloud_secure_pass_123', category: 'email' }, | |
| { id: 2, site: 'Gmail', username: 'john.doe@gmail.com', password: 'super_secret_email_pass', category: 'email' }, | |
| { id: 3, site: 'Netflix', username: 'john.doe@gmail.com', password: 'netflix_stream_2023', category: 'other' }, | |
| { id: 4, site: 'Bank Portal', username: 'client_88291', password: 'weak', category: 'finance' } | |
| ]; | |
| // DOM Elements | |
| const container = document.getElementById('password-container'); | |
| const totalDisplay = document.getElementById('total-count'); | |
| const weakDisplay = document.getElementById('weak-count'); | |
| const lastUpdatedDisplay = document.getElementById('last-updated'); | |
| const modal = document.getElementById('addModal'); | |
| const form = document.getElementById('addForm'); | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', () => { | |
| renderPasswords(); | |
| lastUpdatedDisplay.textContent = new Date().toLocaleDateString(); | |
| }); | |
| // Render Function | |
| function renderPasswords() { | |
| container.innerHTML = ''; | |
| if (passwords.length === 0) { | |
| container.innerHTML = ` | |
| <div style="grid-column: 1/-1; text-align: center; padding: 3rem; background: #1e293b; border-radius: var(--border-radius); border: 2px dashed #334155;"> | |
| <i class="fa-solid fa-lock-open" style="font-size: 3rem; color: var(--text-muted); margin-bottom: 1rem;"></i> | |
| <h3>Nosaved credentials</h3> | |
| <p style="color: var(--text-muted);">Click "Add Account" to securely store your first login.</p> | |
| </div>`; | |
| totalDisplay.textContent = 0; | |
| weakDisplay.textContent = 0; | |
| return; | |
| } | |
| let weakCount = 0; | |
| passwords.forEach(item => { | |
| // Check for weak passwords (simple logic for demo) | |
| if (item.password.length < 8 || item.password.toLowerCase() === item.password || item.password === 'weak') { | |
| weakCount++; | |
| } | |
| const card = document.createElement('div'); | |
| card.className = 'password-card'; | |
| // Determine icon based on category | |
| let iconClass = 'fa-globe'; | |
| if (item.category === 'email') iconClass = 'fa-envelope'; | |
| if (item.category === 'finance') iconClass = 'fa-credit-card'; | |
| if (item.category === 'social') iconClass = 'fa-users'; | |
| card.innerHTML = ` | |
| <div class="card-header"> | |
| <div class="site-name"> | |
| <div class="site-icon"> | |
| <i class="fa-solid ${iconClass}"></i> | |
| </div> | |
| ${escapeHtml(item.site)} | |
| </div> | |
| <div class="card-actions"> | |
| <button class="btn-icon" onclick="editPassword(${item.id})" title="Edit"> | |
| <i class="fa-solid fa-pen-to-square"></i> | |
| </button> | |
| <button class="btn-icon btn-delete" onclick="deletePassword(${item.id})" title="Delete"> | |
| <i class="fa-solid fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="credential-row"> | |
| <span class="label">Username / Email</span> | |
| <div class="input-group"> | |
| <input type="text" value="${escapeHtml(item.username)}" readonly> | |
| <button class="btn-copy" onclick="copyToClipboard('${escapeHtml(item.username)}', this)"> | |
| <i class="fa-solid fa-copy"></i> Copy | |
| </button> | |
| </div> | |
| </div> | |
| <div class="credential-row"> | |
| <span class="label">Password</span> | |
| <div class="input-group"> | |
| <input type="password" id="pass-${item.id}" value="${item.password}" readonly> | |
| <button class="toggle-password" onclick="togglePasswordVisibility('pass-${item.id}')"> | |
| <i class="fa-solid fa-eye" id="icon-${item.id}"></i> | |
| </button> | |
| <button class="btn-copy" onclick="copyToClipboard('${item.password}', this)"> | |
| <i class="fa-solid fa-copy"></i> Copy | |
| </button> | |
| </div> | |
| ${item.password.length < 8 ? '<span style="color: var(--danger); font-size: 0.75rem; margin-top: 5px; display:block;"><i class="fa-solid fa-triangle-exclamation"></i> Weak Password</span>' : ''} | |
| </div> | |
| `; | |
| container.appendChild(card); | |
| }); | |
| totalDisplay.textContent = passwords.length; | |
| weakDisplay.textContent = weakCount; | |
| } | |
| // Helper: Escape HTML to prevent XSS | |
| function escapeHtml(unsafe) { | |
| return unsafe | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """) | |
| .replace(/'/g, "'"); | |
| } | |
| // Toggle Password Visibility | |
| function togglePasswordVisibility(inputId) { | |
| const input = document.getElementById(inputId); | |
| const icon = document.getElementById(inputId.replace('pass-', 'icon-')); | |
| if (input.type === 'password') { | |
| input.type = 'text'; | |
| icon.classList.remove('fa-eye'); | |
| icon.classList.add('fa-eye-slash'); | |
| } else { | |
| input.type = 'password'; | |
| icon.classList.remove('fa-eye-slash'); | |
| icon.classList.add('fa-eye'); | |
| } | |
| } | |
| // Copy to Clipboard | |
| function copyToClipboard(text, btnElement) { | |
| navigator.clipboard.writeText(text).then(() => { | |
| showToast('Copied to clipboard!'); | |
| // Visual feedback on button | |
| const originalHtml = btnElement.innerHTML; | |
| btnElement.innerHTML = '<i class="fa-solid fa-check"></i> Copied'; | |
| setTimeout(() => { | |
| btnElement.innerHTML = originalHtml; | |
| }, 2000); | |
| }).catch(err => { | |
| showToast('Failed to copy'); | |
| }); | |
| } | |
| // Add New Password | |
| form.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const site = document.getElementById('siteName').value; | |
| const username = document.getElementById('username').value; | |
| const password = document.getElementById('password').value; | |
| const category = document.getElementById('category').value; | |
| const newEntry = { | |
| id: Date.now(), | |
| site, | |
| username, | |
| password, | |
| category | |
| }; | |
| passwords.push(newEntry); | |
| renderPasswords(); | |
| closeModal(); | |
| showToast('New credential added securely!'); | |
| }); | |
| // Delete Password | |
| window.deletePassword = function(id) { | |
| if (confirm('Are you sure you want to delete this credential?')) { | |
| passwords = passwords.filter(item => item.id !== id); | |
| renderPasswords(); | |
| showToast('Credential deleted.'); | |
| } | |
| }; | |
| // Edit Password (Simplified) | |
| window.editPassword = function(id) { | |
| const item = passwords.find(p => p.id === id); | |
| if (item) { | |
| document.getElementById('siteName').value = item.site; | |
| document.getElementById('username').value = item.username; | |
| document.getElementById('password').value = item.password; | |
| document.getElementById('category').value = item.category; | |
| // Remove the old item temporarily | |
| passwords = passwords.filter(p => p.id !== id); | |
| renderPasswords(); | |
| openModal(); | |
| // Note: In a full app, this would be a better form state management | |
| } | |
| }; | |
| // Modal Logic | |
| function openModal() { | |
| modal.style.display = 'flex'; | |
| } | |
| function closeModal() { | |
| modal.style.display = 'none'; | |
| form.reset(); | |
| } | |
| // Close modal if clicking outside | |
| window.onclick = function(event) { | |
| if (event.target == modal) { | |
| closeModal(); | |
| } | |
| } | |
| // Toast Logic | |
| const toast = document.getElementById('toast'); | |
| const toastMessage = document.getElementById('toastMessage'); | |
| function showToast(message) { | |
| toastMessage.textContent = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| </script> | |
| </body> | |
| </html> |