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)
This commit is contained in:
@@ -75,6 +75,17 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// WCAG 2.4.2: Document title reflects current view
|
||||||
|
$effect(() => {
|
||||||
|
const viewNames: Record<string, string> = {
|
||||||
|
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,
|
// When fullscreen_mode is OFF, the separate break window handles breaks,
|
||||||
// so the main window should keep showing whatever view it was on (dashboard).
|
// so the main window should keep showing whatever view it was on (dashboard).
|
||||||
const effectiveView = $derived(
|
const effectiveView = $derived(
|
||||||
@@ -85,11 +96,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="relative h-full bg-black">
|
<main class="relative h-full bg-black">
|
||||||
|
<a href="#main-content" class="skip-link">Skip to content</a>
|
||||||
{#if $config.background_blobs_enabled}
|
{#if $config.background_blobs_enabled}
|
||||||
<BackgroundBlobs accentColor={$config.accent_color} breakColor={$config.break_color} />
|
<BackgroundBlobs accentColor={$config.accent_color} breakColor={$config.break_color} />
|
||||||
{/if}
|
{/if}
|
||||||
<Titlebar />
|
<Titlebar />
|
||||||
<div
|
<div
|
||||||
|
id="main-content"
|
||||||
class="relative h-full overflow-hidden origin-top-left"
|
class="relative h-full overflow-hidden origin-top-left"
|
||||||
style="
|
style="
|
||||||
transform: scale({zoomScale});
|
transform: scale({zoomScale});
|
||||||
|
|||||||
@@ -55,20 +55,34 @@
|
|||||||
if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; }
|
if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; }
|
||||||
if (holdInterval) { clearTimeout(holdInterval as unknown as ReturnType<typeof setTimeout>); holdInterval = null; }
|
if (holdInterval) { clearTimeout(holdInterval as unknown as ReturnType<typeof setTimeout>); holdInterval = null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleKeydown(fn: () => void, e: KeyboardEvent) {
|
||||||
|
if (["Enter", " "].includes(e.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
startHold(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyup(e: KeyboardEvent) {
|
||||||
|
if (["Enter", " "].includes(e.key)) {
|
||||||
|
stopHold();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center gap-1.5" role="group" aria-label={label}>
|
<div class="flex items-center gap-1.5" role="group" aria-label={label}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Decrease"
|
aria-label="Decrease"
|
||||||
class="flex h-7 w-7 items-center justify-center rounded-lg
|
class="flex h-9 w-9 min-h-[44px] min-w-[44px] items-center justify-center rounded-lg
|
||||||
bg-[#141414] text-[#8a8a8a] transition-colors
|
border border-[#3a3a3a] bg-[#1a1a1a] text-text-sec transition-colors
|
||||||
hover:bg-[#1c1c1c] hover:text-white
|
hover:bg-[#1c1c1c] hover:text-white
|
||||||
disabled:opacity-20"
|
disabled:opacity-20"
|
||||||
onmousedown={() => startHold(decrement)}
|
onmousedown={() => startHold(decrement)}
|
||||||
onmouseup={stopHold}
|
onmouseup={stopHold}
|
||||||
onmouseleave={stopHold}
|
onmouseleave={stopHold}
|
||||||
onclick={(e) => { if (e.detail === 0) decrement(); }}
|
onkeydown={(e) => handleKeydown(decrement, e)}
|
||||||
|
onkeyup={handleKeyup}
|
||||||
disabled={value <= min}
|
disabled={value <= min}
|
||||||
>
|
>
|
||||||
−
|
−
|
||||||
@@ -79,14 +93,15 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="Increase"
|
aria-label="Increase"
|
||||||
class="flex h-7 w-7 items-center justify-center rounded-lg
|
class="flex h-9 w-9 min-h-[44px] min-w-[44px] items-center justify-center rounded-lg
|
||||||
bg-[#141414] text-[#8a8a8a] transition-colors
|
border border-[#3a3a3a] bg-[#1a1a1a] text-text-sec transition-colors
|
||||||
hover:bg-[#1c1c1c] hover:text-white
|
hover:bg-[#1c1c1c] hover:text-white
|
||||||
disabled:opacity-20"
|
disabled:opacity-20"
|
||||||
onmousedown={() => startHold(increment)}
|
onmousedown={() => startHold(increment)}
|
||||||
onmouseup={stopHold}
|
onmouseup={stopHold}
|
||||||
onmouseleave={stopHold}
|
onmouseleave={stopHold}
|
||||||
onclick={(e) => { if (e.detail === 0) increment(); }}
|
onkeydown={(e) => handleKeydown(increment, e)}
|
||||||
|
onkeyup={handleKeyup}
|
||||||
disabled={value >= max}
|
disabled={value >= max}
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Invisible drag region – traffic lights on the right -->
|
<!-- Invisible drag region – traffic lights on the right -->
|
||||||
<div
|
<header
|
||||||
|
role="banner"
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
class="group absolute top-0 left-0 right-0 z-50 flex h-10 items-center justify-end pr-3.5 select-none"
|
class="group absolute top-0 left-0 right-0 z-50 flex h-10 items-center justify-end pr-3.5 select-none"
|
||||||
>
|
>
|
||||||
@@ -20,65 +21,65 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- Traffic light buttons (order: maximize, minimize, close → close is rightmost) -->
|
<!-- Traffic light buttons (order: maximize, minimize, close → close is rightmost) -->
|
||||||
<div class="flex items-center gap-[8px] opacity-10 transition-opacity duration-300 group-hover:opacity-100 group-focus-within:opacity-100">
|
<div class="flex items-center gap-0 opacity-10 transition-opacity duration-300 group-hover:opacity-100 group-focus-within:opacity-100">
|
||||||
<!-- Maximize (green) -->
|
<!-- Maximize (green) -->
|
||||||
<button
|
<button
|
||||||
aria-label="Maximize"
|
aria-label="Maximize"
|
||||||
class="traffic-btn group/btn relative flex h-[15px] w-[15px] items-center justify-center
|
class="group/btn relative flex h-[44px] w-[44px] items-center justify-center"
|
||||||
rounded-full bg-[#27C93F] transition-all duration-150
|
|
||||||
hover:brightness-110"
|
|
||||||
onclick={() => appWindow.toggleMaximize()}
|
onclick={() => appWindow.toggleMaximize()}
|
||||||
>
|
>
|
||||||
<svg
|
<span class="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-[#27C93F] transition-all duration-150 group-hover/btn:brightness-110">
|
||||||
class="absolute opacity-0 transition-opacity duration-150 group-hover/btn:opacity-100"
|
<svg
|
||||||
width="8"
|
class="absolute opacity-0 transition-opacity duration-150 group-hover/btn:opacity-100"
|
||||||
height="8"
|
width="8"
|
||||||
viewBox="0 0 8 8"
|
height="8"
|
||||||
fill="none"
|
viewBox="0 0 8 8"
|
||||||
>
|
fill="none"
|
||||||
<polyline points="1,5 1,7 3,7" stroke="#006500" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
>
|
||||||
<polyline points="7,3 7,1 5,1" stroke="#006500" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
<polyline points="1,5 1,7 3,7" stroke="#006500" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
||||||
<line x1="1" y1="7" x2="7" y2="1" stroke="#006500" stroke-width="1.2" stroke-linecap="round" />
|
<polyline points="7,3 7,1 5,1" stroke="#006500" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
||||||
</svg>
|
<line x1="1" y1="7" x2="7" y2="1" stroke="#006500" stroke-width="1.2" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Minimize (yellow) -->
|
<!-- Minimize (yellow) -->
|
||||||
<button
|
<button
|
||||||
aria-label="Minimize"
|
aria-label="Minimize"
|
||||||
class="traffic-btn group/btn relative flex h-[15px] w-[15px] items-center justify-center
|
class="group/btn relative flex h-[44px] w-[44px] items-center justify-center"
|
||||||
rounded-full bg-[#FFBD2E] transition-all duration-150
|
|
||||||
hover:brightness-110"
|
|
||||||
onclick={() => appWindow.minimize()}
|
onclick={() => appWindow.minimize()}
|
||||||
>
|
>
|
||||||
<svg
|
<span class="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-[#FFBD2E] transition-all duration-150 group-hover/btn:brightness-110">
|
||||||
class="absolute opacity-0 transition-opacity duration-150 group-hover/btn:opacity-100"
|
<svg
|
||||||
width="8"
|
class="absolute opacity-0 transition-opacity duration-150 group-hover/btn:opacity-100"
|
||||||
height="2"
|
width="8"
|
||||||
viewBox="0 0 8 2"
|
height="2"
|
||||||
fill="none"
|
viewBox="0 0 8 2"
|
||||||
>
|
fill="none"
|
||||||
<line x1="1" y1="1" x2="7" y2="1" stroke="#995700" stroke-width="1.3" stroke-linecap="round" />
|
>
|
||||||
</svg>
|
<line x1="1" y1="1" x2="7" y2="1" stroke="#995700" stroke-width="1.3" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Close (red) — rightmost -->
|
<!-- Close (red) — rightmost -->
|
||||||
<button
|
<button
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
class="traffic-btn group/btn relative flex h-[15px] w-[15px] items-center justify-center
|
class="group/btn relative flex h-[44px] w-[44px] items-center justify-center"
|
||||||
rounded-full bg-[#FF5F57] transition-all duration-150
|
|
||||||
hover:brightness-110"
|
|
||||||
onclick={() => appWindow.close()}
|
onclick={() => appWindow.close()}
|
||||||
>
|
>
|
||||||
<svg
|
<span class="flex h-[20px] w-[20px] items-center justify-center rounded-full bg-[#FF5F57] transition-all duration-150 group-hover/btn:brightness-110">
|
||||||
class="absolute opacity-0 transition-opacity duration-150 group-hover/btn:opacity-100"
|
<svg
|
||||||
width="8"
|
class="absolute opacity-0 transition-opacity duration-150 group-hover/btn:opacity-100"
|
||||||
height="8"
|
width="8"
|
||||||
viewBox="0 0 8 8"
|
height="8"
|
||||||
fill="none"
|
viewBox="0 0 8 8"
|
||||||
>
|
fill="none"
|
||||||
<line x1="1.5" y1="1.5" x2="6.5" y2="6.5" stroke="#4a0002" stroke-width="1.3" stroke-linecap="round" />
|
>
|
||||||
<line x1="6.5" y1="1.5" x2="1.5" y2="6.5" stroke="#4a0002" stroke-width="1.3" stroke-linecap="round" />
|
<line x1="1.5" y1="1.5" x2="6.5" y2="6.5" stroke="#4a0002" stroke-width="1.3" stroke-linecap="round" />
|
||||||
</svg>
|
<line x1="6.5" y1="1.5" x2="1.5" y2="6.5" stroke="#4a0002" stroke-width="1.3" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
role="switch"
|
role="switch"
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
aria-checked={checked}
|
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"
|
transition-colors duration-200 ease-in-out"
|
||||||
style="background: {checked ? $config.accent_color : '#1a1a1a'};"
|
style="background: {checked ? $config.accent_color : '#1a1a1a'};"
|
||||||
onclick={toggle}
|
onclick={toggle}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none inline-block h-[19px] w-[19px] rounded-full
|
class="pointer-events-none inline-block h-[22px] w-[22px] rounded-full
|
||||||
shadow-sm transition-transform duration-200 ease-in-out
|
shadow-sm transition-transform duration-200 ease-in-out
|
||||||
{checked ? 'translate-x-[26px] bg-white' : 'translate-x-[3px] bg-[#444]'} mt-[2.5px]"
|
{checked ? 'translate-x-[27px] bg-white' : 'translate-x-[3px] bg-[#666]'} mt-[3px]"
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -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("mousedown", onDown);
|
||||||
node.addEventListener("mouseup", onUp);
|
node.addEventListener("mouseup", onUp);
|
||||||
node.addEventListener("mouseleave", onUp);
|
node.addEventListener("mouseleave", onUp);
|
||||||
|
node.addEventListener("keydown", onKeyDown);
|
||||||
|
node.addEventListener("keyup", onKeyUp);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
destroy() {
|
destroy() {
|
||||||
node.removeEventListener("mousedown", onDown);
|
node.removeEventListener("mousedown", onDown);
|
||||||
node.removeEventListener("mouseup", onUp);
|
node.removeEventListener("mouseup", onUp);
|
||||||
node.removeEventListener("mouseleave", onUp);
|
node.removeEventListener("mouseleave", onUp);
|
||||||
|
node.removeEventListener("keydown", onKeyDown);
|
||||||
|
node.removeEventListener("keyup", onKeyUp);
|
||||||
active?.cancel();
|
active?.cancel();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -200,6 +217,8 @@ export function glowHover(
|
|||||||
|
|
||||||
node.addEventListener("mouseenter", onEnter);
|
node.addEventListener("mouseenter", onEnter);
|
||||||
node.addEventListener("mouseleave", onLeave);
|
node.addEventListener("mouseleave", onLeave);
|
||||||
|
node.addEventListener("focusin", onEnter);
|
||||||
|
node.addEventListener("focusout", onLeave);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
update(newOptions?: { color?: string }) {
|
update(newOptions?: { color?: string }) {
|
||||||
@@ -209,6 +228,8 @@ export function glowHover(
|
|||||||
destroy() {
|
destroy() {
|
||||||
node.removeEventListener("mouseenter", onEnter);
|
node.removeEventListener("mouseenter", onEnter);
|
||||||
node.removeEventListener("mouseleave", onLeave);
|
node.removeEventListener("mouseleave", onLeave);
|
||||||
|
node.removeEventListener("focusin", onEnter);
|
||||||
|
node.removeEventListener("focusout", onLeave);
|
||||||
enterAnim?.cancel();
|
enterAnim?.cancel();
|
||||||
leaveAnim?.cancel();
|
leaveAnim?.cancel();
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user