1// Heardle Settings - Settings Popup 2// This file handles the settings popup with theme toggle and reset stats 3 4(function() { 5 // Get current theme from localStorage (default is dark) 6 function getCurrentTheme() { 7 return localStorage.getItem('heardleTheme') || 'dark'; 8 } 9 10 // Apply theme to the page 11 function applyTheme(theme) { 12 const body = document.body; 13 14 if (theme === 'light') { 15 body.classList.remove('dark-theme'); 16 body.classList.add('light-theme'); 17 } else { 18 body.classList.remove('light-theme'); 19 body.classList.add('dark-theme'); 20 } 21 22 localStorage.setItem('heardleTheme', theme); 23 24 // Force a repaint to ensure theme changes are applied 25 body.style.display = 'none'; 26 body.offsetHeight; // Trigger reflow 27 body.style.display = ''; 28 } 29 30 // Create the settings modal HTML 31 function createSettingsHTML() { 32 const currentTheme = getCurrentTheme(); 33 const isLight = currentTheme === 'light'; 34 35 return ` 36 <div id="settings-modal" class="settings-modal-overlay"> 37 <div class="settings-modal-content"> 38 <div class="settings-header"> 39 <h2 class="settings-title">SETTINGS</h2> 40 <button id="settings-close" class="settings-close-btn"> 41 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 42 <line x1="18" y1="6" x2="6" y2="18"></line> 43 <line x1="6" y1="6" x2="18" y2="18"></line> 44 </svg> 45 </button> 46 </div> 47 48 <!-- Theme Toggle Section --> 49 <div class="settings-section"> 50 <div class="settings-item"> 51 <div class="settings-label-group"> 52 <div class="settings-label">ð Theme</div> 53 <div class="settings-description">Switch between dark mode and light</div> 54 </div> 55 <label class="theme-toggle"> 56 <input type="checkbox" id="theme-checkbox" ${isLight ? 'checked' : ''}> 57 <span class="theme-toggle-slider"></span> 58 <span class="theme-toggle-labels"> 59<span class="theme-label-dark">ð</span> 60<span class="theme-label-light">âï¸</span> 61 62 </span> 63 </label> 64 </div> 65 </div> 66 67<!-- Sticky Icons Section --> 68 <div class="settings-section"> 69 <div class="settings-item"> 70 <div class="settings-label-group"> 71 <div class="settings-label">ð Sticky Stats</div> 72 <div class="settings-description">Show stats icon on the right side</div> 73 </div> 74 <label class="theme-toggle"> 75 <input type="checkbox" id="sticky-stats-checkbox" ${localStorage.getItem('stickyStats') === 'true' ? 'checked' : ''}> 76 <span class="theme-toggle-slider"></span> 77 </label> 78 </div> 79 </div> 80 81 <div class="settings-section"> 82 <div class="settings-item"> 83 <div class="settings-label-group"> 84 <div class="settings-label">ð Sticky Calendar</div> 85 <div class="settings-description">Show calendar icon on the right side</div> 86 </div> 87 <label class="theme-toggle"> 88 <input type="checkbox" id="sticky-calendar-checkbox" ${localStorage.getItem('stickyCalendar') === 'true' ? 'checked' : ''}>
89 <span class="theme-toggle-slider"></span> 90 </label> 91 </div> 92 </div> 93 94 <!-- Download Stats Section --> 95 <div class="settings-section"> 96 <div class="settings-item settings-item-column"> 97 <div class="settings-label-group"> 98 <div class="settings-label">â¬ï¸ Download Stats</div> 99 <div class="settings-description">Save your stats as a file to transfer to another device</div> 100 </div> 101 <button id="download-stats-btn" class="settings-download-btn"> 102 Download My Stats 103 </button> 104 </div> 105 </div> 106 107 <!-- Upload Stats Section --> 108 <div class="settings-section"> 109 <div class="settings-item settings-item-column"> 110 <div class="settings-label-group"> 111 <div class="settings-label">â¬ï¸ Upload Stats</div> 112 <div class="settings-description">Restore stats from a previously downloaded file</div> 113 </div> 114 <label id="upload-stats-label" class="settings-upload-btn"> 115 Upload Stats File 116 <input type="file" id="upload-stats-input" accept=".json" style="display:none;"> 117 </label> 118 </div> 119 </div> 120 121 <!-- Reset Stats Section --> 122 <div class="settings-section"> 123 <div class="settings-item settings-item-column"> 124 <div class="settings-label-group"> 125 <div class="settings-label">ð Reset Stats</div> 126 <div class="settings-description">Clear all your game statistics and start fresh</div> 127 </div> 128 <button id="reset-stats-btn" class="settings-reset-btn"> 129 Reset All Stats 130 </button> 131 </div> 132 </div> 133 134 <div class="settings-footer"> 135 <div class="settings-version">©HeardleDecades.com</div> 136 </div> 137 </div> 138 </div> 139 140 <!-- Confirmation Modal (hidden by default) --> 141 <div id="confirm-modal" class="confirm-modal-overlay" style="display: none;"> 142 <div class="confirm-modal-content"> 143 <h3 class="confirm-title">â ï¸ Confirm Reset</h3> 144 <p class="confirm-message">Are you sure you want to reset all your stats? This will permanently delete:</p> 145 <ul class="confirm-list"> 146 <li>All your game history</li> 147 <li>Your win streak</li> 148 <li>Your guess distribution</li> 149 </ul> 150 <p class="confirm-warning">This cannot be undone!</p> 151 <div class="confirm-buttons"> 152 <button id="confirm-cancel" class="confirm-btn confirm-btn-cancel">Cancel</button> 153 <button id="confirm-reset" class="confirm-btn confirm-btn-danger">Yes, Reset Everything</button> 154 </div> 155 </div> 156 </div> 157 `; 158 } 159 160 // Create the settings CSS 161 function createSettingsStyles() { 162 const styles = document.createElement('style'); 163 styles.id = 'settings-styles'; 164 styles.textContent = ` 165 .settings-modal-overlay { 166 position: fixed; 167 top: 0; 168 left: 0; 169 width: 100%; 170 height: 100%; 171 background: rgba(0, 0, 0, 0.8); 172 display: flex; 173 justify-content: center; 174 align-items: flex-start; 175 padding-top: 80px; 176 z-index: 9999; 177 overflow-y: auto; 178 } 179 180 .settings-modal-content { 181 background: #1a1a2e; 182 border: 1px solid #4a4a6a; 183 border-radius: 8px; 184 padding: 20px; 185 max-width: 450px; 186 width: 90%; 187 color: #ffffff; 188 position: relative; 189 } 190 191 .settings-header { 192 display: flex; 193 justify-content: space-between; 194 align-items: center; 195 margin-bottom: 20px; 196 border-bottom: 1px solid #4a4a6a; 197 padding-bottom: 12px; 198 } 199 200 .settings-title { 201 font-size: 14px; 202 font-weight: 600; 203 letter-spacing: 2px; 204 color: #9b59b6; 205 margin: 0; 206 } 207 208 .settings-close-btn { 209 background: none; 210 border: none; 211 color: #9b59b6; 212 cursor: pointer; 213 padding: 4px; 214 display: flex; 215 align-items: center; 216 justify-content: center; 217 transition: transform 0.2s; 218 } 219 220 .settings-close-btn:hover { 221 transform: rotate(90deg); 222 } 223 224 .settings-section { 225 margin-bottom: 20px; 226 padding: 16px; 227 background: rgba(74, 74, 106, 0.2); 228 border-radius: 8px; 229 border: 1px solid #4a4a6a; 230 } 231 232 .settings-item { 233 display: flex; 234 justify-content: space-between; 235 align-items: center; 236 gap: 16px; 237 } 238 239 .settings-item-column { 240 flex-direction: column; 241 align-items: stretch; 242 } 243 244 .settings-label-group { 245 flex: 1; 246 } 247 248 .settings-label { 249 font-size: 16px; 250 font-weight: 600; 251 margin-bottom: 4px;
252 color: #ffffff; 253 } 254 255 .settings-description { 256 font-size: 12px; 257 color: #b0b0c0; 258 line-height: 1.4; 259 } 260 261 /* Theme Toggle Switch */ 262 .theme-toggle { 263 position: relative; 264 display: inline-block; 265 width: 60px; 266 height: 30px; 267 flex-shrink: 0; 268 } 269 270 .theme-toggle input { 271 opacity: 0; 272 width: 0; 273 height: 0; 274 } 275 276 .theme-toggle-slider { 277 position: absolute; 278 cursor: pointer; 279 top: 0; 280 left: 0; 281 right: 0; 282 bottom: 0; 283 background: linear-gradient(135deg, #2c3e50, #34495e); 284 transition: 0.4s; 285 border-radius: 30px; 286 } 287 288 .theme-toggle-slider:before { 289 position: absolute; 290 content: ""; 291 height: 22px; 292 width: 22px; 293 left: 4px; 294 bottom: 4px; 295 background-color: white; 296 transition: 0.4s; 297 border-radius: 50%; 298 } 299 300 .theme-toggle input:checked + .theme-toggle-slider { 301 background: linear-gradient(135deg, #ffd700, #ff8c00); 302 } 303 304 .theme-toggle input:checked + .theme-toggle-slider:before { 305 transform: translateX(30px); 306 } 307 308 .theme-toggle-labels { 309 position: absolute; 310 top: 50%; 311 width: 100%; 312 transform: translateY(-50%); 313 display: flex; 314 justify-content: space-between; 315 padding: 0 6px; 316 pointer-events: none; 317 font-size: 14px; 318 } 319 320 .theme-label-light { 321 opacity: 1; 322 transition: opacity 0.3s; 323 } 324 325 .theme-label-dark { 326 opacity: 0.3; 327 transition: opacity 0.3s; 328 } 329 330 .theme-toggle input:checked ~ .theme-toggle-labels .theme-label-light { 331 opacity: 0.3; 332 } 333 334 .theme-toggle input:checked ~ .theme-toggle-labels .theme-label-dark { 335 opacity: 1; 336 } 337 338 /* Download Button */ 339 .settings-download-btn { 340 background: linear-gradient(135deg, #00bcd4, #0097a7); 341 border: none; 342 color: white; 343 padding: 12px 24px; 344 border-radius: 8px; 345 cursor: pointer; 346 font-size: 14px; 347 font-weight: 600; 348 transition: transform 0.2s, box-shadow 0.2s; 349 margin-top: 12px; 350 width: 100%; 351 } 352 353 .settings-download-btn:hover { 354 transform: scale(1.02); 355 box-shadow: 0 4px 15px rgba(0, 188, 212, 0.4); 356 } 357 358 /* Upload Button */ 359 .settings-upload-btn { 360 background: linear-gradient(135deg, #9b59b6, #7d3c98); 361 border: none; 362 color: white; 363 padding: 12px 24px; 364 border-radius: 8px; 365 cursor: pointer; 366 font-size: 14px; 367 font-weight: 600; 368 transition: transform 0.2s, box-shadow 0.2s; 369 margin-top: 12px; 370 width: 100%; 371 display: block; 372 text-align: center; 373 box-sizing: border-box; 374 } 375 376 .settings-upload-btn:hover { 377 transform: scale(1.02); 378 box-shadow: 0 4px 15px rgba(155, 89, 182, 0.4); 379 } 380 381 /* Reset Button */ 382 .settings-reset-btn { 383 background: linear-gradient(135deg, #e74c3c, #c0392b); 384 border: none; 385 color: white; 386 padding: 12px 24px; 387 border-radius: 8px; 388 cursor: pointer; 389 font-size: 14px; 390 font-weight: 600; 391 transition: transform 0.2s, box-shadow 0.2s; 392 margin-top: 12px; 393 width: 100%; 394 } 395 396 .settings-reset-btn:hover { 397 transform: scale(1.02); 398 box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4); 399 } 400 401 .settings-footer { 402 text-align: center; 403 padding-top: 16px; 404 margin-top: 16px; 405 border-top: 1px solid #4a4a6a; 406 } 407 408 .settings-version { 409 font-size: 11px; 410 color: #7a7a8a; 411 } 412 413 /* Confirmation Modal */ 414 .confirm-modal-overlay { 415 position: fixed; 416 top: 0; 417 left: 0; 418 width: 100%; 419 height: 100%; 420 background: rgba(0, 0, 0, 0.9); 421 display: flex; 422 justify-content: center; 423 align-items: center; 424 z-index: 10000; 425 } 426 427 .confirm-modal-content { 428 background: #1a1a2e; 429 border: 2px solid #e74c3c; 430 border-radius: 8px; 431 padding: 24px; 432 max-width: 400px; 433 width: 90%; 434 color: #ffffff; 435 } 436 437 .confirm-title { 438 font-size: 18px; 439 font-weight: 700; 440 margin: 0 0 16px 0; 441 color: #e74c3c; 442 } 443 444 .confirm-message { 445 font-size: 14px; 446 margin-bottom: 12px; 447 color: #ffffff; 448 } 449 450 .confirm-list { 451 list-style: none; 452 padding: 0; 453 margin: 12px 0; 454 } 455 456 .confirm-list li { 457 padding: 6px 0; 458 padding-left: 24px; 459 position: relative; 460 font-size: 13px; 461 color: #b0b0c0; 462 } 463 464 .confirm-list li:before { 465 content: "â¢"; 466 position: absolute; 467 left: 8px; 468 color: #e74c3c; 469 font-weight: bold; 470 } 471 472 .confirm-warning { 473 font-size: 13px; 474 font-weight: 600; 475 color: #e74c3c; 476 margin: 16px 0; 477 } 478 479 .confirm-buttons { 480 display: flex; 481 gap: 12px; 482 margin-top: 20px; 483 } 484 485 .confirm-btn { 486 flex: 1; 487 padding: 12px; 488 border: none; 489 border-radius: 8px; 490 font-size: 14px; 491 font-weight: 600; 492 cursor: pointer; 493 transition: transform 0.2s, box-shadow 0.2s; 494 } 495 496 .confirm-btn-cancel { 497 background: #4a4a6a; 498 color: white; 499 } 500 501 .confirm-btn-cancel:hover { 502 background: #5a5a7a; 503 transform: scale(1.02); 504 } 505 506 .confirm-btn-danger { 507 background: linear-gradient(135deg, #e74c3c, #c0392b); 508 color: white; 509 } 510 511 .confirm-btn-danger:hover { 512 transform: scale(1.02); 513 box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4); 514 } 515 516 /* Success message */ 517 .settings-success-message { 518 position: fixed; 519 bottom: 20px; 520 left: 50%; 521 transform: translateX(-50%); 522 background: #2d5a2d; 523 color: white; 524 padding: 12px 24px; 525 border-radius: 8px; 526 font-size: 14px; 527 font-weight: 600; 528 z-index: 10001; 529 animation: fadeInOut 2s ease-in-out; 530 } 531 532 @keyframes fadeInOut { 533 0% { opacity: 0; transform: translateX(-50%) translateY(20px); } 534 15% { opacity: 1; transform: translateX(-50%) translateY(0); } 535 85% { opacity: 1; transform: translateX(-50%) translateY(0); } 536 100% { opacity: 0; transform: translateX(-50%) translateY(-20px); } 537 } 538 539 @media (max-width: 400px) { 540 .settings-modal-content { 541 padding: 16px; 542 } 543 544 .settings-item { 545 flex-direction: column; 546 align-items: flex-start; 547 } 548 } 549 550/* Sticky Icons Container */ 551 .sticky-icons-container { 552 position: fixed; 553 right: 20px; 554 top: 15px; 555 display: flex; 556 flex-direction: column; 557 gap: 12px; 558 z-index: 1000; 559 } 560 561 .sticky-icon { 562 width: 50px; 563 height: 50px; 564 border-radius: 50%; 565 background: linear-gradient(135deg, #9b59b6, #8e44ad); 566 border: 2px solid #4a4a6a; 567 color: white; 568 font-size: 24px; 569 cursor: pointer; 570 display: flex; 571 align-items: center; 572 justify-content: center; 573 transition: all 0.3s ease; 574 box-shadow: 0 4px 15px rgba(155, 89, 182, 0.3); 575 } 576 577 .sticky-icon:hover { 578 transform: scale(1.1); 579 box-shadow: 0 6px 20px rgba(155, 89, 182, 0.5); 580 background: linear-gradient(135deg, #a569c0, #9b59b6); 581 } 582 583 .sticky-icon:active { 584 transform: scale(0.95); 585 } 586 587 @media (max-width: 768px) { 588 .sticky-icons-container { 589 right: 10px; 590 top: 15px; 591 } 592 593 .sticky-icon { 594 width: 45px; 595 height: 45px; 596 font-size: 22px; 597 } 598 } 599 600/* Light Theme Styles */ 601 body.light-theme { 602 background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); 603 color: #1a1a1a; 604 } 605 606 body.light-theme .settings-modal-content { 607 background: #ffffff; 608 border: 1px solid #d0d0d0; 609 color: #1a1a1a; 610 } 611 612 body.light-theme .settings-title { 613 color: #7b3ff2; 614 } 615 616 body.light-theme .settings-close-btn { 617 color: #7b3ff2; 618 } 619 620 body.light-theme .settings-section { 621 border-bottom: 1px solid #e0e0e0; 622 } 623 624 body.light-theme .settings-label { 625 color: #1a1a1a; 626 } 627 628 body.light-theme .settings-description { 629 color: #666666; 630 } 631 632 body.light-theme .settings-reset-btn { 633 background: #7b3ff2; 634 color: white; 635 } 636 637 body.light-theme .settings-reset-btn:hover { 638 background: #6a2dd4; 639 } 640 641 body.light-theme .settings-version { 642 color: #666666; 643 } 644 645 body.light-theme .confirm-modal-content { 646 background: #ffffff; 647 border: 1px solid #d0d0d0; 648 color: #1a1a1a; 649 } 650 651 body.light-theme .confirm-title { 652 color: #1a1a1a; 653 } 654 655 body.light-theme .confirm-message, 656 body.light-theme .confirm-list, 657 body.light-theme .confirm-warning { 658 color: #333333; 659 } 660 661`; 662 return styles; 663 } 664 665 // Show success message 666 function showSuccessMessage(text) { 667 const existing = document.querySelector('.settings-success-message'); 668 if (existing) existing.remove(); 669 670 const message = document.createElement('div'); 671 message.className = 'settings-success-message'; 672 message.textContent = text; 673 document.body.appendChild(message); 674 675 setTimeout(() => { 676 message.remove(); 677 }, 2000); 678 } 679 680 // Handle theme toggle 681 function toggleTheme() { 682 const checkbox = document.getElementById('theme-checkbox'); 683 const newTheme = checkbox.checked ? 'light' : 'dark'; 684 applyTheme(newTheme); 685 // Shorter message for mobile screens 686 const message = newTheme === 'dark' ? 'ð Dark mode' : 'âï¸ Light mode'; 687 showSuccessMessage(message); 688 } 689
690// Handle sticky stats toggle 691 function toggleStickyStats() { 692 const checkbox = document.getElementById('sticky-stats-checkbox'); 693 const isEnabled = checkbox.checked; 694 localStorage.setItem('stickyStats', isEnabled.toString()); 695 696 if (isEnabled) { 697 showStickyIcons(); 698 showSuccessMessage('ð Stats enabled'); 699 } else { 700 hideStickyIcons(); 701 showSuccessMessage('ð Stats disabled'); 702 } 703 } 704 705 // Handle sticky calendar toggle 706 function toggleStickyCalendar() { 707 const checkbox = document.getElementById('sticky-calendar-checkbox'); 708 const isEnabled = checkbox.checked; 709 localStorage.setItem('stickyCalendar', isEnabled.toString()); 710 711 if (isEnabled) { 712 showStickyIcons(); 713 showSuccessMessage('ð Calendar enabled'); 714 } else { 715 hideStickyIcons(); 716 showSuccessMessage('ð Calendar disabled'); 717 } 718 } 719 720 // Show sticky icons based on settings 721 function showStickyIcons() { 722 // Remove existing icons first 723 const existing = document.getElementById('sticky-icons-container'); 724 if (existing) existing.remove(); 725 726 const showStats = localStorage.getItem('stickyStats') === 'true'; 727 const showCalendar = localStorage.getItem('stickyCalendar') === 'true'; 728 729 // Only create container if at least one is enabled 730 if (!showStats && !showCalendar) return; 731 732 const container = document.createElement('div'); 733 container.id = 'sticky-icons-container'; 734 container.className = 'sticky-icons-container'; 735 736 if (showStats) { 737 const statsIcon = document.createElement('button'); 738 statsIcon.className = 'sticky-icon'; 739 statsIcon.innerHTML = 'ð'; 740 statsIcon.title = 'View Stats'; 741 statsIcon.onclick = () => { 742 if (window.HeardleStats && window.HeardleStats.open) { 743 window.HeardleStats.open(); 744 } 745 }; 746 container.appendChild(statsIcon); 747 } 748 749 if (showCalendar) { 750 const calendarIcon = document.createElement('button'); 751 calendarIcon.className = 'sticky-icon'; 752 calendarIcon.innerHTML = 'ð '; 753 calendarIcon.title = 'View Calendar'; 754 calendarIcon.onclick = () => { 755 if (window.HeardleCalendar && window.HeardleCalendar.open) { 756 window.HeardleCalendar.open(); 757 } 758 }; 759 container.appendChild(calendarIcon); 760 } 761 762 document.body.appendChild(container); 763 } 764 765 // Hide sticky icons 766 function hideStickyIcons() { 767 const container = document.getElementById('sticky-icons-container'); 768 const showStats = localStorage.getItem('stickyStats') === 'true'; 769 const showCalendar = localStorage.getItem('stickyCalendar') === 'true'; 770 771 // If both are disabled, remove container 772 if (!showStats && !showCalendar) { 773 if (container) container.remove(); 774 } else { 775 // Otherwise, refresh to show only enabled ones 776 showStickyIcons(); 777 } 778 } 779 780 // Show confirmation modal 781 function showConfirmModal() { 782 const confirmModal = document.getElementById('confirm-modal'); 783 if (confirmModal) { 784 confirmModal.style.display = 'flex'; 785 } 786 } 787 788 // Hide confirmation modal 789 function hideConfirmModal() { 790 const confirmModal = document.getElementById('confirm-modal'); 791 if (confirmModal) { 792 confirmModal.style.display = 'none'; 793 } 794 } 795 796 // Download stats as a JSON file 797 function downloadStats() { 798 const stats = localStorage.getItem('userStats'); 799 if (!stats || JSON.parse(stats).length === 0) { 800 showSuccessMessage('â ï¸ No stats to download!'); 801 return; 802 } 803 const blob = new Blob([stats], { type: 'application/json' }); 804 const url = URL.createObjectURL(blob); 805 const a = document.createElement('a'); 806 a.href = url; 807 a.download = 'heardle-stats.json'; 808 a.click(); 809 URL.revokeObjectURL(url); 810 showSuccessMessage('â Stats downloaded!'); 811 } 812 813 // Upload stats from a JSON file 814 function uploadStats(file) { 815 if (!file) return; 816 const reader = new FileReader(); 817 reader.onload = function(e) { 818 try { 819 const data = JSON.parse(e.target.result); 820 if (!Array.isArray(data)) throw new Error('Invalid format'); 821 localStorage.setItem('userStats', JSON.stringify(data)); 822 showSuccessMessage('â Stats restored!'); 823 setTimeout(() => { 824 closeSettings();
825 location.reload(); 826 }, 1500); 827 } catch (err) { 828 showSuccessMessage('â Invalid stats file!'); 829 } 830 }; 831 reader.readAsText(file); 832 } 833 834 // Reset all stats 835 function resetAllStats() { 836 // Clear the userStats from localStorage 837 localStorage.removeItem('userStats'); 838 839 // Reinitialize with empty array 840 localStorage.setItem('userStats', JSON.stringify([])); 841 842 // Hide confirmation modal 843 hideConfirmModal(); 844 845 // Show success message (shorter for mobile) 846 showSuccessMessage('â Stats reset!'); 847 848 // Close settings modal 849 setTimeout(() => { 850 closeSettings(); 851 // Reload the page to refresh everything 852 location.reload(); 853 }, 1500); 854 } 855 856 // Open the settings modal 857 function openSettings() { 858 // Add styles if not already added 859 if (!document.getElementById('settings-styles')) { 860 document.head.appendChild(createSettingsStyles()); 861 } 862 863 // Remove any existing modal 864 const existingModal = document.getElementById('settings-modal'); 865 if (existingModal) { 866 existingModal.remove(); 867 } 868 869 const existingConfirm = document.getElementById('confirm-modal'); 870 if (existingConfirm) { 871 existingConfirm.remove(); 872 } 873 874 // Create and add the modal 875 const modalWrapper = document.createElement('div'); 876 modalWrapper.innerHTML = createSettingsHTML(); 877 878 // Add both modals to the body 879 while (modalWrapper.firstChild) { 880 document.body.appendChild(modalWrapper.firstChild); 881 } 882 883 // Add event listeners for settings modal 884 document.getElementById('settings-close').addEventListener('click', closeSettings); 885 document.getElementById('theme-checkbox').addEventListener('change', toggleTheme); 886 document.getElementById('sticky-stats-checkbox').addEventListener('change', toggleStickyStats); 887 document.getElementById('sticky-calendar-checkbox').addEventListener('change', toggleStickyCalendar); 888 document.getElementById('download-stats-btn').addEventListener('click', downloadStats); 889 document.getElementById('upload-stats-input').addEventListener('change', (e) => uploadStats(e.target.files[0])); 890 document.getElementById('reset-stats-btn').addEventListener('click', showConfirmModal); 891 892 // Add event listeners for confirmation modal 893 document.getElementById('confirm-cancel').addEventListener('click', hideConfirmModal); 894 document.getElementById('confirm-reset').addEventListener('click', resetAllStats); 895 896 // Close when clicking outside 897 document.getElementById('settings-modal').addEventListener('click', (e) => { 898 if (e.target.id === 'settings-modal') { 899 closeSettings(); 900 } 901 }); 902 903 // Apply current theme on page load 904 applyTheme(getCurrentTheme()); 905 } 906 907 // Close the settings modal 908 function closeSettings() { 909 const modal = document.getElementById('settings-modal'); 910 if (modal) { 911 modal.remove(); 912 } 913 const confirm = document.getElementById('confirm-modal'); 914 if (confirm) { 915 confirm.remove(); 916 } 917 } 918 919 // Apply saved theme when page loads (runs immediately) 920 (function() { 921 const savedTheme = getCurrentTheme(); 922 applyTheme(savedTheme); 923 })(); 924 925 // Also apply on DOMContentLoaded as backup 926 if (document.readyState === 'loading') { 927 document.addEventListener('DOMContentLoaded', () => { 928 applyTheme(getCurrentTheme()); 929 }); 930 } else { 931 // DOM already loaded 932 applyTheme(getCurrentTheme()); 933 } 934 935 // Expose functions globally 936 window.HeardleSettings = { 937 open: openSettings, 938 close: closeSettings, 939 showStickyIcons: showStickyIcons, 940 hideStickyIcons: hideStickyIcons 941 }; 942 943 // Add styles immediately when page loads 944 if (!document.getElementById('settings-styles')) { 945 document.head.appendChild(createSettingsStyles()); 946 } 947 948 // Initialize sticky icons when page loads 949 function initializeStickyIcons() { 950 // Ensure styles are loaded first 951 if (!document.getElementById('settings-styles')) { 952 document.head.appendChild(createSettingsStyles()); 953 } 954 // Small delay to ensure CSS is parsed 955 setTimeout(() => { 956 showStickyIcons(); 957 }, 150); 958 } 959 960 if (document.readyState === 'loading') { 961 document.addEventListener('DOMContentLoaded', initializeStickyIcons); 962 } else { 963 initializeStickyIcons(); 964 } 965})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.