Live site: aghozlan.github.io
Open-access interactive lessons for Lebanese students in Grades 1–12, built on the 5E inquiry model (Engage → Explore → Explain → Elaborate → Evaluate). Every lab runs in any browser with no installation, no login, and no subscription — one link is enough.
Each file in this repo is a self-contained interactive lesson or exam. The design philosophy:
| Layer | Technology |
|---|---|
| Front-end | HTML5 · CSS3 · Vanilla JS (ES2018+) |
| Simulation | Canvas API · Three.js (selected labs) |
| AI tutoring | Groq API → llama-3.1-8b-instant |
| Grade collection | Google Apps Script → Google Sheets |
| Hosting | GitHub Pages |
| Fonts | Google Fonts (varies per lab — Syne, Orbitron, Amiri, Cairo, IBM Plex, etc.) |
Apps Script deployment URL (shared across all labs):
https://script.google.com/macros/s/AKfycbzTDERohf666RJoXbhx5hK7hTxaOQa63CDFeEKiVeSL8o4u7iBZ0uVyuiFKiMDwggmU/exec
CORS pattern: All fetch calls use Content-Type: text/plain;charset=utf-8 with mode: 'no-cors' — the only approach that bypasses CORS preflight for Apps Script without requiring OAuth.
| File | Title | Description |
|---|---|---|
heatlab.html |
HeatLab — Part 1 | Heat vs temperature · falling-pin conduction animation · radiation glow canvas |
heatlab2.html |
HeatLab — Part 2 | Convection currents · animated particle loops · sea breeze model |
| File | Title | Description |
|---|---|---|
electricitylabG7.html |
ElectricityLab — Part 1 | Circuit components · standard symbols · generators vs receivers |
electricitylab2G7.html |
ElectricityLab — Part 2 | Open vs closed circuits (animated electrons) · conductor test · PhET link · screenshot upload with XHR compression |
electricitylab3G7.html |
ElectricityLab — Pre-Test | Circuit diagram reading (canvas) · symbol drag-match · conductor sorting · animated switch · circuit builder · Grade 7D only |
| File | Title | Description |
|---|---|---|
wavelab.html |
WaveLab — Part 1 | Wave properties · transverse vs longitudinal · v = f·λ live lab · lightning mystery |
wavelab2.html |
WaveLab — Part 2 | Wave behaviour · reflection · superposition · standing waves |
wavelab3.html |
WaveLab — Part 3 | Sound waves · longitudinal pressure · pitch and frequency · resonance |
wave3d.html |
Wave 3D Visualisation | Three.js 3D wave explorer · E-field and B-field animation · tunable parameters |
waveassessment.html |
Wave Assessment | Graded MCQ assessment · submits to grade sheet |
waveexamG8.html |
Wave Exam | Formal exam · 5 anti-cheat versions · absentee version (?v=F) · timer · auto-submit |
| File | Title | Description |
|---|---|---|
emwavesG8.html |
EMWaves — Part 1: Spectrum Command | Draggable spectrum explorer · 7 band deep-dives · Three.js 3D wave lab · 4 MCQ missions + Groq AI evaluator |
emwaves2G8.html |
EMWaves — Part 2: Frequency Festival | Spectrum Survivor drag game · Wave Detective · Wave Impostor (5 rounds) · v=fλ slider calculator · Home Hunt video challenge · AI Examiner |
| File | Title | Description |
|---|---|---|
forceslab.html |
ForcesLab | Contact vs distance forces · interactive force arrow tool · 6 real-world scenarios |
equilibriumlab.html |
EquilibriumLab — Part 1 | Force Painter (draw vectors on canvas) · equilibrium condition · 4 interactive activities |
equilibriumlab2.html |
EquilibriumLab — Part 2: Force Verdict Lab | Equilibrium vs interaction pairs · Sorting Arena (drag/tap) · Body Separator slider · 3-stage Verdict Chamber |
hookslawG9.html |
Hooke’s Law Lab | 3 interactive spring experiments (k = 20 / 80 / 200 N/m) · live T vs Δx graph · derive T = k·Δx · elastic limit · 6 real-world applications · Grade 9A–9D |
Equilibrium series notes:
| File | Grade | Title | Description |
|---|---|---|---|
synapselab.html |
9 | SynapseLab — Nerve Transmission | 3D synapse model · 12 critical thinking + 10 creativity questions · Groq AI Socratic tutor · session timer · full per-question chat log submitted to sheet |
biologyExamG12.html |
12 | Biology Exam — Grade 12 | Formal Grade 12 Biology examination |
SynapseLab sheet columns: Timestamp · Name · Class · Time Spent · Q1–Q4 MCQ · Open Answers · Score · Total Chat Turns · Chat CT_1 through CT_12 · Chat CR_1 through CR_10 · Full Chat Log
| File | Grade | Title | Description |
|---|---|---|---|
bayanlab.html |
9 | بيانLab — علم البيان | التشبيه التام والمجمل · الاستعارة التصريحية · أمثلة من القرآن الكريم · تفاعلي |
NumbersArabicG9.html |
9 | الأعداد بالعربية | الأعداد والمعدود · قواعد المذكر والمؤنث · تطبيقات تفاعلية |
| File | Grade | Title | Description |
|---|---|---|---|
arabicExamG7.html |
7 | امتحان اللغة العربية — الصف السابع | الفاعل · نائب الفاعل · المفعول به · الإعراب · التحويل من المعلوم إلى المجهول · 5 نسخ مضادة للغش · نسخة غائب ?v=F · 23 علامة · 40 دقيقة · الشعبتان 7C و7D |
| File | Grade | Title | Description |
|---|---|---|---|
grade1Fractions.html |
1 | Fractions — Grade 1 | Visual introduction · halves, thirds, quarters · interactive shapes |
grade3Fractions.html |
3 | Fractions — Grade 3 | Equivalent fractions · comparing and ordering · fraction of a set |
All labs use the same Apps Script endpoint. The fetch call pattern across every file:
await fetch(APPS_SCRIPT_URL, {
method: 'POST',
mode: 'no-cors',
headers: { 'Content-Type': 'text/plain;charset=utf-8' },
body: JSON.stringify({ sheet: 'SheetTabName', name, class: cls, ...answers })
});
Why text/plain? Using application/json with no-cors causes browsers to silently strip custom headers, breaking the request. text/plain is a CORS-safe content type that passes through without a preflight.
| Lab | Sheet Tab |
|---|---|
| HeatLab | HeatTransfer_G7 |
| ElectricityLab P1 | Electricity_G7 |
| ElectricityLab P2 | Electricity_G7_P2 |
| ElectricityLab Pre-Test | Electricity_G7_Pretest |
| WaveLab | WaveLab_G8 |
| Wave Exam G8 | WaveExam_G8 |
| EMWaves Part 1 | EMWaves_G8 |
| EMWaves Part 2 | EMWaves_G8_P2 |
| EquilibriumLab P1 | Equilibrium_G9 |
| EquilibriumLab P2 | Equilibrium_G9_P2 |
| Hooke’s Law | HookesLaw_G9 |
| SynapseLab | Synapse_Biology |
| Arabic Exam G7 | Arabic_Exam_G7 |
The API key is stored in Apps Script → Script Properties as GROQ_API_KEY.
It is never written into any HTML file.
The key survives all redeployments of the same Apps Script project. If you see an invalid API error:
diagnoseGroq() from the function dropdown — it performs a live test and reports the exact errorstoreGroqKey(), run it once, then restore the placeholder and redeploy// In Apps Script — run once, then restore placeholder
function storeGroqKey() {
var MY_GROQ_KEY = 'gsk_YOUR-KEY-HERE'; // ← replace, run, restore
PropertiesService.getScriptProperties().setProperty('GROQ_API_KEY', MY_GROQ_KEY);
}
Model: llama-3.1-8b-instant · max_tokens: 300 · temperature: 0.7
Labs using Groq: emwavesG8.html · emwaves2G8.html · synapselab.html
All Groq-enabled labs have a local fallback that generates context-aware feedback if the API is unavailable — no dead ends for students.
Exam files (arabicExamG7.html, waveexamG8.html) use a deterministic version assignment:
// Same student always gets same version
// Adjacent students (similar names) get different versions
function assignVersion(name, section) {
let h = 0;
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
const sectionOffset = { '7C': 0, '7D': 1, '8A': 0, '8B': 1 }[section] || 0;
return ['A','B','C','D','E'][(h + sectionOffset * 7) % 5];
}
?v=F URL parameter — different topic entirelyThis is a standard GitHub Pages repo. The root index.html is the entry point.
# Edit a file locally, then:
git add .
git commit -m "Update lab name"
git push origin main
# GitHub Pages rebuilds in ~30 seconds
Filename conventions in use:
heatlab.html # subject + topic + no grade suffix (G7 is implied)
electricitylabG7.html # subject + topic + grade when needed for disambiguation
emwavesG8.html # always lowercase, no spaces
hookslawG9.html # camelCase for multi-word subjects
arabicExamG7.html # type (Exam/Lab/Assessment) before grade
NumbersArabicG9.html # legacy — capitalised N, keep as-is
The Apps Script file (Code.gs) handles all incoming POST requests, routes by sheet field, formats timestamps in Asia/Beirut timezone, and appends rows to the correct tab.
Key functions:
| Function | Purpose |
|---|---|
doPost(e) |
Main entry — routes action:'ai' to Groq, everything else to sheet handler |
handleSubmission(data, sheetName) |
Finds/creates sheet tab, ensures headers, appends row |
buildRow(data, sheetName, ts) |
Returns ordered array of values for each sheet type |
callGroq(data) |
Proxy to Groq API — reads key from Script Properties |
storeGroqKey() |
One-time setup: store Groq key in Script Properties |
diagnoseGroq() |
Live test — run from editor to debug API issues |
clearGroqKey() |
Wipe key before rotating to a new one |
Required appsscript.json (manifest):
{
"timeZone": "Asia/Beirut",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.external_request"
]
}
"runtimeVersion": "V8"is required — the legacy Rhino runtime is deprecated and will throw errors.
After any Apps Script change: Deploy → Manage Deployments → Edit (pencil icon) → Version: New Version → Deploy. The URL stays the same.
Built with care for Lebanese classrooms — Ahmad Ghozlan · Physics & Science Teacher · Lebanon · 2025–2026