milwright commited on
Commit
20b30b6
·
1 Parent(s): 86f177c

fix level progression and leaderboard accessibility

Browse files
Files changed (3) hide show
  1. src/app.js +6 -10
  2. src/leaderboardUI.js +26 -14
  3. src/styles.css +85 -69
src/app.js CHANGED
@@ -50,17 +50,13 @@ class App {
50
 
51
  // Leaderboard button
52
  if (this.elements.leaderboardBtn) {
53
- this.elements.leaderboardBtn.addEventListener('click', () => {
 
54
  this.leaderboardUI.show();
55
  });
56
  }
57
 
58
- // Allow Enter key to submit when focused on an input
59
- document.addEventListener('keydown', (e) => {
60
- if (e.key === 'Enter' && e.target.classList.contains('cloze-input')) {
61
- this.handleSubmit();
62
- }
63
- });
64
  }
65
 
66
  async startNewGame() {
@@ -170,7 +166,7 @@ class App {
170
  if (results.passed) {
171
  // Check if level was just advanced
172
  if (results.justAdvancedLevel) {
173
- message += ` Level ${results.currentLevel} unlocked!`;
174
 
175
  // Check for milestone notification (every 5 levels)
176
  if (results.currentLevel % 5 === 0) {
@@ -180,11 +176,11 @@ class App {
180
  // Check for high score
181
  this.checkForHighScore();
182
  } else {
183
- message += ` Passed`;
184
  }
185
  this.elements.result.className = 'mt-4 text-center font-semibold text-green-600';
186
  } else {
187
- message += ` - Try again (need ${results.requiredCorrect}/${results.total})`;
188
  this.elements.result.className = 'mt-4 text-center font-semibold text-red-600';
189
  }
190
 
 
50
 
51
  // Leaderboard button
52
  if (this.elements.leaderboardBtn) {
53
+ this.elements.leaderboardBtn.addEventListener('click', (e) => {
54
+ e.stopPropagation();
55
  this.leaderboardUI.show();
56
  });
57
  }
58
 
59
+ // Note: Enter key handling is done per-input in setupInputListeners()
 
 
 
 
 
60
  }
61
 
62
  async startNewGame() {
 
166
  if (results.passed) {
167
  // Check if level was just advanced
168
  if (results.justAdvancedLevel) {
169
+ message += ` - Level ${results.currentLevel} unlocked!`;
170
 
171
  // Check for milestone notification (every 5 levels)
172
  if (results.currentLevel % 5 === 0) {
 
176
  // Check for high score
177
  this.checkForHighScore();
178
  } else {
179
+ message += ` - Passed!`;
180
  }
181
  this.elements.result.className = 'mt-4 text-center font-semibold text-green-600';
182
  } else {
183
+ message += ` - Failed (need ${results.requiredCorrect} correct)`;
184
  this.elements.result.className = 'mt-4 text-center font-semibold text-red-600';
185
  }
186
 
src/leaderboardUI.js CHANGED
@@ -30,7 +30,7 @@ export class LeaderboardUI {
30
  this.modal.innerHTML = `
31
  <div class="leaderboard-modal">
32
  <div class="leaderboard-header">
33
- <h2 class="leaderboard-title">🏆 HIGH SCORES 🏆</h2>
34
  <button class="leaderboard-close" aria-label="Close leaderboard">×</button>
35
  </div>
36
 
@@ -42,11 +42,11 @@ export class LeaderboardUI {
42
  ${playerStats.highestLevel > 1 ? `
43
  <div class="leaderboard-player-stats">
44
  <div class="player-best">
45
- Your Best: <span class="highlight">Level ${playerStats.highestLevel} (Round ${playerStats.roundAtHighestLevel})</span>
46
  </div>
47
  <div class="player-stats-details">
48
  <div>Passages: ${playerStats.totalPassagesPassed}/${playerStats.totalPassagesAttempted} (${playerStats.successRate}%)</div>
49
- <div>Longest Streak: ${playerStats.longestStreak} • Perfect Rounds: ${playerStats.perfectRounds}</div>
50
  </div>
51
  </div>
52
  ` : ''}
@@ -63,6 +63,13 @@ export class LeaderboardUI {
63
 
64
  // Add event listeners
65
  this.modal.querySelector('.leaderboard-close').addEventListener('click', () => this.hide());
 
 
 
 
 
 
 
66
  this.modal.addEventListener('click', (e) => {
67
  if (e.target === this.modal) {
68
  this.hide();
@@ -100,7 +107,7 @@ export class LeaderboardUI {
100
  <div class="leaderboard-entry ${rankClass} ${playerClass}">
101
  <span class="entry-rank">#${entry.rank}</span>
102
  <span class="entry-initials">${entry.initials}</span>
103
- <span class="entry-score">Level ${entry.level} <span class="entry-round">(Round ${entry.round})</span></span>
104
  </div>
105
  `;
106
  }).join('');
@@ -162,9 +169,10 @@ export class LeaderboardUI {
162
  this.initialsModal.innerHTML = `
163
  <div class="initials-modal">
164
  <div class="initials-header">
165
- <h2 class="initials-title">🎉 NEW HIGH SCORE! 🎉</h2>
 
166
  <div class="initials-achievement">
167
- You reached <span class="highlight">Level ${level}</span>!
168
  <br>
169
  <span class="rank-text">${this.getRankText(rank)}</span>
170
  </div>
@@ -188,11 +196,11 @@ export class LeaderboardUI {
188
  <div class="initials-instructions">
189
  <p>Use arrow keys ↑↓ to change letters</p>
190
  <p>Press Tab or ←→ to move between slots</p>
191
- <p>Press Enter to confirm</p>
192
  </div>
193
 
194
  <button class="initials-submit typewriter-button">
195
- SUBMIT
196
  </button>
197
  </div>
198
  </div>
@@ -214,10 +222,10 @@ export class LeaderboardUI {
214
  */
215
  getRankText(rank) {
216
  const ordinal = this.getOrdinal(rank);
217
- if (rank === 1) return `🥇 ${ordinal} PLACE - TOP SCORE! 🥇`;
218
- if (rank === 2) return `🥈 ${ordinal} PLACE 🥈`;
219
- if (rank === 3) return `🥉 ${ordinal} PLACE 🥉`;
220
- return `${ordinal} place on the leaderboard!`;
221
  }
222
 
223
  /**
@@ -281,6 +289,10 @@ export class LeaderboardUI {
281
  e.preventDefault();
282
  this.submitInitials();
283
  break;
 
 
 
 
284
  }
285
  };
286
  document.addEventListener('keydown', this.initialsKeyHandler);
@@ -380,7 +392,7 @@ export class LeaderboardUI {
380
  successDiv.innerHTML = `
381
  <div class="leaderboard-modal success-message">
382
  <div class="success-content">
383
- <h2>✓ SCORE SAVED!</h2>
384
  <p>Your initials have been added to the leaderboard</p>
385
  </div>
386
  </div>
@@ -409,7 +421,7 @@ export class LeaderboardUI {
409
  toast.className = 'milestone-toast';
410
  toast.innerHTML = `
411
  <div class="toast-content">
412
- 🎯 Milestone Reached: Level ${level}!
413
  </div>
414
  `;
415
 
 
30
  this.modal.innerHTML = `
31
  <div class="leaderboard-modal">
32
  <div class="leaderboard-header">
33
+ <h2 class="leaderboard-title">High Scores</h2>
34
  <button class="leaderboard-close" aria-label="Close leaderboard">×</button>
35
  </div>
36
 
 
42
  ${playerStats.highestLevel > 1 ? `
43
  <div class="leaderboard-player-stats">
44
  <div class="player-best">
45
+ Your Best: <span class="highlight">Level ${playerStats.highestLevel}</span>
46
  </div>
47
  <div class="player-stats-details">
48
  <div>Passages: ${playerStats.totalPassagesPassed}/${playerStats.totalPassagesAttempted} (${playerStats.successRate}%)</div>
49
+ <div>Longest Streak: ${playerStats.longestStreak}</div>
50
  </div>
51
  </div>
52
  ` : ''}
 
63
 
64
  // Add event listeners
65
  this.modal.querySelector('.leaderboard-close').addEventListener('click', () => this.hide());
66
+
67
+ // Prevent clicks inside modal content from closing
68
+ this.modal.querySelector('.leaderboard-modal').addEventListener('click', (e) => {
69
+ e.stopPropagation();
70
+ });
71
+
72
+ // Close on backdrop click
73
  this.modal.addEventListener('click', (e) => {
74
  if (e.target === this.modal) {
75
  this.hide();
 
107
  <div class="leaderboard-entry ${rankClass} ${playerClass}">
108
  <span class="entry-rank">#${entry.rank}</span>
109
  <span class="entry-initials">${entry.initials}</span>
110
+ <span class="entry-score">Level ${entry.level}</span>
111
  </div>
112
  `;
113
  }).join('');
 
169
  this.initialsModal.innerHTML = `
170
  <div class="initials-modal">
171
  <div class="initials-header">
172
+ <h2 class="initials-title">New High Score</h2>
173
+ <button class="leaderboard-close" aria-label="Close without saving">×</button>
174
  <div class="initials-achievement">
175
+ You reached <span class="highlight">Level ${level}</span>
176
  <br>
177
  <span class="rank-text">${this.getRankText(rank)}</span>
178
  </div>
 
196
  <div class="initials-instructions">
197
  <p>Use arrow keys ↑↓ to change letters</p>
198
  <p>Press Tab or ←→ to move between slots</p>
199
+ <p>Press Enter to confirm, ESC to cancel</p>
200
  </div>
201
 
202
  <button class="initials-submit typewriter-button">
203
+ Submit
204
  </button>
205
  </div>
206
  </div>
 
222
  */
223
  getRankText(rank) {
224
  const ordinal = this.getOrdinal(rank);
225
+ if (rank === 1) return `${ordinal} place - Top Score`;
226
+ if (rank === 2) return `${ordinal} place`;
227
+ if (rank === 3) return `${ordinal} place`;
228
+ return `${ordinal} place on the leaderboard`;
229
  }
230
 
231
  /**
 
289
  e.preventDefault();
290
  this.submitInitials();
291
  break;
292
+ case 'Escape':
293
+ e.preventDefault();
294
+ this.hideInitialsEntry();
295
+ break;
296
  }
297
  };
298
  document.addEventListener('keydown', this.initialsKeyHandler);
 
392
  successDiv.innerHTML = `
393
  <div class="leaderboard-modal success-message">
394
  <div class="success-content">
395
+ <h2>Score Saved</h2>
396
  <p>Your initials have been added to the leaderboard</p>
397
  </div>
398
  </div>
 
421
  toast.className = 'milestone-toast';
422
  toast.innerHTML = `
423
  <div class="toast-content">
424
+ Milestone Reached: Level ${level}
425
  </div>
426
  `;
427
 
src/styles.css CHANGED
@@ -426,39 +426,85 @@
426
  font-weight: 600;
427
  }
428
 
429
- /* Responsive adjustments */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  @media (max-width: 640px) {
431
  .prose {
432
  font-size: 1rem;
433
  }
434
-
435
  .typewriter-text {
436
  font-size: 1.75rem;
437
  }
438
-
439
- .typewriter-subtitle {
440
- font-size: 0.85rem;
441
- }
442
-
443
  .typewriter-button {
444
  min-width: 100px;
445
  min-height: 38px;
446
  font-size: 0.9rem;
447
  }
 
 
 
 
 
 
448
  }
449
 
450
  @media (max-width: 480px) {
451
  .prose {
452
  font-size: 0.95rem;
453
  }
454
-
455
  .typewriter-text {
456
  font-size: 1.5rem;
457
  }
458
-
459
  .cloze-input {
460
  min-width: 2.5ch;
461
  }
 
 
 
 
 
 
 
 
 
 
 
462
  }
463
 
464
  /* Compact question buttons for chat interface */
@@ -504,6 +550,7 @@
504
  }
505
 
506
  .sticky-controls .controls-inner {
 
507
  max-width: 1024px;
508
  margin: 0 auto;
509
  display: flex;
@@ -575,39 +622,6 @@
575
  }
576
  }
577
 
578
- /* Leaderboard Button Styling */
579
- .typewriter-button-small {
580
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
581
- padding: 8px 16px;
582
- background-color: var(--aged-paper-dark);
583
- color: var(--typewriter-ink);
584
- border: 2px solid black;
585
- border-radius: 6px;
586
- font-weight: 600;
587
- font-size: 14px;
588
- cursor: pointer;
589
- transition: all 0.15s ease;
590
- white-space: nowrap;
591
- box-shadow:
592
- 0 3px 0 rgba(0, 0, 0, 0.3),
593
- 0 4px 8px rgba(0, 0, 0, 0.1);
594
- }
595
-
596
- .typewriter-button-small:hover {
597
- background-color: rgba(0, 0, 0, 0.05);
598
- transform: translateY(-1px);
599
- box-shadow:
600
- 0 4px 0 rgba(0, 0, 0, 0.3),
601
- 0 6px 12px rgba(0, 0, 0, 0.15);
602
- }
603
-
604
- .typewriter-button-small:active {
605
- transform: translateY(2px);
606
- box-shadow:
607
- 0 1px 0 rgba(0, 0, 0, 0.3),
608
- 0 2px 4px rgba(0, 0, 0, 0.1);
609
- }
610
-
611
  /* Leaderboard Overlay */
612
  .leaderboard-overlay {
613
  position: fixed;
@@ -835,36 +849,38 @@
835
  }
836
 
837
  .initials-header {
838
- background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
839
  padding: 24px;
840
- border-bottom: 3px solid black;
841
  text-align: center;
842
  }
843
 
844
  .initials-title {
845
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
846
  font-size: 24px;
847
  font-weight: 700;
848
- color: white;
849
  margin: 0 0 12px 0;
850
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
851
  }
852
 
853
  .initials-achievement {
854
  font-size: 16px;
855
- color: rgba(255, 255, 255, 0.95);
856
  line-height: 1.6;
857
  }
858
 
859
  .initials-achievement .highlight {
860
- color: #fde68a;
861
  font-weight: 700;
 
862
  }
863
 
864
  .rank-text {
865
  font-size: 14px;
866
- color: #fde68a;
867
  font-weight: 600;
 
868
  }
869
 
870
  .initials-content {
@@ -895,9 +911,9 @@
895
  }
896
 
897
  .initial-slot.active .slot-letter {
898
- border-color: #8b5cf6;
899
- background: rgba(139, 92, 246, 0.1);
900
- box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.3);
901
  }
