peach_demo_update_spec.md

42.7 KB · raw

rendered · source

1# Peach Website Demo Update Spec2**Target: peachpasswords.com — HeroDemo.astro**3**Extension version: 1.4.12**4**Goal: make the website demo show the real extension as faithfully as possible**56---78## 1. Overview910The live demo at `peachpasswords.com` renders a simulated 320×520px popup shell directly in the browser. It currently shows a subset of the real popup's surfaces and uses some placeholder or outdated data/markup. This document is a complete spec for bringing the demo to parity with the real extension's v1.4.12 state.1112The implementation approach is:131. Copy the compiled CSS from `popup.css` + `vars.css` into the demo as-is (single source of truth).142. Rebuild the demo HTML surfaces to match the real component markup exactly.153. Wire up the demo's click/hover interactivity to show as many surfaces as possible.164. Use realistic sample data that tells a story about Peach's capabilities.1718---1920## 2. CSS strategy (highest priority)2122The demo currently has its own CSS. **Replace it** with a direct copy of the extension's compiled CSS:2324```25src/popup/styles/vars.css   → embedded in <style> in HeroDemo.astro26src/popup/styles/popup.css  → embedded in <style> in HeroDemo.astro27```2829Do **not** maintain a separate CSS copy. Instead add a build step (or Astro integration) that reads both files from the extension workspace and injects them at build time. This guarantees the demo never drifts from the real product.3031The shell wrapper the website uses should be:3233```html34<div class="popup-root peach-theme" id="hd-root">35  <!-- surfaces go here -->36</div>37```3839Override only one thing at the demo level: scale the popup root down so it fits the marketing page layout:4041```css42#hd-root {43  transform: scale(0.88);44  transform-origin: top center;45  border-radius: 20px;46  overflow: hidden;47  box-shadow: 0 32px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.06);48}49```5051---5253## 3. Fonts5455The real extension self-hosts fonts from `public/fonts/`. The website should reference the same files or load them from a CDN. Required faces:5657| Family | Weights used | Role |58|--------|-------------|------|59| Cormorant Garamond | 300 italic, 400 italic | Display headings (lock screen, entry name) |60| IBM Plex Mono | 400 | Metadata, labels, mono values, TOTP code |61| Inter Variable | 300, 400, 500 | UI body text |6263The `@font-face` declarations in `vars.css` already handle this. Copy them verbatim.6465---6667## 4. Demo surface inventory6869Show the following surfaces. Each has a tab/button in the demo chrome that reveals it. The demo should start on **Surface A (unlocked entry detail)** — this is the most impressive screen and what a visitor should see first.7071| ID | Surface | Entry point |72|----|---------|-------------|73| A | Unlocked — Entry detail (GitHub) | Default / "detail" button |74| B | Unlocked — Entry detail (Stripe, with TOTP) | Click "Stripe" in search bar |75| C | Vault list (with sort + tab-match badge) | Click search bar input |76| D | Add new entry | "+" button in header |77| E | Settings | Gear icon in header |78| F | Vault Health | From Settings → "open" |79| G | Lock screen (unlock) | Lock icon in header |80| H | Lock screen (onboarding / new vault) | Toggle inside demo |8182The passkey approval, CJK export/import, sync pairing, and export surfaces can be shown as static screenshots below the interactive demo rather than as interactive surfaces (they are powerful features but too branched for a simple demo).8384---8586## 5. Sample data8788Use this fixed vault for the demo. It demonstrates all entry types and features:8990```js91const DEMO_ENTRIES = [92  {93    id: "demo-1",94    type: "login",95    title: "GitHub",96    url: "https://github.com",97    username: "juni_patel",98    password: "p#9Kx!mQ3rL@vZ",99    totp: null,100    tags: ["work", "dev"],101    breach: 0,102    createdAt: Date.now() - 86400000 * 90,103    modifiedAt: Date.now() - 86400000 * 14,104  },105  {106    id: "demo-2",107    type: "login",108    title: "Stripe",109    url: "https://dashboard.stripe.com",110    username: "[email protected]",111    password: "Tr!p0d$2024#Acme",112    totp: "JBSWY3DPEHPK3PXP",   // shows TOTP band113    tags: ["work"],114    breach: 0,115    createdAt: Date.now() - 86400000 * 120,116    modifiedAt: Date.now() - 86400000 * 3,117  },118  {119    id: "demo-3",120    type: "login",121    title: "Netflix",122    url: "https://netflix.com",123    username: "[email protected]",124    password: "password123",  // deliberately weak — shows breach badge125    tags: ["personal"],126    breach: 54231,  // shows breach count127    createdAt: Date.now() - 86400000 * 400,128    modifiedAt: Date.now() - 86400000 * 400,129  },130  {131    id: "demo-4",132    type: "passkey",133    title: "Apple ID",134    rpId: "apple.com",135    userName: "juni_patel",136    userDisplayName: "Juni Patel",137    tags: ["personal"],138    createdAt: Date.now() - 86400000 * 30,139    modifiedAt: Date.now() - 86400000 * 30,140  },141  {142    id: "demo-5",143    type: "note",144    title: "Server SSH keys",145    notes: "prod: ssh -i ~/.ssh/peach_prod [email protected]\nstaging: ssh -i ~/.ssh/peach_stage [email protected]",146    tags: ["dev"],147    createdAt: Date.now() - 86400000 * 60,148    modifiedAt: Date.now() - 86400000 * 7,149  },150  {151    id: "demo-6",152    type: "login",153    title: "Gmail",154    url: "https://mail.google.com",155    username: "[email protected]",156    password: "Gm4!l$ecure#88",157    tags: ["personal"],158    breach: 0,159    canary: { label: "honeypot", webhookUrl: "https://hooks.example.com/canary" }, // shows canary badge160    createdAt: Date.now() - 86400000 * 200,161    modifiedAt: Date.now() - 86400000 * 45,162  }163];164```165166The tab domain for the demo should be simulated as `github.com` — this causes "GitHub" to show the tab-match orange dot in the vault list.167168---169170## 6. Surface A — Entry detail (GitHub, login)171172This is the default surface. HTML structure matches `Entry.tsx` exactly:173174```html175<section id="hd-shell" class="peach-shell peach-theme" data-surface="extension">176  <!-- Top chrome -->177  <header class="peach-chrome">178    <div class="peach-wordmark-wrap">179      <!-- PeachWordmark SVG with unlocked=true (no pip hidden class) -->180      <svg class="peach-brand-mark" ...></svg>181      <span class="peach-wordmark-text">peach</span>182      <span class="peach-status-pip unlocked"></span>183    </div>184    <div class="peach-chrome-actions">185      <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-btn-settings" aria-label="Settings">186        <!-- gear SVG -->187      </button>188      <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-btn-add" aria-label="New entry">189        <!-- + SVG -->190      </button>191      <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn peach-lock-btn" id="hd-btn-lock" aria-label="Lock vault">192        <!-- padlock SVG -->193      </button>194    </div>195  </header>196197  <!-- Content frame (entry detail) -->198  <div class="peach-shell-body">199    <div class="peach-shell-veil"></div>200    <div class="peach-content-frame" data-content-phase="idle">201202      <!-- Entry detail -->203      <div class="peach-ghost"></div>204      <div class="peach-stage">205206        <!-- Entry name -->207        <div class="peach-entry-name-wrap">208          <button class="peach-entry-name-button">209            <h1 class="peach-entry-name" aria-label="GitHub">210              <span class="peach-entry-name-text">211                <!-- Each char wrapped: -->212                <span class="peach-entry-name-char" style="--char-index:0">G</span>213                <span class="peach-entry-name-char" style="--char-index:1">i</span>214                <span class="peach-entry-name-char" style="--char-index:2">t</span>215                <span class="peach-entry-name-char" style="--char-index:3">H</span>216                <span class="peach-entry-name-char" style="--char-index:4">u</span>217                <span class="peach-entry-name-char" style="--char-index:5">b</span>218              </span>219            </h1>220          </button>221        </div>222223        <!-- Domain + tags -->224        <div class="peach-domain">225          <div class="peach-domain-row">226            <!-- favicon img or login icon fallback -->227            <img src="https://icons.duckduckgo.com/ip3/github.com.ico" width="16" height="16" style="border-radius:3px" alt="">228            <span class="peach-domain-value">github.com</span>229          </div>230          <!-- Tags -->231          <span class="peach-tag-chip" style="font-size:10px;padding:2px 8px;">work</span>232          <span class="peach-tag-chip" style="font-size:10px;padding:2px 8px;">dev</span>233        </div>234235        <!-- Fields section -->236        <section class="peach-fields">237          <!-- Username field -->238          <div class="peach-field-card">239            <span class="peach-label">username</span>240            <div class="peach-field-value-row">241              <span class="peach-field-value">juni_patel</span>242              <div class="peach-field-actions">243                <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn peach-copy-btn" id="hd-copy-username">244                  <span class="peach-copy-icon">245                    <!-- copy SVG -->246                  </span>247                  <span class="peach-copy-sheen"></span>248                </button>249              </div>250            </div>251          </div>252253          <!-- Password field -->254          <div class="peach-field-card">255            <span class="peach-label">password</span>256            <div class="peach-field-value-row">257              <div class="peach-password-value">258                <span class="peach-password-mask">...............</span>259              </div>260              <div class="peach-field-actions">261                <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-reveal-password" aria-label="Reveal password">262                  <!-- eye SVG -->263                </button>264                <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn peach-copy-btn" id="hd-copy-password">265                  <span class="peach-copy-icon"><!-- copy SVG --></span>266                  <span class="peach-copy-sheen"></span>267                </button>268              </div>269            </div>270          </div>271        </section>272273      </div>274    </div>275276    <!-- Search bar -->277    <section class="peach-search-wrap">278      <div class="peach-search-bar">279        <span class="peach-search-icon"><!-- search SVG --></span>280        <input class="peach-search-input" placeholder="search vault..." readonly id="hd-search-input">281        <div class="peach-search-meta">282          <span class="peach-search-counter">1 / 6</span>283          <span class="peach-divider"></span>284          <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-prev"><!-- chevron left --></button>285          <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-next"><!-- chevron right --></button>286        </div>287      </div>288    </section>289  </div>290</section>291```292293### A — Interactive behaviors294295| Action | Effect |296|--------|--------|297| Click copy (username) | `peach-copy-btn` gets `.is-copied` for 1.5s; drain animation plays |298| Click copy (password) | Same copy animation |299| Click reveal | Toggle `.peach-password-mask` ↔ `.peach-password-text` content |300| Click prev/next arrows | Cycle through `DEMO_ENTRIES`; re-render entry name chars with `--char-index` delay |301| Click search bar | Show Surface C (vault list overlay) |302| Click settings gear | Show Surface E |303| Click "+" | Show Surface D |304| Click lock | Show Surface G |305306---307308## 7. Surface B — Entry detail (Stripe, with TOTP)309310Same structure as A but with:311- Entry name: "Stripe"312- Domain: `dashboard.stripe.com`313- Username: `[email protected]`  314- Tags: `work`315- TOTP band rendered below the password field:316317```html318<section class="peach-totp-band">319  <div class="peach-totp-main">320    <span class="peach-label">2-factor</span>321    <div class="peach-totp-code-row">322      <p class="peach-totp-code">323        <span class="peach-totp-code-text">482 917</span>324      </p>325      <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn peach-copy-btn peach-totp-copy-btn">326        <span class="peach-copy-icon"><!-- copy SVG --></span>327        <span class="peach-copy-sheen"></span>328      </button>329    </div>330  </div>331  <div class="peach-totp-meta">332    <span class="peach-label">expires</span>333    <div class="peach-totp-countdown" id="hd-totp-remaining">18s</div>334    <svg class="peach-totp-ring" width="44" height="44" viewBox="0 0 44 44">335      <circle cx="22" cy="22" r="17" class="peach-totp-ring-track" />336      <circle cx="22" cy="22" r="17" class="peach-totp-ring-progress"337        style="stroke-dasharray: 106.8; stroke-dashoffset: 35.6;" />338    </svg>339  </div>340  <div class="peach-totp-progress">341    <div class="peach-totp-progress-bar" style="width: 60%;"></div>342  </div>343</section>344```345346Animate the TOTP ring/countdown in the demo: use a JavaScript interval to decrement a fake countdown from 30→0, updating `stroke-dashoffset` and the countdown text. Reset to 30 and generate a new fake 6-digit code when it hits 0. This makes the demo feel alive.347348Surface B is reached by clicking "Stripe" in the vault list (Surface C).349350---351352## 8. Surface C — Vault list353354```html355<div class="peach-overlay-view ov-enter-up" id="hd-view-list" style="display:flex">356  <!-- Top bar with back button + search -->357  <div class="peach-list-top">358    <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-list-close">359      <!-- chevron left -->360    </button>361    <input class="peach-search-input" placeholder="search vault..." style="font-size:14px" id="hd-list-search">362    <span class="peach-search-counter peach-search-counter-list">6 entries</span>363  </div>364365  <!-- Sort bar (new in 1.4.12) -->366  <div class="peach-sort-bar">367    <button class="peach-sort-btn is-active">smart</button>368    <button class="peach-sort-btn">A–Z</button>369    <button class="peach-sort-btn">Z–A</button>370    <button class="peach-sort-btn">newest</button>371    <span class="peach-sort-domain-hint">github.com</span>372  </div>373374  <!-- Entry list -->375  <div class="peach-vault-list">376    <!-- GitHub — tab match (is-tab-match) -->377    <button class="peach-entry-row is-active is-tab-match" style="animation-delay:0ms">378      <div class="peach-entry-row-icon">379        <img src="https://icons.duckduckgo.com/ip3/github.com.ico" width="16" height="16" style="border-radius:3px" alt="">380      </div>381      <div class="peach-entry-row-info">382        <span class="peach-entry-row-name">383          GitHub384          <span class="peach-tab-match-badge" title="Matches current tab">●</span>385        </span>386        <span class="peach-entry-row-meta">github.com</span>387      </div>388    </button>389390    <!-- Stripe -->391    <button class="peach-entry-row" style="animation-delay:20ms" id="hd-list-stripe">392      <div class="peach-entry-row-icon">393        <img src="https://icons.duckduckgo.com/ip3/dashboard.stripe.com.ico" width="16" height="16" style="border-radius:3px" alt="">394      </div>395      <div class="peach-entry-row-info">396        <span class="peach-entry-row-name">Stripe</span>397        <span class="peach-entry-row-meta">dashboard.stripe.com</span>398      </div>399    </button>400401    <!-- Netflix — breached -->402    <button class="peach-entry-row" style="animation-delay:40ms">403      <div class="peach-entry-row-icon is-breach">404        <!-- login icon SVG -->405        <svg width="11" height="12" viewBox="0 0 11 12" fill="none" stroke="currentColor" stroke-width="0.9" stroke-linecap="round">406          <rect x="2" y="5" width="7" height="5" rx="1.2"/>407          <path d="M3.5 5V3.75A2 2 0 0 1 5.5 1.75A2 2 0 0 1 7.5 3.75V5"/>408        </svg>409      </div>410      <div class="peach-entry-row-info">411        <span class="peach-entry-row-name">Netflix</span>412        <span class="peach-entry-row-meta">netflix.com</span>413      </div>414      <span class="peach-entry-row-dot" aria-label="breach"></span>415    </button>416417    <!-- Apple ID — passkey -->418    <button class="peach-entry-row" style="animation-delay:60ms">419      <div class="peach-entry-row-icon">420        <!-- passkey icon SVG -->421        <svg width="11" height="12" viewBox="0 0 11 12" fill="none" stroke="currentColor" stroke-width="0.9" stroke-linecap="round">422          <circle cx="4" cy="4.5" r="3"/>423          <path d="M6.5 7l3.5 3.5M8.5 8.5L10 10"/>424        </svg>425      </div>426      <div class="peach-entry-row-info">427        <span class="peach-entry-row-name">428          Apple ID429          <span class="peach-passkey-badge">passkey</span>430        </span>431        <span class="peach-entry-row-meta">apple.com</span>432      </div>433    </button>434435    <!-- Server SSH keys — note -->436    <button class="peach-entry-row" style="animation-delay:80ms">437      <div class="peach-entry-row-icon">438        <!-- note icon SVG -->439        <svg width="11" height="12" viewBox="0 0 11 12" fill="none" stroke="currentColor" stroke-width="0.9" stroke-linecap="round">440          <rect x="1.5" y="1.5" width="8" height="9" rx="1.5"/>441          <path d="M3.5 4.5h4M3.5 6.5h4M3.5 8.5h2"/>442        </svg>443      </div>444      <div class="peach-entry-row-info">445        <span class="peach-entry-row-name">Server SSH keys</span>446        <span class="peach-entry-row-meta">prod: ssh -i ~/.ssh/p...</span>447      </div>448    </button>449450    <!-- Gmail — canary -->451    <button class="peach-entry-row" style="animation-delay:100ms">452      <div class="peach-entry-row-icon">453        <img src="https://icons.duckduckgo.com/ip3/mail.google.com.ico" width="16" height="16" style="border-radius:3px" alt="">454      </div>455      <div class="peach-entry-row-info">456        <span class="peach-entry-row-name">Gmail</span>457        <span class="peach-entry-row-meta">mail.google.com</span>458      </div>459      <span class="peach-entry-row-canary" title="Canary: honeypot">🪤</span>460    </button>461  </div>462463  <!-- New entry row -->464  <button class="peach-new-entry-row" id="hd-list-add">465    <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round">466      <path d="M5 1v8M1 5h8"/>467    </svg>468    new entry469  </button>470</div>471```472473### C — Interactive behaviors474475| Action | Effect |476|--------|--------|477| Type in search input | Filter rows client-side with JS (simple substring match on title/domain) |478| Click sort button | Toggle `.is-active` between sort buttons; demo doesn't need to re-sort, just show the UI working |479| Click "GitHub" row | Close list, show Surface A |480| Click "Stripe" row | Close list, show Surface B (with TOTP) |481| Click "Netflix" row | Close list, show breach version of entry detail (see §9) |482| Click "new entry" | Show Surface D |483| Click back / Esc | Close list, return to current entry detail |484485---486487## 9. Breach entry detail (Netflix)488489Same structure as Surface A, but with the breach strip rendered:490491```html492<!-- Breach badge — above the entry name -->493<div class="peach-breach-badge">494  <span>⚠ 54,231 data breach exposures</span>495  <button class="peach-btn peach-btn--sm peach-btn--accent peach-breach-action">change password</button>496</div>497```498499Also the icon shows with `is-breach` class applied.500501---502503## 10. Surface D — Add new entry504505```html506<div class="peach-overlay-view ov-enter-right" id="hd-view-new" style="display:flex">507  <div class="peach-overlay-header">508    <button class="peach-btn peach-btn--subtle peach-btn--sm peach-overlay-action" id="hd-new-cancel">cancel</button>509    <span class="peach-overlay-title">new entry</span>510    <button class="peach-btn peach-btn--accent peach-btn--sm peach-overlay-action" id="hd-new-create" disabled>create</button>511  </div>512513  <div class="peach-new-body">514    <!-- Type tabs -->515    <div class="peach-type-tabs">516      <button class="peach-type-tab is-active" id="hd-tab-login">login</button>517      <button class="peach-type-tab" id="hd-tab-note">note</button>518    </div>519520    <!-- Title input -->521    <input class="peach-new-name-input" placeholder="entry name" id="hd-new-title">522523    <!-- URL row -->524    <div class="peach-domain peach-new-domain">525      <input class="peach-domain-value peach-new-field-input" placeholder="https://..." id="hd-new-url">526    </div>527528    <!-- Fields section -->529    <section class="peach-fields peach-new-fields">530      <div class="peach-field-card">531        <span class="peach-label">username</span>532        <div class="peach-field-value-row">533          <input class="peach-field-value peach-new-field-input" type="text" placeholder="username or email" id="hd-new-username" autocomplete="off">534        </div>535      </div>536537      <div class="peach-field-card">538        <span class="peach-label">password</span>539        <div class="peach-field-value-row">540          <input class="peach-field-value peach-new-field-input" type="password" placeholder="password" id="hd-new-password" autocomplete="new-password">541          <!-- Inline generator button -->542          <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-new-generate" title="Generate password">543            <!-- dice/sparkle SVG -->544            <svg width="13" height="13" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round">545              <rect x="1.5" y="1.5" width="10" height="10" rx="2"/>546              <circle cx="4.5" cy="4.5" r="1" fill="currentColor" stroke="none"/>547              <circle cx="8.5" cy="8.5" r="1" fill="currentColor" stroke="none"/>548              <circle cx="4.5" cy="8.5" r="1" fill="currentColor" stroke="none"/>549              <circle cx="8.5" cy="4.5" r="1" fill="currentColor" stroke="none"/>550              <circle cx="6.5" cy="6.5" r="1" fill="currentColor" stroke="none"/>551            </svg>552          </button>553        </div>554        <!-- Strength hairline (shown when password has content) -->555        <div class="peach-strength-hairline" id="hd-strength-bar" style="display:none">556          <div class="peach-strength-hairline-track">557            <div class="peach-strength-hairline-fill" id="hd-strength-fill" style="width:0%"></div>558          </div>559          <span class="peach-strength-label" id="hd-strength-label"></span>560        </div>561      </div>562563      <div class="peach-field-card">564        <span class="peach-label">totp secret</span>565        <div class="peach-field-value-row">566          <input class="peach-field-value peach-new-field-input" type="text" placeholder="otpauth:// or base32" id="hd-new-totp" autocomplete="off">567        </div>568      </div>569570      <div class="peach-field-card">571        <span class="peach-label">notes</span>572        <div class="peach-field-value-row">573          <textarea class="peach-new-textarea" rows="3" placeholder="optional notes" id="hd-new-notes"></textarea>574        </div>575      </div>576    </section>577578    <!-- Tags -->579    <div class="peach-field-card" style="margin-top:12px">580      <span class="peach-label">tags</span>581      <div class="peach-tag-input-wrap">582        <input class="peach-tag-input" placeholder="add tags..." id="hd-new-tags">583      </div>584      <div class="peach-tag-suggestions" style="margin-top:4px">585        <button class="peach-tag-suggestion">work</button>586        <button class="peach-tag-suggestion">personal</button>587        <button class="peach-tag-suggestion">dev</button>588      </div>589    </div>590  </div>591</div>592```593594### D — Interactive behaviors595596| Action | Effect |597|--------|--------|598| Click "note" tab | Toggle tab active state; hide url/username/password/totp fields; show note textarea |599| Click "login" tab | Toggle back; restore login fields |600| Type in password field | Show strength bar; update fill width and label text (simple heuristic OK in demo) |601| Click generate button | Fill password field with a random 16-char password; trigger strength bar update |602| All fields filled | Enable "create" button |603| Click "create" | Briefly show "saving…" then return to entry detail with a success animation |604| Click "cancel" | Close overlay, return to entry detail |605606---607608## 11. Surface E — Settings609610```html611<div class="peach-overlay-view ov-enter-right" id="hd-view-settings" style="display:flex">612  <div class="peach-overlay-header">613    <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-settings-back"><!-- chevron --></button>614    <span class="peach-overlay-title">settings</span>615    <div class="peach-overlay-spacer"></div>616  </div>617618  <div class="peach-settings-body">619620    <!-- Security group -->621    <div class="peach-settings-group">622      <p class="peach-settings-label">security</p>623      <div class="peach-settings-row">624        <span>Lock on close</span>625        <button class="peach-toggle is-on"><span class="peach-toggle-knob"></span></button>626      </div>627      <div class="peach-settings-row">628        <span>Breach monitoring</span>629        <button class="peach-toggle is-on"><span class="peach-toggle-knob"></span></button>630      </div>631      <div class="peach-settings-row peach-settings-row--meta">632        <span>Check all passwords</span>633        <button class="peach-btn peach-btn--subtle peach-btn--sm">run</button>634      </div>635      <div class="peach-settings-row peach-settings-row--meta">636        <span>Add canary entry</span>637        <button class="peach-btn peach-btn--subtle peach-btn--sm">open</button>638      </div>639    </div>640641    <!-- Duress password group (new in 1.4.12) -->642    <div class="peach-settings-group">643      <p class="peach-settings-label">duress password</p>644      <div class="peach-settings-row peach-settings-row--top">645        <div class="peach-settings-row-main">646          <span>Duress password</span>647          <span class="peach-settings-value" style="font-size:11px;line-height:1.5;color:var(--t2)">648            If entered under coercion, shows a decoy empty vault instead of your real one.649          </span>650        </div>651        <div class="peach-duress-status">off</div>652      </div>653      <div class="peach-settings-row peach-settings-row--meta">654        <button class="peach-btn peach-btn--subtle peach-btn--sm">Set up</button>655      </div>656    </div>657658    <!-- Passkeys group -->659    <div class="peach-settings-group">660      <p class="peach-settings-label">passkeys</p>661      <div class="peach-settings-row peach-settings-row--top">662        <div class="peach-settings-row-main">663          <span>Intercept passkey requests</span>664          <span class="peach-settings-value" style="font-size:11px;color:var(--t2)">665            Peach can handle passkey sign-in and creation on supported Chromium browsers.666          </span>667        </div>668        <button class="peach-toggle is-on"><span class="peach-toggle-knob"></span></button>669      </div>670      <div class="peach-settings-row peach-settings-row--meta">671        <span>Status</span>672        <span class="peach-settings-value">Active</span>673      </div>674    </div>675676    <!-- Sync group — show Pro gate -->677    <div class="peach-settings-group" style="position:relative">678      <p class="peach-settings-label">sync</p>679      <div class="peach-sync-pro-gate">680        <div class="peach-sync-pro-gate-blur">681          <div class="peach-settings-row">682            <span>Status</span>683            <span class="peach-settings-value peach-settings-value--status">684              <span class="peach-sync-status-dot" style="background:var(--peach-sync-off)"></span>685              Offline686            </span>687          </div>688        </div>689        <div class="peach-sync-pro-gate-overlay">690          <span class="peach-sync-pro-badge">Pro</span>691          <span style="font-size:13px;color:rgba(255,255,255,0.7)">Sync across all your devices</span>692          <a href="https://peachpasswords.com/#pricing" target="_blank" rel="noopener noreferrer"693             class="peach-link" style="color:#D96830;font-size:13px">Upgrade to Pro →</a>694        </div>695      </div>696    </div>697698    <!-- Vault group -->699    <div class="peach-settings-group">700      <p class="peach-settings-label">vault</p>701      <div class="peach-settings-row peach-settings-row--meta">702        <span>Entries</span>703        <span class="peach-settings-value">6</span>704      </div>705      <div class="peach-settings-row peach-settings-row--meta">706        <span>Theme</span>707        <button class="peach-btn peach-btn--subtle peach-btn--sm" id="hd-settings-theme">light</button>708      </div>709      <div class="peach-settings-row peach-settings-row--meta">710        <span>Vault Health</span>711        <button class="peach-btn peach-btn--subtle peach-btn--sm" id="hd-settings-health">open</button>712      </div>713    </div>714715    <!-- Footer -->716    <div class="peach-settings-footer">717      <span>Peach 1.4.12</span>718      <span class="peach-divider"></span>719      <span>Browser extension</span>720    </div>721722  </div>723</div>724```725726### E — Interactive behaviors727728| Action | Effect |729|--------|--------|730| Toggle "Lock on close" | Toggle `.is-on` class on button |731| Toggle "Breach monitoring" | Toggle `.is-on` |732| Toggle "Intercept passkey requests" | Toggle `.is-on` |733| Click "Theme" | Switch demo between dark/light by toggling `.light` on `#hd-root` |734| Click "Vault Health" → open | Show Surface F |735| Click back | Close settings, return to entry detail |736737---738739## 12. Surface F — Vault Health740741```html742<div class="peach-overlay-view ov-enter-right" id="hd-view-health" style="display:flex">743  <div class="peach-overlay-header">744    <button class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn" id="hd-health-back"><!-- chevron --></button>745    <span class="peach-overlay-title">vault health</span>746    <div class="peach-overlay-spacer"></div>747  </div>748749  <div class="peach-health-body">750    <div class="peach-health-summary">751      <span class="peach-health-summary-warn">2 entries need attention</span>752    </div>753    <div class="peach-health-check-row">754      <button class="peach-btn peach-btn--accent peach-btn--sm peach-health-check-btn">755        Check all breaches now756      </button>757    </div>758    <div class="peach-health-sections">759760      <!-- Breached section (1 entry) -->761      <div class="peach-health-section">762        <button class="peach-health-section-header">763          <span class="peach-health-section-title">Breached</span>764          <span class="peach-health-section-count">1</span>765          <svg width="8" height="5" viewBox="0 0 8 5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">766            <path d="M1 1l3 3 3-3"/>767          </svg>768        </button>769        <div class="peach-health-section-body">770          <div class="peach-health-row">771            <div class="peach-health-row-info">772              <span class="peach-health-row-name">Netflix</span>773              <span class="peach-health-row-meta" style="color:var(--peach-breach)">54,231 exposures</span>774            </div>775            <button class="peach-btn peach-btn--ghost peach-btn--sm peach-health-fix-btn">Fix</button>776          </div>777        </div>778      </div>779780      <!-- Reused section (0) -->781      <div class="peach-health-section">782        <button class="peach-health-section-header">783          <span class="peach-health-section-title">Reused</span>784          <span class="peach-health-section-count">0</span>785          <svg width="8" height="5" viewBox="0 0 8 5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">786            <path d="M1 1l3 3 3-3"/>787          </svg>788        </button>789        <div class="peach-health-section-body" style="display:none">790          <div class="peach-list-empty"><span>No reused passwords</span></div>791        </div>792      </div>793794      <!-- Weak section (1 entry — Netflix) -->795      <div class="peach-health-section">796        <button class="peach-health-section-header">797          <span class="peach-health-section-title">Weak</span>798          <span class="peach-health-section-count">1</span>799          <svg width="8" height="5" viewBox="0 0 8 5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">800            <path d="M1 1l3 3 3-3"/>801          </svg>802        </button>803        <div class="peach-health-section-body">804          <div class="peach-health-row">805            <div class="peach-health-row-info">806              <span class="peach-health-row-name">netflix.com</span>807              <span class="peach-health-row-meta">score: 0 (very weak)</span>808            </div>809            <button class="peach-btn peach-btn--ghost peach-btn--sm peach-health-fix-btn">Fix</button>810          </div>811        </div>812      </div>813814      <!-- Old section (0) -->815      <div class="peach-health-section">816        <button class="peach-health-section-header">817          <span class="peach-health-section-title">Old</span>818          <span class="peach-health-section-count">1</span>819          <svg width="8" height="5" viewBox="0 0 8 5" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round">820            <path d="M1 1l3 3 3-3"/>821          </svg>822        </button>823        <div class="peach-health-section-body">824          <div class="peach-health-row">825            <div class="peach-health-row-info">826              <span class="peach-health-row-name">Netflix</span>827              <span class="peach-health-row-meta">last changed: over 1 year ago</span>828            </div>829            <button class="peach-btn peach-btn--ghost peach-btn--sm peach-health-fix-btn">Fix</button>830          </div>831        </div>832      </div>833834    </div>835  </div>836</div>837```838839### F — Interactive behaviors840841| Action | Effect |842|--------|--------|843| Click section header | Collapse/expand that section body (toggle `display:none`) |844| Click "Check all breaches" | Brief "checking…" label then return to "2 entries need attention" |845| Click back | Return to Settings |846847---848849## 13. Surface G — Lock screen (unlock)850851```html852<section class="peach-screen peach-theme lock-screen lock-screen--vault-stage" id="hd-view-lock">853  <header class="peach-screen-header lock-screen-header">854    <!-- PeachWordmark -->855    <div class="peach-wordmark-wrap">856      <svg class="peach-brand-mark" ...></svg>857      <span class="peach-wordmark-text">peach</span>858    </div>859  </header>860861  <div class="peach-lock-hero peach-lock-hero--unlock">862    <div class="peach-lock-hero-art" aria-hidden="true">863      <!-- PeachMark SVG (large, faint background mark) -->864    </div>865    <span class="peach-lock-eyebrow">vault locked</span>866    <h1 class="peach-onboarding-headline peach-lock-headline--unlock">867      unlock<br>your vault.868    </h1>869    <p class="peach-subline peach-subline--lock">870      Everything stays local and encrypted until you unlock.871    </p>872  </div>873874  <section class="peach-lock-panel peach-lock-panel--unlock">875    <label class="peach-label" for="hd-unlock-password">master password</label>876    <input id="hd-unlock-password"877      class="peach-underline-input peach-unlock-input lock-password-input"878      type="password"879      placeholder="master password"880      autocomplete="current-password">881    <button class="peach-btn peach-btn--accent peach-btn--md peach-lock-submit" id="hd-unlock-btn" disabled>882      unlock883    </button>884    <p class="peach-hint">encrypted vault · stored locally</p>885  </section>886</section>887```888889### G — Interactive behaviors890891| Action | Effect |892|--------|--------|893| Type in password field | Enable "unlock" button when field is non-empty |894| Click "unlock" | Show brief "unlocking…" state then transition back to Surface A with spring animation |895| Toggle button inside demo | Show Surface H (onboarding/new vault) instead |896897---898899## 14. Surface H — Lock screen (onboarding / new vault)900901```html902<section class="peach-screen peach-theme lock-screen lock-screen--vault-stage lock-screen--onboarding" id="hd-view-onboarding">903  <header class="peach-screen-header lock-screen-header">904    <div class="peach-wordmark-wrap"><!-- PeachWordmark --></div>905  </header>906907  <div class="peach-lock-hero">908    <div class="peach-lock-hero-art" aria-hidden="true"><!-- PeachMark --></div>909    <span class="peach-lock-eyebrow">new vault</span>910    <h1 class="peach-onboarding-headline">911      your secrets,<br>kept close.912    </h1>913    <p class="peach-subline peach-subline--lock">914      Create a master password to encrypt your vault. Or restore from an existing backup.915    </p>916  </div>917918  <section class="peach-lock-panel peach-lock-panel--primary">919    <label class="peach-label" for="hd-create-pw">master password</label>920    <input id="hd-create-pw" class="peach-underline-input peach-unlock-input lock-password-input"921      type="password" placeholder="master password" autocomplete="new-password">922923    <label class="peach-label" for="hd-create-confirm">confirm password</label>924    <input id="hd-create-confirm" class="peach-underline-input peach-unlock-input"925      type="password" placeholder="confirm password" autocomplete="new-password">926927    <!-- Strength bar (new in 1.4.12 — uses shared peach-password-strength classes) -->928    <div class="peach-password-strength" id="hd-create-strength" style="display:none">929      <div class="peach-strength-bar peach-strength-bar--fair">930        <div class="peach-strength-fill"></div>931      </div>932      <span class="peach-strength-label">fair</span>933    </div>934935    <button class="peach-btn peach-btn--accent peach-btn--md peach-lock-submit" id="hd-create-btn" disabled>936      create vault937    </button>938    <p class="peach-hint">If you lose this password, Peach cannot recover your vault.</p>939940    <div class="peach-lock-panel-footer">941      <p class="peach-hint">Already have a backup?</p>942      <div class="peach-onboarding-alt-actions">943        <button class="peach-lock-restore-link">restore from CJK phrase</button>944        <button class="peach-lock-restore-link">import from backup file</button>945      </div>946    </div>947  </section>948</section>949```950951### H — Interactive behaviors952953| Action | Effect |954|--------|--------|955| Type in master password | Show strength bar; animate fill; update label (weak/fair/good/strong) |956| Both fields match, ≥8 chars | Enable "create vault" button |957| Click "create vault" | Show "creating…" then transition to Surface A |958959---960961## 15. JavaScript interactivity sketch962963All demo interactivity lives in a single `<script>` tag at the bottom of `HeroDemo.astro`. Keep it simple — no framework.964965```js966// Surface routing967const surfaces = {968  detail: document.getElementById('hd-shell'),969  list:   document.getElementById('hd-view-list'),970  add:    document.getElementById('hd-view-new'),971  settings: document.getElementById('hd-view-settings'),972  health: document.getElementById('hd-view-health'),973  lock:   document.getElementById('hd-view-lock'),974  onboarding: document.getElementById('hd-view-onboarding'),975};976977let activeSurface = 'detail';978979function show(id) {980  // Add ov-enter-up / ov-enter-right class on show to trigger animation981  Object.values(surfaces).forEach(s => s && (s.style.display = 'none'));982  const el = surfaces[id];983  if (el) {984    el.style.display = 'flex';985    // Restart animation986    el.classList.remove('ov-enter-up', 'ov-enter-right');987    void el.offsetWidth;988    if (id === 'list') el.classList.add('ov-enter-up');989    else if (['add','settings','health'].includes(id)) el.classList.add('ov-enter-right');990    activeSurface = id;991  }992}993994// Copy animation995function animateCopy(btn) {996  btn.classList.add('is-copied');997  setTimeout(() => btn.classList.remove('is-copied'), 1500);998}9991000// TOTP countdown1001let totpInterval = null;1002let totpRemaining = 18;10031004function startTotpCountdown() {1005  clearInterval(totpInterval);1006  totpInterval = setInterval(() => {1007    totpRemaining--;1008    if (totpRemaining <= 0) {1009      totpRemaining = 30;1010      // Generate new fake code1011      const newCode = String(Math.floor(100000 + Math.random() * 900000));1012      const formatted = newCode.slice(0,3) + ' ' + newCode.slice(3);1013      document.getElementById('hd-totp-code')?.textContent = formatted;1014    }1015    const el = document.getElementById('hd-totp-remaining');1016    if (el) el.textContent = totpRemaining + 's';1017    // Update ring1018    const circumference = 106.8;1019    const progress = totpRemaining / 30;1020    const offset = circumference * (1 - progress);1021    document.querySelector('.peach-totp-ring-progress')1022      ?.style.setProperty('stroke-dashoffset', offset);1023    document.querySelector('.peach-totp-progress-bar')1024      ?.style.setProperty('width', (progress * 100) + '%');1025  }, 1000);1026}10271028// Password strength (simple heuristic for demo)1029function getStrength(pw) {1030  if (!pw) return null;1031  let s = 0;1032  if (pw.length >= 8) s++;1033  if (/[A-Z]/.test(pw) && /[a-z]/.test(pw)) s++;1034  if (/\d/.test(pw)) s++;1035  if (/[^A-Za-z0-9]/.test(pw)) s++;1036  const labels = ['weak','fair','fair','good','strong'];1037  const widths  = ['20%','40%','55%','75%','100%'];1038  return { label: labels[s], width: widths[s], cls: labels[s] };1039}10401041// Wire up all the event handlers here...1042// (full handler list per section above)1043```10441045---10461047## 16. Feature callouts beneath the demo10481049Below the interactive popup, add a row of feature callout chips that reinforce what was just seen. Each chip links to the relevant section on the page:10501051```html1052<div class="hd-feature-chips">1053  <span class="hd-chip">🔐 AES-256-GCM encryption</span>1054  <span class="hd-chip">🛡 HIBP breach monitoring</span>1055  <span class="hd-chip">🗝 Passkeys (WebAuthn)</span>1056  <span class="hd-chip">🟠 Canary honeypots</span>1057  <span class="hd-chip">🈶 CJK phrase backup</span>1058  <span class="hd-chip">🔄 Peach Sync (Pro)</span>1059  <span class="hd-chip">📊 Vault Health</span>1060  <span class="hd-chip">💡 Duress password</span>1061</div>1062```10631064---10651066## 17. Static screenshot surfaces10671068The following surfaces are too branched to demo interactively but should appear as static annotated screenshots in a "More features" section or carousel on the page:106910701. **Passkey approval** — the pop-up that appears when a site requests a passkey (`save a passkey?` / `sign in with passkey?`)10712. **CJK export** — the 2,000+ character Chinese Ideograph backup phrase output10723. **CJK import** — the phrase validation flow with HIBP check10734. **Sync pairing** — the 6-digit pairing code UI10745. **Autofill in-page** — screenshot of the injected Peach icon on a login field + identity-helper dropdown10756. **Duress password setup** — the settings flow for configuring the decoy vault10761077Produce these screenshots by loading the real built extension in a screenshot-capable Chrome instance.10781079---10801081## 18. Accessibility requirements10821083The demo is focusable and keyboard-navigable:1084- All interactive buttons have `aria-label` attributes1085- Copy confirmation is announced via `aria-live="polite"` region1086- Vault list rows are `<button>` elements (already the case in real code)1087- Password reveal toggle updates `aria-label` to reflect state ("Reveal password" / "Hide password")1088- TOTP countdown is wrapped in `aria-live="polite"` so screen readers can announce refresh10891090---10911092## 19. Sync checklist — keeping demo current10931094After every extension release, verify these items before shipping the website update:10951096- [ ] CSS — re-copy `vars.css` and `popup.css` from extension source1097- [ ] Version number in Surface E footer1098- [ ] Settings group order matches real Settings.tsx (currently: security → duress → passkeys → sync → vault → license)1099- [ ] Sort bar labels match real Vault.tsx (`smart`, `A–Z`, `Z–A`, `newest`)1100- [ ] Strength bar classes used in Surface H match real Lock.tsx (`peach-strength-bar--weak/fair/good/strong`)1101- [ ] Feature chip list reflects any new features added in the release1102- [ ] TOTP code animation ring physics (`stroke-dasharray: 106.8` matches `r=17`, circumference = 2π×17 ≈ 106.8)