diff --git a/app/static/css/app.css b/app/static/css/app.css index 5207732..1086c79 100644 --- a/app/static/css/app.css +++ b/app/static/css/app.css @@ -170,7 +170,6 @@ ul { /* Prevent iOS viewport auto-zoom on input focus (requires font-size >= 16px) */ @media (max-width: 768px) { - .form-group input, .form-group select, .form-group textarea { @@ -1139,4 +1138,80 @@ body.is-mobile .form-group textarea { color: var(--color-danger, #e53e3e); font-size: 13px; font-weight: 500; -} \ No newline at end of file +} + +/* ── CSP-safe replacements for former inline styles ─────────────────── */ + +/* TOTP timer: turns red when ≤5 s remain (toggled via JS class) */ +.totp-timer--expiring { + color: #dc2626; +} + +/* Generate-password button injected below password field */ +.btn-generate { + margin-top: 6px; +} + +/* Scroll-lock applied to when mobile sidebar is open */ +html.sidebar-open { + overflow: hidden; +} + +/* Password Generator modal max-width */ +.gen-modal-inner { + max-width: 420px; +} + +/* Generator length slider */ +#gen-length { + width: 100%; + margin-top: 4px; +} + +/* Generator options row */ +.gen-options { + display: flex; + gap: 16px; + flex-wrap: wrap; + margin-bottom: 16px; +} + +/* Generator form-group spacing */ +.gen-form-group { + margin-bottom: 8px; +} + +/* Generator input-with-toggle wrapper */ +.gen-input-wrap { + margin-bottom: 4px; +} + +/* Generator copy button */ +#btn-gen-copy { + font-size: 14px; +} + +/* Generator output field */ +#gen-output { + padding-right: 40px; +} + +/* Form field label — "optional" badge */ +.field-label-optional { + font-size: 11px; + color: #9ca3af; + font-weight: 400; +} + +/* Form field hint text below inputs */ +.field-hint { + font-size: 11px; + color: #6b7280; + margin-top: 4px; +} + +/* Monospace input — used for recovery codes and other token fields */ +.input-monospace { + font-family: monospace; + letter-spacing: 0.05em; +} diff --git a/app/static/js/vault.js b/app/static/js/vault.js index 563bc55..88411cf 100644 --- a/app/static/js/vault.js +++ b/app/static/js/vault.js @@ -299,8 +299,8 @@ const Vault = (() => { _sortOrder === "folder" ? Object.keys(groups).sort() : ["(No folder)", ..._folders.map((f) => f.name)].filter( - (k) => groups[k], - ); + (k) => groups[k], + ); Object.keys(groups).forEach((k) => { if (!keys.includes(k)) keys.push(k); }); @@ -345,7 +345,7 @@ const Vault = (() => { case "card": subText = item.plain.card_number ? "•••• " + - String(item.plain.card_number).replace(/\s/g, "").slice(-4) + String(item.plain.card_number).replace(/\s/g, "").slice(-4) : ""; break; case "bank": @@ -367,7 +367,9 @@ const Vault = (() => { item.item_type, ); const showLaunch = item.item_type === "password" && item.plain?.url; - const showTotp = item.item_type === "password" && !!extractTotpSecret(item.plain?.totp_uri); + const showTotp = + item.item_type === "password" && + !!extractTotpSecret(item.plain?.totp_uri); li.innerHTML = `
${icon}
@@ -400,7 +402,7 @@ const Vault = (() => { const secs = totpSecondsLeft(); timerEl.textContent = " (" + secs + "s)"; // Colour the timer red when it's about to expire. - timerEl.style.color = secs <= 5 ? "#dc2626" : "#9ca3af"; + timerEl.classList.toggle("totp-timer--expiring", secs <= 5); } refreshTotp(); // Refresh every second. Store interval id on the element for cleanup. @@ -524,9 +526,9 @@ const Vault = (() => { 0, Math.round( 100 - - (weak.length / total) * 40 - - (reused.length / total) * 30 - - (old.length / total) * 15, + (weak.length / total) * 40 - + (reused.length / total) * 30 - + (old.length / total) * 15, ), ); const cls = @@ -557,14 +559,14 @@ const Vault = (() => { `; sec.querySelectorAll("[data-sec-edit]").forEach((btn) => { btn.addEventListener("click", () => { @@ -712,10 +714,11 @@ const Vault = (() => { ${escHtml(s.item_name)} From ${escHtml(s.owner_email)} - ${!s.accepted + ${ + !s.accepted ? `` : `` - } + } `, ) .join(""); @@ -1202,9 +1205,10 @@ const Vault = (() => { `; @@ -1338,9 +1342,8 @@ const Vault = (() => { const btn = document.createElement("button"); btn.type = "button"; btn.id = "btn-generate-pass"; - btn.className = "btn-secondary btn-sm"; + btn.className = "btn-secondary btn-sm btn-generate"; btn.textContent = "⚡ Generate"; - btn.style.marginTop = "6px"; pwInput.closest(".form-group").appendChild(btn); } @@ -1362,16 +1365,16 @@ const Vault = (() => { modal.setAttribute("role", "dialog"); modal.setAttribute("aria-modal", "true"); modal.innerHTML = ` -