902
 
903
  .slot-letter {
@@ -909,12 +925,12 @@
909
  font-size: 48px;
910
  font-weight: 700;
911
  font-family: 'Courier New', monospace;
912
- color: var(--typewriter-ink);
913
- background: var(--aged-paper-dark);
914
- border: 3px solid black;
915
  border-radius: 8px;
916
  transition: all 0.2s ease;
917
- box-shadow: 0 4px 0 rgba(0, 0, 0, 0.3);
918
  }
919
 
920
  .slot-arrows {
@@ -973,14 +989,17 @@
973
  }
974
 
975
  .success-content h2 {
976
- font-size: 32px;
977
- color: #10b981;
978
  margin-bottom: 12px;
 
 
979
  }
980
 
981
  .success-content p {
982
  font-size: 16px;
983
  color: #666;
 
984
  }
985
 
986
  /* Milestone Toast */
@@ -989,16 +1008,18 @@
989
  top: 20px;
990
  left: 50%;
991
  transform: translateX(-50%) translateY(-100px);
992
- background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
993
- color: white;
994
  padding: 16px 24px;
995
- border-radius: 12px;
996
- border: 3px solid black;
997
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
998
  z-index: 3000;
999
  transition: transform 0.3s ease;
1000
  font-weight: 600;
1001
  font-size: 18px;
 
 
