Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Zhuyin Bopomofo Learning App</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap'); | |
| body { | |
| font-family: 'Noto Sans TC', sans-serif; | |
| background-color: #f8f9fa; | |
| } | |
| .zhuyin-char { | |
| font-size: 4rem; | |
| line-height: 1; | |
| color: #3b82f6; | |
| } | |
| .lesson-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.1); | |
| } | |
| .character-card { | |
| transition: all 0.3s ease; | |
| } | |
| .character-card:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); | |
| } | |
| .progress-bar { | |
| height: 8px; | |
| border-radius: 4px; | |
| background-color: #e5e7eb; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| border-radius: 4px; | |
| background-color: #10b981; | |
| transition: width 0.3s ease; | |
| } | |
| .correct-answer { | |
| animation: correctPulse 0.5s; | |
| } | |
| .wrong-answer { | |
| animation: wrongShake 0.5s; | |
| } | |
| @keyframes correctPulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.1); } | |
| 100% { transform: scale(1); } | |
| } | |
| @keyframes wrongShake { | |
| 0%, 100% { transform: translateX(0); } | |
| 20%, 60% { transform: translateX(-5px); } | |
| 40%, 80% { transform: translateX(5px); } | |
| } | |
| .tab-active { | |
| border-bottom: 3px solid #3b82f6; | |
| color: #3b82f6; | |
| font-weight: bold; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-6xl"> | |
| <!-- Header --> | |
| <header class="text-center mb-12"> | |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Zhuyin Bopomofo Learning App</h1> | |
| <p class="text-lg text-gray-600">Master the phonetic system for learning Mandarin Chinese</p> | |
| </header> | |
| <!-- Main Content --> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden"> | |
| <!-- Tabs Navigation --> | |
| <div class="flex border-b border-gray-200"> | |
| <button id="lessons-tab" class="flex-1 py-4 px-6 text-center font-medium text-gray-700 hover:text-blue-500 focus:outline-none tab-active"> | |
| <i class="fas fa-book-open mr-2"></i> Lessons | |
| </button> | |
| <button id="practice-tab" class="flex-1 py-4 px-6 text-center font-medium text-gray-700 hover:text-blue-500 focus:outline-none"> | |
| <i class="fas fa-pencil-alt mr-2"></i> Practice | |
| </button> | |
| <button id="progress-tab" class="flex-1 py-4 px-6 text-center font-medium text-gray-700 hover:text-blue-500 focus:outline-none"> | |
| <i class="fas fa-chart-line mr-2"></i> Progress | |
| </button> | |
| </div> | |
| <!-- Tab Content --> | |
| <div class="p-6"> | |
| <!-- Lessons Tab Content --> | |
| <div id="lessons-content"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6">Zhuyin Lessons</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <!-- Lesson 1: Initials --> | |
| <div class="lesson-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm transition duration-300 cursor-pointer" onclick="loadLesson('initials')"> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-blue-100 p-3 rounded-full mr-4"> | |
| <i class="fas fa-volume-up text-blue-500 text-xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold text-gray-800">Initial Consonants</h3> | |
| </div> | |
| <p class="text-gray-600 mb-4">Learn the 21 initial consonant sounds in Zhuyin.</p> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-sm text-gray-500">21 characters</span> | |
| <span class="text-sm font-medium text-blue-500">Start Lesson →</span> | |
| </div> | |
| </div> | |
| <!-- Lesson 2: Medials --> | |
| <div class="lesson-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm transition duration-300 cursor-pointer" onclick="loadLesson('medials')"> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-green-100 p-3 rounded-full mr-4"> | |
| <i class="fas fa-comment text-green-500 text-xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold text-gray-800">Medial Vowels</h3> | |
| </div> | |
| <p class="text-gray-600 mb-4">Master the 3 medial vowel sounds that combine with initials.</p> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-sm text-gray-500">3 characters</span> | |
| <span class="text-sm font-medium text-blue-500">Start Lesson →</span> | |
| </div> | |
| </div> | |
| <!-- Lesson 3: Finals --> | |
| <div class="lesson-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm transition duration-300 cursor-pointer" onclick="loadLesson('finals')"> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-purple-100 p-3 rounded-full mr-4"> | |
| <i class="fas fa-spell-check text-purple-500 text-xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold text-gray-800">Final Sounds</h3> | |
| </div> | |
| <p class="text-gray-600 mb-4">Learn the 13 final sounds that complete Zhuyin syllables.</p> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-sm text-gray-500">13 characters</span> | |
| <span class="text-sm font-medium text-blue-500">Start Lesson →</span> | |
| </div> | |
| </div> | |
| <!-- Lesson 4: Tones --> | |
| <div class="lesson-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm transition duration-300 cursor-pointer" onclick="loadLesson('tones')"> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-yellow-100 p-3 rounded-full mr-4"> | |
| <i class="fas fa-music text-yellow-500 text-xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold text-gray-800">Tones</h3> | |
| </div> | |
| <p class="text-gray-600 mb-4">Understand the 4 tones and neutral tone in Mandarin.</p> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-sm text-gray-500">5 tone marks</span> | |
| <span class="text-sm font-medium text-blue-500">Start Lesson →</span> | |
| </div> | |
| </div> | |
| <!-- Lesson 5: Combined --> | |
| <div class="lesson-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm transition duration-300 cursor-pointer" onclick="loadLesson('combined')"> | |
| <div class="flex items-center mb-4"> | |
| <div class="bg-red-100 p-3 rounded-full mr-4"> | |
| <i class="fas fa-random text-red-500 text-xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold text-gray-800">Combined Sounds</h3> | |
| </div> | |
| <p class="text-gray-600 mb-4">Practice combining initials, medials and finals.</p> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-sm text-gray-500">All characters</span> | |
| <span class="text-sm font-medium text-blue-500">Start Lesson →</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Lesson Detail View (hidden by default) --> | |
| <div id="lesson-detail" class="hidden mt-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 id="lesson-title" class="text-2xl font-bold text-gray-800"></h2> | |
| <button onclick="backToLessons()" class="flex items-center text-blue-500 hover:text-blue-700"> | |
| <i class="fas fa-arrow-left mr-2"></i> Back to Lessons | |
| </button> | |
| </div> | |
| <div id="lesson-description" class="mb-6 text-gray-700"></div> | |
| <div id="lesson-characters" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4"></div> | |
| </div> | |
| </div> | |
| <!-- Practice Tab Content --> | |
| <div id="practice-content" class="hidden"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6">Practice Zhuyin Characters</h2> | |
| <div class="bg-blue-50 rounded-lg p-6 mb-8"> | |
| <div class="flex flex-col md:flex-row items-center"> | |
| <div class="md:w-1/2 mb-4 md:mb-0"> | |
| <h3 class="text-xl font-semibold text-gray-800 mb-2">Test Your Knowledge</h3> | |
| <p class="text-gray-600">Listen to the pronunciation and select the correct Zhuyin character.</p> | |
| </div> | |
| <div class="md:w-1/2 flex justify-end"> | |
| <button id="start-practice" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-lg transition duration-300 flex items-center"> | |
| <i class="fas fa-play mr-2"></i> Start Practice | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="practice-session" class="hidden"> | |
| <div class="bg-white border border-gray-200 rounded-lg p-6 shadow-sm mb-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <span id="progress-text" class="text-sm font-medium text-gray-500">Question 1 of 10</span> | |
| <div class="w-32 bg-gray-200 rounded-full h-2.5"> | |
| <div id="progress-bar" class="bg-blue-600 h-2.5 rounded-full" style="width: 10%"></div> | |
| </div> | |
| </div> | |
| <div class="text-center py-8"> | |
| <button id="play-sound" class="bg-green-500 hover:bg-green-600 text-white font-medium py-3 px-8 rounded-full transition duration-300 flex items-center mx-auto"> | |
| <i class="fas fa-volume-up mr-2"></i> Play Sound | |
| </button> | |
| <p class="text-gray-500 mt-4">Listen to the pronunciation and select the correct character below.</p> | |
| </div> | |
| <div id="answer-options" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 mt-6"></div> | |
| </div> | |
| <div id="feedback" class="hidden"> | |
| <div id="correct-feedback" class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4 hidden"> | |
| <div class="flex items-center"> | |
| <div class="bg-green-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-check text-green-500"></i> | |
| </div> | |
| <div> | |
| <h4 class="font-medium text-green-800">Correct!</h4> | |
| <p class="text-green-600 text-sm">Good job! That was the right answer.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="wrong-feedback" class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4 hidden"> | |
| <div class="flex items-center"> | |
| <div class="bg-red-100 p-2 rounded-full mr-3"> | |
| <i class="fas fa-times text-red-500"></i> | |
| </div> | |
| <div> | |
| <h4 class="font-medium text-red-800">Incorrect</h4> | |
| <p id="correct-answer-text" class="text-red-600 text-sm">The correct answer was: ㄅ (b)</p> | |
| </div> | |
| </div> | |
| </div> | |
| <button id="next-question" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-6 rounded-lg transition duration-300"> | |
| Next Question <i class="fas fa-arrow-right ml-2"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="practice-results" class="hidden"> | |
| <div class="bg-white border border-gray-200 rounded-lg p-6 shadow-sm"> | |
| <div class="text-center mb-6"> | |
| <div class="w-24 h-24 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i class="fas fa-trophy text-green-500 text-4xl"></i> | |
| </div> | |
| <h3 class="text-2xl font-bold text-gray-800 mb-2">Practice Complete!</h3> | |
| <p id="results-score" class="text-xl text-gray-600">You scored 8 out of 10</p> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
| <div class="bg-blue-50 rounded-lg p-4"> | |
| <h4 class="font-medium text-gray-800 mb-2">Correct Answers</h4> | |
| <div class="flex items-center"> | |
| <div class="text-3xl font-bold text-blue-500 mr-3" id="correct-count">8</div> | |
| <div class="progress-bar w-full"> | |
| <div class="progress-fill" style="width: 80%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-red-50 rounded-lg p-4"> | |
| <h4 class="font-medium text-gray-800 mb-2">Incorrect Answers</h4> | |
| <div class="flex items-center"> | |
| <div class="text-3xl font-bold text-red-500 mr-3" id="incorrect-count">2</div> | |
| <div class="progress-bar w-full"> | |
| <div class="progress-fill bg-red-500" style="width: 20%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex flex-col sm:flex-row gap-4"> | |
| <button onclick="restartPractice()" class="flex-1 bg-blue-500 hover:bg-blue-600 text-white font-medium py-3 px-6 rounded-lg transition duration-300"> | |
| <i class="fas fa-redo mr-2"></i> Try Again | |
| </button> | |
| <button onclick="backToLessons()" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-3 px-6 rounded-lg transition duration-300"> | |
| <i class="fas fa-book-open mr-2"></i> Back to Lessons | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Progress Tab Content --> | |
| <div id="progress-content" class="hidden"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6">Your Learning Progress</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
| <div class="bg-white border border-gray-200 rounded-lg p-6 shadow-sm"> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Overall Progress</h3> | |
| <div class="flex items-center mb-2"> | |
| <div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mr-4"> | |
| <i class="fas fa-chart-pie text-blue-500"></i> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-600">Characters Learned</span> | |
| <span id="learned-percent" class="text-sm font-medium text-gray-600">25%</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div id="overall-progress" class="progress-fill" style="width: 25%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white border border-gray-200 rounded-lg p-6 shadow-sm"> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Practice Accuracy</h3> | |
| <div class="flex items-center mb-2"> | |
| <div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center mr-4"> | |
| <i class="fas fa-check-circle text-green-500"></i> | |
| </div> | |
| <div class="flex-1"> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-600">Correct Answers</span> | |
| <span id="accuracy-percent" class="text-sm font-medium text-gray-600">75%</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div id="accuracy-progress" class="progress-fill bg-green-500" style="width: 75%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white border border-gray-200 rounded-lg p-6 shadow-sm"> | |
| <h3 class="text-lg font-semibold text-gray-800 mb-4">Lesson Completion</h3> | |
| <div class="space-y-4"> | |
| <!-- Initials Progress --> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="font-medium text-gray-700">Initial Consonants</span> | |
| <span class="text-sm font-medium text-gray-600">8/21</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" style="width: 38%"></div> | |
| </div> | |
| </div> | |
| <!-- Medials Progress --> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="font-medium text-gray-700">Medial Vowels</span> | |
| <span class="text-sm font-medium text-gray-600">1/3</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" style="width: 33%"></div> | |
| </div> | |
| </div> | |
| <!-- Finals Progress --> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="font-medium text-gray-700">Final Sounds</span> | |
| <span class="text-sm font-medium text-gray-600">4/13</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" style="width: 31%"></div> | |
| </div> | |
| </div> | |
| <!-- Tones Progress --> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="font-medium text-gray-700">Tones</span> | |
| <span class="text-sm font-medium text-gray-600">2/5</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" style="width: 40%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Zhuyin character data | |
| const zhuyinData = { | |
| initials: { | |
| title: "Initial Consonants", | |
| description: "Initial consonants are the starting sounds of Zhuyin syllables. There are 21 initial consonants in total.", | |
| characters: [ | |
| { char: "ㄅ", pinyin: "b", example: "八 (bā)" }, | |
| { char: "ㄆ", pinyin: "p", example: "怕 (pà)" }, | |
| { char: "ㄇ", pinyin: "m", example: "媽 (mā)" }, | |
| { char: "ㄈ", pinyin: "f", example: "發 (fā)" }, | |
| { char: "ㄉ", pinyin: "d", example: "大 (dà)" }, | |
| { char: "ㄊ", pinyin: "t", example: "他 (tā)" }, | |
| { char: "ㄋ", pinyin: "n", example: "你 (nǐ)" }, | |
| { char: "ㄌ", pinyin: "l", example: "來 (lái)" }, | |
| { char: "ㄍ", pinyin: "g", example: "哥 (gē)" }, | |
| { char: "ㄎ", pinyin: "k", example: "看 (kàn)" }, | |
| { char: "ㄏ", pinyin: "h", example: "好 (hǎo)" }, | |
| { char: "ㄐ", pinyin: "j", example: "家 (jiā)" }, | |
| { char: "ㄑ", pinyin: "q", example: "七 (qī)" }, | |
| { char: "ㄒ", pinyin: "x", example: "西 (xī)" }, | |
| { char: "ㄓ", pinyin: "zh", example: "這 (zhè)" }, | |
| { char: "ㄔ", pinyin: "ch", example: "吃 (chī)" }, | |
| { char: "ㄕ", pinyin: "sh", example: "是 (shì)" }, | |
| { char: "ㄖ", pinyin: "r", example: "日 (rì)" }, | |
| { char: "ㄗ", pinyin: "z", example: "在 (zài)" }, | |
| { char: "ㄘ", pinyin: "c", example: "菜 (cài)" }, | |
| { char: "ㄙ", pinyin: "s", example: "三 (sān)" } | |
| ] | |
| }, | |
| medials: { | |
| title: "Medial Vowels", | |
| description: "Medial vowels are the middle sounds that combine with initial consonants. There are 3 medial vowels.", | |
| characters: [ | |
| { char: "ㄧ", pinyin: "i/y", example: "一 (yī)" }, | |
| { char: "ㄨ", pinyin: "u/w", example: "五 (wǔ)" }, | |
| { char: "ㄩ", pinyin: "ü/yu", example: "魚 (yú)" } | |
| ] | |
| }, | |
| finals: { | |
| title: "Final Sounds", | |
| description: "Final sounds complete the Zhuyin syllables. There are 13 final sounds.", | |
| characters: [ | |
| { char: "ㄚ", pinyin: "a", example: "阿 (ā)" }, | |
| { char: "ㄛ", pinyin: "o", example: "喔 (ō)" }, | |
| { char: "ㄜ", pinyin: "e", example: "餓 (è)" }, | |
| { char: "ㄝ", pinyin: "ê", example: "誒 (ê̄)" }, | |
| { char: "ㄞ", pinyin: "ai", example: "愛 (ài)" }, | |
| { char: "ㄟ", pinyin: "ei", example: "飛 (fēi)" }, | |
| { char: "ㄠ", pinyin: "ao", example: "好 (hǎo)" }, | |
| { char: "ㄡ", pinyin: "ou", example: "歐 (ōu)" }, | |
| { char: "ㄢ", pinyin: "an", example: "安 (ān)" }, | |
| { char: "ㄣ", pinyin: "en", example: "恩 (ēn)" }, | |
| { char: "ㄤ", pinyin: "ang", example: "昂 (áng)" }, | |
| { char: "ㄥ", pinyin: "eng", example: "冷 (lěng)" }, | |
| { char: "ㄦ", pinyin: "er", example: "兒 (ér)" } | |
| ] | |
| }, | |
| tones: { | |
| title: "Tones", | |
| description: "Mandarin Chinese has 4 main tones and 1 neutral tone. These are marked with tone marks in Zhuyin.", | |
| characters: [ | |
| { char: "ˉ", pinyin: "1st tone", example: "高平調 (high level)" }, | |
| { char: "ˊ", pinyin: "2nd tone", example: "上升調 (rising)" }, | |
| { char: "ˇ", pinyin: "3rd tone", example: "降升調 (falling-rising)" }, | |
| { char: "ˋ", pinyin: "4th tone", example: "下降調 (falling)" }, | |
| { char: "˙", pinyin: "neutral", example: "輕聲 (light)" } | |
| ] | |
| }, | |
| combined: { | |
| title: "Combined Sounds", | |
| description: "Practice combining initials, medials and finals to form complete syllables.", | |
| characters: [ | |
| { char: "ㄅㄚ", pinyin: "ba", example: "八 (bā)" }, | |
| { char: "ㄆㄠ", pinyin: "pao", example: "跑 (pǎo)" }, | |
| { char: "ㄇㄧ", pinyin: "mi", example: "米 (mǐ)" }, | |
| { char: "ㄈㄨ", pinyin: "fu", example: "服 (fú)" }, | |
| { char: "ㄉㄧㄠ", pinyin: "diao", example: "掉 (diào)" }, | |
| { char: "ㄊㄧㄢ", pinyin: "tian", example: "天 (tiān)" }, | |
| { char: "ㄋㄩ", pinyin: "nü", example: "女 (nǚ)" }, | |
| { char: "ㄌㄧㄤ", pinyin: "liang", example: "兩 (liǎng)" }, | |
| { char: "ㄍㄨㄛ", pinyin: "guo", example: "國 (guó)" }, | |
| { char: "ㄎㄨㄞ", pinyin: "kuai", example: "快 (kuài)" }, | |
| { char: "ㄏㄜ", pinyin: "he", example: "和 (hé)" }, | |
| { char: "ㄐㄧㄝ", pinyin: "jie", example: "姐 (jiě)" }, | |
| { char: "ㄑㄩㄥ", pinyin: "qiong", example: "窮 (qióng)" }, | |
| { char: "ㄒㄩㄝ", pinyin: "xue", example: "學 (xué)" }, | |
| { char: "ㄓㄨㄤ", pinyin: "zhuang", example: "裝 (zhuāng)" }, | |
| { char: "ㄔㄨㄣ", pinyin: "chun", example: "春 (chūn)" }, | |
| { char: "ㄕㄨㄟ", pinyin: "shui", example: "水 (shuǐ)" }, | |
| { char: "ㄖㄣ", pinyin: "ren", example: "人 (rén)" }, | |
| { char: "ㄗㄞ", pinyin: "zai", example: "在 (zài)" }, | |
| { char: "ㄘㄨㄣ", pinyin: "cun", example: "村 (cūn)" }, | |
| { char: "ㄙㄨㄥ", pinyin: "song", example: "送 (sòng)" } | |
| ] | |
| } | |
| }; | |
| // Practice questions | |
| const practiceQuestions = [ | |
| { char: "ㄅ", pinyin: "b", audio: "b" }, | |
| { char: "ㄆ", pinyin: "p", audio: "p" }, | |
| { char: "ㄇ", pinyin: "m", audio: "m" }, | |
| { char: "ㄈ", pinyin: "f", audio: "f" }, | |
| { char: "ㄉ", pinyin: "d", audio: "d" }, | |
| { char: "ㄊ", pinyin: "t", audio: "t" }, | |
| { char: "ㄋ", pinyin: "n", audio: "n" }, | |
| { char: "ㄌ", pinyin: "l", audio: "l" }, | |
| { char: "ㄍ", pinyin: "g", audio: "g" }, | |
| { char: "ㄎ", pinyin: "k", audio: "k" }, | |
| { char: "ㄏ", pinyin: "h", audio: "h" }, | |
| { char: "ㄐ", pinyin: "j", audio: "j" }, | |
| { char: "ㄑ", pinyin: "q", audio: "q" }, | |
| { char: "ㄒ", pinyin: "x", audio: "x" }, | |
| { char: "ㄓ", pinyin: "zh", audio: "zh" }, | |
| { char: "ㄔ", pinyin: "ch", audio: "ch" }, | |
| { char: "ㄕ", pinyin: "sh", audio: "sh" }, | |
| { char: "ㄖ", pinyin: "r", audio: "r" }, | |
| { char: "ㄗ", pinyin: "z", audio: "z" }, | |
| { char: "ㄘ", pinyin: "c", audio: "c" }, | |
| { char: "ㄙ", pinyin: "s", audio: "s" }, | |
| { char: "ㄧ", pinyin: "i/y", audio: "i" }, | |
| { char: "ㄨ", pinyin: "u/w", audio: "u" }, | |
| { char: "ㄩ", pinyin: "ü/yu", audio: "ü" }, | |
| { char: "ㄚ", pinyin: "a", audio: "a" }, | |
| { char: "ㄛ", pinyin: "o", audio: "o" }, | |
| { char: "ㄜ", pinyin: "e", audio: "e" }, | |
| { char: "ㄝ", pinyin: "ê", audio: "ê" }, | |
| { char: "ㄞ", pinyin: "ai", audio: "ai" }, | |
| { char: "ㄟ", pinyin: "ei", audio: "ei" }, | |
| { char: "ㄠ", pinyin: "ao", audio: "ao" }, | |
| { char: "ㄡ", pinyin: "ou", audio: "ou" }, | |
| { char: "ㄢ", pinyin: "an", audio: "an" }, | |
| { char: "ㄣ", pinyin: "en", audio: "en" }, | |
| { char: "ㄤ", pinyin: "ang", audio: "ang" }, | |
| { char: "ㄥ", pinyin: "eng", audio: "eng" }, | |
| { char: "ㄦ", pinyin: "er", audio: "er" } | |
| ]; | |
| // Tab switching functionality | |
| document.getElementById('lessons-tab').addEventListener('click', () => { | |
| switchTab('lessons'); | |
| }); | |
| document.getElementById('practice-tab').addEventListener('click', () => { | |
| switchTab('practice'); | |
| }); | |
| document.getElementById('progress-tab').addEventListener('click', () => { | |
| switchTab('progress'); | |
| }); | |
| function switchTab(tabName) { | |
| // Hide all tab contents | |
| document.getElementById('lessons-content').classList.add('hidden'); | |
| document.getElementById('practice-content').classList.add('hidden'); | |
| document.getElementById('progress-content').classList.add('hidden'); | |
| // Remove active class from all tabs | |
| document.getElementById('lessons-tab').classList.remove('tab-active'); | |
| document.getElementById('practice-tab').classList.remove('tab-active'); | |
| document.getElementById('progress-tab').classList.remove('tab-active'); | |
| // Show selected tab content and add active class | |
| document.getElementById(`${tabName}-content`).classList.remove('hidden'); | |
| document.getElementById(`${tabName}-tab`).classList.add('tab-active'); | |
| } | |
| // Lesson loading functionality | |
| function loadLesson(lessonType) { | |
| const lesson = zhuyinData[lessonType]; | |
| // Hide lessons list and show lesson detail | |
| document.getElementById('lessons-content').querySelector('.grid').classList.add('hidden'); | |
| document.getElementById('lesson-detail').classList.remove('hidden'); | |
| // Set lesson title and description | |
| document.getElementById('lesson-title').textContent = lesson.title; | |
| document.getElementById('lesson-description').textContent = lesson.description; | |
| // Clear previous characters | |
| const charactersContainer = document.getElementById('lesson-characters'); | |
| charactersContainer.innerHTML = ''; | |
| // Add characters to the lesson | |
| lesson.characters.forEach(charData => { | |
| const charElement = document.createElement('div'); | |
| charElement.className = 'character-card bg-white border border-gray-200 rounded-lg p-4 text-center cursor-pointer hover:shadow-md'; | |
| charElement.innerHTML = ` | |
| <div class="zhuyin-char mb-2">${charData.char}</div> | |
| <div class="text-gray-700 font-medium">${charData.pinyin}</div> | |
| <div class="text-sm text-gray-500 mt-1">${charData.example}</div> | |
| `; | |
| charElement.addEventListener('click', () => { | |
| // In a real app, this would play the sound | |
| console.log(`Playing sound for ${charData.char}`); | |
| }); | |
| charactersContainer.appendChild(charElement); | |
| }); | |
| } | |
| function backToLessons() { | |
| // Show lessons list and hide lesson detail | |
| document.getElementById('lessons-content').querySelector('.grid').classList.remove('hidden'); | |
| document.getElementById('lesson-detail').classList.add('hidden'); | |
| } | |
| // Practice session functionality | |
| let currentQuestion = 0; | |
| let correctAnswers = 0; | |
| let questions = []; | |
| let currentCorrectAnswer = ''; | |
| document.getElementById('start-practice').addEventListener('click', startPractice); | |
| function startPractice() { | |
| // Hide start button and show practice session | |
| document.getElementById('start-practice').classList.add('hidden'); | |
| document.getElementById('practice-session').classList.remove('hidden'); | |
| // Reset counters | |
| currentQuestion = 0; | |
| correctAnswers = 0; | |
| // Create a shuffled list of 10 questions | |
| questions = [...practiceQuestions].sort(() => 0.5 - Math.random()).slice(0, 10); | |
| // Load first question | |
| loadQuestion(); | |
| } | |
| function loadQuestion() { | |
| // Update progress | |
| document.getElementById('progress-text').textContent = `Question ${currentQuestion + 1} of ${questions.length}`; | |
| document.getElementById('progress-bar').style.width = `${(currentQuestion / questions.length) * 100}%`; | |
| // Get current question | |
| const question = questions[currentQuestion]; | |
| currentCorrectAnswer = question.char; | |
| // Clear previous options and feedback | |
| document.getElementById('answer-options').innerHTML = ''; | |
| document.getElementById('feedback').classList.add('hidden'); | |
| document.getElementById('correct-feedback').classList.add('hidden'); | |
| document.getElementById('wrong-feedback').classList.add('hidden'); | |
| // Create answer options (current question + 3 random others) | |
| const options = [question]; | |
| const otherOptions = practiceQuestions.filter(q => q.char !== question.char); | |
| // Shuffle and pick 3 random options | |
| const shuffledOthers = [...otherOptions].sort(() => 0.5 - Math.random()).slice(0, 3); | |
| options.push(...shuffledOthers); | |
| // Shuffle the options again | |
| const shuffledOptions = options.sort(() => 0.5 - Math.random()); | |
| // Create option buttons | |
| shuffledOptions.forEach(option => { | |
| const optionButton = document.createElement('button'); | |
| optionButton.className = 'bg-white border border-gray-200 rounded-lg p-4 text-center hover:bg-gray-50 focus:outline-none'; | |
| optionButton.innerHTML = ` | |
| <div class="zhuyin-char">${option.char}</div> | |
| <div class="text-sm text-gray-500">${option.pinyin}</div> | |
| `; | |
| optionButton.addEventListener('click', () => checkAnswer(option.char)); | |
| document.getElementById('answer-options').appendChild(optionButton); | |
| }); | |
| // Set up play sound button | |
| document.getElementById('play-sound').addEventListener('click', () => { | |
| // In a real app, this would play the sound for the current question | |
| console.log(`Playing sound for ${question.char}`); | |
| }); | |
| } | |
| function checkAnswer(selectedChar) { | |
| // Disable all answer options | |
| const options = document.getElementById('answer-options').querySelectorAll('button'); | |
| options.forEach(option => { | |
| option.disabled = true; | |
| if (option.textContent.includes(currentCorrectAnswer)) { | |
| option.classList.add('bg-green-100', 'border-green-300'); | |
| } | |
| if (option.textContent.includes(selectedChar) && selectedChar !== currentCorrectAnswer) { | |
| option.classList.add('bg-red-100', 'border-red-300'); | |
| } | |
| }); | |
| // Show feedback | |
| document.getElementById('feedback').classList.remove('hidden'); | |
| if (selectedChar === currentCorrectAnswer) { | |
| correctAnswers++; | |
| document.getElementById('correct-feedback').classList.remove('hidden'); | |
| } else { | |
| document.getElementById('correct-answer-text').textContent = `The correct answer was: ${currentCorrectAnswer}`; | |
| document.getElementById('wrong-feedback').classList.remove('hidden'); | |
| } | |
| } | |
| document.getElementById('next-question').addEventListener('click', () => { | |
| currentQuestion++; | |
| if (currentQuestion < questions.length) { | |
| loadQuestion(); | |
| } else { | |
| // Practice session complete, show results | |
| showResults(); | |
| } | |
| }); | |
| function showResults() { | |
| document.getElementById('practice-session').classList.add('hidden'); | |
| document.getElementById('practice-results').classList.remove('hidden'); | |
| document.getElementById('results-score').textContent = `You scored ${correctAnswers} out of ${questions.length}`; | |
| document.getElementById('correct-count').textContent = correctAnswers; | |
| document.getElementById('incorrect-count').textContent = questions.length - correctAnswers; | |
| // Update progress bars | |
| document.querySelector('#practice-results .progress-fill').style.width = `${(correctAnswers / questions.length) * 100}%`; | |
| document.querySelector('#practice-results .bg-red-50 .progress-fill').style.width = `${((questions.length - correctAnswers) / questions.length) * 100}%`; | |
| } | |
| function restartPractice() { | |
| document.getElementById('practice-results').classList.add('hidden'); | |
| startPractice(); | |
| } | |
| // Initialize the app with lessons tab | |
| switchTab('lessons'); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MarcusCorvus/bpmf-tw" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |