Files
saqel/backend/public/assets/labs/motion_1d_lab.html
T

363 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>مختبر صَقِل التفاعلي: الحركة في بُعد واحد والتسارع</title>
<style>
:root {
--bg: #06090f;
--surface: #0d111a;
--card: #131a26;
--border: rgba(255, 255, 255, 0.12);
--cyan: #00f5d4;
--amber: #ffd166;
--emerald: #10b981;
--crimson: #ff4d6d;
--blue: #0071e3;
--text: #f8fafc;
--text-sec: #94a3b8;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "SF Pro Arabic", sans-serif; }
body { background: var(--bg); color: var(--text); padding: 16px; display: flex; flex-direction: column; align-items: center; min-height: 100vh; user-select: none; -webkit-user-select: none; }
.header { width: 100%; max-width: 840px; margin-bottom: 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); padding-bottom: 12px; }
.header h1 { font-size: 18px; font-weight: 800; color: #fff; display: flex; align-items: center; gap: 8px; }
.badge { background: rgba(0, 245, 212, 0.15); color: var(--cyan); padding: 4px 10px; border-radius: 8px; font-size: 11px; font-weight: 700; border: 1px solid rgba(0, 245, 212, 0.3); }
.container { width: 100%; max-width: 840px; display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 768px) { .container { grid-template-columns: 1.25fr 1fr; } }
.canvas-card { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 12px; display: flex; flex-direction: column; align-items: center; position: relative; gap: 12px; }
canvas { background: #080c14; border-radius: 12px; border: 1px solid rgba(255,255,255,0.06); width: 100%; height: auto; touch-action: none; }
.controls-card { background: var(--surface); border: 1px solid var(--border); border-radius: 16px; padding: 18px; display: flex; flex-direction: column; gap: 14px; }
.slider-group { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 12px; }
.slider-header { display: flex; justify-content: space-between; font-size: 13px; font-weight: 700; margin-bottom: 8px; }
input[type=range] { width: 100%; height: 6px; border-radius: 3px; background: rgba(255,255,255,0.1); outline: none; -webkit-appearance: none; margin-bottom: 8px; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: #fff; cursor: pointer; box-shadow: 0 0 8px rgba(0,0,0,0.5); }
.btn-row { display: flex; gap: 10px; }
.btn { flex: 1; padding: 10px; border-radius: 10px; border: none; font-weight: 700; font-size: 13px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; transition: 0.2s; }
.btn-play { background: var(--emerald); color: #000; }
.btn-pause { background: var(--amber); color: #000; }
.btn-reset { background: rgba(255,255,255,0.1); color: #fff; border: 1px solid var(--border); }
.math-readout { background: #04070c; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 12px; font-family: monospace; font-size: 12px; line-height: 1.7; color: #cbd5e1; }
.math-val { color: #fff; font-weight: bold; }
.socratic-challenge { background: rgba(255, 209, 102, 0.1); border: 1px solid rgba(255, 209, 102, 0.3); border-radius: 12px; padding: 12px; font-size: 12px; line-height: 1.5; color: #fde68a; }
.socratic-challenge strong { color: var(--amber); display: block; margin-bottom: 4px; }
</style>
</head>
<body oncontextmenu="return false;">
<div class="header">
<h1><span>🏎️</span> مختبر الحركة في بُعد واحد والتسارع</h1>
<span class="badge">فيزياء 10 • الوحدة 2</span>
</div>
<div class="container">
<div class="canvas-card">
<!-- Motion Track Canvas -->
<canvas id="motionCanvas" width="450" height="200"></canvas>
<!-- Real-time v-t & x-t Graphs Canvas -->
<canvas id="graphCanvas" width="450" height="180"></canvas>
</div>
<div class="controls-card">
<div class="btn-row">
<button id="btnPlay" class="btn btn-play">تشغيل الحركة ▶</button>
<button id="btnReset" class="btn btn-reset">إعادة ضبط ↺</button>
</div>
<!-- Initial Velocity Control -->
<div class="slider-group">
<div class="slider-header" style="color:var(--cyan);">
<span>السرعة الابتدائية (v₀)</span>
<span id="labelV0">v₀ = 10 m/s</span>
</div>
<input type="range" id="v0Slider" min="-20" max="30" value="10">
</div>
<!-- Acceleration Control -->
<div class="slider-group">
<div class="slider-header" style="color:var(--amber);">
<span>التسارع الثابت (a)</span>
<span id="labelA">a = -2.0 m/s²</span>
</div>
<input type="range" id="aSlider" min="-8" max="8" step="0.5" value="-2">
</div>
<!-- Real-time Kinematic Readout -->
<div class="math-readout" id="readout">
الزمن t = 0.0 s<br>
الموقع x(t) = v₀t + ½at² = 0.0 m<br>
السرعة اللحظية v(t) = v₀ + at = 10.0 m/s<br>
التسارع a = -2.0 m/s²
</div>
<!-- Socratic Challenge -->
<div class="socratic-challenge">
<strong>💡 فحص سقراطي تطبيقي:</strong>
لاحظ نقطة السكون اللحظي (v = 0) عندما تكون السرعة الابتدائية موجبة والتسارع سالباً! متى تعكس السيارة اتجاه حركتها؟
</div>
</div>
</div>
<script>
const mCanvas = document.getElementById('motionCanvas');
const mCtx = mCanvas.getContext('2d');
const gCanvas = document.getElementById('graphCanvas');
const gCtx = gCanvas.getContext('2d');
const v0Slider = document.getElementById('v0Slider');
const aSlider = document.getElementById('aSlider');
const labelV0 = document.getElementById('labelV0');
const labelA = document.getElementById('labelA');
const readout = document.getElementById('readout');
const btnPlay = document.getElementById('btnPlay');
const btnReset = document.getElementById('btnReset');
let isRunning = false;
let t = 0;
let animId = null;
let lastTime = null;
let history = [];
function getV0() { return parseFloat(v0Slider.value); }
function getA() { return parseFloat(aSlider.value); }
function resetSim() {
isRunning = false;
t = 0;
history = [];
btnPlay.textContent = 'تشغيل الحركة ▶';
btnPlay.className = 'btn btn-play';
if (animId) cancelAnimationFrame(animId);
drawMotion();
drawGraph();
updateReadout(0, 0, getV0());
}
function updateReadout(time, x, v) {
readout.innerHTML = `
الزمن t = <span class="math-val" style="color:#00f5d4;">${time.toFixed(2)} s</span><br>
الموقع x = <span class="math-val" style="color:#10b981;">${x.toFixed(2)} m</span> (الإزاحة)<br>
السرعة اللحظية v = <span class="math-val" style="color:#00f5d4;">${v.toFixed(2)} m/s</span><br>
التسارع الثابت a = <span class="math-val" style="color:#ffd166;">${getA().toFixed(2)} m/s²</span>
`;
}
function drawMotion() {
const w = mCanvas.width;
const h = mCanvas.height;
mCtx.clearRect(0, 0, w, h);
// Draw Track Grid & Axis
mCtx.strokeStyle = 'rgba(255,255,255,0.1)';
mCtx.lineWidth = 1;
const trackY = 120;
mCtx.beginPath();
mCtx.moveTo(20, trackY);
mCtx.lineTo(w - 20, trackY);
mCtx.stroke();
// Draw Track Marks (-50m to +100m)
const originX = 140;
const scale = 2.4; // pixels per meter
for (let m = -40; m <= 120; m += 20) {
const px = originX + m * scale;
if (px >= 20 && px <= w - 20) {
mCtx.strokeStyle = (m === 0) ? '#00f5d4' : 'rgba(255,255,255,0.2)';
mCtx.lineWidth = (m === 0) ? 2 : 1;
mCtx.beginPath();
mCtx.moveTo(px, trackY - 8);
mCtx.lineTo(px, trackY + 8);
mCtx.stroke();
mCtx.fillStyle = (m === 0) ? '#00f5d4' : '#94a3b8';
mCtx.font = '10px sans-serif';
mCtx.textAlign = 'center';
mCtx.fillText(m + 'm', px, trackY + 22);
}
}
// Calculate state
const v0 = getV0();
const a = getA();
const x = v0 * t + 0.5 * a * t * t;
const v = v0 + a * t;
const carX = Math.min(Math.max(originX + x * scale, 30), w - 30);
// Draw Car (Apple-grade rounded tech pod)
mCtx.fillStyle = '#0071e3';
mCtx.beginPath();
mCtx.roundRect(carX - 22, trackY - 26, 44, 22, 6);
mCtx.fill();
mCtx.strokeStyle = '#00f5d4';
mCtx.lineWidth = 1.5;
mCtx.stroke();
// Wheels
mCtx.fillStyle = '#1e293b';
mCtx.beginPath(); mCtx.arc(carX - 12, trackY - 2, 5, 0, Math.PI * 2); mCtx.fill();
mCtx.beginPath(); mCtx.arc(carX + 12, trackY - 2, 5, 0, Math.PI * 2); mCtx.fill();
// Velocity Vector (Cyan Arrow)
if (Math.abs(v) > 0.2) {
const vLen = Math.min(Math.max(v * 2, -60), 60);
drawArrow(mCtx, carX, trackY - 35, carX + vLen, trackY - 35, '#00f5d4', 2.5);
mCtx.fillStyle = '#00f5d4';
mCtx.font = 'bold 11px sans-serif';
mCtx.textAlign = 'center';
mCtx.fillText(`v = ${v.toFixed(1)} m/s`, carX + vLen / 2, trackY - 42);
}
// Acceleration Vector (Amber Arrow)
if (Math.abs(a) > 0.1) {
const aLen = Math.min(Math.max(a * 5, -50), 50);
drawArrow(mCtx, carX, trackY + 36, carX + aLen, trackY + 36, '#ffd166', 2.5);
mCtx.fillStyle = '#ffd166';
mCtx.font = 'bold 10px sans-serif';
mCtx.textAlign = 'center';
mCtx.fillText(`a = ${a.toFixed(1)}`, carX + aLen / 2, trackY + 50);
}
// Permanent Watermark
mCtx.save();
mCtx.fillStyle = 'rgba(0, 245, 212, 0.18)';
mCtx.font = 'bold 11px sans-serif';
mCtx.textAlign = 'left';
mCtx.fillText('منصة صَقِل التعليمية الذكية © Saqel Lab - محمي', 14, 22);
mCtx.restore();
updateReadout(t, x, v);
}
function drawGraph() {
const w = gCanvas.width;
const h = gCanvas.height;
gCtx.clearRect(0, 0, w, h);
// Graph title
gCtx.fillStyle = '#94a3b8';
gCtx.font = '11px sans-serif';
gCtx.textAlign = 'right';
gCtx.fillText('منحنى السرعة - الزمن v(t) باللون السماوي', w - 14, 18);
// Axes
const originX = 40;
const originY = h / 2;
gCtx.strokeStyle = 'rgba(255,255,255,0.2)';
gCtx.lineWidth = 1;
gCtx.beginPath();
gCtx.moveTo(originX, 10);
gCtx.lineTo(originX, h - 10);
gCtx.moveTo(originX, originY);
gCtx.lineTo(w - 10, originY);
gCtx.stroke();
// Axis labels
gCtx.fillStyle = '#94a3b8';
gCtx.font = '10px sans-serif';
gCtx.textAlign = 'center';
gCtx.fillText('t (s)', w - 16, originY + 14);
gCtx.fillText('v (m/s)', originX + 2, 14);
if (history.length < 2) return;
// Plot v(t)
gCtx.strokeStyle = '#00f5d4';
gCtx.lineWidth = 2;
gCtx.beginPath();
for (let i = 0; i < history.length; i++) {
const pt = history[i];
const px = originX + pt.t * 35;
const py = originY - pt.v * 2.2;
if (i === 0) gCtx.moveTo(px, py);
else gCtx.lineTo(px, py);
}
gCtx.stroke();
}
function drawArrow(ctx, fromx, fromy, tox, toy, color, lineWidth = 2) {
const headlen = 8;
const dx = tox - fromx;
const dy = toy - fromy;
const angle = Math.atan2(dy, dx);
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(tox, toy);
ctx.lineTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6));
ctx.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6));
ctx.closePath();
ctx.fill();
}
function loop(timestamp) {
if (!isRunning) return;
if (!lastTime) lastTime = timestamp;
const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
lastTime = timestamp;
t += dt;
const v = getV0() + getA() * t;
const x = getV0() * t + 0.5 * getA() * t * t;
history.push({ t, v, x });
drawMotion();
drawGraph();
if (t >= 10 || Math.abs(x) > 150) {
isRunning = false;
btnPlay.textContent = 'إعادة التشغيل ▶';
btnPlay.className = 'btn btn-play';
return;
}
animId = requestAnimationFrame(loop);
}
btnPlay.addEventListener('click', () => {
isRunning = !isRunning;
if (isRunning) {
btnPlay.textContent = 'إيقاف مؤقت ⏸';
btnPlay.className = 'btn btn-pause';
lastTime = null;
animId = requestAnimationFrame(loop);
} else {
btnPlay.textContent = 'استئناف ▶';
btnPlay.className = 'btn btn-play';
if (animId) cancelAnimationFrame(animId);
}
});
btnReset.addEventListener('click', resetSim);
v0Slider.addEventListener('input', () => {
labelV0.textContent = `v₀ = ${v0Slider.value} m/s`;
if (!isRunning) drawMotion();
});
aSlider.addEventListener('input', () => {
labelA.textContent = `a = ${aSlider.value} m/s²`;
if (!isRunning) drawMotion();
});
// Disable Right-Click and Copy
document.addEventListener('contextmenu', e => e.preventDefault());
document.addEventListener('keydown', e => {
if (e.ctrlKey && (e.key === 'c' || e.key === 'u' || e.key === 's')) e.preventDefault();
});
resetSim();
</script>
</body>
</html>