1002
  }
1003
 
1004
  .milestone-toast.visible {
@@ -1047,11 +1068,6 @@
1047
  .initials-slots {
1048
  gap: 12px;
1049
  }
1050
-
1051
- .typewriter-button-small {
1052
- font-size: 13px;
1053
- padding: 8px 18px;
1054
- }
1055
  }
1056
 
1057
  /* Print styles */
 
426
  font-weight: 600;
427
  }
428
 
429
+ /* Leaderboard button in footer */
430
+ .leaderboard-footer-btn {
431
+ background: none;
432
+ border: none;
433
+ padding: 8px 16px;
434
+ cursor: pointer;
435
+ font-size: 1.5rem;
436
+ line-height: 1;
437
+ transition: transform 0.2s ease, filter 0.2s ease;
438
+ display: flex;
439
+ align-items: center;
440
+ justify-content: center;
441
+ position: absolute;
442
+ right: 16px;
443
+ top: 50%;
444
+ transform: translateY(-50%);
445
+ }
446
+
447
+ .leaderboard-footer-btn:hover {
448
+ transform: translateY(-50%) scale(1.2);
449
+ }
450
+
451
+ .leaderboard-footer-btn:active {
452
+ transform: translateY(-50%) scale(1.05);
453
+ }
454
+
455
+
456
+ /* Mobile responsive */
457
+ @media (max-width: 768px) {
458
+ .typewriter-subtitle {
459
+ display: none;
460
+ }
461
+ }
462
+
463
  @media (max-width: 640px) {
464
  .prose {
465
  font-size: 1rem;
466
  }
467
+
468
  .typewriter-text {
469
  font-size: 1.75rem;
470
  }
471
+
 
 
 
 
472
  .typewriter-button {
473
  min-width: 100px;
474
  min-height: 38px;
475
  font-size: 0.9rem;
476
  }
477
+
478
+ .leaderboard-footer-btn {
479
+ font-size: 1.25rem;
480
+ padding: 6px 12px;
481
+ right: 12px;
482
+ }
483
  }
