915845b2a0ad
replace notes flying-title animation with smooth 200ms crossfade (v2.41.3)
- time
- 2026-05-13 20:31 UTC · 5 months ago
- machine
- koh2
- parent
- cac826341309
↓ peach-browser-915845b2.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/styles/popup.css+9 −72src/popup/views/NotesMode.tsx+17 −131manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.41.2",4+ "version": "2.41.3",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": "2.41.2",4+ "version": "2.41.3",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": "2.41.2",3+ "version": "2.41.3",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/popup/styles/popup.css+9 −72
@@ -8163,22 +8163,25 @@81638163 .peach-notes-toc-wrapper {81648164 position: absolute;81658165 inset: 0;8166- transition: opacity 180ms ease-out;81678166 z-index: 1;8167+ opacity: 1;8168+ pointer-events: auto;8169+ transition: opacity 200ms ease;81688170 }81698171 8170-.peach-notes-toc-wrapper.is-visible { opacity: 1; pointer-events: auto; }8171-.peach-notes-toc-wrapper.is-expanding { opacity: 0; pointer-events: none; transition: opacity 150ms ease-out; }8172-.peach-notes-toc-wrapper.is-collapsing { opacity: 1; pointer-events: none; }8172+.peach-notes-toc-wrapper.is-hidden {8173+ opacity: 0;8174+ pointer-events: none;8175+}81738176 81748177 /* Editor wrapper */81758178 .peach-notes-editor-wrapper {81768179 position: absolute;81778180 inset: 0;8181+ z-index: 2;81788182 opacity: 0;81798183 pointer-events: none;8180- z-index: 2;8181- transition: opacity 220ms ease-in;8184+ transition: opacity 200ms ease;81828185 }81838186 81848187 .peach-notes-editor-wrapper.is-visible {@@ -8186,24 +8189,6 @@81868189 pointer-events: auto;81878190 }81888191 8189-.peach-notes-editor-wrapper.is-entering {8190- animation: peach-note-editor-enter 220ms 180ms ease-in both;8191-}8192-8193-.peach-notes-editor-wrapper.is-leaving {8194- animation: peach-note-editor-leave 200ms ease-out both;8195-}8196-8197-@keyframes peach-note-editor-enter {8198- from { opacity: 0; transform: translateY(6px); }8199- to { opacity: 1; transform: translateY(0); }8200-}8201-8202-@keyframes peach-note-editor-leave {8203- from { opacity: 1; transform: translateY(0); }8204- to { opacity: 0; transform: translateY(6px); }8205-}8206-82078192 /* TOC inner */82088193 .peach-notes-toc {82098194 position: relative;@@ -8343,15 +8328,6 @@83438328 background: rgba(255,255,255,0.02);83448329 }83458330 8346-/* Row fade animations (expansion) */8347-.peach-note-row.is-expanding-away {8348- transition: opacity 150ms ease, transform 150ms ease;8349- opacity: 0;8350-}8351-8352-.peach-note-row.is-expanding-away-up { transform: translateY(-6px); }8353-.peach-note-row.is-expanding-away-down { transform: translateY(6px); }8354-83558331 .peach-note-numeral {83568332 font-family: 'Cormorant Garamond', serif;83578333 font-style: italic;@@ -8692,42 +8668,3 @@86928668 }86938669 86948670 /* Flying title — shared element during expansion/collapse */8695-.peach-note-expanding-title {8696- position: absolute;8697- left: 20px;8698- right: 20px;8699- font-family: 'Cormorant Garamond', serif;8700- font-style: italic;8701- font-weight: 600;8702- font-size: 32px;8703- color: var(--peach-t0);8704- line-height: 1.2;8705- white-space: nowrap;8706- overflow: hidden;8707- text-overflow: ellipsis;8708- pointer-events: none;8709- z-index: 50;8710- transform-origin: left top;8711- transition:8712- top 280ms cubic-bezier(0, 0, 0, 1),8713- transform 280ms cubic-bezier(0, 0, 0, 1);8714- will-change: top, transform;8715-}8716-8717-/* TOC wrapper states */8718-.peach-notes-toc-wrapper.is-toc { opacity: 1; pointer-events: auto; }8719-.peach-notes-toc-wrapper.is-expanding {8720- opacity: 0;8721- pointer-events: none;8722- transition: opacity 150ms ease-out;8723-}8724-.peach-notes-toc-wrapper.is-collapsing {8725- pointer-events: none;8726- transition: opacity 200ms 100ms ease-in;8727- opacity: 1;8728-}8729-8730-/* Expanding target row hides its title text (flying title takes over) */8731-.peach-note-row.is-expanding-target .peach-note-title {8732- opacity: 0;8733-}src/popup/views/NotesMode.tsx+17 −131
@@ -1,4 +1,4 @@1-import { createSignal, onCleanup, onMount, Show } from "solid-js";1+import { createSignal, onMount, Show } from "solid-js";22 import type { VaultEntry } from "../../shared/types";33 import { sendMessage } from "../../shared/bridge";44 import { NotesTOC } from "./NotesTOC";@@ -11,39 +11,16 @@1111 notes: VaultEntry[];1212 }1313 14-type Phase =15- | "toc" // TOC fully visible16- | "expanding" // rows fading, title flying up17- | "editor" // editor fully visible18- | "collapsing"; // editor fading, title flying down14+type Phase = "toc" | "editor" | "collapsing";1915 20-interface ExpansionOrigin {21- top: number; // px from top of container22- title: string;23-}16+const FADE_MS = 200;2417 25-// Editor title top offset (below header + spacing)26-const EDITOR_TITLE_TOP = 68;27-const EXPAND_DURATION = 320;28-const COLLAPSE_DURATION = 320;29-3018 export function NotesMode(props: NotesModeProps) {31- let modeEl!: HTMLDivElement;32- let flyingTitleEl: HTMLDivElement | undefined;33- let tocWrapperEl: HTMLDivElement | undefined;34- let editorWrapperEl: HTMLDivElement | undefined;35-3619 const [phase, setPhase] = createSignal<Phase>("toc");3720 const [activeNote, setActiveNote] = createSignal<VaultEntry | null>(null);3821 const [isNewNote, setIsNewNote] = createSignal(false);39- const [origin, setOrigin] = createSignal<ExpansionOrigin | null>(null);40- const [flyingTitle, setFlyingTitle] = createSignal<string>("");41- const [flyingTop, setFlyingTop] = createSignal<number>(0);42- const [flyingFontSize, setFlyingFontSize] = createSignal<number>(18);43- const [flyingVisible, setFlyingVisible] = createSignal(false);4422 4523 onMount(() => {46- // Direct-to-editor from global search4724 if (props.initialNoteId) {4825 const note = props.notes.find((n) => n.id === props.initialNoteId);4926 if (note) {@@ -57,40 +34,12 @@5734 try { navigator.vibrate?.(ms); } catch { /* unsupported */ }5835 }5936 60- function handleNoteRowTap(note: VaultEntry, rowEl: HTMLElement) {37+ function handleNoteRowTap(note: VaultEntry, _rowEl: HTMLElement) {6138 if (phase() !== "toc") return;6239 vibrate(12);63-64- const rect = rowEl.getBoundingClientRect();65- const containerRect = modeEl.getBoundingClientRect();66- const rowTop = rect.top - containerRect.top;67- // The title text starts about 50% vertically within the row68- const titleTop = rowTop + (rect.height - 18) / 2;69-70- setOrigin({ top: titleTop, title: note.title || "Untitled" });71- setFlyingTitle(note.title || "Untitled");72- setFlyingTop(titleTop);73- setFlyingFontSize(18);74- setFlyingVisible(true);7540 setActiveNote(note);7641 setIsNewNote(false);77-78- // Phase 1: show flying title at row position, fade TOC rows79- setPhase("expanding");80-81- // Phase 2: animate title to editor position82- requestAnimationFrame(() => {83- requestAnimationFrame(() => {84- setFlyingTop(EDITOR_TITLE_TOP);85- setFlyingFontSize(32);86- });87- });88-89- // Phase 3: after animation, show editor and hide flying title90- setTimeout(() => {91- setPhase("editor");92- setFlyingVisible(false);93- }, EXPAND_DURATION);42+ requestAnimationFrame(() => setPhase("editor"));9443 }9544 9645 function handleNewNote() {@@ -98,61 +47,24 @@9847 vibrate(12);9948 setActiveNote(null);10049 setIsNewNote(true);101- setOrigin(null);102- // Simple expand from bottom — no flying title for new note103- setPhase("expanding");104- requestAnimationFrame(() => {105- setTimeout(() => {106- setPhase("editor");107- }, 80);108- });50+ requestAnimationFrame(() => setPhase("editor"));10951 }11052 11153 function handleEditorBack() {11254 if (phase() !== "editor") return;11355 vibrate(12);114-115- const orig = origin();116- if (orig) {117- // Animate title back to its row118- setFlyingTitle(activeNote()?.title || "Untitled");119- setFlyingTop(EDITOR_TITLE_TOP);120- setFlyingFontSize(32);121- setFlyingVisible(true);122- setPhase("collapsing");123-124- requestAnimationFrame(() => {125- requestAnimationFrame(() => {126- setFlyingTop(orig.top);127- setFlyingFontSize(18);128- });129- });130-131- setTimeout(() => {132- setFlyingVisible(false);133- setActiveNote(null);134- setIsNewNote(false);135- setOrigin(null);136- setPhase("toc");137- }, COLLAPSE_DURATION);138- } else {139- // No animation (new note or direct open)140- setPhase("collapsing");141- setTimeout(() => {142- setActiveNote(null);143- setIsNewNote(false);144- setPhase("toc");145- }, 200);146- }56+ setPhase("collapsing");57+ setTimeout(() => {58+ setActiveNote(null);59+ setIsNewNote(false);60+ setPhase("toc");61+ }, FADE_MS);14762 }14863 14964 function handleSaved(note: VaultEntry) {15065 if (!activeNote() || activeNote()!.id !== note.id) {15166 setActiveNote(note);15267 }153- if (flyingTitle() !== note.title) {154- setFlyingTitle(note.title || "Untitled");155- }15668 props.onNoteListChanged();15769 }15870 @@ -163,16 +75,11 @@16375 }16476 16577 return (166- <div ref={modeEl} class="peach-notes-mode" data-phase={phase()}>167- {/* TOC wrapper */}78+ <div class="peach-notes-mode" data-phase={phase()}>79+ {/* TOC — fades out when editor opens */}16880 <div169- ref={tocWrapperEl}17081 class="peach-notes-toc-wrapper"171- classList={{172- "is-toc": phase() === "toc",173- "is-expanding": phase() === "expanding",174- "is-collapsing": phase() === "collapsing",175- }}82+ classList={{ "is-hidden": phase() === "editor" }}17683 aria-hidden={phase() !== "toc"}17784 >17885 <NotesTOC@@ -181,35 +88,14 @@18188 onOpenNote={handleNoteRowTap}18289 onNewNote={handleNewNote}18390 onRefresh={props.onNoteListChanged}184- expandingNoteId={phase() === "expanding" || phase() === "collapsing" ? (activeNote()?.id ?? null) : null}18591 />18692 </div>18793 188- {/* Flying title — shared element between TOC and editor during animation */}189- <Show when={flyingVisible()}>190- <div191- ref={flyingTitleEl}192- class="peach-note-expanding-title"193- style={{194- top: `${flyingTop()}px`,195- transform: `scale(${flyingFontSize() / 32})`,196- }}197- aria-hidden="true"198- >199- {flyingTitle()}200- </div>201- </Show>202-203- {/* Editor wrapper */}94+ {/* Editor — fades in over the TOC */}20495 <Show when={activeNote() !== null || isNewNote()}>20596 <div206- ref={editorWrapperEl}20797 class="peach-notes-editor-wrapper"208- classList={{209- "is-visible": phase() === "editor",210- "is-entering": phase() === "expanding",211- "is-leaving": phase() === "collapsing",212- }}98+ classList={{ "is-visible": phase() === "editor" }}21399 >214100 <NotesEditor215101 note={activeNote()}