From a99ff9a1844d024fde2048c7986a87a0b7e748ba Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Fri, 28 Aug 2026 02:00:56 +0300 Subject: [PATCH] feat: Implement Socratic Step-by-Step Problem Lab with 60s locked struggle timer and progressive step disclosure scaffolding --- backend/app/Views/StudentPortal.php | 300 +++++++++++++++++++++++++++- 1 file changed, 299 insertions(+), 1 deletion(-) diff --git a/backend/app/Views/StudentPortal.php b/backend/app/Views/StudentPortal.php index 3191e8f..6bca26f 100644 --- a/backend/app/Views/StudentPortal.php +++ b/backend/app/Views/StudentPortal.php @@ -593,6 +593,7 @@ class StudentPortal
+
@@ -741,6 +742,155 @@ class StudentPortal + + +
SAQEL-DRM • 079XXXXXXX @@ -1216,16 +1366,164 @@ class StudentPortal let activeTeacherId = null; + // ========================================== + // Socratic Step-by-Step Lab Engine + // ========================================== + let stepLabTimerInterval = null; + let stepLabRemainingSeconds = 60; + let currentStepLabIdx = 0; + + const stepLabProblems = [ + { + topic: "الرياضيات العلمي — المعدلات المرتبطة بالزمن (توجيهي 2008)", + source: "المصدر: توماس كالكولوس + بنك التميز الوزاري", + text: "خزان ماء على شكل مخروط دائري قائم مقلوب، رأسه إلى أسفل وقاعدته أفقية، نصف قطر قاعدته 2 متر وارتفاعه 6 أمتار. يُصب فيه الماء بمعدل ثابت (0.5 م³/دقيقة). جد معدل ارتفاع منسوب الماء في الخزان عندما يكون عمق الماء 3 أمتار.", + steps: [ + "• نصف قطر المخروط الكامل: R = 2 m ، الارتفاع الكامل: H = 6 m.
• معدل تدفق الحجم: dV/dt = +0.5 m³/min (إشارة موجبة لأن الحجم يزداد).
• اللحظة الزمنية المطلوبة: عندما يكون عمق الماء h = 3 m.
• المطلوب إيجاده: معدل تغير الارتفاع (dh/dt) = ؟", + "1. قانون حجم المخروط المائي: V = (1/3) * π * r² * h.
2. العلاقة المساعدة من تشابه مقطع المثلثين: r / h = 2 / 6 = 1 / 3 ⟹ r = (1/3) * h.
3. التخلص من المتغير r لأن المطلوب هو dh/dt: V = (1/3) * π * ((1/3)*h)² * h = (π / 27) * h³.", + "باشتقاق طرفي المعادلة [V = (π/27) * h³] بالنسبة للزمن t:
• dV/dt = (π / 27) * 3h² * (dh/dt)
• dV/dt = (π / 9) * h² * (dh/dt)", + "بالتعويض بالقيم [dV/dt = 0.5] و [h = 3 m]:
• 0.5 = (π / 9) * (3)² * (dh/dt)
• 0.5 = (π / 9) * 9 * (dh/dt)
• 0.5 = π * (dh/dt)
• dh/dt = 0.5 / π = 1 / (2π) m/min ≈ 0.159 m/min" + ] + }, + { + topic: "الرياضيات العلمي — تطبيقات التفاضل وهندسة الحركة", + source: "المصدر: ستيوارت كالكولوس + أسئلة قدرات عليا وزارية", + text: "سلّم طوله 5 أمتار يستند بطرفه العلوي على حائط رأسي وبطرفه السفلي على أرض أفقية. إذا بدأ الطرف السفلي ينزلق مبتعداً عن الحائط بمعدل (2 م/ث). جد سرعة انزلاق الطرف العلوي للسلّم عندما يكون الطرف السفلي على بُعد 3 أمتار من الحائط.", + steps: [ + "• طول السلّم ثابت (الوتر): L = 5 m.
• بعد الطرف السفلي عن الحائط: x = 3 m ، ومعدل انزلاقه: dx/dt = +2 m/s.
• بعد الطرف العلوي عن الأرض: y (متغير يتناقص).
• المطلوب: معدل انزلاق الطرف العلوي (dy/dt) = ؟", + "1. العلاقة الأساسية من مبرهنة فيثاغورس: x² + y² = 5² = 25.
2. إيجاد قيمة y عند اللحظة المطلوبة (x = 3): (3)² + y² = 25 ⟹ y² = 16 ⟹ y = 4 m.", + "باشتقاق العلاقة [x² + y² = 25] ضمنياً بالنسبة للزمن t:
• 2x * (dx/dt) + 2y * (dy/dt) = 0
• x * (dx/dt) + y * (dy/dt) = 0", + "بالتعويض بالقيم [x = 3, dx/dt = 2, y = 4]:
• (3)(2) + 4(dy/dt) = 0
• 6 + 4(dy/dt) = 0
• dy/dt = -6 / 4 = -1.5 m/s (الإشارة السالبة تعني أن الطرف العلوي ينزلق للأسفل بمعدل 1.5 م/ث)." + ] + }, + { + topic: "الفيزياء العلمي — المقذوفات والحركة في بعدين", + source: "المصدر: فيزياء سيرويه + معايير التميز الوزاري", + text: "أُطلقت قذيفة من قمة برج ارتفاعه 45 متراً بسرعة ابتدائية 50 م/ث وبزاوية 30° فوق الأفق. بافتراض تسارع الجاذبية g = 10 m/s² وإهمال مقاومة الهواء، جد أقصى ارتفاع تصل إليه القذيفة عن سطح الأرض، والزمن الكلي للوصول إلى الأرض.", + steps: [ + "• السرعة الابتدائية: v0 = 50 m/s ، زاوية الإطلاق: θ = 30°.
• المركبة الأفقية للسرعة: v0x = 50 * cos(30°) = 25√3 m/s (ثابتة).
• المركبة الرأسية الابتدائية: v0y = 50 * sin(30°) = 25 m/s.
• الارتفاع الابتدائي: y0 = 45 m.", + "1. عند أقصى ارتفاع تكون السرعة الرأسية vy = 0.
2. معادلة الحركة: vy² = v0y² - 2g(h_max - y0)
3. 0 = (25)² - 2(10)(Δy) ⟹ 20(Δy) = 625 ⟹ Δy = 31.25 m.", + "أقصى ارتفاع كلي عن سطح الأرض:
• H_total = y0 + Δy = 45 + 31.25 = 76.25 متراً.", + "لحساب زمن الوصول للأرض نطبق معادلة الإزاحة الرأسية [y - y0 = v0y*t - 0.5g*t²]:
• -45 = 25t - 5t² ⟹ 5t² - 25t - 45 = 0 ⟹ t² - 5t - 9 = 0
• بالمعادلة التربيعية: t ≈ 6.41 ثانية." + ] + } + ]; + + function loadStepLabProblem(idx) { + currentStepLabIdx = idx; + const prob = stepLabProblems[idx]; + document.getElementById('steplab_prob_topic').textContent = prob.topic; + document.getElementById('steplab_prob_text').innerHTML = prob.text; + + // Reset buttons styling + [0, 1, 2].forEach(i => { + const b = document.getElementById(`btn_prob_${i}`); + if (b) { + if (i === idx) { + b.style.background = 'linear-gradient(135deg, #0284C7, #0369A1)'; + b.style.border = 'none'; + } else { + b.style.background = 'rgba(255,255,255,0.06)'; + b.style.border = '1px solid var(--border)'; + } + } + }); + + // Populate step contents + for (let s = 1; s <= 4; s++) { + const el = document.getElementById(`step_content_${s}`); + if (el) el.innerHTML = prob.steps[s - 1]; + + const card = document.getElementById(`step_card_${s}`); + const body = document.getElementById(`step_body_${s}`); + const btn = document.getElementById(`btn_reveal_step_${s}`); + + if (body) body.style.display = 'none'; + if (card) card.style.opacity = (s === 1) ? '1' : '0.5'; + if (btn) { + btn.disabled = (s !== 1); + btn.textContent = (s === 1) ? 'كشف تفاصيل الخطوة 💡' : `كشف الخطوة ${s}`; + } + } + + // Start struggle timer + startStruggleTimer(); + } + + function startStruggleTimer() { + clearInterval(stepLabTimerInterval); + stepLabRemainingSeconds = 60; + const box = document.getElementById('steplab_struggle_box'); + const ladder = document.getElementById('steplab_steps_ladder'); + const display = document.getElementById('steplab_timer_display'); + + box.style.display = 'block'; + ladder.style.display = 'none'; + display.textContent = '00:60'; + + stepLabTimerInterval = setInterval(() => { + stepLabRemainingSeconds--; + const s = stepLabRemainingSeconds.toString().padStart(2, '0'); + display.textContent = `00:${s}`; + + if (stepLabRemainingSeconds <= 0) { + clearInterval(stepLabTimerInterval); + unlockStepsLadder(); + } + }, 1000); + } + + function skipStruggleTimer() { + clearInterval(stepLabTimerInterval); + unlockStepsLadder(); + } + + function unlockStepsLadder() { + document.getElementById('steplab_struggle_box').style.display = 'none'; + document.getElementById('steplab_steps_ladder').style.display = 'flex'; + playChimeNotification(); + showLuxuryToast('انتهت فترة التفكير 💡', 'يمكنك الآن كشف خطوات الحل خطوة بخطوة.'); + } + + function revealStep(stepNum) { + const body = document.getElementById(`step_body_${stepNum}`); + const btn = document.getElementById(`btn_reveal_step_${stepNum}`); + if (body) body.style.display = 'block'; + if (btn) { + btn.style.display = 'none'; + } + + // Unlock next step card + const nextStep = stepNum + 1; + const nextCard = document.getElementById(`step_card_${nextStep}`); + const nextBtn = document.getElementById(`btn_reveal_step_${nextStep}`); + if (nextCard) nextCard.style.opacity = '1'; + if (nextBtn) nextBtn.disabled = false; + } + + function verifyStep(stepNum, isSuccess) { + if (isSuccess) { + showLuxuryToast('إنجاز ممتاز ✓', `أتقنت الخطوة ${stepNum} بنجاح!`); + playChimeNotification(); + } else { + showLuxuryToast('نقطة تعلم 💡', `راجع تفاصيل الخطوة ${stepNum} لتثبيت الفهم.`); + } + } + function switchStudentTab(tab) { document.getElementById('tab_lessons_content').style.display = (tab === 'lessons') ? 'block' : 'none'; + document.getElementById('tab_steplab_content').style.display = (tab === 'steplab') ? 'block' : 'none'; document.getElementById('tab_chat_content').style.display = (tab === 'chat') ? 'block' : 'none'; document.getElementById('tab_exams_content').style.display = (tab === 'exams') ? 'block' : 'none'; document.getElementById('tab_btn_lessons').className = (tab === 'lessons') ? 'tab-btn active' : 'tab-btn'; + document.getElementById('tab_btn_steplab').className = (tab === 'steplab') ? 'tab-btn active' : 'tab-btn'; document.getElementById('tab_btn_chat').className = (tab === 'chat') ? 'tab-btn active' : 'tab-btn'; document.getElementById('tab_btn_exams').className = (tab === 'exams') ? 'tab-btn active' : 'tab-btn'; - if (tab === 'chat') { + if (tab === 'steplab') { + loadStepLabProblem(currentStepLabIdx); + } else if (tab === 'chat') { loadStudentChat(); } }