363 lines
14 KiB
HTML
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>
|