From 95f684450c055bb2915706b6136e1608ca103361 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Feb 2026 18:08:14 +0200 Subject: [PATCH] a11y: Tasks 2-6 - App shell, Titlebar, ToggleSwitch, Stepper, animate.ts - Add skip-to-content link and dynamic document title (App.svelte) - Wrap titlebar in header landmark, enlarge traffic lights to 44px (Titlebar.svelte) - Enlarge toggle switch to 52x28, improve OFF knob contrast (ToggleSwitch.svelte) - Enlarge stepper buttons to 36px, add keyboard hold-to-repeat (Stepper.svelte) - Add keyboard feedback to pressable, focus glow to glowHover (animate.ts) --- src/App.svelte | 13 ++++ src/lib/components/Stepper.svelte | 27 ++++++-- src/lib/components/Titlebar.svelte | 85 +++++++++++++------------- src/lib/components/ToggleSwitch.svelte | 6 +- src/lib/utils/animate.ts | 21 +++++++ 5 files changed, 101 insertions(+), 51 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 07c364b..c0e366f 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -75,6 +75,17 @@ }); }); + // WCAG 2.4.2: Document title reflects current view + $effect(() => { + const viewNames: Record = { + dashboard: "Dashboard", + breakScreen: "Break", + settings: "Settings", + stats: "Statistics", + }; + document.title = `Core Cooldown — ${viewNames[effectiveView] ?? "Dashboard"}`; + }); + // When fullscreen_mode is OFF, the separate break window handles breaks, // so the main window should keep showing whatever view it was on (dashboard). const effectiveView = $derived( @@ -85,11 +96,13 @@
+ {#if $config.background_blobs_enabled} {/if}
-
+ diff --git a/src/lib/components/ToggleSwitch.svelte b/src/lib/components/ToggleSwitch.svelte index fe0d671..9ffe20c 100644 --- a/src/lib/components/ToggleSwitch.svelte +++ b/src/lib/components/ToggleSwitch.svelte @@ -20,14 +20,14 @@ role="switch" aria-label={label} aria-checked={checked} - class="relative inline-flex h-[24px] w-[48px] shrink-0 cursor-pointer rounded-full + class="relative inline-flex h-[28px] w-[52px] min-h-[44px] shrink-0 cursor-pointer rounded-full transition-colors duration-200 ease-in-out" style="background: {checked ? $config.accent_color : '#1a1a1a'};" onclick={toggle} > diff --git a/src/lib/utils/animate.ts b/src/lib/utils/animate.ts index 6206c6b..2c76856 100644 --- a/src/lib/utils/animate.ts +++ b/src/lib/utils/animate.ts @@ -140,15 +140,32 @@ export function pressable(node: HTMLElement) { ); } + function onKeyDown(e: KeyboardEvent) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onDown(); + } + } + + function onKeyUp(e: KeyboardEvent) { + if (e.key === "Enter" || e.key === " ") { + onUp(); + } + } + node.addEventListener("mousedown", onDown); node.addEventListener("mouseup", onUp); node.addEventListener("mouseleave", onUp); + node.addEventListener("keydown", onKeyDown); + node.addEventListener("keyup", onKeyUp); return { destroy() { node.removeEventListener("mousedown", onDown); node.removeEventListener("mouseup", onUp); node.removeEventListener("mouseleave", onUp); + node.removeEventListener("keydown", onKeyDown); + node.removeEventListener("keyup", onKeyUp); active?.cancel(); }, }; @@ -200,6 +217,8 @@ export function glowHover( node.addEventListener("mouseenter", onEnter); node.addEventListener("mouseleave", onLeave); + node.addEventListener("focusin", onEnter); + node.addEventListener("focusout", onLeave); return { update(newOptions?: { color?: string }) { @@ -209,6 +228,8 @@ export function glowHover( destroy() { node.removeEventListener("mouseenter", onEnter); node.removeEventListener("mouseleave", onLeave); + node.removeEventListener("focusin", onEnter); + node.removeEventListener("focusout", onLeave); enterAnim?.cancel(); leaveAnim?.cancel(); },