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
7 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 −11
manifest.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": {

view file

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",

view file

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": {

view file

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}

view file

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 }

view file

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   );

view file

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 

view file