feat: MFA tools, single-instance, tray polish, prefs persistence
Session highlights:
- feat(prefs): DPAPI-backed key/value store (PM.UserPrefs) — fixes
rememberedUsername being lost across reboots due to the random
ephemeral HTTP port changing the localStorage origin every launch.
Bridge cmd://prefs/{get,set} round-trips through Delphi.
- feat(tray): icon visible from startup (NIM_ADD at constructor, not
at first minimize). Tray context menu themed via uxtheme!135
SetPreferredAppMode so it follows the app's dark/light setting.
- feat(single-instance): named mutex + RegisterWindowMessage broadcast.
Second launch posts WM_PMSHOW to HWND_BROADCAST and exits; the
running bridge restores the window from tray. Mutex lives in Local\
namespace so distinct Windows users can still each run one.
- feat(mfa): Authenticator sidebar view (live TOTP codes for every
entry with a secret) + standalone TOTP generator modal (paste
base32 / otpauth:// URI, or generate a random 20-byte secret).
- feat(sidebar): Folders / Tags / Tools sections collapsible with
chevron toggle. Badge counts stay visible when collapsed. State
persisted in settings_json (synced across devices).
- feat(autofill): hotkey when vault is locked now restores the app
and focuses the master password input instead of no-op'ing
silently. Cleaner UX for the common "I hit Ctrl+Shift+L but the
vault was locked" path.
- feat(quick-unlock): when enabled, skip lockVault on Windows lock /
sleep. Rationale: the DPAPI blob already gates access via the
Windows account, so re-locking on top of the OS lock is redundant.
Idle auto-lock still fires (separate opt-in).
- fix(quick-unlock): re-sync state.quickUnlockEnabled from DPAPI
source-of-truth at boot, instead of trusting (now-volatile)
localStorage.
- docs: CLAUDE.md updated with all new modules, bridge commands,
and the port-ephemeral pitfall.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
:root {
|
||||
--bg: #0e1015; --bg2: #191d27; --text: #e2e6ea; --text2: #7f8a98;
|
||||
--accent: #6b7280; --accent-rgb: 107,114,128;
|
||||
--danger: #ef4444; --success: #22c55e; --warning: #f59e0b;
|
||||
--border: rgba(255,255,255,0.12); --card: #212734;
|
||||
--input: #11141a; --vault-bg: rgba(255,255,255,0.06);
|
||||
}
|
||||
.light {
|
||||
--bg: #eef0f4; --bg2: #e2e5eb; --text: #161b24; --text2: #5f6a7a;
|
||||
--accent: #6b7280; --accent-rgb: 107,114,128;
|
||||
--card: #ffffff; --input: #ffffff;
|
||||
--border: rgba(0,0,0,0.16); --vault-bg: rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body {
|
||||
background: linear-gradient(145deg, var(--bg) 0%, var(--bg2) 100%);
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
min-height: 100vh; display: flex; justify-content: center;
|
||||
align-items: flex-start; padding: 1.2rem; color: var(--text);
|
||||
transition: background 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.toast-container { position:fixed; top:1rem; right:1rem; z-index:9999; display:flex; flex-direction:column; gap:0.5rem; }
|
||||
.toast { padding:0.7rem 1.2rem; border-radius:0.8rem; font-size:0.85rem; animation:slideIn 0.3s ease; color:#fff; }
|
||||
.toast.error { background:var(--danger); }
|
||||
.toast.success { background:var(--success); }
|
||||
.toast { display:flex; align-items:center; gap:0.6rem; max-width:400px; }
|
||||
.toast-action { background:rgba(0,0,0,0.3); border:1px solid rgba(255,255,255,0.4); color:#fff; font-weight:700; font-size:0.8rem; padding:0.25rem 0.7rem; border-radius:1rem; cursor:pointer; white-space:nowrap; flex-shrink:0; transition:0.15s; }
|
||||
.toast-action:hover { background:rgba(0,0,0,0.5); }
|
||||
@keyframes slideIn { from { transform:translateX(100%); opacity:0; } to { transform:translateX(0); opacity:1; } }
|
||||
|
||||
.vault {
|
||||
width:96%; max-width:1700px; background:var(--vault-bg);
|
||||
backdrop-filter:blur(20px); border:1px solid var(--border);
|
||||
border-radius:2rem; padding:1.8rem; margin:0.5rem auto;
|
||||
box-shadow:0 30px 50px rgba(0,0,0,0.4);
|
||||
user-select:none; -webkit-user-drag:none;
|
||||
}
|
||||
h1 { font-size:2rem; margin-bottom:0.6rem; display:flex; align-items:center; gap:0.6rem; flex-wrap:wrap; }
|
||||
h1 span { background:var(--accent); padding:0.2rem 0.7rem; border-radius:3rem; font-size:0.8rem; color:#fff; }
|
||||
|
||||
/* Auth */
|
||||
.auth-section { background:rgba(0,0,0,0.25); border-radius:1.5rem; padding:1.3rem; margin-bottom:1rem; border:1px solid var(--border); }
|
||||
.auth-tabs { display:flex; gap:1rem; margin-bottom:0.8rem; }
|
||||
.auth-tab { background:none; border:none; color:var(--text2); padding:0.4rem 0.8rem; cursor:pointer; border-bottom:2px solid transparent; font-size:0.9rem; }
|
||||
.auth-tab.active { color:var(--accent); border-bottom-color:var(--accent); }
|
||||
|
||||
input, select, textarea {
|
||||
flex:1; min-width:130px; background:var(--input); border:1px solid #2d3748;
|
||||
padding:0.65rem 1rem; border-radius:2rem; color:var(--text); font-size:0.85rem;
|
||||
outline:none; font-family:inherit;
|
||||
}
|
||||
input:focus, select:focus { border-color:var(--accent); }
|
||||
|
||||
.btn {
|
||||
background:linear-gradient(135deg, #6b7280, #4b5563); border:none; color:#fff;
|
||||
font-weight:600; padding:0.65rem 1.3rem; border-radius:2rem; cursor:pointer;
|
||||
font-size:0.85rem; transition:0.2s; white-space:nowrap;
|
||||
}
|
||||
.btn:hover { filter:brightness(1.15); transform:scale(1.02); }
|
||||
.btn:disabled { opacity:0.5; cursor:not-allowed; transform:none; }
|
||||
.btn-outline { background:transparent; border:1px solid #475569; color:var(--text2); }
|
||||
.btn-sm { padding:0.35rem 0.9rem; font-size:0.75rem; }
|
||||
.btn-xs { padding:0.2rem 0.5rem; font-size:0.7rem; }
|
||||
.btn-danger { background:var(--danger); }
|
||||
|
||||
.input-group { display:flex; gap:0.5rem; margin:0.7rem 0; flex-wrap:wrap; align-items:center; }
|
||||
|
||||
/* Toolbar */
|
||||
.toolbar { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.5rem; margin-bottom:0.7rem; }
|
||||
.view-dropdown { position:relative; }
|
||||
.view-dropdown-menu { position:absolute; top:100%; right:0; margin-top:4px; min-width:155px; background:var(--bg2); border:1px solid var(--border); border-radius:0.6rem; padding:0.3rem; box-shadow:0 8px 24px rgba(0,0,0,0.3); z-index:100; display:flex; flex-direction:column; gap:2px; }
|
||||
.view-opt { background:none; border:none; color:var(--text); padding:0.45rem 0.8rem; border-radius:0.4rem; cursor:pointer; font-size:0.8rem; text-align:left; transition:0.12s; white-space:nowrap; }
|
||||
.view-opt:hover { background:var(--accent); color:#fff; }
|
||||
.view-opt.active { background:var(--accent); color:#fff; font-weight:600; }
|
||||
|
||||
.toggles-row { display:flex; gap:1.2rem; align-items:center; flex-wrap:wrap; }
|
||||
.toggle-item { display:flex; align-items:center; gap:0.4rem; font-size:0.75rem; color:var(--text2); }
|
||||
.toggle-switch { position:relative; width:36px; height:20px; background:#334155; border-radius:10px; cursor:pointer; transition:0.2s; }
|
||||
.toggle-switch.active { background:var(--accent); }
|
||||
.toggle-switch::after { content:''; position:absolute; top:2px; left:2px; width:16px; height:16px; background:#fff; border-radius:50%; transition:0.2s; }
|
||||
.toggle-switch.active::after { left:18px; }
|
||||
|
||||
.status-badge { background:var(--bg2); padding:0.25rem 0.8rem; border-radius:2rem; font-size:0.75rem; white-space:nowrap; }
|
||||
.hidden { display:none !important; }
|
||||
|
||||
/* Strength */
|
||||
.strength-bar { height:4px; border-radius:2px; transition:0.3s; margin-top:0.2rem; }
|
||||
.s0 { background:var(--danger); width:20%; }
|
||||
.s1 { background:var(--warning); width:40%; }
|
||||
.s2 { background:#eab308; width:60%; }
|
||||
.s3 { background:#84cc16; width:80%; }
|
||||
.s4 { background:var(--success); width:100%; }
|
||||
|
||||
/* ========== SEARCH BOX WITH CLEAR BUTTON (FIXED) ========== */
|
||||
.search-box {
|
||||
position: relative;
|
||||
width: 240px; /* fixed width – adjust if needed */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-right: 30px; /* space for the ✕ button */
|
||||
}
|
||||
.clear-search-btn {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text2);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
padding: 6px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Folders */
|
||||
.folders-bar { display:flex; gap:0.4rem; margin-bottom:0.8rem; flex-wrap:wrap; align-items:center; padding:0.4rem 0.6rem; background:rgba(0,0,0,0.2); border-radius:1rem; }
|
||||
.folder-chip { background:var(--bg2); border:1px solid var(--border); color:var(--text2); padding:0.3rem 0.8rem; border-radius:2rem; cursor:pointer; font-size:0.78rem; transition:0.2s; white-space:nowrap; }
|
||||
.folder-chip:hover { background:var(--chip-color, var(--accent)); color:#fff; border-color:var(--chip-color, var(--accent)); }
|
||||
.folder-chip.active { background:var(--chip-color, var(--accent)); color:#fff; border-color:var(--chip-color, var(--accent)); }
|
||||
.folder-chip.drag-over { border-color:var(--accent)!important; box-shadow:0 0 12px rgba(var(--accent-rgb),0.5); transform:scale(1.05); }
|
||||
.pw-display.pw-hover { cursor:pointer; }
|
||||
.folder-count { background:rgba(0,0,0,0.3); padding:0.1rem 0.4rem; border-radius:1rem; margin-left:0.3rem; font-size:0.7rem; }
|
||||
.folder-delete-btn { background:transparent; border:none; color:var(--danger); cursor:pointer; font-size:0.7rem; margin-left:0.2rem; opacity:0.7; }
|
||||
.folder-delete-btn:hover { opacity:1; }
|
||||
.folder-add-btn { background:transparent; border:1px dashed #475569; color:var(--text2); padding:0.3rem 0.6rem; border-radius:2rem; cursor:pointer; font-size:0.75rem; transition:0.2s; }
|
||||
.folder-add-btn:hover { border-color:var(--accent); color:var(--accent); }
|
||||
|
||||
.light .entry-card, .light .entry-row, .light .entry-compact { box-shadow:0 1px 4px rgba(0,0,0,0.08); }
|
||||
#addFolderSelect { min-width:110px; max-width:150px; background:var(--input); border:1px solid #2d3748; color:var(--text); padding:0.5rem 0.8rem; border-radius:2rem; font-size:0.8rem; cursor:pointer; }
|
||||
|
||||
/* Entries */
|
||||
#entriesContainer { user-select:none; -webkit-user-drag:none; }
|
||||
#entriesContainer.grid-view { display:grid; grid-template-columns: repeat(auto-fill, minmax(180px,1fr)); gap:0.6rem; }
|
||||
#entriesContainer.list-view { display:flex; flex-direction:column; gap:0.4rem; }
|
||||
#entriesContainer.compact-view { display:flex; flex-direction:column; gap:0.2rem; }
|
||||
#entriesContainer.table-view { overflow-x:auto; }
|
||||
#entriesContainer.table-view table { width:100%; border-collapse:collapse; }
|
||||
#entriesContainer.table-view th { text-align:left; padding:0.4rem 0.6rem; color:var(--text2); font-size:0.75rem; border-bottom:1px solid var(--border); }
|
||||
#entriesContainer.table-view td { padding:0.4rem 0.6rem; font-size:0.8rem; border-bottom:1px solid rgba(255,255,255,0.03); }
|
||||
|
||||
.entry-card, .entry-row, .entry-compact, .table-row-drag { cursor:pointer; user-select:none; }
|
||||
.entry-card.drag-over, .entry-row.drag-over, .entry-compact.drag-over, .table-row-drag.drag-over { border-color:var(--accent)!important; box-shadow:0 0 15px rgba(var(--accent-rgb),0.3); }
|
||||
.entry-card.selected { border-color:var(--accent)!important; box-shadow:0 0 0 2px var(--accent),0 0 18px rgba(var(--accent-rgb),0.35); background:rgba(var(--accent-rgb),0.18); }
|
||||
.entry-row.selected, .entry-compact.selected { border-color:var(--accent)!important; box-shadow:0 0 0 2px var(--accent),0 0 18px rgba(var(--accent-rgb),0.35); background:rgba(var(--accent-rgb),0.14); border-left:3px solid var(--accent); }
|
||||
.table-row-drag.selected td { background:rgba(var(--accent-rgb),0.14)!important; box-shadow:inset 0 0 0 1px var(--accent); border-bottom:1px solid var(--accent); }
|
||||
.entry-card { background:var(--card); border:1px solid var(--border); border-radius:0.8rem; padding:0.9rem 2.8rem 0.9rem 0.9rem; transition:0.2s; position:relative; word-break:break-word; box-shadow:0 2px 6px rgba(0,0,0,0.2); }
|
||||
.entry-card:hover { border-color:rgba(255,255,255,0.15); transform:translateY(-2px); box-shadow:0 4px 12px rgba(0,0,0,0.3); }
|
||||
.card-site { font-weight:700; font-size:0.9rem; color:var(--text); margin-bottom:0.2rem; }
|
||||
.card-user { color:var(--text2); font-size:0.75rem; margin-bottom:0.3rem; }
|
||||
.card-folder { font-size:0.65rem; color:var(--accent); margin-bottom:0.3rem; background:rgba(var(--accent-rgb),0.15); display:inline-block; padding:0.1rem 0.5rem; border-radius:1rem; }
|
||||
.card-password { background:var(--bg2); padding:0.3rem 0.5rem; border-radius:0.6rem; display:flex; align-items:center; justify-content:space-between; font-family:monospace; font-size:0.75rem; gap:0.2rem; }
|
||||
#entriesContainer.card-view .entry-card { padding:1.2rem 3rem 1.2rem 1.2rem; }
|
||||
#entriesContainer.card-view .card-site { font-size:1.05rem; }
|
||||
#entriesContainer.card-view .card-password { font-size:0.9rem; }
|
||||
#entriesContainer.card-view .entry-card:hover { transform:translateY(-3px); }
|
||||
.entry-row { background:var(--card); border:1px solid var(--border); border-radius:0.8rem; padding:0.7rem 2.8rem 0.7rem 0.9rem; display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:0.4rem; position:relative; box-shadow:0 2px 6px rgba(0,0,0,0.15); }
|
||||
.entry-compact { display:flex; align-items:center; gap:0.5rem; padding:0.35rem 2.8rem 0.35rem 0.7rem; background:var(--card); border-radius:0.5rem; border:1px solid var(--border); font-size:0.8rem; position:relative; box-shadow:0 1px 4px rgba(0,0,0,0.12); }
|
||||
.action-btns { position:absolute; top:4px; right:6px; display:flex; gap:4px; z-index:1; }
|
||||
.delete-btn { width:20px; height:20px; background:transparent; color:var(--danger); border:none; cursor:pointer; font-size:0.9rem; font-weight:700; display:flex; align-items:center; justify-content:center; }
|
||||
.delete-btn:hover { color:#fff; transform:scale(1.2); }
|
||||
.light .delete-btn:hover { color:#000!important; }
|
||||
.edit-btn { width:20px; height:20px; background:transparent; color:var(--accent); border:none; cursor:pointer; font-size:0.75rem; display:flex; align-items:center; justify-content:center; }
|
||||
.edit-btn:hover { color:#fff; transform:scale(1.2); }
|
||||
.light .edit-btn:hover { color:#000!important; }
|
||||
.star-btn { width:20px; height:20px; background:transparent; border:none; cursor:pointer; font-size:0.85rem; display:flex; align-items:center; justify-content:center; color:var(--text2); }
|
||||
.star-btn:hover { transform:scale(1.3); }
|
||||
.entry-card.favorite { border-color:rgba(255,200,0,0.3); background:rgba(255,200,0,0.05); }
|
||||
.entry-row.favorite { border-color:rgba(255,200,0,0.3); background:rgba(255,200,0,0.05); }
|
||||
.entry-compact.favorite { border-color:rgba(255,200,0,0.3); background:rgba(255,200,0,0.05); }
|
||||
.table-row-drag.favorite td { background:rgba(255,200,0,0.05); }
|
||||
|
||||
/* Grouped view */
|
||||
#entriesContainer.grouped-view { display:flex; flex-direction:column; gap:0.15rem; }
|
||||
.grouped-header { position:sticky; top:0; z-index:2; background:var(--bg); padding:0.55rem 0.8rem; border-radius:0.5rem; font-weight:600; font-size:0.9rem; color:var(--accent); display:flex; align-items:center; gap:0.5rem; border-bottom:2px solid var(--accent); margin-top:0.4rem; }
|
||||
.grouped-header:first-child { margin-top:0; }
|
||||
#entriesContainer.grouped-view .entry-row { border-left:3px solid transparent; transition:0.15s; padding-left:1rem; }
|
||||
#entriesContainer.grouped-view .entry-row:hover { border-left-color:var(--accent); }
|
||||
|
||||
/* Detail view */
|
||||
#entriesContainer.detail-view { display:flex; flex-direction:column; gap:1rem; }
|
||||
.detail-nav { display:flex; align-items:center; justify-content:center; gap:1rem; padding:0.5rem 0; position:sticky; top:0; z-index:2; background:var(--bg); }
|
||||
.detail-nav button { background:var(--bg2); border:1px solid var(--border); color:var(--text); padding:0.4rem 1rem; border-radius:0.5rem; cursor:pointer; transition:0.15s; font-size:0.85rem; }
|
||||
.detail-nav button:hover { border-color:var(--accent); color:var(--accent); }
|
||||
.detail-nav button:disabled { opacity:0.4; cursor:default; }
|
||||
.detail-nav .detail-counter { font-size:0.8rem; color:var(--text2); min-width:80px; text-align:center; }
|
||||
.detail-card { background:var(--bg2); border:1px solid var(--border); border-radius:1rem; padding:1.5rem; display:flex; flex-direction:column; gap:0.9rem; max-width:520px; margin:0 auto; width:100%; }
|
||||
.detail-field { display:flex; flex-direction:column; gap:0.15rem; }
|
||||
.detail-label { font-size:0.7rem; color:var(--text2); text-transform:uppercase; letter-spacing:0.5px; }
|
||||
.detail-value { font-size:1.2rem; word-break:break-all; }
|
||||
.detail-value.pw-display { font-family:monospace; letter-spacing:2px; font-size:1.3rem; cursor:default; }
|
||||
.detail-folder { display:flex; gap:0.5rem; align-items:center; }
|
||||
.detail-actions { display:flex; gap:0.5rem; margin-top:0.3rem; }
|
||||
.detail-actions button { flex:1; padding:0.5rem; border-radius:0.5rem; border:1px solid var(--border); background:var(--bg); cursor:pointer; transition:0.15s; font-size:0.85rem; }
|
||||
.detail-actions button:hover { border-color:var(--accent); background:var(--bg2); }
|
||||
|
||||
|
||||
.entry-info { display:flex; gap:0.5rem; align-items:center; flex-wrap:wrap; flex:1; }
|
||||
.entry-site { font-weight:700; color:var(--text); }
|
||||
.entry-user { color:var(--text2); }
|
||||
.entry-folder { font-size:0.7rem; color:var(--accent); background:rgba(var(--accent-rgb),0.15); padding:0.1rem 0.5rem; border-radius:1rem; }
|
||||
.password-field { display:flex; align-items:center; gap:0.3rem; background:var(--bg2); padding:0.2rem 0.5rem; border-radius:2rem; }
|
||||
.password-text { font-family:monospace; color:var(--text2); font-size:0.8rem; }
|
||||
.icon-btn { background:none; border:1px solid #475569; color:var(--text2); border-radius:2rem; padding:0.2rem 0.5rem; font-size:0.65rem; cursor:pointer; }
|
||||
.icon-btn:hover { background:rgba(var(--accent-rgb),0.2); }
|
||||
|
||||
/* Settings Dropdown */
|
||||
.settings-dropdown { position:relative; display:inline-block; }
|
||||
.settings-menu { position:absolute; top:100%; right:0; background:var(--bg2); border:1px solid var(--border); border-radius:0.8rem; padding:0.5rem; min-width:200px; z-index:1000; box-shadow:0 10px 25px rgba(0,0,0,0.4); }
|
||||
.settings-item { display:flex; justify-content:space-between; align-items:center; padding:0.4rem 0.6rem; border-radius:0.5rem; font-size:0.8rem; color:var(--text2); cursor:pointer; }
|
||||
.settings-item:hover { background:rgba(255,255,255,0.05); }
|
||||
.settings-item select { background:var(--input); border:1px solid #334155; color:var(--text); padding:0.2rem 0.5rem; border-radius:1rem; font-size:0.75rem; }
|
||||
|
||||
/* Trash */
|
||||
.trash-badge { background:var(--danger); color:#fff; padding:0.15rem 0.5rem; border-radius:1rem; font-size:0.65rem; margin-left:0.3rem; }
|
||||
.trash-info { font-size:0.7rem; color:var(--text2); margin-top:0.2rem; }
|
||||
.restore-btn { background:none; color:#fff; border:none; padding:0.2rem 0.6rem; border-radius:1.5rem; cursor:pointer; font-size:0.7rem; }
|
||||
.restore-btn:hover { filter:brightness(1.2); }
|
||||
.empty-trash-btn { background:var(--danger); color:#fff; border:none; padding:0.3rem 0.8rem; border-radius:1.5rem; cursor:pointer; font-size:0.75rem; }
|
||||
.empty-trash-btn:hover { filter:brightness(1.2); }
|
||||
|
||||
/* Edit Modal */
|
||||
.edit-modal { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); display:none; justify-content:center; align-items:center; z-index:1001; }
|
||||
.edit-modal.show { display:flex; }
|
||||
.edit-box { background:var(--bg2); border-radius:1.5rem; padding:1.5rem; min-width:380px; max-width:90%; }
|
||||
.edit-box h3 { margin-bottom:1rem; color:var(--text); }
|
||||
.edit-box label { color:var(--text2); font-size:0.8rem; display:block; margin-bottom:0.2rem; }
|
||||
.edit-box input, .edit-box select { width:100%; margin-bottom:0.5rem; }
|
||||
|
||||
/* Custom modals */
|
||||
.custom-modal-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.6); display:flex; justify-content:center; align-items:center; z-index:10001; display:none; }
|
||||
.custom-modal-overlay.show { display:flex; }
|
||||
.custom-modal { background:var(--bg2); border:1px solid var(--border); border-radius:1.2rem; padding:1.5rem; min-width:300px; max-width:90%; }
|
||||
.custom-modal h3 { margin-bottom:1rem; color:var(--text); }
|
||||
.custom-modal input { width:100%; margin-bottom:1rem; }
|
||||
.custom-modal .modal-actions { display:flex; gap:0.5rem; justify-content:flex-end; }
|
||||
|
||||
/* Confirm popup near element */
|
||||
.custom-confirm { position:fixed; background:var(--bg2); border:1px solid var(--accent); border-radius:0.8rem; padding:0.7rem 1rem; z-index:9999; box-shadow:0 10px 30px rgba(0,0,0,0.5); display:none; font-size:0.8rem; color:var(--text); white-space:nowrap; }
|
||||
.custom-confirm.show { display:block; }
|
||||
.custom-confirm .confirm-text { margin-bottom:0.5rem; }
|
||||
.custom-confirm .confirm-btns { display:flex; gap:0.4rem; }
|
||||
.custom-confirm .confirm-yes { background:var(--danger); color:#fff; border:none; padding:0.3rem 0.8rem; border-radius:1.5rem; cursor:pointer; font-size:0.75rem; }
|
||||
.custom-confirm .confirm-no { background:#334155; color:#fff; border:none; padding:0.3rem 0.8rem; border-radius:1.5rem; cursor:pointer; font-size:0.75rem; }
|
||||
|
||||
/* Zigzag toast */
|
||||
.toast-zigzag { position:fixed; z-index:9998; padding:0.4rem 0.7rem; border-radius:0.5rem; font-size:0.72rem; font-weight:600; pointer-events:none; animation:zigzagUp 1.2s ease-out forwards; white-space:nowrap; }
|
||||
.toast-zigzag.success { background:rgba(34,197,94,0.95); color:#fff; }
|
||||
.toast-zigzag.error { background:rgba(239,68,68,0.95); color:#fff; }
|
||||
@keyframes zigzagUp { 0%{opacity:1;transform:translate(0,0) scale(1)} 20%{opacity:0.9;transform:translate(-10px,-14px) scale(0.9)} 40%{opacity:0.65;transform:translate(12px,-28px) scale(0.75)} 70%{opacity:0.3;transform:translate(-8px,-42px) scale(0.6)} 100%{opacity:0;transform:translate(0,-60px) scale(0.4)} }
|
||||
|
||||
.idle-warning { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:rgba(0,0,0,0.95); color:#fff; padding:2rem; border-radius:2rem; z-index:10000; text-align:center; display:none; }
|
||||
.idle-warning.show { display:block; }
|
||||
|
||||
@media(max-width:700px) {
|
||||
.vault { padding:1rem; border-radius:1.5rem; width:98%; }
|
||||
.entry-row { flex-direction:column; }
|
||||
#entriesContainer.grid-view { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
|
||||
.toolbar { flex-direction:column; }
|
||||
.folders-bar { flex-direction:column; align-items:stretch; }
|
||||
}
|
||||
|
||||
/* Floating action button */
|
||||
.fab {
|
||||
position: fixed; bottom: 2rem; right: 2rem;
|
||||
width: 56px; height: 56px; border-radius: 50%;
|
||||
background: var(--accent); color: #fff; border: none;
|
||||
font-size: 1.8rem; cursor: pointer; box-shadow: 0 8px 20px rgba(0,0,0,0.4);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: 0.2s; z-index: 100;
|
||||
}
|
||||
.fab:hover { transform: scale(1.1); filter: brightness(1.1); }
|
||||
.fab-trash { left: 2rem; right: auto; font-size:1.4rem; background:transparent; box-shadow:none; }
|
||||
.fab-trash.active { box-shadow:0 0 18px rgba(var(--accent-rgb),0.5); }
|
||||
|
||||
/* Generic modal overlay (if you don’t already have it) */
|
||||
.modal-overlay {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.7); display: none;
|
||||
justify-content: center; align-items: center; z-index: 1001;
|
||||
}
|
||||
.modal-overlay.show { display: flex; }
|
||||
|
||||
/* Modal box (used by both edit and add modals) */
|
||||
.modal-box {
|
||||
background: var(--bg2); border-radius: 1.5rem; padding: 1.5rem;
|
||||
min-width: 380px; max-width: 90%;
|
||||
}
|
||||
.modal-box h3 { margin-bottom: 1rem; color: var(--text); }
|
||||
.modal-box label { color: var(--text2); font-size: 0.8rem; display: block; margin-bottom: 0.2rem; }
|
||||
.modal-box input, .modal-box select { width: 100%; margin-bottom: 0.5rem; }
|
||||
/* Batch select */
|
||||
.select-checkbox {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
z-index: 2;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
.batch-actions {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--bg2);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 2rem;
|
||||
padding: 0.5rem 1.5rem;
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.5);
|
||||
font-size: 0.85rem;
|
||||
color: var(--text);
|
||||
}
|
||||
.batch-actions button {
|
||||
font-size: 0.78rem;
|
||||
padding: 0.35rem 0.8rem;
|
||||
}
|
||||
.selected-count {
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
#rectSelect {
|
||||
position:fixed; pointer-events:none; z-index:999;
|
||||
border:1px solid var(--accent);
|
||||
background:rgba(var(--accent-rgb),0.1);
|
||||
display:none;
|
||||
}
|
||||
#trashBtn.drag-over {
|
||||
border-color:var(--danger)!important;
|
||||
box-shadow:0 0 15px rgba(239,68,68,0.4);
|
||||
background:rgba(239,68,68,0.15);
|
||||
}
|
||||
/* Batch confirm modal backdrop */
|
||||
.batch-confirm-overlay { background:rgba(0,0,0,0.3); }
|
||||
+428
-19
@@ -490,6 +490,41 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
.section-toggle {
|
||||
flex: 1;
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
background: none; border: none; padding: 2px 0;
|
||||
font: inherit; color: inherit; text-transform: inherit;
|
||||
letter-spacing: inherit;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
.section-toggle:hover { color: var(--text-dim); }
|
||||
.section-count {
|
||||
min-width: 16px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 9px;
|
||||
background: var(--bg);
|
||||
color: var(--text-dim);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
/* Push everything after the count toward the right of the toggle button
|
||||
so the +/etc. action button stays at the row's edge. */
|
||||
.section-toggle { gap: 6px; }
|
||||
.section-chevron {
|
||||
width: 12px; height: 12px;
|
||||
transition: transform 0.18s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sidebar-section.is-collapsed .section-chevron {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.sidebar-section.is-collapsed .section-body {
|
||||
display: none;
|
||||
}
|
||||
.sidebar-bottom {
|
||||
margin-top: auto;
|
||||
padding-top: 12px;
|
||||
@@ -513,6 +548,13 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
background: var(--bg-glass);
|
||||
backdrop-filter: var(--blur);
|
||||
-webkit-backdrop-filter: var(--blur);
|
||||
position: relative;
|
||||
/* backdrop-filter creates a stacking context, so child z-indexes (e.g.
|
||||
.user-dropdown : 50) are confined here. Without an explicit z-index
|
||||
the topbar lands behind .slideover (z-index 30), clipping the
|
||||
dropdown when an entry detail panel is open. 40 puts the topbar
|
||||
(and its children) above the slideover; modals at 200+ still win. */
|
||||
z-index: 40;
|
||||
}
|
||||
.search { flex: 1; max-width: 480px; position: relative; display: flex; align-items: center; }
|
||||
.search svg {
|
||||
@@ -615,14 +657,6 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
}
|
||||
|
||||
/* Marquee rectangle for rubber-band selection */
|
||||
.marquee {
|
||||
position: fixed;
|
||||
z-index: 5;
|
||||
background: var(--accent-soft);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.entry-card {
|
||||
position: relative;
|
||||
@@ -714,26 +748,55 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
restore + purge (trash view), or kebab (compact mode). Same order slot. */
|
||||
.entry-grid.is-list .entry-fav,
|
||||
.entry-grid.is-list .entry-del,
|
||||
.entry-grid.is-list .entry-dup,
|
||||
.entry-grid.is-list .entry-kebab-wrap,
|
||||
.entry-grid.is-list .entry-head .icon-btn {
|
||||
order: 6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.entry-avatar.is-checkable {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.entry-avatar.is-checkable:hover::after {
|
||||
content: '';
|
||||
position: absolute; inset: 0;
|
||||
/* Selection checkbox overlay — nested INSIDE the avatar (.entry-avatar
|
||||
is position:relative) so it covers the avatar 1:1, no overlap with the
|
||||
avatar's footprint. Hidden by default; appears on card hover OR when
|
||||
the entry is already checked. The avatar's initials stay visible
|
||||
underneath through the checkbox's transparent-then-solid transition. */
|
||||
.entry-check {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid; place-items: center;
|
||||
background: var(--bg);
|
||||
border: 1.5px solid var(--accent);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--accent-soft);
|
||||
border: 2px solid var(--accent);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity var(--t-fast),
|
||||
background var(--t-fast), border-color var(--t-fast);
|
||||
z-index: 2;
|
||||
}
|
||||
.entry-card.is-checked .entry-avatar {
|
||||
.entry-card:hover .entry-check,
|
||||
.entry-check.is-checked {
|
||||
opacity: 1;
|
||||
}
|
||||
.entry-check.is-checked {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
/* Standalone variant used in the table view's check column. The default
|
||||
rule above is absolute+inset:0 so the box sizes itself from its avatar
|
||||
parent (card view). When the checkbox lives on its own in a <td>, no
|
||||
parent provides a size and the box collapses to a thin line. This
|
||||
modifier restores explicit dimensions and always-on visibility. */
|
||||
.entry-check.entry-check-static {
|
||||
position: static;
|
||||
width: 18px; height: 18px;
|
||||
border-radius: 4px;
|
||||
border-width: 1.5px;
|
||||
opacity: 1;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Batch action bar (appears when selection is non-empty) */
|
||||
.batch-bar {
|
||||
@@ -766,6 +829,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
|
||||
.entry-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
||||
.entry-avatar {
|
||||
position: relative; /* anchor for .entry-check overlay */
|
||||
width: 32px; height: 32px;
|
||||
display: grid; place-items: center;
|
||||
background: var(--bg-elev-2);
|
||||
@@ -784,7 +848,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
font-size: 12px;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.entry-fav, .entry-del {
|
||||
.entry-fav, .entry-del, .entry-dup {
|
||||
color: var(--text-faint);
|
||||
background: transparent; border: none;
|
||||
padding: 4px;
|
||||
@@ -793,6 +857,18 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
}
|
||||
.entry-fav.is-on { color: var(--warning); }
|
||||
.entry-fav:hover { color: var(--warning); transform: scale(1.1); }
|
||||
.entry-dup {
|
||||
color: var(--text-dim);
|
||||
opacity: 0;
|
||||
}
|
||||
.entry-card:hover .entry-dup { opacity: 0.8; }
|
||||
.entry-dup:hover {
|
||||
opacity: 1;
|
||||
color: var(--accent);
|
||||
background: var(--accent-soft);
|
||||
border-radius: 6px;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* Delete button — always visible, brightens on hover */
|
||||
.entry-del {
|
||||
@@ -862,6 +938,10 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
animation: modalIn var(--t-fast);
|
||||
}
|
||||
.entry-kebab-menu.is-open { display: block; }
|
||||
/* Lift the card containing an open kebab menu above its siblings so the
|
||||
dropdown isn't clipped by the next card in DOM order. */
|
||||
.entry-card:has(.entry-kebab-menu.is-open) { z-index: 100; }
|
||||
.entry-row:has(.entry-kebab-menu.is-open) { position: relative; z-index: 100; }
|
||||
|
||||
.kebab-item {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
@@ -966,6 +1046,62 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
transition: width 0.8s linear, background 0.2s;
|
||||
}
|
||||
|
||||
/* ---- Authenticator view (TOTP grid) ---------------------- */
|
||||
.entry-grid.is-auth {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.auth-card {
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 16px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, transform 0.05s;
|
||||
}
|
||||
.auth-card:hover { border-color: var(--accent); }
|
||||
.auth-card:active { transform: scale(0.99); }
|
||||
.auth-card-head { margin-bottom: 8px; }
|
||||
.auth-card-title {
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.auth-card-sub {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
margin-top: 2px;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.auth-card-code-row {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.auth-card-code {
|
||||
flex: 1;
|
||||
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
||||
font-size: 26px;
|
||||
letter-spacing: 3px;
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
user-select: all;
|
||||
}
|
||||
.auth-card-bar-wrap {
|
||||
margin-top: 8px;
|
||||
height: 3px;
|
||||
background: var(--bg);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.auth-card-bar {
|
||||
height: 100%;
|
||||
background: var(--accent);
|
||||
width: 100%;
|
||||
transition: width 0.8s linear, background 0.2s;
|
||||
}
|
||||
.auth-card-bar.is-warning { background: #f59e0b; }
|
||||
|
||||
/* ---- 10. SLIDE-OVER -------------------------------------- */
|
||||
|
||||
.slideover {
|
||||
@@ -1276,3 +1412,276 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
.content { padding: 16px; }
|
||||
.slideover { width: 100%; }
|
||||
}
|
||||
|
||||
/* ---- 17. AUTOFILL PICKER -------------------------------- */
|
||||
|
||||
.autofill-pick-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
padding: 10px 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: background var(--t-fast);
|
||||
}
|
||||
.autofill-pick-btn:hover { background: var(--bg-elev-2); }
|
||||
.autofill-pick-site {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
}
|
||||
.autofill-pick-user {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* ---- Hotkey capture buttons (Settings → Autofill) ---- */
|
||||
.hotkey-capture-btn {
|
||||
font-family: ui-monospace, "Cascadia Code", Menlo, monospace;
|
||||
font-size: 11px;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
}
|
||||
.hotkey-capture-btn.is-capturing {
|
||||
background: var(--accent);
|
||||
color: var(--accent-fg, #fff);
|
||||
animation: pulse-capture 1.2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse-capture {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
/* ---- Card display title + subtitle (when entry.title set) ---- */
|
||||
.entry-subtitle {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
margin-top: 1px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ---- Sidebar "(no folder)" pseudo-entry ---- */
|
||||
.nav-item.is-uncategorized {
|
||||
color: var(--text-faint);
|
||||
font-style: italic;
|
||||
}
|
||||
.nav-item.is-uncategorized:hover { color: var(--text-dim); }
|
||||
.nav-item.is-uncategorized.is-active { color: var(--text); font-style: normal; }
|
||||
/* Spacer occupies the same footprint as the SVG folder icon so labels
|
||||
stay vertically aligned across real-folder rows and "(no folder)". */
|
||||
.nav-icon-spacer {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Table view
|
||||
============================================================ */
|
||||
.entry-grid.is-table { display: block; padding: 0; }
|
||||
.entry-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
.entry-table thead th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-faint);
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-elev);
|
||||
user-select: none;
|
||||
position: sticky; top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.entry-table th.is-sortable { cursor: pointer; }
|
||||
.entry-table th.is-sortable:hover { color: var(--text-dim); }
|
||||
.entry-table th.is-active { color: var(--accent); }
|
||||
.entry-table th .sort-arrow {
|
||||
margin-left: 4px;
|
||||
font-size: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.entry-table tbody td {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
color: var(--text);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.entry-row { cursor: pointer; transition: background var(--t-fast); }
|
||||
.entry-row:hover { background: var(--bg-elev); }
|
||||
.entry-row.is-selected { background: var(--accent-soft); }
|
||||
.entry-row.is-checked { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
|
||||
.col-check { width: 36px; }
|
||||
.col-name { min-width: 180px; }
|
||||
.col-name .entry-avatar-sm {
|
||||
width: 22px; height: 22px;
|
||||
display: inline-grid; place-items: center;
|
||||
font-size: 10px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.col-name .cell-name-wrap { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.col-name .fav-dot { color: var(--warning); font-size: 12px; }
|
||||
.col-site, .col-user { color: var(--text-dim); }
|
||||
.col-site { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.col-user { max-width: 200px; }
|
||||
.col-user .icon-btn { margin-left: 4px; opacity: 0; transition: opacity var(--t-fast); }
|
||||
.entry-row:hover .col-user .icon-btn { opacity: 0.7; }
|
||||
.col-folder { color: var(--text-dim); white-space: nowrap; }
|
||||
.col-updated { color: var(--text-faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
||||
.col-actions { width: 80px; text-align: right; white-space: nowrap; }
|
||||
.col-actions .icon-btn { opacity: 0; transition: opacity var(--t-fast); }
|
||||
.entry-row:hover .col-actions .icon-btn { opacity: 0.7; }
|
||||
.col-actions .entry-kebab-wrap { display: inline-block; vertical-align: middle; }
|
||||
|
||||
/* Hide Edge/IE native password reveal eye (we ship our own toggle). */
|
||||
input[type="password"]::-ms-reveal,
|
||||
input[type="password"]::-ms-clear {
|
||||
display: none !important;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* Login screen pw toggle — only one action button, override the
|
||||
2-buttons layout from .input-with-action. */
|
||||
.input-with-action:has(> .icon-btn:only-of-type) input {
|
||||
padding-right: 40px;
|
||||
}
|
||||
.input-with-action:has(> .icon-btn:only-of-type) .icon-btn:only-of-type {
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
/* Custom right-click menu (replaces native Edge contextmenu in inputs). */
|
||||
.custom-ctxmenu {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
min-width: 160px;
|
||||
padding: 4px;
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.custom-ctxmenu-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 7px 12px;
|
||||
font-size: 13px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.custom-ctxmenu-item:hover:not(.is-disabled) { background: var(--bg-elev-2); }
|
||||
.custom-ctxmenu-item.is-disabled { color: var(--text-faint); cursor: default; }
|
||||
|
||||
/* Folder delete X button — visible on row hover only. */
|
||||
.nav-item .folder-delete {
|
||||
margin-left: auto;
|
||||
width: 20px; height: 20px;
|
||||
padding: 0;
|
||||
line-height: 0;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
background: transparent; border: none;
|
||||
color: var(--text-faint);
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity var(--t-fast), color var(--t-fast), background var(--t-fast);
|
||||
cursor: pointer;
|
||||
}
|
||||
.nav-item .folder-delete svg { width: 14px; height: 14px; display: block; }
|
||||
.nav-item:hover .folder-delete { opacity: 0.7; }
|
||||
.nav-item .folder-delete:hover {
|
||||
opacity: 1;
|
||||
color: var(--danger);
|
||||
background: var(--danger-soft);
|
||||
}
|
||||
.nav-item:hover .nav-count { display: none; }
|
||||
|
||||
/* Remember username checkbox row on auth screen. */
|
||||
.remember-row {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
margin: -4px 0 12px;
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.remember-row input[type="checkbox"] {
|
||||
width: 14px; height: 14px;
|
||||
accent-color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Just-added entry feedback — pulse background fades out over 2.5s. */
|
||||
@keyframes entryFlash {
|
||||
0% { background: var(--accent-soft); box-shadow: 0 0 0 2px var(--accent), 0 8px 24px rgba(6,182,212,0.3); }
|
||||
40% { background: var(--accent-soft); box-shadow: 0 0 0 2px var(--accent), 0 8px 24px rgba(6,182,212,0.2); }
|
||||
100% { background: var(--bg-elev); box-shadow: 0 0 0 0 transparent; }
|
||||
}
|
||||
.entry-card.is-flash { animation: entryFlash 2.5s ease-out; }
|
||||
.entry-row.is-flash { animation: entryFlash 2.5s ease-out; }
|
||||
|
||||
/* Pagination footer (list + table views). */
|
||||
.pagination {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 14px 12px 4px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 13px;
|
||||
color: var(--text-dim);
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.pagination-info {
|
||||
margin-right: auto;
|
||||
color: var(--text-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
.pagination-btn {
|
||||
min-width: 32px;
|
||||
padding: 5px 9px;
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
color: var(--text-dim);
|
||||
cursor: pointer;
|
||||
transition: all var(--t-fast);
|
||||
}
|
||||
.pagination-btn:hover:not(.is-active):not(.is-disabled) {
|
||||
border-color: var(--accent);
|
||||
color: var(--text);
|
||||
}
|
||||
.pagination-btn.is-active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: white;
|
||||
cursor: default;
|
||||
}
|
||||
.pagination-btn.is-disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.pagination-ellipsis { padding: 0 4px; color: var(--text-faint); }
|
||||
.pagination-size {
|
||||
margin-left: 8px;
|
||||
padding: 5px 8px;
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
color: var(--text-dim);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user