@@ -772,6 +772,41 @@ class StudentPortal
خزان ماء على شكل مخروط دائري قائم مقلوب، رأسه إلى أسفل وقاعدته أفقية، نصف قطر قاعدته 2 متر وارتفاعه 6 أمتار. يُصب فيه الماء بمعدل ثابت (0.5 م³/دقيقة). جد معدل ارتفاع منسوب الماء في الخزان عندما يكون عمق الماء 3 أمتار.
+
+
⏳
@@ -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);
}
}