c1d0eb3891
Same discriminant as the Android client - "/api/v1/repos/" in the URL - so one address tells both ends which transport to speak, and nothing new appears in Settings beyond a placeholder and a warning. The bridge stays shared. Three things differ, and they are confined: the header is "token <PAT>" rather than Basic (Gitea answers a silent 401 to the other), a PUT carries a JSON envelope with the file base64-encoded inside it, and the connection test uses GET because the contents API does not answer HEAD. The sha does the ETag's whole job. It comes back from the pull, rides in the PUT body instead of an If-Match, and a stale one earns a 409 - handled exactly like WebDAV's 412, by re-running pull->merge->push so the other device's changes are folded in rather than clobbered. On a first push `sha` is OMITTED rather than sent empty: Gitea reads its presence as "replace that exact version". `branch` is omitted too, so the repo's default is used instead of a name we would be guessing. A failing PUT now reads its response body: Gitea explains a 409 or a 422 there, and without it the error was just a number. The test button checks the SHAPE of what came back, not only the status. A URL one path segment off answers 200 with valid JSON that is not a file - which would otherwise surface much later as "wrong sync password" and send you looking on the wrong side. Four tests cover that parsing, including the line-wrapped base64 Gitea sends and the array an accidental directory URL returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1252 lines
73 KiB
HTML
1252 lines
73 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" data-theme="dark">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Vault</title>
|
||
<link rel="stylesheet" href="css/style.css">
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- ICON SPRITE (Lucide subset) -->
|
||
<!-- ============================================================ -->
|
||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
|
||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||
<symbol id="i-unlock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 9.9-1"/></symbol>
|
||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></symbol>
|
||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5v14"/></symbol>
|
||
<symbol id="i-folder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></symbol>
|
||
<symbol id="i-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></symbol>
|
||
<symbol id="i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></symbol>
|
||
<symbol id="i-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></symbol>
|
||
<symbol id="i-eye-off" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61M2 2l20 20"/></symbol>
|
||
<symbol id="i-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></symbol>
|
||
<symbol id="i-edit" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></symbol>
|
||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></symbol>
|
||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z"/></symbol>
|
||
<symbol id="i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></symbol>
|
||
<symbol id="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"/></symbol>
|
||
<symbol id="i-globe" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10Z"/></symbol>
|
||
<symbol id="i-dice" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="16" cy="8" r="1.2" fill="currentColor"/><circle cx="12" cy="12" r="1.2" fill="currentColor"/><circle cx="8" cy="16" r="1.2" fill="currentColor"/><circle cx="16" cy="16" r="1.2" fill="currentColor"/></symbol>
|
||
<symbol id="i-tag" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.59 13.41 13.42 20.58a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82Z"/><line x1="7" y1="7" x2="7.01" y2="7"/></symbol>
|
||
<symbol id="i-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></symbol>
|
||
<symbol id="i-log-in" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" y1="12" x2="3" y2="12"/></symbol>
|
||
<symbol id="i-rotate-ccw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></symbol>
|
||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></symbol>
|
||
<symbol id="i-command" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3Z"/></symbol>
|
||
<symbol id="i-log-out" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></symbol>
|
||
<symbol id="i-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></symbol>
|
||
<symbol id="i-list" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></symbol>
|
||
<symbol id="i-table" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></symbol>
|
||
<symbol id="i-more" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1.4" fill="currentColor"/><circle cx="12" cy="12" r="1.4" fill="currentColor"/><circle cx="12" cy="19" r="1.4" fill="currentColor"/></symbol>
|
||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></symbol>
|
||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></symbol>
|
||
<symbol id="i-key" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 2-9.6 9.6"/><circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-2 2 2 2-3 3-2-2"/></symbol>
|
||
<symbol id="i-user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
|
||
<symbol id="i-printer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></symbol>
|
||
<symbol id="i-pin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></symbol>
|
||
<symbol id="i-paperclip" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05 12.25 20.24a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></symbol>
|
||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></symbol>
|
||
<symbol id="i-empty-vault" viewBox="0 0 120 120" fill="none">
|
||
<circle cx="60" cy="60" r="50" fill="var(--accent-soft)"/>
|
||
<rect x="40" y="50" width="40" height="35" rx="4" fill="var(--bg-elev)" stroke="var(--accent)" stroke-width="2"/>
|
||
<path d="M48 50V40a12 12 0 0 1 24 0v10" fill="none" stroke="var(--accent)" stroke-width="2" stroke-linecap="round"/>
|
||
<circle cx="60" cy="65" r="3" fill="var(--accent)"/>
|
||
<line x1="60" y1="68" x2="60" y2="74" stroke="var(--accent)" stroke-width="2" stroke-linecap="round"/>
|
||
</symbol>
|
||
<symbol id="i-empty-trash" viewBox="0 0 120 120" fill="none">
|
||
<circle cx="60" cy="60" r="50" fill="var(--accent-soft)"/>
|
||
<path d="M40 45h40M48 45v-5a3 3 0 0 1 3-3h18a3 3 0 0 1 3 3v5M44 45v35a4 4 0 0 0 4 4h24a4 4 0 0 0 4-4V45" stroke="var(--accent)" stroke-width="2" stroke-linecap="round" fill="none"/>
|
||
<path d="M54 55v22M66 55v22" stroke="var(--accent)" stroke-width="2" stroke-linecap="round"/>
|
||
</symbol>
|
||
<symbol id="i-empty-search" viewBox="0 0 120 120" fill="none">
|
||
<circle cx="60" cy="60" r="50" fill="var(--accent-soft)"/>
|
||
<circle cx="55" cy="55" r="14" stroke="var(--accent)" stroke-width="2.5" fill="none"/>
|
||
<path d="m66 66 11 11" stroke="var(--accent)" stroke-width="2.5" stroke-linecap="round"/>
|
||
</symbol>
|
||
</svg>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- TOAST CONTAINER -->
|
||
<!-- ============================================================ -->
|
||
<div id="toastContainer" class="toast-container"></div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- IDLE WARNING (auto-lock countdown) -->
|
||
<!-- ============================================================ -->
|
||
<div id="idleWarning" class="idle-warning is-hidden">
|
||
<svg class="idle-icon"><use href="#i-lock"/></svg>
|
||
<h3>Auto-lock</h3>
|
||
<p>Vault locks in <b id="idleCountdown">30</b>s due to inactivity</p>
|
||
<button class="btn btn-primary btn-block" id="idleStayBtn">Stay unlocked</button>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- AUTH SCREEN (login + register) -->
|
||
<!-- ============================================================ -->
|
||
<section id="authScreen" class="auth-screen">
|
||
<div class="auth-card">
|
||
<div class="auth-brand">
|
||
<svg class="auth-brand-icon"><use href="#i-lock"/></svg>
|
||
<h1>Vault</h1>
|
||
<p>Your passwords, encrypted locally.</p>
|
||
</div>
|
||
|
||
<div class="auth-tabs">
|
||
<button class="auth-tab is-active" data-tab="login">Login</button>
|
||
<button class="auth-tab" data-tab="register">Register</button>
|
||
</div>
|
||
|
||
<div id="authHint" class="auth-hint"></div>
|
||
|
||
<form id="loginForm" class="auth-form" autocomplete="off">
|
||
<label class="field">
|
||
<span>Username</span>
|
||
<input id="loginUsername" type="text" required>
|
||
</label>
|
||
<label class="field" id="loginPasswordField">
|
||
<span>Master password</span>
|
||
<div class="input-with-action">
|
||
<input id="loginPassword" type="password" required>
|
||
<button type="button" class="icon-btn" id="loginPwToggle" title="Show/hide password">
|
||
<svg><use href="#i-eye"/></svg>
|
||
</button>
|
||
</div>
|
||
</label>
|
||
<label class="field" id="loginPinField" style="display:none">
|
||
<span>PIN</span>
|
||
<input id="loginPin" type="password" inputmode="numeric"
|
||
autocomplete="off" maxlength="12">
|
||
</label>
|
||
<button type="button" id="loginUseMasterBtn" class="btn btn-link btn-block"
|
||
style="font-size:12px;margin-top:-4px;display:none">
|
||
Use master password instead
|
||
</button>
|
||
<label class="remember-row">
|
||
<input type="checkbox" id="loginRememberUser">
|
||
<span>Remember username on this device</span>
|
||
</label>
|
||
<button id="loginBtn" type="submit" class="btn btn-primary btn-block">
|
||
<svg><use href="#i-unlock"/></svg>
|
||
Unlock
|
||
</button>
|
||
<button id="recoveryBtn" type="button" class="btn btn-link btn-block" style="font-size:12px;margin-top:4px">
|
||
Forgot master password? Use a recovery code
|
||
</button>
|
||
</form>
|
||
|
||
<form id="registerForm" class="auth-form is-hidden" autocomplete="off">
|
||
<label class="field">
|
||
<span>Username <span class="hint">min 3 chars</span></span>
|
||
<input id="regUsername" type="text" required minlength="3">
|
||
</label>
|
||
<label class="field">
|
||
<span>Master password <span class="hint">min 8 chars</span></span>
|
||
<input id="regPassword" type="password" required minlength="8">
|
||
<div class="strength-bar" id="regStrengthBar"></div>
|
||
</label>
|
||
<button id="registerBtn" type="submit" class="btn btn-primary btn-block">
|
||
<svg><use href="#i-plus"/></svg>
|
||
Create vault
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- APP SHELL (sidebar + main + slide-over) -->
|
||
<!-- ============================================================ -->
|
||
<div id="appShell" class="app-shell is-hidden">
|
||
|
||
<!-- ---- SIDEBAR ----------------------------------------- -->
|
||
<aside class="sidebar">
|
||
<div class="sidebar-brand">
|
||
<svg><use href="#i-lock"/></svg>
|
||
<span>Vault</span>
|
||
</div>
|
||
|
||
<nav class="sidebar-nav">
|
||
<button class="nav-item is-active" data-view="all">
|
||
<svg><use href="#i-globe"/></svg>
|
||
<span>All items</span>
|
||
<span class="nav-count" id="countAll">0</span>
|
||
</button>
|
||
<button class="nav-item" data-view="favorites">
|
||
<svg><use href="#i-star"/></svg>
|
||
<span>Favorites</span>
|
||
<span class="nav-count" id="countFav">0</span>
|
||
</button>
|
||
<button class="nav-item" data-view="notes">
|
||
<svg><use href="#i-edit"/></svg>
|
||
<span>Notes</span>
|
||
<span class="nav-count" id="countNotes">0</span>
|
||
</button>
|
||
<button class="nav-item" id="sidebarRecentBtn">
|
||
<svg><use href="#i-rotate-ccw"/></svg>
|
||
<span>Recently used</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<div class="sidebar-section" data-section="folders">
|
||
<div class="sidebar-section-header">
|
||
<button class="section-toggle" data-section-toggle="folders">
|
||
<svg class="section-chevron"><use href="#i-chevron-down"/></svg>
|
||
<span>Folders</span>
|
||
<span class="section-count" id="countFolders">0</span>
|
||
</button>
|
||
<button class="icon-btn icon-btn-sm" id="addFolderBtn" title="New folder">
|
||
<svg><use href="#i-plus"/></svg>
|
||
</button>
|
||
</div>
|
||
<nav class="sidebar-nav section-body" id="foldersList"></nav>
|
||
</div>
|
||
|
||
<div class="sidebar-section" data-section="tags">
|
||
<div class="sidebar-section-header">
|
||
<button class="section-toggle" data-section-toggle="tags">
|
||
<svg class="section-chevron"><use href="#i-chevron-down"/></svg>
|
||
<span>Tags</span>
|
||
<span class="section-count" id="countTags">0</span>
|
||
</button>
|
||
</div>
|
||
<nav class="sidebar-nav section-body" id="tagsList"></nav>
|
||
</div>
|
||
|
||
<div class="sidebar-section" data-section="tools">
|
||
<div class="sidebar-section-header">
|
||
<button class="section-toggle" data-section-toggle="tools">
|
||
<svg class="section-chevron"><use href="#i-chevron-down"/></svg>
|
||
<span>Tools</span>
|
||
</button>
|
||
</div>
|
||
<nav class="sidebar-nav section-body">
|
||
<button class="nav-item" id="sidebarGenBtn">
|
||
<svg><use href="#i-dice"/></svg>
|
||
<span>Generator</span>
|
||
</button>
|
||
<button class="nav-item" id="sidebarAuthenticatorBtn">
|
||
<svg><use href="#i-shield"/></svg>
|
||
<span>Authenticator</span>
|
||
</button>
|
||
<button class="nav-item" id="sidebarTotpToolBtn">
|
||
<svg><use href="#i-key"/></svg>
|
||
<span>TOTP generator</span>
|
||
</button>
|
||
<button class="nav-item" id="sidebarHealthBtn">
|
||
<svg><use href="#i-alert"/></svg>
|
||
<span>Vault health</span>
|
||
</button>
|
||
<button class="nav-item" id="sidebarAuditBtn">
|
||
<svg><use href="#i-list"/></svg>
|
||
<span>Audit log</span>
|
||
</button>
|
||
<button class="nav-item" id="sidebarImportBtn">
|
||
<svg><use href="#i-log-in"/></svg>
|
||
<span>Import vault</span>
|
||
</button>
|
||
<button class="nav-item" id="sidebarExportBtn">
|
||
<svg><use href="#i-log-out"/></svg>
|
||
<span>Export vault</span>
|
||
</button>
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="sidebar-bottom">
|
||
<button class="nav-item" data-view="trash">
|
||
<svg><use href="#i-trash"/></svg>
|
||
<span>Trash</span>
|
||
<span class="nav-count" id="countTrash">0</span>
|
||
</button>
|
||
<button class="nav-item" id="settingsBtn">
|
||
<svg><use href="#i-settings"/></svg>
|
||
<span>Settings</span>
|
||
</button>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ---- MAIN -------------------------------------------- -->
|
||
<main class="main">
|
||
<header class="topbar">
|
||
<div class="search">
|
||
<svg><use href="#i-search"/></svg>
|
||
<input id="searchInput" type="search" placeholder="Search…" autocomplete="off">
|
||
<kbd>Ctrl+K</kbd>
|
||
<div id="searchHistoryMenu" class="search-history is-hidden"></div>
|
||
</div>
|
||
<div class="topbar-actions">
|
||
<div class="view-toggle">
|
||
<button class="view-btn is-active" id="viewCardsBtn" data-view="cards" title="Cards view">
|
||
<svg><use href="#i-grid"/></svg>
|
||
</button>
|
||
<button class="view-btn" id="viewListBtn" data-view="list" title="List view">
|
||
<svg><use href="#i-list"/></svg>
|
||
</button>
|
||
<button class="view-btn" id="viewTableBtn" data-view="table" title="Table view">
|
||
<svg><use href="#i-table"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="filters-wrap">
|
||
<button class="icon-btn" id="filtersBtn" title="Filters">
|
||
<svg><use href="#i-list"/></svg>
|
||
<span class="filters-count" id="filtersCount" style="display:none">0</span>
|
||
</button>
|
||
<div class="filters-menu is-hidden" id="filtersMenu"></div>
|
||
</div>
|
||
<button class="icon-btn" id="cheatsheetBtn" title="Keyboard shortcuts (?)">
|
||
<span style="font-weight:700;font-size:14px">?</span>
|
||
</button>
|
||
<button class="icon-btn" id="themeBtn" title="Toggle theme">
|
||
<svg class="theme-icon theme-icon-dark"><use href="#i-sun"/></svg>
|
||
<svg class="theme-icon theme-icon-light"><use href="#i-moon"/></svg>
|
||
</button>
|
||
<div class="new-entry-wrap">
|
||
<button class="btn btn-primary btn-sm" id="newEntryBtn">
|
||
<svg><use href="#i-plus"/></svg>
|
||
New
|
||
</button>
|
||
<button class="btn btn-primary btn-sm new-entry-caret" id="newEntryCaretBtn"
|
||
title="Choose entry type">
|
||
<svg><use href="#i-chevron-down"/></svg>
|
||
</button>
|
||
<div class="new-entry-menu is-hidden" id="newEntryMenu">
|
||
<button class="dropdown-item" data-new-kind="login">
|
||
<svg><use href="#i-key"/></svg>
|
||
<span>New login</span>
|
||
</button>
|
||
<button class="dropdown-item" data-new-kind="note">
|
||
<svg><use href="#i-edit"/></svg>
|
||
<span>New note</span>
|
||
</button>
|
||
<div class="dropdown-sep"></div>
|
||
<div class="dropdown-section">Templates</div>
|
||
<button class="dropdown-item" data-new-template="credit-card">
|
||
<svg><use href="#i-key"/></svg>
|
||
<span>Credit card</span>
|
||
</button>
|
||
<button class="dropdown-item" data-new-template="ssh-key">
|
||
<svg><use href="#i-shield"/></svg>
|
||
<span>SSH key</span>
|
||
</button>
|
||
<button class="dropdown-item" data-new-template="server">
|
||
<svg><use href="#i-globe"/></svg>
|
||
<span>Server credentials</span>
|
||
</button>
|
||
<button class="dropdown-item" data-new-template="recovery-codes">
|
||
<svg><use href="#i-shield"/></svg>
|
||
<span>Recovery codes (note)</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="user-menu">
|
||
<button class="user-chip" id="userChip">
|
||
<span class="user-avatar" id="userAvatar"></span>
|
||
<span id="userName">user</span>
|
||
</button>
|
||
<div class="user-dropdown is-hidden" id="userDropdown">
|
||
<button class="dropdown-item" id="dropdownSettingsBtn">
|
||
<svg><use href="#i-settings"/></svg> Settings
|
||
</button>
|
||
<button class="dropdown-item" id="lockBtn">
|
||
<svg><use href="#i-lock"/></svg> Lock vault
|
||
</button>
|
||
<button class="dropdown-item" id="logoutBtn">
|
||
<svg><use href="#i-log-out"/></svg> Sign out
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="content">
|
||
<div class="content-header">
|
||
<h2 id="contentTitle">All items</h2>
|
||
<span class="content-meta" id="contentMeta">0 items</span>
|
||
</div>
|
||
|
||
<!-- Active filter chips (populated by renderFilterChips) -->
|
||
<div id="filterChips" class="filter-chips is-hidden"></div>
|
||
|
||
<!-- Entry grid -->
|
||
<div id="entryGrid" class="entry-grid"></div>
|
||
|
||
<!-- Empty state — illustration + message are filled dynamically -->
|
||
<div id="emptyState" class="empty-state is-hidden">
|
||
<svg class="empty-illustration" id="emptyIllustration"><use href="#i-empty-vault"/></svg>
|
||
<h3 id="emptyTitle">Nothing here yet</h3>
|
||
<p id="emptyMessage">Click <b>+ New</b> to add your first password.</p>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- ---- SLIDE-OVER DETAIL ------------------------------- -->
|
||
<aside class="slideover" id="slideover">
|
||
<header class="slideover-header">
|
||
<h3 id="slideoverTitle">Entry</h3>
|
||
<button class="icon-btn" id="slideoverClose"><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<div class="slideover-body" id="slideoverBody"></div>
|
||
</aside>
|
||
|
||
<!-- ---- SETTINGS SLIDE-OVER ----------------------------- -->
|
||
<aside class="slideover" id="settingsPanel">
|
||
<header class="slideover-header">
|
||
<h3>Settings</h3>
|
||
<button class="icon-btn" id="settingsClose"><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<div class="settings-search-wrap">
|
||
<svg class="settings-search-icon"><use href="#i-search"/></svg>
|
||
<input type="text" id="settingsSearch" class="settings-search-input"
|
||
placeholder="Search settings… (Esc to clear)" />
|
||
<button class="settings-search-clear" id="settingsSearchClear" type="button"
|
||
title="Clear search">
|
||
<svg><use href="#i-x"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="settings-main">
|
||
<div class="settings-tabs" id="settingsTabs">
|
||
<button class="settings-tab is-active" data-tab="general"><svg><use href="#i-settings"/></svg>General</button>
|
||
<button class="settings-tab" data-tab="security"><svg><use href="#i-shield"/></svg>Security</button>
|
||
<button class="settings-tab" data-tab="autofill"><svg><use href="#i-key"/></svg>Autofill</button>
|
||
<button class="settings-tab" data-tab="account"><svg><use href="#i-user"/></svg>Account</button>
|
||
<button class="settings-tab" data-tab="sync"><svg><use href="#i-rotate-ccw"/></svg>Sync & Backup</button>
|
||
</div>
|
||
<div class="slideover-body" id="settingsBody">
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Getting started</div>
|
||
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||
A quick guided tour of the main features — search, autofill,
|
||
tools and sync.
|
||
</p>
|
||
<button class="btn btn-ghost btn-sm" id="startTourBtn">
|
||
<svg><use href="#i-log-in"/></svg> How it works
|
||
</button>
|
||
</div>
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Appearance</div>
|
||
<div class="setting-row">
|
||
<span>Theme</span>
|
||
<select id="settingTheme">
|
||
<option value="dark">Dark</option>
|
||
<option value="light">Light</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>Sort entries by</span>
|
||
<select id="settingSort">
|
||
<option value="name:asc">Name (A → Z)</option>
|
||
<option value="name:desc">Name (Z → A)</option>
|
||
<option value="updated:desc">Most recent first</option>
|
||
<option value="updated:asc">Oldest update first</option>
|
||
<option value="created:desc">Recently created</option>
|
||
<option value="created:asc">Oldest created</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Editor position
|
||
<small class="setting-hint">Where the add / edit panel and Settings appear. Affects both side panels.</small>
|
||
</span>
|
||
<select id="settingEditorPosition">
|
||
<option value="right">Slide-over (right)</option>
|
||
<option value="left">Slide-over (left)</option>
|
||
<option value="center">Centered modal</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Confirm before closing unsaved edits
|
||
<small class="setting-hint">When the entry editor has changes, ask before dismissing it via X, Esc, or click-outside.</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingConfirmUnsaved">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>Compact action menu (⋯)</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingCompact">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>Mask usernames (ab***)</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingMaskUser">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Show site / URL
|
||
<small class="setting-hint">On cards: shown under the display name when the entry has a custom title. In table view: adds a Site column.</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingShowSite">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row" id="settingFaviconsRow">
|
||
<span>
|
||
Fetch website icons
|
||
<small class="setting-hint">
|
||
Loads a small icon for each entry via DuckDuckGo
|
||
(privacy-friendly proxy). Each entry's domain is
|
||
sent to <b>icons.duckduckgo.com</b> once and cached
|
||
locally. OFF by default.
|
||
</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingFavicons">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div id="settingFaviconActionsRow" style="display:flex;gap:6px;margin-top:6px">
|
||
<button class="btn btn-ghost btn-sm" id="settingFaviconsRefresh">
|
||
<svg><use href="#i-rotate-ccw"/></svg> Fetch missing
|
||
</button>
|
||
<button class="btn btn-ghost btn-sm" id="settingFaviconsRefreshAll">
|
||
Re-fetch all
|
||
</button>
|
||
<button class="btn btn-ghost btn-sm is-danger" id="settingFaviconsClear">
|
||
<svg><use href="#i-trash"/></svg> Clear cache
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Startup & tray</div>
|
||
<div class="setting-row" id="settingTrayNotifRow">
|
||
<span>
|
||
Show tray notifications
|
||
<small class="setting-hint">
|
||
The first time the app minimises to the tray
|
||
it shows a small balloon explaining how to
|
||
restore it. Turn off if you've seen it.
|
||
</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingTrayNotif">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row" id="settingAutoStartRow">
|
||
<span>
|
||
Start with Windows
|
||
<small class="setting-hint">
|
||
Launch this app in the tray when you sign in to
|
||
Windows — so the autofill hotkeys are ready
|
||
immediately. Device-only setting (per Windows user).
|
||
</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingAutoStart">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Security</div>
|
||
<div class="setting-row">
|
||
<span>Auto-lock after (minutes)</span>
|
||
<select id="settingAutoLock">
|
||
<option value="0">Never</option>
|
||
<option value="1">1</option>
|
||
<option value="5">5</option>
|
||
<option value="15">15</option>
|
||
<option value="30">30</option>
|
||
<option value="60">60</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Lock vault when Windows locks or sleeps
|
||
<small class="setting-hint">
|
||
With Quick Unlock enabled the vault stays unlocked
|
||
anyway — Windows sign-in already gates access.
|
||
</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingLockOnSystemLock">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>Ask before moving to trash</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingAskDelete">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Auto-purge trash after
|
||
<small class="setting-hint">
|
||
Permanently delete entries that have been in
|
||
the trash for longer than this. Runs at every
|
||
unlock.
|
||
</small>
|
||
</span>
|
||
<select id="settingTrashPurge">
|
||
<option value="0">Never</option>
|
||
<option value="7">7 days</option>
|
||
<option value="30">30 days</option>
|
||
<option value="90">90 days</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Flag aged passwords
|
||
<small class="setting-hint">Show an "Aged" badge on entries whose password hasn't changed in this many days.</small>
|
||
</span>
|
||
<select id="settingPasswordExpiry">
|
||
<option value="0">Off</option>
|
||
<option value="90">90 days</option>
|
||
<option value="180">180 days</option>
|
||
<option value="365">1 year</option>
|
||
<option value="730">2 years</option>
|
||
</select>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Check passwords against breach database (HIBP)
|
||
<small class="setting-hint">Only the first 5 chars of the SHA-1 are sent — your passwords never leave the machine.</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingHIBP">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Autofill</div>
|
||
<div class="setting-row" id="settingAutofillRow">
|
||
<span>
|
||
Autofill with global hotkey
|
||
<small class="setting-hint">
|
||
Press your hotkey in any browser to fill the matching
|
||
login. The vault must be unlocked.
|
||
</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingAutofill">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Clear the field before typing
|
||
<small class="setting-hint">
|
||
Sends Ctrl+A + Del to empty the target field first.
|
||
Disable for apps where Ctrl+A isn't "select all"
|
||
(terminals, some remote desktops).
|
||
</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingAutofillClear">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span>
|
||
Tray alert when autofill is blocked
|
||
<small class="setting-hint">
|
||
Shows a Windows balloon if the fill fails while the
|
||
app is hidden (e.g. the target runs as administrator).
|
||
</small>
|
||
</span>
|
||
<label class="toggle">
|
||
<input type="checkbox" id="settingAutofillFailBalloon">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
</div>
|
||
<div id="settingAutofillHotkeysRow" style="margin-top:8px">
|
||
<div class="setting-row">
|
||
<span style="font-size:12px;color:var(--text-dim)">
|
||
Full fill (username + Tab + password)
|
||
</span>
|
||
<button class="btn btn-ghost btn-sm hotkey-capture-btn" id="settingAutofillFullCombo">Ctrl+Shift+L</button>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span style="font-size:12px;color:var(--text-dim)">
|
||
Password only (for step-2 forms / unlock screens)
|
||
</span>
|
||
<button class="btn btn-ghost btn-sm hotkey-capture-btn" id="settingAutofillPwdCombo">Ctrl+Shift+P</button>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span style="font-size:12px;color:var(--text-dim)">
|
||
2FA code only (click the 2FA field first)
|
||
</span>
|
||
<button class="btn btn-ghost btn-sm hotkey-capture-btn" id="settingAutofillTotpCombo">Ctrl+Shift+T</button>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span style="font-size:12px;color:var(--text-dim)">
|
||
Quick search picker (find entry, fill from anywhere)
|
||
</span>
|
||
<button class="btn btn-ghost btn-sm hotkey-capture-btn" id="settingQuickSearchCombo">Ctrl+Shift+Q</button>
|
||
</div>
|
||
<div class="setting-row">
|
||
<span style="font-size:12px;color:var(--text-dim)">
|
||
New entry from the foreground window title
|
||
</span>
|
||
<button class="btn btn-ghost btn-sm hotkey-capture-btn" id="settingNewEntryCombo">Ctrl+Shift+A</button>
|
||
</div>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:6px 0 0;line-height:1.4">
|
||
Click a button, then press your new combo. Needs Ctrl, Alt or Win + a letter / digit / F-key.
|
||
</p>
|
||
<button class="btn btn-ghost btn-sm" id="settingAutofillResetHotkeys" style="margin-top:6px">
|
||
Reset to defaults
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Clipboard privacy</div>
|
||
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||
Passwords copied from this app are <b>excluded from Windows
|
||
clipboard history (Win+V)</b> and cloud clipboard, and cleared
|
||
automatically when the app is minimised — no manual setup needed.
|
||
</p>
|
||
<button class="btn btn-ghost btn-sm" id="openClipboardSettings">How?</button>
|
||
<div class="setting-row" style="margin-top:8px">
|
||
<span>
|
||
Auto-clear copied secrets after
|
||
<small class="setting-hint">Only clears if you haven't copied something else since.</small>
|
||
</span>
|
||
<select id="settingClipboardClear">
|
||
<option value="0">Never</option>
|
||
<option value="15">15 seconds</option>
|
||
<option value="30">30 seconds</option>
|
||
<option value="60">1 minute</option>
|
||
<option value="120">2 minutes</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Export</div>
|
||
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||
Download an <b>encrypted</b> backup of your vault. You'll
|
||
choose a password independent of your master password —
|
||
save it carefully, you need it to restore.
|
||
</p>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
||
<button class="btn btn-ghost btn-sm" id="exportBtn">
|
||
<svg><use href="#i-log-out"/></svg> Encrypted JSON
|
||
</button>
|
||
<button class="btn btn-ghost btn-sm is-danger" id="exportCsvBtn"
|
||
title="Plaintext! Use only for migration">
|
||
<svg><use href="#i-alert"/></svg> Plaintext CSV
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Import</div>
|
||
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||
Import a vault from a JSON export (this app) or a CSV file
|
||
(Bitwarden, KeePass, Chrome, 1Password…). Entries are added
|
||
to your current vault — duplicates are NOT removed.
|
||
</p>
|
||
<button class="btn btn-ghost btn-sm" id="importBtn">
|
||
<svg><use href="#i-log-in"/></svg> Import vault
|
||
</button>
|
||
</div>
|
||
|
||
<div class="slideover-field" id="syncField" style="display:none">
|
||
<div class="slideover-field-label">Sync (WebDAV or Gitea)</div>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.5">
|
||
Bidirectional sync with a WebDAV server (Nextcloud,
|
||
ownCloud, Apache mod_dav) <b>or a private Gitea
|
||
repository</b> — paste a contents-API URL and it switches
|
||
transport on its own. Auto-merge by entry
|
||
timestamp. <b>Master password is never sent to the
|
||
remote</b> — the snapshot is encrypted with a separate
|
||
sync password you set once per device (same on each).
|
||
</p>
|
||
<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||
<input type="checkbox" id="settingSyncEnabled">
|
||
<span>Enable sync</span>
|
||
</label>
|
||
<div id="syncConfig" style="display:none;border-left:2px solid var(--border);padding-left:10px;margin-left:4px">
|
||
<div style="margin-bottom:6px">
|
||
<div style="font-size:11px;color:var(--text-faint);margin-bottom:4px">URL — full path to the snapshot file, or a Gitea contents path</div>
|
||
<input type="text" id="syncUrl" class="so-input"
|
||
placeholder="https://git.example.com/api/v1/repos/OWNER/REPO/contents/vault-sync.json"
|
||
style="width:100%;font-size:12px">
|
||
</div>
|
||
<div style="display:flex;gap:8px;margin-bottom:6px">
|
||
<label style="flex:1">
|
||
<div style="font-size:11px;color:var(--text-faint);margin-bottom:4px">Username <span style="opacity:.7">(WebDAV only)</span></div>
|
||
<input type="text" id="syncUser" class="so-input" style="width:100%">
|
||
</label>
|
||
<label style="flex:1">
|
||
<div style="font-size:11px;color:var(--text-faint);margin-bottom:4px">Password / token</div>
|
||
<input type="password" id="syncPwd" class="so-input" style="width:100%">
|
||
</label>
|
||
</div>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.5">
|
||
On Gitea the username is ignored and the token needs
|
||
<b>write:repository</b> — a read-only one can pull but
|
||
never push. Each sync is a commit, and Git history is
|
||
kept: an old sync password stays attackable in earlier
|
||
versions, so prune the history if you rotate it.
|
||
</p>
|
||
<div style="margin-bottom:8px">
|
||
<button class="btn btn-ghost btn-sm" id="syncTestBtn">Test connection</button>
|
||
</div>
|
||
<label style="display:flex;align-items:center;gap:8px;margin:8px 0">
|
||
<input type="checkbox" id="syncPreBackup">
|
||
<span style="font-size:12px">Create a local backup before each sync</span>
|
||
</label>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.4">
|
||
Sync password ≠ master password. <b>Use the same one
|
||
on every device</b> — that's how the encrypted snapshot
|
||
round-trips. Stored DPAPI-protected locally.
|
||
</p>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:6px">
|
||
<button class="btn btn-ghost btn-sm" id="syncSetPwdBtn">Set sync password</button>
|
||
<span id="syncPwdStatus" style="font-size:11px;color:var(--text-faint)">Not set.</span>
|
||
</div>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:6px">
|
||
<button class="btn btn-primary btn-sm" id="syncNowBtn">Sync now</button>
|
||
<span id="syncStatus" style="font-size:11px;color:var(--accent);display:none"></span>
|
||
<span id="syncLast" style="font-size:11px;color:var(--text-faint)"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="slideover-field" id="autoBackupField" style="display:none">
|
||
<div class="slideover-field-label">Auto-backup</div>
|
||
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||
Encrypted JSON dropped silently into a folder of your
|
||
choice, on a schedule. Old backups beyond the retention
|
||
count are pruned automatically.
|
||
</p>
|
||
<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||
<input type="checkbox" id="settingAutoBackupEnabled">
|
||
<span>Enable auto-backup</span>
|
||
</label>
|
||
<div id="autoBackupConfig" style="display:none;border-left:2px solid var(--border);padding-left:10px;margin-left:4px">
|
||
<div style="margin-bottom:8px">
|
||
<div style="font-size:11px;color:var(--text-faint);margin-bottom:4px">Folder</div>
|
||
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
||
<code id="autoBackupDir" style="font-size:11px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--bg-2);padding:4px 6px;border-radius:4px">(not set)</code>
|
||
<button class="btn btn-ghost btn-sm" id="autoBackupPickDirBtn">Choose…</button>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:8px">
|
||
<label style="display:flex;flex-direction:column;gap:2px">
|
||
<span style="font-size:11px;color:var(--text-faint)">Every (days)</span>
|
||
<input type="number" id="settingAutoBackupInterval" min="1" max="365" value="7" style="width:80px">
|
||
</label>
|
||
<label style="display:flex;flex-direction:column;gap:2px">
|
||
<span style="font-size:11px;color:var(--text-faint)">Keep last N</span>
|
||
<input type="number" id="settingAutoBackupKeep" min="1" max="100" value="10" style="width:80px">
|
||
</label>
|
||
</div>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.4">
|
||
Backups are encrypted with a password you choose
|
||
(independent of your master password — you'll need it
|
||
to restore). Filename pattern: <code>vault-autobackup-YYYYMMDD-HHmmss.json</code>.
|
||
</p>
|
||
<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px">
|
||
<button class="btn btn-ghost btn-sm" id="autoBackupSetPwdBtn">Set backup password</button>
|
||
<span id="autoBackupPwdStatus" style="font-size:11px;display:inline-flex;align-items:center;gap:4px"></span>
|
||
</div>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">
|
||
<button class="btn btn-ghost btn-sm" id="autoBackupNowBtn">
|
||
<svg><use href="#i-log-out"/></svg> Backup now
|
||
</button>
|
||
<span id="autoBackupLast" style="font-size:11px;color:var(--text-faint)"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Account</div>
|
||
<div style="display:flex;align-items:center;gap:12px;margin:0 0 10px">
|
||
<span class="user-avatar user-avatar-lg" id="settingAvatarPreview"></span>
|
||
<div style="display:flex;flex-direction:column;gap:4px">
|
||
<p style="font-size:12px;color:var(--text-dim);margin:0">
|
||
Signed in as <b id="settingUser"></b>
|
||
</p>
|
||
<div style="display:flex;gap:6px">
|
||
<button class="btn btn-ghost btn-sm" id="settingAvatarUpload">
|
||
<svg><use href="#i-user"/></svg> Change picture
|
||
</button>
|
||
<button class="btn btn-ghost btn-sm" id="settingAvatarRemove" style="display:none;color:var(--danger)">
|
||
Remove
|
||
</button>
|
||
</div>
|
||
<input type="file" id="settingAvatarInput" accept="image/*" style="display:none">
|
||
</div>
|
||
</div>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px" id="settingVersionRow">
|
||
PMServer <span id="settingVersion">—</span>
|
||
</p>
|
||
<button class="btn btn-ghost btn-sm" id="changeMasterBtn">
|
||
<svg><use href="#i-lock"/></svg> Change master password
|
||
</button>
|
||
</div>
|
||
|
||
<div class="slideover-field" id="pinUnlockField" style="display:none">
|
||
<div class="slideover-field-label">PIN unlock</div>
|
||
<p id="pinStatus" style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||
No PIN set.
|
||
</p>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.4">
|
||
Shortcut unlock with a 4–12 digit PIN. Stored DPAPI-encrypted on
|
||
this device. <b>Master password is still required for sensitive
|
||
actions</b> (export, change master password, recovery code…).
|
||
</p>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap;margin:0 0 8px">
|
||
<button class="btn btn-ghost btn-sm" id="pinSetBtn">Set PIN</button>
|
||
<button class="btn btn-ghost btn-sm is-danger" id="pinRemoveBtn" style="display:none">Remove PIN</button>
|
||
</div>
|
||
<div class="setting-row" style="margin:0">
|
||
<span>
|
||
Unlock method
|
||
<small class="setting-hint">Master pw (default), PIN only, or both required.</small>
|
||
</span>
|
||
<select id="settingUnlockMode">
|
||
<option value="pw">Master password</option>
|
||
<option value="pin">PIN only</option>
|
||
<option value="both">Master password + PIN</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Quick unlock</div>
|
||
<p id="quickUnlockStatus" style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||
Disabled.
|
||
</p>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.4">
|
||
Unlock the vault on this device without retyping your
|
||
master password. The key is stored encrypted with
|
||
Windows DPAPI (tied to your Windows account) — convenient
|
||
on a personal machine, not safe on a shared one.
|
||
</p>
|
||
<button class="btn btn-ghost btn-sm" id="quickUnlockToggleBtn">
|
||
Enable on this device
|
||
</button>
|
||
</div>
|
||
|
||
<div class="slideover-field">
|
||
<div class="slideover-field-label">Recovery key</div>
|
||
<p id="recoveryStatus" style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||
No recovery key set.
|
||
</p>
|
||
<p style="font-size:11px;color:var(--text-faint);margin:0 0 8px;line-height:1.4">
|
||
A single-use code that lets you recover access if you
|
||
forget your master password. Generate it once, save the
|
||
code somewhere offline — the server never sees it again.
|
||
</p>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
||
<button class="btn btn-ghost btn-sm" id="recoverySetupBtn">
|
||
Generate recovery code
|
||
</button>
|
||
<button class="btn btn-ghost btn-sm is-danger" id="recoveryRemoveBtn" style="display:none">
|
||
Remove
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- MODAL: Re-auth (for export and other sensitive ops) -->
|
||
<!-- ============================================================ -->
|
||
<div id="reauthModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-panel modal-panel-sm">
|
||
<header class="modal-header">
|
||
<h3 id="reauthTitle">Confirm master password</h3>
|
||
<button class="icon-btn" data-close><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<form id="reauthForm" class="modal-body" autocomplete="off">
|
||
<p style="margin:0 0 12px;color:var(--text-dim);font-size:13px" id="reauthMessage">
|
||
This action requires your master password.
|
||
</p>
|
||
<label class="field">
|
||
<span>Master password</span>
|
||
<input id="reauthPassword" type="password" required autofocus>
|
||
</label>
|
||
<p id="reauthError" class="is-hidden"
|
||
style="margin:8px 0 0;color:var(--danger);font-size:12px"></p>
|
||
</form>
|
||
<footer class="modal-footer">
|
||
<button type="button" class="btn btn-ghost" data-close>Cancel</button>
|
||
<button type="submit" form="reauthForm" class="btn btn-primary" id="reauthConfirm">
|
||
<svg><use href="#i-check"/></svg> Confirm
|
||
</button>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- MODAL: Change Master Password -->
|
||
<!-- ============================================================ -->
|
||
<div id="changeMasterModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-panel modal-panel-sm">
|
||
<header class="modal-header">
|
||
<h3 id="changeMasterTitle">Change master password</h3>
|
||
<button class="icon-btn" data-close><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<form id="changeMasterForm" class="modal-body" autocomplete="off">
|
||
<p style="margin:0 0 12px;color:var(--text-dim);font-size:13px;line-height:1.5">
|
||
Your vault will be re-encrypted with the new key.
|
||
All other sessions will be signed out.
|
||
</p>
|
||
<label class="field">
|
||
<span>Current master password</span>
|
||
<input id="cmCurrentPwd" type="password" required>
|
||
</label>
|
||
<label class="field">
|
||
<span>New master password (min 8 chars)</span>
|
||
<input id="cmNewPwd" type="password" required minlength="8">
|
||
</label>
|
||
<label class="field">
|
||
<span>Confirm new master password</span>
|
||
<input id="cmConfirmPwd" type="password" required>
|
||
</label>
|
||
<p id="cmError" style="margin:8px 0 0;color:#dc2626;font-size:12px;display:none"></p>
|
||
</form>
|
||
<footer class="modal-footer">
|
||
<button type="button" class="btn btn-ghost" data-close>Cancel</button>
|
||
<button type="submit" form="changeMasterForm" class="btn btn-primary" id="cmConfirmBtn">
|
||
<svg><use href="#i-check"/></svg> Change password
|
||
</button>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- MODAL: Password Generator -->
|
||
<!-- ============================================================ -->
|
||
<div id="genModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-panel modal-panel-sm">
|
||
<header class="modal-header">
|
||
<h3>Password generator</h3>
|
||
<button class="icon-btn" data-close><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<div class="modal-body">
|
||
<div class="gen-preview" id="genPreview">click regenerate</div>
|
||
<label class="field">
|
||
<span>Length <b id="genLenLabel">16</b></span>
|
||
<input id="genLen" type="range" min="6" max="128" value="16">
|
||
</label>
|
||
<div class="gen-options">
|
||
<label class="check"><input type="checkbox" id="genUpper" checked><span>A-Z</span></label>
|
||
<label class="check"><input type="checkbox" id="genLower" checked><span>a-z</span></label>
|
||
<label class="check"><input type="checkbox" id="genNum" checked><span>0-9</span></label>
|
||
<label class="check"><input type="checkbox" id="genSym" checked><span>!@#</span></label>
|
||
</div>
|
||
</div>
|
||
<footer class="modal-footer">
|
||
<button class="btn btn-ghost" id="genRegen"><svg><use href="#i-rotate-ccw"/></svg> Regenerate</button>
|
||
<button class="btn btn-ghost" id="genCopy"><svg><use href="#i-copy"/></svg> Copy</button>
|
||
<button class="btn btn-primary" id="genUse"><svg><use href="#i-check"/></svg> Use</button>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- MODAL: Password history — list previous versions + revert -->
|
||
<!-- ============================================================ -->
|
||
<div id="historyModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-panel modal-panel-sm">
|
||
<header class="modal-header">
|
||
<h3 id="historyTitle">Previous versions</h3>
|
||
<button class="icon-btn" data-close><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<div class="modal-body" id="historyBody"></div>
|
||
<footer class="modal-footer">
|
||
<span style="font-size:11px;color:var(--text-faint);line-height:1.4">
|
||
Up to 20 versions kept. Master-password change clears the history.
|
||
</span>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- CHEATSHEET overlay — press '?' to discover hotkeys -->
|
||
<!-- ============================================================ -->
|
||
<div id="cheatsheetModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-panel cheatsheet-panel">
|
||
<header class="modal-header">
|
||
<h3>Keyboard shortcuts</h3>
|
||
<button class="icon-btn" data-close><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<div class="modal-body cheatsheet-body" id="cheatsheetBody"></div>
|
||
<footer class="modal-footer">
|
||
<span style="font-size:11px;color:var(--text-faint)">Press <kbd>?</kbd> anytime to reopen.</span>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- MODAL: Quick search (tray menu → fast password copy) -->
|
||
<!-- ============================================================ -->
|
||
<div id="quickSearchModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-panel modal-panel-sm quick-search-panel">
|
||
<div class="quick-search-input">
|
||
<svg><use href="#i-search"/></svg>
|
||
<input id="quickSearchInput" type="text"
|
||
placeholder="Search and press Enter to copy password…"
|
||
autocomplete="off">
|
||
<kbd>Esc</kbd>
|
||
</div>
|
||
<div class="quick-search-results" id="quickSearchResults"></div>
|
||
<div class="quick-search-hint">
|
||
Enter / click = copy password · Ctrl+Enter / Ctrl+click = copy username · Esc = close
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- MODAL: Standalone TOTP generator (paste secret → live code) -->
|
||
<!-- ============================================================ -->
|
||
<div id="totpToolModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-panel modal-panel-sm">
|
||
<header class="modal-header">
|
||
<h3>TOTP generator</h3>
|
||
<button class="icon-btn" data-close><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<div class="modal-body">
|
||
<p style="margin:0 0 12px;color:var(--text-dim);font-size:13px;line-height:1.5">
|
||
Paste a base32 secret or an <code>otpauth://</code> URI to
|
||
generate a code on the fly. Nothing is saved.
|
||
</p>
|
||
<label class="field">
|
||
<span>Secret</span>
|
||
<input id="totpToolSecret" type="text" autocomplete="off"
|
||
placeholder="JBSWY3DPEHPK3PXP or otpauth://totp/...">
|
||
<div style="display:flex;gap:6px;margin-top:6px">
|
||
<button type="button" class="btn btn-ghost btn-sm" id="totpToolGen" style="flex:1">
|
||
<svg><use href="#i-dice"/></svg> Generate
|
||
</button>
|
||
<button type="button" class="btn btn-ghost btn-sm" id="totpToolCopySecret" style="flex:1">
|
||
<svg><use href="#i-copy"/></svg> Copy secret
|
||
</button>
|
||
</div>
|
||
</label>
|
||
<div class="totp-panel" style="margin-top:12px">
|
||
<div class="totp-code" id="totpToolCode">— — — — — —</div>
|
||
<button class="icon-btn icon-btn-sm" id="totpToolCopy" title="Copy code">
|
||
<svg><use href="#i-copy"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="totp-bar-wrap"><div class="totp-bar" id="totpToolBar"></div></div>
|
||
<p id="totpToolError" style="margin:8px 0 0;color:#dc2626;font-size:12px;display:none"></p>
|
||
</div>
|
||
<footer class="modal-footer">
|
||
<button type="button" class="btn btn-ghost" data-close>Close</button>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- CONFIRM / PROMPT MODAL (replaces native confirm/prompt) -->
|
||
<!-- ============================================================ -->
|
||
<div id="confirmModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-confirm-cancel></div>
|
||
<div class="modal-panel modal-panel-sm">
|
||
<header class="modal-header">
|
||
<h3 id="confirmTitle">Confirm</h3>
|
||
<button class="icon-btn" data-confirm-cancel><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<form id="confirmForm" class="modal-body">
|
||
<p id="confirmMessage" style="margin:0 0 12px;color:var(--text-dim);font-size:13px;line-height:1.5">
|
||
Are you sure?
|
||
</p>
|
||
<label class="field is-hidden" id="confirmInputField" style="position:relative">
|
||
<input id="confirmInput" type="text" autocomplete="off">
|
||
<button type="button" id="confirmInputEye" class="icon-btn icon-btn-sm"
|
||
title="Show / hide"
|
||
style="display:none;position:absolute;right:4px;top:50%;transform:translateY(-50%)">
|
||
<svg><use href="#i-eye"/></svg>
|
||
</button>
|
||
</label>
|
||
</form>
|
||
<footer class="modal-footer">
|
||
<button type="button" class="btn btn-ghost" data-confirm-cancel id="confirmCancelBtn">Cancel</button>
|
||
<button type="submit" form="confirmForm" class="btn btn-primary" id="confirmOkBtn">
|
||
<svg><use href="#i-check"/></svg> Confirm
|
||
</button>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Global busy overlay: spinner + text for long operations (export…). -->
|
||
<div id="busyOverlay" class="busy-overlay is-hidden">
|
||
<div class="busy-box">
|
||
<div class="busy-spinner"></div>
|
||
<div class="busy-text" id="busyText">Working…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- AUTOFILL PICKER — shown when multiple entries match hotkey -->
|
||
<!-- ============================================================ -->
|
||
<div id="autofillPickerModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="modal-panel modal-panel-sm">
|
||
<header class="modal-header">
|
||
<h3 id="autofillPickerTitle">Choose an entry to autofill</h3>
|
||
<button class="icon-btn" data-close><svg><use href="#i-x"/></svg></button>
|
||
</header>
|
||
<div class="modal-body" style="padding:8px 0">
|
||
<p style="margin:0 0 8px 16px;font-size:12px;color:var(--text-dim)">
|
||
Click an entry to type its credentials into the active window.
|
||
</p>
|
||
<div id="autofillPickerList"></div>
|
||
</div>
|
||
<footer class="modal-footer">
|
||
<button class="btn btn-ghost" data-close>Cancel</button>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- COMMAND PALETTE (Cmd+K) — phase 3, shell only for now -->
|
||
<!-- ============================================================ -->
|
||
<div id="cmdPalette" class="cmd-palette is-hidden" role="dialog" aria-modal="true">
|
||
<div class="modal-backdrop" data-close></div>
|
||
<div class="cmd-panel">
|
||
<div class="cmd-input">
|
||
<svg><use href="#i-command"/></svg>
|
||
<input id="cmdInput" type="text" placeholder="Type a command or search…">
|
||
</div>
|
||
<div class="cmd-results" id="cmdResults"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="js/argon2.js"></script>
|
||
<script src="js/app.crypto.js"></script>
|
||
<script src="js/app.totp.js"></script>
|
||
<script src="js/app.favicon.js"></script>
|
||
<script src="js/app.import.js"></script>
|
||
<script src="js/app.backup.js"></script>
|
||
<script src="js/app.health.js"></script>
|
||
<script src="js/app.overlays.js"></script>
|
||
<script src="js/app.attachments.js"></script>
|
||
<script src="js/app.autofill.js"></script>
|
||
<script src="js/app.js"></script>
|
||
<script src="js/app.unlock.js"></script>
|
||
<script src="js/app.sync.js"></script>
|
||
</body>
|
||
</html>
|