diff --git "a/frontend/static/js/script.js" "b/frontend/static/js/script.js"
new file mode 100644--- /dev/null
+++ "b/frontend/static/js/script.js"
@@ -0,0 +1,2348 @@
+// Global State
+let token = localStorage.getItem('token');
+let userId = localStorage.getItem('userId');
+let selectedChildId = localStorage.getItem('selectedChildId');
+let currentEmotionLogId = null;
+
+// --- Data Templates ---
+
+const animalTemplates = {
+ 'Dog': `
+
+
+
+
+
+
+ `,
+ 'Cat': `
+
+
+
+
+
+
+ `,
+ 'Lion': `
+
+
+
+
+
+ `,
+ 'Bunny': `
+
+
+
+
+
+
+ `,
+ 'Fish': `
+
+
+
+
+
+ `
+};
+
+const coloringTemplates = {
+ 'House': `
+
+
+
+
+
+ `,
+ 'Flower': `
+
+
+
+
+
+
+ `,
+ 'Car': `
+
+
+
+
+
+ `
+};
+
+const quizData = {
+ 'Social Skills': [
+ {
+ question: "A friend is feeling sad and crying. What should you do?",
+ options: [
+ { text: "Ask if they are okay 🫂", correct: true },
+ { text: "Laugh at them 😆", correct: false },
+ { text: "Run away 🏃", correct: false }
+ ]
+ },
+ {
+ question: "You want to play with a toy someone else has. What do you say?",
+ options: [
+ { text: "Can I have a turn please? 🧸", correct: true },
+ { text: "Grab it quickly! 🖐️", correct: false },
+ { text: "Yell at them 📢", correct: false }
+ ]
+ }
+ ],
+ 'Daily Routine': [
+ {
+ question: "You just finished eating dinner. What comes next?",
+ options: [
+ { text: "Brush your teeth 🪥", correct: true },
+ { text: "Go to school 🏫", correct: false },
+ { text: "Eat breakfast 🥣", correct: false }
+ ]
+ }
+ ]
+};
+
+// UI Initial Checks
+document.addEventListener('DOMContentLoaded', () => {
+ console.log("DOM Loaded. Path:", window.location.pathname);
+ updateNav();
+ applySensoryUI();
+ applyLevelUI();
+ if (window.location.pathname === '/login') {
+ if (token) {
+ const loginSection = document.getElementById('login-section');
+ const childSection = document.getElementById('child-section');
+ if (loginSection) loginSection.classList.add('hidden');
+ if (childSection) {
+ childSection.classList.remove('hidden');
+ loadChildren();
+ } else {
+ window.location.href = '/children';
+ }
+ }
+ }
+ if (window.location.pathname === '/dashboard' || window.location.pathname === '/') {
+ loadChildrenForDashboard();
+ }
+ if (window.location.pathname === '/diary') {
+ loadDiary();
+ }
+ if (window.location.pathname === '/emotion-learning') {
+ loadCustomEmotions();
+ }
+});
+
+function updateNav() {
+ if (token) {
+ const loginNav = document.getElementById('nav-login');
+ const logoutNav = document.getElementById('nav-logout');
+ if (loginNav) loginNav.classList.add('hidden');
+ if (logoutNav) logoutNav.classList.remove('hidden');
+ }
+}
+
+function logout() {
+ localStorage.clear();
+ window.location.href = '/login';
+}
+
+function toggleAuth(showRegister) {
+ const loginSection = document.getElementById('login-section');
+ const regSection = document.getElementById('register-section');
+ if (showRegister) {
+ if (loginSection) loginSection.classList.add('hidden');
+ if (regSection) regSection.classList.remove('hidden');
+ } else {
+ if (loginSection) loginSection.classList.remove('hidden');
+ if (regSection) regSection.classList.add('hidden');
+ }
+}
+
+// Auth API Calls
+async function register() {
+ const username = document.getElementById('reg-username').value;
+ const password = document.getElementById('reg-password').value;
+ const res = await fetch('/auth/register', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password })
+ });
+ const data = await res.json();
+ if (res.ok) {
+ alert("Registered! Please login.");
+ toggleAuth(false);
+ } else alert(data.detail);
+}
+
+async function login() {
+ const usernameInput = document.getElementById('login-username');
+ const passwordInput = document.getElementById('login-password');
+ if (!usernameInput || !passwordInput) return;
+
+ const username = usernameInput.value;
+ const password = passwordInput.value;
+
+ const res = await fetch('/auth/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password })
+ });
+ const data = await res.json();
+ if (res.ok) {
+ localStorage.setItem('token', data.access_token);
+ localStorage.setItem('userId', data.user_id);
+ token = data.access_token;
+ userId = data.user_id;
+ window.location.href = '/children';
+ } else alert(data.detail);
+}
+
+async function addChild() {
+ const nameInput = document.getElementById('child-name');
+ const ageInput = document.getElementById('child-age');
+ const inheritanceInput = document.getElementById('child-autism-inheritance');
+ const sensoryInput = document.getElementById('child-sensory-level');
+
+ if (!nameInput || !ageInput) return;
+
+ const name = nameInput.value;
+ const age = parseInt(ageInput.value);
+ const parent_id = parseInt(userId || localStorage.getItem('userId'));
+
+ if (!parent_id) {
+ alert("Please log in again.");
+ window.location.href = '/login';
+ return;
+ }
+
+ const autism_inheritance = inheritanceInput ? inheritanceInput.value : "";
+ const sensory_level = sensoryInput ? sensoryInput.value : "standard";
+
+ console.log("Adding child with data:", { name, age, parent_id, autism_inheritance, sensory_level });
+
+ try {
+ const res = await fetch('/auth/add-child', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ name,
+ age,
+ parent_id,
+ autism_inheritance,
+ sensory_level
+ })
+ });
+ const data = await res.json();
+ if (res.ok) {
+ loadChildren();
+ alert("Child added!");
+ nameInput.value = '';
+ ageInput.value = '';
+ } else {
+ console.error("Server error:", data);
+ alert(data.detail || "Error adding child profile.");
+ }
+ } catch (err) {
+ console.error("Network error:", err);
+ alert("Could not connect to server.");
+ }
+}
+
+async function loadChildren() {
+ const res = await fetch(`/auth/children/${userId}`);
+ const children = await res.json();
+ const list = document.getElementById('child-list');
+ if (!list) return;
+ list.innerHTML = '';
+ if (children.length === 0) {
+ list.innerHTML = '
No child profiles found. Add one to get started!
';
+ return;
+ }
+ children.forEach(c => {
+ const div = document.createElement('div');
+ div.className = 'card child-card';
+ div.innerHTML = `
+
+
${c.name}
+
Age: ${c.age} | Sensory: ${c.sensory_level}
+
+
+
+
+
+ `;
+ list.appendChild(div);
+ });
+}
+
+async function deleteChild(id) {
+ if (!confirm("Are you sure you want to delete this child profile? All progress will be lost.")) return;
+ const res = await fetch(`/auth/delete-child/${id}`, { method: 'DELETE' });
+ if (res.ok) {
+ alert("Child profile deleted.");
+ if (selectedChildId == id) {
+ localStorage.removeItem('selectedChildId');
+ localStorage.removeItem('selectedChildAge');
+ localStorage.removeItem('selectedChildSensory');
+ selectedChildId = null;
+ }
+ loadChildren();
+ } else {
+ const data = await res.json();
+ alert(data.detail || "Error deleting child.");
+ }
+}
+
+function selectChild(id, name, age, sensory) {
+ localStorage.setItem('selectedChildId', id);
+ localStorage.setItem('selectedChildAge', age);
+ localStorage.setItem('selectedChildSensory', sensory);
+ selectedChildId = id;
+ alert(`Child profile selected: ${name} (Age: ${age})`);
+ applySensoryUI(sensory);
+ applyLevelUI(age);
+ window.location.href = '/dashboard';
+}
+
+function applySensoryUI(level) {
+ if (!level) level = localStorage.getItem('selectedChildSensory') || 'standard';
+ console.log("Applying UI for sensory level:", level);
+
+ if (level === 'high') {
+ document.body.classList.add('calm-theme');
+ } else {
+ document.body.classList.remove('calm-theme');
+ }
+}
+
+function applyLevelUI(age) {
+ // Reverted to normal UI for all levels as requested.
+ // No specific theme classes added here.
+ document.body.classList.remove('level-2-theme', 'level-3-theme');
+}
+
+// Real-time Emotion Tracker Logic
+let videoStream = null;
+let captureInterval = null;
+let emotionHistory = []; // Buffer for smoothing results
+const SMOOTHING_WINDOW = 5; // Average over last 5 frames
+
+async function startCamera() {
+ const display = document.getElementById('live-result');
+ const video = document.getElementById('webcam');
+
+ console.log("Attempting to start camera...");
+ console.log("Hostname:", window.location.hostname);
+ console.log("Secure Context:", window.isSecureContext);
+
+ if (!selectedChildId) {
+ alert("Please select a child profile first on the Child page!");
+ return;
+ }
+
+ // 1. Check for Secure Context (HTTPS or localhost)
+ if (!window.isSecureContext && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
+ const errorMsg = `❌ Camera BLOCKED. Your browser requires HTTPS for camera access unless you are using 'localhost'. (Current: ${window.location.hostname})`;
+ console.error(errorMsg);
+ if (display) display.innerHTML = `${errorMsg}`;
+ alert(errorMsg);
+ return;
+ }
+
+ // 2. Check for MediaDevices support
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
+ const errorMsg = "❌ Camera API not supported in this browser. Try Chrome or Edge.";
+ console.error(errorMsg);
+ if (display) display.innerHTML = `${errorMsg}`;
+ alert(errorMsg);
+ return;
+ }
+
+ // Stop any existing camera session
+ stopCamera();
+ emotionHistory = [];
+
+ if (!video) {
+ console.error("Video element 'webcam' not found in DOM.");
+ return;
+ }
+
+ if (display) display.innerText = "⌛ Requesting camera permission...";
+
+ try {
+ console.log("Calling getUserMedia...");
+ const constraints = {
+ video: {
+ facingMode: "user",
+ width: { ideal: 640 },
+ height: { ideal: 480 }
+ }
+ };
+
+ videoStream = await navigator.mediaDevices.getUserMedia(constraints);
+
+ console.log("Camera stream obtained.");
+ video.srcObject = videoStream;
+
+ // Ensure video plays
+ video.onloadedmetadata = () => {
+ console.log("Video metadata loaded, playing...");
+ video.play()
+ .then(() => {
+ console.log("Video playing successfully.");
+ if (display) display.innerHTML = '✅ Camera Active!';
+ })
+ .catch(e => {
+ console.error("Error playing video:", e);
+ if (display) display.innerHTML = `Error playing video: ${e.message}`;
+ });
+ };
+
+ // Start capturing frames every 1.5 seconds
+ captureInterval = setInterval(captureFrame, 1500);
+
+ } catch (err) {
+ console.error("Camera start error:", err);
+ let msg = "❌ Camera Error.";
+
+ if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError')
+ msg = "❌ Permission Denied. Please allow camera access in your browser settings.";
+ else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError')
+ msg = "❌ No camera found on this device.";
+ else if (err.name === 'NotReadableError' || err.name === 'TrackStartError')
+ msg = "❌ Camera is already in use by another app.";
+ else
+ msg = `❌ Error: ${err.name} - ${err.message}`;
+
+ if (display) display.innerHTML = `${msg}`;
+ alert(msg);
+ }
+}
+
+function stopCamera() {
+ console.log("Stopping camera...");
+ if (captureInterval) {
+ clearInterval(captureInterval);
+ captureInterval = null;
+ }
+
+ if (videoStream) {
+ videoStream.getTracks().forEach(track => {
+ track.stop();
+ });
+ videoStream = null;
+ }
+
+ const video = document.getElementById('webcam');
+ if (video) {
+ video.srcObject = null;
+ }
+
+ const display = document.getElementById('live-result');
+ if (display) display.innerText = "Camera Stopped";
+
+ const overlay = document.getElementById('live-overlay');
+ if (overlay) overlay.innerText = "Ready...";
+
+ emotionHistory = [];
+}
+
+// Automatically stop camera if user leaves the page
+window.addEventListener('beforeunload', stopCamera);
+window.addEventListener('popstate', stopCamera);
+
+async function captureFrame() {
+ const video = document.getElementById('webcam');
+ if (!video || !video.srcObject || !videoStream) {
+ return;
+ }
+
+ if (video.paused || video.ended) return;
+
+ console.log("Capturing frame for analysis...");
+
+ const canvas = document.createElement('canvas');
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+
+ if (canvas.width === 0 || canvas.height === 0) return;
+
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+ const frameData = canvas.toDataURL('image/jpeg', 0.6); // Lower quality for speed
+ const formData = new FormData();
+ formData.append('child_id', selectedChildId);
+ formData.append('frame_data', frameData);
+
+ try {
+ const res = await fetch('/emotion/process-frame', {
+ method: 'POST',
+ body: formData
+ });
+
+ if (!res.ok) {
+ console.error("Frame processing failed on server:", res.status);
+ return;
+ }
+
+ const data = await res.json();
+ if (data.emotion) {
+ console.log("Detected emotion:", data.emotion);
+ // Add to history for smoothing
+ emotionHistory.push(data.emotion);
+ if (emotionHistory.length > SMOOTHING_WINDOW) {
+ emotionHistory.shift();
+ }
+
+ // Get the most frequent emotion in the window
+ const counts = {};
+ emotionHistory.forEach(e => counts[e] = (counts[e] || 0) + 1);
+ const stableEmotion = Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b);
+
+ const overlay = document.getElementById('live-overlay');
+ const display = document.getElementById('live-result');
+ const emotion = stableEmotion.toUpperCase();
+
+ if (overlay) {
+ overlay.innerText = emotion;
+ const colors = {
+ 'HAPPY': '#4CAF50', 'SAD': '#2196F3', 'ANGRY': '#F44336',
+ 'SURPRISE': '#FFEB3B', 'NEUTRAL': '#9E9E9E', 'FEAR': '#9C27B0', 'DISGUST': '#795548'
+ };
+ overlay.style.borderColor = colors[emotion] || '#4a90e2';
+ overlay.style.background = (emotion === 'SURPRISE') ? 'rgba(255,235,59,0.9)' : 'rgba(0,0,0,0.7)';
+ overlay.style.color = (emotion === 'SURPRISE') ? '#000' : '#fff';
+ }
+
+ if (display) {
+ display.innerHTML = `LIVE: ${emotion}`;
+ }
+ }
+ } catch (err) {
+ console.error("Frame processing error:", err);
+ }
+}
+
+async function capturePhoto() {
+ console.log("Capturing photo...");
+ if (!selectedChildId) return alert("Select child first!");
+ const video = document.getElementById('webcam');
+ if (!video || !video.srcObject) return alert("Start camera first!");
+
+ const canvas = document.createElement('canvas');
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+
+ const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg'));
+ const formData = new FormData();
+ formData.append('file', blob, 'capture.jpg');
+ formData.append('child_id', selectedChildId);
+
+ try {
+ const res = await fetch('/emotion/upload', {
+ method: 'POST',
+ body: formData
+ });
+ if (!res.ok) {
+ const errData = await res.json();
+ throw new Error(errData.detail || "Upload failed");
+ }
+ const data = await res.json();
+ currentEmotionLogId = data.id;
+ document.getElementById('predicted-emotion').innerText = data.predicted_emotion.toUpperCase();
+ document.getElementById('result-img').src = data.image_url;
+ document.getElementById('emotion-result').classList.remove('hidden');
+ } catch (err) {
+ console.error("Capture upload error:", err);
+ alert("Upload error: " + err.message);
+ }
+}
+
+// Emotion Module (Legacy Upload)
+
+async function loadCustomEmotions() {
+ try {
+ const res = await fetch('/emotion/unique-emotions');
+ if (!res.ok) return;
+ const emotions = await res.json();
+ const select = document.getElementById('corrected-emotion');
+ if (!select) return;
+
+ // Save current "other" option
+ const otherOption = select.querySelector('option[value="other"]');
+ select.innerHTML = '';
+
+ emotions.forEach(emo => {
+ const opt = document.createElement('option');
+ opt.value = emo;
+ opt.innerText = emo.charAt(0).toUpperCase() + emo.slice(1);
+ select.appendChild(opt);
+ });
+
+ if (otherOption) select.appendChild(otherOption);
+ } catch (err) {
+ console.error("Failed to load custom emotions:", err);
+ }
+}
+
+async function uploadEmotion() {
+ console.log("Uploading emotion image...");
+ if (!selectedChildId) return alert("Please select a child profile first!");
+ const fileInput = document.getElementById('emotion-upload');
+ if (!fileInput || fileInput.files.length === 0) return alert("Select an image first.");
+
+ const formData = new FormData();
+ formData.append('file', fileInput.files[0]);
+ formData.append('child_id', selectedChildId);
+
+ try {
+ const res = await fetch('/emotion/upload', {
+ method: 'POST',
+ body: formData
+ });
+ if (!res.ok) {
+ const errData = await res.json();
+ throw new Error(errData.detail || "Upload failed");
+ }
+ const data = await res.json();
+ currentEmotionLogId = data.id;
+ document.getElementById('predicted-emotion').innerText = data.predicted_emotion.toUpperCase();
+ document.getElementById('result-img').src = data.image_url;
+ document.getElementById('emotion-result').classList.remove('hidden');
+ } catch (err) {
+ console.error("Upload error:", err);
+ alert("Upload error: " + err.message);
+ }
+}
+
+async function confirmEmotion(confirmed) {
+ if (confirmed) {
+ await fetch('/emotion/confirm', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: `log_id=${currentEmotionLogId}&confirmed=true`
+ });
+ alert("Confirmed! Thank you.");
+ const resDiv = document.getElementById('emotion-result');
+ if (resDiv) resDiv.classList.add('hidden');
+ } else {
+ const corrArea = document.getElementById('correction-area');
+ if (corrArea) corrArea.classList.remove('hidden');
+ }
+}
+
+function toggleCustomEmotion() {
+ const select = document.getElementById('corrected-emotion');
+ const customInput = document.getElementById('custom-emotion-name');
+ if (select.value === 'other') {
+ customInput.classList.remove('hidden');
+ } else {
+ customInput.classList.add('hidden');
+ }
+}
+
+async function saveCorrection() {
+ const select = document.getElementById('corrected-emotion');
+ const customInput = document.getElementById('custom-emotion-name');
+
+ let corrected = select.value;
+ if (corrected === 'other') {
+ corrected = customInput.value.toLowerCase().trim();
+ if (!corrected) return alert("Please type a new emotion name.");
+ }
+
+ await fetch('/emotion/confirm', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: `log_id=${currentEmotionLogId}&confirmed=true&corrected_emotion=${corrected}`
+ });
+ alert(`Learned! This image is now marked as: ${corrected}`);
+ const resDiv = document.getElementById('emotion-result');
+ if (resDiv) resDiv.classList.add('hidden');
+
+ // Refresh the list to include the new emotion
+ loadCustomEmotions();
+
+ if (customInput) customInput.value = '';
+ if (select) select.value = 'happy';
+ toggleCustomEmotion();
+}
+
+async function trainAI() {
+ const status = document.getElementById('train-status');
+ if (status) status.innerText = "Training in progress... please wait.";
+
+ try {
+ const res = await fetch('/emotion/train', { method: 'POST' });
+ const data = await res.json();
+ if (res.ok) {
+ alert(data.message);
+ if (status) status.innerText = "Last training: " + data.message;
+ } else {
+ alert("Training failed: " + data.error);
+ if (status) status.innerText = "Training failed.";
+ }
+ } catch (err) {
+ console.error("Training error:", err);
+ alert("Error connecting to server.");
+ }
+}
+
+// Activities and Games Logic
+let activeGame = "";
+let gameScore = 0;
+let startTime = 0;
+
+function startGame(name) {
+ if (!selectedChildId) return alert("Select child first.");
+
+ name = (name || "").trim();
+ console.log("Starting game:", name);
+
+ activeGame = name;
+ gameScore = 0;
+ startTime = Date.now();
+
+ const gameArea = document.getElementById('game-area');
+ if (gameArea) gameArea.classList.remove('hidden');
+
+ const title = document.getElementById('current-game-title');
+ if (title) {
+ title.innerText = name;
+ title.style.display = 'block';
+ }
+
+ const controls = document.getElementById('game-controls');
+ if (controls) controls.classList.remove('hidden');
+
+ const scoreDisplay = document.getElementById('game-score');
+ if (scoreDisplay) scoreDisplay.innerText = gameScore;
+
+ const closeBtn = document.getElementById('close-game-btn');
+ if (closeBtn) closeBtn.classList.add('hidden');
+
+ const container = document.getElementById('game-container');
+ if (!container) return console.error("Game container not found!");
+
+ container.innerHTML = '';
+
+ const lowerName = name.toLowerCase();
+ if (lowerName === 'color match' || lowerName === 'learn colors' || lowerName === 'learn about colors') startColorMatch(container);
+ else if (lowerName === 'memory game') startMemoryGame(container);
+ else if (lowerName === 'coloring book' || lowerName === 'online coloring' || lowerName === 'online coloring game') startColoringBook(container);
+ else if (lowerName === 'animal coloring' || lowerName === 'animal coloring game') startAnimalColoring(container);
+ else if (lowerName === 'pattern match' || lowerName === 'patternmatch') startPatternMatch(container);
+ else if (lowerName === 'mood matcher' || lowerName === 'moodmatcher') startMoodMatch(container);
+ else if (lowerName === 'learn emotions' || lowerName === 'learn about emotions') startLearnEmotions(container);
+ else if (lowerName === 'shape match' || lowerName === 'learn shapes' || lowerName === 'learn about shapes') startShapeMatch(container);
+ else if (lowerName === 'alphabet trace') startAlphabetTrace(container);
+ else if (lowerName === 'number write') startNumberWrite(container);
+ else if (lowerName === 'alphabet memory') startAlphabetMemory(container);
+ else if (lowerName === 'word match') startWordMatch(container);
+ else if (lowerName === 'halves match') startHalvesMatch(container);
+ else if (lowerName === 'alphabet sort') startAbcSort(container);
+ else if (lowerName === 'alphabet order') startAlphabetOrder(container);
+ else if (lowerName === 'missing letter') startMissingLetter(container);
+ else if (lowerName === 'word picture match') startWordPictureMatch(container);
+ else if (lowerName === 'object search') startObjectSearch(container);
+ else if (lowerName === 'spatial puzzle') startSpatialPuzzle(container);
+ else if (lowerName === 'jigsaw puzzle') startJigsawPuzzle(container);
+ else if (lowerName === 'advanced patterns') startAdvancedPatterns(container);
+ else {
+ console.error("Unknown game:", name);
+ container.innerHTML = `Oops! Game "${name}" not found.
`;
+ }
+}
+window.startGame = startGame;
+
+// --- New Level 2 Game Functions ---
+
+function startAlphabetTrace(container) {
+ const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
+ let currentIdx = 0;
+
+ window.renderTraceLetter = (idx) => {
+ const char = letters[idx];
+ container.innerHTML = `
+ Trace the Letter: ${char}
+
+
+
+
+
+
+
+
+ `;
+ initTracing();
+ };
+
+ window.nextTraceLetter = () => {
+ gameScore += 10;
+ document.getElementById('game-score').innerText = gameScore;
+ currentIdx = (currentIdx + 1) % letters.length;
+ renderTraceLetter(currentIdx);
+ };
+
+ function initTracing() {
+ const canvas = document.getElementById('trace-canvas');
+ const ctx = canvas.getContext('2d');
+ let drawing = false;
+
+ ctx.strokeStyle = '#8d6e63';
+ ctx.lineWidth = 15;
+ ctx.lineCap = 'round';
+ ctx.font = '250px Nunito';
+ ctx.fillStyle = '#f0f0f0';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(letters[currentIdx], 150, 160);
+
+ const startDraw = (e) => { drawing = true; draw(e); };
+ const endDraw = () => { drawing = false; ctx.beginPath(); };
+ const draw = (e) => {
+ if (!drawing) return;
+ const rect = canvas.getBoundingClientRect();
+ const x = (e.clientX || e.touches[0].clientX) - rect.left;
+ const y = (e.clientY || e.touches[0].clientY) - rect.top;
+ ctx.lineTo(x, y);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ };
+
+ canvas.addEventListener('mousedown', startDraw);
+ canvas.addEventListener('mousemove', draw);
+ canvas.addEventListener('mouseup', endDraw);
+ canvas.addEventListener('touchstart', startDraw);
+ canvas.addEventListener('touchmove', draw);
+ canvas.addEventListener('touchend', endDraw);
+ }
+
+ renderTraceLetter(currentIdx);
+}
+
+function startNumberWrite(container) {
+ const numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
+ let currentIdx = 0;
+
+ window.renderTraceNumber = (idx) => {
+ const num = numbers[idx];
+ container.innerHTML = `
+ Write the Number: ${num}
+
+
+
+
+
+
+
+
+ `;
+ initTracing();
+ };
+
+ window.nextTraceNumber = () => {
+ gameScore += 10;
+ document.getElementById('game-score').innerText = gameScore;
+ currentIdx = (currentIdx + 1) % numbers.length;
+ renderTraceNumber(currentIdx);
+ };
+
+ function initTracing() {
+ const canvas = document.getElementById('trace-canvas');
+ const ctx = canvas.getContext('2d');
+ let drawing = false;
+
+ ctx.strokeStyle = '#8d6e63';
+ ctx.lineWidth = 15;
+ ctx.lineCap = 'round';
+ ctx.font = '250px Nunito';
+ ctx.fillStyle = '#f0f0f0';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(numbers[currentIdx], 150, 160);
+
+ const startDraw = (e) => { drawing = true; draw(e); };
+ const endDraw = () => { drawing = false; ctx.beginPath(); };
+ const draw = (e) => {
+ if (!drawing) return;
+ const rect = canvas.getBoundingClientRect();
+ const x = (e.clientX || e.touches[0].clientX) - rect.left;
+ const y = (e.clientY || e.touches[0].clientY) - rect.top;
+ ctx.lineTo(x, y);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ };
+
+ canvas.addEventListener('mousedown', startDraw);
+ canvas.addEventListener('mousemove', draw);
+ canvas.addEventListener('mouseup', endDraw);
+ canvas.addEventListener('touchstart', startDraw);
+ canvas.addEventListener('touchmove', draw);
+ canvas.addEventListener('touchend', endDraw);
+ }
+
+ renderTraceNumber(currentIdx);
+}
+
+function startAlphabetMemory(container) {
+ const letters = [
+ { u: 'A', l: 'a' }, { u: 'B', l: 'b' }, { u: 'C', l: 'c' }, { u: 'D', l: 'd' },
+ { u: 'E', l: 'e' }, { u: 'F', l: 'f' }
+ ];
+ let cards = [];
+ letters.forEach(pair => {
+ cards.push({ val: pair.u, match: pair.l, type: 'upper' });
+ cards.push({ val: pair.l, match: pair.u, type: 'lower' });
+ });
+ cards.sort(() => Math.random() - 0.5);
+
+ let firstCard = null;
+ let secondCard = null;
+ let lockBoard = false;
+
+ container.innerHTML = `
+ Match Upper & Lower Case!
+
+ ${cards.map((card, i) => `
+
+ ${card.val}
+
+ `).join('')}
+
+
+
+
+
+ `;
+
+ window.flipAbcCard = (i) => {
+ if (lockBoard) return;
+ const cardEl = document.getElementById(`abc-card-${i}`);
+ if (cardEl.classList.contains('matched') || cardEl === firstCard) return;
+
+ cardEl.style.color = 'var(--text)';
+ cardEl.style.background = 'white';
+
+ if (!firstCard) {
+ firstCard = cardEl;
+ return;
+ }
+
+ secondCard = cardEl;
+ lockBoard = true;
+
+ const val1 = firstCard.innerText.trim();
+ const val2 = secondCard.innerText.trim();
+
+ const isMatch = (val1.toUpperCase() === val2.toUpperCase() && val1 !== val2);
+
+ if (isMatch) {
+ firstCard.classList.add('matched');
+ secondCard.classList.add('matched');
+ firstCard.style.background = '#d7ccc8';
+ secondCard.style.background = '#d7ccc8';
+ gameScore += 20;
+ document.getElementById('game-score').innerText = gameScore;
+ resetBoard();
+ } else {
+ setTimeout(() => {
+ firstCard.style.color = 'transparent';
+ firstCard.style.background = 'var(--secondary)';
+ secondCard.style.color = 'transparent';
+ secondCard.style.background = 'var(--secondary)';
+ resetBoard();
+ }, 1000);
+ }
+ };
+
+ function resetBoard() {
+ firstCard = null;
+ secondCard = null;
+ lockBoard = false;
+ }
+}
+
+function startWordMatch(container) {
+ const wordPairs = [
+ { word: 'Apple', icon: '🍎' },
+ { word: 'Dog', icon: '🐶' },
+ { word: 'Sun', icon: '☀️' },
+ { word: 'Book', icon: '📚' }
+ ];
+ let currentPair = wordPairs[Math.floor(Math.random() * wordPairs.length)];
+
+ window.renderWordMatch = () => {
+ currentPair = wordPairs[Math.floor(Math.random() * wordPairs.length)];
+ const options = [...wordPairs].sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ Which picture matches the word?
+
+
+
+
+ ${options.map(opt => `
+
+ ${opt.icon}
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.speakWord = (word) => {
+ const msg = new SpeechSynthesisUtterance();
+ msg.text = word;
+ window.speechSynthesis.speak(msg);
+ };
+
+ window.checkWordMatch = (selected, target) => {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "Correct! 🌟";
+ feedback.style.color = "var(--success)";
+ gameScore += 15;
+ document.getElementById('game-score').innerText = gameScore;
+ setTimeout(renderWordMatch, 1500);
+ } else {
+ feedback.innerText = "Try again! 😊";
+ feedback.style.color = "#e74c3c";
+ }
+ };
+
+ renderWordMatch();
+}
+
+function startHalvesMatch(container) {
+ const objects = [
+ { icon: '🍎', left: '🍎', right: '🍎' }, // In real app, these would be split images
+ { icon: '🐶', left: '🐶', right: '🐶' },
+ { icon: '🚗', left: '🚗', right: '🚗' },
+ { icon: '🏠', left: '🏠', right: '🏠' }
+ ];
+
+ window.renderHalves = () => {
+ const target = objects[Math.floor(Math.random() * objects.length)];
+ const options = [...objects].sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ Find the matching half!
+
+
+ ${target.icon}
+
+
+ ?
+
+
+
+ ${options.map(opt => `
+
+ ${opt.icon}
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.checkHalvesMatch = (selected, target) => {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "You completed it! 🌟";
+ feedback.style.color = "var(--success)";
+ gameScore += 15;
+ document.getElementById('game-score').innerText = gameScore;
+ setTimeout(renderHalves, 1500);
+ } else {
+ feedback.innerText = "Keep looking! 😊";
+ feedback.style.color = "#e74c3c";
+ }
+ };
+
+ renderHalves();
+}
+
+// --- Game Functions ---
+
+function startAnimalColoring(container) {
+ console.log("Starting Animal Coloring selection...");
+ if (!animalTemplates || Object.keys(animalTemplates).length === 0) {
+ console.error("No animal templates found!");
+ container.innerHTML = "Sorry, no animals found! Please refresh the page.
";
+ return;
+ }
+
+ let animalCards = Object.keys(animalTemplates).map(name => {
+ let icon = '🐾';
+ if (name === 'Dog') icon = '🐶';
+ else if (name === 'Cat') icon = '🐱';
+ else if (name === 'Lion') icon = '🦁';
+ else if (name === 'Bunny') icon = '🐰';
+ else if (name === 'Fish') icon = '🐠';
+
+ return `
+ `;
+ }).join('');
+
+ container.innerHTML = `
+ Pick an animal friend to color!
+
+ ${animalCards}
+
+
+
+
+ `;
+}
+
+function selectAnimalTemplate(name) {
+ const container = document.getElementById('game-container');
+ const templateSvg = animalTemplates[name];
+ const referenceSvg = templateSvg.replace(/data-ref-fill="([^"]+)"/g, 'fill="$1"');
+ const drawingSvg = templateSvg.replace(/data-ref-fill="([^"]+)"/g, (match, p1) => {
+ if (p1 === 'none') return 'fill="none"';
+ return 'fill="#fff" class="block-to-color" onclick="window.colorBlock(this)"';
+ });
+
+ const palette = [
+ '#FF5252', '#FF4081', '#E040FB', '#7C4DFF',
+ '#536DFE', '#448AFF', '#40C4FF', '#18FFFF',
+ '#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41',
+ '#FFFF00', '#FFD740', '#FFAB40', '#FF6E40',
+ '#8D6E63', '#9E9E9E', '#CFD8DC', '#000000', '#FFFFFF'
+ ];
+
+ container.innerHTML = `
+
+
+
Look at this!
+
+
+
+
+
+
+
+ Step 1: Click a color ⬇️ | Step 2: Click the animal 🎨
+
+
+ ${palette.map(c => `
+
+
+ `).join('')}
+
+
+
+
+
+
+
+ `;
+ const firstSwatch = document.querySelector('.color-swatch');
+ if (firstSwatch) window.selectPaletteColor(palette[0], firstSwatch);
+}
+
+function startLearnEmotions(container) {
+ const basicEmotions = [
+ { name: 'Happy', emoji: '😊', color: '#f1c40f' },
+ { name: 'Sad', emoji: '😢', color: '#3498db' },
+ { name: 'Angry', emoji: '😠', color: '#e74c3c' },
+ { name: 'Silly', emoji: '😜', color: '#9b59b6' }
+ ];
+
+ const target = basicEmotions[Math.floor(Math.random() * basicEmotions.length)];
+ const options = [...basicEmotions].sort(() => 0.5 - Math.random());
+
+ container.innerHTML = `
+ Can you find the ${target.name.toUpperCase()} face?
+
+
+ ${target.emoji}
+
+
+
+ ${options.map(m => `
+
+
${m.name}
+
+ `).join('')}
+
+
+
+
+
+
+
+
+ `;
+}
+
+window.checkBasicEmotion = function(selected, target) {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "Yay! You got it! 🌟";
+ feedback.style.color = "#2ecc71";
+ gameScore += 10;
+ document.getElementById('game-score').innerText = gameScore;
+ setTimeout(() => startLearnEmotions(document.getElementById('game-container')), 1500);
+ } else {
+ feedback.innerText = "Not quite, try again! 😊";
+ feedback.style.color = "#e74c3c";
+ }
+};
+
+function startShapeMatch(container) {
+ const shapes = [
+ { name: 'Circle', icon: '🔴' },
+ { name: 'Square', icon: '🟧' },
+ { name: 'Triangle', icon: '🔺' },
+ { name: 'Star', icon: '⭐' },
+ { name: 'Heart', icon: '❤️' },
+ { name: 'Moon', icon: '🌙' }
+ ];
+ const target = shapes[Math.floor(Math.random() * shapes.length)];
+ const options = [...shapes].sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ Can you find the ${target.name.toUpperCase()}?
+
+
+ ${options.map(s => `
+
+ ${s.icon}
+
+ `).join('')}
+
+
+
+
+
+
+
+
+ `;
+}
+
+window.checkShape = function(selected, target) {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "Correct! You found the " + target + "! 🌟";
+ feedback.style.color = "#2ecc71";
+ gameScore += 10;
+ document.getElementById('game-score').innerText = gameScore;
+ setTimeout(() => startShapeMatch(document.getElementById('game-container')), 1200);
+ } else {
+ feedback.innerText = "Not that one. Try again! 🤔";
+ feedback.style.color = "#e74c3c";
+ }
+};
+
+function startPatternMatch(container) {
+ const emojiSets = [
+ ['🍎', '🍌'], ['🐶', '🐱'], ['☀️', '🌙'], ['🚗', '🚲'], ['🎈', '🎁'], ['🍦', '🍰']
+ ];
+ const set = emojiSets[Math.floor(Math.random() * emojiSets.length)];
+ const pattern = [set[0], set[1], set[0], set[1]];
+ const target = set[0];
+ const options = [...set].sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ What comes next in the pattern?
+
+
${pattern[0]}
+
➡️
+
${pattern[1]}
+
➡️
+
${pattern[2]}
+
➡️
+
${pattern[3]}
+
➡️
+
?
+
+
+ ${options.map(emoji => `
+
+ ${emoji}
+
+ `).join('')}
+
+
+
+
+
+
+
+ `;
+}
+
+window.checkPattern = function(selected, target) {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "Wow! You're so smart! 🌟";
+ feedback.style.color = "#2ecc71";
+ gameScore += 10;
+ document.getElementById('game-score').innerText = gameScore;
+ const placeholders = document.querySelectorAll('#game-container div');
+ placeholders.forEach(div => {
+ if (div.innerText === '?') {
+ div.innerText = target;
+ div.style.color = '#000';
+ div.style.borderColor = '#2ecc71';
+ }
+ });
+ setTimeout(() => startPatternMatch(document.getElementById('game-container')), 1500);
+ } else {
+ feedback.innerText = "Not quite, try looking at the sequence again! 😊";
+ feedback.style.color = "#e74c3c";
+ }
+};
+
+function startColorMatch(container) {
+ const colors = [
+ { name: 'Red', color: '#e74c3c' }, { name: 'Blue', color: '#3498db' }, { name: 'Green', color: '#2ecc71' },
+ { name: 'Yellow', color: '#f1c40f' }, { name: 'Purple', color: '#9b59b6' }, { name: 'Orange', color: '#e67e22' }
+ ];
+ const target = colors[Math.floor(Math.random() * colors.length)];
+
+ container.innerHTML = `
+ Pick the ${target.name.toUpperCase()} block!
+
+ ${colors.map(c => `
+
+
+ `).join('')}
+
+
+
+
+
+
+
+ `;
+}
+
+window.checkColor = function(selected, target) {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "Correct! You're a Color Master! 🎨";
+ feedback.style.color = "#2ecc71";
+ gameScore += 10;
+ document.getElementById('game-score').innerText = gameScore;
+ setTimeout(() => startColorMatch(document.getElementById('game-container')), 1200);
+ } else {
+ feedback.innerText = "Oops! Not that one. Try again!";
+ feedback.style.color = "#e74c3c";
+ }
+};
+
+let flippedCards = [];
+function startMemoryGame(container) {
+ const items = ['😊', '😢', '🐶', '🐱'];
+ const bonus = '⭐';
+ let deck = [...items, ...items, bonus];
+ deck.sort(() => Math.random() - 0.5);
+ const cardGradients = [
+ 'linear-gradient(135deg, #ff7eb3, #ff758c)', 'linear-gradient(135deg, #4facfe, #00f2fe)',
+ 'linear-gradient(135deg, #43e97b, #38f9d7)', 'linear-gradient(135deg, #fa709a, #fee140)',
+ 'linear-gradient(135deg, #667eea, #764ba2)', 'linear-gradient(135deg, #f093fb, #f5576c)',
+ 'linear-gradient(135deg, #5ee7df, #b490ca)', 'linear-gradient(135deg, #c3cfe2, #c3cfe2)',
+ 'linear-gradient(135deg, #f6d365, #fda085)'
+ ];
+ flippedCards = [];
+ container.innerHTML = `
+ Find the 4 pairs + The Magic Star!
+
+ ${deck.map((item, index) => `
+
+ ${item}
+
+ `).join('')}
+
+
+
+
+
+
+
+ `;
+}
+
+window.flipCard = function(index, item) {
+ const card = document.getElementById(`card-${index}`);
+ const content = card.querySelector('.content');
+ const feedback = document.getElementById('feedback');
+ if (card.classList.contains('matched') || !content.classList.contains('hidden') || flippedCards.length >= 2) return;
+ if (item === '⭐') {
+ content.classList.remove('hidden');
+ card.style.background = 'linear-gradient(135deg, #fff95b, #ff930f)';
+ card.classList.add('matched');
+ feedback.innerText = "You found the Magic Star! +50 Points! 🌟";
+ feedback.style.color = "#f39c12";
+ gameScore += 50;
+ document.getElementById('game-score').innerText = gameScore;
+ return;
+ }
+ content.classList.remove('hidden');
+ card.style.background = '#ffffff';
+ flippedCards.push({ index, item });
+ if (flippedCards.length === 2) setTimeout(checkMatch, 800);
+};
+
+function checkMatch() {
+ const [c1, c2] = flippedCards;
+ const card1 = document.getElementById(`card-${c1.index}`);
+ const card2 = document.getElementById(`card-${c2.index}`);
+ const feedback = document.getElementById('feedback');
+ if (c1.item === c2.item) {
+ card1.classList.add('matched');
+ card2.classList.add('matched');
+ card1.style.background = 'linear-gradient(135deg, #43e97b, #38f9d7)';
+ card2.style.background = 'linear-gradient(135deg, #43e97b, #38f9d7)';
+ feedback.innerText = "Match found! 🎉";
+ feedback.style.color = "#2ecc71";
+ gameScore += 20;
+ } else {
+ card1.querySelector('.content').classList.add('hidden');
+ card2.querySelector('.content').classList.add('hidden');
+ card1.style.background = '';
+ card2.style.background = '';
+ feedback.innerText = "Not a match, try again!";
+ feedback.style.color = "#e74c3c";
+ }
+ document.getElementById('game-score').innerText = gameScore;
+ flippedCards = [];
+}
+
+let selectedBrushColor = '#f1c40f';
+function startColoringBook(container) {
+ container.innerHTML = `
+ Choose a picture to color!
+
+ ${Object.keys(coloringTemplates).map(name => `
+
+
${name === 'House' ? '🏠' : name === 'Flower' ? '🌸' : '🚗'}
+
${name}
+
+ `).join('')}
+
+ `;
+}
+
+window.selectTemplate = function(name) {
+ const container = document.getElementById('game-container');
+ const templateSvg = coloringTemplates[name];
+ const referenceSvg = templateSvg.replace(/data-ref-fill="([^"]+)"/g, 'fill="$1"');
+ const drawingSvg = templateSvg.replace(/data-ref-fill="([^"]+)"/g, 'fill="#fff" class="block-to-color" onclick="window.colorBlock(this)"');
+ container.innerHTML = `
+
+
+
Reference
+
+
+
+
+
+
+
+ ${['#f1c40f', '#f39c12', '#e74c3c', '#34495e', '#95a5a6', '#2ecc71', '#3498db', '#ecf0f1', '#333'].map(c => `
+
+ `).join('')}
+
+
+
+
+
+
+
+ `;
+ const firstSwatch = document.querySelector('.color-swatch');
+ if (firstSwatch) window.selectPaletteColor('#f1c40f', firstSwatch);
+};
+
+window.selectPaletteColor = function(color, element) {
+ selectedBrushColor = color;
+ document.querySelectorAll('.color-swatch').forEach(s => s.style.border = 'none');
+ element.style.border = '3px solid #333';
+};
+
+window.colorBlock = function(element) {
+ element.setAttribute('fill', selectedBrushColor);
+ gameScore += 5;
+ document.getElementById('game-score').innerText = gameScore;
+};
+
+window.nextImage = function(currentName) {
+ const keys = Object.keys(coloringTemplates);
+ const currentIndex = keys.indexOf(currentName);
+ const nextIndex = (currentIndex + 1) % keys.length;
+ window.selectTemplate(keys[nextIndex]);
+};
+
+function closeGame() {
+ window.location.href = '/activities';
+}
+
+function restartGame() {
+ startGame(activeGame);
+}
+
+async function finishGame() {
+ const duration = Math.floor((Date.now() - startTime) / 1000);
+ const res = await fetch('/activities/log-activity', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ child_id: selectedChildId,
+ activity_name: activeGame,
+ score: gameScore,
+ duration_seconds: duration
+ })
+ });
+ if (res.ok) {
+ alert(`Great Job! You scored ${gameScore} points! 🎉`);
+ window.location.href = '/activities';
+ }
+}
+
+let currentQuizType = "";
+let currentQuestionIndex = 0;
+let quizScore = 0;
+
+function startNewQuiz(type) {
+ if (!selectedChildId) return alert("Please select a child profile first.");
+ currentQuizType = type;
+ currentQuestionIndex = 0;
+ quizScore = 0;
+ document.getElementById('quiz-selection').classList.add('hidden');
+ document.getElementById('quiz-area').classList.remove('hidden');
+ document.getElementById('quiz-content').classList.remove('hidden');
+ document.getElementById('quiz-results').classList.add('hidden');
+ document.getElementById('current-quiz-name').innerText = type;
+ loadQuizQuestion();
+}
+
+function loadQuizQuestion() {
+ const questions = quizData[currentQuizType];
+ const q = questions[currentQuestionIndex];
+ document.getElementById('question-counter').innerText = `Question ${currentQuestionIndex + 1} of ${questions.length}`;
+ document.getElementById('quiz-question-text').innerText = q.question;
+ const progress = (currentQuestionIndex / questions.length) * 100;
+ document.getElementById('progress-fill').style.width = `${progress}%`;
+ const optionsGrid = document.getElementById('quiz-options-grid');
+ optionsGrid.innerHTML = '';
+ q.options.forEach(opt => {
+ const div = document.createElement('div');
+ div.className = 'card quiz-option';
+ div.innerHTML = `${opt.text.split(' ').pop()}${opt.text.split(' ').slice(0, -1).join(' ')}
`;
+ div.onclick = () => window.selectQuizOption(opt.correct);
+ optionsGrid.appendChild(div);
+ });
+}
+
+window.selectQuizOption = function(isCorrect) {
+ if (isCorrect) {
+ quizScore++;
+ alert("Correct! Great thinking! 🌟");
+ } else {
+ alert("Not quite. Let's try to remember for next time! 👍");
+ }
+ currentQuestionIndex++;
+ if (currentQuestionIndex < quizData[currentQuizType].length) loadQuizQuestion();
+ else finishQuiz();
+};
+
+async function finishQuiz() {
+ const total = quizData[currentQuizType].length;
+ await fetch('/activities/log-quiz', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ child_id: selectedChildId,
+ quiz_name: currentQuizType,
+ score: quizScore,
+ total_questions: total
+ })
+ });
+ document.getElementById('progress-fill').style.width = `100%`;
+ document.getElementById('quiz-content').classList.add('hidden');
+ document.getElementById('quiz-results').classList.remove('hidden');
+ document.getElementById('final-score-text').innerText = `You got ${quizScore} out of ${total} correct!`;
+}
+
+async function loadChildrenForDashboard() {
+ if (!userId) return;
+ const res = await fetch(`/auth/children/${userId}`);
+ const children = await res.json();
+ const select = document.getElementById('dashboard-child-select');
+ if (!select) return;
+ select.innerHTML = '';
+ children.forEach(c => {
+ const isSelected = selectedChildId == c.id ? 'selected' : '';
+ select.innerHTML += ``;
+ });
+ if (selectedChildId) loadProgress();
+}
+
+function getLevelInfo(age) {
+ if (age >= 10) return { level: 3, name: "Level 3: Advanced (Ages 10+)", desc: "Mood analysis and emotional intelligence.", theme: "purple" };
+ if (age >= 7) return { level: 2, name: "Level 2: Intermediate (Ages 7-9)", desc: "Patterns and logical puzzles.", theme: "sand" };
+ return { level: 1, name: "Level 1: Basic (Ages 3-6)", desc: "Colors and creative expression.", theme: "orange" };
+}
+
+async function syncChildAge() {
+ const childId = localStorage.getItem('selectedChildId');
+ if (!childId || !userId) return;
+ try {
+ const res = await fetch(`/auth/children/${userId}`);
+ const children = await res.json();
+ const child = children.find(c => c.id == childId);
+ if (child) {
+ localStorage.setItem('selectedChildAge', child.age);
+ localStorage.setItem('selectedChildSensory', child.sensory_level);
+ applySensoryUI(child.sensory_level);
+ }
+ } catch (err) { console.error("Failed to sync child age:", err); }
+}
+
+async function loadProgress() {
+ const select = document.getElementById('dashboard-child-select');
+ if (!select) return;
+ const childId = select.value;
+ if (!childId) return;
+ localStorage.setItem('selectedChildId', childId);
+ const resChild = await fetch(`/auth/children/${userId}`);
+ const children = await resChild.json();
+ const child = children.find(c => c.id == childId);
+ if (child) {
+ localStorage.setItem('selectedChildAge', child.age);
+ const levelInfo = getLevelInfo(child.age);
+ const levelDisplay = document.getElementById('dashboard-level-display');
+ if (levelDisplay) {
+ levelDisplay.innerHTML = `
+
+
🌟 Current Level: ${levelInfo.name}
+
${levelInfo.desc}
+
+ `;
+ }
+ }
+ const resAct = await fetch(`/dashboard/progress/${childId}`);
+ const data = await resAct.json();
+ const ctxAct = document.getElementById('activitiesChart');
+ if (ctxAct) {
+ new Chart(ctxAct.getContext('2d'), {
+ type: 'bar',
+ data: {
+ labels: data.activities.map(a => a.name),
+ datasets: [{ label: 'Avg Score', data: data.activities.map(a => a.avg_score), backgroundColor: '#8d6e63' }]
+ }
+ });
+ }
+ const ctxEmo = document.getElementById('emotionChart');
+ if (ctxEmo) {
+ new Chart(ctxEmo.getContext('2d'), {
+ type: 'pie',
+ data: {
+ labels: data.emotions.map(e => e.emotion),
+ datasets: [{ data: data.emotions.map(e => e.count), backgroundColor: ['#8d6e63', '#d6c6a2', '#a1887f', '#d7ccc8', '#bcaaa4', '#efebe9'] }]
+ }
+ });
+ }
+}
+
+async function generateReport() {
+ const select = document.getElementById('dashboard-child-select');
+ if (!select) return;
+ const childId = select.value;
+ if (!childId) return alert("Select child.");
+ const res = await fetch(`/dashboard/generate-report/${childId}`);
+ const data = await res.json();
+ const reportLink = document.getElementById('report-link');
+ if (reportLink) reportLink.innerHTML = `View Report PDF`;
+}
+
+async function saveDiary() {
+ if (!userId) return alert("Login first.");
+ const titleVal = document.getElementById('diary-title');
+ const msgVal = document.getElementById('diary-message');
+ if (!titleVal || !msgVal) return;
+ const title = titleVal.value;
+ const message = msgVal.value;
+ const fileInput = document.getElementById('diary-upload');
+ const file = fileInput ? fileInput.files[0] : null;
+ const formData = new FormData();
+ formData.append('parent_id', userId);
+ formData.append('child_name', "Child");
+ formData.append('title', title);
+ formData.append('message', message);
+ if (file) formData.append('file', file);
+ const res = await fetch('/diary/add', { method: 'POST', body: formData });
+ if (res.ok) {
+ alert("Memory Saved!");
+ loadDiary();
+ titleVal.value = '';
+ msgVal.value = '';
+ }
+}
+
+async function loadDiary() {
+ if (!userId) return;
+ const res = await fetch(`/diary/${userId}`);
+ const diary = await res.json();
+ const list = document.getElementById('diary-timeline');
+ if (!list) return;
+ list.innerHTML = '';
+ diary.forEach(e => {
+ const div = document.createElement('div');
+ div.className = 'card';
+ div.innerHTML = `${e.title}
${e.message}
${e.timestamp}`;
+ if (e.image_path) div.innerHTML += `
`;
+ list.appendChild(div);
+ });
+}
+
+function startMoodMatch(container) {
+ const moods = [
+ { name: 'Happy', emoji: '😊', color: '#f1c40f' }, { name: 'Sad', emoji: '😢', color: '#3498db' },
+ { name: 'Angry', emoji: '😠', color: '#e74c3c' }, { name: 'Surprised', emoji: '😲', color: '#9b59b6' },
+ { name: 'Scared', emoji: '😨', color: '#2ecc71' }, { name: 'Sleepy', emoji: '😴', color: '#95a5a6' }
+ ];
+ const shuffled = [...moods].sort(() => 0.5 - Math.random());
+ const options = shuffled.slice(0, 3);
+ const target = options[Math.floor(Math.random() * options.length)];
+ container.innerHTML = `
+ How is the friend feeling?
+
+
+ ${options.map(m => `
${m.name}
`).join('')}
+
+
+
+
+
+
+
+ `;
+}
+
+window.checkMood = function(selected, target) {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "Yes! That's exactly right! 🌟";
+ feedback.style.color = "#2ecc71";
+ gameScore += 15;
+ document.getElementById('game-score').innerText = gameScore;
+ setTimeout(() => startMoodMatch(document.getElementById('game-container')), 1500);
+ } else {
+ feedback.innerText = "Not quite. Look closely at the face! 😊";
+ feedback.style.color = "#e74c3c";
+ }
+};
+
+// --- Level 3 Advanced Game Functions ---
+
+function startAbcSort(container) {
+ const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
+ const lowercase = "abcdefghijklmnopqrstuvwxyz".split("");
+ let currentSet = [];
+
+ window.renderAbcSort = () => {
+ // Pick 4 random pairs
+ const indices = [];
+ while(indices.length < 4) {
+ const r = Math.floor(Math.random() * 26);
+ if(!indices.includes(r)) indices.push(r);
+ }
+
+ const items = [];
+ indices.forEach(i => {
+ items.push({ val: uppercase[i], type: 'upper' });
+ items.push({ val: lowercase[i], type: 'lower' });
+ });
+
+ items.sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ Sort into Upper and Lower Case
+
+
+ ${items.map((item, i) => `
+
+ ${item.val}
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.allowDrop = (ev) => ev.preventDefault();
+ window.drag = (ev) => {
+ ev.dataTransfer.setData("text", ev.target.id);
+ ev.dataTransfer.setData("type", ev.target.getAttribute("data-type"));
+ };
+ window.drop = (ev, binType) => {
+ ev.preventDefault();
+ const data = ev.dataTransfer.getData("text");
+ const itemType = ev.dataTransfer.getData("type");
+ const draggedEl = document.getElementById(data);
+ const feedback = document.getElementById('feedback');
+
+ if (itemType === binType) {
+ let bin = ev.target;
+ if (!bin.classList.contains('bin-content')) {
+ bin = bin.querySelector('.bin-content') || bin.closest('div').querySelector('.bin-content');
+ }
+ bin.appendChild(draggedEl);
+ draggedEl.setAttribute("draggable", "false");
+ draggedEl.style.cursor = "default";
+ gameScore += 10;
+ const scoreEl = document.getElementById('game-score');
+ if (scoreEl) scoreEl.innerText = gameScore;
+
+ const sourceBin = document.getElementById('source-bin');
+ if (sourceBin && sourceBin.children.length === 0) {
+ feedback.innerText = "Great sorting! 🌟";
+ feedback.style.color = "var(--success)";
+ setTimeout(window.renderAbcSort, 1500);
+ }
+ } else {
+ feedback.innerText = "Try the other bin! 😊";
+ feedback.style.color = "var(--danger)";
+ setTimeout(() => feedback.innerText = "", 1500);
+ }
+ };
+
+ window.renderAbcSort();
+}
+
+function startMissingLetter(container) {
+ const words = [
+ { word: 'APPLE', missing: 0, icon: '🍎' },
+ { word: 'BREAD', missing: 2, icon: '🍞' },
+ { word: 'CANDY', missing: 1, icon: '🍬' },
+ { word: 'DRESS', missing: 3, icon: '👗' },
+ { word: 'EARTH', missing: 4, icon: '🌍' },
+ { word: 'FROGS', missing: 2, icon: '🐸' }
+ ];
+
+ window.renderMissingLetter = () => {
+ const target = words[Math.floor(Math.random() * words.length)];
+ const wordArr = target.word.split("");
+ const correctLetter = wordArr[target.missing];
+ wordArr[target.missing] = "_";
+
+ // Generate options including correct one
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
+ const options = [correctLetter];
+ while(options.length < 4) {
+ const r = alphabet[Math.floor(Math.random() * 26)];
+ if(!options.includes(r)) options.push(r);
+ }
+ options.sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ Fill in the missing letter!
+
+
${target.icon}
+
+ ${wordArr.join("")}
+
+
+
+ ${options.map(letter => `
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.checkMissingLetter = (selected, target) => {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "Excellent spelling! 🌟";
+ feedback.style.color = "var(--success)";
+ gameScore += 20;
+ const scoreEl = document.getElementById('game-score');
+ if (scoreEl) scoreEl.innerText = gameScore;
+ setTimeout(window.renderMissingLetter, 1500);
+ } else {
+ feedback.innerText = "Try another letter! 😊";
+ feedback.style.color = "var(--danger)";
+ }
+ };
+
+ window.renderMissingLetter();
+}
+
+function startWordPictureMatch(container) {
+ const items = [
+ { word: 'Elephant', icon: '🐘' }, { word: 'Rocket', icon: '🚀' },
+ { word: 'Guitar', icon: '🎸' }, { word: 'Pizza', icon: '🍕' },
+ { word: 'Rainbow', icon: '🌈' }, { word: 'Bicycle', icon: '🚲' }
+ ];
+
+ window.renderWordPicture = () => {
+ const target = items[Math.floor(Math.random() * items.length)];
+ const options = [...items].sort(() => Math.random() - 0.5).slice(0, 4);
+ if (!options.find(o => o.word === target.word)) {
+ options[Math.floor(Math.random() * 4)] = target;
+ }
+
+ container.innerHTML = `
+ Match the picture to the word!
+
+
${target.word.toUpperCase()}
+
+
+ ${options.map(opt => `
+
+ ${opt.icon}
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.checkWordPicture = (selected, target) => {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ feedback.innerText = "Perfect Match! 🌟";
+ feedback.style.color = "var(--success)";
+ gameScore += 15;
+ const scoreEl = document.getElementById('game-score');
+ if (scoreEl) scoreEl.innerText = gameScore;
+ setTimeout(window.renderWordPicture, 1500);
+ } else {
+ feedback.innerText = "That's a different one! 😊";
+ feedback.style.color = "var(--danger)";
+ }
+ };
+
+ window.renderWordPicture();
+}
+
+function startObjectSearch(container) {
+ const objects = [
+ { name: 'Red Ball', icon: '🔴', color: 'red' }, { name: 'Blue Square', icon: '🟦', color: 'blue' },
+ { name: 'Yellow Star', icon: '⭐', color: 'yellow' }, { name: 'Green Apple', icon: '🍏', color: 'green' },
+ { name: 'Purple Heart', icon: '💜', color: 'purple' }, { name: 'Orange Orange', icon: '🍊', color: 'orange' }
+ ];
+
+ window.renderObjectSearch = () => {
+ const target = objects[Math.floor(Math.random() * objects.length)];
+ // Create a grid of 12 items
+ const gridItems = [];
+ for(let i=0; i<11; i++) {
+ gridItems.push(objects[Math.floor(Math.random() * objects.length)]);
+ }
+ gridItems.push(target);
+ gridItems.sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ Find the ${target.name}!
+
+ ${gridItems.map((item, i) => `
+
+ ${item.icon}
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.checkObjectSearch = (selected, target, el) => {
+ const feedback = document.getElementById('feedback');
+ if (selected === target) {
+ el.style.background = "#e8f5e9";
+ el.style.transform = "scale(1.1)";
+ feedback.innerText = "Found it! Well done! 🌟";
+ feedback.style.color = "var(--success)";
+ gameScore += 10;
+ const scoreEl = document.getElementById('game-score');
+ if (scoreEl) scoreEl.innerText = gameScore;
+ setTimeout(window.renderObjectSearch, 1500);
+ } else {
+ el.style.opacity = "0.3";
+ feedback.innerText = "Keep looking... 🔍";
+ feedback.style.color = "var(--text)";
+ }
+ };
+
+ window.renderObjectSearch();
+}
+
+function startSpatialPuzzle(container) {
+ const puzzles = [
+ { full: '🧩', parts: ['🟦', '🟩', '🟨'] },
+ { full: '🏠', parts: ['📐', '🧱', '🚪'] },
+ { full: '🌸', parts: ['🍃', '📍', '💗'] }
+ ];
+
+ window.renderSpatialPuzzle = () => {
+ const target = puzzles[Math.floor(Math.random() * puzzles.length)];
+
+ container.innerHTML = `
+ Spatial Reasoning: Build the Object
+
+
+ ?
+
+
+ ${target.parts.map(part => `
+
+ ${part}
+
+ `).join('')}
+
+
+
+
+
+
+
+ `;
+
+ let piecesPlaced = 0;
+ window.addSpatialPiece = (el, fullIcon, totalPieces) => {
+ if (el.style.opacity === "0.5") return;
+ el.style.opacity = "0.5";
+ piecesPlaced++;
+ gameScore += 5;
+ const scoreEl = document.getElementById('game-score');
+ if (scoreEl) scoreEl.innerText = gameScore;
+
+ if (piecesPlaced >= totalPieces) {
+ const feedback = document.getElementById('feedback');
+ const puzzleTarget = document.getElementById('puzzle-target');
+ if (feedback) {
+ feedback.innerText = "Puzzle Complete! 🌟";
+ feedback.style.color = "var(--success)";
+ }
+ if (puzzleTarget) {
+ puzzleTarget.innerText = fullIcon;
+ puzzleTarget.style.background = "#fff";
+ puzzleTarget.style.borderColor = "var(--success)";
+ }
+ setTimeout(window.renderSpatialPuzzle, 2000);
+ }
+ };
+ };
+
+ window.renderSpatialPuzzle();
+}
+
+function startAlphabetOrder(container) {
+ const letters = "ABCDEFG".split("");
+
+ window.renderAlphabetOrder = () => {
+ const items = [...letters].sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ Put the letters in order! (A to G)
+
+ ${letters.map((_, i) => `
+
+ ${i+1}
+
+ `).join('')}
+
+
+ ${items.map((item, i) => `
+
+ ${item}
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.dropOrder = (ev, targetIdx) => {
+ ev.preventDefault();
+ const data = ev.dataTransfer.getData("text");
+ const draggedEl = document.getElementById(data);
+ if (!draggedEl) return;
+ const val = draggedEl.getAttribute("data-val");
+ const feedback = document.getElementById('feedback');
+
+ if (val === letters[targetIdx]) {
+ ev.target.innerText = val;
+ ev.target.style.background = "#e8f5e9";
+ ev.target.style.borderColor = "#4caf50";
+ ev.target.style.color = "#2e7d32";
+ ev.target.style.borderStyle = "solid";
+ draggedEl.remove();
+ gameScore += 15;
+ const scoreEl = document.getElementById('game-score');
+ if (scoreEl) scoreEl.innerText = gameScore;
+
+ const sourceRow = document.getElementById('source-row');
+ if (sourceRow && sourceRow.children.length === 0) {
+ feedback.innerText = "Alphabet Master! 🌟";
+ feedback.style.color = "var(--success)";
+ setTimeout(window.renderAlphabetOrder, 2000);
+ }
+ } else {
+ feedback.innerText = "That's not where " + val + " goes! 😊";
+ feedback.style.color = "var(--danger)";
+ setTimeout(() => feedback.innerText = "", 1500);
+ }
+ };
+
+ window.renderAlphabetOrder();
+}
+
+function startJigsawPuzzle(container) {
+ const images = [
+ { icon: '🐶', name: 'Puppy' }, { icon: '🐱', name: 'Kitty' },
+ { icon: '🦁', name: 'Lion' }, { icon: '🐘', name: 'Elephant' }
+ ];
+
+ window.renderJigsaw = () => {
+ const target = images[Math.floor(Math.random() * images.length)];
+ const parts = [1, 2, 3, 4];
+ const shuffledParts = [...parts].sort(() => Math.random() - 0.5);
+
+ container.innerHTML = `
+ Complete the Jigsaw!
+
+ ${parts.map(p => `
+
+ ${p}
+
+ `).join('')}
+
+
+ ${shuffledParts.map(p => `
+
+ ${target.icon}
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.dropJigsaw = (ev, targetPart) => {
+ ev.preventDefault();
+ const data = ev.dataTransfer.getData("text");
+ const draggedEl = document.getElementById(data);
+ if (!draggedEl) return;
+ const part = draggedEl.getAttribute("data-part");
+ const feedback = document.getElementById('feedback');
+
+ if (parseInt(part) === targetPart) {
+ ev.target.innerHTML = draggedEl.innerHTML;
+ ev.target.style.background = "var(--secondary)";
+ ev.target.style.borderColor = "var(--primary)";
+ ev.target.style.borderStyle = "solid";
+ draggedEl.remove();
+ gameScore += 20;
+ const scoreEl = document.getElementById('game-score');
+ if (scoreEl) scoreEl.innerText = gameScore;
+
+ const pieces = document.getElementById('jigsaw-pieces');
+ if (pieces && pieces.children.length === 0) {
+ feedback.innerText = "Puzzle Solved! 🌟";
+ feedback.style.color = "var(--success)";
+ setTimeout(window.renderJigsaw, 2000);
+ }
+ } else {
+ feedback.innerText = "Wrong spot! 😊";
+ feedback.style.color = "var(--danger)";
+ setTimeout(() => feedback.innerText = "", 1500);
+ }
+ };
+
+ window.renderJigsaw();
+}
+
+function startAdvancedPatterns(container) {
+ const sequences = [
+ { pattern: ['🔴', '🔵', '🟢', '🔴', '🔵'], next: '🟢', options: ['🟢', '🟡', '🟠'] },
+ { pattern: ['1', '2', '4', '8'], next: '16', options: ['10', '16', '12'] },
+ { pattern: ['⬆️', '➡️', '⬇️', '⬅️', '⬆️'], next: '➡️', options: ['⬇️', '➡️', '⬆️'] }
+ ];
+
+ window.renderAdvancedPattern = () => {
+ const target = sequences[Math.floor(Math.random() * sequences.length)];
+
+ container.innerHTML = `
+ Complete the Logic Pattern
+
+ ${target.pattern.map(item => `
${item}
`).join('
•
')}
+
•
+
?
+
+
+ ${target.options.map(opt => `
+
+ `).join('')}
+
+
+
+
+
+
+ `;
+ };
+
+ window.checkAdvancedPattern = (selected, target) => {
+ const feedback = document.getElementById('feedback');
+ const patternTarget = document.getElementById('pattern-target');
+ if (selected === target) {
+ if (feedback) {
+ feedback.innerText = "Brilliant Logic! 🌟";
+ feedback.style.color = "var(--success)";
+ }
+ gameScore += 25;
+ const scoreEl = document.getElementById('game-score');
+ if (scoreEl) scoreEl.innerText = gameScore;
+ if (patternTarget) {
+ patternTarget.innerText = target;
+ patternTarget.style.color = "#000";
+ patternTarget.style.borderColor = "var(--success)";
+ }
+ setTimeout(window.renderAdvancedPattern, 2000);
+ } else {
+ if (feedback) {
+ feedback.innerText = "Look closer at the sequence! 😊";
+ feedback.style.color = "var(--danger)";
+ }
+ }
+ };
+
+ window.renderAdvancedPattern();
+}
+
+// Final Window Exports
+window.startCamera = startCamera;
+window.stopCamera = stopCamera;
+window.capturePhoto = capturePhoto;
+window.uploadEmotion = uploadEmotion;
+window.confirmEmotion = confirmEmotion;
+window.toggleCustomEmotion = toggleCustomEmotion;
+window.saveCorrection = saveCorrection;
+window.trainAI = trainAI;
+window.startGame = startGame;
+window.startNewQuiz = startNewQuiz;
+window.saveDiary = saveDiary;
+window.logout = logout;
+window.register = register;
+window.login = login;
+window.addChild = addChild;
+window.selectChild = selectChild;
+window.deleteChild = deleteChild;
+window.toggleAuth = toggleAuth;
+window.syncChildAge = syncChildAge;
+window.loadProgress = loadProgress;
+window.generateReport = generateReport;
+window.startAnimalColoring = startAnimalColoring;
+window.selectAnimalTemplate = selectAnimalTemplate;
+window.startLearnEmotions = startLearnEmotions;
+window.startShapeMatch = startShapeMatch;
+window.startPatternMatch = startPatternMatch;
+window.startColorMatch = startColorMatch;
+window.startMemoryGame = startMemoryGame;
+window.startColoringBook = startColoringBook;
+window.selectTemplate = selectTemplate;
+window.selectPaletteColor = selectPaletteColor;
+window.colorBlock = colorBlock;
+window.nextImage = nextImage;
+window.closeGame = closeGame;
+window.restartGame = restartGame;
+window.finishGame = finishGame;
+window.startMoodMatch = startMoodMatch;
+window.startAbcSort = startAbcSort;
+window.startAlphabetOrder = startAlphabetOrder;
+window.startMissingLetter = startMissingLetter;
+window.startWordPictureMatch = startWordPictureMatch;
+window.startObjectSearch = startObjectSearch;
+window.startSpatialPuzzle = startSpatialPuzzle;
+window.startJigsawPuzzle = startJigsawPuzzle;
+window.startAdvancedPatterns = startAdvancedPatterns;