// 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 } ] }, { question: "Someone says 'Hello' to you. What is a nice way to respond?", options: [ { text: "Smile and say 'Hi'! ๐Ÿ‘‹", correct: true }, { text: "Look at the floor โฌ‡๏ธ", correct: false }, { text: "Walk away ๐Ÿšถ", correct: false } ] }, { question: "You accidentally bumped into a friend. What do you say?", options: [ { text: "I'm sorry! ๐Ÿ™Š", correct: true }, { text: "It was your fault! ๐Ÿ˜ ", correct: false }, { text: "Say nothing ๐Ÿค", correct: false } ] }, { question: "A friend is talking, but you have something to say too. What should you do?", options: [ { text: "Wait for them to finish, then speak ๐Ÿ™Š", correct: true }, { text: "Interrupt them immediately ๐Ÿ“ข", correct: false }, { text: "Start talking louder ๐Ÿ—ฃ๏ธ", 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 } ] }, { question: "What should you do right after you wake up in the morning?", options: [ { text: "Wash your face and get dressed โ˜€๏ธ", correct: true }, { text: "Go to sleep ๐Ÿ˜ด", correct: false }, { text: "Eat dinner ๐Ÿฝ๏ธ", correct: false } ] }, { question: "What is the first thing you do before eating your lunch?", options: [ { text: "Wash your hands ๐Ÿงผ", correct: true }, { text: "Run outside ๐ŸŒณ", correct: false }, { text: "Start singing ๐ŸŽค", correct: false } ] }, { question: "Where do your toys go when you are finished playing?", options: [ { text: "In the toy box ๐Ÿ“ฆ", correct: true }, { text: "On the floor ๐Ÿงน", correct: false }, { text: "In the fridge ๐ŸงŠ", correct: false } ] }, { question: "What do we do before going to bed at night?", options: [ { text: "Put on pajamas and read a story ๐Ÿ“–", correct: true }, { text: "Go for a swim ๐ŸŠ", correct: false }, { text: "Eat a big meal ๐Ÿ•", correct: false } ] } ], 'Safety & Help': [ { question: "If you feel lost in a big store, who should you look for?", options: [ { text: "A store worker in a uniform ๐Ÿ‘ฎ", correct: true }, { text: "A stranger ๐Ÿ‘ค", correct: false }, { text: "Run outside ๐Ÿƒ", correct: false } ] }, { question: "What should you do if you see something hot on the stove?", options: [ { text: "Stay away and tell a grown-up ๐Ÿšซ", correct: true }, { text: "Touch it ๐Ÿ–๏ธ", correct: false }, { text: "Blow on it ๐ŸŒฌ๏ธ", correct: false } ] }, { question: "If you get a small scrape on your knee, what should you do?", options: [ { text: "Tell a teacher or parent ๐Ÿฉน", correct: true }, { text: "Keep running ๐Ÿƒ", correct: false }, { text: "Cry all day ๐Ÿ˜ญ", correct: false } ] }, { question: "A stranger asks you to go with them. What do you do?", options: [ { text: "Say 'NO' and run to a safe adult ๐Ÿ›‘", correct: true }, { text: "Go with them ๐Ÿšถ", correct: false }, { text: "Take the candy they offer ๐Ÿฌ", correct: false } ] }, { question: "What number should you know for emergencies?", options: [ { text: "911 (or your local emergency number) โ˜Ž๏ธ", correct: true }, { text: "123 ๐Ÿ”ข", correct: false }, { text: "555 ๐Ÿ“ž", correct: false } ] } ], 'Advanced Social Skills': [ { question: "Your friend is building a tower and it keeps falling. How might they feel?", options: [ { text: "Frustrated or annoyed ๐Ÿ˜ ", correct: true }, { text: "Excited ๐Ÿคฉ", correct: false }, { text: "Sleepy ๐Ÿ˜ด", correct: false } ] }, { question: "A friend is talking about their favorite cat. What is a good thing to do?", options: [ { text: "Listen and ask 'What is your cat's name?' ๐Ÿฑ", correct: true }, { text: "Start talking about your dog ๐Ÿถ", correct: false }, { text: "Walk away ๐Ÿšถ", correct: false } ] }, { question: "You want to play a game with a group. How can you join in?", options: [ { text: "Wait for a break and ask 'Can I play too?' ๐Ÿค", correct: true }, { text: "Jump into the middle of the game ๐Ÿƒ", correct: false }, { text: "Take the ball away โšฝ", correct: false } ] }, { question: "You see someone sitting alone at recess. What could you do?", options: [ { text: "Ask if they want to play with you ๐Ÿค", correct: true }, { text: "Ignore them ๐Ÿ™ˆ", correct: false }, { text: "Point and laugh โ˜๏ธ", correct: false } ] }, { question: "If your friend wins a game and you lose, what should you say?", options: [ { text: "Good game! You did well! ๐Ÿ‘", correct: true }, { text: "I hate this game! ๐Ÿ˜ ", correct: false }, { text: "You cheated! ๐Ÿ˜ค", 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} | Level: ${c.level || 1} | 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'); localStorage.removeItem('selectedChildLevel'); selectedChildId = null; } loadChildren(); } else { const data = await res.json(); alert(data.detail || "Error deleting child."); } } function selectChild(id, name, age, sensory, level) { localStorage.setItem('selectedChildId', id); localStorage.setItem('selectedChildAge', age); localStorage.setItem('selectedChildSensory', sensory); localStorage.setItem('selectedChildLevel', level || 1); selectedChildId = id; alert(`Child profile selected: ${name} (Age: ${age}, Level: ${level || 1})`); applySensoryUI(sensory); applyLevelUI(age); // Note: age still used for some legacy UI logic if any 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 `
${icon}

