add sound effects
This commit is contained in:
@@ -15,6 +15,7 @@ let cryptoKey = null;
|
||||
let idleT, warnT, countT;
|
||||
let draggedId = null;
|
||||
let showTrash = false;
|
||||
|
||||
// ==================== SOUND ====================
|
||||
let soundEnabled = localStorage.getItem('soundEnabled') !== 'false';
|
||||
let audioCtx = null;
|
||||
@@ -46,9 +47,7 @@ function playTone(freq, duration, type = 'sine', volume = 0.08) {
|
||||
function playSound(type) {
|
||||
if (!soundEnabled) return;
|
||||
switch (type) {
|
||||
case 'click':
|
||||
playTone(800, 0.08, 'sine', 0.06);
|
||||
break;
|
||||
case 'click': playTone(800, 0.08, 'sine', 0.06); break;
|
||||
case 'success':
|
||||
playTone(523, 0.1, 'sine', 0.1);
|
||||
setTimeout(() => playTone(659, 0.1, 'sine', 0.1), 100);
|
||||
@@ -95,19 +94,15 @@ function toggleSound() {
|
||||
soundEnabled = !soundEnabled;
|
||||
localStorage.setItem('soundEnabled', soundEnabled);
|
||||
const btn = document.getElementById('soundToggle');
|
||||
if (btn) {
|
||||
btn.textContent = soundEnabled ? '🔊' : '🔇';
|
||||
}
|
||||
if (soundEnabled) playTone(440, 0.05, 'sine', 0.05); // confirmation beep
|
||||
if (btn) btn.textContent = soundEnabled ? '🔊' : '🔇';
|
||||
if (soundEnabled) playTone(440, 0.05, 'sine', 0.05);
|
||||
}
|
||||
|
||||
// Update the button on startup
|
||||
function updateSoundButton() {
|
||||
const btn = document.getElementById('soundToggle');
|
||||
if (btn) {
|
||||
btn.textContent = soundEnabled ? '🔊' : '🔇';
|
||||
}
|
||||
if (btn) btn.textContent = soundEnabled ? '🔊' : '🔇';
|
||||
}
|
||||
|
||||
// ==================== TOAST ====================
|
||||
function toast(m, t) { t = t || 'success'; const c = document.getElementById('toastContainer'); const d = document.createElement('div'); d.className = 'toast ' + t; d.textContent = m; c.appendChild(d); setTimeout(() => d.remove(), 3000); }
|
||||
function showZigzagToast(elem, msg, type) {
|
||||
@@ -123,7 +118,7 @@ function showZigzagToast(elem, msg, type) {
|
||||
|
||||
// ==================== THEME ====================
|
||||
function applyTheme() { document.body.classList.toggle('light', !dark); }
|
||||
function toggleTheme() { dark = !dark; localStorage.setItem('darkTheme', dark); applyTheme(); }
|
||||
function toggleTheme() { dark = !dark; localStorage.setItem('darkTheme', dark); applyTheme(); playSound('click'); }
|
||||
|
||||
// ==================== CRYPTO ====================
|
||||
function checkStrength() { const p = document.getElementById('passwordInput').value; const b = document.getElementById('strengthBar'); let s = 0; if (p.length >= 8) s++; if (p.length >= 12) s++; if (/[A-Z]/.test(p) && /[a-z]/.test(p)) s++; if (/\d/.test(p)) s++; if (/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(p)) s++; b.className = 'strength-bar s' + Math.min(4, s); }
|
||||
@@ -227,6 +222,7 @@ function selectFolder(f) {
|
||||
renderFolders();
|
||||
populateAddFolderSelect();
|
||||
render();
|
||||
playSound('click');
|
||||
}
|
||||
|
||||
function showAddFolderModal() {
|
||||
@@ -239,9 +235,10 @@ function showAddFolderModal() {
|
||||
const name = document.getElementById('newFolderName').value.trim();
|
||||
if (!name) { toast('Enter a name', 'error'); return; }
|
||||
const ok = await addFolderToServer(name);
|
||||
if (ok) { renderFolders(); populateAddFolderSelect(); overlay.remove(); toast('📁 Folder created!'); }
|
||||
if (ok) { renderFolders(); populateAddFolderSelect(); overlay.remove(); toast('📁 Folder created!'); playSound('success'); }
|
||||
};
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
playSound('open');
|
||||
}
|
||||
|
||||
function showDeleteFolderConfirm(folderName) {
|
||||
@@ -252,7 +249,7 @@ function showDeleteFolderConfirm(folderName) {
|
||||
document.getElementById('cancelDeleteFolder').onclick = () => overlay.remove();
|
||||
document.getElementById('confirmDeleteFolder').onclick = async () => {
|
||||
const ok = await deleteFolderFromServer(folderName);
|
||||
if (ok) { renderFolders(); populateAddFolderSelect(); render(); overlay.remove(); toast('📁 Folder deleted'); }
|
||||
if (ok) { renderFolders(); populateAddFolderSelect(); render(); overlay.remove(); toast('📁 Folder deleted'); playSound('delete'); }
|
||||
};
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
}
|
||||
@@ -265,17 +262,18 @@ function toggleTrash() {
|
||||
if (btn) { btn.textContent = showTrash ? '📋 Active' : '🗑️ Trash'; btn.classList.toggle('btn-danger', showTrash); }
|
||||
if (actions) actions.classList.toggle('hidden', !showTrash);
|
||||
loadEntries();
|
||||
playSound('click');
|
||||
}
|
||||
async function restoreEntry(id) {
|
||||
try { const r = await fetch(API + '/entries/' + id + '/restore', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token } }); if (r.ok) { toast('✅ Restored!'); playSound('success'); loadEntries(); } } catch (e) { toast('⚠️ Error', 'error'); }
|
||||
try { const r = await fetch(API + '/entries/' + id + '/restore', { method: 'POST', headers: { 'Authorization': 'Bearer ' + token } }); if (r.ok) { toast('✅ Restored!'); loadEntries(); playSound('success'); } } catch (e) { toast('⚠️ Error', 'error'); }
|
||||
}
|
||||
async function permanentDelete(id) {
|
||||
if (!confirm('Permanently delete?')) return;
|
||||
try { const r = await fetch(API + '/entries/' + id + '?permanent=1', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }); if (r.ok) { toast('🗑️ Permanently deleted'); playSound('error'); loadEntries(); } } catch (e) { toast('⚠️ Error', 'error'); }
|
||||
try { const r = await fetch(API + '/entries/' + id + '?permanent=1', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }); if (r.ok) { toast('🗑️ Permanently deleted'); loadEntries(); playSound('error'); } } catch (e) { toast('⚠️ Error', 'error'); }
|
||||
}
|
||||
async function emptyTrash() {
|
||||
if (!confirm('Delete ALL trashed entries?')) return;
|
||||
try { const r = await fetch(API + '/entries/trash/empty', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }); if (r.ok) { toast('🗑️ Trash emptied'); playSound('error'); loadEntries(); } } catch (e) { toast('⚠️ Error', 'error'); }
|
||||
try { const r = await fetch(API + '/entries/trash/empty', { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }); if (r.ok) { toast('🗑️ Trash emptied'); loadEntries(); playSound('error'); } } catch (e) { toast('⚠️ Error', 'error'); }
|
||||
}
|
||||
function timeAgo(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
@@ -295,6 +293,7 @@ function init() {
|
||||
loadUsername();
|
||||
const sl = localStorage.getItem('savedLoginUser');
|
||||
if (sl) document.getElementById('loginUsername').value = sl;
|
||||
updateSoundButton();
|
||||
document.getElementById('viewToggle').addEventListener('click', e => {
|
||||
if (e.target.classList.contains('view-btn')) {
|
||||
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
|
||||
@@ -302,19 +301,19 @@ function init() {
|
||||
view = e.target.dataset.view;
|
||||
localStorage.setItem('vaultView', view);
|
||||
render();
|
||||
playSound('click');
|
||||
}
|
||||
});
|
||||
updateSoundButton();
|
||||
}
|
||||
function toggleViewBtn() { showView = !showView; localStorage.setItem('showViewBtn', showView); document.getElementById('showViewBtnToggle').classList.toggle('active', showView); render(); }
|
||||
function toggleShowEmail() { showMail = !showMail; localStorage.setItem('showEmail', showMail); document.getElementById('showEmailToggle').classList.toggle('active', showMail); document.getElementById('usernameInput').style.display = showMail ? '' : 'none'; render(); }
|
||||
function toggleViewBtn() { showView = !showView; localStorage.setItem('showViewBtn', showView); document.getElementById('showViewBtnToggle').classList.toggle('active', showView); render(); playSound('click'); }
|
||||
function toggleShowEmail() { showMail = !showMail; localStorage.setItem('showEmail', showMail); document.getElementById('showEmailToggle').classList.toggle('active', showMail); document.getElementById('usernameInput').style.display = showMail ? '' : 'none'; render(); playSound('click'); }
|
||||
|
||||
// ==================== GENERATOR ====================
|
||||
function openGen() { document.getElementById('genModal').style.display = 'flex'; genPwd(); }
|
||||
function closeGen() { document.getElementById('genModal').style.display = 'none'; }
|
||||
function openGen() { document.getElementById('genModal').style.display = 'flex'; genPwd(); playSound('open'); }
|
||||
function closeGen() { document.getElementById('genModal').style.display = 'none'; playSound('close'); }
|
||||
function onLenChange() { document.getElementById('lenVal').textContent = document.getElementById('pwdLen').value; genPwd(); }
|
||||
function genPwd() { const l = parseInt(document.getElementById('pwdLen').value); let c = ''; if (document.getElementById('useUpper').checked) c += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if (document.getElementById('useLower').checked) c += 'abcdefghijklmnopqrstuvwxyz'; if (document.getElementById('useNum').checked) c += '0123456789'; if (document.getElementById('useSym').checked) c += '!@#$%^&*()_+-=[]{}|;:,.<>?'; if (!c) { document.getElementById('genPreview').textContent = 'Select option'; return; } let p = ''; for (let i = 0; i < l; i++) p += c.charAt(Math.floor(Math.random() * c.length)); genPwdVal = p; document.getElementById('genPreview').textContent = p; }
|
||||
function useGen() { if (!genPwdVal) genPwd(); document.getElementById('passwordInput').value = genPwdVal; checkStrength(); navigator.clipboard.writeText(genPwdVal); toast('🎲 Copied!'); closeGen(); }
|
||||
function useGen() { if (!genPwdVal) genPwd(); document.getElementById('passwordInput').value = genPwdVal; checkStrength(); navigator.clipboard.writeText(genPwdVal); toast('🎲 Copied!'); playSound('generate'); closeGen(); }
|
||||
|
||||
// ==================== AUTH ====================
|
||||
function switchTab(t) { document.querySelectorAll('.auth-tab').forEach(x => x.classList.remove('active')); event.target.classList.add('active'); document.getElementById('loginForm').classList.toggle('hidden', t !== 'login'); document.getElementById('registerForm').classList.toggle('hidden', t !== 'register'); }
|
||||
@@ -337,6 +336,7 @@ async function login() {
|
||||
sessionStorage.setItem('currentUsername', u);
|
||||
await loadFolders();
|
||||
toast('✅ Login!');
|
||||
playSound('login');
|
||||
showVault();
|
||||
loadEntries();
|
||||
} else { toast('❌ ' + (d.error || 'Invalid'), 'error'); document.getElementById('loginPassword').value = ''; }
|
||||
@@ -362,6 +362,7 @@ async function register() {
|
||||
sessionStorage.setItem('currentUsername', u);
|
||||
await loadFolders();
|
||||
toast('✅ Created!');
|
||||
playSound('register');
|
||||
showVault();
|
||||
loadEntries();
|
||||
} else { toast('❌ ' + (d.error || 'Failed'), 'error'); }
|
||||
@@ -500,7 +501,7 @@ function showConfirm(btn, message, callback) {
|
||||
let leftPos = rect.left - confirm.offsetWidth + rect.width;
|
||||
if (leftPos < 10) leftPos = 10;
|
||||
confirm.style.left = leftPos + 'px';
|
||||
confirm.querySelector('.confirm-yes').onclick = () => { confirm.remove(); callback(id); showZigzagToast(btn, '🗑️ Deleted!', 'error'); };
|
||||
confirm.querySelector('.confirm-yes').onclick = () => { confirm.remove(); callback(id); showZigzagToast(btn, '🗑️ Deleted!', 'error'); playSound('delete'); };
|
||||
confirm.querySelector('.confirm-no').onclick = () => confirm.remove();
|
||||
setTimeout(() => { document.addEventListener('click', function closeConfirm(e) { if (!confirm.contains(e.target) && e.target !== btn) { confirm.remove(); document.removeEventListener('click', closeConfirm); } }); }, 10);
|
||||
}
|
||||
@@ -508,7 +509,7 @@ function attachEvents() {
|
||||
document.querySelectorAll('.delete-btn').forEach(b => b.onclick = function(ev) { ev.stopPropagation(); if (showTrash) { permanentDelete(this.dataset.id); } else { showConfirm(this, 'Delete this entry?', id => delEntry(id)); } });
|
||||
document.querySelectorAll('.edit-btn').forEach(b => b.onclick = function(ev) { ev.stopPropagation(); openEdit(this.dataset.id); });
|
||||
document.querySelectorAll('.toggle-p').forEach(b => b.onclick = function(ev) { ev.stopPropagation(); const el = document.getElementById('p-' + this.dataset.id); el.textContent = el.textContent === '••••••••' ? el.dataset.pw : '••••••••'; });
|
||||
document.querySelectorAll('.copy-p').forEach(b => b.onclick = async function(ev) { ev.stopPropagation(); const el = document.getElementById('p-' + this.dataset.id); try { await navigator.clipboard.writeText(el.dataset.pw); this.textContent = '✓'; const btn = this; setTimeout(() => { btn.textContent = '📋'; }, 1000); showZigzagToast(this, '📋 Copied!', 'success'); } catch (e) { showZigzagToast(this, 'Failed', 'error'); } });
|
||||
document.querySelectorAll('.copy-p').forEach(b => b.onclick = async function(ev) { ev.stopPropagation(); const el = document.getElementById('p-' + this.dataset.id); try { await navigator.clipboard.writeText(el.dataset.pw); this.textContent = '✓'; const btn = this; setTimeout(() => { btn.textContent = '📋'; }, 1000); showZigzagToast(this, '📋 Copied!', 'success'); playSound('copy'); } catch (e) { showZigzagToast(this, 'Failed', 'error'); } });
|
||||
}
|
||||
function setupDrag() {
|
||||
const c = document.getElementById('entriesContainer'); if (!c) return;
|
||||
@@ -542,8 +543,9 @@ function openEdit(id) {
|
||||
document.getElementById('editPassword').value = e.password;
|
||||
document.getElementById('editPassword').type = 'password';
|
||||
document.getElementById('editModal').classList.add('show');
|
||||
playSound('open');
|
||||
}
|
||||
function closeEdit() { document.getElementById('editModal').classList.remove('show'); }
|
||||
function closeEdit() { document.getElementById('editModal').classList.remove('show'); playSound('close'); }
|
||||
function toggleEditPassword() { const f = document.getElementById('editPassword'); f.type = f.type === 'password' ? 'text' : 'password'; }
|
||||
async function saveEdit() {
|
||||
const id = document.getElementById('editId').value;
|
||||
@@ -555,7 +557,7 @@ async function saveEdit() {
|
||||
try {
|
||||
const enc = await encryptPwd(password);
|
||||
const r = await fetch(API + '/entries/' + id, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ site, username, encrypted_password: enc.encrypted, iv: enc.iv, folder }) });
|
||||
if (r.ok) { toast('✅ Updated!'); playSound('success'); closeEdit(); loadEntries(); }
|
||||
if (r.ok) { toast('✅ Updated!'); closeEdit(); loadEntries(); playSound('success'); }
|
||||
else { const d = await r.json(); toast('❌ ' + (d.error || 'Failed'), 'error'); }
|
||||
} catch (e) { toast('⚠️ Error', 'error'); }
|
||||
}
|
||||
@@ -572,7 +574,7 @@ async function addEntry() {
|
||||
const enc = await encryptPwd(pass);
|
||||
const folder = document.getElementById('addFolderSelect').value;
|
||||
const r = await fetch(API + '/entries', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify({ site, username: user, encrypted_password: enc.encrypted, iv: enc.iv, encryption_method: 'client', folder }) });
|
||||
if (r.ok) { document.getElementById('siteInput').value = ''; document.getElementById('passwordInput').value = ''; document.getElementById('strengthBar').className = 'strength-bar s0'; toast('✅ Saved!'); playSound('success');loadEntries(); }
|
||||
if (r.ok) { document.getElementById('siteInput').value = ''; document.getElementById('passwordInput').value = ''; document.getElementById('strengthBar').className = 'strength-bar s0'; toast('✅ Saved!'); loadEntries(); playSound('success'); }
|
||||
else { const d = await r.json(); toast('❌ ' + (d.error || 'Failed'), 'error'); }
|
||||
} catch (e) { toast('⚠️ Error', 'error'); }
|
||||
finally { document.getElementById('addBtn').disabled = false; }
|
||||
@@ -580,7 +582,7 @@ async function addEntry() {
|
||||
async function delEntry(id) {
|
||||
try {
|
||||
const r = await fetch(API + '/entries/' + id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } });
|
||||
if (r.ok) { order = order.filter(x => x != id); localStorage.setItem('entryOrder', JSON.stringify(order)); toast('📦 Moved to trash'); playSound('delete'); loadEntries(); }
|
||||
if (r.ok) { order = order.filter(x => x != id); localStorage.setItem('entryOrder', JSON.stringify(order)); toast('📦 Moved to trash'); loadEntries(); playSound('delete'); }
|
||||
} catch (e) { toast('Error', 'error'); }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user