1<!DOCTYPE html> 2<html lang="de"> 3 4<head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>Quiz - Zitat des Tages</title> 8 <link rel="preconnect" href="https://fonts.googleapis.com"> 9 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 <link 11 href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Readex+Pro:wght@300;400;500;600;700&display=swap" 12 rel="stylesheet"> 13 <style> 14 * { 15 margin: 0; 16 padding: 0; 17 box-sizing: border-box; 18 } 19 20 body { 21 font-family: 'Readex Pro', sans-serif; 22 background-color: #00001A; 23 color: #F5F5F5; 24 min-height: 100vh; 25 overflow: hidden; 26 } 27 28 .quiz-container { 29 position: relative; 30 width: 100%; 31 height: 100vh; 32 background-size: cover; 33 background-position: center; 34 background-repeat: no-repeat; 35 display: flex; 36 flex-direction: column; 37 justify-content: center; 38 align-items: center; 39 padding: 20px; 40 transition: background-image 0.5s ease-in-out; 41 } 42 43 .quiz-container::before { 44 content: ''; 45 position: absolute; 46 top: 0; 47 left: 0; 48 right: 0; 49 bottom: 0; 50 background: rgba(0, 0, 26, 0.6); 51 backdrop-filter: blur(5px); 52 } 53 54 .content { 55 position: relative; 56 z-index: 1; 57 max-width: 800px; 58 width: 100%; 59 text-align: center; 60 } 61 62 .header { 63 margin-bottom: 40px; 64 } 65 66 .header h1 { 67 font-family: 'Inter', sans-serif; 68 font-size: 28px; 69 font-weight: 600; 70 color: white; 71 margin-bottom: 12px; 72 } 73 74 .header p { 75 color: #19DB8A; 76 font-size: 16px; 77 font-weight: 500; 78 } 79 80 .quote-card { 81 background: rgba(255, 255, 255, 0.05); 82 backdrop-filter: blur(20px); 83 border-radius: 20px; 84 padding: 24px 32px; 85 margin-bottom: 32px; 86 border: 1px solid rgba(255, 255, 255, 0.1); 87 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); 88 } 89 90 .quote-text { 91 font-size: 24px; 92 line-height: 1.6; 93 color: white; 94 font-style: italic; 95 margin-bottom: 20px; 96 min-height: 50px; 97 display: flex; 98 align-items: center; 99 justify-content: center; 100 text-align: center; 101 } 102 103 .question-text { 104 font-size: 18px; 105 color: #19DB8A; 106 font-weight: 500; 107 margin-bottom: 32px; 108 } 109 110 .answers-container { 111 display: grid; 112 grid-template-columns: 1fr 1fr; 113 gap: 12px; 114 margin-bottom: 0; 115 } 116 117 .answer-btn { 118 background: rgba(255, 255, 255, 0.1); 119 border: 2px solid rgba(255, 255, 255, 0.2); 120 border-radius: 12px; 121 padding: 16px; 122 color: white; 123 font-size: 16px; 124 font-weight: 500; 125 cursor: pointer; 126 transition: all 0.3s ease; 127 min-height: 70px; 128 display: flex; 129 align-items: center; 130 justify-content: center; 131 text-align: center; 132 } 133 134 .answer-btn:hover { 135 background: rgba(255, 255, 255, 0.15); 136 border-color: rgba(25, 219, 138, 0.5); 137 transform: translateY(-2px); 138 } 139 140 /* Entferne Animation von Button selbst */ 141 .answer-btn.correct, 142 .answer-btn.wrong { 143 /* nur Farbänderung, keine transform-animation */ 144 background: rgba(25, 219, 138, 0.2); 145 border-color: #19DB8A; 146 } 147 148 .answer-btn.wrong { 149 background: rgba(255, 59, 48, 0.2); 150 border-color: #FF3B30; 151 } 152 153 /* Neues internes Label für Animation */ 154 .answer-btn .label { 155 display: inline-block; 156 will-change: transform; 157 transform-origin: center center; 158 } 159 160 .answer-btn.correct .label { 161 animation: pulse 0.6s ease; 162 } 163 164 .answer-btn.wrong .label { 165 animation: shake 0.6s ease; 166 } 167 168 @keyframes pulse { 169 170 0%, 171 100% { 172 transform: scale(1); 173 } 174 175 50% { 176 transform: scale(1.12); 177 } 178 } 179 180 @keyframes shake { 181 182 0%, 183 100% { 184 transform: translateX(0); 185 } 186 187 20% { 188 transform: translateX(-14px); 189 } 190 191 40% { 192 transform: translateX(14px); 193 } 194 195 60% { 196 transform: translateX(-12px); 197 } 198 199 80% { 200 transform: translateX(12px); 201 } 202 } 203 204 .stats-bar { 205 display: flex; 206 justify-content: space-between; 207 gap: 16px; 208 margin-top: 24px; 209 } 210 211 .stat { 212 background: rgba(255, 255, 255, 0.1); 213 backdrop-filter: blur(10px); 214 border-radius: 12px; 215 padding: 16px 24px; 216 flex: 1; 217 border: 1px solid rgba(255, 255, 255, 0.1); 218 } 219 220 .stat-label { 221 font-size: 12px; 222 color: #A0A0A0; 223 margin-bottom: 4px; 224 } 225 226 .stat-value { 227 font-size: 24px; 228 font-weight: 700; 229 color: #19DB8A; 230 } 231 232 .loading { 233 text-align: center; 234 padding: 60px; 235 color: #A0A0A0; 236 } 237 238 .spinner { 239 border: 4px solid rgba(255, 255, 255, 0.1);
240 border-top: 4px solid #19DB8A; 241 border-radius: 50%; 242 width: 50px; 243 height: 50px; 244 animation: spin 1s linear infinite; 245 margin: 20px auto; 246 } 247 248 @keyframes spin { 249 0% { 250 transform: rotate(0deg); 251 } 252 253 100% { 254 transform: rotate(360deg); 255 } 256 } 257 258 .error { 259 background: rgba(255, 0, 0, 0.1); 260 color: #ff6b6b; 261 padding: 20px; 262 border-radius: 12px; 263 margin: 20px 0; 264 border: 1px solid rgba(255, 0, 0, 0.3); 265 } 266 267 .next-btn { 268 background: linear-gradient(135deg, #19DB8A 0%, #0FA968 100%); 269 border: none; 270 border-radius: 12px; 271 padding: 16px 48px; 272 color: #00001A; 273 font-size: 16px; 274 font-weight: 600; 275 cursor: pointer; 276 transition: all 0.3s ease; 277 margin-top: 12px; 278 } 279 280 .next-btn:hover { 281 transform: translateY(-2px); 282 box-shadow: 0 8px 24px rgba(25, 219, 138, 0.4); 283 } 284 285 .next-btn:disabled { 286 opacity: 0.5; 287 cursor: not-allowed; 288 } 289 290 .result-message { 291 font-size: 20px; 292 font-weight: 600; 293 margin-top: 12px; 294 min-height: 0; 295 } 296 297 .completed-message { 298 text-align: center; 299 padding: 40px; 300 } 301 302 .completed-message h2 { 303 font-size: 32px; 304 color: #19DB8A; 305 margin-bottom: 16px; 306 } 307 308 .completed-message p { 309 font-size: 18px; 310 color: #A0A0A0; 311 margin-bottom: 32px; 312 } 313 314 .results-btn { 315 background: linear-gradient(135deg, #19DB8A 0%, #0FA968 100%); 316 border: none; 317 border-radius: 12px; 318 padding: 16px 48px; 319 color: #00001A; 320 font-size: 16px; 321 font-weight: 600; 322 cursor: pointer; 323 text-decoration: none; 324 display: inline-block; 325 transition: all 0.3s ease; 326 } 327 328 .results-btn:hover { 329 transform: translateY(-2px); 330 box-shadow: 0 8px 24px rgba(25, 219, 138, 0.4); 331 } 332 333 /* App Button (Desktop Only) */ 334 .app-btn-top { 335 position: fixed; 336 top: 20px; 337 right: 20px; 338 z-index: 1000; 339 background: rgba(160, 160, 160, 0.15); 340 backdrop-filter: blur(20px); 341 -webkit-backdrop-filter: blur(20px); 342 border: 1px solid rgba(160, 160, 160, 0.3); 343 border-radius: 50px; 344 padding: 12px 24px; 345 color: #E0E0E0; 346 text-decoration: none; 347 font-weight: 600; 348 font-size: 0.95rem; 349 display: flex; 350 align-items: center; 351 gap: 8px; 352 transition: all 0.3s ease; 353 } 354 355 .app-btn-top:hover { 356 background: rgba(160, 160, 160, 0.25); 357 border-color: #19DB8A; 358 transform: translateY(-2px); 359 box-shadow: 0 4px 12px rgba(25, 219, 138, 0.3); 360 } 361 362 .app-btn-top svg { 363 width: 20px; 364 height: 20px; 365 } 366 367 /* Hide app button on mobile/tablet */ 368 @media (max-width: 1024px) { 369 .app-btn-top { 370 display: none; 371 } 372 } 373 374 /* Hamburger Menu Styles */ 375 .hamburger-menu { 376 position: fixed; 377 top: 20px; 378 left: 60px; 379 z-index: 1000; 380 } 381 382 .hamburger-btn { 383 background: rgba(160, 160, 160, 0.15); 384 backdrop-filter: blur(20px); 385 border: 1px solid rgba(160, 160, 160, 0.3); 386 border-radius: 12px; 387 width: 50px; 388 height: 50px; 389 display: flex; 390 flex-direction: column; 391 justify-content: center; 392 align-items: center; 393 gap: 5px; 394 cursor: pointer; 395 transition: all 0.3s ease; 396 padding: 0; 397 } 398 399 .hamburger-btn:hover { 400 background: rgba(160, 160, 160, 0.25); 401 transform: scale(1.05); 402 } 403 404 .hamburger-btn span { 405 width: 24px; 406 height: 2px; 407 background: #A0A0A0; 408 border-radius: 2px; 409 transition: all 0.3s ease; 410 } 411 412 .hamburger-btn.active span:nth-child(1) { 413 transform: rotate(45deg) translate(6px, 6px); 414 } 415 416 .hamburger-btn.active span:nth-child(2) { 417 opacity: 0; 418 } 419 420 .hamburger-btn.active span:nth-child(3) { 421 transform: rotate(-45deg) translate(6px, -6px); 422 } 423 424 .menu-overlay { 425 position: fixed; 426 top: 0; 427 left: -100%; 428 width: 100%; 429 max-width: 320px; 430 height: 100vh; 431 background: rgb(0 0 0 / 25%); 432 backdrop-filter: blur(40px); 433 -webkit-backdrop-filter: blur(40px); 434 z-index: 999; 435 transition: left 0.3s ease; 436 padding: 92px 40px 30px 40px; 437 overflow-y: auto; 438 border-right: 1px solid rgba(255, 255, 255, 0.1); 439 } 440 441 .menu-overlay.active { 442 left: 0; 443 } 444 445 .menu-items { 446 list-style: none; 447 padding: 0; 448 margin: 0; 449 display: flex; 450 flex-direction: column; 451 align-items: center; 452 } 453 454 .menu-item { 455 margin-bottom: 12px; 456 width: 100%; 457 max-width: 240px; 458 } 459 460 .menu-item a { 461 display: block; 462 background: rgba(255, 255, 255, 0.1); 463 backdrop-filter: blur(10px); 464 -webkit-backdrop-filter: blur(10px); 465 border: 2px solid rgba(255, 255, 255, 0.2); 466 border-radius: 12px; 467 padding: 14px 24px; 468 color: #E0E0E0; 469 text-decoration: none; 470 font-size: 16px; 471 font-weight: 500; 472 transition: all 0.3s ease; 473 text-align: center; 474 word-wrap: break-word; 475 } 476 477 .menu-item a:hover { 478 background: rgba(255, 255, 255, 0.15); 479 border-color: rgba(25, 219, 138, 0.5); 480 transform: translateX(5px); 481 } 482 483 .menu-item.featured a { 484 font-weight: 700; 485 background: rgba(25, 219, 138, 0.2); 486 border-color: #19DB8A; 487 color: #19DB8A; 488 } 489 490 .menu-item.featured a:hover { 491 background: rgba(25, 219, 138, 0.25); 492 border-color: #19DB8A; 493 } 494 495 .menu-backdrop { 496 position: fixed; 497 top: 0; 498 left: 0; 499 width: 100%; 500 height: 100vh; 501 background: rgba(0, 0, 0, 0.5); 502 z-index: 998; 503 opacity: 0; 504 visibility: hidden; 505 transition: all 0.3s ease; 506 } 507 508 .menu-backdrop.active { 509 opacity: 1; 510 visibility: visible; 511 } 512 513 .quick-links { 514 margin-top: 28px; 515 display: flex; 516 gap: 12px; 517 flex-wrap: wrap; 518 justify-content: center; 519 } 520 521 .quick-links a { 522 background: rgba(255, 255, 255, 0.1); 523 border: 2px solid rgba(255, 255, 255, 0.2); 524 border-radius: 12px; 525 padding: 14px 24px; 526 color: #E0E0E0; 527 font-size: 14px; 528 font-weight: 500; 529 text-decoration: none; 530 backdrop-filter: blur(10px); 531 transition: all 0.3s ease; 532 } 533 534 .quick-links a:hover { 535 background: rgba(255, 255, 255, 0.15); 536 border-color: rgba(25, 219, 138, 0.5); 537 } 538 539 @media (min-width: 769px) { 540 .quiz-container::before { 541 backdrop-filter: blur(10px); 542 } 543 } 544 545 @media (max-width: 768px) { 546 body { 547 overflow-y: auto; 548 } 549 550 .quiz-container { 551 padding: 16px; 552 min-height: 100vh; 553 height: auto; 554 display: flex; 555 flex-direction: column; 556 align-items: center; 557 justify-content: flex-start; 558 /* oben ausrichten */ 559 padding-top: 50px; 560 /* kleineres Padding */ 561 } 562 563 .quiz-container::before { 564 backdrop-filter: blur(5px); 565 } 566 567 .content { 568 width: 100%; 569 } 570 571 .header { 572 margin-bottom: 24px; 573 } 574 575 .header h1 { 576 font-size: 20px; 577 } 578 579 .header p { 580 font-size: 14px; 581 } 582 583 .quote-card { 584 padding: 16px 20px; 585 margin-bottom: 20px; 586 } 587 588 .quote-text { 589 font-size: 16px; 590 min-height: auto; 591 line-height: 1.5; 592 margin-bottom: 16px; 593 } 594 595 .question-text { 596 font-size: 14px; 597 margin-bottom: 20px; 598 } 599 600 .answers-container { 601 grid-template-columns: 1fr; 602 gap: 10px; 603 } 604 605 .answer-btn { 606 min-height: 54px; 607 font-size: 14px; 608 padding: 12px; 609 } 610 611 .result-message { 612 font-size: 16px; 613 margin-top: 12px; 614 } 615 616 .next-btn { 617 padding: 14px 32px; 618 font-size: 14px; 619 margin-top: 10px; 620 } 621 622 .stats-bar { 623 flex-direction: row; 624 gap: 8px; 625 margin-top: 16px; 626 } 627 628 .stat { 629 padding: 12px 8px; 630 } 631 632 .stat-label { 633 font-size: 10px; 634 } 635 636 .stat-value { 637 font-size: 18px; 638 } 639 640 .completed-message { 641 padding: 24px 16px; 642 } 643 644 .completed-message h2 { 645 font-size: 24px; 646 margin-bottom: 12px; 647 } 648 649 .completed-message p { 650 font-size: 14px; 651 margin-bottom: 16px; 652 } 653 654 .results-btn { 655 padding: 14px 32px; 656 font-size: 14px; 657 } 658 659 .loading { 660 padding: 40px 20px; 661 } 662 663 .loading p { 664 font-size: 14px; 665 } 666 667 .error { 668 font-size: 14px; 669 padding: 16px; 670 } 671 672 .hamburger-menu { 673 top: 16px; 674 left: 16px; 675 } 676 677 .menu-overlay { 678 max-width: 280px; 679 padding: 82px 0 20px 16px; 680 background: rgb(0 0 0 / 25%); 681 } 682 683 .menu-items { 684 align-items: flex-start; 685 } 686 687 .menu-item { 688 max-width: calc(100% - 16px); 689 } 690 691 .menu-item a { 692 text-align: left; 693 } 694 695 .menu-item a:hover { 696 transform: translateX(5px); 697 } 698 } 699 700 @media (max-width: 480px) { 701 .quiz-container { 702 padding: 12px; 703 padding-top: 30px; 704 } 705 706 .header h1 { 707 font-size: 18px; 708 } 709 710 .quote-text { 711 font-size: 15px; 712 } 713 714 .stat-label { 715 font-size: 9px; 716 } 717 718 .stat-value { 719 font-size: 16px; 720 } 721 722 .hamburger-menu { 723 top: 12px; 724 left: 12px; 725 } 726 727 .menu-overlay { 728 padding: 82px 0 20px 12px; 729 } 730 731 .menu-item { 732 max-width: calc(100% - 12px); 733 } 734 } 735 736 /* Newsletter Overlay Styles */ 737 .share-overlay { 738 position: fixed; 739 top: 0; 740 left: 0; 741 width: 100%; 742 height: 100%; 743 background: rgba(0, 0, 0, 0.6); 744 backdrop-filter: blur(8px); 745 -webkit-backdrop-filter: blur(8px); 746 z-index: 2000; 747 display: none; 748 justify-content: center; 749 align-items: center; 750 padding: 20px; 751 opacity: 0; 752 transition: opacity 0.3s ease; 753 } 754 755 .share-overlay.active { 756 display: flex; 757 opacity: 1; 758 } 759 760 .share-modal { 761 background: linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.08)); 762 backdrop-filter: blur(30px); 763 -webkit-backdrop-filter: blur(30px); 764 border: 1px solid rgba(255, 255, 255, 0.2); 765 border-radius: 24px; 766 padding: 40px; 767 max-width: 500px; 768 width: 100%; 769 text-align: center; 770 position: relative; 771 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); 772 animation: slideUp 0.4s ease-out; 773 } 774 775 @keyframes slideUp { 776 from { 777 transform: translateY(30px); 778 opacity: 0; 779 } 780 781 to { 782 transform: translateY(0); 783 opacity: 1; 784 } 785 } 786 787 .share-modal h3 { 788 font-size: 1.5rem; 789 margin-bottom: 15px; 790 color: #fff; 791 font-family: 'Readex Pro', sans-serif; 792 } 793 794 .share-modal p { 795 font-size: 1rem; 796 color: #cbd5e1; 797 margin-bottom: 30px; 798 line-height: 1.6; 799 } 800 801 .share-close-btn { 802 position: absolute; 803 top: 15px; 804 right: 15px; 805 background: rgba(255, 255, 255, 0.1); 806 border: 1px solid rgba(255, 255, 255, 0.2); 807 color: white; 808 width: 35px; 809 height: 35px; 810 border-radius: 50%; 811 cursor: pointer; 812 font-size: 20px; 813 transition: all 0.3s; 814 display: flex; 815 align-items: center; 816 justify-content: center; 817 } 818 819 .share-close-btn:hover { 820 background: rgba(255, 255, 255, 0.2); 821 transform: scale(1.1); 822 } 823 824 .newsletter-input { 825 width: 100%; 826 padding: 12px 15px; 827 border-radius: 12px; 828 border: 1px solid rgba(255, 255, 255, 0.2); 829 background: rgba(255, 255, 255, 0.1); 830 color: white; 831 margin-bottom: 15px; 832 font-size: 1rem; 833 outline: none; 834 transition: all 0.3s; 835 font-family: 'Readex Pro', sans-serif; 836 } 837 838 .newsletter-input:focus { 839 background: rgba(255, 255, 255, 0.2); 840 border-color: rgba(255, 255, 255, 0.4); 841 } 842 843 .newsletter-input::placeholder { 844 color: rgba(255, 255, 255, 0.6); 845 } 846 847 .newsletter-submit { 848 width: 100%; 849 cursor: pointer; 850 justify-content: center; 851 border: none; 852 font-size: 1rem; 853 font-weight: 600; 854 background: linear-gradient(135deg, #19DB8A 0%, #0FA968 100%); 855 color: #00001A; 856 padding: 12px; 857 border-radius: 12px; 858 transition: all 0.3s ease; 859 font-family: 'Readex Pro', sans-serif; 860 } 861 862 .newsletter-submit:hover { 863 transform: translateY(-2px); 864 box-shadow: 0 8px 24px rgba(25, 219, 138, 0.4); 865 } 866 867 .newsletter-footer { 868 font-size: 0.85rem !important; 869 opacity: 0.7; 870 margin-top: 20px !important; 871 margin-bottom: 0 !important; 872 } 873 </style> 874</head> 875 876<body> 877 <!-- Hamburger Menu --> 878 <div class="hamburger-menu"> 879 <button class="hamburger-btn" id="hamburgerBtn" aria-label=
879"Menü öffnen"> 880 <span></span> 881 <span></span> 882 <span></span> 883 </button> 884 </div> 885 886 <!-- App Button (Desktop Only) --> 887 <a href="/app" class="app-btn-top" aria-label="Zur App"> 888 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" 889 stroke-linejoin="round"> 890 <rect x="5" y="2" width="14" height="20" rx="2" ry="2"></rect> 891 <line x1="12" y1="18" x2="12.01" y2="18"></line> 892 </svg> 893 App 894 </a> 895 896 <!-- Menu Backdrop --> 897 <div class="menu-backdrop" id="menuBackdrop"></div> 898 899 <!-- Menu Overlay --> 900 <div class="menu-overlay" id="menuOverlay"> 901 <ul class="menu-items"> 902 <li class="menu-item"> 903 <a href="https://www.zitatdestages.net">Zitat des Tages</a> 904 </li> 905 <li class="menu-item"> 906 <a href="#" onclick="openNewsletterOverlay(); return false;">Newsletter</a> 907 </li> 908 <li class="menu-item featured"> 909 <a href="/app">App</a> 910 </li> 911 </ul> 912 </div> 913 914 <div class="quiz-container" id="quizContainer"> 915 <div class="content"> 916 <div id="quizContent"> 917 <div class="loading"> 918 <div class="spinner"></div> 919 <p>Lade Quiz...</p> 920 </div> 921 </div> 922 </div> 923 </div> 924 925
925<script> 926 // Hamburger Menu Logic 927 const hamburgerBtn = document.getElementById('hamburgerBtn'); 928 const menuOverlay = document.getElementById('menuOverlay'); 929 const menuBackdrop = document.getElementById('menuBackdrop'); 930 931 function toggleMenu() { 932 hamburgerBtn.classList.toggle('active'); 933 menuOverlay.classList.toggle('active'); 934 menuBackdrop.classList.toggle('active'); 935 936 // Prevent body scroll when menu is open 937 if (menuOverlay.classList.contains('active')) { 938 document.body.style.overflow = 'hidden'; 939 } else { 940 document.body.style.overflow = ''; 941 } 942 } 943 944 hamburgerBtn.addEventListener('click', toggleMenu); 945 menuBackdrop.addEventListener('click', toggleMenu); 946 947 // Close menu on Escape key 948 document.addEventListener('keydown', (e) => { 949 if (e.key === 'Escape' && menuOverlay.classList.contains('active')) { 950 toggleMenu(); 951 } 952 }); 953 954 // Configuration 955 const API_BASE_URL = 'https://api.zitatdestages.net'; 956 const API_VERSION = 'quotes'; 957 const MAX_QUESTIONS_PER_DAY = 5; 958 959 // LocalStorage Keys 960 const STORAGE_KEYS = { 961 QUIZ_POINTS_TODAY: 'quiz_points_today', 962 QUIZ_STREAK: 'quiz_streak', 963 QUIZ_TOTAL_POINTS: 'quiz_total_points', 964 QUIZ_LAST_PLAY_DATE: 'quiz_last_play_date', 965 QUIZ_QUESTIONS_TODAY: 'quiz_questions_today', 966 QUIZ_QUESTIONS_LEFT: 'quiz_questions_left', 967 QUIZ_QUESTIONS_CACHE: 'quiz_questions_cache', 968 QUIZ_CACHE_DATE: 'quiz_cache_date', 969 QUIZ_PERFORMANCE_WEEK: 'quiz_performance_week', 970 QUIZ_PLAYED_DATES: 'quiz_played_dates' // NEU: Liste aller gespielten Datums 971 }; 972 973 // State 974 let currentQuestion = null; 975 let currentQuestionIndex = 0; 976 let questionsToday = []; 977 let answered = false; 978 let correctAnswers = 0; 979 let stats = { 980 totalPoints: 0, 981 questionsLeft: MAX_QUESTIONS_PER_DAY 982 }; 983 984 // Initialize 985 document.addEventListener('DOMContentLoaded', () => { 986 const isIPad = /iPad|Macintosh/.test(navigator.userAgent) && 'ontouchend' in document; 987 if (isIPad) { 988 const container = document.querySelector('.quiz-container'); 989 if (container) { 990 container.style.justifyContent = 'flex-start'; 991 container.style.paddingTop = '100px'; 992 } 993 } 994 995 loadStats(); 996 initializeQuiz(); 997 }); 998 999 // Load Stats 1000 function loadStats() { 1001 stats.totalPoints = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_TOTAL_POINTS) || '0'); 1002 const questionsLeft = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_QUESTIONS_LEFT) || MAX_QUESTIONS_PER_DAY.toString()); 1003 stats.questionsLeft = questionsLeft; 1004 } 1005 1006 // Add Points 1007 function addPoints(points) { 1008 const currentPoints = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_POINTS_TODAY) || '0'); 1009 localStorage.setItem(STORAGE_KEYS.QUIZ_POINTS_TODAY, (currentPoints + points).toString()); 1010 1011 const totalPoints = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_TOTAL_POINTS) || '0'); 1012 const newTotalPoints = totalPoints + points; 1013 localStorage.setItem(STORAGE_KEYS.QUIZ_TOTAL_POINTS, newTotalPoints.toString()); 1014 stats.totalPoints = newTotalPoints; 1015 1016 // Update last play date 1017 const today = new Date().toDateString(); 1018 localStorage.setItem(STORAGE_KEYS.QUIZ_LAST_PLAY_DATE, today); 1019 1020 // Update performance data for chart 1021 updatePerformanceData(points); 1022 } 1023 1024 // Update Performance Data 1025 function updatePerformanceData(points) { 1026 const today = new Date().toDateString(); 1027 let performanceData = JSON.parse(localStorage.getItem(STORAGE_KEYS.QUIZ_PERFORMANCE_WEEK) || '{}'); 1028 1029 performanceData[today] = (performanceData[today] || 0) + points; 1030 1031 const dates = Object.keys(performanceData).sort((a, b) => new Date(b) - new Date(a)); 1032 const last7Days = {};
1033 dates.slice(0, 7).forEach(date => { 1034 last7Days[date] = performanceData[date]; 1035 }); 1036 1037 localStorage.setItem(STORAGE_KEYS.QUIZ_PERFORMANCE_WEEK, JSON.stringify(last7Days)); 1038 } 1039 1040 // Update Streak - Simple: increment if played yesterday, reset otherwise 1041 function updateStreak() { 1042 const today = new Date().toDateString(); 1043 const lastPlayDate = localStorage.getItem(STORAGE_KEYS.QUIZ_LAST_PLAY_DATE); 1044 1045 if (!lastPlayDate) { 1046 // First time playing - start streak at 1 1047 localStorage.setItem(STORAGE_KEYS.QUIZ_STREAK, '1'); 1048 return; 1049 } 1050 1051 // Only update streak once per day 1052 if (lastPlayDate === today) { 1053 return; 1054 } 1055 1056 const lastPlay = new Date(lastPlayDate); 1057 const todayDate = new Date(today); 1058 const diffDays = Math.floor((todayDate - lastPlay) / (1000 * 60 * 60 * 24)); 1059 1060 let currentStreak = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_STREAK) || '1'); 1061 1062 if (diffDays === 1) { 1063 // Played yesterday - increment streak 1064 currentStreak++; 1065 localStorage.setItem(STORAGE_KEYS.QUIZ_STREAK, currentStreak.toString()); 1066 } else if (diffDays > 1) { 1067 // Missed a day - reset to 1 1068 localStorage.setItem(STORAGE_KEYS.QUIZ_STREAK, '1'); 1069 } 1070 } 1071 1072 // Initialize Quiz 1073 async function initializeQuiz() { 1074 const today = new Date().toDateString(); 1075 const lastPlayDate = localStorage.getItem(STORAGE_KEYS.QUIZ_LAST_PLAY_DATE); 1076 1077 // Reset if new day 1078 if (lastPlayDate !== today) { 1079 localStorage.setItem(STORAGE_KEYS.QUIZ_QUESTIONS_LEFT, MAX_QUESTIONS_PER_DAY.toString()); 1080 localStorage.setItem(STORAGE_KEYS.QUIZ_QUESTIONS_TODAY, JSON.stringify([])); 1081 localStorage.setItem(STORAGE_KEYS.QUIZ_POINTS_TODAY, '0'); 1082 1083 // Update streak BEFORE setting new last_play_date 1084 updateStreak(); 1085 } 1086 1087 const questionsLeft = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_QUESTIONS_LEFT) || MAX_QUESTIONS_PER_DAY.toString()); 1088 1089 if (questionsLeft <= 0) { 1090 showCompletedMessage(); 1091 return; 1092 } 1093 1094 await loadQuestion(); 1095 } 1096 1097 // Load Question from API 1098 async function loadQuestion() { 1099 const container = document.getElementById('quizContent'); 1100 1101 try { 1102 // Simuliere API-Call (ersetze mit echtem API-Endpoint wenn verfügbar) 1103 const question = await fetchQuizQuestion(); 1104 1105 if (!question) { 1106 throw new Error('Keine Frage verfügbar'); 1107 } 1108 1109 currentQuestion = question; 1110 displayQuestion(question); 1111 1112 } catch (error) { 1113 console.error('Error loading question:', error); 1114 container.innerHTML = ` 1115 <div class="error"> 1116 <h3>Fehler beim Laden der Frage</h3> 1117 <p>Bitte versuche es später erneut.</p> 1118 </div> 1119 `; 1120 } 1121 } 1122 1123 // Fetch Quiz Question (Real API Call) 1124 async function fetchQuizQuestion() { 1125 try { 1126 const urlParams = getUrlParams(); 1127 const cacheDate = localStorage.getItem(STORAGE_KEYS.QUIZ_CACHE_DATE); 1128 const today = new Date().toDateString(); 1129 1130 // Cache check 1131 if (cacheDate !== today) { 1132 localStorage.removeItem(STORAGE_KEYS.QUIZ_QUESTIONS_CACHE); 1133 localStorage.removeItem(STORAGE_KEYS.QUIZ_CACHE_DATE); 1134 } else { 1135 const cachedQuestions = JSON.parse(localStorage.getItem(STORAGE_KEYS.QUIZ_QUESTIONS_CACHE) || '[]'); 1136 if (cachedQuestions.length > 0) { 1137 const firstQuestion = cachedQuestions[0]; 1138 const hasValidFormat = firstQuestion && 1139 firstQuestion.quote && 1140 firstQuestion.answer1 && 1141 firstQuestion.answer2 && 1142 typeof firstQuestion.correctAnswer === 'number'; 1143 1144 if (hasValidFormat) { 1145 return selectQuestionByDate(cachedQuestions, urlParam
1145s.date); 1146 } else { 1147 localStorage.removeItem(STORAGE_KEYS.QUIZ_QUESTIONS_CACHE); 1148 localStorage.removeItem(STORAGE_KEYS.QUIZ_CACHE_DATE); 1149 } 1150 } 1151 } 1152 1153 // Fetch all questions from API 1154 const language = 'de'; 1155 const url = `${API_BASE_URL}/quotes/questions?l=${language}`; 1156 const response = await fetch(url); 1157 if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); 1158 const data = await response.json(); 1159 if (!data || data.length === 0) throw new Error('No questions available'); 1160 1161 // Transform API response 1162 const questions = data.map(item => { 1163 const backgroundImageUuid = item.background_image; 1164 const backgroundImageUrl = backgroundImageUuid 1165 ? `http://api.zitatdestages.net/image?b=${backgroundImageUuid}` 1166 : 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=1920'; 1167 return { 1168 uuid: item.uuid || '', 1169 date: item.date || '', 1170 quote: item.quote || '', 1171 answer1: item.answer1 || '', 1172 answer2: item.answer2 || '', 1173 correctAnswer: parseInt(item.correct_answer) || 1, 1174 answer1Uuid: item.answer1_uuid || '', 1175 answer2Uuid: item.answer2_uuid || '', 1176 backgroundImage: backgroundImageUrl, 1177 backgroundImageUuid: backgroundImageUuid || '', 1178 language: item.language || 'de' 1179 }; 1180 }); 1181 1182 // Cache questions 1183 localStorage.setItem(STORAGE_KEYS.QUIZ_QUESTIONS_CACHE, JSON.stringify(questions)); 1184 localStorage.setItem(STORAGE_KEYS.QUIZ_CACHE_DATE, today); 1185 1186 return selectQuestionByDate(questions, urlParams.date); 1187 1188 } catch (error) { 1189 console.error('Error fetching quiz question:', error); 1190 return getFallbackQuestion(); 1191 } 1192 } 1193 1194 // Smart question selection based on played dates 1195 function selectQuestionByDate(questions, requestedDate) { 1196 const playedDates = JSON.parse(localStorage.getItem(STORAGE_KEYS.QUIZ_PLAYED_DATES) || '[]'); 1197
1198 // Get today's date in user timezone (YYYY-MM-DD) 1199 const now = new Date(); 1200 const todayDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; 1201 1202 // Case 1: Specific date requested (via URL parameter) 1203 if (requestedDate) { 1204 const filtered = questions.filter(q => q.date === requestedDate); 1205 if (filtered.length > 0) { 1206 return filtered[0]; 1207 } 1208 } 1209 1210 // Case 2: No URL parameter â Show today's quiz (if not played) 1211 if (!requestedDate) { 1212 const todayQuestion = questions.find(q => q.date === todayDate); 1213 const todayPlayed = playedDates.includes(todayDate); 1214 1215 if (todayQuestion && !todayPlayed) { 1216 return todayQuestion; 1217 } 1218 } 1219 1220 // Case 3: Find an unplayed question 1221 const unplayedQuestions = questions.filter(q => !playedDates.includes(q.date)); 1222 1223 if (unplayedQuestions.length > 0) { 1224 const randomIndex = Math.floor(Math.random() * unplayedQuestions.length); 1225 return unplayedQuestions[randomIndex]; 1226 } 1227 1228 // Case 4: All questions played â Show random question 1229 const randomIndex = Math.floor(Math.random() * questions.length); 1230 return questions[randomIndex]; 1231 } 1232 1233 // Mark date as played 1234 function markDateAsPlayed(date) { 1235 if (!date) return; 1236 1237 const playedDates = JSON.parse(localStorage.getItem(STORAGE_KEYS.QUIZ_PLAYED_DATES) || '[]'); 1238 1239 if (!playedDates.includes(date)) { 1240 playedDates.push(date); 1241 localStorage.setItem(STORAGE_KEYS.QUIZ_PLAYED_DATES, JSON.stringify(playedDates)); 1242 } 1243 } 1244 1245 // Update Streak - Verbessert: berücksichtigt mehrere Tage 1246 function updateStreak() { 1247 const today = new Date().toDateString(); 1248 const lastPlayDate = localStorage.getItem(STORAGE_KEYS.QUIZ_LAST_PLAY_DATE); 1249 1250 if (!lastPlayDate) { 1251 // First time playing - start streak at 1 1252 localStorage.setItem(STORAGE_KEYS.QUIZ_STREAK, '1'); 1253 return; 1254 } 1255 1256 // Only update streak once per day 1257 if (lastPlayDate === today) { 1258 return; 1259 } 1260 1261 const lastPlay = new Date(lastPlayDate); 1262 const todayDate = new Date(today); 1263 const diffDays = Math.floor((todayDate - lastPlay) / (1000 * 60 * 60 * 24)); 1264 1265 let currentStreak = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_STREAK) || '1'); 1266 1267 if (diffDays === 1) { 1268 // Played yesterday - increment streak 1269 currentStreak++; 1270 localStorage.setItem(STORAGE_KEYS.QUIZ_STREAK, currentStreak.toString()); 1271 } else if (diffDays > 1) { 1272 // Missed a day - reset to 1 1273 localStorage.setItem(STORAGE_KEYS.QUIZ_STREAK, '1'); 1274 } 1275 } 1276 1277 // Check Answer 1278 function checkAnswer(selectedAnswer) { 1279 if (answered) return; 1280 answered = true; 1281 1282 const buttons = document.querySelectorAll('.answer-btn'); 1283 const resultMessage = document.getElementById('resultMessage'); 1284 const isCorrect = selectedAnswer === currentQuestion.correctAnswer; 1285 1286 buttons.forEach((btn, index) => { 1287 btn.disabled = true; 1288 if (index + 1 === currentQuestion.correctAnswer) { 1289 btn.classList.add('correct'); 1290 } else if (index + 1 === selectedAnswer && !isCorrect) { 1291 btn.classList.add('wrong'); 1292 } 1293 }); 1294 1295 if (isCorrect) { 1296 correctAnswers++; 1297 resultMessage.textContent = 'Richtig!'; 1298 resultMessage.className = 'result-message correct'; 1299 addPoints(1); 1300 updateStatsDisplay(); 1301 } else { 1302 resultMessage.textContent = 'Leider falsch!'; 1303 resultMessage.className = 'result-message wrong'; 1304 } 1305 1306 // Mark current quiz date as played 1307 if (currentQuestion && currentQuestion.date) { 1308 markDateAsPlayed(currentQuestion.date); 1309 } 1310 1311 // Update questions left 1312 const questionsLeft = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_QUESTIONS_LEFT) || MAX_QUESTIONS_PER_DAY.toString()); 1313 localStorage.setItem(STORAGE_KEYS.QUIZ_QUESTIONS_LEFT, (questionsLeft - 1).toString()); 1314 1315 // Remove URL parameters after auto-answer
1316 const urlParams = getUrlParams(); 1317 if (urlParams.date || urlParams.answer) { 1318 const cleanUrl = window.location.pathname; 1319 window.history.replaceState({}, document.title, cleanUrl); 1320 } 1321 1322 // Show next button 1323 const quoteCard = document.querySelector('.quote-card'); 1324 const nextButton = document.createElement('button'); 1325 nextButton.className = 'next-btn'; 1326 1327 if (questionsLeft - 1 > 0) { 1328 nextButton.textContent = 'Nächste Frage'; 1329 nextButton.onclick = () => { 1330 loadQuestion(); 1331 }; 1332 } else { 1333 nextButton.textContent = 'Ergebnisse ansehen'; 1334 nextButton.onclick = () => { 1335 window.location.href = '/app/quiz-results'; 1336 }; 1337 } 1338 1339 quoteCard.appendChild(nextButton); 1340 } 1341 1342 // Update Stats Display 1343 function updateStatsDisplay() { 1344 const todayPoints = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_POINTS_TODAY) || '0'); 1345 const questionsLeft = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_QUESTIONS_LEFT) || MAX_QUESTIONS_PER_DAY.toString()); 1346 1347 const statValues = document.querySelectorAll('.stat-value'); 1348 if (statValues.length >= 3) { 1349 statValues[0].textContent = questionsLeft; 1350 statValues[1].textContent = todayPoints; 1351 statValues[2].textContent = stats.totalPoints; 1352 } 1353 } 1354 1355 // Show Completed Message 1356 function showCompletedMessage() { 1357 const content = document.getElementById('quizContent'); 1358 const todayPoints = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_POINTS_TODAY) || '0'); 1359 1360 content.innerHTML = ` 1361 <div class="completed-message"> 1362 <h2>Glückwunsch!</h2> 1363 <p>Du hast heute alle ${MAX_QUESTIONS_PER_DAY} Fragen beantwortet.</p> 1364 <p>Du hast heute <strong>${todayPoints} Punkte</strong> erzielt.</p> 1365 <p>Komm morgen wieder für neue Fragen.</p> 1366 <a href="/app/quiz-results" class="results-btn">Ergebnisse ansehen</a> 1367 <div class="quick-links"> 1368 <a href="https://www.zitatdestages.net">Zitat des Tages</a> 1369 <a href="/newsletter">Newsletter</a> 1370 <a href="/app">App</a> 1371 </div> 1372 </div> 1373 `; 1374 } 1375 1376 // Fallback Question 1377 function getFallbackQuestion() { 1378 const now = new Date(); 1379 const todayDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; 1380 1381 return { 1382 quote: "Das Leben ist das, was passiert, während du eifrig dabei bist, andere Pläne zu machen.", 1383 answer1: "John Lennon", 1384 answer2: "Albert Einstein", 1385 correctAnswer: 1, 1386 backgroundImage: "https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=1920", 1387 date: todayDate, 1388 uuid: 'fallback-1', 1389 answer1Uuid: '', 1390 answer2Uuid: '', 1391 backgroundImageUuid: '', 1392 language: 'de' 1393 }; 1394 } 1395 1396 // Display Question 1397 function displayQuestion(question) { 1398 const container = document.getElementById('quizContainer'); 1399 const content = document.getElementById('quizContent'); 1400 1401 if (!question || !question.quote) { 1402 content.innerHTML = ` 1403 <div class="error"> 1404 <h3>Fehler</h3> 1405 <p>Ungültige Frage. Bitte versuche es erneut.</p> 1406 <button class="next-btn" onclick="location.reload()">Neu laden</button> 1407 </div> 1408 `; 1409 return; 1410 } 1411 1412 // Set background image with fallback 1413 const backgroundImage = question.backgroundImage || 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=1920'; 1414 1415 // Preload image before setting 1416 const img = new Image(); 1417 img.onload = () => { 1418 container.style.backgroundImage = `url('${backgroundImage}')`; 1419 }; 1420 img.onerror = () => { 1421 container.style.backgroundImage = `url('https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=1920')`; 1422 }; 1423 img.src = backgroundImage; 1424 1425 const questionsLeft = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_QUESTIONS_LEFT) || MAX_QUESTIONS_PER_DAY.toString()); 1426 const todayPoints = parseInt(localStorage.getItem(STORAGE_KEYS.QUIZ_POINTS_TODAY) || '0'); 1427 1428 content.innerHTML = ` 1429 <div class="header"> 1430 <h1>Quiz des Tages</h1> 1431 <p>
1431Von wem stammt dieses Zitat?</p> 1432 </div> 1433 1434 <div class="quote-card"> 1435 <div class="quote-text">${escapeHtml(question.quote)}</div> 1436 1437 <div class="answers-container"> 1438 <button class="answer-btn" onclick="checkAnswer(1)"><span class="label">${escapeHtml(question.answer1)}</span></button> 1439 <button class="answer-btn" onclick="checkAnswer(2)"><span class="label">${escapeHtml(question.answer2)}</span></button> 1440 </div> 1441 1442 <div class="result-message" id="resultMessage"></div> 1443 </div> 1444 1445 <div class="stats-bar"> 1446 <div class="stat"> 1447 <div class="stat-label">Fragen übrig</div> 1448 <div class="stat-value">${questionsLeft}</div> 1449 </div> 1450 <div class="stat"> 1451 <div class="stat-label">Heute richtig</div> 1452 <div class="stat-value">${todayPoints}</div> 1453 </div> 1454 <div class="stat"> 1455 <div class="stat-label">Gesamtpunkte</div> 1456 <div class="stat-value">${stats.totalPoints}</div> 1457 </div> 1458 </div> 1459 `; 1460 1461 answered = false; 1462 1463 // NEU: Auto-Answer wenn answer-Parameter vorhanden 1464 const urlParams = getUrlParams(); 1465 if (urlParams.answer && (urlParams.answer === '1' || urlParams.answer === '2')) { 1466 // Kleine Verzögerung damit Animation sichtbar ist 1467 setTimeout(() => { 1468 checkAnswer(parseInt(urlParams.answer)); 1469 }, 300); 1470 } 1471 } 1472 1473 // Utility: URL-Parameter auslesen 1474 function getUrlParams() { 1475 const params = new URLSearchParams(window.location.search); 1476 return { 1477 date: params.get('date'), // z.B. ?date=2024-01-15 1478 answer: params.get('answer') // z.B. ?answer=1 oder ?answer=2 1479 }; 1480 } 1481 1482 // Utility: Escape HTML 1483 function escapeHtml(text) { 1484 const map = { 1485 '&': '&', 1486 '<': '<', 1487 '>': '>', 1488 '"': '"', 1489 "'": ''' 1490 }; 1491 return text.replace(/[&<>"']/g, m => map[m]); 1492 } 1493 </script>
1493 1494</body> 1495 1496<!-- Share Overlay --> 1497<div class="share-overlay" id="shareOverlay"> 1498 <div class="share-modal"> 1499 <button class="share-close-btn" onclick="closeShareOverlay()">Ã</button> 1500 <h3 id="shareTitle">Tu dir selbst oder jemandem anderen was gutes</h3> 1501 <p>Sende das Zitat des Tages an dein Handy, deine Freunden oder Familie. Gemeinsam inspirieren wir uns 1502 gegenseitig!</p> 1503 <div class="share-modal-buttons"> 1504 <button class="newsletter-submit" onclick="shareApp()" 1505 style="width: 100%; padding: 12px 24px; display: flex; align-items: center; justify-content: center; gap: 8px;"> 1506 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" 1507 stroke-linecap="round" stroke-linejoin="round"> 1508 <circle cx="18" cy="5" r="3"></circle> 1509 <circle cx="6" cy="12" r="3"></circle> 1510 <circle cx="18" cy="19" r="3"></circle> 1511 <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line> 1512 <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line> 1513 </svg> 1514 Sende dieses Quiz an ... 1515 </button> 1516 </div> 1517 </div> 1518</div> 1519
1520<script> 1521 // Share functionality 1522 let shareOverlayShown = false; 1523 1524 function shareApp() { 1525 const url = window.location.href; 1526 const title = 'Quiz - Zitat des Tages'; 1527 const text = 'Teste dein Wissen beim Zitat des Tages Quiz!'; 1528 1529 if (navigator.share) { 1530 navigator.share({ 1531 title: title, 1532 text: text, 1533 url: url 1534 }).then(() => { 1535 closeShareOverlay(); 1536 }).catch((error) => { 1537 console.log('Share cancelled or failed:', error); 1538 }); 1539 } else { 1540 // Fallback: Copy to clipboard 1541 navigator.clipboard.writeText(url).then(() => { 1542 alert('Link wurde in die Zwischenablage kopiert!'); 1543 closeShareOverlay(); 1544 }).catch((error) => { 1545 console.error('Failed to copy:', error); 1546 }); 1547 } 1548 } 1549 1550 function openShareOverlay(title) { 1551 if (shareOverlayShown) return; 1552 1553 shareOverlayShown = true; 1554 const overlay = document.getElementById('shareOverlay'); 1555 const titleElement = document.getElementById('shareTitle'); 1556 1557 if (title) { 1558 titleElement.textContent = title; 1559 } 1560 1561 overlay.classList.add('active'); 1562 } 1563 1564 function closeShareOverlay() { 1565 const overlay = document.getElementById('shareOverlay'); 1566 overlay.classList.remove('active'); 1567 } 1568 1569 // Detect mobile 1570 function isMobile() { 1571 return window.innerWidth <= 768; 1572 } 1573 1574 // Timer-based trigger (20 seconds) 1575 setTimeout(() => { 1576 if (!shareOverlayShown) { 1577 openShareOverlay(isMobile() ? 'Tu dir selbst oder jemandem anderen was gutes!' : 'Tu dir selbst oder jemandem anderen was gutes!'); 1578 } 1579 }, 20000); 1580 1581 // Desktop: Mouse out detection 1582 if (!isMobile()) { 1583 document.addEventListener('mouseout', function (e) { 1584 if (!e.toElement && !e.relatedTarget && !shareOverlayShown) { 1585 openShareOverlay('Bitte geh noch nicht!'); 1586 } 1587 }); 1588 } 1589 1590 // Scroll to bottom detection 1591 let scrollTriggered = false; 1592 window.addEventListener('scroll', function () { 1593 if (scrollTriggered || shareOverlayShown) return; 1594 1595 const scrollTop = window.pageYOffset || document.documentElement.scrollTop; 1596 const windowHeight = window.innerHeight; 1597 const documentHeight = document.documentElement.scrollHeight; 1598 1599 // Check if scrolled to bottom (with 100px threshold) 1600 if (scrollTop + windowHeight >= documentHeight - 100) { 1601 scrollTriggered = true; 1602 openShareOverlay('Tu dir selbst oder jemandem anderen was gutes!'); 1603 } 1604 }); 1605 1606 // Close overlay when clicking outside 1607 document.getElementById('shareOverlay').addEventListener('click', function (e) { 1608 if (e.target === this) { 1609 closeShareOverlay(); 1610 } 1611 }); 1612</script>
1612 1613 1614<!-- Newsletter Overlay --> 1615<div class="share-overlay" id="newsletterOverlay"> 1616 <div class="share-modal"> 1617 <button class="share-close-btn" onclick="closeNewsletterOverlay()">Ã</button> 1618 <h3>Abonniere das Zitat des Tages per Mail</h3> 1619 <p>Täglich ein ausgewähltes Zitat in dein Postfach</p> 1620 1621 <div class="newsletter-form-container"> 1622 <form style="display:inline" method="post" action="https://www.zitatdestages.net/?na=s" 1623 onsubmit="return newsletter_check(this)"> 1624 <input type="hidden" name="nlang" value=""> 1625 <input type="hidden" name="nr" value="page"> 1626 <input class="newsletter-input" placeholder="Deine E-Mail Adresse" type="email" name="ne" required=""> 1627 <input class="newsletter-submit" type="submit" value="Anmelden"> 1628 </form> 1629 </div> 1630 1631 <p class="newsletter-footer">Kostenlos, ohne Spam und jederzeit wieder abmelden.</p> 1632 </div> 1633</div> 1634
1635<script> 1636 // Newsletter Overlay Logic 1637 function openNewsletterOverlay() { 1638 document.getElementById('newsletterOverlay').classList.add('active'); 1639 document.body.style.overflow = 'hidden'; 1640 // Close menu if open 1641 if (document.getElementById('menuOverlay').classList.contains('active')) { 1642 toggleMenu(); 1643 } 1644 } 1645 1646 function closeNewsletterOverlay() { 1647 document.getElementById('newsletterOverlay').classList.remove('active'); 1648 document.body.style.overflow = ''; 1649 } 1650 1651 // Close on click outside 1652 document.getElementById('newsletterOverlay').addEventListener('click', (e) => { 1653 if (e.target.id === 'newsletterOverlay') closeNewsletterOverlay(); 1654 }); 1655 1656 function newsletter_check(f) { 1657 var re = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; 1658 if (!re.test(f.elements["ne"].value)) { 1659 alert("Bitte eine gültige E-Mail Adresse eingeben."); 1660 return false; 1661 } 1662 return true; 1663 } 1664</script>
1664 1665</body> 1666 1667</html>
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.