Spaces:
Running
Running
Upload 6 files
Browse files- CHANGELOG.md +34 -0
- index.html +44 -35
- lightning.js +25 -13
- script.js +13 -2
- styles.css +5 -2
CHANGELOG.md
CHANGED
|
@@ -6,6 +6,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
| 6 |
|
| 7 |
---
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
## [1.2.2] — 2025-12-14 🎵 AUDIO FIX
|
| 10 |
|
| 11 |
### 🐛 Bug Fixes
|
|
|
|
| 6 |
|
| 7 |
---
|
| 8 |
|
| 9 |
+
## [1.2.3] — 2025-12-16 🌿 IVY'S AUDIT
|
| 10 |
+
|
| 11 |
+
### 🐛 Bug Fixes
|
| 12 |
+
|
| 13 |
+
- **CSS Missing Variable** — Added `--bg-surface` variable (was undefined, used in `.shortcuts-grid kbd`)
|
| 14 |
+
- **Duplicate Animation** — Renamed second `radioPulse` to `radioBtnPulse` (was defined twice)
|
| 15 |
+
- **HuggingFace URLs** — Fixed both footer and About modal links (`/elysia-suite` → `/spaces/Elysia-Suite`)
|
| 16 |
+
- **Session Count Logic** — Clarified comment: increment happens AFTER break type decision
|
| 17 |
+
|
| 18 |
+
### ♿ Accessibility Improvements
|
| 19 |
+
|
| 20 |
+
- **ARIA Labels** — Added to all interactive elements:
|
| 21 |
+
- Start/Pause/Reset buttons
|
| 22 |
+
- Radio play button and volume slider
|
| 23 |
+
- Ambient sound buttons with `aria-pressed` state
|
| 24 |
+
- Settings toggle with `aria-expanded` and `aria-controls`
|
| 25 |
+
- **Modal Accessibility** — Added `role="dialog"`, `aria-modal="true"`, `aria-labelledby` to About and Shortcuts modals
|
| 26 |
+
- **Timer Accessibility** — Added `role="timer"`, `aria-live="polite"` to timer display for screen reader announcements
|
| 27 |
+
- **Progress Ring** — Added `role="progressbar"` with dynamic `aria-valuenow` updates
|
| 28 |
+
- **Decorative Elements** — Added `aria-hidden="true"` to emoji icons (screen readers skip them)
|
| 29 |
+
|
| 30 |
+
### ⚡ Performance Optimizations
|
| 31 |
+
|
| 32 |
+
- **Flash Element Reuse** — `flashScreen()` now reuses a single DOM element instead of creating/destroying on each lightning strike (reduces DOM thrashing)
|
| 33 |
+
|
| 34 |
+
### 🎨 Code Quality
|
| 35 |
+
|
| 36 |
+
- **Aria-pressed Sync** — Ambient buttons now properly update `aria-pressed` state when toggled
|
| 37 |
+
- **Aria-expanded Sync** — Settings button toggles `aria-expanded` when panel opens/closes
|
| 38 |
+
|
| 39 |
+
> _Audit performed with 💚 by Ivy 🌿 — "Le lierre pousse où il veut. Moi aussi."_
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
## [1.2.2] — 2025-12-14 🎵 AUDIO FIX
|
| 44 |
|
| 45 |
### 🐛 Bug Fixes
|
index.html
CHANGED
|
@@ -77,12 +77,12 @@
|
|
| 77 |
<!-- Radio Player (top for easy access 🎵) -->
|
| 78 |
<section class="radio-section">
|
| 79 |
<div class="radio-player">
|
| 80 |
-
<button id="btn-radio" class="btn-radio" title="Play/Pause Radio">
|
| 81 |
-
<span id="radio-icon">🎵</span>
|
| 82 |
</button>
|
| 83 |
<div class="radio-info">
|
| 84 |
<span id="radio-status" class="radio-status">Radio Off</span>
|
| 85 |
-
<select id="radio-select" class="radio-select">
|
| 86 |
<optgroup label="Lo-Fi & Chill">
|
| 87 |
<option value="lofi-girl">☕ Lofi Girl</option>
|
| 88 |
<option value="chillhop">🎧 Chillhop</option>
|
|
@@ -103,7 +103,7 @@
|
|
| 103 |
</select>
|
| 104 |
</div>
|
| 105 |
<input type="range" id="radio-volume" class="radio-volume" min="0" max="100" value="50"
|
| 106 |
-
title="Volume" />
|
| 107 |
</div>
|
| 108 |
</section>
|
| 109 |
|
|
@@ -111,29 +111,36 @@
|
|
| 111 |
<section class="ambient-section">
|
| 112 |
<div class="ambient-player">
|
| 113 |
<span class="ambient-label">🌙 Ambiance</span>
|
| 114 |
-
<div class="ambient-buttons">
|
| 115 |
-
<button class="ambient-btn" data-sound="rain" title="Rain"
|
| 116 |
-
|
| 117 |
-
<button class="ambient-btn" data-sound="
|
| 118 |
-
|
| 119 |
-
<button class="ambient-btn" data-sound="
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
</div>
|
| 122 |
<input type="range" id="ambient-volume" class="ambient-volume" min="0" max="100" value="30"
|
| 123 |
-
title="Ambient Volume" />
|
| 124 |
</div>
|
| 125 |
</section>
|
| 126 |
|
| 127 |
<!-- Timer Display -->
|
| 128 |
<section class="timer-section">
|
| 129 |
<div class="timer-ring">
|
| 130 |
-
<svg class="progress-ring" viewBox="0 0 200 200"
|
|
|
|
| 131 |
<circle class="progress-ring__bg" cx="100" cy="100" r="90" />
|
| 132 |
<circle class="progress-ring__progress" cx="100" cy="100" r="90" />
|
| 133 |
</svg>
|
| 134 |
-
<div class="timer-display">
|
| 135 |
<span id="minutes">25</span>
|
| 136 |
-
<span class="separator">:</span>
|
| 137 |
<span id="seconds">00</span>
|
| 138 |
</div>
|
| 139 |
</div>
|
|
@@ -149,16 +156,16 @@
|
|
| 149 |
|
| 150 |
<!-- Controls -->
|
| 151 |
<section class="controls">
|
| 152 |
-
<button id="btn-start" class="btn btn-primary">
|
| 153 |
-
<span class="btn-icon">▶</span>
|
| 154 |
<span class="btn-text">Start</span>
|
| 155 |
</button>
|
| 156 |
-
<button id="btn-pause" class="btn btn-secondary hidden">
|
| 157 |
-
<span class="btn-icon">⏸</span>
|
| 158 |
<span class="btn-text">Pause</span>
|
| 159 |
</button>
|
| 160 |
-
<button id="btn-reset" class="btn btn-ghost">
|
| 161 |
-
<span class="btn-icon">↺</span>
|
| 162 |
<span class="btn-text">Reset</span>
|
| 163 |
</button>
|
| 164 |
</section>
|
|
@@ -178,11 +185,12 @@
|
|
| 178 |
|
| 179 |
<!-- Settings Toggle -->
|
| 180 |
<section class="settings-section">
|
| 181 |
-
<button id="btn-settings" class="btn-settings"
|
| 182 |
-
|
|
|
|
| 183 |
</button>
|
| 184 |
|
| 185 |
-
<div id="settings-panel" class="settings-panel hidden">
|
| 186 |
<div class="setting-item">
|
| 187 |
<label for="sound-toggle">🔔 Sound notifications</label>
|
| 188 |
<input type="checkbox" id="sound-toggle" checked />
|
|
@@ -230,7 +238,7 @@
|
|
| 230 |
<span class="divider">•</span>
|
| 231 |
<a href="https://github.com/elysia-suite" target="_blank" rel="noopener">GitHub</a>
|
| 232 |
<span class="divider">•</span>
|
| 233 |
-
<a href="https://huggingface.co/
|
| 234 |
<span class="divider">•</span>
|
| 235 |
<a href="#" id="btn-shortcuts">Shortcuts (?)</a>
|
| 236 |
<span class="divider">•</span>
|
|
@@ -241,14 +249,14 @@
|
|
| 241 |
</div>
|
| 242 |
|
| 243 |
<!-- About Modal ⚡ -->
|
| 244 |
-
<div id="about-modal" class="modal hidden">
|
| 245 |
-
<div class="modal-overlay"></div>
|
| 246 |
<div class="modal-content">
|
| 247 |
-
<button class="modal-close" id="modal-close">×</button>
|
| 248 |
|
| 249 |
<div class="modal-header">
|
| 250 |
-
<h2>⚡ Lo-fi Focus Timer</h2>
|
| 251 |
-
<p class="modal-version">Version 1.2.
|
| 252 |
</div>
|
| 253 |
|
| 254 |
<div class="modal-body">
|
|
@@ -332,7 +340,7 @@
|
|
| 332 |
Website</a>
|
| 333 |
<a href="https://github.com/elysia-suite" target="_blank" rel="noopener" class="modal-link">📦
|
| 334 |
GitHub</a>
|
| 335 |
-
<a href="https://huggingface.co/
|
| 336 |
class="modal-link">🤗 Hugging Face</a>
|
| 337 |
</div>
|
| 338 |
<p class="modal-copyright">© 2025 Kai ⚡ — Elysia Suite</p>
|
|
@@ -342,13 +350,14 @@
|
|
| 342 |
</div>
|
| 343 |
|
| 344 |
<!-- Keyboard Shortcuts Modal ⌨️ -->
|
| 345 |
-
<div id="shortcuts-modal" class="modal hidden"
|
| 346 |
-
|
|
|
|
| 347 |
<div class="modal-content">
|
| 348 |
-
<button class="modal-close" id="shortcuts-close">×</button>
|
| 349 |
|
| 350 |
<div class="modal-header">
|
| 351 |
-
<h2>⌨️ Keyboard Shortcuts</h2>
|
| 352 |
<p class="modal-version">Quick reference for power users</p>
|
| 353 |
</div>
|
| 354 |
|
|
|
|
| 77 |
<!-- Radio Player (top for easy access 🎵) -->
|
| 78 |
<section class="radio-section">
|
| 79 |
<div class="radio-player">
|
| 80 |
+
<button id="btn-radio" class="btn-radio" title="Play/Pause Radio" aria-label="Play or pause radio">
|
| 81 |
+
<span id="radio-icon" aria-hidden="true">🎵</span>
|
| 82 |
</button>
|
| 83 |
<div class="radio-info">
|
| 84 |
<span id="radio-status" class="radio-status">Radio Off</span>
|
| 85 |
+
<select id="radio-select" class="radio-select" aria-label="Select radio station">
|
| 86 |
<optgroup label="Lo-Fi & Chill">
|
| 87 |
<option value="lofi-girl">☕ Lofi Girl</option>
|
| 88 |
<option value="chillhop">🎧 Chillhop</option>
|
|
|
|
| 103 |
</select>
|
| 104 |
</div>
|
| 105 |
<input type="range" id="radio-volume" class="radio-volume" min="0" max="100" value="50"
|
| 106 |
+
title="Volume" aria-label="Radio volume" />
|
| 107 |
</div>
|
| 108 |
</section>
|
| 109 |
|
|
|
|
| 111 |
<section class="ambient-section">
|
| 112 |
<div class="ambient-player">
|
| 113 |
<span class="ambient-label">🌙 Ambiance</span>
|
| 114 |
+
<div class="ambient-buttons" role="group" aria-label="Ambient sounds">
|
| 115 |
+
<button class="ambient-btn" data-sound="rain" title="Rain" aria-label="Toggle rain sounds"
|
| 116 |
+
aria-pressed="false">🌧️</button>
|
| 117 |
+
<button class="ambient-btn" data-sound="fire" title="Fireplace"
|
| 118 |
+
aria-label="Toggle fireplace sounds" aria-pressed="false">🔥</button>
|
| 119 |
+
<button class="ambient-btn" data-sound="cafe" title="Café" aria-label="Toggle café ambiance"
|
| 120 |
+
aria-pressed="false">☕</button>
|
| 121 |
+
<button class="ambient-btn" data-sound="forest" title="Forest" aria-label="Toggle forest sounds"
|
| 122 |
+
aria-pressed="false">🌲</button>
|
| 123 |
+
<button class="ambient-btn" data-sound="waves" title="Waves" aria-label="Toggle wave sounds"
|
| 124 |
+
aria-pressed="false">🌊</button>
|
| 125 |
+
<button class="ambient-btn" data-sound="thunder" title="Thunder"
|
| 126 |
+
aria-label="Toggle thunder sounds" aria-pressed="false">⛈️</button>
|
| 127 |
</div>
|
| 128 |
<input type="range" id="ambient-volume" class="ambient-volume" min="0" max="100" value="30"
|
| 129 |
+
title="Ambient Volume" aria-label="Ambient sounds volume" />
|
| 130 |
</div>
|
| 131 |
</section>
|
| 132 |
|
| 133 |
<!-- Timer Display -->
|
| 134 |
<section class="timer-section">
|
| 135 |
<div class="timer-ring">
|
| 136 |
+
<svg class="progress-ring" viewBox="0 0 200 200" role="progressbar" aria-valuemin="0"
|
| 137 |
+
aria-valuemax="100" aria-valuenow="100" aria-label="Timer progress">
|
| 138 |
<circle class="progress-ring__bg" cx="100" cy="100" r="90" />
|
| 139 |
<circle class="progress-ring__progress" cx="100" cy="100" r="90" />
|
| 140 |
</svg>
|
| 141 |
+
<div class="timer-display" role="timer" aria-live="polite" aria-atomic="true">
|
| 142 |
<span id="minutes">25</span>
|
| 143 |
+
<span class="separator" aria-hidden="true">:</span>
|
| 144 |
<span id="seconds">00</span>
|
| 145 |
</div>
|
| 146 |
</div>
|
|
|
|
| 156 |
|
| 157 |
<!-- Controls -->
|
| 158 |
<section class="controls">
|
| 159 |
+
<button id="btn-start" class="btn btn-primary" aria-label="Start timer">
|
| 160 |
+
<span class="btn-icon" aria-hidden="true">▶</span>
|
| 161 |
<span class="btn-text">Start</span>
|
| 162 |
</button>
|
| 163 |
+
<button id="btn-pause" class="btn btn-secondary hidden" aria-label="Pause timer">
|
| 164 |
+
<span class="btn-icon" aria-hidden="true">⏸</span>
|
| 165 |
<span class="btn-text">Pause</span>
|
| 166 |
</button>
|
| 167 |
+
<button id="btn-reset" class="btn btn-ghost" aria-label="Reset timer">
|
| 168 |
+
<span class="btn-icon" aria-hidden="true">↺</span>
|
| 169 |
<span class="btn-text">Reset</span>
|
| 170 |
</button>
|
| 171 |
</section>
|
|
|
|
| 185 |
|
| 186 |
<!-- Settings Toggle -->
|
| 187 |
<section class="settings-section">
|
| 188 |
+
<button id="btn-settings" class="btn-settings" aria-label="Toggle settings panel" aria-expanded="false"
|
| 189 |
+
aria-controls="settings-panel">
|
| 190 |
+
<span aria-hidden="true">⚙️</span> Settings
|
| 191 |
</button>
|
| 192 |
|
| 193 |
+
<div id="settings-panel" class="settings-panel hidden" role="region" aria-label="Timer settings">
|
| 194 |
<div class="setting-item">
|
| 195 |
<label for="sound-toggle">🔔 Sound notifications</label>
|
| 196 |
<input type="checkbox" id="sound-toggle" checked />
|
|
|
|
| 238 |
<span class="divider">•</span>
|
| 239 |
<a href="https://github.com/elysia-suite" target="_blank" rel="noopener">GitHub</a>
|
| 240 |
<span class="divider">•</span>
|
| 241 |
+
<a href="https://huggingface.co/Elysia-Suite" target="_blank" rel="noopener">HuggingFace</a>
|
| 242 |
<span class="divider">•</span>
|
| 243 |
<a href="#" id="btn-shortcuts">Shortcuts (?)</a>
|
| 244 |
<span class="divider">•</span>
|
|
|
|
| 249 |
</div>
|
| 250 |
|
| 251 |
<!-- About Modal ⚡ -->
|
| 252 |
+
<div id="about-modal" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="about-modal-title">
|
| 253 |
+
<div class="modal-overlay" aria-hidden="true"></div>
|
| 254 |
<div class="modal-content">
|
| 255 |
+
<button class="modal-close" id="modal-close" aria-label="Close modal">×</button>
|
| 256 |
|
| 257 |
<div class="modal-header">
|
| 258 |
+
<h2 id="about-modal-title">⚡ Lo-fi Focus Timer</h2>
|
| 259 |
+
<p class="modal-version">Version 1.2.3 — December 2025 (Ivy's Audit 🌿)</p>
|
| 260 |
</div>
|
| 261 |
|
| 262 |
<div class="modal-body">
|
|
|
|
| 340 |
Website</a>
|
| 341 |
<a href="https://github.com/elysia-suite" target="_blank" rel="noopener" class="modal-link">📦
|
| 342 |
GitHub</a>
|
| 343 |
+
<a href="https://huggingface.co/Elysia-Suite" target="_blank" rel="noopener"
|
| 344 |
class="modal-link">🤗 Hugging Face</a>
|
| 345 |
</div>
|
| 346 |
<p class="modal-copyright">© 2025 Kai ⚡ — Elysia Suite</p>
|
|
|
|
| 350 |
</div>
|
| 351 |
|
| 352 |
<!-- Keyboard Shortcuts Modal ⌨️ -->
|
| 353 |
+
<div id="shortcuts-modal" class="modal hidden" role="dialog" aria-modal="true"
|
| 354 |
+
aria-labelledby="shortcuts-modal-title">
|
| 355 |
+
<div class="modal-overlay" aria-hidden="true"></div>
|
| 356 |
<div class="modal-content">
|
| 357 |
+
<button class="modal-close" id="shortcuts-close" aria-label="Close modal">×</button>
|
| 358 |
|
| 359 |
<div class="modal-header">
|
| 360 |
+
<h2 id="shortcuts-modal-title">⌨️ Keyboard Shortcuts</h2>
|
| 361 |
<p class="modal-version">Quick reference for power users</p>
|
| 362 |
</div>
|
| 363 |
|
lightning.js
CHANGED
|
@@ -660,20 +660,32 @@
|
|
| 660 |
return new THREE.Line(geometry, material);
|
| 661 |
}
|
| 662 |
|
|
|
|
|
|
|
|
|
|
| 663 |
function flashScreen() {
|
| 664 |
-
//
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
}
|
| 678 |
|
| 679 |
// Add flash animation to document
|
|
|
|
| 660 |
return new THREE.Line(geometry, material);
|
| 661 |
}
|
| 662 |
|
| 663 |
+
// Reusable flash element (created once, reused)
|
| 664 |
+
let flashElement = null;
|
| 665 |
+
|
| 666 |
function flashScreen() {
|
| 667 |
+
// Create flash element once and reuse
|
| 668 |
+
if (!flashElement) {
|
| 669 |
+
flashElement = document.createElement("div");
|
| 670 |
+
flashElement.style.cssText = `
|
| 671 |
+
position: fixed;
|
| 672 |
+
inset: 0;
|
| 673 |
+
background: rgba(59, 130, 246, 0.1);
|
| 674 |
+
pointer-events: none;
|
| 675 |
+
z-index: 9999;
|
| 676 |
+
opacity: 0;
|
| 677 |
+
transition: opacity 0.15s ease-out;
|
| 678 |
+
`;
|
| 679 |
+
document.body.appendChild(flashElement);
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
// Trigger flash via opacity
|
| 683 |
+
flashElement.style.opacity = "1";
|
| 684 |
+
requestAnimationFrame(() => {
|
| 685 |
+
requestAnimationFrame(() => {
|
| 686 |
+
flashElement.style.opacity = "0";
|
| 687 |
+
});
|
| 688 |
+
});
|
| 689 |
}
|
| 690 |
|
| 691 |
// Add flash animation to document
|
script.js
CHANGED
|
@@ -217,6 +217,7 @@
|
|
| 217 |
audio.currentTime = 0;
|
| 218 |
state.ambient.active = state.ambient.active.filter(s => s !== soundKey);
|
| 219 |
btn.classList.remove("active");
|
|
|
|
| 220 |
console.log(`🌙 Stopped: ${AMBIENT_SOUNDS[soundKey].name}`);
|
| 221 |
|
| 222 |
// Update visualizer connection
|
|
@@ -234,6 +235,7 @@
|
|
| 234 |
// Remove loading, add active
|
| 235 |
btn.classList.remove("loading");
|
| 236 |
btn.classList.add("active");
|
|
|
|
| 237 |
state.ambient.active.push(soundKey);
|
| 238 |
console.log(`🌙 Playing: ${AMBIENT_SOUNDS[soundKey].name}`);
|
| 239 |
|
|
@@ -651,13 +653,14 @@
|
|
| 651 |
|
| 652 |
// Determine next mode
|
| 653 |
if (state.mode === "focus") {
|
| 654 |
-
// Increment session count BEFORE deciding break type (more logical)
|
| 655 |
// Long break every 4 completed focus sessions
|
|
|
|
| 656 |
if (state.sessionCount % SESSIONS_BEFORE_LONG_BREAK === 0) {
|
| 657 |
setMode("long");
|
| 658 |
} else {
|
| 659 |
setMode("short");
|
| 660 |
}
|
|
|
|
| 661 |
state.sessionCount++;
|
| 662 |
} else {
|
| 663 |
// After any break, go back to focus
|
|
@@ -739,6 +742,13 @@
|
|
| 739 |
const progress = state.timeRemaining / state.totalTime;
|
| 740 |
const offset = RING_CIRCUMFERENCE * (1 - progress);
|
| 741 |
elements.progressRing.style.strokeDashoffset = offset;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 742 |
}
|
| 743 |
|
| 744 |
function updateDocumentTitle(minutes, seconds) {
|
|
@@ -762,7 +772,8 @@
|
|
| 762 |
// ═══════════════════════════════════════════════════════════════════════
|
| 763 |
|
| 764 |
function toggleSettings() {
|
| 765 |
-
elements.settingsPanel.classList.toggle("hidden");
|
|
|
|
| 766 |
}
|
| 767 |
|
| 768 |
function loadSettings() {
|
|
|
|
| 217 |
audio.currentTime = 0;
|
| 218 |
state.ambient.active = state.ambient.active.filter(s => s !== soundKey);
|
| 219 |
btn.classList.remove("active");
|
| 220 |
+
btn.setAttribute("aria-pressed", "false");
|
| 221 |
console.log(`🌙 Stopped: ${AMBIENT_SOUNDS[soundKey].name}`);
|
| 222 |
|
| 223 |
// Update visualizer connection
|
|
|
|
| 235 |
// Remove loading, add active
|
| 236 |
btn.classList.remove("loading");
|
| 237 |
btn.classList.add("active");
|
| 238 |
+
btn.setAttribute("aria-pressed", "true");
|
| 239 |
state.ambient.active.push(soundKey);
|
| 240 |
console.log(`🌙 Playing: ${AMBIENT_SOUNDS[soundKey].name}`);
|
| 241 |
|
|
|
|
| 653 |
|
| 654 |
// Determine next mode
|
| 655 |
if (state.mode === "focus") {
|
|
|
|
| 656 |
// Long break every 4 completed focus sessions
|
| 657 |
+
// Increment session count AFTER deciding break type (current session just completed)
|
| 658 |
if (state.sessionCount % SESSIONS_BEFORE_LONG_BREAK === 0) {
|
| 659 |
setMode("long");
|
| 660 |
} else {
|
| 661 |
setMode("short");
|
| 662 |
}
|
| 663 |
+
// Increment AFTER the break decision (next focus will be new session)
|
| 664 |
state.sessionCount++;
|
| 665 |
} else {
|
| 666 |
// After any break, go back to focus
|
|
|
|
| 742 |
const progress = state.timeRemaining / state.totalTime;
|
| 743 |
const offset = RING_CIRCUMFERENCE * (1 - progress);
|
| 744 |
elements.progressRing.style.strokeDashoffset = offset;
|
| 745 |
+
|
| 746 |
+
// Update ARIA progress for screen readers
|
| 747 |
+
const progressPercent = Math.round(progress * 100);
|
| 748 |
+
const progressRingSvg = document.querySelector(".progress-ring");
|
| 749 |
+
if (progressRingSvg) {
|
| 750 |
+
progressRingSvg.setAttribute("aria-valuenow", progressPercent);
|
| 751 |
+
}
|
| 752 |
}
|
| 753 |
|
| 754 |
function updateDocumentTitle(minutes, seconds) {
|
|
|
|
| 772 |
// ═══════════════════════════════════════════════════════════════════════
|
| 773 |
|
| 774 |
function toggleSettings() {
|
| 775 |
+
const isHidden = elements.settingsPanel.classList.toggle("hidden");
|
| 776 |
+
elements.btnSettings.setAttribute("aria-expanded", !isHidden);
|
| 777 |
}
|
| 778 |
|
| 779 |
function loadSettings() {
|
styles.css
CHANGED
|
@@ -67,6 +67,9 @@
|
|
| 67 |
--border-color: #2a2a3a;
|
| 68 |
--border-focus: var(--accent-primary);
|
| 69 |
|
|
|
|
|
|
|
|
|
|
| 70 |
/* Shadows */
|
| 71 |
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
|
| 72 |
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
@@ -690,10 +693,10 @@ body {
|
|
| 690 |
|
| 691 |
.btn-radio.playing {
|
| 692 |
background: var(--accent-primary);
|
| 693 |
-
animation:
|
| 694 |
}
|
| 695 |
|
| 696 |
-
@keyframes
|
| 697 |
0%,
|
| 698 |
100% {
|
| 699 |
transform: scale(1);
|
|
|
|
| 67 |
--border-color: #2a2a3a;
|
| 68 |
--border-focus: var(--accent-primary);
|
| 69 |
|
| 70 |
+
/* Surface (for interactive elements like kbd) */
|
| 71 |
+
--bg-surface: #1e1e2a;
|
| 72 |
+
|
| 73 |
/* Shadows */
|
| 74 |
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
|
| 75 |
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
|
|
| 693 |
|
| 694 |
.btn-radio.playing {
|
| 695 |
background: var(--accent-primary);
|
| 696 |
+
animation: radioBtnPulse 1.5s ease-in-out infinite;
|
| 697 |
}
|
| 698 |
|
| 699 |
+
@keyframes radioBtnPulse {
|
| 700 |
0%,
|
| 701 |
100% {
|
| 702 |
transform: scale(1);
|