Spaces:
Running
Running
| const storyData = [ | |
| { start: 0.000, end: 12.693, text: "تَقِفُ سَنْدِيَانَةٌ 🌳 عَالِيَةٌ عَلَى مُرْتَفَعٍ، تُشْرِفُ عَلَى نَهْرٍ 🌊 غَزِيرِ الْمِيَاهِ، أَغْصَانُهَا 🍃 كَثِيفَةٌ، وَأَوْرَاقُهَا خَضْرَاءُ، وَسَاقُهَا طَوِيلَةٌ كَالْمِئْذَنَةِ فِي الِارْتِفَاعِ." }, | |
| { start: 12.693, end: 20.742, text: "تَتَنَقَّلُ عَلَيْهَا الْعَصَافِيرُ 🐦 مُغَرِّدَةً، وَيَجْلِسُ عِنْدَهَا الرُّعَاةُ وَالصَّيَّادُونَ لِيَرْتَاحُوا مِنَ التَّعَبِ." }, | |
| { start: 20.742, end: 29.782, text: "وَتَعِيشُ قَصَبَةٌ 🌾 مَعَ رَفِيقَتِهَا، عَلَى ضِفَّةِ هَذَا النَّهْرِ، تَنْظُرُ بِخَفَرٍ لِمِيَاهِهِ الْجَارِيَةِ، وَتَتَمَايَلُ مَعَ الْهَوَاءِ." }, | |
| { start: 29.782, end: 35.045, text: "قَالَتِ السَّنْدِيَانَةُ مَرَّةً لِلْقَصَبَةِ: مَا أَحْقَرَكِ أَيَّتُهَا الْقَصَبَةُ!" }, | |
| { start: 35.045, end: 42.599, text: "تَعِيشِينَ فِي هَذَا الْمُنْخَفَضِ، وَتَخْتَفِينَ وَرَاءَهُ طِيلَةَ النَّهَارِ، فَلَا تَرَيْنَ النُّورَ، وَمَا أَضْعَفَكِ!" }, | |
| { start: 42.599, end: 48.233, text: "لَا تَسْتَطِيعِينَ أَنْ تَحْمِلِي عُصْفُورًا، حَقًّا إِنَّ الطَّبِيعَةَ قَاسِيَةٌ عَلَيْكِ." }, | |
| { start: 48.233, end: 54.611, text: "قَالَتْ لَهَا الْقَصَبَةُ: أَنْتِ مَغْرُورَةٌ بِنَفْسِكِ، وَغُرُورُكِ 😌 هَذَا سَوْفَ يَقْضِي عَلَيْكِ." }, | |
| { start: 54.611, end: 74.177, text: "هَبَّتْ عَاصِفَةٌ 🌪️ قَوِيَّةٌ، فَتَأَرْجَحَتِ الْقَصَبَةُ مَعَ الْعَاصِفَةِ، وَتَمَكَّنَتْ بِفَضْلِ ذَلِكَ مِنَ الثَّبَاتِ، وَهِيَ تَمِيلُ يَمِينًا وَيَسَارًا، أَمَّا السَّنْدِيَانَةُ فَلَمْ تَسْتَطِعْ أَنْ تُقَاوِمَ الْعَاصِفَةَ، فَتَكَسَّرَتْ أَغْصَانُهَا، وَاقْتَلَعَتْهَا الْعَاصِفَةُ بِجِذْعِهَا 🌳 الضَّخْمِ." }, | |
| { start: 74.177, end: 83.000, text: "نَظَرَتِ الْقَصَبَةُ بِعَطْفٍ إِلَى السَّنْدِيَانَةِ، وَقَالَتْ لَهَا: هَذِهِ نَتِيجَةُ غُرُورِكِ، وَاحْتِقَارِكِ مَنْ هُوَ أَصْغَرُ مِنْكِ." } | |
| ]; | |
| const translations = { | |
| "سَنْدِيَانَةٌ": "Oak tree", | |
| "مُرْتَفَعٍ": "High ground", | |
| "نَهْرٍ": "River", | |
| "غَزِيرِ": "Abundant", | |
| "أَغْصَانُهَا": "Its branches", | |
| "كَثِيفَةٌ": "Dense", | |
| "أَوْرَاقُهَا": "Its leaves", | |
| "خَضْرَاءُ": "Green", | |
| "سَاقُهَا": "Its trunk", | |
| "طَوِيلَةٌ": "Tall", | |
| "كَالْمِئْذَنَةِ": "Like a minaret", | |
| "الِارْتِفَاعِ": "Height", | |
| "تَتَنَقَّلُ": "Move around", | |
| "الْعَصَافِيرُ": "Birds", | |
| "مُغَرِّدَةً": "Chirping", | |
| "الرُّعَاةُ": "Shepherds", | |
| "وَالصَّيَّادُونَ": "And hunters", | |
| "لِيَرْتَاحُوا": "To rest", | |
| "التَّعَبِ": "Fatigue", | |
| "قَصَبَةٌ": "Reed", | |
| "رَفِيقَتِهَا": "Its companion", | |
| "ضِفَّةِ": "Bank (of river)", | |
| "بِخَفَرٍ": "Shyly", | |
| "لِمِيَاهِهِ": "To its waters", | |
| "الْجَارِيَةِ": "Running", | |
| "وَتَتَمَايَلُ": "And sways", | |
| "الْهَوَاءِ": "The air / wind", | |
| "أَحْقَرَكِ": "How despicable you are", | |
| "الْمُنْخَفَضِ": "Lowland", | |
| "وَتَخْتَفِينَ": "And you hide", | |
| "وَرَاءَهُ": "Behind it", | |
| "طِيلَةَ": "Throughout", | |
| "النَّهَارِ": "The day", | |
| "النُّورَ": "The light", | |
| "أَضْعَفَكِ": "How weak you are", | |
| "تَحْمِلِي": "Carry", | |
| "عُصْفُورًا": "A bird", | |
| "الطَّبِيعَةَ": "Nature", | |
| "قَاسِيَةٌ": "Cruel / Harsh", | |
| "مَغْرُورَةٌ": "Arrogant", | |
| "بِنَفْسِكِ": "With yourself", | |
| "وَغُرُورُكِ": "And your arrogance", | |
| "يَقْضِي": "Will destroy / Finish", | |
| "هَبَّتْ": "Blew", | |
| "عَاصِفَةٌ": "Storm", | |
| "قَوِيَّةٌ": "Strong", | |
| "فَتَأَرْجَحَتِ": "So it swayed", | |
| "وَتَمَكَّنَتْ": "And it was able", | |
| "بِفَضْلِ": "Thanks to", | |
| "الثَّبَاتِ": "Steadiness / Stability", | |
| "تَمِيلُ": "Leans / Bends", | |
| "يَمِينًا": "Right", | |
| "وَيَسَارًا": "And left", | |
| "تُقَاوِمَ": "Resist", | |
| "فَتَكَسَّرَتْ": "So they broke", | |
| "وَاقْتَلَعَتْهَا": "And uprooted it", | |
| "بِجِذْعِهَا": "By its trunk", | |
| "الضَّخْمِ": "Huge", | |
| "بِعَطْفٍ": "With sympathy", | |
| "نَتِيجَةُ": "Result", | |
| "غُرُورِكِ": "Your arrogance", | |
| "وَاحْتِقَارِكِ": "And your contempt" | |
| }; | |
| const quizData = [ | |
| { | |
| question: "أين تعيش السنديانة؟ (Where does the oak tree live?)", | |
| options: [ | |
| { text: "على ضفة النهر (On the riverbank)", isCorrect: false }, | |
| { text: "في منخفض (In a lowland)", isCorrect: false }, | |
| { text: "على مرتفع (On a high ground)", isCorrect: true } | |
| ] | |
| }, | |
| { | |
| question: "ماذا حدث للسنديانة عندما هبت العاصفة؟ (What happened to the oak tree when the storm blew?)", | |
| options: [ | |
| { text: "تأرجحت مع العاصفة (Swayed with the storm)", isCorrect: false }, | |
| { text: "اقتلعتها العاصفة (The storm uprooted it)", isCorrect: true }, | |
| { text: "قاومت العاصفة (Resisted the storm)", isCorrect: false } | |
| ] | |
| }, | |
| { | |
| question: "ما هي الحكمة من القصة؟ (What is the moral of the story?)", | |
| options: [ | |
| { text: "الطبيعة قاسية (Nature is harsh)", isCorrect: false }, | |
| { text: "الغرور يقضي على صاحبه (Arrogance destroys its owner)", isCorrect: true }, | |
| { text: "النهر جميل (The river is beautiful)", isCorrect: false } | |
| ] | |
| } | |
| ]; | |
| document.addEventListener("DOMContentLoaded", () => { | |
| const textContentEl = document.getElementById("text-content"); | |
| const audioEl = document.getElementById("story-audio"); | |
| const playBtn = document.getElementById("play-btn"); | |
| const resetBtn = document.getElementById("reset-btn"); | |
| const progressBar = document.getElementById("progress-bar"); | |
| const progressContainer = document.getElementById("progress-container"); | |
| const tooltip = document.getElementById("word-tooltip"); | |
| const tooltipAr = document.getElementById("tooltip-ar"); | |
| const tooltipEn = document.getElementById("tooltip-en"); | |
| const tooltipArrow = document.getElementById("tooltip-arrow"); | |
| const quizContainer = document.getElementById("quiz-container"); | |
| let isPlaying = false; | |
| // Remove any character that is not an Arabic letter, diacritic or space | |
| function cleanWord(word) { | |
| return word.replace(/[^\u0600-\u06FF\s]/g, '').trim(); | |
| } | |
| // Render Text | |
| storyData.forEach((section, index) => { | |
| const wrapper = document.createElement("div"); | |
| wrapper.className = "paragraph-wrapper"; | |
| const imgName = index === 5 ? "Patagraph 6.png" : `paragraph ${index + 1}.png`; | |
| const img = document.createElement("img"); | |
| img.src = imgName; | |
| img.className = "paragraph-image"; | |
| img.alt = `صورة للفقرة ${index + 1}`; | |
| wrapper.appendChild(img); | |
| const sentenceSpan = document.createElement("div"); | |
| sentenceSpan.className = "sentence"; | |
| sentenceSpan.id = `sentence-${index}`; | |
| sentenceSpan.dataset.start = section.start; | |
| sentenceSpan.dataset.end = section.end; | |
| const words = section.text.split(" "); | |
| words.forEach((word) => { | |
| const wordSpan = document.createElement("span"); | |
| const clean = cleanWord(word); | |
| wordSpan.className = "word"; | |
| wordSpan.innerText = word + " "; | |
| if (translations[clean]) { | |
| wordSpan.classList.add("has-translation"); | |
| wordSpan.addEventListener("click", (e) => { | |
| e.stopPropagation(); | |
| const rect = wordSpan.getBoundingClientRect(); | |
| tooltipAr.innerText = clean; | |
| tooltipEn.innerText = translations[clean]; | |
| // Force layout measurement | |
| const tHeight = tooltip.offsetHeight; | |
| const tWidth = tooltip.offsetWidth; | |
| // Calculate center position | |
| const centerX = rect.left + window.scrollX + (rect.width / 2); | |
| let leftPos = centerX; | |
| // Keep tooltip within screen bounds | |
| const padding = 15; | |
| const minLeft = padding + (tWidth / 2); | |
| const maxLeft = window.innerWidth - padding - (tWidth / 2); | |
| if (leftPos < minLeft) leftPos = minLeft; | |
| if (leftPos > maxLeft) leftPos = maxLeft; | |
| tooltip.style.left = `${leftPos}px`; | |
| tooltip.style.top = `${rect.top + window.scrollY - tHeight - 15}px`; | |
| // Adjust arrow to point directly at the word, even if tooltip is shifted | |
| let arrowOffset = centerX - leftPos; | |
| const maxArrowOffset = (tWidth / 2) - 15; | |
| if (arrowOffset > maxArrowOffset) arrowOffset = maxArrowOffset; | |
| if (arrowOffset < -maxArrowOffset) arrowOffset = -maxArrowOffset; | |
| tooltipArrow.style.left = `calc(50% + ${arrowOffset}px)`; | |
| tooltip.classList.add("show"); | |
| }); | |
| } | |
| sentenceSpan.appendChild(wordSpan); | |
| }); | |
| wrapper.appendChild(sentenceSpan); | |
| textContentEl.appendChild(wrapper); | |
| }); | |
| // Audio Play/Pause | |
| playBtn.addEventListener("click", () => { | |
| if (isPlaying) { | |
| audioEl.pause(); | |
| playBtn.innerHTML = '<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M8 5v14l11-7z"/></svg> استمع للقصة'; | |
| } else { | |
| audioEl.play(); | |
| playBtn.innerHTML = '<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg> إيقاف'; | |
| } | |
| isPlaying = !isPlaying; | |
| }); | |
| audioEl.addEventListener("ended", () => { | |
| isPlaying = false; | |
| playBtn.innerHTML = '<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M8 5v14l11-7z"/></svg> استمع للقصة'; | |
| progressBar.style.width = "0%"; | |
| }); | |
| // Reset Functionality | |
| resetBtn.addEventListener("click", () => { | |
| audioEl.pause(); | |
| audioEl.currentTime = 0; | |
| isPlaying = false; | |
| playBtn.innerHTML = '<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M8 5v14l11-7z"/></svg> استمع للقصة'; | |
| progressBar.style.width = "0%"; | |
| document.querySelectorAll(".sentence").forEach(el => el.classList.remove("active")); | |
| document.querySelectorAll(".option-btn").forEach(btn => { | |
| btn.classList.remove("correct", "wrong"); | |
| btn.disabled = false; | |
| }); | |
| document.querySelectorAll(".feedback").forEach(fb => { | |
| fb.classList.remove("show"); | |
| }); | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| }); | |
| // Sync Audio with Text | |
| audioEl.addEventListener("timeupdate", () => { | |
| const currentTime = audioEl.currentTime; | |
| const duration = audioEl.duration; | |
| if (duration) { | |
| progressBar.style.width = `${(currentTime / duration) * 100}%`; | |
| } | |
| storyData.forEach((section, index) => { | |
| const sentenceEl = document.getElementById(`sentence-${index}`); | |
| if (currentTime >= section.start && currentTime < section.end) { | |
| sentenceEl.classList.add("active"); | |
| } else { | |
| sentenceEl.classList.remove("active"); | |
| } | |
| }); | |
| }); | |
| // Seek Audio | |
| progressContainer.addEventListener("click", (e) => { | |
| const rect = progressContainer.getBoundingClientRect(); | |
| const pos = (e.clientX - rect.left) / rect.width; | |
| audioEl.currentTime = pos * audioEl.duration; | |
| }); | |
| // Hide Tooltip on document interactions | |
| document.addEventListener("click", () => { | |
| tooltip.classList.remove("show"); | |
| }); | |
| window.addEventListener("scroll", () => { | |
| tooltip.classList.remove("show"); | |
| }); | |
| window.addEventListener("resize", () => { | |
| tooltip.classList.remove("show"); | |
| }); | |
| // Render Quiz | |
| quizData.forEach((q, index) => { | |
| const card = document.createElement("div"); | |
| card.className = "question-card"; | |
| const qText = document.createElement("div"); | |
| qText.className = "question-text"; | |
| qText.innerText = q.question; | |
| card.appendChild(qText); | |
| const optionsContainer = document.createElement("div"); | |
| optionsContainer.className = "options-container"; | |
| const feedback = document.createElement("div"); | |
| feedback.className = "feedback"; | |
| q.options.forEach(opt => { | |
| const btn = document.createElement("button"); | |
| btn.className = "option-btn"; | |
| btn.innerText = opt.text; | |
| btn.addEventListener("click", () => { | |
| // Remove existing classes | |
| Array.from(optionsContainer.children).forEach(c => { | |
| c.classList.remove("correct", "wrong"); | |
| c.disabled = true; | |
| }); | |
| if (opt.isCorrect) { | |
| btn.classList.add("correct"); | |
| feedback.innerText = "إجابة صحيحة! (Correct Answer!)"; | |
| feedback.style.color = "#155724"; | |
| } else { | |
| btn.classList.add("wrong"); | |
| feedback.innerText = "إجابة خاطئة (Wrong Answer). حاول مرة أخرى."; | |
| feedback.style.color = "#721c24"; | |
| // Highlight correct answer | |
| Array.from(optionsContainer.children).forEach((c, i) => { | |
| if (q.options[i].isCorrect) c.classList.add("correct"); | |
| }); | |
| } | |
| feedback.classList.add("show"); | |
| }); | |
| optionsContainer.appendChild(btn); | |
| }); | |
| card.appendChild(optionsContainer); | |
| card.appendChild(feedback); | |
| quizContainer.appendChild(card); | |
| }); | |
| }); | |