Elysia-Suite commited on
Commit
d804252
·
verified ·
1 Parent(s): 6897412

Upload 6 files

Browse files
Files changed (5) hide show
  1. CHANGELOG.md +34 -0
  2. index.html +44 -35
  3. lightning.js +25 -13
  4. script.js +13 -2
  5. 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">🌧️</button>
116
- <button class="ambient-btn" data-sound="fire" title="Fireplace">🔥</button>
117
- <button class="ambient-btn" data-sound="cafe" title="Café">☕</button>
118
- <button class="ambient-btn" data-sound="forest" title="Forest">🌲</button>
119
- <button class="ambient-btn" data-sound="waves" title="Waves">🌊</button>
120
- <button class="ambient-btn" data-sound="thunder" title="Thunder">⛈️</button>
 
 
 
 
 
 
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
- <span>⚙️</span> Settings
 
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/elysia-suite" target="_blank" rel="noopener">HuggingFace</a>
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">&times;</button>
248
 
249
  <div class="modal-header">
250
- <h2>⚡ Lo-fi Focus Timer</h2>
251
- <p class="modal-version">Version 1.2.1 — December 2025 (Optimized + Accessible)</p>
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/elysia-suite" target="_blank" rel="noopener"
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
- <div class="modal-overlay"></div>
 
347
  <div class="modal-content">
348
- <button class="modal-close" id="shortcuts-close">&times;</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">&times;</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">&times;</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
- // Brief white flash overlay
665
- const flash = document.createElement("div");
666
- flash.style.cssText = `
667
- position: fixed;
668
- inset: 0;
669
- background: rgba(59, 130, 246, 0.1);
670
- pointer-events: none;
671
- z-index: 9999;
672
- animation: flashFade 0.15s ease-out forwards;
673
- `;
674
- document.body.appendChild(flash);
675
-
676
- setTimeout(() => flash.remove(), 150);
 
 
 
 
 
 
 
 
 
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: radioPulse 1.5s ease-in-out infinite;
694
  }
695
 
696
- @keyframes radioPulse {
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);