From b846717db92d504547ff700d4c25d6210f7eba94 Mon Sep 17 00:00:00 2001 From: Aculix Technologies Date: Sun, 16 Aug 2026 02:36:48 +0530 Subject: [PATCH] fix: touch reachability, contrast and alignment A pass over desktop, tablet and phone widths, measuring rather than eyeballing. The one that matters: a task could not be deleted on a phone. The delete button is revealed on hover, touch devices report hover: none, so it sat at zero opacity with nothing to reveal it. The README told people to hover over a task and click it. It is now always visible where hover does not exist. Touch targets were under the 44px minimum: the checkbox at 24, delete at 32, the footer links at 23 tall. They now get 44px hit areas from overlays that leave the drawn size alone, behind a pointer: coarse query so a mouse keeps small precise targets and the full row stays draggable. Two contrast failures in light mode. The date was the brand blue on the near-white background at 3.54:1, so text now uses a darker --accent-text and reads 5.14:1. Completed rows carried opacity 0.8 over an already mid-contrast blue, which came to 3.34:1; the tint, the strikethrough and the colour say "done" well enough without it, and dropping it gives 4.78:1. Both themes now pass AA everywhere measured. The checkbox sat vertically centred, so on a task wrapping to five lines it floated in the middle of the block. Rows align to the top and task text gets a 24px line box, matching the checkbox exactly, which also spaces wrapped lines better. Reduced motion was only honoured by the empty-state drawing. Svelte's transitions run in JavaScript and never saw the media query, so tasks still flew in on a stagger. The component reads the preference directly and collapses its durations, and CSS covers the rest. The lift on a dragged card stays, since it tracks the pointer rather than playing at you. Long unbroken words already wrapped and no breakpoint scrolled sideways. --- README.md | 2 +- src/App.svelte | 27 ++++++++++++++++++--- src/style.css | 66 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 87 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f984626..903c04b 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ The date, storage, rollover, task and undo logic lives in `src/lib/` as plain mo ### Managing Tasks - **Add a task**: Type in the input field and press Enter - **Complete a task**: Click the checkbox next to the task -- **Delete a task**: Hover over a task and click the delete icon, or press `Delete` with the task focused +- **Delete a task**: Click the delete icon on the task (it shows on hover, and is always visible on touch), or press `Delete` with the task focused - **Undo a delete**: Press `Cmd/Ctrl+Z`. The task returns to where it was - **Reorder tasks**: Drag with a mouse, long-press then drag on touch, or focus a task and press `Alt+↑`/`Alt+↓` - **Clear input**: Press Escape while the input is focused diff --git a/src/App.svelte b/src/App.svelte index 5cda81d..6fbd9c7 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -84,6 +84,19 @@ darkMode = !darkMode; } + // Svelte's transitions are driven in JavaScript, so the CSS media query in + // style.css cannot reach them. Read the same preference here and collapse the + // durations. The lift on a dragged card stays: it tracks the pointer rather + // than playing at you, and losing it would make dragging harder to follow. + const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); + let reduceMotion = $state(motionQuery.matches); + + const flyIn = (index) => + reduceMotion ? { duration: 0 } : { y: -10, duration: 300, delay: index * 30, easing: cubicOut }; + + const flyOut = (index) => + reduceMotion ? { duration: 0 } : { x: 30, opacity: 0, duration: 250, delay: index * 20, easing: cubicOut }; + let fileInput; let status = $state(''); let statusIsError = $state(false); @@ -399,6 +412,10 @@ }, msUntilNextMidnight(new Date())); } + function handleMotionPreference(event) { + reduceMotion = event.matches; + } + function handleVisibility() { if (document.visibilityState === 'visible') runRollover(); } @@ -426,11 +443,13 @@ scheduleMidnight(); document.addEventListener('visibilitychange', handleVisibility); window.addEventListener('focus', runRollover); + motionQuery.addEventListener('change', handleMotionPreference); return () => { clearTimeout(midnightTimer); document.removeEventListener('visibilitychange', handleVisibility); window.removeEventListener('focus', runRollover); + motionQuery.removeEventListener('change', handleMotionPreference); }; }); @@ -518,7 +537,7 @@
{#key selectedKey} {#if tasks.length === 0} -
+