40b3154a34
Session highlights:
- feat(prefs): DPAPI-backed key/value store (PM.UserPrefs) — fixes
rememberedUsername being lost across reboots due to the random
ephemeral HTTP port changing the localStorage origin every launch.
Bridge cmd://prefs/{get,set} round-trips through Delphi.
- feat(tray): icon visible from startup (NIM_ADD at constructor, not
at first minimize). Tray context menu themed via uxtheme!135
SetPreferredAppMode so it follows the app's dark/light setting.
- feat(single-instance): named mutex + RegisterWindowMessage broadcast.
Second launch posts WM_PMSHOW to HWND_BROADCAST and exits; the
running bridge restores the window from tray. Mutex lives in Local\
namespace so distinct Windows users can still each run one.
- feat(mfa): Authenticator sidebar view (live TOTP codes for every
entry with a secret) + standalone TOTP generator modal (paste
base32 / otpauth:// URI, or generate a random 20-byte secret).
- feat(sidebar): Folders / Tags / Tools sections collapsible with
chevron toggle. Badge counts stay visible when collapsed. State
persisted in settings_json (synced across devices).
- feat(autofill): hotkey when vault is locked now restores the app
and focuses the master password input instead of no-op'ing
silently. Cleaner UX for the common "I hit Ctrl+Shift+L but the
vault was locked" path.
- feat(quick-unlock): when enabled, skip lockVault on Windows lock /
sleep. Rationale: the DPAPI blob already gates access via the
Windows account, so re-locking on top of the OS lock is redundant.
Idle auto-lock still fires (separate opt-in).
- fix(quick-unlock): re-sync state.quickUnlockEnabled from DPAPI
source-of-truth at boot, instead of trusting (now-volatile)
localStorage.
- docs: CLAUDE.md updated with all new modules, bridge commands,
and the port-ephemeral pitfall.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
777 lines
45 KiB
HTML
777 lines
45 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-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">
|
|
<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="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="passkeyBtn" type="button" class="btn btn-ghost btn-block">
|
|
<svg><use href="#i-lock"/></svg>
|
|
Use a passkey
|
|
</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>
|
|
</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="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>
|
|
<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>
|
|
<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>
|
|
<button class="btn btn-primary btn-sm" id="newEntryBtn">
|
|
<svg><use href="#i-plus"/></svg>
|
|
New
|
|
</button>
|
|
<div class="user-menu">
|
|
<button class="user-chip" id="userChip">
|
|
<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>
|
|
|
|
<!-- 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="slideover-body">
|
|
<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>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>
|
|
|
|
<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>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>
|
|
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 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 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>
|
|
<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">
|
|
Windows keeps copied passwords in <b>clipboard history (Win+V)</b>.
|
|
To prevent that, disable history in <i>Settings → System → Clipboard</i>.
|
|
</p>
|
|
<button class="btn btn-ghost btn-sm" id="openClipboardSettings">Why?</button>
|
|
</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>
|
|
<button class="btn btn-ghost btn-sm" id="exportBtn">
|
|
<svg><use href="#i-log-out"/></svg> Export vault
|
|
</button>
|
|
</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">
|
|
<div class="slideover-field-label">Account</div>
|
|
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px">
|
|
Signed in as <b id="settingUser"></b>
|
|
</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">
|
|
<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>
|
|
</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>
|
|
</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: New / Edit Entry -->
|
|
<!-- ============================================================ -->
|
|
<div id="entryModal" class="modal is-hidden" role="dialog" aria-modal="true">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-panel">
|
|
<header class="modal-header">
|
|
<h3 id="entryModalTitle">New entry</h3>
|
|
<button class="icon-btn" data-close><svg><use href="#i-x"/></svg></button>
|
|
</header>
|
|
<form id="entryForm" class="modal-body" autocomplete="off">
|
|
<input type="hidden" id="entryId">
|
|
<label class="field">
|
|
<span>Display name <small style="color:var(--text-faint);font-weight:400">— shown on cards, also used for autofill matching</small></span>
|
|
<input id="entryTitle" type="text" placeholder="GitHub (work account)">
|
|
</label>
|
|
<label class="field">
|
|
<span>Website / App <small style="color:var(--text-faint);font-weight:400">— hostname or brand, used for autofill matching</small></span>
|
|
<input id="entrySite" type="text" required placeholder="github.com">
|
|
</label>
|
|
<label class="field">
|
|
<span>Username / Email</span>
|
|
<input id="entryUsername" type="text" placeholder="me@example.com">
|
|
</label>
|
|
<label class="field">
|
|
<span>Password</span>
|
|
<div class="input-with-action">
|
|
<input id="entryPassword" type="password" required placeholder="••••••••">
|
|
<button type="button" class="icon-btn" id="entryPwToggle"><svg><use href="#i-eye"/></svg></button>
|
|
<button type="button" class="icon-btn" id="entryPwGen" title="Generate"><svg><use href="#i-dice"/></svg></button>
|
|
</div>
|
|
<div class="strength-bar" id="entryStrengthBar"></div>
|
|
</label>
|
|
<label class="field">
|
|
<span>Folder</span>
|
|
<select id="entryFolder"></select>
|
|
</label>
|
|
<label class="field">
|
|
<span>Tags <span class="hint">enter to add</span></span>
|
|
<div class="chip-input" id="entryTagsInput">
|
|
<input id="entryTagsField" type="text" placeholder="add a tag…" autocomplete="off">
|
|
</div>
|
|
<input type="hidden" id="entryTags">
|
|
</label>
|
|
</form>
|
|
<footer class="modal-footer">
|
|
<button class="btn btn-ghost" data-close>Cancel</button>
|
|
<button class="btn btn-primary" id="entrySaveBtn">
|
|
<svg><use href="#i-check"/></svg> Save
|
|
</button>
|
|
</footer>
|
|
</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: 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">
|
|
<input id="confirmInput" type="text" autocomplete="off">
|
|
</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>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 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/app.js"></script>
|
|
</body>
|
|
</html>
|