diff --git a/app/static/css/app.css b/app/static/css/app.css index 9adc778..fb432dc 100644 --- a/app/static/css/app.css +++ b/app/static/css/app.css @@ -313,28 +313,50 @@ ul { color: #1558b0; } -/* ── Vault list items ────────────────────────────────────────────── */ +/* ── Vault list — card grid layout ───────────────────────────────── */ +#vault-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 10px; + padding: 4px 0; +} + +/* Group header spans all columns */ +.vault-group-header { + grid-column: 1 / -1; +} + .vault-item { display: flex; align-items: center; gap: 12px; - padding: 10px 14px; - border-bottom: 1px solid var(--color-border); + padding: 14px; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); cursor: default; - transition: background var(--transition); -} - -.vault-item:last-child { - border-bottom: none; + transition: box-shadow var(--transition), border-color var(--transition); } .vault-item:hover { - background: #f9fafb; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); + border-color: #d1d5db; } -.item-icon { - font-size: 20px; +/* Initials avatar */ +.item-avatar { + width: 40px; + height: 40px; + border-radius: 10px; + background: #fde8ea; + color: var(--color-primary-dark); + font-size: 14px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; flex-shrink: 0; + user-select: none; } .item-info { @@ -369,6 +391,31 @@ ul { flex-shrink: 0; } +/* Strength pill inside card */ +.item-strength-pill { + display: inline-block; + font-size: 11px; + font-weight: 600; + padding: 1px 8px; + border-radius: 999px; + margin-top: 4px; +} + +.item-strength-pill.strength-strong { + background: #d1fae5; + color: #065f46; +} + +.item-strength-pill.strength-medium { + background: #fef3c7; + color: #92400e; +} + +.item-strength-pill.strength-weak { + background: #fee2e2; + color: #991b1b; +} + /* ── TOTP live display in vault item ─────────────────────────────── */ .item-totp { display: inline-flex; @@ -584,6 +631,7 @@ ul { color: var(--color-text-muted); padding: 60px 0; font-size: 15px; + grid-column: 1 / -1; } .btn-icon { @@ -953,7 +1001,7 @@ ul { .security-summary { display: flex; - gap: 20px; + gap: 24px; align-items: center; background: var(--color-surface); border: 1px solid var(--color-border); @@ -962,82 +1010,95 @@ ul { flex-wrap: wrap; } -.sec-score-card { - text-align: center; +/* ── Circular score ring ──────────────────────────────────────────── */ +.sec-ring-wrap { + position: relative; + width: 100px; + height: 100px; flex-shrink: 0; } -.sec-score { - font-size: 56px; - font-weight: 700; +.sec-ring-svg { + width: 100%; + height: 100%; +} + +.sec-ring-score { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + font-weight: 800; line-height: 1; } -.sec-score.score-good { - color: #2e7d32; +.sec-summary-right { + flex: 1; + min-width: 200px; } -.sec-score.score-fair { - color: #e65100; -} - -.sec-score.score-poor { - color: #c62828; -} - -.sec-score-label { - font-size: 16px; - font-weight: 600; - margin-top: 4px; -} - -.sec-score-desc { - font-size: 12px; - color: var(--color-text-muted); - margin-top: 2px; -} - -.sec-stats { - display: flex; - gap: 16px; - flex-wrap: wrap; -} - -.sec-stat { - text-align: center; - background: var(--color-bg); - border: 1px solid var(--color-border); - border-radius: var(--radius); - padding: 12px 20px; - min-width: 80px; -} - -.sec-stat-warn { - border-color: #ffb74d; - background: #fff8e1; -} - -.sec-stat-info { - border-color: #90caf9; - background: #e3f2fd; -} - -.sec-stat-ok { - border-color: #a5d6a7; - background: #e8f5e9; -} - -.sec-stat-num { - display: block; - font-size: 28px; +.sec-summary-title { + font-size: 18px; font-weight: 700; + color: var(--color-text); + margin-bottom: 4px; } -.sec-stat-label { - display: block; +.sec-summary-desc { + font-size: 14px; + color: var(--color-text-muted); + margin-bottom: 16px; +} + +/* ── Progress bars ────────────────────────────────────────────────── */ +.sec-bars { + display: flex; + flex-direction: column; + gap: 8px; +} + +.sec-bar-row { + display: flex; + align-items: center; + gap: 10px; +} + +.sec-bar-label { + font-size: 13px; + color: var(--color-text); + width: 130px; + flex-shrink: 0; +} + +.sec-bar-track { + flex: 1; + height: 6px; + background: #e5e7eb; + border-radius: 3px; + overflow: hidden; +} + +.sec-bar-fill { + height: 100%; + border-radius: 3px; + transition: width 0.4s ease; +} + +.sec-bar-pct { font-size: 12px; color: var(--color-text-muted); - margin-top: 2px; + width: 36px; + text-align: right; + flex-shrink: 0; +} + +/* ── Section cards grid ───────────────────────────────────────────── */ +.sec-sections-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; } .sec-section { @@ -1047,23 +1108,34 @@ ul { overflow: hidden; } +/* HIBP card spans full width above grid */ +.sec-section + .sec-sections-grid { + margin-top: 0; +} + .sec-section-header { display: flex; align-items: flex-start; gap: 12px; padding: 16px 20px; border-bottom: 1px solid var(--color-border); - background: var(--color-bg); } -.sec-section-icon { - font-size: 20px; +.sec-section-icon-wrap { + width: 36px; + height: 36px; + border-radius: 9px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; flex-shrink: 0; } .sec-section-title { font-weight: 600; font-size: 15px; + color: var(--color-text); } .sec-section-desc { @@ -1072,6 +1144,14 @@ ul { margin-top: 2px; } +.sec-section-issue { + color: var(--color-text-muted); +} + +.sec-section-issue--danger { + color: #c62828; +} + .sec-item-list { padding: 0; } @@ -1079,7 +1159,7 @@ ul { .sec-item { display: flex; align-items: center; - gap: 12px; + gap: 10px; padding: 10px 20px; border-bottom: 1px solid var(--color-border); } @@ -1088,15 +1168,46 @@ ul { border-bottom: none; } +.sec-item-icon { + font-size: 18px; + flex-shrink: 0; + width: 24px; + text-align: center; +} + .sec-item-name { font-weight: 500; flex: 1; + font-size: 14px; } .sec-item-sub { font-size: 12px; color: var(--color-text-muted); - flex: 1; +} + +/* Strength / status badge inside sec-item */ +.sec-item-badge { + font-size: 11px; + font-weight: 600; + padding: 2px 8px; + border-radius: 999px; + flex-shrink: 0; +} + +.sec-item-badge.strength-strong { + background: #d1fae5; + color: #065f46; +} + +.sec-item-badge.strength-medium { + background: #fef3c7; + color: #92400e; +} + +.sec-item-badge.strength-weak { + background: #fee2e2; + color: #991b1b; } /* ── Item Detail Modal ───────────────────────────────────────────── */ @@ -1724,9 +1835,8 @@ body.is-mobile .app-layout { gap: 12px; } - .sec-stats { - flex-wrap: wrap; - gap: 10px; + .sec-sections-grid { + grid-template-columns: 1fr; } .modal { @@ -1850,24 +1960,23 @@ body.is-mobile .security-summary { flex-wrap: wrap; } -body.is-mobile .sec-score { - font-size: 40px; +body.is-mobile .sec-ring-wrap { + width: 80px; + height: 80px; } -body.is-mobile .sec-stats { - gap: 8px; - flex-wrap: wrap; -} - -body.is-mobile .sec-stat { - padding: 8px 12px; - min-width: 68px; -} - -body.is-mobile .sec-stat-num { +body.is-mobile .sec-ring-score { font-size: 22px; } +body.is-mobile .sec-sections-grid { + grid-template-columns: 1fr; +} + +body.is-mobile #vault-list { + grid-template-columns: 1fr; +} + body.is-mobile .detail-field { flex-direction: column; gap: 4px; @@ -2692,4 +2801,4 @@ html.sidebar-open { .pw-history-reveal:hover, .pw-history-restore:hover { color: var(--text); -} +} \ No newline at end of file diff --git a/app/static/js/vault.js b/app/static/js/vault.js index efe2c9f..61f977d 100644 --- a/app/static/js/vault.js +++ b/app/static/js/vault.js @@ -520,22 +520,29 @@ const Vault = (() => { }); } + // ── Helper: password strength label & CSS class ────────────────────────── + function _passwordStrength(password) { + if (!password) return null; + const charTypes = [/[A-Z]/, /[a-z]/, /[0-9]/, /[^A-Za-z0-9]/].filter( + (r) => r.test(password), + ).length; + if (password.length < 8 || charTypes < 2) return { label: "Weak", cls: "strength-weak" }; + if (password.length < 12 || charTypes < 3) return { label: "Medium", cls: "strength-medium" }; + return { label: "Strong", cls: "strength-strong" }; + } + + // ── Helper: initials avatar for a vault item name ────────────────────────── + function _itemInitials(name) { + const words = name.trim().split(/\s+/); + if (words.length >= 2) return (words[0][0] + words[1][0]).toUpperCase(); + return name.slice(0, 2).toUpperCase(); + } + function createItemElement(item) { const li = document.createElement("li"); li.className = "vault-item"; li.dataset.id = item.id; - const iconMap = { - password: "🔑", - note: "📝", - card: "💳", - bank: "🏦", - address: "🏠", - ssn: "🪪", - passkey: "🔐", - }; - const icon = iconMap[item.item_type] || "🔑"; - const itemTags = item.plain?.tags || []; let subText = ""; if (item.plain) { @@ -589,14 +596,27 @@ const Vault = (() => { .map((t) => `${escHtml(t)}`) .join(""); + // Strength pill — only for password type + const strength = + item.item_type === "password" + ? _passwordStrength(item.plain?.password) + : null; + const strengthPill = strength + ? `${strength.label}` + : ""; + + // Initials avatar + const initials = _itemInitials(item.name); + li.innerHTML = ` -
+