66c3fd7b35ec
redesign sync pro gate with mobile bird + chirp waves, add isNative variant to vault list top bar (no back arrow, search icon, flat counter), bump v3.5.0
- time
- 2026-05-18 14:23 UTC · 5 months ago
- machine
- koh2
- parent
- 86d0c56266c8
↓ peach-browser-66c3fd7b.tar.gzatlas at this save
files changed7 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+1 −0src/popup/styles/popup.css+108 −21src/popup/views/ChirpSync.tsx+17 −13src/popup/views/Vault.tsx+29 −11manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22 "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33 "manifest_version": 3,44 "name": "Peach",5- "version": "3.4.0",5+ "version": "3.5.0",66 "description": "Zero-knowledge password manager",77 "browser_specific_settings": {88 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "3.4.0",4+ "version": "3.5.0",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "3.4.0",3+ "version": "3.5.0",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/popup/App.tsx+1 −0
@@ -1303,6 +1303,7 @@13031303 sortOrder={sortOrder()}13041304 searchSeed={listSearchSeed()}13051305 searchSeedNonce={listSearchSeedNonce()}1306+ isNative={true}13061307 onSelectEntry={handleSelectEntry}13071308 onSortOrderChange={setSortOrder}13081309 onAdd={handleAdd}src/popup/styles/popup.css+108 −21
@@ -1313,6 +1313,36 @@13131313 flex-shrink: 0;13141314 background: var(--peach-s1);13151315 }1316+1317+/* Native (mobile) variant: flat search-bar look matching the extension's1318+ `.peach-search-bar`. No back arrow — the sheet is dismissed by swipe.1319+ Search-glass icon on the left, counter on the right, no nav buttons. */1320+.peach-list-top--native {1321+ min-height: 54px;1322+ padding: 0 14px 0 14px;1323+ gap: 10px;1324+ background: transparent;1325+ border-bottom: 1px solid color-mix(in srgb, var(--peach-t3) 60%, transparent);1326+}1327+.peach-list-top--native .peach-search-icon {1328+ color: var(--peach-t2);1329+ display: inline-flex;1330+ align-items: center;1331+ flex-shrink: 0;1332+}1333+.peach-list-top--native .peach-search-input {1334+ font-family: var(--font-display);1335+ font-style: italic;1336+ font-size: 18px;1337+ caret-color: var(--peach-accent);1338+ flex: 1;1339+}1340+.peach-list-top--native .peach-search-counter {1341+ font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, monospace;1342+ font-size: 0.625rem;1343+ color: var(--peach-t2);1344+ flex-shrink: 0;1345+}13161346 .peach-vault-list {13171347 flex: 1;13181348 overflow-y: auto;@@ -9692,43 +9722,100 @@96929722 align-items: center;96939723 }96949724 9695-/* Pro-gate card — soft upsell with a stylised wave illustration. */9725+/* Pro-gate card — soft upsell with the mobile bird perched, chirping. */96969726 .peach-chirp-pro-card {9697- margin: 8px 0;9698- padding: 22px 18px;9699- border-radius: 14px;9700- background: color-mix(in srgb, var(--peach-bg-elevated, var(--peach-bg)) 84%, var(--peach-accent) 16%);9701- border: 1px solid color-mix(in srgb, var(--peach-accent) 18%, transparent);9727+ position: relative;9728+ margin: 18px 12px;9729+ padding: 18px 20px 22px;9730+ border-radius: 22px;9731+ background:9732+ radial-gradient(140% 90% at 50% 0%,9733+ color-mix(in srgb, var(--peach-accent) 18%, transparent) 0%,9734+ transparent 60%),9735+ color-mix(in srgb, var(--peach-bg-elevated, var(--peach-bg)) 88%, var(--peach-accent) 12%);9736+ border: 1px solid color-mix(in srgb, var(--peach-accent) 22%, transparent);9737+ box-shadow:9738+ 0 1px 0 color-mix(in srgb, var(--peach-accent) 8%, transparent) inset,9739+ 0 18px 40px -24px rgba(0, 0, 0, 0.45);97029740 display: flex;97039741 flex-direction: column;97049742 align-items: center;9705- gap: 12px;9743+ gap: 10px;97069744 text-align: center;9745+ overflow: hidden;97079746 }9708-.peach-chirp-pro-illustration {9747+.peach-chirp-pro-stage {9748+ position: relative;9749+ width: 220px;9750+ height: 140px;97099751 display: flex;9710- align-items: center;9752+ align-items: flex-end;97119753 justify-content: center;9712- gap: 10px;9754+ margin-bottom: 2px;9755+}9756+.peach-chirp-pro-bird-slot {9757+ width: 140px;9758+ height: 132px;9759+ display: flex;9760+ align-items: flex-end;9761+ justify-content: center;9762+ filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.35));9763+}9764+.peach-chirp-pro-bird-slot .peach-chirp-bird {9765+ width: 100%;9766+ height: 100%;9767+}9768+.peach-chirp-pro-bird-slot .peach-chirp-bird svg {9769+ width: 100%;9770+ height: 100%;9771+ overflow: visible;9772+}9773+.peach-chirp-pro-chirp {9774+ position: absolute;9775+ top: 14px;9776+ right: 8px;9777+ width: 96px;9778+ height: 80px;97139779 color: var(--peach-accent);9780+ animation: peach-chirp-pulse 2.6s ease-in-out infinite;9781+ transform-origin: 0% 50%;9782+ pointer-events: none;97149783 }9715-.peach-chirp-pro-mark {9716- width: 44px;9717- height: 44px;9784+@keyframes peach-chirp-pulse {9785+ 0%, 100% { opacity: 0.35; transform: translateX(0) scale(0.92); }9786+ 45% { opacity: 1; transform: translateX(2px) scale(1); }9787+ 70% { opacity: 0.6; transform: translateX(4px) scale(1.04); }97189788 }9719-.peach-chirp-pro-waves {9720- opacity: 0.85;9789+@media (prefers-reduced-motion: reduce) {9790+ .peach-chirp-pro-chirp { animation: none; opacity: 0.7; }97219791 }97229792 .peach-chirp-pro-headline {9723- margin: 2px 0 0;9724- font-size: 16px;9725- font-weight: 600;9726- color: var(--peach-text);9793+ margin: 4px 0 0;9794+ font-family: var(--font-display, "Cormorant Garamond", serif);9795+ font-style: italic;9796+ font-size: 19px;9797+ font-weight: 500;9798+ letter-spacing: 0.01em;9799+ color: var(--peach-text, var(--peach-t0));9800+ max-width: 280px;9801+ line-height: 1.25;97279802 }97289803 .peach-chirp-pro-body {97299804 margin: 0;97309805 font-size: 13px;97319806 line-height: 1.55;9732- color: var(--peach-text-dim);9733- max-width: 320px;9807+ color: var(--peach-text-dim, var(--peach-t2));9808+ max-width: 300px;9809+}9810+.peach-chirp-pro-cta {9811+ margin-top: 4px;9812+}9813+.peach-chirp-pro-foot {9814+ margin: 2px 0 0;9815+ font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, monospace;9816+ font-size: 0.625rem;9817+ letter-spacing: 0.08em;9818+ text-transform: uppercase;9819+ color: var(--peach-t2);9820+ opacity: 0.75;97349821 }src/popup/views/ChirpSync.tsx+17 −13
@@ -39,7 +39,6 @@3939 import { getLocalSubnet } from "../../shared/local-network";4040 import { isNativePlatform } from "../../shared/native";4141 import { openCapacitorPairingServer } from "../chirp-pair-capacitor";42-import { PeachMark } from "../components/PeachMark";4342 4443 export interface ChirpSyncViewProps {4544 isPro: boolean;@@ -202,23 +201,28 @@202201 <h1 class="peach-chirp-settings-title">Sync</h1>203202 </header>204203 <div class="peach-chirp-pro-card">205- <div class="peach-chirp-pro-illustration">206- <PeachMark class="peach-chirp-pro-mark" />207- <svg class="peach-chirp-pro-waves" width="200" height="80" viewBox="0 0 200 80" fill="none" stroke="currentColor" stroke-width="1.4">208- <path d="M 30 40 Q 100 10 170 40" />209- <path d="M 30 50 Q 100 20 170 50" opacity="0.6" />210- <path d="M 30 60 Q 100 30 170 60" opacity="0.3" />204+ <div class="peach-chirp-pro-stage" aria-hidden="true">205+ <svg class="peach-chirp-pro-chirp" viewBox="0 0 200 120" fill="none" stroke="currentColor"206+ stroke-linecap="round" stroke-linejoin="round">207+ <path d="M 124 36 Q 150 30 168 44" stroke-width="1.6" opacity="0.9" />208+ <path d="M 128 52 Q 162 50 184 64" stroke-width="1.3" opacity="0.55" />209+ <path d="M 124 68 Q 158 76 178 92" stroke-width="1.1" opacity="0.32" />211210 </svg>211+ <div class="peach-chirp-pro-bird-slot">212+ <MobileBird pose="idle" />213+ </div>212214 </div>213- <h2 class="peach-chirp-pro-headline">Chirp Sync is a Peach Pro feature</h2>215+ <h2 class="peach-chirp-pro-headline">A little bird that carries your vault</h2>214216 <p class="peach-chirp-pro-body">215- Pair your phone with your laptop using a quick inaudible chirp —216- then your vault stays in sync across devices on your local network.217- No QR codes, no relay servers, no cloud accounts.217+ Chirp Sync pairs your phone with your laptop using a quick218+ inaudible chirp — then your vault stays in sync across devices on219+ your local network. No QR codes, no relay servers, no cloud220+ accounts.218221 </p>219- <button class="peach-btn peach-btn-primary" onClick={() => props.onLearnPro()}>220- Learn more about Peach Pro222+ <button class="peach-btn peach-btn-primary peach-chirp-pro-cta" onClick={() => props.onLearnPro()}>223+ Unlock with Peach Pro221224 </button>225+ <p class="peach-chirp-pro-foot">Peach Pro · one-time purchase</p>222226 </div>223227 </div>224228 );src/popup/views/Vault.tsx+29 −11
@@ -103,6 +103,10 @@103103 sortOrder: SortOrder;104104 searchSeed?: string;105105 searchSeedNonce?: number;106+ // When true, render the mobile flat top bar: no back arrow (use swipe-down107+ // on the bottom sheet), search-glass icon on the left, counter on the108+ // right. Matches the extension's `.peach-search-bar` look.109+ isNative?: boolean;106110 onSelectEntry: (entry: VaultEntry) => void;107111 onSortOrderChange: (order: SortOrder) => void;108112 onAdd: () => void;@@ -429,21 +433,33 @@429433 430434 return (431435 <div class="peach-overlay-view ov-enter-up" style="display:flex">432- <div class="peach-list-top">433- <button434- class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"435- onClick={props.onClose}436- aria-label="Back"436+ <div class="peach-list-top" classList={{ "peach-list-top--native": !!props.isNative }}>437+ <Show438+ when={!props.isNative}439+ fallback={440+ <span class="peach-search-icon" aria-hidden="true">441+ <svg width="13" height="13" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round">442+ <circle cx="5.5" cy="5.5" r="3.75" />443+ <path d="M8.75 8.75L11.5 11.5" />444+ </svg>445+ </span>446+ }437447 >438- <svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">439- <path d="M6 1L1 6L6 11" />440- </svg>441- </button>448+ <button449+ class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"450+ onClick={props.onClose}451+ aria-label="Back"452+ >453+ <svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">454+ <path d="M6 1L1 6L6 11" />455+ </svg>456+ </button>457+ </Show>442458 <input443459 class="peach-search-input"444460 placeholder="search vault..."445461 autocomplete="off"446- style="font-size:14px"462+ style={props.isNative ? undefined : "font-size:14px"}447463 ref={focusSearch}448464 value={search()}449465 onInput={(e) => setSearch((e.currentTarget as HTMLInputElement).value)}@@ -452,7 +468,9 @@452468 <span class="peach-search-counter peach-search-counter-list">453469 {search() || selectedTags().size > 0454470 ? `${filteredEntries().length} of ${props.entries.length}`455- : `${props.entries.length} entries`}471+ : props.isNative472+ ? `${props.entries.length}`473+ : `${props.entries.length} entries`}456474 </span>457475 </div>458476