Skip to content

Commit 0fabe59

Browse files
committed
Gate ink bleed reduced-motion completion
1 parent 7aa047b commit 0fabe59

3 files changed

Lines changed: 14 additions & 12 deletions

File tree

frontend/taskdeck-web/src/components/paper/InkBleed.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,16 @@ const dropletData = computed<VisibleDroplet[]>(() =>
298298
.ink-bleed--bloom .ink-bleed__drop,
299299
.ink-bleed--compose .ink-bleed__drop,
300300
.ink-bleed--settle .ink-bleed__drop,
301-
.ink-bleed--stamp .ink-bleed__drop,
302-
.ink-bleed--dried .ink-bleed__drop {
301+
.ink-bleed--stamp .ink-bleed__drop {
303302
animation: ink-bleed-bloom 1400ms linear forwards,
304303
ink-bleed-grow 1000ms cubic-bezier(0.2, 0.65, 0.25, 1) forwards;
305304
}
306305
306+
.ink-bleed--dried .ink-bleed__drop {
307+
opacity: 0.78;
308+
transform: translate(-50%, -50%) scale(1.4);
309+
}
310+
307311
/* Settle: desaturate. Stamp / dried: hold the desaturated state with full blur. */
308312
.ink-bleed--drying .ink-bleed__drop {
309313
filter: blur(10px);

frontend/taskdeck-web/src/composables/useInkBleed.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* - Singleton guard: at most one active bleed per view. Calling `start()` while
1313
* one is already running cancels the previous and begins a new sequence.
1414
* - Reduced-motion users skip the timer pipeline entirely. `start()` resolves
15-
* immediately to `dried` and `done` fires after one tick.
15+
* immediately to `dried`, but `done` still waits for `finish(runId)`.
1616
*/
1717
import { onBeforeUnmount, readonly, ref } from 'vue'
1818
import {
@@ -90,14 +90,9 @@ export function useInkBleed(
9090
const runId = activeRunId
9191

9292
if (isReducedMotion.value) {
93-
// Short-circuit: skip the timer pipeline entirely.
93+
// Short-circuit animation timers, but keep completion gated by the
94+
// wrapped async work calling finish(runId).
9495
phase.value = 'dried'
95-
// Defer done to next tick so callers can subscribe before it fires.
96-
const id = (globalThis.setTimeout as typeof setTimeout)(() => {
97-
if (!active || runId !== activeRunId) return
98-
fireDone()
99-
}, 0) as unknown as number
100-
timers.push(id)
10196
return runId
10297
}
10398

frontend/taskdeck-web/src/tests/components/paper/useInkBleed.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe('useInkBleed', () => {
165165

166166
it('cancel() clears state without firing done', () => {
167167
const { exposed } = createHost()
168-
exposed.bleed.start()
168+
const run = exposed.bleed.start()
169169
vi.advanceTimersByTime(1000)
170170

171171
exposed.bleed.cancel()
@@ -190,11 +190,14 @@ describe('useInkBleed', () => {
190190
const { exposed } = createHost()
191191
expect(exposed.bleed.isReducedMotion.value).toBe(true)
192192

193-
exposed.bleed.start()
193+
const run = exposed.bleed.start()
194194
expect(exposed.bleed.phase.value).toBe('dried')
195195

196196
// Advance one tick — done should fire deferred via setTimeout(0).
197197
vi.advanceTimersByTime(0)
198+
expect(exposed.doneCount).toBe(0)
199+
200+
exposed.bleed.finish(run)
198201
expect(exposed.doneCount).toBe(1)
199202

200203
// Advancing further does nothing — no per-phase timers were ever queued.

0 commit comments

Comments
 (0)