f326b4

style: remove accent ring + band glow, digit flare on reset, kill copy button border (v1.4.49)

time
2026-04-23 07:08 UTC · 6 months ago
machine
koh2
parent
548580
7 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/App.tsx+0 −18src/popup/styles/popup.css+9 −51src/popup/views/Entry.tsx+8 −5src/popup/views/contracts.test.tsx+0 −3
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "1.4.48",4+  "version": "1.4.49",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "1.4.48",4+  "version": "1.4.49",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": "1.4.48",3+  "version": "1.4.49",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/popup/App.tsx+0 −18
@@ -31,7 +31,6 @@3131 3232 const canarySuppressMap = new Map<string, boolean>();3333 let shellRingAnimation: Animation | null = null;34-let totpRingResetTimer: number | null = null;3534 3635 export function isCanarySuppressed(entryId: string): boolean {3736   return canarySuppressMap.get(entryId) ?? false;@@ -77,23 +76,6 @@7776   animation.oncancel = clearGlow;7877 }7978 80-export function triggerTotpRing() {81-  const shell = document.getElementById("hd-shell");82-  if (!shell) return;83-84-  shell.classList.remove("peach-shell--totp-ring");85-  void shell.offsetWidth;86-  shell.classList.add("peach-shell--totp-ring");87-88-  if (totpRingResetTimer !== null) {89-    window.clearTimeout(totpRingResetTimer);90-  }91-92-  totpRingResetTimer = window.setTimeout(() => {93-    shell.classList.remove("peach-shell--totp-ring");94-    totpRingResetTimer = null;95-  }, 920);96-}9779 9880 export function triggerPipPulse() {9981   const pip = document.querySelector(".peach-status-pip, .pip");

view file

src/popup/styles/popup.css+9 −51
@@ -96,21 +96,7 @@9696   position: relative;9797   isolation: isolate;9898 }99-.peach-shell::after {100-  content: "";101-  position: absolute;102-  inset: 2px;103-  border: 1px solid rgba(217, 104, 48, 0);104-  box-shadow: 0 0 0 rgba(217, 104, 48, 0);105-  border-radius: 1px;106-  opacity: 0;107-  transform: scale(0.998);108-  pointer-events: none;109-  z-index: 20;110-}111-.peach-shell.peach-shell--totp-ring::after {112-  animation: peach-edge-ring-breath 920ms cubic-bezier(0.2, 0.72, 0.24, 1) both;113-}99+114100 .peach-shell[data-surface="extension"] {115101   width: min(320px, 100vw);116102   border-radius: 0;@@ -666,12 +652,7 @@666652   display: grid;667653   grid-template-columns: 1fr auto;668654   gap: 10px;669-  box-shadow: inset 0 0 28px rgba(217, 104, 48, 0);670-  transition: box-shadow 500ms ease;671655 }672-.peach-totp-band.is-urgent {673-  box-shadow: inset 0 0 28px rgba(217, 104, 48, 0.13);674-}675656 .peach-totp-main {676657   min-width: 0;677658 }@@ -699,6 +680,7 @@699680 .peach-totp-copy-btn {700681   margin-bottom: 2px;701682   flex-shrink: 0;683+  border-color: transparent;702684 }703685 .peach-totp-meta { display: grid; justify-items: end; align-content: start; gap: 4px; }704686 .peach-totp-countdown {@@ -739,37 +721,13 @@739721   box-shadow: inset 0 6px 18px rgba(217, 104, 48, 0.09);740722 }741723 742-@keyframes peach-edge-ring-breath {743-  0% {744-    opacity: 0;745-    border-color: rgba(217, 104, 48, 0);746-    box-shadow: 0 0 0 rgba(217, 104, 48, 0);747-    transform: scale(0.998);748-  }749-  36% {750-    opacity: 0.88;751-    border-color: rgba(217, 104, 48, 0.42);752-    box-shadow:753-      inset 0 0 0 1px rgba(217, 104, 48, 0.16),754-      inset 0 0 16px rgba(217, 104, 48, 0.14),755-      0 0 10px rgba(217, 104, 48, 0.08);756-    transform: scale(1);757-  }758-  64% {759-    opacity: 0.46;760-    border-color: rgba(217, 104, 48, 0.24);761-    box-shadow:762-      inset 0 0 0 1px rgba(217, 104, 48, 0.1),763-      inset 0 0 20px rgba(217, 104, 48, 0.1),764-      0 0 12px rgba(217, 104, 48, 0.06);765-    transform: scale(1.0005);766-  }767-  100% {768-    opacity: 0;769-    border-color: rgba(217, 104, 48, 0);770-    box-shadow: 0 0 0 rgba(217, 104, 48, 0);771-    transform: scale(1.0012);772-  }724+@keyframes peach-totp-code-flare {725+  0%   { text-shadow: none; }726+  18%  { text-shadow: 0 0 10px rgba(240, 130, 60, 0.72), 0 0 26px rgba(217, 104, 48, 0.42); }727+  100% { text-shadow: none; }728+}729+.peach-totp-code.is-hot .peach-totp-code-text {730+  animation: peach-totp-code-flare 900ms cubic-bezier(0.2, 0, 0.4, 1) both;773731 }774732 .peach-search-bar {775733   min-height: 54px;

view file

src/popup/views/Entry.tsx+8 −5
@@ -3,7 +3,7 @@33 import { getDisplayDomain } from "../../shared/domain";44 import { faviconUrl } from "../../shared/favicon";55 import { generateTOTP, parseOtpauthURI } from "../../crypto/totp";6-import { sendMessage, triggerPipPulse, triggerRing, triggerTotpRing } from "../App";6+import { sendMessage, triggerPipPulse, triggerRing } from "../App";77 import { GeneratorDrawer } from "../components/GeneratorDrawer";88 99 type EditableField = "title" | "url" | "username" | "password" | "notes" | "totp" | null;@@ -25,6 +25,7 @@2525   const [copied, setCopied] = createSignal<string | null>(null);2626   const [totpTick, setTotpTick] = createSignal(0);2727   const [confirmDelete, setConfirmDelete] = createSignal(false);28+  const [totpHot, setTotpHot] = createSignal(false);2829   const [faviconSrc, setFaviconSrc] = createSignal<string | null>(null);2930 3031   const interval = window.setInterval(() => setTotpTick((n) => n + 1), 1000);@@ -49,7 +50,10 @@4950   createEffect(() => {5051     const state = totpState();5152     if (!state) { prevTotpCode = undefined; return; }52-    if (prevTotpCode !== undefined && state.code !== prevTotpCode) triggerTotpRing();53+    if (prevTotpCode !== undefined && state.code !== prevTotpCode) {54+      setTotpHot(true);55+      window.setTimeout(() => setTotpHot(false), 900);56+    }5357     prevTotpCode = state.code;5458   });5559 @@ -665,19 +669,18 @@665669               {/* ── TOTP ─────────────────────────────────────────── */}666670               <Show when={totpState()}>667671                 {(state) => (668-                  <section class={`peach-totp-band${state().remaining <= 9 ? " is-urgent" : ""}`}>672+                  <section class="peach-totp-band">669673                     <Show670674                       when={editingField() === "totp"}671675                       fallback={672676                         <>673677                           <div674678                             class="peach-totp-main peach-inline-clickable"675-                            title="Click to edit 2FA secret"676679                             onClick={() => startEdit("totp", entry().totp ?? "")}677680                           >678681                             <span class="peach-label">2-factor</span>679682                             <div class="peach-totp-code-row">680-                              <p class="peach-totp-code">683+                              <p class={`peach-totp-code${totpHot() ? " is-hot" : ""}`}>681684                                 <span class="peach-totp-code-text">682685                                   {state().code.slice(0, 3)} {state().code.slice(3)}683686                                 </span>

view file

src/popup/views/contracts.test.tsx+0 −3
@@ -15,7 +15,6 @@1515 1616 const sendMessageMock = vi.fn<(msg: unknown) => Promise<unknown>>();1717 const triggerRingMock = vi.fn();18-const triggerTotpRingMock = vi.fn();1918 const triggerPipPulseMock = vi.fn();2019 const exportEntriesToPeachJsonMock = vi.fn();2120 const parseImportSourceMock = vi.fn<(raw: string, password?: string) => Promise<ImportParseResult>>();@@ -24,7 +23,6 @@2423 vi.mock("../App", () => ({2524   sendMessage: (msg: unknown) => sendMessageMock(msg),2625   triggerRing: () => triggerRingMock(),27-  triggerTotpRing: () => triggerTotpRingMock(),2826   triggerPipPulse: () => triggerPipPulseMock(),2927 }));3028 @@ -71,7 +69,6 @@7169     document.body.innerHTML = "";7270     sendMessageMock.mockReset();7371     triggerRingMock.mockReset();74-    triggerTotpRingMock.mockReset();7572     triggerPipPulseMock.mockReset();7673     exportEntriesToPeachJsonMock.mockReset();7774     parseImportSourceMock.mockReset();

view file