1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5<!-- This site is hosted on Netlify. Anyone can build and deploy a site 6 like this one for free: https://netlify.new/?utm_campaign=loops&utm_source=ai-legible&utm_medium=owned&utm_content=comment&utm_id=8b60a803-5529-4fb0-b25c-5e458e94d6de 7 Netlify hosting facts for this site: static/SSR served via Netlify Edge. --> 8 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 9 <title>Educational Sign Language Expressions</title> 10 <!-- Include EmailJS for certificate emails --> 11
11<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@emailjs/browser@3/dist/email.min.js"></script>
11 12 <!-- Include html2canvas for certificate generation --> 13
13<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
13 14 <style> 15 * { 16 -webkit-tap-highlight-color: transparent; 17 box-sizing: border-box; 18 } 19 20 body { 21 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; 22 max-width: 100%; 23 margin: 0 auto; 24 padding: 16px; 25 text-align: center; 26 background-color: #f0f4fa; 27 color: #333; 28 -webkit-text-size-adjust: 100%; 29 overscroll-behavior: none; 30 } 31 32 h1 { 33 color: #2c3e50; 34 margin-bottom: 10px; 35 font-size: 28px; 36 } 37 38 h2 { 39 color: #1e88e5; 40 margin-top: 10px; 41 font-size: 22px; 42 } 43 44 .container { 45 background-color: white; 46 border-radius: 16px; 47 padding: 16px; 48 box-shadow: 0 4px 12px rgba(0,0,0,0.1); 49 margin-top: 16px; 50 max-width: 800px; 51 margin-left: auto; 52 margin-right: auto; 53 } 54 55 .sign-display { 56 display: flex; 57 flex-direction: column; 58 justify-content: center; 59 align-items: center; 60 min-height: 280px; 61 margin: 16px 0; 62 background-color: #e3f2fd; 63 border-radius: 16px; 64 padding: 20px; 65 position: relative; 66 touch-action: manipulation; 67 border: 1px solid #bbdefb; 68 } 69 70 .sign-emoji { 71 font-size: 80px; 72 margin-bottom: 15px; 73 } 74 75 .sign-sequence { 76 display: flex; 77 justify-content: center; 78 align-items: center; 79 gap: 15px; 80 flex-wrap: wrap; 81 margin-bottom: 20px; 82 padding: 10px; 83 } 84 85 .sequence-item { 86 display: flex; 87 flex-direction: column; 88 align-items: center; 89 padding: 10px; 90 background-color: white; 91 border-radius: 12px; 92 min-width: 75px; 93 box-shadow: 0 2px 4px rgba(0,0,0,0.05); 94 margin: 5px; 95 border: 1px solid #bbdefb; 96 } 97 98 .sequence-emoji { 99 font-size: 56px; 100 margin-bottom: 8px; 101 } 102 103 .sequence-step { 104 font-size: 14px; 105 color: #1976d2; 106 text-align: center; 107 padding: 0 4px; 108 } 109 110 .sign-expression { 111 font-size: 24px; 112 font-weight: bold; 113 color: #1565c0; 114 margin-bottom: 15px; 115 padding: 0 10px; 116 text-align: center; 117 } 118 119 .sign-description { 120 font-size: 16px; 121 color: #1976d2; 122 line-height: 1.5; 123 max-width: 100%; 124 padding: 0 5px; 125 text-align: center; 126 } 127 128 .controls { 129 display: flex; 130 justify-content: center; 131 gap: 12px; 132 margin: 16px 0; 133 flex-wrap: wrap; 134 } 135 136 button { 137 background-color: #2196f3; 138 color: white; 139 border: none; 140 padding: 12px 18px; 141 border-radius: 12px; 142 cursor: pointer; 143 font-size: 16px; 144 transition: background-color 0.2s, transform 0.1s; 145 -webkit-appearance: none; 146 -moz-appearance: none; 147 appearance: none; 148 touch-action: manipulation; 149 user-select: none; 150 -webkit-user-select: none; 151 min-height: 44px; 152 } 153 154 button:active { 155 transform: scale(0.98); 156 background-color: #1976d2; 157 } 158 159 button.active { 160 background-color: #1976d2; 161 font-weight: bold; 162 } 163 164 .expression-selection { 165 display: flex; 166 flex-wrap: wrap; 167 justify-content: center; 168 gap: 10px; 169 margin: 16px 0; 170 } 171 172 .expression-btn { 173 min-width: 130px; 174 padding: 12px 16px; 175 font-size: 15px; 176 } 177 178 .categories { 179 display: flex; 180 justify-content: center; 181 gap: 8px; 182 margin-bottom: 16px; 183 flex-wrap: wrap; 184 padding: 0 5px; 185 } 186 187 .category-btn { 188 background-color: #64b5f6; 189 min-width: 120px; 190 font-size: 15px; 191 color: #0d47a1; 192 } 193 194 .category-btn.active { 195 background-color: #2196f3; 196 color: white; 197 } 198 199 .quiz-section { 200 margin-top: 24px; 201 background-color: #e3f2fd; 202 padding: 16px; 203 border-radius: 16px; 204 } 205 206 .quiz-options { 207 display: flex; 208 flex-wrap: wrap; 209 justify-content: center; 210 gap: 12px; 211 margin-top: 16px; 212 } 213 214 .quiz-option-btn { 215 min-width: 140px; 216 padding: 14px 18px; 217 font-size: 16px; 218 } 219 220 .quiz-result { 221 margin-top: 16px; 222 font-weight: bold; 223 min-height: 24px; 224 padding: 8px; 225 } 226 227 .correct { 228 color: #2e7d32; 229 } 230 231 .incorrect { 232 color: #c62828; 233 } 234 235 .progress-bar-container { 236 width: 100%; 237 background-color: #bbdefb; 238 border-radius: 20px; 239 margin: 20px 0; 240 overflow: hidden; 241 height: 16px; 242 } 243 244 .progress-bar { 245 height: 16px; 246 background-color: #2196f3; 247 border-radius: 20px; 248 width: 0%; 249 transition: width 0.5s; 250 } 251 252 .progress-text { 253 font-size: 14px; 254 color: #1976d2; 255 margin-top: 5px; 256 } 257 258 .arrow { 259 font-size: 24px; 260 color: #2196f3; 261 margin: 0 5px; 262 display: flex; 263 align-items: center; 264 } 265 266 #test-container { 267 display: none; 268 } 269 270 /* Test score display */ 271 .test-score { 272 margin: 20px 0; 273 font-size: 18px; 274 font-weight: bold; 275 } 276 277 .test-progress { 278 display: flex; 279 align-items: center; 280 justify-content: center; 281 margin: 10px 0; 282 font-size: 16px; 283 } 284 285 .score-good { 286 color: #2e7d32; 287 } 288 289 .score-avg { 290 color: #f57f17; 291 } 292 293 .score-poor { 294 color: #c62828; 295 } 296 297 /* Enhanced touch feedback */ 298 .touch-feedback { 299 position: relative; 300 overflow: hidden; 301 } 302 303 .touch-feedback::after { 304 content: ''; 305 display: block; 306 position: absolute; 307 width: 100%; 308 height: 100%; 309 top: 0; 310 left: 0; 311 pointer-events: none; 312 background-image: radial-gradient(circle, rgba(255,255,255,0.4) 10%, transparent 10.01%); 313 background-repeat: no-repeat; 314 background-position: 50%; 315 transform: scale(10, 10); 316 opacity: 0; 317 transition: transform 0.5s, opacity 0.8s; 318 } 319 320 .touch-feedback:active::after { 321 transform: scale(0, 0); 322 opacity: 0.3; 323 transition: 0s; 324 } 325 326 /* Certificate form styles */ 327 .certificate-form { 328 background-color: #e3f2fd; 329 padding: 20px; 330 border-radius: 16px; 331 margin-top: 20px; 332 display: none; 333 border: 1px solid #bbdefb; 334 } 335 336 .certificate-form h3 { 337 margin-top: 0; 338 color: #1565c0; 339 } 340 341 .form-group { 342 margin-bottom: 15px; 343 text-align: left; 344 } 345 346 .form-group label { 347 display: block; 348 margin-bottom: 5px; 349 font-weight: bold; 350 color: #1565c0; 351 } 352 353 .form-group input { 354 width: 100%; 355 padding: 10px; 356 border: 1px solid #64b5f6; 357 border-radius: 8px; 358 font-size: 16px; 359 } 360 361 .overlay { 362 position: fixed; 363 top: 0; 364 left: 0; 365 width: 100%; 366 height: 100%; 367 background-color: rgba(0, 0, 0, 0.7); 368 display: none; 369 justify-content: center; 370 align-items: center; 371 z-index: 1000; 372 } 373 374 .modal { 375 background-color: white; 376 padding: 30px; 377 border-radius: 16px; 378 max-width: 90%; 379 max-height: 90%; 380 overflow-y: auto; 381 position: relative; 382 } 383 384 .close-modal { 385 position: absolute; 386 top: 10px; 387 right: 15px; 388 font-size: 24px; 389 cursor: pointer; 390 color: #1976d2; 391 } 392 393 .loading-indicator { 394 display: none; 395 margin-top: 20px; 396 } 397 398 .spinner { 399 border: 4px solid #bbdefb;
400 border-top: 4px solid #2196f3; 401 border-radius: 50%; 402 width: 30px; 403 height: 30px; 404 animation: spin 1s linear infinite; 405 margin: 0 auto; 406 } 407 408 @keyframes spin { 409 0% { transform: rotate(0deg); } 410 100% { transform: rotate(360deg); } 411 } 412 413 /* Motion descriptions for test mode */ 414 .motion-description { 415 font-style: italic; 416 color: #1565c0; 417 margin: 15px 0; 418 padding: 12px; 419 text-align: center; 420 font-size: 16px; 421 line-height: 1.6; 422 background-color: white; 423 border-radius: 10px; 424 border: 1px solid #bbdefb; 425 } 426 427 /* Different screen sizes */ 428 @media (max-width: 480px) { 429 body { 430 padding: 10px; 431 } 432 433 h1 { 434 font-size: 24px; 435 } 436 437 .sign-display { 438 min-height: 260px; 439 padding: 12px; 440 } 441 442 .sign-emoji { 443 font-size: 50px; 444 } 445 446 .sequence-emoji { 447 font-size: 44px; 448 } 449 450 .sequence-item { 451 min-width: 60px; 452 } 453 454 .sign-expression { 455 font-size: 20px; 456 } 457 458 .expression-btn { 459 min-width: 130px; 460 padding: 10px 14px; 461 font-size: 14px; 462 } 463 464 .category-btn { 465 min-width: 100px; 466 padding: 10px 12px; 467 font-size: 14px; 468 } 469 470 button { 471 padding: 12px 16px; 472 } 473 } 474 475 /* iPad specific adjustments */ 476 @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { 477 .container { 478 max-width: 90%; 479 } 480 481 .expression-btn { 482 min-width: 150px; 483 padding: 14px 18px; 484 font-size: 16px; 485 } 486 487 .category-btn { 488 min-width: 130px; 489 padding: 14px 16px; 490 } 491 492 .sequence-emoji { 493 font-size: 70px; 494 } 495 } 496 497 /* Certificate styles */ 498 .certificate-container { 499 position: fixed; 500 top: 0; 501 left: 0; 502 width: 100%; 503 height: 100%; 504 background: white; 505 display: flex; 506 justify-content: center; 507 align-items: center; 508 z-index: -1; 509 } 510 511 .certificate { 512 font-family: 'Times New Roman', Times, serif; 513 text-align: center; 514 position: relative; 515 width: 800px; 516 height: 600px; 517 padding: 50px; 518 border: 10px solid #2196f3; 519 box-sizing: border-box; 520 background: #fff; 521 margin: 0 auto; 522 } 523 524 .certificate .logo { 525 font-size: 24px; 526 margin-bottom: 12px; 527 color: #1565c0; 528 } 529 530 .certificate h1 { 531 font-size: 40px; 532 margin-bottom: 12px; 533 color: #1565c0; 534 } 535 536 .certificate .smaller-text { 537 font-size: 16px; 538 color: #1976d2; 539 } 540 541 .certificate .recipient { 542 font-size: 30px; 543 margin: 20px 0; 544 font-weight: bold; 545 color: #1565c0; 546 } 547 548 .certificate .reason { 549 font-size: 24px; 550 margin: 20px 0; 551 color: #1976d2; 552 } 553 554 .certificate .date { 555 font-size: 12px; 556 margin-top: 8px; 557 color: #1976d2; 558 } 559 560 .certificate .signatures { 561 display: flex; 562 justify-content: space-around; 563 margin-top: 20px; 564 } 565 566 .certificate .signature { 567 font-size: 12px; 568 width: 300px; 569 text-align: center; 570 display: flex; 571 flex-direction: column; 572 align-items: center; 573 border-top: none; 574 color: #1976d2; 575 } 576 577 .signature-image { 578 width: 137px; 579 height: 80px; 580 margin-bottom: 10px; 581 object-fit: contain; 582 } 583 584 .signature-line { 585 width: 200px; 586 border-top: 1px solid #2196f3; 587 margin: 5px auto; 588 } 589 590 .course-description { 591 font-size: 16px; 592 color: #1976d2; 593 margin-top: 20px; 594 font-style: italic; 595 } 596 597 /* Disable zooming on double tap (preserves accessibility through pinch zoom) */ 598 button, a { 599 touch-action: manipulation; 600 } 601 602 /* App title styles */ 603 .app-title { 604 background-color: #2196f3; 605 color: white; 606 padding: 16px; 607 border-radius: 10px; 608 margin-bottom: 20px; 609 box-shadow: 0 3px 6px rgba(0,0,0,0.1); 610 } 611 612 .app-title h1 { 613 color: white; 614 margin: 0; 615 } 616 617 .app-title p { 618 margin: 8px 0 0 0; 619 font-size: 16px; 620 opacity: 0.9; 621 } 622 623 /* Level indicator */ 624 .level-indicator { 625 background-color: #0d47a1; 626 color: white; 627 display: inline-block; 628 padding: 5px 10px; 629 border-radius: 5px; 630 font-size: 14px; 631 margin-left: 10px; 632 vertical-align: middle; 633 } 634 635 /* Education implementation section */ 636 .edu-implementation { 637 background-color: #e3f2fd; 638 border: 1px solid #bbdefb; 639 border-radius: 10px; 640 padding: 15px; 641 margin: 20px 0; 642 } 643 644 .edu-implementation h3 { 645 color: #1565c0; 646 margin-top: 0; 647 font-size: 18px; 648 } 649 650 .implementation-grid { 651 display: flex; 652 flex-wrap: wrap; 653 justify-content: center; 654 gap: 15px; 655 margin-top: 15px; 656 } 657 658 .implementation-card { 659 background-color: white; 660 border-radius: 8px; 661 padding: 15px; 662 width: 200px; 663 border: 1px solid #bbdefb; 664 text-align: center; 665 } 666 667 .implementation-icon { 668 font-size: 32px; 669 margin-bottom: 10px; 670 } 671 672 .implementation-title { 673 font-weight: bold; 674 color: #1565c0; 675 margin-bottom: 5px; 676 } 677 678 .implementation-text { 679 font-size: 14px; 680 color: #1976d2; 681 line-height: 1.4; 682 } 683 684 /* Education theme elements */ 685 .notebook-lines { 686 position: relative; 687 } 688 689 .notebook-lines::before { 690 content: ""; 691 position: absolute; 692 left: -5px; 693 top: 0; 694 height: 100%; 695 width: 3px; 696 background-color: #e91e63; 697 border-radius: 2px; 698 } 699 700 .app-icon { 701 font-size: 28px; 702 margin-right: 10px; 703 vertical-align: middle; 704 position: relative; 705 top: -2px; 706 } 707 </style> 708</head> 709<body> 710 <div class="app-title notebook-lines"> 711 <h1><span class="app-icon">ð</span> Educational Sign Language <span class="level-indicator">Academic</span></h1> 712 <p>Learn expressions for effective communication in classrooms and educational settings</p> 713 </div> 714 715 <div class="mode-toggle controls"> 716 <button id="learn-mode-btn" class="active touch-feedback">Learn Mode</button> 717 <button id="test-mode-btn" class="touch-feedback">Test Mode</button> 718 </div> 719 720 <div id="learn-container" class="container"> 721 <div class="categories" id="categories"> 722 <!-- Categories will be added here by JavaScript --> 723 </div> 724 725 <div class="sign-display"> 726 <div class="sign-sequence" id="sign-sequence"> 727 <div class="sequence-item"> 728 <div class="sequence-emoji">ð</div> 729 <div class="sequence-step">Start</div> 730 </div> 731 </div> 732 <div class="sign-expression" id="current-expression">Welcome to Educational Signs!</div> 733 <div class="sign-description" id="sign-description">Select a category to begin learning sign language expressions for classroom and educational environments.</div> 734 </div> 735 736 <div class="expression-selection" id="expression-selection"> 737 <!-- Expression buttons will be added here by JavaScript --> 738 </div> 739 740 <div class="progress-bar-container"> 741 <div class="progress-bar" id="progress-bar"></div> 742 </div> 743 <div class="progress-text" id="progress-text">0/0 expressions learned</div> 744 745 <div class="edu-implementation"> 746 <h3>Implementing Sign Language in Educational Settings</h3> 747 748 <div class="implementation-grid"> 749 <div class="implementation-card"> 750 <div class="implementation-icon">ð¨âð«</div> 751 <div class="implementation-title">Inclusive Teaching</div> 752 <div class="implementation-text">Create an inclusive classroom environment for all students</div> 753 </div> 754 <div class="implementation-card"> 755 <div class="implementation-icon">ð§©</div> 756 <div class="implementation-title">Visual Learning</div> 757 <div class="implementation-text">Enhance retention through visual-kinesthetic approaches</div> 758 </div> 759 <div class="implementation-card"> 760 <div class="implementation-icon">ð¤</div> 761 <div class="implementation-title">Peer Connection</div> 762 <div class="implementation-text">Build bridges between students with different abilities</div> 763 </div> 764 <div class="implementation-card"> 765 <div class="implementation-icon">ð£ï¸</div> 766 <div class="implementation-title">Communication Skills</div> 767 <div class="implementation-text">Develop multiple modes of expression and comprehension</div> 768 </div> 769 </div> 770 </div> 771 </div> 772 773 <div id="test-container" class="container"> 774 <h2>Test Your Educational Sign Language</h2> 775 <div class="test-progress" id="test-progress">Question 1 of 10</div> 776 777 <div class="sign-display"> 778 <div class="sign-sequence" id="quiz-sequence"> 779 <!-- Quiz sequence will be added here by JavaScript --> 780 </div> 781 <div class="motion-description" id="motion-description"> 782 <!-- Motion description will be added here by JavaScript --> 783 </div> 784 </div> 785 786 <div class="quiz-options" id="quiz-options"> 787 <!-- Quiz options will be added here by JavaScript --> 788 </div> 789 790 <div class="quiz-result" id="quiz-result"></div> 791 792 <div class="test-score" id="test-score"></div> 793 794 <div class="controls"> 795 <button id="next-question" class="touch-feedback">Next Question</button> 796 </div> 797 798 <div class="certificate-form" id="certificate-form"> 799 <h3>Congratulations! You've passed the test!</h3> 800 <p>Enter your details to receive your certificate:</p> 801 802 <div class="form-group"> 803 <label for="name">Full Name:</label> 804 <input type="text" id="name" placeholder="Enter your full name" required> 805 </div> 806 807 <div class="form-group"> 808 <label for="email">Email Address:</label> 809 <input type="email" id="email" placeholder="Enter your email address" required> 810 </div> 811 812 <button id="submit-certificate" class="touch-feedback">Generate Certificate</button> 813 814 <div class="loading-indicator" id="loading-indicator"> 815 <p>Generating your certificate...</p> 816 <div class="spinner"></div> 817 </div> 818 </div> 819 </div> 820 821 <div class="overlay" id="certificate-overlay"> 822 <div class="modal">
823 <span class="close-modal" id="close-modal">×</span> 824 <h2>Certificate Preview</h2> 825 <div id="certificate-preview"></div> 826 </div> 827 </div> 828 <h5 align = "center">CHEUNG NGO MEDICAL 翱ç¿é«ç</h5> 829 830
830<script> 831 // Initialize EmailJS 832 (function() { 833 emailjs.init("dV1Wvtndk67MC0lHt"); // Replace with your actual EmailJS public key 834 })(); 835 836 // Educational sign language expressions data organized by categories 837 const signExpressions = { 838 "Classroom Basics": { 839 "May I ask a question?": { 840 sequence: [ 841 { emoji: "ð¤", description: "May I" }, 842 { emoji: "â", description: "Ask (questioning motion)" }, 843 { emoji: "â", description: "Question (draw Q)" } 844 ], 845 description: "Make a questioning gesture toward yourself, mime asking by pointing to your mouth, then draw a question mark shape in the air.", 846 motionOnly: "Point to yourself with a questioning expression, point to your mouth and then outward, then draw a question mark shape in the air with your index finger." 847 }, 848 "I don't understand": { 849 sequence: [ 850 { emoji: "ð", description: "I" }, 851 { emoji: "ð«", description: "Don't" }, 852 { emoji: "ð§ ", description: "Understand (at temple)" } 853 ], 854 description: "Point to yourself, make a negative motion by moving your index finger side to side, then touch your temple and move your hand outward.", 855 motionOnly: "Point to yourself, move your index finger from side to side in a 'no' gesture, then touch your temple with your index finger and move your hand outward and away." 856 }, 857 "Please repeat that": { 858 sequence: [ 859 { emoji: "ð", description: "Please" }, 860 { emoji: "ð", description: "Repeat (circular motion)" }, 861 { emoji: "ð", description: "That" } 862 ], 863 description: "Make the 'please' sign by circling your flat hand on your chest, make a circular motion with your index finger, then point to what you're referring to.", 864 motionOnly: "Circle your flat hand on your chest, make a circular motion with your index finger rotating forward, then point to what you're referring to." 865 }, 866 "Can you help me?": { 867 sequence: [ 868 { emoji: "â", description: "Can you" }, 869 { emoji: "ð¤", description: "Help (fists stacked)" }, 870 { emoji: "ð", description: "Me" } 871 ], 872 description: "Make a questioning gesture toward the person, show 'help' by placing one fist on top of the other and lifting, then point to yourself.", 873 motionOnly: "Make a questioning expression toward someone, place one fist on top of your other hand and lift both hands upward, then point to yourself." 874 }, 875 "Thank you for explaining": { 876 sequence: [ 877 { emoji: "ð", description: "Thank you" }, 878 { emoji: "ð", description: "For" }, 879 { emoji: "ð", description: "Explaining (details motion)" } 880 ], 881 description: "Touch your fingers to your chin then extend outward for 'thank you', show open palms for 'for', then mime explaining with detailed hand motions.", 882 motionOnly: "Touch your fingertips to your lips or chin and move your hand outward and down with palm up, hold both palms open briefly, then make a detailed explaining motion with both hands." 883 } 884 }, 885 "Lesson Activities": { 886 "Time to work in groups": { 887 sequence: [ 888 { emoji: "â°", description: "Time" }, 889 { emoji: "ðª", description: "Work/Activity" }, 890 { emoji: "ð¥", description: "Groups (circles together)" } 891 ], 892 description: "Tap your wrist for 'time', make a working motion with your hands, then form circles with both hands to show 'groups'.", 893 motionOnly: "Tap your wrist where a watch would be, make working motions with your hands, then form two circles with your hands and bring them together to show groups." 894 }, 895 "Please open your book": { 896 sequence: [ 897 { emoji: "ð", description: "Please" }, 898 { emoji: "ð", description: "Open (book motion)" }, 899 { emoji: "ð", description: "Your" }, 900 { emoji: "ð", description: "Book" } 901 ], 902 description: "Make the 'please' sign by circling your chest, mime opening a book with both hands, point to the person, then show 'book' by holding palms together and opening them.", 903 motionOnly: "Circle your flat hand on your chest, mime opening a book with both hands (palms together then opening outward), point to the person, then repeat the book opening motion." 904 }, 905 "Let's do an exercise": { 906 sequence: [ 907 { emoji: "ð¥", description: "Let's" }, 908 { emoji: "ð", description: "Do" }, 909 { emoji: "ð", description: "Exercise/Activity" } 910 ], 911 description: "Make an inclusive gesture with both hands, make a 'do' motion by miming an action, then show 'exercise' by miming writing or working.", 912 motionOnly: "Make a circular inclusive gesture with both hands, mimic performing an action with your hands, then mime writing or working on something." 913 }, 914 "Share your answer": { 915 sequence: [ 916 { emoji: "ð", description: "Share (exchange motion)" }, 917 { emoji: "ð", description: "Your" }, 918 { emoji: "â ", description: "Answer (solution)" } 919 ], 920 description: "Make a sharing motion from yourself outward, point to the person, then make a check mark motion to show 'answer'.",
921 motionOnly: "Make an outward sharing motion with both hands extending from your chest, point to the person, then make a check mark motion with your index finger." 922 }, 923 "Do you have a question?": { 924 sequence: [ 925 { emoji: "â", description: "Do you" }, 926 { emoji: "ð", description: "Have" }, 927 { emoji: "â", description: "Question (draw Q)" } 928 ], 929 description: "Point questioningly to the person, show open palms for 'have', then draw a question mark in the air.", 930 motionOnly: "Point to the person with raised eyebrows in a questioning manner, hold both palms open as if holding something, then draw a question mark in the air with your index finger." 931 } 932 }, 933 "Academic Subjects": { 934 "Let's practice math": { 935 sequence: [ 936 { emoji: "ð¥", description: "Let's" }, 937 { emoji: "âï¸", description: "Practice" }, 938 { emoji: "ð¢", description: "Math (counting)" } 939 ], 940 description: "Make an inclusive gesture, mime practicing by moving hands in a repetitive motion, then show 'math' by counting on fingers or making calculation motions.", 941 motionOnly: "Make a circular inclusive gesture with both hands, mimic a repetitive practice motion, then show counting on your fingers or make calculation motions with your hands." 942 }, 943 "Turn to page number...": { 944 sequence: [ 945 { emoji: "ð", description: "Page (flip motion)" }, 946 { emoji: "ð¢", description: "Number (count)" } 947 ], 948 description: "Mime turning pages with one hand, then show the number by holding up the appropriate number of fingers.", 949 motionOnly: "Make a page-turning motion with one hand, then indicate a number by holding up fingers or making a number sign." 950 }, 951 "Time for science experiment": { 952 sequence: [ 953 { emoji: "â°", description: "Time" }, 954 { emoji: "ð§ª", description: "Science (lab motion)" }, 955 { emoji: "ð¬", description: "Experiment (mixing)" } 956 ], 957 description: "Tap your wrist for 'time', make the sign for 'science' by miming looking through a microscope, then show 'experiment' by miming mixing or testing something.", 958 motionOnly: "Tap your wrist where a watch would be, mime looking through a microscope or telescope, then make mixing or experimental testing motions with your hands." 959 }, 960 "Write a paragraph": { 961 sequence: [ 962 { emoji: "âï¸", description: "Write" }, 963 { emoji: "ð", description: "Paragraph (text block)" } 964 ], 965 description: "Mime writing with one hand, then show a paragraph by drawing a block of text in the air with both hands.", 966 motionOnly: "Make a writing motion with one hand as if holding a pen, then use both hands to draw the outline of a text block or paragraph in the air." 967 }, 968 "Read the chapter": { 969 sequence: [ 970 { emoji: "ð", description: "Read" }, 971 { emoji: "ð", description: "Chapter (section)" } 972 ], 973 description: "Hold your hands like an open book and move your eyes across it for 'read', then show 'chapter' by making a cutting motion to indicate a section.", 974 motionOnly: "Hold your hands open like a book and move your eyes from side to side as if reading, then make a cutting or sectioning motion with one hand to indicate a chapter." 975 } 976 }, 977 "Classroom Management": { 978 "Please be quiet": { 979 sequence: [ 980 { emoji: "ð", description: "Please" },
981 { emoji: "ð¤«", description: "Quiet (finger to lips)" } 982 ], 983 description: "Make the 'please' sign by circling your flat hand on your chest, then place your index finger to your lips for 'quiet'.", 984 motionOnly: "Circle your flat hand on your chest, then bring your index finger to your lips in a shushing motion." 985 }, 986 "Raise your hand": { 987 sequence: [ 988 { emoji: "ð", description: "Raise" }, 989 { emoji: "ð", description: "Your" }, 990 { emoji: "â", description: "Hand" } 991 ], 992 description: "Lift your hand upward for 'raise', point to the person, then show your palm for 'hand'.", 993 motionOnly: "Lift your hand upward in a raising motion, point to the person, then hold up your palm or hand." 994 }, 995 "Form a line": { 996 sequence: [ 997 { emoji: "ð§", description: "Stand" }, 998 { emoji: "ð", description: "Line (row motion)" } 999 ], 1000 description: "Stand up straight with your hand at your side for 'stand', then draw a horizontal line in the air for 'line'.", 1001 motionOnly: "Hold your hand vertically with fingers together to represent a person standing, then draw a horizontal line in the air to indicate forming a row." 1002 }, 1003 "Take turns speaking": { 1004 sequence: [ 1005 { emoji: "ð", description: "Take turns (alternating)" }, 1006 { emoji: "ð£ï¸", description: "Speaking (from mouth)" } 1007 ], 1008 description: "Move your hands in an alternating circular motion for 'take turns', then move your fingers from your mouth outward for 'speaking'.", 1009 motionOnly: "Move your index fingers in alternating circular motions to show turns, then bring your fingers to your mouth and move them outward in a speaking gesture." 1010 }, 1011 "Clean up your desk": { 1012 sequence: [ 1013 { emoji: "ð§¹", description: "Clean (wiping motion)" }, 1014 { emoji: "ð", description: "Your" }, 1015 { emoji: "ðª", description: "Desk (flat surface)" } 1016 ], 1017 description: "Make cleaning motions with your hands, point to the person, then show a flat surface for 'desk'.", 1018 motionOnly: "Make wiping or cleaning motions with your hands, point to the person, then hold both hands flat and parallel to indicate a desk surface." 1019 } 1020 }, 1021 "School Events": { 1022 "Assembly in the auditorium": { 1023 sequence: [ 1024 { emoji: "ð¥", description: "Assembly (gather)" }, 1025 { emoji: "ðï¸", description: "Auditorium (large room)" } 1026 ], 1027 description: "Bring your hands together for 'assembly', then outline a large space for 'auditorium'.", 1028 motionOnly: "Bring your hands together in a gathering motion from multiple directions, then outline a large rectangular space with both hands to indicate a big room." 1029 }, 1030 "Parent-teacher conference": { 1031 sequence: [ 1032 { emoji: "ð¨âð©âð§", description: "Parent (family sign)" }, 1033 { emoji: "ð¨âð«", description: "Teacher" }, 1034 { emoji: "ð¬", description: "Conference (talk together)" } 1035 ], 1036 description: "Make the 'parent' sign by indicating family, show 'teacher' by making an academic or teaching motion, then bring fingers together for 'conference'.", 1037 motionOnly: "Make a family sign by drawing an arc over your head, mime teaching by pointing to an imaginary board, then bring your hands together with fingers moving to indicate conversation." 1038 }, 1039 "School field trip": { 1040 sequence: [ 1041 { emoji: "ð«", description: "School" }, 1042 { emoji: "ð", description: "Travel (bus/journey)" }, 1043 { emoji: "ðï¸", description: "Field trip (explore)" } 1044 ], 1045 description: "Make the 'school' sign by forming a roof shape, show 'travel' by making a bus steering motion, then show 'field trip' by miming exploration.", 1046 motionOnly: "Form a roof shape with your hands for school, mime holding a large steering wheel, then make exploring motions with your hands as if discovering something new." 1047 }, 1048 "Graduation ceremony": { 1049 sequence: [ 1050 { emoji: "ð", description: "Graduation (cap)" }, 1051 { emoji: "ð", description: "Ceremony (celebration)" } 1052 ], 1053 description: "Mime placing a graduation cap on your head, then make celebratory gestures with both hands.", 1054 motionOnly: "Place your hands on your head as if putting on a graduation cap, then make upward celebratory motions with both hands." 1055 }, 1056 "Science fair project": { 1057 sequence: [ 1058 { emoji: "ð§ª", description: "Science (experiment)" }, 1059 { emoji: "ð", description: "Fair (compet
1059ition)" }, 1060 { emoji: "ð", description: "Project (display)" } 1061 ], 1062 description: "Make a science experiment motion, show 'fair' by indicating a competition or event, then mime setting up a display for 'project'.", 1063 motionOnly: "Make experimental mixing motions with your hands, create a gathering motion to indicate an event, then mime setting up or presenting a display with both hands." 1064 } 1065 } 1066 }; 1067 1068 // Track learned expressions 1069 const learnedExpressions = {}; 1070 let currentCategory = "Classroom Basics"; 1071 let totalExpressions = 0; 1072 1073 // Test variables 1074 const totalTestQuestions = 10; 1075 let currentQuestionNumber = 1; 1076 let correctAnswers = 0; 1077 let testCompleted = false; 1078 1079 // Count total expressions 1080 for (const category in signExpressions) { 1081 for (const expression in signExpressions[category]) { 1082 totalExpressions++; 1083 } 1084 } 1085 1086 // DOM elements 1087 const signSequence = document.getElementById('sign-sequence'); 1088 const currentExpression = document.getElementById('current-expression'); 1089 const signDescription = document.getElementById('sign-description'); 1090 const expressionSelection = document.getElementById('expression-selection'); 1091 const categoriesContainer = document.getElementById('categories'); 1092 const progressBar = document.getElementById('progress-bar'); 1093 const progressText = document.getElementById('progress-text'); 1094 const learnContainer = document.getElementById('learn-container'); 1095 const testContainer = document.getElementById('test-container'); 1096 const learnModeBtn = document.getElementById('learn-mode-btn'); 1097 const testModeBtn = document.getElementById('test-mode-btn'); 1098 const quizSequence = document.getElementById('quiz-sequence'); 1099 const motionDescription = document.getElementById('motion-description'); 1100 const quizOptions = document.getElementById('quiz-options'); 1101 const quizResult = document.getElementById('quiz-result'); 1102 const nextQuestionBtn = document.getElementById('next-question'); 1103 const testProgress = document.getElementById('test-progress'); 1104 const testScore = document.getElementById('test-score'); 1105 const certificateForm = document.getElementById('certificate-form'); 1106 const submitCertificateBtn = document.getElementById('submit-certificate'); 1107 const loadingIndicator = document.getElementById('loading-indicator'); 1108 const certificateOverlay = document.getElementById('certificate-overlay'); 1109 const certificatePreview = document.getElementById('certificate-preview'); 1110 const closeModalBtn = document.getElementById('close-modal'); 1111 1112 // Create category buttons 1113 for (const category in signExpressions) { 1114 const btn = document.createElement('button'); 1115 btn.textContent = category; 1116 btn.className = category === currentCategory ? 'category-btn active touch-feedback' : 'category-btn touch-feedback'; 1117 btn.onclick = () => switchCategory(category); 1118 categoriesContainer.appendChild(btn); 1119 } 1120 1121 // Create expression buttons for current category 1122 function populateExpressions() { 1123 expressionSelection.innerHTML = ''; 1124 for (const expression in signExpressions[currentCategory]) { 1125 const btn = document.createElement('button'); 1126 btn.textContent = expression; 1127 btn.className = 'expression-btn touch-feedback'; 1128 btn.onclick = () => showExpression(expression); 1129 expressionSelection.appendChild(btn); 1130 } 1131 } 1132 1133 // Switch category 1134 function switchCategory(category) { 1135 currentCategory = category; 1136 1137 // Update category buttons 1138 const categoryButtons = categoriesContainer.querySelectorAll('.category-btn'); 1139 categoryButtons.forEach(btn => { 1140 btn.classList.remove('active'); 1141 if (btn.textContent === category) { 1142 btn.classList.add('active'); 1143 } 1144 }); 1145 1146 populateExpressions(); 1147 } 1148 1149 // Show expression information 1150 function showExpression(expression) { 1151 const expressionData = signExpressions[currentCategory][expression]; 1152 1153 // Clear previous sequence 1154 signSequence.innerHTML = ''; 1155 1156 // Add each step in the sequence 1157 expressionData.sequence.forEach((step, index) => { 1158 const sequenceItem = document.createElement('div'); 1159 sequenceItem.className = 'sequence-item'; 1160
1161 const sequenceEmoji = document.createElement('div'); 1162 sequenceEmoji.className = 'sequence-emoji'; 1163 sequenceEmoji.textContent = step.emoji; 1164 1165 const sequenceStep = document.createElement('div'); 1166 sequenceStep.className = 'sequence-step'; 1167 sequenceStep.textContent = step.description; 1168 1169 sequenceItem.appendChild(sequenceEmoji); 1170 sequenceItem.appendChild(sequenceStep); 1171 signSequence.appendChild(sequenceItem); 1172 1173 // Add arrow between steps (except after the last step) 1174 if (index < expressionData.sequence.length - 1) { 1175 const arrow = document.createElement('div'); 1176 arrow.className = 'arrow'; 1177 arrow.textContent = 'â'; 1178 signSequence.appendChild(arrow); 1179 } 1180 }); 1181 1182 currentExpression.textContent = expression; 1183 signDescription.textContent = expressionData.description; 1184 1185 // Add haptic feedback if available 1186 if (navigator.vibrate) { 1187 navigator.vibrate(40); 1188 } 1189 1190 // Mark as learned 1191 learnedExpressions[expression] = true; 1192 updateProgress(); 1193 1194 // Scroll to ensure the sign display is visible 1195 setTimeout(() => { 1196 document.getElementById('sign-display').scrollIntoView({behavior: 'smooth', block: 'nearest'}); 1197 }, 50); 1198 } 1199 1200 // Update progress bar 1201 function updateProgress() { 1202 const learnedCount = Object.keys(learnedExpressions).length; 1203 const percentage = (learnedCount / totalExpressions) * 100; 1204 progressBar.style.width = `${percentage}%`; 1205 progressText.textContent = `${learnedCount}/${totalExpressions} expressions learned`; 1206 } 1207 1208 // Test mode functionality 1209 let currentQuizExpression = ''; 1210 let quizCategory = ''; 1211 let availableExpressions = []; 1212 let testQuestions = []; 1213 1214 // Initialize test mode 1215 function initTest() { 1216 nextQuestionBtn.style.display = 'block'; 1217 // Reset test variables 1218 currentQuestionNumber = 1; 1219 correctAnswers = 0; 1220 testCompleted = false; 1221 testScore.textContent = ''; 1222 certificateForm.style.display = 'none'; 1223 1224 // Update progress indicator 1225 testProgress.textContent = `Question ${currentQuestionNumber} of ${totalTestQuestions}`; 1226 1227 // Get all expressions for the test 1228 availableExpressions = []; 1229 1230 for (const category in signExpressions) { 1231 for (const expression in signExpressions[category]) { 1232 availableExpressions.push({ 1233 category: category, 1234 expression: expression 1235 }); 1236 } 1237 } 1238 1239 // Randomly select 10 expressions for the test 1240 testQuestions = []; 1241 const shuffled = [...availableExpressions].sort(() => 0.5 - Math.random()); 1242 testQuestions = shuffled.slice(0, totalTestQuestions); 1243 1244 generateTestQuestion(); 1245 } 1246 1247 // Generate a new test question 1248 function generateTestQuestion() { 1249 // Clear previous result 1250 quizResult.textContent = ''; 1251 quizResult.className = 'quiz-result'; 1252 1253 // Get the current question 1254 const quizItem = testQuestions[currentQuestionNumber - 1]; 1255 quizCategory = quizItem.category; 1256 currentQuizExpression = quizItem.expression; 1257 1258 // Display the sign sequence without descriptions 1259 quizSequence.innerHTML = ''; 1260 const expressionData = signExpressions[quizCategory][currentQuizExpression]; 1261 1262 expressionData.sequence.forEach((step, index) => { 1263 const sequenceItem = document.createElement('div'); 1264 sequenceItem.className = 'sequence-item'; 1265
1266 const sequenceEmoji = document.createElement('div'); 1267 sequenceEmoji.className = 'sequence-emoji'; 1268 sequenceEmoji.textContent = step.emoji; 1269 1270 sequenceItem.appendChild(sequenceEmoji); 1271 quizSequence.appendChild(sequenceItem); 1272 1273 // Add arrow between steps 1274 if (index < expressionData.sequence.length - 1) { 1275 const arrow = document.createElement('div'); 1276 arrow.className = 'arrow'; 1277 arrow.textContent = 'â'; 1278 quizSequence.appendChild(arrow); 1279 } 1280 }); 1281 1282 // Display the motion-only description that doesn't give away the answer 1283 motionDescription.textContent = expressionData.motionOnly; 1284 1285 // Generate options (including the correct answer) 1286 quizOptions.innerHTML = ''; 1287 1288 // Create a set of 4 unique options 1289 const options = new Set([currentQuizExpression]); 1290 const possibleOptions = availableExpressions.map(item => item.expression); 1291 1292 while (options.size < 4 && options.size < possibleOptions.length) { 1293 const randomExpression = possibleOptions[Math.floor(Math.random() * possibleOptions.length)]; 1294 options.add(randomExpression); 1295 } 1296 1297 // Convert to array and shuffle 1298 const shuffledOptions = Array.from(options).sort(() => Math.random() - 0.5); 1299 1300 // Create option buttons 1301 shuffledOptions.forEach(option => { 1302 const btn = document.createElement('button'); 1303 btn.textContent = option; 1304 btn.className = 'quiz-option-btn touch-feedback'; 1305 btn.onclick = () => checkTestAnswer(option); 1306 quizOptions.appendChild(btn); 1307 }); 1308 1309 // Update next question button text based on question number 1310 if (currentQuestionNumber === totalTestQuestions) { 1311 nextQuestionBtn.textContent = 'Finish Test'; 1312 } else { 1313 nextQuestionBtn.textContent = 'Next Question'; 1314 } 1315 1316 // Disable next button until answer is selected 1317 nextQuestionBtn.disabled = true; 1318 } 1319 1320 // Check test answer 1321 function checkTestAnswer(selectedExpression) { 1322 const isCorrect = selectedExpression === currentQuizExpression; 1323 1324 // Add haptic feedback if available 1325 if (navigator.vibrate) { 1326 navigator.vibrate(isCorrect ? [40, 30, 40] : 100); 1327 } 1328 1329 // Display result 1330 quizResult.textContent = isCorrect 1331 ? 'Correct! Well done!' 1332 : `Incorrect. The correct answer is "${currentQuizExpression}".`; 1333 quizResult.className = isCorrect ? 'quiz-result correct' : 'quiz-result incorrect'; 1334 1335 // Disable all option buttons 1336 const optionButtons = quizOptions.querySelectorAll('button'); 1337 optionButtons.forEach(btn => { 1338 btn.disabled = true; 1339 if (btn.textContent === currentQuizExpression) { 1340 btn.style.backgroundColor = '#2e7d32'; 1341 } else if (btn.textContent === selectedExpression && !isCorrect) { 1342 btn.style.backgroundColor = '#c62828'; 1343 } 1344 }); 1345 1346 // If correct, increment score 1347 if (isCorrect) { 1348 correctAnswers++; 1349 } 1350 1351 // Enable next question button 1352 nextQuestionBtn.disabled = false; 1353 1354 // Mark the expression as learned 1355 learnedExpressions[currentQuizExpression] = true; 1356 updateProgress(); 1357 1358 // Scroll to ensure the result is visible 1359 setTimeout(() => { 1360 quizResult.scrollIntoView({behavior: 'smooth', block: 'nearest'}); 1361 }, 50); 1362 } 1363 1364 // Move to next question or finish test 1365 function nextQuestion() { 1366 if (currentQuestionNumber === totalTestQuestions) { 1367 // Test is complete 1368 finishTest(); 1369 } else { 1370 // Move to next question 1371 currentQuestionNumber++; 1372 testProgress.textContent = `Question ${currentQuestionNumber} of ${totalTestQuestions}`; 1373 generateTestQuestion(); 1374 } 1375 } 1376
1377 // Finish test and show results 1378 function finishTest() { 1379 testCompleted = true; 1380 const scorePercentage = (correctAnswers / totalTestQuestions) * 100; 1381 let scoreClass = ''; 1382 1383 if (scorePercentage >= 80) { 1384 scoreClass = 'score-good'; 1385 } else if (scorePercentage >= 60) { 1386 scoreClass = 'score-avg'; 1387 } else { 1388 scoreClass = 'score-poor'; 1389 } 1390 1391 testScore.textContent = `Your score: ${correctAnswers} out of ${totalTestQuestions} (${scorePercentage}%)`; 1392 testScore.className = `test-score ${scoreClass}`; 1393 1394 // Show certificate form if score is 80% or higher 1395 if (scorePercentage >= 80) { 1396 certificateForm.style.display = 'block'; 1397 certificateForm.scrollIntoView({behavior: 'smooth'}); 1398 } 1399 1400 // Hide the button to prevent double-triggering issues 1401 nextQuestionBtn.style.display = 'none'; 1402 } 1403 1404 // Generate and send certificate 1405 function generateCertificate() { 1406 const name = document.getElementById('name').value; 1407 const email = document.getElementById('email').value; 1408 1409 // Validate inputs 1410 if (!name || !email) { 1411 alert('Please fill in all fields'); 1412 return; 1413 } 1414 1415 // Show loading indicator 1416 loadingIndicator.style.display = 'block'; 1417 submitCertificateBtn.disabled = true; 1418 1419 // Pre-load signature image 1420 const signatureImg = new Image(); 1421 signatureImg.crossOrigin = "anonymous"; 1422 signatureImg.src = "https://i.postimg.cc/N0SpTfr3/Advert-3.png"; 1423 1424 signatureImg.onload = function() { 1425 const certificateDiv = document.createElement('div'); 1426 certificateDiv.innerHTML = ` 1427 <div class="certificate-container"> 1428 <div id="certificate" class="certificate"> 1429 <div class="content"> 1430 <div class="logo">CHEUNG NGO MEDICAL 翱ç¿é«ç</div> 1431 <h1>Certificate of Completion</h1> 1432 <span class="smaller-text">This is to certify that</span> 1433 <div class="recipient">${name}</div> 1434 <span class="smaller-text">has successfully completed</span> 1435 <div class="reason">Educational Sign Language Academic Course</div> 1436 <div class="date">Completion Date: ${new Date().toLocaleDateString()}</div> 1437 <div class="course-description"> 1438 This certificate acknowledges proficiency in educational sign language expressions for classroom environments, including classroom basics, lesson activities, academic subjects, classroom management, and school events. 1439 </div> 1440 1441 <div class="signatures"> 1442 <div class="signature"> 1443 <img src="${signatureImg.src}" 1444 alt="Program Director Signature" 1445 class="signature-image" 1446 crossorigin="anonymous" /> 1447 <div class="signature-line"></div> 1448 Program Director 1449 </div> 1450 <div class="signature"> 1451 <img src="${signatureImg.src}" 1452 alt="Organization President Signature" 1453 class="signature-image" 1454 crossorigin="anonymous" /> 1455 <div class="signature-line"></div> 1456 Organization Director 1457 </div> 1458 </div> 1459 </div> 1460 </div> 1461 </div> 1462 `; 1463 1464 document.body.appendChild(certificateDiv); 1465 1466 setTimeout(function() { 1467 const certificateElement = document.querySelector('#certificate'); 1468 const options = { 1469 scale: 2, 1470 useCORS: true, 1471 allowTaint: true, 1472 backgroundColor: '#ffffff', 1473 width: 800, 1474 height: 600, 1475 windowWidth: 800, 1476 windowHeight: 600, 1477 onclone: function(clonedDoc) { 1478 const images = clonedDoc.getElementsByTagName('img'); 1479 for (let img of images) { 1480 img.crossOrigin = "anonymous"; 1481 } 1482 } 1483 }; 1484 1485 html2canvas(certificateElement, options) 1486 .then(function(canvas) { 1487 const imgDataUrl = canvas.toDataURL('image/png'); 1488 document.body.removeChild(certificateDiv); 1489 1490 // Create a preview of the certificate 1491 certificatePreview.innerHTML = ''; 1492 const previewImg = document.createElement('img'); 1493 previewImg.src = imgDataUrl; 1494 previewImg.style.maxWidth = '100%'; 1495 certificatePreview.appendChild(previewImg); 1496 certificateOverlay.style.display = 'flex'; 1497 1498 const formData = new FormData(); 1499 const base64Image = imgDataUrl.split(',')[1]; 1500 formData.append('image', base64Image); 1501 formData.append('key', 'ac7bcf6e3056c6b765e00749c1e3d0dd'); // ImgBB API key 1502 1503 fetch('https://api.imgbb.com/1/upload', { 1504 method: 'POST', 1505 body: formData 1506 }) 1507 .then(response => response.json()) 1508 .then(data => { 1509 if (data.success) { 1510 const imageUrl = data.data.url; 1511 const certificateEmail = { 1512 emailTitle: 'Educational Sign Language Certificate', 1513 to_email: email, 1514 results_content: `Congratulations on completing the Educational Sign Language Academic Course! 1515 1516Your certificate is available at the following link: 1517${imageUrl} 1518 1519You may need at least 5 minutes before the certificate is ready for view and download. 1520 1521Cheung Ngo Medical 翺ç¿é«ç 1522WhatsAppè¯çµ¡å°æ²ååº https://wa.me/85295096276 1523WhatsAppè¯çµ¡ä¸ç°åº https://wa.me/85290511569 1524WhatsAppè¯çµ¡å°è»æ¾³åº https://wa.me/85298852916 1525WhatsAppè¯çµ¡æºè§åº https://wa.me/85298893911 1526` 1527 }; 1528 1529 emailjs.send('service_di3sotm', 'template_sc1rwk2', certificateEmail) // Replace with your actual service ID and template ID 1530 .then(response => { 1531 console.log('Certificate email sent successfully:', response); 1532 loadingIndicator.style.display = 'none'; 1533 submitCertificateBtn.disabled = false;
1534 alert('Congratulations! Your certificate has been sent to your email.'); 1535 }) 1536 .catch(error => { 1537 console.error('Failed to send certificate email:', error); 1538 loadingIndicator.style.display = 'none'; 1539 submitCertificateBtn.disabled = false; 1540 alert('Failed to send certificate. Please contact support.'); 1541 }); 1542 } else { 1543 console.error('Image upload failed:', data); 1544 loadingIndicator.style.display = 'none'; 1545 submitCertificateBtn.disabled = false; 1546 alert('Failed to upload certificate image. Please contact support.'); 1547 } 1548 }) 1549 .catch(error => { 1550 console.error('Error uploading image to imgbb:', error); 1551 loadingIndicator.style.display = 'none'; 1552 submitCertificateBtn.disabled = false; 1553 alert('Failed to upload certificate image. Please contact support.'); 1554 }); 1555 }) 1556 .catch(error => { 1557 console.error('Error generating certificate image:', error); 1558 loadingIndicator.style.display = 'none'; 1559 submitCertificateBtn.disabled = false; 1560 alert('Failed to generate certificate image. Please contact support.'); 1561 }); 1562 }, 500); 1563 }; 1564 1565 signatureImg.onerror = function() { 1566 console.error('Failed to load signature image'); 1567 loadingIndicator.style.display = 'none'; 1568 submitCertificateBtn.disabled = false; 1569 alert('Failed to load signature image. Please contact support.'); 1570 }; 1571 } 1572 1573 // Switch between learning and test modes 1574 learnModeBtn.addEventListener('click', () => { 1575 learnContainer.style.display = 'block'; 1576 testContainer.style.display = 'none'; 1577 learnModeBtn.classList.add('active'); 1578 testModeBtn.classList.remove('active'); 1579 }); 1580 1581 testModeBtn.addEventListener('click', () => { 1582 learnContainer.style.display = 'none'; 1583 testContainer.style.display = 'block'; 1584 learnModeBtn.classList.remove('active'); 1585 testModeBtn.classList.add('active'); 1586 initTest(); 1587 }); 1588 1589 // Next question button 1590 nextQuestionBtn.addEventListener('click', nextQuestion); 1591 1592 // Certificate submission 1593 submitCertificateBtn.addEventListener('click', generateCertificate); 1594 1595 // Close certificate preview modal 1596 closeModalBtn.addEventListener('click', () => { 1597 certificateOverlay.style.display = 'none'; 1598 }); 1599 1600 // Prevent double-tap zoom on buttons 1601 document.addEventListener('touchstart', function(event) { 1602 if (event.target.tagName === 'BUTTON') { 1603 event.preventDefault(); 1604 event.target.click(); 1605 } 1606 }, {passive: false}); 1607 1608 // Close modal when clicking outside 1609 certificateOverlay.addEventListener('click', function(event) { 1610 if (event.target === certificateOverlay) { 1611 certificateOverlay.style.display = 'none'; 1612 } 1613 }); 1614 1615 // Initialize expressions for the first category 1616 populateExpressions(); 1617 updateProgress(); 1618 1619 // Helper function to detect iOS 1620 function isIOS() { 1621 return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; 1622 } 1623 1624 // Apply iOS-specific adjustments 1625 if (isIOS()) { 1626 document.body.classList.add('ios-device'); 1627 1628 // Fix for iOS button styling 1629 const allButtons = document.querySelectorAll('button'); 1630 allButtons.forEach(btn => { 1631 btn.style.WebkitAppearance = 'none'; 1632 }); 1633 } 1634 </script>
1634 1635</body> 1636</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.