fix(a11y): bump record button to 48x48 and announce Recording stopped to screen readers

Record button bumped from 40x40 to 48x48 (WCAG 2.5.5 AA requires 44x44; 48 chosen as forgivable oversize for the most-touched control). Inner stop-square and record-circle bumped 14x14 to 16x16, Loader2 16 to 18, to keep the visual ratio.

Recording-status sr-only live region now announces both transitions: "Recording started" on start and "Recording stopped" on stop, with the stop message clearing after 1200ms so the live region does not hold stale text. Transition tracked via a plain let prevRecording (not $state) since it is bookkeeping and must not retrigger the effect. aria-atomic="true" added so the whole region re-reads on update.
This commit is contained in:
2026-05-07 11:20:35 +01:00
parent ca3a55320b
commit 47b005ad70

View File

@@ -794,11 +794,27 @@
); );
let transcriptionFailed = $state(false); let transcriptionFailed = $state(false);
// Screen-reader announcer for recording state transitions.
// Announces both start and stop, then clears so the live region does not
// hold stale text. prevRecording is a plain let (not $state) because it is
// bookkeeping and must not re-trigger the effect.
let recordingAnnouncement = $state("");
let prevRecording = false;
$effect(() => {
if (page.recording && !prevRecording) {
recordingAnnouncement = "Recording started";
} else if (!page.recording && prevRecording) {
recordingAnnouncement = "Recording stopped";
setTimeout(() => { recordingAnnouncement = ""; }, 1200);
}
prevRecording = page.recording;
});
</script> </script>
<div class="flex flex-col h-full animate-fade-in"> <div class="flex flex-col h-full animate-fade-in">
<div aria-live="assertive" class="sr-only" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);"> <div aria-live="assertive" aria-atomic="true" class="sr-only" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);">
{#if page.recording}Recording started{/if} {recordingAnnouncement}
</div> </div>
{#if needsDownload} {#if needsDownload}
<ModelDownloader <ModelDownloader
@@ -811,7 +827,7 @@
<div class="flex items-center gap-3 px-5 h-[56px] border-b border-border-subtle flex-shrink-0"> <div class="flex items-center gap-3 px-5 h-[56px] border-b border-border-subtle flex-shrink-0">
<!-- Record button --> <!-- Record button -->
<button <button
class="relative flex items-center justify-center w-[40px] h-[40px] min-w-[40px] flex-shrink-0 rounded-full text-white class="relative flex items-center justify-center w-[48px] h-[48px] min-w-[48px] flex-shrink-0 rounded-full text-white
{page.recording {page.recording
? 'bg-danger animate-pulse-warm' ? 'bg-danger animate-pulse-warm'
: modelLoading : modelLoading
@@ -824,11 +840,11 @@
aria-label={page.recording ? "Stop recording" : "Start recording"} aria-label={page.recording ? "Stop recording" : "Start recording"}
> >
{#if page.recording} {#if page.recording}
<span class="w-[14px] h-[14px] rounded-[3px] bg-white"></span> <span class="w-[16px] h-[16px] rounded-[3px] bg-white"></span>
{:else if modelLoading} {:else if modelLoading}
<Loader2 size={16} class="animate-spin" aria-hidden="true" /> <Loader2 size={18} class="animate-spin" aria-hidden="true" />
{:else} {:else}
<span class="w-[14px] h-[14px] rounded-full bg-white/90"></span> <span class="w-[16px] h-[16px] rounded-full bg-white/90"></span>
{/if} {/if}
</button> </button>
<span class="text-[12px] font-medium flex-shrink-0 {page.recording ? 'text-danger' : 'text-text-secondary'}"> <span class="text-[12px] font-medium flex-shrink-0 {page.recording ? 'text-danger' : 'text-text-secondary'}">