${name}

`; }).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!

${referenceSvg}
${drawingSvg}

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) => `
`).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

${referenceSvg}
${drawingSvg}
${['#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}%`; // Reset feedback const feedback = document.getElementById('quiz-feedback'); if (feedback) feedback.innerText = ""; 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, div); optionsGrid.appendChild(div); }); } window.selectQuizOption = function(isCorrect, element) { const feedback = document.getElementById('quiz-feedback'); if (isCorrect) { quizScore++; if (feedback) { feedback.innerText = "Correct! Great thinking! ๐ŸŒŸ"; feedback.style.color = "var(--success)"; } element.style.borderColor = "var(--success)"; element.style.background = "#e8f5e9"; // Disable all options during the transition document.querySelectorAll('.quiz-option').forEach(opt => opt.onclick = null); setTimeout(() => { currentQuestionIndex++; if (currentQuestionIndex < quizData[currentQuizType].length) { loadQuizQuestion(); } else { finishQuiz(); } }, 1500); } else { if (feedback) { feedback.innerText = "Not quite. Let's try again! ๐Ÿ‘"; feedback.style.color = "#e74c3c"; } element.style.borderColor = "#e74c3c"; element.style.opacity = "0.7"; // Do NOT increment index, allowing retry } }; async function finishQuiz() { const total = quizData[currentQuizType].length; // Every question was eventually answered correctly const finalScore = total; await fetch('/activities/log-quiz', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ child_id: selectedChildId, quiz_name: currentQuizType, score: finalScore, 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 = `Amazing! You completed all ${total} questions! ๐ŸŽ‰`; } 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(child) { console.log("getLevelInfo called with:", child); let age = 0; if (typeof child === 'number') { age = child; } else if (child && typeof child === 'object') { age = parseInt(child.age) || 0; } else { // Try getting from localStorage as fallback age = parseInt(localStorage.getItem('selectedChildAge')) || 0; } console.log("Determined age for level check:", age); let level = 1; if (age >= 10) level = 3; else if (age >= 7) level = 2; else level = 1; console.log("Calculated level:", level); if (level === 3) return { level: 3, name: "Level 3: Advanced (10+)", desc: "Complex emotions and mood analysis.", theme: "purple" }; if (level === 2) return { level: 2, name: "Level 2: Intermediate (7-9)", desc: "Patterns and alphabetical logic.", theme: "sand" }; return { level: 1, name: "Level 1: Basic (3-6)", desc: "Colors, shapes, and basic emotions.", 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); localStorage.setItem('selectedChildLevel', child.level || 1); const levelInfo = getLevelInfo(child); 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?

${target.emoji}
${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

UPPER CASE

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; window.getLevelInfo = getLevelInfo;