This commit is contained in:
@@ -37,6 +37,16 @@
|
||||
: "PAUSED",
|
||||
);
|
||||
|
||||
// Track status changes for aria-live region (announce only on change, not every tick)
|
||||
let lastAnnouncedStatus = $state("");
|
||||
let statusAnnouncement = $state("");
|
||||
$effect(() => {
|
||||
if (statusText !== lastAnnouncedStatus) {
|
||||
lastAnnouncedStatus = statusText;
|
||||
statusAnnouncement = `Timer status: ${statusText}. ${formatTime($timer.timeRemaining)} remaining.`;
|
||||
}
|
||||
});
|
||||
|
||||
const toggleBtnText = $derived(
|
||||
$timer.state === "running" ? "PAUSE" : "START",
|
||||
);
|
||||
@@ -87,6 +97,9 @@
|
||||
|
||||
<svelte:window bind:innerWidth={windowW} bind:innerHeight={windowH} />
|
||||
|
||||
<h1 class="sr-only" tabindex="-1">Dashboard</h1>
|
||||
<div aria-live="polite" class="sr-only">{statusAnnouncement}</div>
|
||||
|
||||
<div class="relative flex h-full flex-col items-center justify-center">
|
||||
<!-- Outer: responsive scaling (JS-driven), Inner: mount animation -->
|
||||
<div style="transform: scale({ringScale}); transform-origin: center center; margin-bottom: {ringMargin}px;">
|
||||
@@ -96,17 +109,20 @@
|
||||
size={280}
|
||||
strokeWidth={8}
|
||||
accentColor={$config.accent_color}
|
||||
label="Focus timer"
|
||||
valueText="{formatTime($timer.timeRemaining)} remaining"
|
||||
>
|
||||
<!-- Counter-scale wrapper: text shrinks less than ring -->
|
||||
<div style="transform: scale({textCounterScale}); transform-origin: center center;">
|
||||
<!-- Eye icon -->
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="mx-auto mb-3 eye-blink"
|
||||
width="26"
|
||||
height="26"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#444"
|
||||
stroke="#888"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -130,10 +146,7 @@
|
||||
<!-- Status label -->
|
||||
<span
|
||||
class="block text-center text-[11px] font-medium tracking-[0.25em]"
|
||||
class:text-[#444]={!$timer.prebreakWarning &&
|
||||
$timer.state === "running"}
|
||||
class:text-[#333]={!$timer.prebreakWarning &&
|
||||
$timer.state === "paused"}
|
||||
class:text-[#8a8a8a]={!$timer.prebreakWarning}
|
||||
class:text-warning={$timer.prebreakWarning}
|
||||
>
|
||||
{statusText}
|
||||
@@ -146,7 +159,7 @@
|
||||
<!-- Last break info -->
|
||||
<div use:fadeIn={{ delay: 0.4, y: 10 }}>
|
||||
{#if $timer.hasHadBreak}
|
||||
<p style="margin-bottom: {ringMargin}px;" class="text-[12px] text-[#2a2a2a]">
|
||||
<p style="margin-bottom: {ringMargin}px;" class="text-[12px] text-[#8a8a8a]">
|
||||
Last break {formatDurationAgo($timer.secondsSinceLastBreak)}
|
||||
</p>
|
||||
{:else}
|
||||
@@ -157,12 +170,13 @@
|
||||
<!-- Natural break notification toast -->
|
||||
{#if showNaturalBreakToast}
|
||||
<div
|
||||
role="alert"
|
||||
class="absolute top-6 left-1/2 -translate-x-1/2 rounded-2xl border px-5 py-3 text-center backdrop-blur-xl transition-all duration-500"
|
||||
style="border-color: rgba(63, 185, 80, 0.3); background: rgba(63, 185, 80, 0.1);"
|
||||
use:scaleIn={{ duration: 0.3, delay: 0 }}
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#3fb950" stroke-width="2">
|
||||
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#3fb950" stroke-width="2">
|
||||
<path d="M20 6L9 17l-5-5"/>
|
||||
</svg>
|
||||
<span class="text-[13px] text-[#3fb950]">Natural break detected - timer reset</span>
|
||||
@@ -190,12 +204,13 @@
|
||||
aria-label="Start break now"
|
||||
use:pressable
|
||||
class="flex h-11 w-11 items-center justify-center rounded-full
|
||||
border border-[#222] text-[#383838]
|
||||
border border-[#222] text-[#8a8a8a]
|
||||
transition-colors duration-200
|
||||
hover:border-[#333] hover:text-[#666]"
|
||||
hover:border-[#333] hover:text-[#aaa]"
|
||||
onclick={startBreakNow}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -216,15 +231,16 @@
|
||||
aria-label="Statistics"
|
||||
use:pressable
|
||||
class="flex h-11 w-11 items-center justify-center rounded-full
|
||||
border border-[#222] text-[#383838]
|
||||
border border-[#222] text-[#8a8a8a]
|
||||
transition-colors duration-200
|
||||
hover:border-[#333] hover:text-[#666]"
|
||||
hover:border-[#333] hover:text-[#aaa]"
|
||||
onclick={() => {
|
||||
invoke("set_view", { view: "stats" });
|
||||
currentView.set("stats");
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -247,12 +263,13 @@
|
||||
aria-label="Settings"
|
||||
use:pressable
|
||||
class="flex h-11 w-11 items-center justify-center rounded-full
|
||||
border border-[#222] text-[#383838]
|
||||
border border-[#222] text-[#8a8a8a]
|
||||
transition-colors duration-200
|
||||
hover:border-[#333] hover:text-[#666]"
|
||||
hover:border-[#333] hover:text-[#aaa]"
|
||||
onclick={openSettings}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 24 24"
|
||||
|
||||
Reference in New Issue
Block a user