diff --git a/backend/app/Views/StudentPortal.php b/backend/app/Views/StudentPortal.php index 6bca26f..cb22cbf 100644 --- a/backend/app/Views/StudentPortal.php +++ b/backend/app/Views/StudentPortal.php @@ -764,7 +764,7 @@ class StudentPortal
-
+
الرياضيات العلمي — المعدلات المرتبطة بالزمن (توجيهي 2008) المصدر: توماس كالكولوس + بنك التميز الوزاري
@@ -772,6 +772,41 @@ class StudentPortal خزان ماء على شكل مخروط دائري قائم مقلوب، رأسه إلى أسفل وقاعدته أفقية، نصف قطر قاعدته 2 متر وارتفاعه 6 أمتار. يُصب فيه الماء بمعدل ثابت (0.5 م³/دقيقة). جد معدل ارتفاع منسوب الماء في الخزان عندما يكون عمق الماء 3 أمتار. + +
+
+
+ 🎮 المحاكاة البصرية الحية وتغيير المعطيات (DML Interactive Canvas) + تحريك حي بالثواني ⚡ +
+
+ dh/dt = 0.159 m/min +
+
+ +
+ +
+ + +
+
+
+ عمق الماء الحالي (h): + 3.0 m +
+ +
+
+
+ معدل التدفق (dV/dt): + 0.5 m³/min +
+ +
+
+
+
⏳
@@ -1446,6 +1481,9 @@ class StudentPortal } } + // Initialize interactive DML canvas simulation + initSimControls(idx); + // Start struggle timer startStruggleTimer(); } @@ -1501,12 +1539,254 @@ class StudentPortal if (nextBtn) nextBtn.disabled = false; } - function verifyStep(stepNum, isSuccess) { - if (isSuccess) { - showLuxuryToast('إنجاز ممتاز ✓', `أتقنت الخطوة ${stepNum} بنجاح!`); - playChimeNotification(); - } else { - showLuxuryToast('نقطة تعلم 💡', `راجع تفاصيل الخطوة ${stepNum} لتثبيت الفهم.`); + // ========================================== + // DML Interactive Visual Simulation Engine (HTML5 Canvas) + // ========================================== + function initSimControls(idx) { + const s1 = document.getElementById('sim_slider_1'); + const s2 = document.getElementById('sim_slider_2'); + const l1 = document.getElementById('slider_label_1'); + const l2 = document.getElementById('slider_label_2'); + const v1 = document.getElementById('slider_val_1'); + const v2 = document.getElementById('slider_val_2'); + + if (idx === 0) { + l1.textContent = 'عمق الماء الحالي (h):'; + s1.min = '0.5'; s1.max = '5.8'; s1.step = '0.1'; s1.value = '3.0'; + v1.textContent = '3.0 m'; + + l2.textContent = 'معدل تدفق الحجم (dV/dt):'; + s2.min = '0.1'; s2.max = '2.0'; s2.step = '0.1'; s2.value = '0.5'; + v2.textContent = '0.5 m³/min'; + } else if (idx === 1) { + l1.textContent = 'بُعد الطرف السفلي عن الحائط (x):'; + s1.min = '0.5'; s1.max = '4.8'; s1.step = '0.1'; s1.value = '3.0'; + v1.textContent = '3.0 m'; + + l2.textContent = 'سرعة ابتعاد الطرف السفلي (dx/dt):'; + s2.min = '0.5'; s2.max = '4.0'; s2.step = '0.1'; s2.value = '2.0'; + v2.textContent = '2.0 m/s'; + } else if (idx === 2) { + l1.textContent = 'زاوية إطلاق المقذوف (θ):'; + s1.min = '10'; s1.max = '75'; s1.step = '1'; s1.value = '30'; + v1.textContent = '30°'; + + l2.textContent = 'السرعة الابتدائية (v0):'; + s2.min = '20'; s2.max = '70'; s2.step = '1'; s2.value = '50'; + v2.textContent = '50 m/s'; + } + + onSimSliderChange(); + } + + function onSimSliderChange() { + const val1 = parseFloat(document.getElementById('sim_slider_1').value); + const val2 = parseFloat(document.getElementById('sim_slider_2').value); + const v1 = document.getElementById('slider_val_1'); + const v2 = document.getElementById('slider_val_2'); + + if (currentStepLabIdx === 0) { + v1.textContent = `${val1.toFixed(1)} m`; + v2.textContent = `${val2.toFixed(1)} m³/min`; + } else if (currentStepLabIdx === 1) { + v1.textContent = `${val1.toFixed(1)} m`; + v2.textContent = `${val2.toFixed(1)} m/s`; + } else if (currentStepLabIdx === 2) { + v1.textContent = `${Math.round(val1)}°`; + v2.textContent = `${Math.round(val2)} m/s`; + } + + renderCanvasSim(currentStepLabIdx, val1, val2); + } + + function renderCanvasSim(idx, val1, val2) { + const canvas = document.getElementById('steplab_sim_canvas'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + const W = canvas.width; + const H = canvas.height; + + ctx.clearRect(0, 0, W, H); + + if (idx === 0) { + // Conical Tank Simulation + const h = val1; + const dV = val2; + const r = h / 3; + const dh_dt = dV / (Math.PI * Math.pow(r, 2)); + + document.getElementById('sim_live_readout').textContent = `dh/dt = ${dh_dt.toFixed(3)} m/min | r = ${r.toFixed(2)} m`; + + const cx = W / 2; + const topY = 30; + const coneH = 190; + const coneTopR = 90; + const tipY = topY + coneH; + + // Inverted Cone Outline + ctx.strokeStyle = 'rgba(56, 189, 248, 0.4)'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(cx - coneTopR, topY); + ctx.lineTo(cx, tipY); + ctx.lineTo(cx + coneTopR, topY); + ctx.stroke(); + + // Cone Top Ellipse + ctx.beginPath(); + ctx.ellipse(cx, topY, coneTopR, 18, 0, 0, Math.PI * 2); + ctx.stroke(); + + // Water inside cone + const waterFraction = h / 6.0; + const waterH = coneH * waterFraction; + const waterY = tipY - waterH; + const waterR = coneTopR * waterFraction; + + const grad = ctx.createLinearGradient(0, waterY, 0, tipY); + grad.addColorStop(0, 'rgba(0, 245, 212, 0.7)'); + grad.addColorStop(1, 'rgba(2, 132, 199, 0.9)'); + + ctx.fillStyle = grad; + ctx.beginPath(); + ctx.moveTo(cx - waterR, waterY); + ctx.lineTo(cx, tipY); + ctx.lineTo(cx + waterR, waterY); + ctx.closePath(); + ctx.fill(); + + // Water surface ellipse + ctx.fillStyle = 'rgba(0, 245, 212, 0.9)'; + ctx.beginPath(); + ctx.ellipse(cx, waterY, waterR, 10 * waterFraction, 0, 0, Math.PI * 2); + ctx.fill(); + + // Text labels on canvas + ctx.fillStyle = '#F59E0B'; + ctx.font = 'bold 12px Alexandria, sans-serif'; + ctx.fillText(`h = ${h.toFixed(1)} m`, cx + waterR + 12, waterY + 4); + ctx.fillStyle = '#00F5D4'; + ctx.fillText(`r = ${(h/3).toFixed(2)} m`, cx - 20, waterY - 8); + + } else if (idx === 1) { + // Sliding Ladder Simulation + const x = val1; + const dx_dt = val2; + const y = Math.sqrt(Math.max(0.1, 25 - x * x)); + const dy_dt = -(x * dx_dt) / y; + + document.getElementById('sim_live_readout').textContent = `dy/dt = ${dy_dt.toFixed(2)} m/s (انزلاق للأسفل) | y = ${y.toFixed(2)} m`; + + const originX = 480; + const originY = 210; + const scale = 32; + + // Wall and Ground + ctx.strokeStyle = 'rgba(255,255,255,0.2)'; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.moveTo(originX, 20); + ctx.lineTo(originX, originY); + ctx.lineTo(80, originY); + ctx.stroke(); + + // Ladder Endpoints + const ladderTopY = originY - (y * scale); + const ladderBottomX = originX - (x * scale); + + // Ladder Bar + ctx.strokeStyle = '#F59E0B'; + ctx.lineWidth = 6; + ctx.beginPath(); + ctx.moveTo(originX, ladderTopY); + ctx.lineTo(ladderBottomX, originY); + ctx.stroke(); + + // Base velocity arrow (leftwards away from wall) + ctx.strokeStyle = '#38BDF8'; + ctx.fillStyle = '#38BDF8'; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.moveTo(ladderBottomX, originY + 16); + ctx.lineTo(ladderBottomX - 35, originY + 16); + ctx.stroke(); + ctx.fillText(`dx/dt = +${dx_dt.toFixed(1)} m/s`, ladderBottomX - 110, originY + 20); + + // Top velocity arrow (downwards) + ctx.strokeStyle = '#EF4444'; + ctx.fillStyle = '#EF4444'; + ctx.beginPath(); + ctx.moveTo(originX + 16, ladderTopY); + ctx.lineTo(originX + 16, ladderTopY + 35); + ctx.stroke(); + ctx.fillText(`dy/dt = ${dy_dt.toFixed(2)} m/s`, originX + 26, ladderTopY + 20); + + } else if (idx === 2) { + // Projectile Simulation + const rad = (val1 * Math.PI) / 180; + const v0 = val2; + const g = 10; + const y0 = 45; + + const v0y = v0 * Math.sin(rad); + const v0x = v0 * Math.cos(rad); + const hMax = y0 + (v0y * v0y) / (2 * g); + + // Flight time + const disc = Math.sqrt(v0y * v0y + 2 * g * y0); + const tFlight = (v0y + disc) / g; + const range = v0x * tFlight; + + document.getElementById('sim_live_readout').textContent = `أقصى ارتفاع H = ${hMax.toFixed(1)} m | زمن التحليق = ${tFlight.toFixed(2)} s`; + + const startX = 60; + const groundY = 220; + const towerH = 45 * 0.9; + const startY = groundY - towerH; + + // Draw Tower + ctx.fillStyle = 'rgba(255,255,255,0.1)'; + ctx.fillRect(startX - 20, startY, 20, towerH); + ctx.strokeStyle = '#475569'; + ctx.strokeRect(startX - 20, startY, 20, towerH); + + // Draw Ground + ctx.strokeStyle = 'rgba(255,255,255,0.2)'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(30, groundY); + ctx.lineTo(W - 30, groundY); + ctx.stroke(); + + // Draw Trajectory Parabola + ctx.strokeStyle = '#00F5D4'; + ctx.lineWidth = 2.5; + ctx.setLineDash([4, 4]); + ctx.beginPath(); + ctx.moveTo(startX, startY); + + const dt = tFlight / 60; + const scaleX = (W - 140) / range; + const scaleY = (groundY - 30) / hMax; + + for (let t = 0; t <= tFlight; t += dt) { + const px = startX + (v0x * t) * scaleX; + const py = groundY - (y0 + v0y * t - 0.5 * g * t * t) * scaleY; + ctx.lineTo(px, py); + } + ctx.stroke(); + ctx.setLineDash([]); + + // Apex Marker + const apexT = v0y / g; + const apexX = startX + (v0x * apexT) * scaleX; + const apexY = groundY - hMax * scaleY; + + ctx.fillStyle = '#F59E0B'; + ctx.beginPath(); + ctx.arc(apexX, apexY, 5, 0, Math.PI * 2); + ctx.fill(); + ctx.fillText(`H_max = ${hMax.toFixed(1)} m`, apexX - 35, apexY - 10); } }