feat(a11y): respect prefers-reduced-motion for scroll-sync

Uses scrollIntoView behavior 'instant' instead of 'smooth', skips
CSS animations (static highlight instead), and extends timeout to
2s for reduced-motion users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-07 11:27:01 +02:00
parent 81b14e5026
commit 10cecb01f5
3 changed files with 32 additions and 7 deletions

View File

@@ -194,4 +194,11 @@ const containerStyle = $derived(
.annotation-flash {
animation: annotation-flash-anim 1.5s ease-out;
}
@media (prefers-reduced-motion: reduce) {
.annotation-flash {
animation: none;
outline: 3px solid rgba(0, 199, 177, 0.8);
}
}
</style>

View File

@@ -48,4 +48,11 @@ let sorted = $derived([...blocks].sort((a, b) => a.sortOrder - b.sortOrder));
.flash-highlight {
animation: flash 1.2s ease-out;
}
@media (prefers-reduced-motion: reduce) {
.flash-highlight {
animation: none;
background-color: rgba(0, 199, 177, 0.18);
}
}
</style>

View File

@@ -56,6 +56,10 @@ let activeAnnotationId = $state<string | null>(null);
let highlightBlockId = $state<string | null>(null);
let flashAnnotationId = $state<string | null>(null);
const prefersReducedMotion = $derived(
typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches
);
// ── Transcription blocks ─────────────────────────────────────────────────────
let transcriptionBlocks = $state<TranscriptionBlockData[]>([]);
@@ -162,16 +166,20 @@ async function handleAnnotationClick(annotationId: string) {
const block = transcriptionBlocks.find((b) => b.annotationId === annotationId);
if (block) {
highlightBlockId = block.id;
setTimeout(() => {
highlightBlockId = null;
}, 1500);
setTimeout(
() => {
highlightBlockId = null;
},
prefersReducedMotion ? 2000 : 1500
);
}
// Wait for DOM to render, then scroll to the matching block
const scrollBehavior = prefersReducedMotion ? 'instant' : 'smooth';
requestAnimationFrame(() => {
if (block) {
const el = document.querySelector(`[data-block-id="${block.id}"]`);
el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
el?.scrollIntoView({ behavior: scrollBehavior, block: 'nearest' });
}
});
}
@@ -179,9 +187,12 @@ async function handleAnnotationClick(annotationId: string) {
function handleParagraphClick(annotationId: string) {
activeAnnotationId = annotationId;
flashAnnotationId = annotationId;
setTimeout(() => {
flashAnnotationId = null;
}, 1500);
setTimeout(
() => {
flashAnnotationId = null;
},
prefersReducedMotion ? 2000 : 1500
);
}
// Load blocks when transcribe mode is entered and set default panel mode