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 ` +
+ ${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}%`; + 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?

+
${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;