3f5891a8b076
onboarding cleanup: drop the bird-pair avatars from AdoptVaultView (pair and success phases) and PairDevice — the two circles on a bar looked like testicles. Trashed PairingBirds.tsx, removed its first CSS block. Drop the 'Already have a backup?' section from Lock.tsx — the welcome screen already asks, and the test for it. (v4.3.10)
- time
- 2026-07-28 01:38 UTC · 3 months ago
- machine
- koh2
- agent
- claude-MiniMax-M3
- session
- sync false-failure and bug trio
- parent
- 58c7a0bdb8f3
↓ peach-browser-3f5891a8.tar.gzatlas at this save
files changed10 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/components/PairingBirds.tsx+0 −161src/popup/styles/popup.css+0 −264src/popup/views/AdoptVaultView.tsx+0 −8src/popup/views/Lock.tsx+0 −32src/popup/views/PairDevice.test.tsx+0 −1src/popup/views/PairDevice.tsx+0 −15src/popup/views/contracts.test.tsx+0 −38manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.3.9",4+ "version": "4.3.10",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.3.9",4+ "version": "4.3.10",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": "4.3.9",3+ "version": "4.3.10",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/popup/components/PairingBirds.tsx+0 −161
@@ -1,161 +0,0 @@1-// Pairing birds — the two illustrated characters used on the pairing screens.2-//3-// These are all that remains of the removed audio-pairing animation: the flow4-// itself went with it, but the birds outlived it and still animate above the5-// code-entry, connecting, SAS and paired screens (PairDevice.tsx's bird stage).6-// The `-chirping` pose names are kept on purpose — the bird visibly chirps.7-8-import { Component } from "solid-js";9-10-const FINCH = {11- hi: "#f4a6b6", mid: "#dc8294", lo: "#a85870", shade: "#7a3e54",12- cream: "#f5d8c8", beak: "#f5c764", beakShade: "#c8932e", legs: "#7a3e54",13-};14-const SWALLOW = {15- hi: "#f4c8a0", mid: "#dc9968", lo: "#a86b3e", shade: "#7a4a26",16- cream: "#f7e0c8", beak: "#f5c764", beakShade: "#b87a22", legs: "#5e3a1f",17-};18-19-export const DesktopBird: Component<{ pose: string }> = (props) => {20- const p = FINCH;21- return (22- <div class={`peach-pair-bird peach-pair-bird-desktop peach-pair-bird-pose-${props.pose}`}>23- <svg viewBox="0 0 160 150" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMax meet">24- <defs>25- <radialGradient id="peach-pair-finch-body" cx="0.42" cy="0.32" r="0.72">26- <stop offset="0%" stop-color={p.hi} />27- <stop offset="55%" stop-color={p.mid} />28- <stop offset="100%" stop-color={p.lo} />29- </radialGradient>30- <radialGradient id="peach-pair-finch-head" cx="0.5" cy="0.4" r="0.7">31- <stop offset="0%" stop-color={p.hi} />32- <stop offset="100%" stop-color={p.mid} />33- </radialGradient>34- <linearGradient id="peach-pair-finch-belly" x1="0" y1="0" x2="0" y2="1">35- <stop offset="0%" stop-color={p.cream} stop-opacity="0" />36- <stop offset="50%" stop-color={p.cream} stop-opacity="0.85" />37- <stop offset="100%" stop-color={p.cream} stop-opacity="0.5" />38- </linearGradient>39- </defs>40- <ellipse class="peach-pair-bird-shadow" cx="78" cy="138" rx="40" ry="3.5" fill="rgba(0,0,0,0.45)" />41- <g class="peach-pair-bird-aura" opacity="0">42- <ellipse cx="76" cy="74" rx="60" ry="46" fill={p.hi} opacity="0.18" style={{ filter: "blur(8px)" }} />43- </g>44- <g class="peach-pair-bird-tail">45- <path d="M 32 92 Q 8 90, 6 80 Q 10 95, 28 100 Z" fill={p.lo} />46- <path d="M 32 95 Q 4 99, 4 92 Q 10 102, 28 102 Z" fill={p.shade} opacity="0.85" />47- <path d="M 32 98 Q 8 108, 12 110 Q 20 110, 30 105 Z" fill={p.lo} opacity="0.9" />48- </g>49- <g class="peach-pair-bird-feet" stroke={p.legs} stroke-width="1.6" stroke-linecap="round" fill="none">50- <line x1="62" y1="118" x2="60" y2="134" />51- <path d="M 60 134 L 55 138 M 60 134 L 60 139 M 60 134 L 65 138" stroke-width="1.2" />52- <line x1="84" y1="118" x2="86" y2="134" />53- <path d="M 86 134 L 81 138 M 86 134 L 86 139 M 86 134 L 91 138" stroke-width="1.2" />54- </g>55- <g class="peach-pair-bird-body">56- <path d="M 30 88 C 24 64, 40 38, 72 36 C 105 34, 124 56, 122 84 C 120 110, 100 122, 70 120 C 44 118, 32 108, 30 88 Z" fill="url(#peach-pair-finch-body)" />57- <path d="M 40 82 C 42 100, 60 112, 80 110 C 100 108, 110 100, 112 86 C 100 102, 80 106, 60 102 C 50 100, 44 92, 40 82 Z" fill="url(#peach-pair-finch-belly)" />58- <g class="peach-pair-bird-wing">59- <path d="M 56 60 C 68 56, 88 60, 102 68 C 108 76, 106 86, 96 90 C 80 92, 64 86, 54 78 C 50 70, 50 64, 56 60 Z" fill={p.lo} />60- <g stroke={p.shade} stroke-width="0.7" stroke-linecap="round" fill="none" opacity="0.55">61- <path d="M 64 64 Q 72 70, 78 80" />62- <path d="M 74 62 Q 82 70, 88 82" />63- <path d="M 84 64 Q 90 72, 94 84" />64- </g>65- <path d="M 56 60 C 68 56, 88 60, 102 68" stroke={p.shade} stroke-width="1" fill="none" opacity="0.4" />66- </g>67- </g>68- <g class="peach-pair-bird-head-group" style={{ "transform-origin": "95px 52px" }}>69- <g class="peach-pair-bird-tuft">70- <path d="M 88 22 Q 86 12, 94 12 Q 94 20, 92 26 Z" fill={p.lo} />71- <path d="M 96 20 Q 96 10, 104 14 Q 102 22, 100 26 Z" fill={p.shade} opacity="0.95" />72- <path d="M 104 24 Q 108 16, 114 20 Q 110 28, 106 30 Z" fill={p.lo} opacity="0.85" />73- </g>74- <circle cx="98" cy="48" r="26" fill="url(#peach-pair-finch-head)" />75- <path d="M 76 46 Q 80 30, 96 26 Q 86 32, 80 48 Z" fill={p.shade} opacity="0.35" />76- <g class="peach-pair-bird-eye-group">77- <circle cx="108" cy="42" r="4.5" fill="#1f0a10" />78- <circle cx="109.4" cy="40.5" r="1.4" fill="rgba(255,240,225,0.95)" />79- <circle cx="106.5" cy="44" r="0.5" fill="rgba(255,240,225,0.6)" />80- </g>81- <g class="peach-pair-bird-beak" style={{ "transform-origin": "120px 52px" }}>82- <path d="M 120 48 L 138 50 L 122 56 Z" fill={p.beak} />83- <path d="M 120 48 L 138 50 L 128 52 Z" fill={p.beakShade} opacity="0.55" />84- <path d="M 122 52 L 134 51" stroke={p.beakShade} stroke-width="0.6" opacity="0.6" />85- </g>86- </g>87- </svg>88- </div>89- );90-};91-92-export const MobileBird: Component<{ pose: string }> = (props) => {93- const p = SWALLOW;94- return (95- <div class={`peach-pair-bird peach-pair-bird-mobile peach-pair-bird-pose-${props.pose}`}>96- <svg viewBox="0 0 160 150" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMax meet" style={{ transform: "scaleX(-1)" }}>97- <defs>98- <radialGradient id="peach-pair-swallow-body" cx="0.55" cy="0.35" r="0.75">99- <stop offset="0%" stop-color={p.hi} />100- <stop offset="50%" stop-color={p.mid} />101- <stop offset="100%" stop-color={p.lo} />102- </radialGradient>103- <radialGradient id="peach-pair-swallow-head" cx="0.45" cy="0.4" r="0.7">104- <stop offset="0%" stop-color={p.hi} />105- <stop offset="100%" stop-color={p.mid} />106- </radialGradient>107- <linearGradient id="peach-pair-swallow-throat" x1="0" y1="0" x2="0" y2="1">108- <stop offset="0%" stop-color={p.cream} stop-opacity="0.95" />109- <stop offset="100%" stop-color={p.cream} stop-opacity="0.2" />110- </linearGradient>111- </defs>112- <ellipse class="peach-pair-bird-shadow" cx="86" cy="140" rx="34" ry="3" fill="rgba(0,0,0,0.45)" />113- <g class="peach-pair-bird-aura" opacity="0">114- <ellipse cx="80" cy="78" rx="60" ry="42" fill={p.hi} opacity="0.18" style={{ filter: "blur(8px)" }} />115- </g>116- <g class="peach-pair-bird-tail">117- <path d="M 38 80 Q 8 70, 0 60 Q 8 78, 28 88 Q 14 90, 6 96 Q 16 94, 32 92 L 38 84 Z" fill={p.lo} />118- <path d="M 30 84 Q 14 78, 6 70 Q 18 84, 30 88 Z" fill={p.shade} opacity="0.7" />119- </g>120- <g class="peach-pair-bird-feet" stroke={p.legs} stroke-width="1.5" stroke-linecap="round" fill="none">121- <line x1="76" y1="118" x2="74" y2="136" />122- <path d="M 74 136 L 69 140 M 74 136 L 74 141 M 74 136 L 79 140" stroke-width="1.1" />123- <line x1="96" y1="118" x2="98" y2="136" />124- <path d="M 98 136 L 93 140 M 98 136 L 98 141 M 98 136 L 103 140" stroke-width="1.1" />125- </g>126- <g class="peach-pair-bird-body">127- <path d="M 36 82 C 32 56, 56 42, 88 44 C 116 46, 134 60, 134 80 C 134 102, 116 118, 90 118 C 60 118, 40 104, 36 82 Z" fill="url(#peach-pair-swallow-body)" />128- <path d="M 50 64 C 60 52, 80 46, 100 48 C 116 50, 126 60, 128 70 C 120 60, 100 56, 80 60 C 64 64, 56 68, 50 64 Z" fill={p.shade} opacity="0.5" />129- <path d="M 64 82 C 66 100, 86 114, 102 112 C 116 110, 124 100, 126 88 C 116 102, 100 108, 84 104 C 74 102, 68 94, 64 82 Z" fill="url(#peach-pair-swallow-throat)" />130- <g class="peach-pair-bird-wing">131- <path d="M 50 70 C 64 64, 84 66, 100 72 C 112 78, 112 90, 100 96 C 84 100, 64 96, 52 88 C 46 80, 46 74, 50 70 Z" fill={p.lo} />132- <g fill={p.shade} opacity="0.85">133- <path d="M 96 88 L 110 92 L 100 96 Z" />134- <path d="M 88 92 L 102 98 L 92 100 Z" />135- <path d="M 80 94 L 92 100 L 84 102 Z" />136- </g>137- <g stroke={p.shade} stroke-width="0.6" stroke-linecap="round" fill="none" opacity="0.5">138- <path d="M 58 76 Q 70 82, 80 90" />139- <path d="M 68 72 Q 80 80, 90 88" />140- <path d="M 80 70 Q 90 78, 100 86" />141- </g>142- <path d="M 50 70 C 64 64, 84 66, 100 72" stroke={p.shade} stroke-width="1" fill="none" opacity="0.55" />143- </g>144- </g>145- <g class="peach-pair-bird-head-group" style={{ "transform-origin": "116px 64px" }}>146- <circle cx="118" cy="64" r="20" fill="url(#peach-pair-swallow-head)" />147- <path d="M 100 60 Q 108 46, 124 46 Q 134 50, 136 60 Q 124 54, 108 60 Z" fill={p.shade} opacity="0.55" />148- <g class="peach-pair-bird-eye-group">149- <circle cx="126" cy="60" r="4" fill="#1f0a10" />150- <circle cx="127.2" cy="58.8" r="1.2" fill="rgba(255,240,225,0.95)" />151- </g>152- <g class="peach-pair-bird-beak" style={{ "transform-origin": "136px 64px" }}>153- <path d="M 136 60 L 154 62 L 138 66 Z" fill={p.beak} />154- <path d="M 136 60 L 154 62 L 145 63 Z" fill={p.beakShade} opacity="0.5" />155- <path d="M 138 63 L 152 63" stroke={p.beakShade} stroke-width="0.5" opacity="0.6" />156- </g>157- </g>158- </svg>159- </div>160- );161-};src/popup/styles/popup.css+0 −264
@@ -7062,43 +7062,6 @@70627062 flex: 1;70637063 }70647064 7065-.peach-adopt-birds {7066- width: 120px;7067- height: 54px;7068- margin-bottom: 14px;7069- position: relative;7070-}7071-7072-.peach-adopt-birds::before {7073- content: "";7074- position: absolute;7075- left: 29px;7076- right: 29px;7077- top: 25px;7078- height: 1px;7079- background: color-mix(in srgb, var(--peach-accent) 50%, transparent);7080-}7081-7082-.peach-adopt-birds span {7083- position: absolute;7084- top: 11px;7085- width: 34px;7086- height: 28px;7087- border-radius: 50% 50% 46% 46%;7088- background: color-mix(in srgb, var(--peach-accent) 45%, var(--peach-t0));7089- box-shadow: 0 0 18px color-mix(in srgb, var(--peach-accent) 24%, transparent);7090- animation: peach-adopt-bob 1.8s ease-in-out infinite;7091-}7092-7093-.peach-adopt-birds span:first-child {7094- left: 12px;7095-}7096-7097-.peach-adopt-birds span:last-child {7098- right: 12px;7099- animation-delay: 0.25s;7100-}7101-71027065 .peach-adopt-sas {71037066 margin: 18px 0;71047067 padding: 14px;@@ -7111,11 +7074,6 @@71117074 text-align: center;71127075 }71137076 7114-@keyframes peach-adopt-bob {7115- 0%, 100% { transform: translateY(0); }7116- 50% { transform: translateY(-5px); }7117-}7118-71197077 .peach-onboarding-mobile-card-buttons {71207078 display: flex;71217079 flex-direction: column;@@ -9284,228 +9242,6 @@92849242 box-shadow: 0 0 6px color-mix(in srgb, var(--peach-warn, #d29257) 55%, transparent);92859243 }92869244 9287-/* Bird stage ----------------------------------------------------------- */9288-.peach-pair-stage {9289- position: relative;9290- /* Never let the flex column steal the stage's height to fit whatever is9291- below it — that is what clipped the birds to their legs before. Anything9292- that does not fit has to give somewhere else. */9293- flex: none;9294- margin: 14px 0 8px;9295- height: 180px;9296- border-radius: 12px;9297- background:9298- radial-gradient(ellipse 70% 60% at 50% 30%, color-mix(in srgb, var(--peach-accent) 10%, transparent), transparent 70%),9299- radial-gradient(ellipse 90% 100% at 50% 100%, color-mix(in srgb, #d8748a 14%, transparent), transparent 70%),9300- color-mix(in srgb, var(--peach-bg) 92%, #000);9301- border: 1px solid color-mix(in srgb, var(--peach-t0) 8%, transparent);9302- overflow: hidden;9303- isolation: isolate;9304-}9305-.peach-pair-stage::after {9306- content: '';9307- position: absolute;9308- left: 12%; right: 12%;9309- bottom: 36px;9310- height: 1px;9311- background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--peach-t0) 14%, transparent), transparent);9312-}9313-9314-.peach-pair-bird-slot {9315- position: absolute;9316- bottom: 8px;9317- width: 100px;9318- height: 110px;9319- display: flex;9320- align-items: flex-end;9321- justify-content: center;9322-}9323-.peach-pair-bird-slot-desktop { left: 28px; }9324-.peach-pair-bird-slot-mobile { right: 28px; }9325-9326-/* The QR screen is the tallest in the flow: a 180px stage plus headline, sub,9327- QR and action overflows the popup, and the stage is what gets clipped — you9328- saw bird legs and nothing else. Shrink the stage and the birds with it here9329- only, so the birds stay whole and the QR stays comfortably above the fold.9330- Uses the individual `scale` property rather than `transform`, which the9331- mobile bird's hop animation already owns. */9332-.peach-adoption-source[data-phase="invitation"] .peach-pair-stage {9333- height: 108px;9334- margin: 8px 0 6px;9335-}9336-.peach-adoption-source[data-phase="invitation"] .peach-pair-bird-slot {9337- scale: 0.62;9338- transform-origin: center bottom;9339-}9340-.peach-adoption-source[data-phase="invitation"] .peach-pair-stage::after {9341- bottom: 20px;9342-}9343-9344-/* Mobile bird hops in from the right when handshake begins */9345-.peach-pair-bird-slot-mobile {9346- opacity: 1;9347- transform: translateX(-6px);9348- transition: opacity 0.4s ease, transform 0.65s cubic-bezier(.5, 1.7, .4, 1);9349-}9350-.peach-pair-stage.act-listening .peach-pair-bird-slot-mobile,9351-.peach-pair-stage.act-chirping .peach-pair-bird-slot-mobile,9352-.peach-pair-stage.act-idle .peach-pair-bird-slot-mobile {9353- opacity: 0;9354- transform: translateX(60px) rotate(8deg);9355-}9356-.peach-pair-stage.act-handshake .peach-pair-bird-slot-desktop,9357-.peach-pair-stage.act-paired .peach-pair-bird-slot-desktop {9358- transform: translateX(6px);9359- transition: transform 0.5s cubic-bezier(.4, 1.4, .4, 1);9360-}9361-9362-/* Paired: both birds bob together */9363-.peach-pair-stage.act-paired .peach-pair-bird-slot {9364- animation: peach-pair-happy-bob 1.2s ease-in-out infinite;9365-}9366-.peach-pair-stage.act-paired .peach-pair-bird-slot-mobile { animation-delay: 0.2s; }9367-.peach-pair-stage.act-paired .peach-pair-bird-slot-desktop { --bb-x: 6px; }9368-.peach-pair-stage.act-paired .peach-pair-bird-slot-mobile { --bb-x: -6px; }9369-@keyframes peach-pair-happy-bob {9370- 0%, 100% { transform: translateX(var(--bb-x, 0)) translateY(0); }9371- 50% { transform: translateX(var(--bb-x, 0)) translateY(-3px); }9372-}9373-9374-/* Bird internal -------------------------------------------------------- */9375-.peach-pair-bird {9376- width: 108px;9377- height: 108px;9378- transform-origin: 50% 95%;9379-}9380-.peach-pair-bird svg { width: 100%; height: 100%; overflow: visible; }9381-9382-.peach-pair-bird-body {9383- transform-origin: 50% 80%;9384- animation: peach-pair-bird-bob 2.4s ease-in-out infinite;9385-}9386-@keyframes peach-pair-bird-bob {9387- 0%, 100% { transform: translateY(0) scale(1, 1); }9388- 50% { transform: translateY(-1.5px) scale(1.01, 0.99); }9389-}9390-9391-.peach-pair-bird-eye-group {9392- animation: peach-pair-blink 5.2s steps(1, end) infinite;9393- transform-origin: center;9394- transform-box: fill-box;9395-}9396-@keyframes peach-pair-blink {9397- 0%, 92%, 96%, 100% { transform: scaleY(1); }9398- 94% { transform: scaleY(0.05); }9399-}9400-9401-/* Pose: chirping — beak opens, head bobs, aura blooms */9402-.peach-pair-bird-pose-chirping .peach-pair-bird-body {9403- animation:9404- peach-pair-bird-bob 2.4s ease-in-out infinite,9405- peach-pair-squeeze 1.7s ease-in-out infinite;9406-}9407-@keyframes peach-pair-squeeze {9408- 0%, 70%, 100% { transform-origin: 50% 85%; }9409- 78% { transform: scale(1.04, 0.96) translateY(1px); }9410- 86% { transform: scale(0.98, 1.02); }9411-}9412-.peach-pair-bird-pose-chirping .peach-pair-bird-head-group {9413- animation: peach-pair-head-bob 1.7s ease-in-out infinite;9414-}9415-@keyframes peach-pair-head-bob {9416- 0%, 70%, 100% { transform: translateY(0) rotate(0); }9417- 78% { transform: translateY(-3px) rotate(-4deg); }9418- 86% { transform: translateY(-1px) rotate(-2deg); }9419-}9420-.peach-pair-bird-pose-chirping .peach-pair-bird-beak {9421- animation: peach-pair-beak-open 1.7s ease-in-out infinite;9422-}9423-@keyframes peach-pair-beak-open {9424- 0%, 70%, 100% { transform: rotate(0) scaleX(1); }9425- 78% { transform: rotate(-14deg) scaleX(1.05) translateY(-1px); }9426- 86% { transform: rotate(-6deg); }9427-}9428-.peach-pair-bird-pose-chirping .peach-pair-bird-aura {9429- animation: peach-pair-aura 1.7s ease-in-out infinite;9430-}9431-@keyframes peach-pair-aura {9432- 0%, 100% { opacity: 0; }9433- 50% { opacity: 0.8; }9434-}9435-.peach-pair-bird-pose-chirping .peach-pair-bird-tail {9436- animation: peach-pair-tail-twitch 1.7s ease-in-out infinite;9437- transform-origin: 100% 50%;9438-}9439-@keyframes peach-pair-tail-twitch {9440- 0%, 75%, 100% { transform: rotate(0); }9441- 82% { transform: rotate(-3deg) translateX(-1px); }9442-}9443-9444-/* Pose: attentive — head tilts toward partner */9445-.peach-pair-bird-pose-attentive .peach-pair-bird-head-group {9446- animation: peach-pair-head-tilt 0.6s cubic-bezier(.4, 1.6, .5, 1) both;9447-}9448-@keyframes peach-pair-head-tilt {9449- 0% { transform: rotate(0) translateY(0); }9450- 60% { transform: rotate(-10deg) translateY(-1px) translateX(2px); }9451- 100% { transform: rotate(-6deg); }9452-}9453-.peach-pair-bird-pose-attentive .peach-pair-bird-tail {9454- animation: peach-pair-tail-cock 0.6s cubic-bezier(.4, 1.6, .5, 1) both;9455- transform-origin: 100% 50%;9456-}9457-@keyframes peach-pair-tail-cock {9458- 0% { transform: rotate(0); }9459- 100% { transform: rotate(-4deg); }9460-}9461-9462-/* Pose: happy — wing flutter, tuft perk, tail wag */9463-.peach-pair-bird-pose-happy .peach-pair-bird-wing {9464- transform-origin: 80% 60%;9465- animation: peach-pair-wing-flutter 0.32s ease-in-out 5;9466-}9467-@keyframes peach-pair-wing-flutter {9468- 0%, 100% { transform: rotate(0) translateY(0); }9469- 50% { transform: rotate(-16deg) translateY(-2px); }9470-}9471-.peach-pair-bird-pose-happy .peach-pair-bird-tuft {9472- transform-origin: 0% 100%;9473- animation: peach-pair-tuft-perk 0.4s ease-in-out 3;9474-}9475-@keyframes peach-pair-tuft-perk {9476- 0%, 100% { transform: rotate(0); }9477- 50% { transform: rotate(-15deg) translateY(-1px); }9478-}9479-.peach-pair-bird-pose-happy .peach-pair-bird-tail {9480- transform-origin: 100% 50%;9481- animation: peach-pair-tail-wag 0.5s ease-in-out 4;9482-}9483-@keyframes peach-pair-tail-wag {9484- 0%, 100% { transform: rotate(0); }9485- 25% { transform: rotate(6deg); }9486- 75% { transform: rotate(-6deg); }9487-}9488-.peach-pair-bird-pose-happy .peach-pair-bird-head-group {9489- animation: peach-pair-head-perk 0.5s ease-out;9490-}9491-@keyframes peach-pair-head-perk {9492- 0% { transform: translateY(0); }9493- 40% { transform: translateY(-3px) rotate(2deg); }9494- 100% { transform: translateY(0); }9495-}9496-9497-/* Pose: sad — body droops forward */9498-.peach-pair-bird-pose-sad .peach-pair-bird-body {9499- transform: translateY(5px) rotate(-2deg);9500- animation: none;9501-}9502-.peach-pair-bird-pose-sad .peach-pair-bird-head-group {9503- transform: translateY(4px) rotate(8deg);9504-}9505-.peach-pair-bird-pose-sad .peach-pair-bird-tuft { transform: rotate(15deg) translateY(2px); }9506-.peach-pair-bird-pose-sad .peach-pair-bird-tail { transform: rotate(8deg) translateY(2px); }9507-9508-.peach-pair-bird-pose-hidden { opacity: 0; }95099245 95109246 /* Sound arcs ----------------------------------------------------------- */95119247 .peach-pair-arcs {src/popup/views/AdoptVaultView.tsx+0 −8
@@ -238,10 +238,6 @@238238 </Match>239239 240240 <Match when={phase() === "pair"}>241- <div class="peach-adopt-birds" aria-hidden="true">242- <span />243- <span />244- </div>245241 <h1>Pair with your other device.</h1>246242 {/* The populated source owns mesh configuration. Its invitation247243 carries either Peach's public relay or a self-hosted relay, so@@ -317,10 +313,6 @@317313 </Match>318314 319315 <Match when={phase() === "success"}>320- <div class="peach-adopt-birds" aria-hidden="true">321- <span />322- <span />323- </div>324316 <h1>Joined your vault.</h1>325317 <p>Your passwords will sync shortly.</p>326318 <button class="peach-btn peach-btn--accent peach-btn--md" onClick={() => void props.onDone()}>src/popup/views/Lock.tsx+0 −32
@@ -256,14 +256,6 @@256256 setLoading(false);257257 }258258 259- function openRestore(surface: Exclude<RestoreSurface, null>) {260- if (!props.ready || loading()) return;261- setPassword("");262- setConfirmPassword("");263- setError("");264- setRestoreSurface(surface);265- }266-267259 // ── All surfaces routed reactively via Switch/Match ───────────────────268260 return (269261 <Switch>@@ -518,30 +510,6 @@518510 {createBlockerMessage()}519511 </Show>520512 </button>521-522- {/* Restore lives directly under the primary CTA, not in a page523- footer — the 520px popup clipped a bottom footer below the524- fold, so a fresh install looked like "new vault" was the only525- path. These must stay on-screen without scrolling. */}526- <div class="peach-newvault-restore">527- <p class="peach-hint">Already have a backup?</p>528- <button529- class="peach-lock-restore-link"530- onClick={() => void openRestore("restore")}531- disabled={!props.ready}532- >533- › restore from backup534- </button>535- <Show when={props.onOpenCodexRestore}>536- <button537- class="peach-lock-restore-link"538- onClick={() => props.onOpenCodexRestore?.()}539- disabled={!props.ready}540- >541- › restore from peach codex542- </button>543- </Show>544- </div>545513 546514 <p class="peach-newvault-warning">547515 <span class="peach-newvault-warning-icon" aria-hidden="true">⚠</span>src/popup/views/PairDevice.test.tsx+0 −1
@@ -157,6 +157,5 @@157157 158158 pushFromBackground("ADOPTION_SOURCE_RESULT", { status: "complete" });159159 expect(container.textContent).toContain("Device added");160- expect(container.querySelectorAll(".peach-pair-bird-pose-happy")).toHaveLength(2);161160 });162161 });src/popup/views/PairDevice.tsx+0 −15
@@ -21,7 +21,6 @@2121 onMount,2222 } from "solid-js";2323 import { sendMessage } from "../../shared/bridge";24-import { DesktopBird, MobileBird } from "../components/PairingBirds";2524 import { PairingQrCode } from "../components/PairingQrCode";2625 import { SyncModeSelector } from "../components/SyncModeSelector";2726 @@ -130,18 +129,6 @@130129 </svg>131130 );132131 133- // The bird stage — always animating — shared by every pairing screen.134- const birdStage = (phase: SharePhase) => (135- <div class={`peach-pair-stage ${phase === "sas" || phase === "paired" ? "act-handshake" : "act-listening"}`} aria-hidden="true">136- <div class="peach-pair-bird-slot peach-pair-bird-slot-desktop">137- <DesktopBird pose={phase === "paired" ? "happy" : "chirping"} />138- </div>139- <div class="peach-pair-bird-slot peach-pair-bird-slot-mobile">140- <MobileBird pose={phase === "paired" ? "happy" : "attentive"} />141- </div>142- </div>143- );144-145132 function errorCopy(): string {146133 switch (shareError()) {147134 case "vault_locked":@@ -183,8 +170,6 @@183170 <div class="peach-pair-title">Add a new device</div>184171 <div class="peach-pair-head-spacer" />185172 </header>186-187- {birdStage(sharePhase())}188173 189174 <Show when={sharePhase() === "opening"}>190175 <div class="peach-desktop-searching">src/popup/views/contracts.test.tsx+0 −38
@@ -368,44 +368,6 @@368368 expect(container.textContent).toContain("CSV");369369 });370370 371- it("Lock onboarding restore options include Peach Codex", async () => {372- const onOpenCodexRestore = vi.fn();373- const container = mount(374- <LockView375- hasVault={false}376- ready={true}377- onUnlock={vi.fn()}378- onOpenCodexRestore={onOpenCodexRestore}379- />380- );381-382- const restoreButton = Array.from(container.querySelectorAll("button")).find(383- (candidate) => candidate.textContent?.includes("restore from backup")384- ) as HTMLButtonElement;385- restoreButton.click();386- await flushView();387-388- expect(container.textContent).toContain("PeachScript");389- expect(container.textContent).toContain("Codex");390- expect(container.textContent).toContain("CSV");391-392- const codexTab = Array.from(container.querySelectorAll("button")).find(393- (candidate) => candidate.textContent?.trim() === "Codex"394- ) as HTMLButtonElement;395- codexTab.click();396- await flushView();397-398- expect(container.textContent).toContain("Restore from Peach Codex");399- expect(container.textContent).toContain("Scan printed Codex pages");400-401- const openCodexButton = Array.from(container.querySelectorAll("button")).find(402- (candidate) => candidate.textContent?.trim() === "open Codex restore →"403- ) as HTMLButtonElement;404- openCodexButton.click();405-406- expect(onOpenCodexRestore).toHaveBeenCalledTimes(1);407- });408-409371 it("Lock can open directly on backup restore from onboarding", async () => {410372 const container = mount(() => (411373 <LockView