484
 
485
  @media (max-width: 480px) {
486
  .prose {
487
  font-size: 0.95rem;
488
  }
489
+
490
  .typewriter-text {
491
  font-size: 1.5rem;
492
  }
493
+
494
  .cloze-input {
495
  min-width: 2.5ch;
496
  }
497
+
498
+ header img {
499
+ width: 2rem;
500
+ height: 2rem;
501
+ }
502
+
503
+ .leaderboard-footer-btn {
504
+ font-size: 1.1rem;
505
+ padding: 4px 10px;
506
+ right: 8px;
507
+ }
508
  }
509
 
510
  /* Compact question buttons for chat interface */
 
550
  }
551
 
552
  .sticky-controls .controls-inner {
553
+ position: relative;
554
  max-width: 1024px;
555
  margin: 0 auto;
556
  display: flex;
 
622
  }
623
  }
624
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  /* Leaderboard Overlay */
626
  .leaderboard-overlay {
627
  position: fixed;
 
849
  }
850
 
851
  .initials-header {
852
+ background: linear-gradient(180deg, var(--aged-paper-dark) 0%, #d4c9b3 100%);
853
  padding: 24px;
854
+ border-bottom: 3px solid rgba(0, 0, 0, 0.3);
855
  text-align: center;
856
  }
857
 
858
  .initials-title {
859
+ font-family: 'Special Elite', 'Courier New', monospace;
860
  font-size: 24px;
861
  font-weight: 700;
862
+ color: var(--typewriter-ink);
863
  margin: 0 0 12px 0;
864
+ letter-spacing: 1px;
865
  }
866
 
867
  .initials-achievement {
868
  font-size: 16px;
869
+ color: var(--typewriter-ink);
870
  line-height: 1.6;
871
  }
872
 
873
  .initials-achievement .highlight {
874
+ color: #8b4513;
875
  font-weight: 700;
876
+ text-decoration: underline;
877
  }
878
 
879
  .rank-text {
880
  font-size: 14px;
881
+ color: #666;
882
  font-weight: 600;
883
+ font-style: italic;
884
  }
885
 
886
  .initials-content {
 
911
  }
912
 
913
  .initial-slot.active .slot-letter {
914
+ border-color: #2563eb;
915
+ background: #eff6ff;
916
+ box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.3), 0 4px 0 rgba(0, 0, 0, 0.5);
917
  }
