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
↓ peach-browser-f326b4.tar.gzatlas at this save
files changed7 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 −3manifest.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": {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",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": {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");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;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>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();