mirror of
https://github.com/aculix/negotium.git
synced 2026-09-11 07:28:17 +00:00
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.
This commit is contained in:
+23
-4
@@ -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);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@@ -518,7 +537,7 @@
|
||||
<div class="task-list">
|
||||
{#key selectedKey}
|
||||
{#if tasks.length === 0}
|
||||
<div class="empty-state" transition:fade={{ duration: 200 }}>
|
||||
<div class="empty-state" transition:fade={{ duration: reduceMotion ? 0 : 200 }}>
|
||||
<svg class="empty-art" viewBox="0 0 120 120" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
|
||||
<g class="empty-art-motes" stroke="currentColor" stroke-width="3" stroke-linecap="round">
|
||||
<path d="M40 34 L40 34" />
|
||||
@@ -550,9 +569,9 @@
|
||||
class:dragging={task.id === draggedId}
|
||||
class:settling={task.id === settlingId}
|
||||
style={rowStyle(task.id)}
|
||||
animate:flip={{ duration: task.id === draggedId ? 0 : 240, easing: cubicOut }}
|
||||
in:fly={{ y: -10, duration: 300, delay: index * 30, easing: cubicOut }}
|
||||
out:fly={{ x: 30, opacity: 0, duration: 250, delay: index * 20, easing: cubicOut }}
|
||||
animate:flip={{ duration: (reduceMotion || task.id === draggedId) ? 0 : 240, easing: cubicOut }}
|
||||
in:fly={flyIn(index)}
|
||||
out:fly={flyOut(index)}
|
||||
onpointerdown={(e) => handlePointerDown(e, index)}
|
||||
onpointermove={handlePointerMove}
|
||||
onpointerup={handlePointerUp}
|
||||
|
||||
Reference in New Issue
Block a user