Update Saqel Platform: 2026-09-05 22:08:42
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,362 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,246 @@
|
||||
<!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;
|
||||
--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; }
|
||||
.header { width: 100%; max-width: 800px; 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: 800px; display: grid; grid-template-columns: 1fr; gap: 16px; }
|
||||
@media (min-width: 768px) { .container { grid-template-columns: 1.2fr 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; }
|
||||
canvas { background: #080c14; border-radius: 12px; border: 1px solid rgba(255,255,255,0.06); width: 100%; height: auto; aspect-ratio: 1; 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; }
|
||||
.slider-header.vec-a { color: var(--cyan); }
|
||||
.slider-header.vec-b { color: var(--amber); }
|
||||
.slider-header.res { color: var(--emerald); }
|
||||
|
||||
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); }
|
||||
|
||||
.toggle-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; background: rgba(16, 185, 129, 0.1); border: 1px solid rgba(16, 185, 129, 0.3); border-radius: 12px; cursor: pointer; }
|
||||
.toggle-row label { font-size: 13px; font-weight: 700; color: var(--emerald); cursor: pointer; }
|
||||
|
||||
.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.6; 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>
|
||||
|
||||
<div class="header">
|
||||
<h1><span>🧪</span> مختبر جمع وتحليل المتجهات التفاعلي</h1>
|
||||
<span class="badge">فيزياء 10 • الوحدة 1</span>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<!-- Canvas Area -->
|
||||
<div class="canvas-card">
|
||||
<canvas id="vectorCanvas" width="400" height="400"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Controls Area -->
|
||||
<div class="controls-card">
|
||||
<!-- Vector A Control -->
|
||||
<div class="slider-group">
|
||||
<div class="slider-header vec-a">
|
||||
<span>المتجه A (السماوي)</span>
|
||||
<span id="labelA">|A| = 100 N, θ = 30°</span>
|
||||
</div>
|
||||
<label style="font-size:11px;color:var(--text-sec);">المقدار (|A|):</label>
|
||||
<input type="range" id="magA" min="20" max="150" value="100">
|
||||
<label style="font-size:11px;color:var(--text-sec);">الزاوية (θ):</label>
|
||||
<input type="range" id="angA" min="0" max="360" value="30">
|
||||
</div>
|
||||
|
||||
<!-- Vector B Control -->
|
||||
<div class="slider-group">
|
||||
<div class="slider-header vec-b">
|
||||
<span>المتجه B (الذهبي)</span>
|
||||
<span id="labelB">|B| = 80 N, θ = 120°</span>
|
||||
</div>
|
||||
<label style="font-size:11px;color:var(--text-sec);">المقدار (|B|):</label>
|
||||
<input type="range" id="magB" min="20" max="150" value="80">
|
||||
<label style="font-size:11px;color:var(--text-sec);">الزاوية (θ):</label>
|
||||
<input type="range" id="angB" min="0" max="360" value="120">
|
||||
</div>
|
||||
|
||||
<!-- Resultant Toggle -->
|
||||
<div class="toggle-row" onclick="document.getElementById('showRes').click()">
|
||||
<label for="showRes">إظهار متجه المحصلة (R = A + B) 🎯</label>
|
||||
<input type="checkbox" id="showRes" checked style="cursor:pointer; width:18px; height:18px; accent-color:var(--emerald);">
|
||||
</div>
|
||||
|
||||
<!-- Real-time Mathematical Breakdown -->
|
||||
<div class="math-readout" id="mathOutput">
|
||||
حسابات التحليل المتعامد...
|
||||
</div>
|
||||
|
||||
<!-- Socratic Challenge -->
|
||||
<div class="socratic-challenge">
|
||||
<strong>💡 فحص سقراطي مباشر:</strong>
|
||||
اجعل الزاوية بين المتجهين 180° وشاهد ماذا يحدث لقيمة المحصلة R! هل تكون قيمة صغرى أم عظمى؟
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const canvas = document.getElementById('vectorCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const magA = document.getElementById('magA');
|
||||
const angA = document.getElementById('angA');
|
||||
const magB = document.getElementById('magB');
|
||||
const angB = document.getElementById('angB');
|
||||
const showRes = document.getElementById('showRes');
|
||||
const labelA = document.getElementById('labelA');
|
||||
const labelB = document.getElementById('labelB');
|
||||
const mathOutput = document.getElementById('mathOutput');
|
||||
|
||||
function drawArrow(ctx, fromx, fromy, tox, toy, color, lineWidth = 3) {
|
||||
const headlen = 10;
|
||||
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 render() {
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
const cx = w / 2;
|
||||
const cy = h / 2;
|
||||
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
// Draw Grid
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
|
||||
ctx.lineWidth = 1;
|
||||
for (let x = 0; x <= w; x += 25) {
|
||||
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
|
||||
}
|
||||
for (let y = 0; y <= h; y += 25) {
|
||||
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw Axes
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.beginPath(); ctx.moveTo(0, cy); ctx.lineTo(w, cy); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(cx, 0); ctx.lineTo(cx, h); ctx.stroke();
|
||||
|
||||
// Math calculations
|
||||
const mA = parseFloat(magA.value);
|
||||
const radA = (parseFloat(angA.value) * Math.PI) / 180;
|
||||
const ax = mA * Math.cos(radA);
|
||||
const ay = mA * Math.sin(radA);
|
||||
|
||||
const mB = parseFloat(magB.value);
|
||||
const radB = (parseFloat(angB.value) * Math.PI) / 180;
|
||||
const bx = mB * Math.cos(radB);
|
||||
const by = mB * Math.sin(radB);
|
||||
|
||||
const rx = ax + bx;
|
||||
const ry = ay + by;
|
||||
const rMag = Math.sqrt(rx * rx + ry * ry);
|
||||
let rDeg = (Math.atan2(ry, rx) * 180) / Math.PI;
|
||||
if (rDeg < 0) rDeg += 360;
|
||||
|
||||
// Update labels
|
||||
labelA.textContent = `|A| = ${mA.toFixed(0)} N, θ = ${angA.value}°`;
|
||||
labelB.textContent = `|B| = ${mB.toFixed(0)} N, θ = ${angB.value}°`;
|
||||
|
||||
// Draw Vector A (Cyan)
|
||||
drawArrow(ctx, cx, cy, cx + ax, cy - ay, '#00f5d4', 3);
|
||||
ctx.fillStyle = '#00f5d4';
|
||||
ctx.font = 'bold 12px sans-serif';
|
||||
ctx.fillText('A', cx + ax + 6, cy - ay - 4);
|
||||
|
||||
// Draw Vector B (Amber)
|
||||
drawArrow(ctx, cx, cy, cx + bx, cy - by, '#ffd166', 3);
|
||||
ctx.fillStyle = '#ffd166';
|
||||
ctx.fillText('B', cx + bx + 6, cy - by - 4);
|
||||
|
||||
// Draw Resultant R (Emerald)
|
||||
if (showRes.checked) {
|
||||
drawArrow(ctx, cx, cy, cx + rx, cy - ry, '#10b981', 4);
|
||||
ctx.fillStyle = '#10b981';
|
||||
ctx.font = 'bold 13px sans-serif';
|
||||
ctx.fillText(`R (${rMag.toFixed(1)} N)`, cx + rx + 8, cy - ry - 4);
|
||||
|
||||
// Draw parallelogram dotted lines
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
|
||||
ctx.setLineDash([4, 4]);
|
||||
ctx.beginPath(); ctx.moveTo(cx + ax, cy - ay); ctx.lineTo(cx + rx, cy - ry); ctx.stroke();
|
||||
ctx.beginPath(); ctx.moveTo(cx + bx, cy - by); ctx.lineTo(cx + rx, cy - ry); ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
|
||||
// Draw Permanent Anti-Copy Watermark
|
||||
ctx.save();
|
||||
ctx.fillStyle = 'rgba(0, 245, 212, 0.18)';
|
||||
ctx.font = "bold 11px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
||||
ctx.fillText('منصة صَقِل التعليمية الذكية © Saqel Lab - محمي وغير قابل للنقل', 12, h - 14);
|
||||
ctx.restore();
|
||||
|
||||
// Vector Dot & Cross Products
|
||||
const deltaAngle = Math.abs(aRad - bRad);
|
||||
const dotProd = (mA * mB * Math.cos(deltaAngle)).toFixed(1);
|
||||
const crossProd = (mA * mB * Math.sin(deltaAngle)).toFixed(1);
|
||||
|
||||
// Update Math readout
|
||||
mathOutput.innerHTML = `
|
||||
Ax = ${ax.toFixed(1)} N | Ay = ${ay.toFixed(1)} N<br>
|
||||
Bx = ${bx.toFixed(1)} N | By = ${by.toFixed(1)} N<br>
|
||||
<span style="color:#10b981;font-weight:bold;">Rx = Ax + Bx = ${rx.toFixed(1)} N | Ry = Ay + By = ${ry.toFixed(1)} N</span><br>
|
||||
<strong>المحصلة |R| = √(${rx.toFixed(1)}² + ${ry.toFixed(1)}²) = <span class="math-val" style="color:#10b981;">${rMag.toFixed(1)} N</span></strong><br>
|
||||
<strong>اتجاه المحصلة θ_R = <span class="math-val" style="color:#10b981;">${rDeg.toFixed(1)}°</span></strong><br>
|
||||
<span style="color:#ffd166;">الضرب القياسي (A · B) = ${dotProd} J | الضرب المتجهي |A × B| = ${crossProd} N·m</span>
|
||||
`;
|
||||
}
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
||||
[magA, angA, magB, angB, showRes].forEach(el => el.addEventListener('input', render));
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -34,6 +34,14 @@ $router->get('/military', function ($request, $response) {
|
||||
$response->html(\App\Views\MilitaryCulturePitch::render());
|
||||
});
|
||||
|
||||
// Dedicated Institutional Proposal for Vocational Training Corporation (VTC)
|
||||
$router->get('/vtc', function ($request, $response) {
|
||||
$response->html(\App\Views\VocationalTrainingPitch::render());
|
||||
});
|
||||
$router->get('/vocational', function ($request, $response) {
|
||||
$response->html(\App\Views\VocationalTrainingPitch::render());
|
||||
});
|
||||
|
||||
// Web Portals (HTML + Native JavaScript)
|
||||
$router->get('/student', function ($request, $response) {
|
||||
$response->html(\App\Views\StudentPortal::render());
|
||||
@@ -182,5 +190,10 @@ $router->get('/api/curriculum/integrated-map', function ($request, $response) {
|
||||
$response->json(\App\Services\CurriculumIntegrationService::getIntegratedCurriculumMap($grade));
|
||||
});
|
||||
|
||||
// Vocational Training Corporation (VTC) Modular CBT Curricula & Simulation Endpoints
|
||||
$router->get('/api/vocational/tree', [\App\Controllers\CurriculumController::class, 'getVocationalTree']);
|
||||
$router->get('/api/vocational/specialization', [\App\Controllers\CurriculumController::class, 'getVocationalSpecialization']);
|
||||
$router->get('/api/vocational/simulation', [\App\Controllers\CurriculumController::class, 'getVocationalSimulation']);
|
||||
|
||||
// 5. Dispatch the request
|
||||
$router->dispatch($request, $response);
|
||||
|
||||
Reference in New Issue
Block a user