password-manager / index.html
MonsieurMory's picture
Add 2 files
a4d164c verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Personal Vault - Secure Password Manager</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.password-strength-meter {
height: 5px;
transition: all 0.3s ease;
}
.blur-effect {
filter: blur(5px);
transition: filter 0.3s ease;
}
.blur-effect:hover {
filter: blur(0);
}
</style>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<!-- Header -->
<header class="flex justify-between items-center mb-10">
<div class="flex items-center">
<i class="fas fa-lock-open text-indigo-500 text-3xl mr-3"></i>
<h1 class="text-2xl font-bold bg-gradient-to-r from-indigo-500 to-purple-600 bg-clip-text text-transparent">
Personal Vault
</h1>
</div>
<div id="auth-section">
<button id="logout-btn" class="hidden bg-gray-800 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition">
<i class="fas fa-sign-out-alt mr-2"></i>Lock Vault
</button>
</div>
</header>
<!-- Login Screen -->
<div id="login-screen" class="fade-in max-w-md mx-auto bg-gray-800 rounded-xl shadow-2xl p-8">
<div class="text-center mb-8">
<i class="fas fa-lock text-indigo-500 text-5xl mb-4"></i>
<h2 class="text-2xl font-bold mb-2">Welcome to Your Vault</h2>
<p class="text-gray-400">Enter your master password to unlock</p>
</div>
<form id="login-form" class="space-y-6">
<div>
<label for="master-password" class="block text-sm font-medium mb-2">Master Password</label>
<div class="relative">
<input type="password" id="master-password" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter your master password" required>
<button type="button" class="absolute right-3 top-3 text-gray-400 hover:text-gray-200" id="toggle-password">
<i class="fas fa-eye"></i>
</button>
</div>
<div class="mt-2 flex items-center">
<input type="checkbox" id="remember-me" class="rounded bg-gray-700 border-gray-600 text-indigo-600 focus:ring-indigo-500">
<label for="remember-me" class="ml-2 text-sm text-gray-400">Remember this device for 30 days</label>
</div>
</div>
<button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-4 rounded-lg transition flex items-center justify-center">
<i class="fas fa-unlock-alt mr-2"></i> Unlock Vault
</button>
</form>
<div class="mt-6 text-center text-sm text-gray-500">
<p>First time here? Your vault will be created automatically.</p>
</div>
</div>
<!-- Main App (Hidden by default) -->
<div id="app-container" class="hidden fade-in">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold">Your Saved Passwords</h2>
<button id="add-password-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center">
<i class="fas fa-plus mr-2"></i> Add Password
</button>
</div>
<!-- Search and Filter -->
<div class="mb-6 flex items-center">
<div class="relative flex-grow mr-4">
<input type="text" id="search-passwords" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 pl-10 focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Search passwords...">
<i class="fas fa-search absolute left-3 top-3 text-gray-500"></i>
</div>
<select id="category-filter" class="bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="all">All Categories</option>
<option value="social">Social Media</option>
<option value="work">Work</option>
<option value="finance">Finance</option>
<option value="shopping">Shopping</option>
<option value="other">Other</option>
</select>
</div>
<!-- Password List -->
<div id="password-list" class="space-y-3">
<!-- Passwords will be loaded here -->
<div class="text-center py-10 text-gray-500">
<i class="fas fa-lock-open text-4xl mb-3 opacity-30"></i>
<p>No passwords saved yet. Add your first password to get started.</p>
</div>
</div>
</div>
<!-- Add/Edit Password Modal -->
<div id="password-modal" class="hidden fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 p-4">
<div class="bg-gray-800 rounded-xl shadow-2xl max-w-md w-full p-6 fade-in">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold" id="modal-title">Add New Password</h3>
<button id="close-modal" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<form id="password-form" class="space-y-4">
<input type="hidden" id="password-id">
<div>
<label for="service-name" class="block text-sm font-medium mb-1">Service/Website</label>
<input type="text" id="service-name" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" required>
</div>
<div>
<label for="username" class="block text-sm font-medium mb-1">Username/Email</label>
<input type="text" id="username" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" required>
</div>
<div>
<label for="password" class="block text-sm font-medium mb-1">Password</label>
<div class="relative">
<input type="password" id="password" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500" required>
<button type="button" class="absolute right-3 top-2 text-gray-400 hover:text-gray-200" id="toggle-modal-password">
<i class="fas fa-eye"></i>
</button>
<button type="button" id="generate-password" class="absolute right-10 top-2 text-gray-400 hover:text-indigo-400" title="Generate strong password">
<i class="fas fa-random"></i>
</button>
</div>
<div class="mt-2 flex items-center space-x-2">
<div class="password-strength-meter flex-grow h-1 bg-gray-700 rounded overflow-hidden">
<div id="strength-bar" class="h-full w-0 bg-red-500"></div>
</div>
<span id="strength-text" class="text-xs text-gray-400">Weak</span>
</div>
</div>
<div>
<label for="category" class="block text-sm font-medium mb-1">Category</label>
<select id="category" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="social">Social Media</option>
<option value="work">Work</option>
<option value="finance">Finance</option>
<option value="shopping">Shopping</option>
<option value="other">Other</option>
</select>
</div>
<div>
<label for="notes" class="block text-sm font-medium mb-1">Notes</label>
<textarea id="notes" rows="2" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
</div>
<div class="flex justify-end space-x-3 pt-4">
<button type="button" id="cancel-password" class="px-4 py-2 rounded-lg border border-gray-600 hover:bg-gray-700 transition">
Cancel
</button>
<button type="submit" class="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-700 transition">
Save Password
</button>
</div>
</form>
</div>
</div>
<!-- Password Generator Modal -->
<div id="generator-modal" class="hidden fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center z-50 p-4">
<div class="bg-gray-800 rounded-xl shadow-2xl max-w-md w-full p-6 fade-in">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Password Generator</h3>
<button id="close-generator" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div class="flex items-center justify-between bg-gray-700 p-4 rounded-lg">
<input type="text" id="generated-password" readonly class="bg-transparent border-none outline-none w-full text-lg font-mono">
<button id="copy-generated" class="text-indigo-400 hover:text-indigo-300 ml-2">
<i class="fas fa-copy"></i>
</button>
</div>
<div class="space-y-3">
<div class="flex items-center justify-between">
<label for="password-length" class="text-sm font-medium">Length: <span id="length-value">12</span></label>
<input type="range" id="password-length" min="8" max="32" value="12" class="w-32">
</div>
<div class="space-y-2">
<div class="flex items-center">
<input type="checkbox" id="include-uppercase" checked class="rounded bg-gray-700 border-gray-600 text-indigo-600 focus:ring-indigo-500">
<label for="include-uppercase" class="ml-2 text-sm">Include Uppercase (A-Z)</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="include-lowercase" checked class="rounded bg-gray-700 border-gray-600 text-indigo-600 focus:ring-indigo-500">
<label for="include-lowercase" class="ml-2 text-sm">Include Lowercase (a-z)</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="include-numbers" checked class="rounded bg-gray-700 border-gray-600 text-indigo-600 focus:ring-indigo-500">
<label for="include-numbers" class="ml-2 text-sm">Include Numbers (0-9)</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="include-symbols" checked class="rounded bg-gray-700 border-gray-600 text-indigo-600 focus:ring-indigo-500">
<label for="include-symbols" class="ml-2 text-sm">Include Symbols (!@#$%^&*)</label>
</div>
</div>
</div>
<div class="flex justify-end pt-4">
<button id="use-generated" class="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-700 transition">
Use Password
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// DOM Elements
const loginScreen = document.getElementById('login-screen');
const appContainer = document.getElementById('app-container');
const loginForm = document.getElementById('login-form');
const masterPasswordInput = document.getElementById('master-password');
const togglePasswordBtn = document.getElementById('toggle-password');
const logoutBtn = document.getElementById('logout-btn');
const addPasswordBtn = document.getElementById('add-password-btn');
const passwordList = document.getElementById('password-list');
const passwordModal = document.getElementById('password-modal');
const closeModalBtn = document.getElementById('close-modal');
const cancelPasswordBtn = document.getElementById('cancel-password');
const passwordForm = document.getElementById('password-form');
const toggleModalPasswordBtn = document.getElementById('toggle-modal-password');
const generatePasswordBtn = document.getElementById('generate-password');
const generatorModal = document.getElementById('generator-modal');
const closeGeneratorBtn = document.getElementById('close-generator');
const useGeneratedBtn = document.getElementById('use-generated');
const copyGeneratedBtn = document.getElementById('copy-generated');
const searchPasswords = document.getElementById('search-passwords');
const categoryFilter = document.getElementById('category-filter');
// State
let masterKey = null;
let passwords = [];
let isEditing = false;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
checkRememberMe();
loadEventListeners();
});
// Event Listeners
function loadEventListeners() {
// Login
loginForm.addEventListener('submit', handleLogin);
togglePasswordBtn.addEventListener('click', () => togglePassword(masterPasswordInput, togglePasswordBtn));
// Logout
logoutBtn.addEventListener('click', handleLogout);
// Password Modal
addPasswordBtn.addEventListener('click', () => showPasswordModal());
closeModalBtn.addEventListener('click', () => hidePasswordModal());
cancelPasswordBtn.addEventListener('click', () => hidePasswordModal());
passwordForm.addEventListener('submit', handlePasswordSubmit);
toggleModalPasswordBtn.addEventListener('click', () => togglePassword(document.getElementById('password'), toggleModalPasswordBtn));
// Password Generator
generatePasswordBtn.addEventListener('click', showGeneratorModal);
closeGeneratorBtn.addEventListener('click', () => generatorModal.classList.add('hidden'));
useGeneratedBtn.addEventListener('click', useGeneratedPassword);
copyGeneratedBtn.addEventListener('click', copyGeneratedPassword);
// Password strength
document.getElementById('password').addEventListener('input', updatePasswordStrength);
// Search and filter
searchPasswords.addEventListener('input', filterPasswords);
categoryFilter.addEventListener('change', filterPasswords);
// Generator options
document.getElementById('password-length').addEventListener('input', updateGenerator);
document.getElementById('include-uppercase').addEventListener('change', updateGenerator);
document.getElementById('include-lowercase').addEventListener('change', updateGenerator);
document.getElementById('include-numbers').addEventListener('change', updateGenerator);
document.getElementById('include-symbols').addEventListener('change', updateGenerator);
}
// Check if user is remembered
function checkRememberMe() {
const remembered = localStorage.getItem('rememberMe') === 'true';
if (remembered) {
const storedHash = localStorage.getItem('masterPasswordHash');
if (storedHash) {
masterPasswordInput.value = '••••••••••';
document.getElementById('remember-me').checked = true;
}
}
}
// Handle Login
function handleLogin(e) {
e.preventDefault();
const password = masterPasswordInput.value;
const rememberMe = document.getElementById('remember-me').checked;
if (password === '••••••••••' && localStorage.getItem('rememberMe') === 'true') {
// Use stored hash for remembered user
const storedHash = localStorage.getItem('masterPasswordHash');
masterKey = storedHash;
unlockVault();
} else {
// Create new master key
const salt = CryptoJS.lib.WordArray.random(128/8);
const key = CryptoJS.PBKDF2(password, salt, {
keySize: 256/32,
iterations: 1000
});
masterKey = key.toString();
// Store hash if remember me is checked
if (rememberMe) {
localStorage.setItem('rememberMe', 'true');
localStorage.setItem('masterPasswordHash', masterKey);
} else {
localStorage.removeItem('rememberMe');
localStorage.removeItem('masterPasswordHash');
}
unlockVault();
}
}
// Unlock the vault
function unlockVault() {
// Load passwords from localStorage
const encryptedPasswords = localStorage.getItem('encryptedPasswords');
if (encryptedPasswords) {
try {
// In a real app, you would decrypt here
// For this demo, we'll just store the encrypted data
passwords = JSON.parse(encryptedPasswords);
} catch (e) {
console.error('Failed to decrypt passwords', e);
passwords = [];
}
}
// Switch to app view
loginScreen.classList.add('hidden');
appContainer.classList.remove('hidden');
logoutBtn.classList.remove('hidden');
// Render passwords
renderPasswords();
}
// Handle Logout
function handleLogout() {
// Clear master key
masterKey = null;
// Switch to login view
loginScreen.classList.remove('hidden');
appContainer.classList.add('hidden');
logoutBtn.classList.add('hidden');
// Clear password field
masterPasswordInput.value = '';
document.getElementById('remember-me').checked = false;
}
// Show Password Modal
function showPasswordModal(editId = null) {
isEditing = editId !== null;
if (isEditing) {
document.getElementById('modal-title').textContent = 'Edit Password';
const password = passwords.find(p => p.id === editId);
// In a real app, you would decrypt the password here
document.getElementById('password-id').value = password.id;
document.getElementById('service-name').value = password.service;
document.getElementById('username').value = password.username;
document.getElementById('password').value = password.password;
document.getElementById('category').value = password.category;
document.getElementById('notes').value = password.notes || '';
updatePasswordStrength();
} else {
document.getElementById('modal-title').textContent = 'Add New Password';
passwordForm.reset();
}
passwordModal.classList.remove('hidden');
}
// Hide Password Modal
function hidePasswordModal() {
passwordModal.classList.add('hidden');
}
// Handle Password Submit
function handlePasswordSubmit(e) {
e.preventDefault();
const id = document.getElementById('password-id').value || generateId();
const service = document.getElementById('service-name').value.trim();
const username = document.getElementById('username').value.trim();
const password = document.getElementById('password').value;
const category = document.getElementById('category').value;
const notes = document.getElementById('notes').value.trim();
if (!service || !username || !password) {
alert('Please fill in all required fields');
return;
}
// In a real app, you would encrypt the password here
const passwordData = {
id,
service,
username,
password,
category,
notes,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
if (isEditing) {
// Update existing password
const index = passwords.findIndex(p => p.id === id);
if (index !== -1) {
passwords[index] = passwordData;
}
} else {
// Add new password
passwords.push(passwordData);
}
// Save to localStorage
// In a real app, you would encrypt all passwords before saving
localStorage.setItem('encryptedPasswords', JSON.stringify(passwords));
// Update UI
renderPasswords();
hidePasswordModal();
}
// Render Passwords
function renderPasswords() {
if (passwords.length === 0) {
passwordList.innerHTML = `
<div class="text-center py-10 text-gray-500">
<i class="fas fa-lock-open text-4xl mb-3 opacity-30"></i>
<p>No passwords saved yet. Add your first password to get started.</p>
</div>
`;
return;
}
passwordList.innerHTML = passwords.map(password => `
<div class="password-item bg-gray-800 rounded-lg p-4 hover:bg-gray-750 transition border border-gray-750" data-id="${password.id}" data-category="${password.category}">
<div class="flex justify-between items-start">
<div class="flex items-center">
<div class="w-10 h-10 rounded-full bg-indigo-900 flex items-center justify-center text-indigo-400 mr-3">
${getServiceIcon(password.service)}
</div>
<div>
<h3 class="font-medium">${password.service}</h3>
<p class="text-sm text-gray-400">${password.username}</p>
</div>
</div>
<div class="flex space-x-2">
<button class="copy-password text-gray-400 hover:text-indigo-400" data-password="${password.password}" title="Copy password">
<i class="fas fa-copy"></i>
</button>
<button class="edit-password text-gray-400 hover:text-yellow-400" data-id="${password.id}" title="Edit">
<i class="fas fa-edit"></i>
</button>
<button class="delete-password text-gray-400 hover:text-red-400" data-id="${password.id}" title="Delete">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
<div class="mt-3 flex justify-between items-center text-xs">
<span class="category-tag px-2 py-1 rounded-full ${getCategoryColor(password.category)}">
${getCategoryName(password.category)}
</span>
<span class="text-gray-500">Last updated: ${formatDate(password.updatedAt)}</span>
</div>
</div>
`).join('');
// Add event listeners to new elements
document.querySelectorAll('.copy-password').forEach(btn => {
btn.addEventListener('click', (e) => {
const password = e.currentTarget.getAttribute('data-password');
copyToClipboard(password);
showToast('Password copied to clipboard!', 'success');
});
});
document.querySelectorAll('.edit-password').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = e.currentTarget.getAttribute('data-id');
showPasswordModal(id);
});
});
document.querySelectorAll('.delete-password').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = e.currentTarget.getAttribute('data-id');
if (confirm('Are you sure you want to delete this password?')) {
deletePassword(id);
}
});
});
}
// Delete Password
function deletePassword(id) {
passwords = passwords.filter(p => p.id !== id);
localStorage.setItem('encryptedPasswords', JSON.stringify(passwords));
renderPasswords();
showToast('Password deleted', 'info');
}
// Filter Passwords
function filterPasswords() {
const searchTerm = searchPasswords.value.toLowerCase();
const category = categoryFilter.value;
document.querySelectorAll('.password-item').forEach(item => {
const matchesSearch = item.textContent.toLowerCase().includes(searchTerm);
const matchesCategory = category === 'all' || item.getAttribute('data-category') === category;
if (matchesSearch && matchesCategory) {
item.classList.remove('hidden');
} else {
item.classList.add('hidden');
}
});
}
// Password Generator
function showGeneratorModal() {
updateGenerator();
generatorModal.classList.remove('hidden');
}
function updateGenerator() {
const length = parseInt(document.getElementById('password-length').value);
const includeUpper = document.getElementById('include-uppercase').checked;
const includeLower = document.getElementById('include-lowercase').checked;
const includeNumbers = document.getElementById('include-numbers').checked;
const includeSymbols = document.getElementById('include-symbols').checked;
document.getElementById('length-value').textContent = length;
const chars = [];
if (includeUpper) chars.push('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
if (includeLower) chars.push('abcdefghijklmnopqrstuvwxyz');
if (includeNumbers) chars.push('0123456789');
if (includeSymbols) chars.push('!@#$%^&*()_+-=[]{}|;:,.<>?');
if (chars.length === 0) {
document.getElementById('generated-password').value = 'Select at least one character type';
return;
}
let password = '';
const allChars = chars.join('');
// Ensure at least one character from each selected set
chars.forEach(charSet => {
password += charSet.charAt(Math.floor(Math.random() * charSet.length));
});
// Fill the rest randomly
for (let i = password.length; i < length; i++) {
password += allChars.charAt(Math.floor(Math.random() * allChars.length));
}
// Shuffle the password to mix the required characters
password = password.split('').sort(() => 0.5 - Math.random()).join('');
document.getElementById('generated-password').value = password;
}
function useGeneratedPassword() {
const generatedPassword = document.getElementById('generated-password').value;
document.getElementById('password').value = generatedPassword;
updatePasswordStrength();
generatorModal.classList.add('hidden');
}
function copyGeneratedPassword() {
const generatedPassword = document.getElementById('generated-password').value;
copyToClipboard(generatedPassword);
showToast('Password copied to clipboard!', 'success');
}
// Password Strength
function updatePasswordStrength() {
const password = document.getElementById('password').value;
const strengthBar = document.getElementById('strength-bar');
const strengthText = document.getElementById('strength-text');
if (!password) {
strengthBar.style.width = '0%';
strengthText.textContent = '';
return;
}
// Simple strength calculation
let strength = 0;
// Length
if (password.length >= 8) strength += 1;
if (password.length >= 12) strength += 1;
if (password.length >= 16) strength += 1;
// Character variety
if (/[A-Z]/.test(password)) strength += 1;
if (/[a-z]/.test(password)) strength += 1;
if (/[0-9]/.test(password)) strength += 1;
if (/[^A-Za-z0-9]/.test(password)) strength += 1;
// Common patterns (negative)
if (password.length < 6) strength = 0;
if (password === password.toLowerCase()) strength = Math.max(0, strength - 1);
if (password === password.toUpperCase()) strength = Math.max(0, strength - 1);
if (/^[0-9]+$/.test(password)) strength = Math.max(0, strength - 2);
// Map to 0-100% for the bar
const percentage = Math.min(100, strength * 20);
strengthBar.style.width = `${percentage}%`;
// Update color and text
if (percentage < 30) {
strengthBar.style.backgroundColor = '#ef4444'; // red
strengthText.textContent = 'Weak';
} else if (percentage < 70) {
strengthBar.style.backgroundColor = '#f59e0b'; // yellow
strengthText.textContent = 'Medium';
} else {
strengthBar.style.backgroundColor = '#10b981'; // green
strengthText.textContent = 'Strong';
}
}
// Helper Functions
function togglePassword(input, button) {
const type = input.getAttribute('type') === 'password' ? 'text' : 'password';
input.setAttribute('type', type);
if (type === 'text') {
button.innerHTML = '<i class="fas fa-eye-slash"></i>';
} else {
button.innerHTML = '<i class="fas fa-eye"></i>';
}
}
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
function copyToClipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `fixed bottom-4 right-4 px-4 py-2 rounded-lg shadow-lg bg-gray-800 border-l-4 ${type === 'success' ? 'border-green-500' : type === 'error' ? 'border-red-500' : 'border-blue-500'} text-white animate-bounce`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.remove('animate-bounce');
toast.classList.add('opacity-0', 'transition-opacity', 'duration-300');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
function getServiceIcon(service) {
const serviceLower = service.toLowerCase();
if (serviceLower.includes('google')) return '<i class="fab fa-google"></i>';
if (serviceLower.includes('facebook')) return '<i class="fab fa-facebook-f"></i>';
if (serviceLower.includes('twitter')) return '<i class="fab fa-twitter"></i>';
if (serviceLower.includes('instagram')) return '<i class="fab fa-instagram"></i>';
if (serviceLower.includes('linkedin')) return '<i class="fab fa-linkedin-in"></i>';
if (serviceLower.includes('github')) return '<i class="fab fa-github"></i>';
if (serviceLower.includes('amazon')) return '<i class="fab fa-amazon"></i>';
if (serviceLower.includes('apple')) return '<i class="fab fa-apple"></i>';
if (serviceLower.includes('microsoft')) return '<i class="fab fa-microsoft"></i>';
if (serviceLower.includes('paypal')) return '<i class="fab fa-paypal"></i>';
if (serviceLower.includes('bank') || serviceLower.includes('finance')) return '<i class="fas fa-university"></i>';
if (serviceLower.includes('email') || serviceLower.includes('mail')) return '<i class="fas fa-envelope"></i>';
return '<i class="fas fa-globe"></i>';
}
function getCategoryName(category) {
const names = {
'social': 'Social Media',
'work': 'Work',
'finance': 'Finance',
'shopping': 'Shopping',
'other': 'Other'
};
return names[category] || category;
}
function getCategoryColor(category) {
const colors = {
'social': 'bg-blue-900 text-blue-300',
'work': 'bg-purple-900 text-purple-300',
'finance': 'bg-green-900 text-green-300',
'shopping': 'bg-yellow-900 text-yellow-300',
'other': 'bg-gray-700 text-gray-300'
};
return colors[category] || 'bg-gray-700 text-gray-300';
}
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MonsieurMory/password-manager" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>