Files
Password-Manager/css/style.css
T
r-zakarya 3f07c1217b feat(notes): eye toggle to mask the note body in the editor
The note textarea showed its plaintext to anyone glancing at the screen,
unlike every other secret in the editor. An eye button on the NOTE label
masks it with -webkit-text-security (a textarea has no type="password").

The button IS the setting: it flips state.maskNotes, which is synced via
settings_json, so the choice sticks across entries and devices — no extra
Settings row for a one-bit preference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 20:16:06 +01:00

3087 lines
90 KiB
CSS

/* ============================================================
Vault — UI V2 (Linear/Notion design system)
Dark default, cyan/violet accent, glassmorphism subtil.
============================================================ */
/* ---- 1. RESET & TOKENS ----------------------------------- */
*,*::before,*::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text);
background: var(--bg);
-webkit-font-smoothing: antialiased;
overflow: hidden;
}
button { font-family: inherit; cursor: pointer; }
input, select, textarea { font-family: inherit; font-size: inherit; }
kbd {
font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
font-size: 11px;
padding: 2px 6px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg-elev);
color: var(--text-dim);
}
:root, [data-theme="dark"] {
/* Slightly lighter surfaces + warmer tones, brighter text-dim/faint
and more visible borders. Less "black hole" feel. */
--bg: #15151c;
--bg-elev: #1d1d26;
--bg-elev-2: #292932;
--bg-elev-3: #34343f; /* one step above elev-2 — hover / raised bits */
--bg-glass: rgba(29,29,38,0.78);
--border: #353541;
--border-soft: #25252e;
--text: #f1f1f5;
--text-dim: #b8b8c4;
--text-faint: #82828f;
--accent: #06b6d4;
--accent-hover: #22d3ee;
--accent-soft: rgba(6,182,212,0.12);
--accent-fg: #ffffff; /* text/icon colour on an accent background */
--accent-2: #a78bfa;
--danger: #f43f5e;
--danger-soft: rgba(244,63,94,0.12);
--success: #10b981;
--warning: #f59e0b;
--shadow: 0 8px 24px rgba(0,0,0,0.4);
--shadow-lg: 0 24px 48px rgba(0,0,0,0.55);
--blur: saturate(140%) blur(20px);
--radius: 12px;
--radius-sm: 8px;
--radius-lg: 16px;
--t-fast: 120ms cubic-bezier(0.16,1,0.3,1);
--t-base: 200ms cubic-bezier(0.16,1,0.3,1);
}
[data-theme="light"] {
--bg: #f7f7f5;
--bg-elev: #ffffff;
--bg-elev-2: #efefec;
--bg-elev-3: #e3e3dd; /* one step above elev-2 — hover / raised bits */
--bg-glass: rgba(255,255,255,0.92);
--border: #dcdcd7;
--border-soft: #ebebe6;
--text: #1a1a1f;
--text-dim: #57575e;
--text-faint: #8e8e96;
--accent: #0891b2; /* cyan-600 — darker for better contrast on white */
--accent-hover: #0e7490;
--accent-soft: rgba(8,145,178,0.10);
--accent-2: #7c3aed;
--shadow: 0 4px 16px rgba(0,0,0,0.06);
--shadow-lg: 0 16px 36px rgba(0,0,0,0.08);
--blur: saturate(160%) blur(16px);
}
[data-theme="light"] .auth-screen {
background:
radial-gradient(ellipse at top, rgba(8,145,178,0.08), transparent 50%),
radial-gradient(ellipse at bottom right, rgba(124,58,237,0.06), transparent 50%),
var(--bg);
}
svg { width: 16px; height: 16px; flex-shrink: 0; }
/* ---- 2. UTILITIES ---------------------------------------- */
.is-hidden { display: none !important; }
.hint { color: var(--text-faint); font-weight: 400; font-size: 12px; margin-left: 4px; }
/* ---- 3. BUTTONS ------------------------------------------ */
.btn {
display: inline-flex; align-items: center; justify-content: center;
gap: 6px;
padding: 8px 14px;
border: 1px solid transparent;
border-radius: var(--radius-sm);
font-size: 13px; font-weight: 500; line-height: 1;
transition: all var(--t-fast);
white-space: nowrap;
}
.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: var(--accent-hover); }
.btn.is-danger { background: var(--danger); color: white; }
.btn.is-danger:hover { background: #e11d48; }
.btn-ghost {
background: transparent;
color: var(--text-dim);
border-color: var(--border);
}
.btn-ghost:hover { background: var(--bg-elev-2); color: var(--text); }
.btn-block { width: 100%; }
.btn-sm { padding: 6px 10px; font-size: 12px; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-link {
background: transparent;
color: var(--text-dim);
border-color: transparent;
text-decoration: underline;
text-underline-offset: 2px;
}
.btn-link:hover { color: var(--accent); background: transparent; }
.icon-btn {
display: inline-flex; align-items: center; justify-content: center;
width: 32px; height: 32px;
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
color: var(--text-dim);
transition: all var(--t-fast);
}
.icon-btn:hover { background: var(--bg-elev-2); color: var(--text); }
.icon-btn-sm { width: 24px; height: 24px; }
.icon-btn-sm svg { width: 14px; height: 14px; }
/* ---- 4. FORM CONTROLS ------------------------------------ */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field > span {
font-size: 12px; font-weight: 500; color: var(--text-dim);
display: flex; align-items: center;
}
.field input[type="text"],
.field input[type="password"],
.field input[type="search"],
.field input[type="email"],
.field select {
width: 100%;
padding: 10px 12px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
transition: all var(--t-fast);
}
.field input:focus, .field select:focus {
outline: none;
border-color: var(--accent);
background: var(--bg);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.input-with-action { position: relative; display: flex; align-items: center; gap: 4px; }
.input-with-action input { padding-right: 80px; }
.input-with-action .icon-btn {
position: absolute;
top: 50%; transform: translateY(-50%);
}
.input-with-action .icon-btn:nth-of-type(1) { right: 38px; }
.input-with-action .icon-btn:nth-of-type(2) { right: 4px; }
.strength-bar {
height: 4px;
background: var(--border);
border-radius: 2px;
position: relative;
overflow: hidden;
margin-top: 4px;
}
.strength-bar::after {
content: '';
position: absolute; inset: 0;
width: var(--strength, 0%);
background: linear-gradient(90deg, var(--danger), var(--warning) 50%, var(--success));
transition: width var(--t-base);
}
input[type="range"] {
-webkit-appearance: none; appearance: none;
width: 100%; height: 4px;
background: var(--border);
border-radius: 2px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 16px; height: 16px;
background: var(--accent);
border-radius: 50%;
cursor: pointer;
}
.check {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 10px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: 12px;
cursor: pointer;
user-select: none;
transition: all var(--t-fast);
}
.check:has(input:checked) {
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent);
}
.check input { display: none; }
/* ---- 5. AUTH SCREEN -------------------------------------- */
.auth-screen {
position: fixed; inset: 0;
display: grid; place-items: center;
padding: 24px;
background:
radial-gradient(ellipse at top, var(--accent-soft), transparent 50%),
radial-gradient(ellipse at bottom right, rgba(167,139,250,0.08), transparent 50%),
var(--bg);
}
.auth-card {
width: 100%; max-width: 400px;
padding: 32px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
}
.auth-brand { text-align: center; margin-bottom: 24px; }
.auth-brand-icon {
width: 40px; height: 40px;
padding: 10px;
background: var(--accent-soft);
color: var(--accent);
border-radius: var(--radius);
margin-bottom: 12px;
}
.auth-brand h1 { font-size: 20px; font-weight: 600; margin: 0 0 4px; }
.auth-brand p { color: var(--text-dim); font-size: 13px; margin: 0; }
.auth-tabs {
display: flex;
padding: 4px;
background: var(--bg);
border-radius: var(--radius-sm);
margin-bottom: 20px;
}
.auth-tab {
flex: 1;
padding: 8px 12px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-dim);
font-size: 13px; font-weight: 500;
transition: all var(--t-fast);
}
.auth-tab.is-active {
background: var(--bg-elev);
color: var(--text);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.auth-form { display: flex; flex-direction: column; }
.auth-form .btn-block { margin-top: 8px; }
.auth-hint {
padding: 10px 12px;
margin-bottom: 14px;
background: var(--accent-soft);
border: 1px solid var(--accent);
border-radius: var(--radius-sm);
color: var(--accent);
font-size: 12px;
text-align: center;
}
.auth-hint:empty { display: none; }
/* Tag chip input */
.chip-input {
display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
padding: 6px 8px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
min-height: 38px;
transition: all var(--t-fast);
}
.chip-input:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
background: var(--bg);
}
/* Existing-tag autocomplete dropdown anchored under the chip-input. */
.tag-suggest {
position: absolute;
top: 100%; left: 0; right: 0;
margin-top: 4px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-lg);
z-index: 60;
overflow: hidden;
max-height: 240px;
overflow-y: auto;
}
.tag-suggest.is-hidden { display: none; }
.tag-suggest-item {
display: flex; align-items: center; gap: 8px;
width: 100%;
padding: 7px 10px;
background: transparent;
border: none;
color: var(--text);
font-size: 13px;
text-align: left;
cursor: pointer;
}
.tag-suggest-item:hover { background: var(--bg-elev-2); }
.tag-suggest-item svg { width: 12px; height: 12px; color: var(--text-faint); }
.chip-input input {
flex: 1;
min-width: 80px;
background: transparent;
border: none;
outline: none;
color: var(--text);
font-size: 13px;
padding: 2px 4px;
}
.chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 4px 2px 8px;
background: var(--accent-soft);
color: var(--accent);
border-radius: 10px;
font-size: 11px;
font-weight: 500;
}
.chip button {
display: inline-flex; align-items: center; justify-content: center;
width: 16px; height: 16px;
background: transparent;
border: none;
border-radius: 50%;
color: inherit;
cursor: pointer;
opacity: 0.6;
}
.chip button:hover { opacity: 1; background: rgba(0,0,0,0.1); }
.chip button svg { width: 10px; height: 10px; }
/* Tag suggestions dropdown */
.chip-suggestions {
position: absolute;
z-index: 50;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow);
padding: 4px;
max-height: 200px;
overflow-y: auto;
min-width: 160px;
}
.chip-suggestion {
padding: 6px 10px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
}
.chip-suggestion:hover, .chip-suggestion.is-active { background: var(--bg-elev-2); color: var(--text); }
.chip-suggestion-empty { padding: 6px 10px; color: var(--text-faint); font-size: 12px; }
/* Settings rows */
.setting-row {
display: flex; align-items: center; justify-content: space-between;
gap: 12px;
padding: 10px 12px;
background: var(--bg);
border: 1px solid var(--border-soft);
border-radius: var(--radius-sm);
margin-bottom: 6px;
font-size: 13px;
}
.setting-row > span { display: flex; flex-direction: column; gap: 2px; }
.setting-hint {
color: var(--text-faint);
font-size: 11px;
font-weight: normal;
line-height: 1.35;
max-width: 320px;
}
.setting-row select {
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
padding: 4px 8px;
font-size: 12px;
}
.setting-row select:focus {
outline: none;
border-color: var(--accent);
}
/* iOS-style toggle switch */
.toggle {
position: relative;
display: inline-block;
width: 36px; height: 20px;
flex-shrink: 0;
}
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
position: absolute; inset: 0;
background: var(--bg-elev-2);
border: 1px solid var(--border);
border-radius: 12px;
cursor: pointer;
transition: all var(--t-fast);
}
.toggle-slider::before {
content: '';
position: absolute;
left: 2px;
top: 50%;
width: 14px; height: 14px;
background: var(--text-dim);
border-radius: 50%;
transform: translateY(-50%);
transition: all var(--t-fast);
}
.toggle input:checked + .toggle-slider {
background: var(--accent);
border-color: var(--accent);
}
.toggle input:checked + .toggle-slider::before {
background: white;
transform: translate(16px, -50%);
}
/* ---- 6. APP SHELL ---------------------------------------- */
.app-shell {
display: grid;
grid-template-columns: 240px 1fr;
height: 100vh;
background: var(--bg);
}
/* ---- 7. SIDEBAR ------------------------------------------ */
.sidebar {
display: flex; flex-direction: column;
padding: 16px 12px;
background: var(--bg-elev);
border-right: 1px solid var(--border);
overflow-y: auto;
}
.sidebar-brand {
display: flex; align-items: center; gap: 10px;
padding: 6px 8px 18px;
font-size: 15px; font-weight: 600;
}
.sidebar-brand svg { color: var(--accent); width: 18px; height: 18px; }
.sidebar-nav { display: flex; flex-direction: column; gap: 2px; }
.nav-item {
display: flex; align-items: center; gap: 10px;
padding: 7px 10px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-dim);
font-size: 13px; font-weight: 500;
text-align: left;
width: 100%;
transition: all var(--t-fast);
}
.nav-item:hover { background: var(--bg-elev-2); color: var(--text); }
.nav-item.is-active {
background: var(--accent-soft);
color: var(--accent);
}
.nav-item.is-active svg { color: var(--accent); }
.nav-item svg { color: var(--text-faint); }
.nav-item > span:first-of-type { flex: 1; }
.nav-count {
font-size: 11px;
color: var(--text-faint);
background: var(--bg-elev-2);
padding: 1px 6px;
border-radius: 8px;
min-width: 18px;
text-align: center;
}
.nav-item.is-active .nav-count { background: var(--accent); color: white; }
.sidebar-section { margin-top: 18px; }
.sidebar-section-header {
display: flex; align-items: center; justify-content: space-between;
padding: 4px 10px 6px;
font-size: 11px; font-weight: 600;
text-transform: uppercase;
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;
border-top: 1px solid var(--border-soft);
display: flex; flex-direction: column; gap: 2px;
}
.folder-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.nav-item.drag-over {
background: var(--accent-soft);
box-shadow: inset 0 0 0 2px var(--accent);
}
/* Folder reorder: insertion line above or below the hovered item. */
.nav-item.drop-before { box-shadow: inset 0 2px 0 0 var(--accent); }
.nav-item.drop-after { box-shadow: inset 0 -2px 0 0 var(--accent); }
.nav-item.is-dragging { opacity: 0.4; }
.nav-item[draggable="true"] { cursor: grab; }
.nav-item[draggable="true"]:active { cursor: grabbing; }
/* ---- 8. MAIN --------------------------------------------- */
.main { display: flex; flex-direction: column; overflow: hidden; }
.topbar {
display: flex; align-items: center; gap: 12px;
padding: 12px 24px;
border-bottom: 1px solid var(--border);
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 {
position: absolute; left: 10px;
color: var(--text-faint);
pointer-events: none;
}
.search input {
width: 100%;
padding: 8px 70px 8px 34px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
transition: all var(--t-fast);
}
.search input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
/* Replace the native (blue/grey) search-cancel button with a custom one
tinted via background-color so it picks up the accent on hover and
matches the other X buttons in the app. mask-image keeps it crisp. */
.search input::-webkit-search-cancel-button {
appearance: none;
-webkit-appearance: none;
width: 16px; height: 16px;
margin-right: 8px;
cursor: pointer;
background-color: var(--accent);
-webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><path d='M18 6 6 18M6 6l12 12'/></svg>");
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><path d='M18 6 6 18M6 6l12 12'/></svg>");
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
opacity: 0.85;
transition: opacity var(--t-fast);
}
.search input::-webkit-search-cancel-button:hover {
opacity: 1;
}
.search kbd { position: absolute; right: 8px; }
/* Search history dropdown */
.search-history {
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-lg);
z-index: 50;
overflow: hidden;
}
.search-history.is-hidden { display: none; }
.search-history-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 10px;
border-bottom: 1px solid var(--border-soft);
font-size: 11px;
color: var(--text-faint);
text-transform: uppercase;
letter-spacing: 0.4px;
}
.search-history-clear {
background: transparent;
border: none;
color: var(--text-dim);
font-size: 11px;
cursor: pointer;
padding: 2px 6px;
border-radius: 3px;
}
.search-history-clear:hover { color: var(--danger); background: var(--danger-soft); }
.search-history-row {
display: flex;
align-items: stretch;
}
.search-history-row:hover { background: var(--bg-elev-2); }
.search-history-item {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
padding: 8px 12px;
background: transparent;
border: none;
color: var(--text);
font-size: 13px;
cursor: pointer;
text-align: left;
}
.search-history-item svg { width: 13px; height: 13px; color: var(--text-faint); }
.search-history-del {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
flex-shrink: 0;
background: transparent;
border: none;
color: var(--text-dim);
cursor: pointer;
transition: color var(--t-fast), background var(--t-fast);
}
.search-history-del {
font-size: 18px;
line-height: 1;
font-weight: 400;
}
.search-history-row:hover .search-history-del { color: var(--text); }
.search-history-del:hover { color: var(--danger); background: var(--danger-soft); }
.topbar-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
/* View toggle (Cards / List) */
.view-toggle {
display: inline-flex;
padding: 2px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
}
.view-btn {
width: 28px; height: 28px;
display: grid; place-items: center;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-dim);
transition: all var(--t-fast);
}
.view-btn:hover { color: var(--text); }
.view-btn.is-active {
background: var(--accent-soft);
color: var(--accent);
}
[data-theme="dark"] .theme-icon-light { display: none; }
[data-theme="light"] .theme-icon-dark { display: none; }
.user-menu { position: relative; }
.user-chip {
display: inline-flex; align-items: center; gap: 8px;
padding: 5px 12px 5px 6px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: 20px;
color: var(--text);
font-size: 12px;
transition: all var(--t-fast);
}
.user-chip:hover { background: var(--bg-elev-2); }
/* Round initials avatar. Background colour is set inline from a hash of
the username (deterministic — same user, same colour every render).
Falls back to a background-image when a custom picture is set. */
.user-avatar {
display: inline-flex; align-items: center; justify-content: center;
width: 30px; height: 30px;
margin-left: 0;
border-radius: 50%;
font-size: 13px; font-weight: 600;
color: #fff;
text-transform: uppercase;
background-size: cover; background-position: center;
flex-shrink: 0;
user-select: none;
}
/* Larger preview variant shown in Settings → Account. */
.user-avatar-lg {
width: 48px; height: 48px;
margin-left: 0;
font-size: 20px;
}
.user-dropdown {
position: absolute; top: calc(100% + 6px); right: 0;
min-width: 180px;
padding: 4px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow);
z-index: 50;
}
.dropdown-item {
display: flex; align-items: center; gap: 8px;
width: 100%;
padding: 8px 10px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text);
font-size: 13px;
text-align: left;
}
.dropdown-item:hover { background: var(--bg-elev-2); }
.dropdown-item svg { color: var(--text-dim); }
/* No padding-top: the sticky .content-header owns it, so its box starts flush
with the scrollport and nothing can scroll through above it. */
.content { flex: 1; padding: 0 32px 24px; overflow-y: auto; }
/* Sticky title row — carries .content's top padding so its opaque box reaches
the scrollport edge. Everything else that sticks (batch bar, pagination,
table header) offsets by --hdr-h. */
.content-header {
display: flex; align-items: baseline; gap: 12px; margin-bottom: 20px;
position: sticky; top: 0; z-index: 12;
background: var(--bg);
padding-top: 24px;
}
.content-header h2 { font-size: 22px; font-weight: 600; margin: 0; }
.content-meta { color: var(--text-faint); font-size: 12px; }
/* ---- 9. ENTRY GRID --------------------------------------- */
.entry-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
position: relative;
user-select: none; /* avoid native text-selection during marquee */
}
/* Marquee rectangle for rubber-band selection */
.entry-card {
position: relative;
padding: 14px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
transition: all var(--t-fast);
cursor: pointer;
}
.entry-card:hover {
transform: translateY(-1px);
border-color: var(--accent);
box-shadow: var(--shadow);
}
.entry-card.is-selected {
border-color: var(--accent);
background: var(--accent-soft);
}
.entry-card.is-checked {
border-color: var(--accent);
background: var(--accent-soft);
box-shadow: inset 0 0 0 2px var(--accent);
}
/* Keyboard cursor (j/k nav) — distinct from selected so a focused card
stays visible after the slideover closes. */
.entry-card.is-cursor,
.entry-row.is-cursor {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
/* === List view layout === */
/* Cards are flattened into a single horizontal row using flexbox + `order`.
`.entry-head` and `.entry-pw-row` get display:contents so their children
participate directly in the card flex container — no wrapping, ever. */
.entry-grid.is-list {
grid-template-columns: 1fr;
gap: 4px;
}
.entry-grid.is-list .entry-card {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
}
.entry-grid.is-list .entry-card:hover { transform: none; }
.entry-grid.is-list .entry-head { display: contents; }
.entry-grid.is-list .entry-pw-row { display: contents; }
.entry-grid.is-list .entry-note-row { display: contents; }
.entry-grid.is-list .entry-note-placeholder {
order: 3;
/* Same track as .entry-pw so note rows line their copy button up with
the password rows'. Full text on hover via the title attr. */
flex: 0 0 110px;
min-width: 0;
font-size: 11px;
color: var(--text-dim);
font-style: italic;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 4px 8px;
background: var(--bg);
border-radius: 6px;
border: 1px solid var(--border-soft);
}
.entry-grid.is-list .entry-note-row .icon-btn {
order: 4;
flex-shrink: 0;
}
.entry-grid.is-list .entry-avatar {
order: 1;
width: 28px; height: 28px;
font-size: 12px;
margin: 0;
}
.entry-grid.is-list .entry-title {
order: 2;
/* Fixed track (not flex:1) so the password field that follows starts at
the SAME x on every row — otherwise variable trailing content
(tags/folder/icons) let the title grow by different amounts and the
pw pill drifted left/right per row. Short names leave whitespace;
long names ellipsise. */
flex: 0 0 240px;
min-width: 0;
display: flex; flex-direction: column;
}
.entry-grid.is-list .entry-title b { font-size: 13px; }
.entry-grid.is-list .entry-title small { font-size: 11px; }
.entry-grid.is-list .entry-pw {
order: 3;
font-size: 11px;
/* Full shorthand, not width: the base .entry-pw is `flex: 1` (basis 0),
which ignores width and let the pill stretch across the whole row. */
flex: 0 0 110px;
padding: 4px 8px;
background: var(--bg);
border-radius: 6px;
border: 1px solid var(--border-soft);
}
.entry-grid.is-list .entry-pw-row .icon-btn {
order: 4;
flex-shrink: 0;
}
.entry-grid.is-list .entry-meta {
order: 5;
/* Absorb the free space freed up by the now-fixed title, pushing tags/
folder + the order-6 actions to the far right while pw stays put.
Sole auto margin in the row — see the note below the order-6 block. */
margin: 0 0 0 auto;
flex-shrink: 0;
max-width: 220px;
overflow: hidden;
flex-wrap: nowrap;
}
.entry-grid.is-list .entry-meta .entry-chip {
flex-shrink: 0;
font-size: 10px;
}
/* Right-side action buttons: favorite + delete (normal view),
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;
}
/* NOTE: no margin-left:auto here. .entry-meta is always in the DOM (empty
when the entry has no chips) and already carries the auto, so it absorbs
all the free space. A second auto margin would SPLIT the free space
between the two, drifting the chips left/right depending on row width. */
/* 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);
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: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 {
position: sticky;
top: var(--hdr-h, 0px);
z-index: 10;
display: flex; align-items: center; gap: 8px;
padding: 10px 14px;
margin-bottom: 16px;
background: var(--bg-elev);
border: 1px solid var(--accent);
border-radius: var(--radius);
box-shadow: var(--shadow);
animation: toastIn var(--t-base);
}
.batch-bar-count {
font-weight: 600;
color: var(--accent);
margin-right: 8px;
}
.batch-bar select {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
padding: 4px 8px;
font-size: 12px;
}
.batch-bar .grow { flex: 1; }
.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);
border-radius: var(--radius-sm);
font-weight: 600; font-size: 14px;
color: var(--text);
flex-shrink: 0;
overflow: hidden;
}
.entry-avatar-img {
width: 100%; height: 100%;
object-fit: contain;
padding: 4px;
box-sizing: border-box;
image-rendering: -webkit-optimize-contrast; /* sharper 16x16 icons */
}
.entry-title { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.entry-title b {
font-size: 14px; font-weight: 600;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.entry-title small {
color: var(--text-dim);
font-size: 12px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.entry-fav, .entry-del, .entry-dup, .entry-pin {
color: var(--text-faint);
background: transparent; border: none;
padding: 4px;
transition: all var(--t-fast);
cursor: pointer;
}
.entry-fav.is-on { color: var(--warning); }
.entry-fav:hover { color: var(--warning); transform: scale(1.1); }
.entry-pin.is-on { color: var(--accent); }
.entry-pin:hover { color: var(--accent); 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 {
color: var(--text-dim);
opacity: 0.8;
}
.entry-del:hover {
opacity: 1;
color: var(--danger);
background: var(--danger-soft);
border-radius: 6px;
transform: scale(1.1);
}
/* Username row with inline copy button (visible on card hover) */
.entry-user-row {
display: flex;
align-items: center;
gap: 4px;
}
.entry-user-row > span {
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.entry-copy-user {
background: transparent; border: none;
padding: 2px;
color: var(--text-faint);
opacity: 0;
cursor: pointer;
transition: all var(--t-fast);
border-radius: 4px;
}
.entry-copy-user svg { width: 11px; height: 11px; }
.entry-card:hover .entry-copy-user { opacity: 0.7; }
.entry-copy-user:hover {
opacity: 1 !important;
color: var(--accent);
background: var(--accent-soft);
}
/* Kebab (⋯) menu */
.entry-kebab-wrap {
position: relative;
}
.entry-kebab {
background: transparent; border: none;
padding: 4px;
color: var(--text-dim);
border-radius: 6px;
cursor: pointer;
transition: all var(--t-fast);
}
.entry-kebab:hover { color: var(--text); background: var(--bg-elev-2); }
.entry-kebab-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
min-width: 180px;
padding: 4px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow);
z-index: 60;
display: none;
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;
width: 100%;
padding: 8px 10px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text);
font-size: 13px;
text-align: left;
cursor: pointer;
transition: all var(--t-fast);
}
.kebab-item:hover { background: var(--bg-elev-2); }
.kebab-item svg { color: var(--text-dim); }
.kebab-item.is-danger { color: var(--danger); }
.kebab-item.is-danger:hover { background: var(--danger-soft); }
.kebab-item.is-danger svg { color: var(--danger); }
.entry-pw-row {
display: flex; align-items: center; gap: 6px;
padding: 6px 8px;
background: var(--bg);
border: 1px solid var(--border-soft);
border-radius: 6px;
margin-bottom: 8px;
}
.entry-pw {
flex: 1;
font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
font-size: 12px;
color: var(--text-dim);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
user-select: none;
}
.entry-pw.is-revealed { color: var(--text); }
.entry-pw.is-error { color: var(--danger); }
.entry-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.entry-chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 8px;
background: var(--bg-elev-2);
border-radius: 10px;
font-size: 11px;
color: var(--text-dim);
}
.entry-chip svg { width: 11px; height: 11px; }
.entry-chip.is-folder { color: var(--accent); background: var(--accent-soft); }
.entry-chip.is-pwned {
color: #fff;
background: #dc2626; /* solid red — high-attention signal */
font-weight: 600;
}
.entry-chip.is-pwned svg { stroke: #fff; }
.entry-chip.is-2fa {
color: var(--accent);
background: var(--accent-soft);
font-weight: 600;
}
.entry-chip.is-aged {
color: #b45309;
background: rgba(245, 158, 11, 0.15);
font-weight: 600;
}
.entry-chip.is-aged svg { stroke: #b45309; }
/* TOTP live-code panel (inside slide-over Two-factor field) */
.totp-panel {
display: flex; align-items: center; gap: 12px;
margin-top: 8px;
padding: 10px 12px;
background: var(--bg);
border: 1px solid var(--border-soft);
border-radius: var(--radius-sm);
min-height: 36px;
}
.totp-code {
flex: 1;
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 22px;
letter-spacing: 2px;
color: var(--accent);
font-weight: 600;
user-select: all; /* triple-click selects the code */
}
.totp-code.is-invalid {
color: var(--text-faint);
font-size: 13px;
letter-spacing: normal;
font-style: italic;
font-weight: normal;
}
.totp-bar-wrap {
margin-top: 4px;
height: 3px;
background: var(--bg);
border-radius: 2px;
overflow: hidden;
}
.totp-bar {
height: 100%;
background: var(--accent);
width: 0%;
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; }
/* ---- Vault health dashboard ----------------------------- */
.entry-grid.is-health {
display: flex;
flex-direction: column;
gap: 16px;
}
.health-loading {
color: var(--text-dim);
text-align: center;
padding: 40px;
font-size: 14px;
}
.health-header {
display: flex; align-items: center; gap: 20px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px;
}
.health-score {
width: 110px; height: 110px;
border-radius: 50%;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
flex-shrink: 0;
border: 4px solid var(--accent);
background: var(--bg);
}
.health-score.is-ok { border-color: #10b981; color: #10b981; }
.health-score.is-fair { border-color: var(--accent); color: var(--accent); }
.health-score.is-warn { border-color: #f59e0b; color: #f59e0b; }
.health-score.is-danger { border-color: #ef4444; color: #ef4444; }
.health-score-num {
font-size: 36px; font-weight: 700;
font-family: 'JetBrains Mono', ui-monospace, monospace;
}
.health-score-lbl {
font-size: 11px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.5px;
margin-top: 2px;
}
.health-intro { flex: 1; }
.health-intro h3 { margin: 0 0 6px; font-size: 16px; }
.health-intro p {
margin: 0 0 10px;
color: var(--text-dim);
font-size: 13px;
line-height: 1.5;
}
.health-card {
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px 18px;
}
.health-card-head {
display: flex; align-items: center; gap: 10px;
margin-bottom: 6px;
}
.health-card-head h4 {
margin: 0;
font-size: 14px;
font-weight: 600;
}
.health-badge {
min-width: 22px;
padding: 2px 8px;
border-radius: 11px;
background: var(--accent);
color: white;
font-size: 11px;
font-weight: 700;
text-align: center;
}
.health-badge.is-empty {
background: var(--bg);
color: var(--text-dim);
}
.health-hint {
margin: 0 0 10px;
font-size: 12px;
color: var(--text-dim);
line-height: 1.4;
}
.health-empty {
margin: 0;
font-size: 12px;
color: var(--text-faint);
font-style: italic;
}
.health-list {
list-style: none;
margin: 0;
padding: 0;
display: flex; flex-direction: column;
gap: 4px;
}
.health-item {
display: flex; align-items: center; gap: 8px;
padding: 6px 10px;
background: var(--bg);
border-radius: var(--radius-sm);
font-size: 13px;
}
.health-item-label {
flex: 1;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
color: var(--text);
}
.health-more {
margin: 6px 0 0;
color: var(--text-faint);
font-size: 11px;
font-style: italic;
text-align: center;
}
.health-more-btn {
display: block;
margin: 8px auto 0;
}
/* ---- Slideover custom icon field ------------------------ */
.so-icon-field {
display: flex;
align-items: center;
gap: 12px;
}
.so-icon-preview {
width: 56px; height: 56px;
display: grid; place-items: center;
background: var(--bg-elev-2);
border-radius: var(--radius-sm);
overflow: hidden;
font-weight: 600;
font-size: 20px;
color: var(--text);
flex-shrink: 0;
}
.so-icon-preview img {
width: 100%; height: 100%;
object-fit: contain;
padding: 6px;
box-sizing: border-box;
}
.so-icon-actions {
display: flex; flex-direction: column; gap: 6px;
}
/* ---- Password history modal --------------------- */
.so-history-wrap {
margin-top: -8px;
margin-bottom: 4px;
}
.so-history-wrap .btn { padding: 4px 8px; font-size: 11px; }
/* Make the history body actually scrollable. The .modal-body's default
doesn't fix the height, so the panel grew to fit all rows → no scroll. */
#historyBody {
max-height: 65vh;
overflow-y: auto;
padding: 12px 16px;
}
.history-loading, .history-empty {
padding: 24px;
text-align: center;
color: var(--text-dim);
font-size: 13px;
}
.history-list {
list-style: none;
margin: 0; padding: 0;
display: flex; flex-direction: column;
gap: 6px;
}
.history-row {
background: var(--bg);
border: 1px solid var(--border-soft);
border-radius: var(--radius-sm);
padding: 10px 12px;
display: flex; flex-direction: column;
gap: 6px;
}
.history-meta {
display: flex; align-items: center; gap: 8px;
}
.history-date {
font-size: 11px;
color: var(--text-dim);
font-family: 'JetBrains Mono', ui-monospace, monospace;
}
.history-preview {
display: flex; align-items: flex-start; gap: 6px;
min-width: 0;
}
.history-value {
flex: 1 1 0;
min-width: 0;
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 12px;
color: var(--text);
/* Long revealed passwords wrap to multiple lines (no ellipsis) so
the user sees the full value. break-all is needed because real
passwords have no whitespace for the browser to break on. */
white-space: normal;
word-break: break-all;
overflow-wrap: anywhere;
line-height: 1.4;
}
.history-row { min-width: 0; }
.history-actions {
display: flex; justify-content: flex-end;
}
/* ---- Cheatsheet overlay --------------------------- */
.cheatsheet-panel { max-width: 720px; width: 100%; }
.cheatsheet-body {
max-height: 60vh;
overflow-y: auto;
padding: 18px 22px;
}
.cheatsheet-group { margin-bottom: 18px; }
.cheatsheet-group h4 {
margin: 0 0 8px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-dim);
}
.cheatsheet-list {
display: flex; flex-direction: column;
gap: 6px;
}
.cheatsheet-row {
display: grid;
grid-template-columns: 180px 1fr;
gap: 16px;
align-items: start;
padding: 4px 0;
font-size: 13px;
}
.cheatsheet-desc {
color: var(--text-dim);
line-height: 1.4;
}
.cheatsheet-keys {
display: flex; align-items: center; gap: 4px;
flex-wrap: wrap;
}
.cheatsheet-keys kbd {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 11px;
padding: 2px 7px;
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: 4px;
box-shadow: 0 1px 0 var(--border-soft);
}
.cheatsheet-plus {
color: var(--text-faint);
font-size: 10px;
}
.cheatsheet-icon-chip {
display: inline-flex; align-items: center; justify-content: center;
width: 24px; height: 22px;
padding: 2px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
box-shadow: 0 1px 0 var(--border-soft);
color: var(--text);
}
.cheatsheet-icon-chip svg { width: 14px; height: 14px; }
/* ---- Slideover custom-fields editor ------------------ */
.so-custom-list {
display: flex; flex-direction: column;
gap: 6px;
margin-top: 6px;
}
.so-custom-row {
display: grid;
grid-template-columns: 1fr 1.4fr auto auto auto auto;
gap: 4px;
align-items: center;
}
.so-custom-row .so-input {
padding: 4px 8px;
font-size: 12px;
}
/* Editable combobox for option-backed custom fields (card brand, expiry
year…). Input fills the cell; the arrow drops a menu of ALL options. */
.so-combo { position: relative; display: flex; align-items: center; }
.so-combo-input { flex: 1; padding-right: 24px !important; width: 100%; }
.so-combo-arrow {
position: absolute; right: 2px; top: 50%;
transform: translateY(-50%);
display: flex; align-items: center; justify-content: center;
width: 20px; height: 20px;
background: transparent; border: 0; padding: 0;
color: var(--text-faint); cursor: pointer;
}
.so-combo-arrow svg { width: 14px; height: 14px; }
.so-combo-arrow:hover { color: var(--text); }
.so-combo-menu {
position: absolute; top: calc(100% + 2px); left: 0; right: 0;
z-index: 20;
max-height: 200px; overflow-y: auto;
background: var(--bg-elev-2);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow);
padding: 4px;
}
.so-combo-menu.is-hidden { display: none; }
.so-combo-item {
padding: 6px 10px;
font-size: 12px;
border-radius: 4px;
cursor: pointer;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.so-combo-item:hover { background: var(--accent); color: #fff; }
.so-custom-secret-toggle {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
width: 28px; height: 28px;
cursor: pointer;
color: var(--text-dim);
font-size: 13px;
line-height: 1;
padding: 0;
}
.so-custom-secret-toggle.is-on {
background: var(--accent-soft);
color: var(--accent);
border-color: var(--accent);
}
.so-custom-add {
margin-top: 8px;
}
.so-custom-add .btn { padding: 4px 10px; font-size: 12px; }
/* ---- Audit log viewer -------------------------------- */
.entry-grid.is-audit {
display: flex;
flex-direction: column;
gap: 10px;
}
.audit-loading, .audit-empty {
color: var(--text-dim);
padding: 24px;
text-align: center;
font-size: 13px;
}
.audit-header {
display: flex; gap: 8px;
align-items: center;
}
.audit-search {
flex: 1;
padding: 6px 12px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
font-size: 13px;
}
.audit-search:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.audit-note {
margin: 0;
font-size: 11px;
color: var(--text-faint);
font-style: italic;
}
.audit-list {
display: flex;
flex-direction: column;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-elev);
max-height: 70vh;
overflow-y: auto;
}
.audit-row {
display: grid;
grid-template-columns: 160px 1fr 120px;
gap: 12px;
padding: 8px 14px;
font-size: 12px;
border-bottom: 1px solid var(--border-soft);
align-items: center;
}
.audit-row:last-child { border-bottom: none; }
.audit-row:hover { background: var(--bg); }
.audit-date {
color: var(--text-dim);
font-family: 'JetBrains Mono', ui-monospace, monospace;
white-space: nowrap;
}
.audit-action {
color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.audit-ip {
color: var(--text-faint);
font-family: 'JetBrains Mono', ui-monospace, monospace;
text-align: right;
}
/* ---- Filters dropdown + chips ------------------------ */
.filters-wrap { position: relative; }
.filters-count {
position: absolute;
top: -2px; right: -4px;
background: var(--accent);
color: white;
font-size: 9px;
font-weight: 700;
padding: 1px 5px;
border-radius: 8px;
line-height: 1.3;
pointer-events: none;
}
.filters-menu {
position: absolute;
top: calc(100% + 4px); right: 0;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-lg);
z-index: 60;
min-width: 240px;
padding: 6px;
}
.filters-menu-title {
padding: 4px 8px 6px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-faint);
}
.filters-menu-row {
display: flex; align-items: center; gap: 8px;
padding: 6px 8px;
cursor: pointer;
border-radius: 4px;
font-size: 13px;
color: var(--text);
}
.filters-menu-row:hover { background: var(--accent-soft); }
.filters-menu-row svg { width: 14px; height: 14px; color: var(--text-dim); }
.filters-menu-cb {
margin: 0;
accent-color: var(--accent);
cursor: pointer;
}
.filters-menu-clear {
width: 100%;
margin-top: 6px;
padding: 6px 8px;
background: none;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-dim);
font-size: 12px;
cursor: pointer;
}
.filters-menu-clear:hover {
color: var(--text);
border-color: var(--accent);
}
.filter-chips {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
padding: 8px 0;
margin-bottom: 4px;
}
.filter-chip {
display: inline-flex; align-items: center; gap: 6px;
background: var(--accent-soft);
color: var(--accent);
border: 1px solid var(--accent);
border-radius: 14px;
padding: 3px 4px 3px 10px;
font-size: 11px;
font-weight: 600;
}
.filter-chip svg { width: 12px; height: 12px; }
.filter-chip-x {
display: inline-flex; align-items: center; justify-content: center;
width: 16px; height: 16px;
background: none;
border: none;
cursor: pointer;
color: var(--accent);
border-radius: 50%;
padding: 0;
}
.filter-chip-x:hover { background: var(--accent); color: white; }
.filter-chip-x svg { width: 10px; height: 10px; }
.filter-chips-clear {
background: none;
border: none;
color: var(--text-faint);
font-size: 11px;
cursor: pointer;
padding: 3px 8px;
text-decoration: underline;
}
.filter-chips-clear:hover { color: var(--text-dim); }
/* ---- Note cards (kind=note) -------------------------- */
.entry-note-row {
display: flex; align-items: center;
padding: 4px 0 8px;
color: var(--text-dim);
font-size: 12px;
font-style: italic;
}
.entry-note-placeholder {
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Note marker in table-view "Name" cell + faint placeholder in user cell */
.kind-badge {
display: inline-block;
padding: 1px 6px;
margin-left: 6px;
border-radius: 3px;
background: var(--accent-soft);
color: var(--accent);
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
vertical-align: middle;
}
.col-user-note {
color: var(--text-faint);
font-style: italic;
font-size: 12px;
}
/* ---- Slideover note textarea ------------------------- */
.so-note-body {
min-height: 220px;
resize: vertical;
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 13px;
line-height: 1.55;
white-space: pre-wrap;
}
/* A textarea has no type="password"; -webkit-text-security is the Chromium
equivalent (WebView2 is Chromium, so it's always available here). */
.so-note-body.is-masked { -webkit-text-security: disc; }
.so-note-label { display: flex; align-items: center; justify-content: space-between; }
/* ---- + New dropdown ---------------------------------- */
.new-entry-wrap {
position: relative;
display: flex;
gap: 1px;
}
.new-entry-caret {
padding-left: 6px;
padding-right: 6px;
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.new-entry-wrap > #newEntryBtn {
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
}
.new-entry-menu {
position: absolute;
top: calc(100% + 4px); right: 0;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-lg);
z-index: 60;
min-width: 160px;
padding: 4px;
}
.new-entry-menu .dropdown-item {
width: 100%;
display: flex; align-items: center; gap: 10px;
padding: 8px 10px;
background: none; border: none;
color: var(--text);
font: inherit;
cursor: pointer;
border-radius: 4px;
text-align: left;
}
.new-entry-menu .dropdown-item:hover { background: var(--accent-soft); }
.new-entry-menu .dropdown-item svg { width: 14px; height: 14px; }
.new-entry-menu .dropdown-sep {
height: 1px;
background: var(--border-soft);
margin: 4px 0;
}
.new-entry-menu .dropdown-section {
padding: 4px 10px 2px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-faint);
}
/* Table column picker dropdown — only rendered in table view. */
.cols-wrap {
position: relative; /* anchor for the absolutely-positioned menu */
display: inline-flex;
vertical-align: middle;
}
.cols-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-lg);
z-index: 60;
min-width: 160px;
padding: 4px;
overflow: hidden;
}
.cols-menu.is-hidden { display: none; }
.cols-menu-item {
display: flex; align-items: center; gap: 8px;
padding: 7px 10px;
font-size: 13px;
color: var(--text);
cursor: pointer;
border-radius: 4px;
}
.cols-menu-item:hover { background: var(--bg-elev-2); }
.cols-menu-item input { cursor: pointer; }
/* ---- Quick search modal (tray menu) ------------------ */
.quick-search-panel {
padding: 0;
overflow: hidden;
max-width: 520px;
width: 100%;
}
.quick-search-input {
display: flex; align-items: center; gap: 10px;
padding: 14px 18px;
border-bottom: 1px solid var(--border);
}
.quick-search-input svg {
width: 18px; height: 18px;
color: var(--text-dim);
flex-shrink: 0;
}
.quick-search-input input {
flex: 1;
background: transparent;
border: none;
color: var(--text);
font-size: 15px;
outline: none;
}
.quick-search-input kbd {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
background: var(--bg);
color: var(--text-dim);
border: 1px solid var(--border);
}
.quick-search-results {
max-height: 360px;
overflow-y: auto;
padding: 6px 0;
}
.quick-search-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 18px;
cursor: pointer;
}
.quick-search-row.is-selected {
background: var(--accent-soft);
}
.quick-search-row:hover { background: var(--accent-soft); }
.quick-search-avatar {
width: 32px; height: 32px;
border-radius: var(--radius-sm);
background: var(--bg-elev-2);
display: grid; place-items: center;
overflow: hidden;
font-weight: 600;
font-size: 13px;
flex-shrink: 0;
}
.quick-search-avatar img {
width: 100%; height: 100%;
object-fit: contain;
padding: 4px;
box-sizing: border-box;
}
.quick-search-main {
flex: 1; min-width: 0;
}
.quick-search-name {
font-size: 14px; font-weight: 600;
color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.quick-search-sub {
font-size: 12px;
color: var(--text-dim);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.quick-search-empty {
padding: 24px 18px;
text-align: center;
color: var(--text-dim);
font-size: 13px;
font-style: italic;
}
.quick-search-hint {
border-top: 1px solid var(--border);
padding: 8px 18px;
font-size: 11px;
color: var(--text-faint);
line-height: 1.5;
}
/* Slideover title — tint the "+ New entry" prefix in accent colour so
the mode is unambiguous without changing the header layout. */
#slideoverTitle.is-new-mode { color: var(--accent); }
#slideoverTitle.is-edit-mode { color: var(--text); }
.btn-xs {
font-size: 11px;
padding: 3px 10px;
}
/* Brief attention pulse on the slideover generator button after Fix */
@keyframes attentionPulse {
0%, 100% { box-shadow: 0 0 0 0 var(--accent); }
50% { box-shadow: 0 0 0 6px transparent; }
}
.icon-btn.is-pulse {
animation: attentionPulse 0.7s ease-out 3;
border-radius: var(--radius-sm);
}
/* ---- 10. SLIDE-OVER -------------------------------------- */
.slideover {
position: fixed; top: 0; right: 0; bottom: 0;
width: 420px;
background: var(--bg-elev);
border-left: 1px solid var(--border);
box-shadow: var(--shadow-lg);
transform: translateX(100%);
transition: transform var(--t-base);
display: flex; flex-direction: column;
/* Above the topbar (z-index 40) — otherwise the topbar's
backdrop-filter stacking context clips the slideover header
(where the title / close button live) under it. */
z-index: 50;
}
.slideover.is-open { transform: translateX(0); }
/* Settings needs more width than the entry editor — the left tab column eats
~100px. Only #settingsPanel; #slideover stays 420. (Center mode already
sets its own 520.) */
#settingsPanel { width: 800px; }
/* Suppress the slide animation when the panel is closed — otherwise
changing editor-position animates the off-screen position from right
to left and the panel briefly crosses the viewport. */
.slideover:not(.is-open) { transition: none; }
/* Push the app shell to the left when the entry slideover is open,
so the table, pagination etc. stay visible. Only applies to the
entry editor (#slideover) — NOT #settingsPanel which would visually
re-flow the whole app. Width matches .slideover (420px). */
body[data-editor-position="right"]:has(#slideover.is-open) #appShell { margin-right: 420px; }
#appShell { transition: margin-right var(--t-base), margin-left var(--t-base); }
/* ---- Editor position: LEFT (mirrored slideover) ---- */
body[data-editor-position="left"] #slideover,
body[data-editor-position="left"] #settingsPanel {
left: 0; right: auto;
border-left: none;
border-right: 1px solid var(--border);
transform: translateX(-100%);
}
body[data-editor-position="left"] #slideover.is-open,
body[data-editor-position="left"] #settingsPanel.is-open { transform: translateX(0); }
body[data-editor-position="left"]:has(#slideover.is-open) #appShell {
margin-left: 420px;
}
/* ---- Editor position: CENTER (modal-style) ---- */
body[data-editor-position="center"] #slideover,
body[data-editor-position="center"] #settingsPanel {
top: 50%; left: 50%; right: auto; bottom: auto;
width: 520px;
max-height: 90vh;
border-radius: var(--radius-lg);
border-left: 1px solid var(--border);
transform: translate(-50%, -50%) scale(0.96);
opacity: 0;
/* Hidden panels in center mode live INSIDE the viewport (opacity 0
instead of off-screen translate), so without this guard the
buttons would stay clickable through the invisible panel. */
pointer-events: none;
transition: transform var(--t-base), opacity var(--t-base);
}
/* Settings gets a FIXED height in centered mode so switching tabs (short vs
long) doesn't resize + re-center the modal. Entry #slideover stays
content-sized (grows with the form). */
body[data-editor-position="center"] #settingsPanel { width: 800px; height: min(94vh, 720px); }
body[data-editor-position="center"] #slideover.is-open,
body[data-editor-position="center"] #settingsPanel.is-open {
transform: translate(-50%, -50%) scale(1);
opacity: 1;
pointer-events: auto;
}
/* Center mode = true modal: dim + blur backdrop, click anywhere
outside the panel dismisses it (handled in JS). The backdrop is
painted via a body pseudo-element so it covers everything but
the active panel. */
body[data-editor-position="center"]:has(#slideover.is-open)::before,
body[data-editor-position="center"]:has(#settingsPanel.is-open)::before {
content: '';
position: fixed; inset: 0;
background: rgba(0, 0, 0, 0.45);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
z-index: 45; /* above topbar (40), below .slideover (50) */
pointer-events: auto; /* swallow clicks so cards/sidebar don't see them */
}
.slideover-header {
display: flex; align-items: center; justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
}
.slideover-header h3 {
font-size: 15px; font-weight: 600;
margin: 0;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.slideover-body { padding: 20px; overflow-y: auto; flex: 1; }
.slideover-field { margin-bottom: 16px; }
/* Settings panel search — sits below the header, above the body. Only
present inside #settingsPanel. */
.settings-search-wrap {
position: relative;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center;
}
.settings-search-icon {
position: absolute; left: 26px; top: 50%;
transform: translateY(-50%);
width: 14px; height: 14px;
color: var(--text-faint);
pointer-events: none;
}
.settings-search-input {
flex: 1;
padding: 7px 30px 7px 32px;
background: var(--bg-elev-2);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
font-size: 13px;
outline: none;
transition: border-color var(--t-fast);
}
.settings-search-input:focus { border-color: var(--accent); }
.settings-search-clear {
position: absolute; right: 22px; top: 50%;
transform: translateY(-50%);
background: transparent; border: 0; padding: 4px;
cursor: pointer;
color: var(--text-faint);
border-radius: 4px;
display: none;
}
.settings-search-clear svg { width: 12px; height: 12px; }
.settings-search-clear:hover { color: var(--text); background: var(--bg-elev-2); }
.settings-search-wrap.has-query .settings-search-clear { display: inline-flex; }
/* Settings tabs — vertical column on the left; JS toggles .is-tab-hidden. */
.settings-main { display: flex; flex: 1; min-height: 0; }
.settings-tabs {
display: flex; flex-direction: column; gap: 4px; padding: 12px 8px;
flex: 0 0 auto; border-right: 1px solid var(--border); overflow-y: auto;
}
.settings-tab {
display: flex; align-items: center; gap: 8px;
text-align: left; width: 100%; padding: 7px 12px; font-size: 12px; cursor: pointer;
color: var(--text-dim); background: transparent; border: 1px solid transparent;
border-radius: 6px; white-space: nowrap;
}
.settings-tab svg { width: 15px; height: 15px; flex: 0 0 auto; }
.settings-tab:hover { background: var(--bg-elev-2); }
.settings-tab.is-active { color: var(--accent-fg); background: var(--accent); border-color: var(--accent); }
.slideover-field.is-tab-hidden { display: none; }
/* Guided tour (spotlight + bubble). Backdrop blocks app clicks; the spot's
huge box-shadow is the dimmer (no separate overlay needed). */
.tour-backdrop { position: fixed; inset: 0; z-index: 300; }
.tour-spot {
position: fixed; z-index: 301; pointer-events: none; border-radius: 8px;
border: 2px solid var(--accent);
box-shadow: 0 0 0 9999px rgba(0, 0, 0, .6);
transition: top .2s ease, left .2s ease, width .2s ease, height .2s ease;
}
.tour-bubble {
position: fixed; z-index: 302; max-width: 300px;
background: var(--bg-elev-2, var(--bg-2)); color: var(--text);
border: 1px solid var(--border); border-radius: 10px;
padding: 14px 16px; box-shadow: 0 8px 30px rgba(0, 0, 0, .45);
}
.tour-bubble-title { font-weight: 700; font-size: 14px; margin-bottom: 4px; }
.tour-bubble-body { font-size: 12px; color: var(--text-dim); line-height: 1.5; }
.tour-bubble-foot {
display: flex; align-items: center; justify-content: space-between;
gap: 12px; margin-top: 12px;
}
.tour-bubble-count { font-size: 11px; color: var(--text-faint); }
.tour-bubble-btns { display: flex; gap: 6px; }
/* Hidden section + per-row + no-results banner driven by JS. */
.slideover-field.is-search-hidden,
.setting-row.is-search-hidden { display: none; }
.settings-no-results {
padding: 20px;
color: var(--text-faint);
font-size: 13px;
text-align: center;
display: none;
}
.settings-no-results.is-visible { display: block; }
/* Global busy overlay — spinner + text for long ops (export, big saves). */
.busy-overlay {
position: fixed; inset: 0;
z-index: 200;
display: flex; align-items: center; justify-content: center;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
}
.busy-overlay.is-hidden { display: none; }
.busy-box {
display: flex; flex-direction: column; align-items: center; gap: 14px;
padding: 24px 32px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
}
.busy-spinner {
width: 34px; height: 34px;
border: 3px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: busy-spin 0.7s linear infinite;
}
@keyframes busy-spin { to { transform: rotate(360deg); } }
.busy-text { font-size: 13px; color: var(--text-dim); text-align: center; }
.slideover-field-label { font-size: 11px; font-weight: 500; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
.slideover-field-value {
display: flex; align-items: center; gap: 6px;
padding: 10px 12px;
background: var(--bg);
border: 1px solid var(--border-soft);
border-radius: var(--radius-sm);
font-size: 13px;
word-break: break-all;
}
.so-input {
width: 100%;
padding: 10px 12px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
font-size: 13px;
transition: all var(--t-fast);
}
.so-input:focus {
outline: none;
border-color: var(--accent);
background: var(--bg);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.so-pw-row {
display: flex; align-items: center; gap: 4px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 2px 4px 2px 10px;
}
.so-pw-row:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.so-pw-row .so-input {
background: transparent;
border: none;
padding: 8px 4px;
}
.so-pw-row .so-input:focus { box-shadow: none; }
/* Sticky action row — stays glued to the bottom of the slideover body
while it scrolls, so Save is reachable without scrolling all the way
down on entries with lots of custom fields / attachments. */
.slideover-actions {
display: flex; gap: 8px;
position: sticky;
bottom: -20px; /* cancel the .slideover-body padding-bottom */
margin: 24px -20px -20px; /* extend to the edges */
padding: 12px 20px;
background: var(--bg-elev);
border-top: 1px solid var(--border);
z-index: 2;
}
/* ---- 11. MODALS ------------------------------------------ */
.modal {
position: fixed; inset: 0;
z-index: 100;
display: grid; place-items: center;
padding: 24px;
}
.modal-backdrop {
position: absolute; inset: 0;
background: rgba(0,0,0,0.5);
backdrop-filter: blur(4px);
}
.modal-panel {
position: relative;
width: 100%; max-width: 480px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
overflow: hidden;
animation: modalIn var(--t-base);
}
.modal-panel-sm { max-width: 360px; }
@keyframes modalIn {
from { opacity: 0; transform: scale(0.96) translateY(8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.modal-header {
display: flex; align-items: center; justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
}
.modal-header h3 { font-size: 15px; font-weight: 600; margin: 0; }
.modal-body { padding: 20px; }
.modal-footer {
display: flex; gap: 8px; justify-content: flex-end;
padding: 14px 20px;
background: var(--bg);
border-top: 1px solid var(--border);
}
.gen-preview {
padding: 12px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
word-break: break-all;
margin-bottom: 16px;
min-height: 42px;
display: flex; align-items: center;
}
.gen-options { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
/* ---- 12. COMMAND PALETTE -------------------------------- */
.cmd-palette {
position: fixed; inset: 0;
z-index: 200;
display: grid; place-items: start center;
padding-top: 15vh;
}
.cmd-panel {
position: relative;
width: 100%; max-width: 560px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
overflow: hidden;
animation: modalIn var(--t-base);
}
.cmd-input {
display: flex; align-items: center; gap: 10px;
padding: 14px 16px;
border-bottom: 1px solid var(--border);
}
.cmd-input svg { color: var(--text-faint); }
.cmd-input input {
flex: 1;
background: transparent; border: none; outline: none;
color: var(--text);
font-size: 15px;
}
.cmd-results { max-height: 360px; overflow-y: auto; padding: 8px; }
.cmd-item {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px;
border-radius: 6px;
cursor: pointer;
color: var(--text);
font-size: 13px;
}
.cmd-item:hover, .cmd-item.is-active { background: var(--bg-elev-2); }
/* ---- 13. EMPTY STATE ------------------------------------ */
.empty-state {
display: flex; flex-direction: column; align-items: center; gap: 8px;
padding: 80px 20px;
color: var(--text-dim);
animation: fadeIn var(--t-base);
}
.empty-illustration {
width: 140px; height: 140px;
margin-bottom: 12px;
}
.empty-icon {
width: 48px; height: 48px;
padding: 12px;
background: var(--bg-elev);
border-radius: var(--radius);
color: var(--text-faint);
}
.empty-state h3 { margin: 8px 0 0; font-size: 18px; color: var(--text); font-weight: 600; }
.empty-state p { margin: 0; font-size: 13px; max-width: 320px; text-align: center; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ---- Skeleton loaders ----------------------------------- */
.skeleton-card {
padding: 14px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
position: relative;
}
.skeleton-card::after {
content: '';
position: absolute; inset: 0;
background: linear-gradient(90deg, transparent, var(--bg-elev-2), transparent);
animation: shimmer 1.4s infinite;
}
.skeleton-line {
height: 12px;
background: var(--bg-elev-2);
border-radius: 4px;
margin-bottom: 10px;
}
.skeleton-line.w-40 { width: 40%; }
.skeleton-line.w-60 { width: 60%; }
.skeleton-line.w-80 { width: 80%; }
.skeleton-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.skeleton-circle {
width: 32px; height: 32px;
background: var(--bg-elev-2);
border-radius: var(--radius-sm);
flex-shrink: 0;
}
@keyframes shimmer {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
/* ---- Idle warning ---------------------------------------- */
.idle-warning {
position: fixed;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
z-index: 400;
width: 320px;
padding: 24px;
background: var(--bg-elev);
border: 1px solid var(--warning);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
text-align: center;
animation: modalIn var(--t-base);
}
.idle-warning .idle-icon {
width: 36px; height: 36px;
padding: 8px;
background: rgba(245,158,11,0.12);
color: var(--warning);
border-radius: 50%;
margin: 0 auto 8px;
display: block;
}
.idle-warning h3 { font-size: 16px; font-weight: 600; margin: 0 0 4px; }
.idle-warning p { color: var(--text-dim); font-size: 13px; margin: 0 0 16px; }
.idle-warning b { color: var(--warning); font-size: 20px; }
/* ---- 14. TOASTS ----------------------------------------- */
.toast-container {
position: fixed; bottom: 20px; right: 20px;
z-index: 300;
display: flex; flex-direction: column-reverse; gap: 8px;
pointer-events: none;
}
.toast {
pointer-events: auto;
display: flex; align-items: center; gap: 8px;
padding: 10px 14px;
background: var(--bg-elev);
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
border-radius: var(--radius-sm);
box-shadow: var(--shadow);
font-size: 13px;
min-width: 240px;
animation: toastIn var(--t-base);
}
.toast.is-success { border-left-color: var(--success); }
.toast.is-error { border-left-color: var(--danger); }
.toast.is-warning { border-left-color: var(--warning); }
@keyframes toastIn {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
/* ---- 15. SCROLLBARS ------------------------------------- */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
/* ---- 16. RESPONSIVE ------------------------------------- */
@media (max-width: 900px) {
.app-shell { grid-template-columns: 60px 1fr; }
.sidebar { padding: 12px 6px; }
.sidebar-brand span,
.nav-item > span:first-of-type,
.nav-count,
.sidebar-section-header { display: none; }
.nav-item { justify-content: center; padding: 8px; }
.topbar { padding: 12px 16px; }
.content { padding: 0 16px 16px; }
.content-header { padding-top: 16px; }
.slideover { width: 100%; }
/* On a narrow viewport the slideover fills the screen — no point
pushing the shell, the table isn't visible anyway. */
body:has(.slideover.is-open) #appShell { margin-right: 0; margin-left: 0; }
}
/* ---- 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: calc(var(--hdr-h, 0px) + var(--pag-h, 0px));
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; }
/* NAME stops growing past 360px so it doesn't push Username/Folder/
Updated all the way to the right. Long names ellipsise on a single
line so the avatar never falls under the title. */
.col-name {
min-width: 180px;
width: auto; /* shrinks to short names, grows to fit
long ones — no fixed cap, the column
matches its widest entry. */
white-space: nowrap;
padding-right: 32px; /* extra breathing room so the longest
title never butts up against Username. */
}
.col-name .cell-name-wrap {
display: inline-flex;
align-items: center;
gap: 6px;
vertical-align: middle;
}
.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; }
/* Soak the remaining horizontal space so NAME/Username/Folder stay
close to each other on the left rather than spreading out. */
.col-actions { width: 100%; min-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; }
/* Folder edit (pencil) button — same hover-reveal as the delete X. */
.nav-item .folder-edit {
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-edit svg { width: 13px; height: 13px; display: block; }
.nav-item:hover .folder-edit { opacity: 0.7; }
.nav-item .folder-edit:hover {
opacity: 1;
color: var(--accent);
background: var(--accent-soft);
}
/* When folder-edit is rendered before folder-delete in the DOM, push delete
to the far right so the layout stays {icon} {label} … {edit} {delete}. */
.nav-item .folder-edit + .folder-delete { margin-left: 2px; }
/* ============================================================
FOLDER CUSTOMIZATION MODAL
============================================================ */
#folderModal .form-field { margin-bottom: 14px; }
#folderModal .form-field label {
display: block;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-faint);
margin-bottom: 4px;
}
#folderModal .so-input { width: 100%; }
/* ============================================================
ATTACHMENTS list inside the slideover
============================================================ */
.so-attach-list {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.so-attach-empty {
font-size: 12px;
color: var(--text-faint);
padding: 6px 2px;
font-style: italic;
}
.so-attach-row {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
}
.so-attach-info {
flex: 1;
min-width: 0;
}
.so-attach-name {
font-size: 13px;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.so-attach-meta {
font-size: 11px;
color: var(--text-faint);
margin-top: 1px;
}
.so-attach-row .icon-btn { flex-shrink: 0; }
.folder-swatch-row,
.folder-icon-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 6px;
}
.folder-swatch {
width: 26px; height: 26px;
border-radius: 50%;
border: 2px solid transparent;
background: var(--bg-elev);
cursor: pointer;
padding: 0;
transition: transform var(--t-fast), border-color var(--t-fast);
}
.folder-swatch:hover { transform: scale(1.1); }
.folder-swatch.is-active {
border-color: var(--text);
box-shadow: 0 0 0 2px var(--bg);
}
.folder-swatch.is-default {
background:
linear-gradient(135deg, transparent 47%, var(--danger) 47% 53%, transparent 53%),
var(--bg-elev);
}
.folder-icon-btn {
width: 36px; height: 36px;
display: inline-flex; align-items: center; justify-content: center;
border: 1px solid var(--border);
background: var(--bg-elev);
border-radius: 6px;
cursor: pointer;
color: var(--text-dim);
transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.folder-icon-btn svg { width: 18px; height: 18px; }
.folder-icon-btn:hover { background: var(--bg-elev-2); color: var(--text); }
.folder-icon-btn.is-active {
border-color: var(--accent);
color: var(--accent);
background: var(--accent-soft);
}
.folder-preview {
display: flex; align-items: center; gap: 10px;
margin: 16px 0 4px;
padding: 12px 14px;
background: var(--bg-elev);
border-radius: 6px;
color: var(--text);
font-weight: 500;
}
.folder-preview svg { width: 18px; height: 18px; }
/* 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 bar (all view modes). Sticky so the page buttons stay reachable
while scrolling a long page; z-index beats the sticky table header, which
is pushed down by --pag-h (measured in renderEntries). */
.pagination {
position: sticky;
top: var(--hdr-h, 0px);
z-index: 2;
background: var(--bg);
display: flex; align-items: center; gap: 6px;
padding: 14px 12px 10px;
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;
}
/* ============================================================
PRINTABLE RECOVERY CODE — only renders on the printed page
============================================================ */
#printRecoveryArea { display: none; }
@media print {
/* Hide the running app entirely; show only the printable sheet. */
body > *:not(#printRecoveryArea) { display: none !important; }
#printRecoveryArea {
display: block !important;
position: static;
background: #fff !important;
color: #000 !important;
}
.prc-sheet {
font-family: Georgia, 'Times New Roman', serif;
padding: 40px;
max-width: 700px;
margin: 0 auto;
}
.prc-sheet h1 {
font-size: 22px;
margin: 0 0 18px;
border-bottom: 2px solid #000;
padding-bottom: 8px;
}
.prc-meta {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-bottom: 28px;
}
.prc-code {
font-family: 'Courier New', monospace;
font-size: 32px;
letter-spacing: 6px;
text-align: center;
padding: 24px;
border: 2px dashed #000;
margin: 28px 0;
font-weight: bold;
}
.prc-instructions {
font-size: 13px;
line-height: 1.6;
}
.prc-instructions ol { padding-left: 22px; }
@page { margin: 1.5cm; }
}