918
 
919
  .slot-letter {
 
925
  font-size: 48px;
926
  font-weight: 700;
927
  font-family: 'Courier New', monospace;
928
+ color: #000000;
929
+ background: #ffffff;
930
+ border: 3px solid #000000;
931
  border-radius: 8px;
932
  transition: all 0.2s ease;
933
+ box-shadow: 0 4px 0 rgba(0, 0, 0, 0.5);
934
  }
935
 
936
  .slot-arrows {
 
989
  }
990
 
991
  .success-content h2 {
992
+ font-size: 28px;
993
+ color: #2d5016;
994
  margin-bottom: 12px;
995
+ font-family: 'Special Elite', 'Courier New', monospace;
996
+ letter-spacing: 1px;
997
  }
998
 
999
  .success-content p {
1000
  font-size: 16px;
1001
  color: #666;
1002
+ font-family: 'Courier New', monospace;
1003
  }
1004
 
1005
  /* Milestone Toast */
 
1008
  top: 20px;
1009
  left: 50%;
1010
  transform: translateX(-50%) translateY(-100px);
1011
+ background: var(--aged-paper-dark);
1012
+ color: var(--typewriter-ink);
1013
  padding: 16px 24px;
1014
+ border-radius: 8px;
1015
+ border: 3px solid rgba(0, 0, 0, 0.4);
1016
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 4px 0 rgba(0, 0, 0, 0.2);
1017
  z-index: 3000;
1018
  transition: transform 0.3s ease;
1019
  font-weight: 600;
1020
  font-size: 18px;
1021
+ font-family: 'Special Elite', 'Courier New', monospace;
1022
+ letter-spacing: 0.5px;
1023
  }
1024
 
1025
  .milestone-toast.visible {
 
1068
  .initials-slots {
1069
  gap: 12px;
1070
  }
 
 
 
 
 
1071
  }
1072
 
1073
  /* Print styles */