class KidsCodeApp { constructor() { this.codeBlocks = []; this.character = document.getElementById('character'); this.dropZone = document.getElementById('drop-zone'); this.runButton = document.getElementById('run-button'); this.clearButton = document.getElementById('clear-button'); this.checkButton = document.getElementById('check-button'); this.isRunning = false; this.currentAssignment = 0; this.characterColors = ['๐Ÿฑ', '๐ŸฆŠ', '๐Ÿฐ', '๐Ÿธ', '๐Ÿผ']; this.currentColorIndex = 0; this.sounds = { dance: this.createSound(440, 0.3, 'triangle'), jump: this.createSound(660, 0.2, 'square'), wave: this.createSound(330, 0.4, 'sine'), sound: this.createSound(880, 0.3, 'sawtooth'), spin: this.createSound(392, 0.4, 'sine'), sleep: this.createSound(220, 0.8, 'triangle'), grow: this.createSound(523, 0.3, 'square'), shrink: this.createSound(294, 0.3, 'square'), color: this.createSound(659, 0.2, 'sawtooth'), success: this.createSound(523, 0.2, 'sine'), complete: this.createSound([523, 659, 784], 0.5, 'sine') }; this.assignments = [ { title: "First Steps!", description: "Let's make Cody dance! ๐ŸŽ‰", goal: "Use the Dance block", solution: ["dance"], hint: "Drag the ๐Ÿ’ƒ Dance block to your code area and press Play!" }, { title: "Jump for Joy!", description: "Now make Cody jump up high! ๐Ÿฆ˜", goal: "Use the Jump block", solution: ["jump"], hint: "Find the ๐Ÿฆ˜ Jump block and drag it over!" }, { title: "Dance Party!", description: "Make Cody dance AND jump! ๐ŸŽŠ", goal: "Use Dance then Jump blocks", solution: ["dance", "jump"], hint: "Put two blocks together - Dance first, then Jump!" }, { title: "Say Hello!", description: "Make Cody wave hello to everyone! ๐Ÿ‘‹", goal: "Use the Wave block", solution: ["wave"], hint: "The ๐Ÿ‘‹ Wave block will make Cody friendly!" }, { title: "Make Some Noise!", description: "Let Cody make a fun sound! ๐Ÿ”Š", goal: "Use the Make Sound block", solution: ["sound"], hint: "The ๐Ÿ”Š Make Sound block is waiting for you!" }, { title: "Spin Around!", description: "Make Cody spin like a tornado! ๐ŸŒช๏ธ", goal: "Use the Spin block", solution: ["spin"], hint: "Find the ๐ŸŒ€ Spin block to make Cody dizzy!" }, { title: "Growing Up!", description: "Make Cody grow big and then small! ๐Ÿ“", goal: "Use Grow Big then Get Small blocks", solution: ["grow", "shrink"], hint: "First ๐Ÿ” Grow Big, then ๐Ÿ”Ž Get Small!" }, { title: "Colorful Cat!", description: "Change Cody's look with colors! ๐ŸŽจ", goal: "Use the Change Color block", solution: ["color"], hint: "The ๐ŸŽจ Change Color block makes Cody look different!" }, { title: "Repeat Magic!", description: "Make Cody dance twice using repeat! ๐Ÿ”„", goal: "Use Repeat 1 time then Dance", solution: ["repeat", "dance"], hint: "Put ๐Ÿ”„ Repeat 1 time first, then ๐Ÿ’ƒ Dance after it!" }, { title: "Grand Finale!", description: "Create an amazing show! Use 4 different blocks! ๐ŸŒŸ", goal: "Use any 4 different action blocks", solution: ["dance", "jump", "spin", "sound"], hint: "Mix and match your favorite blocks to create a show!", flexible: true } ]; this.init(); } init() { this.setupDragAndDrop(); this.setupButtons(); this.setupAssignmentPanel(); this.hideDropHint(); this.loadAssignment(0); } createSound(frequency, duration, type = 'sine') { return () => { try { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); if (Array.isArray(frequency)) { frequency.forEach((freq, index) => { setTimeout(() => { const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.setValueAtTime(freq, audioContext.currentTime); oscillator.type = type; gainNode.gain.setValueAtTime(0.2, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration / 3); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + duration / 3); }, index * (duration * 1000 / 3)); }); } else { const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); oscillator.type = type; gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + duration); } } catch (error) { console.log('Audio not available'); } }; } setupDragAndDrop() { const blocks = document.querySelectorAll('.block[draggable="true"]'); blocks.forEach(block => { block.addEventListener('dragstart', (e) => { e.dataTransfer.setData('text/plain', JSON.stringify({ type: block.dataset.blockType, html: block.outerHTML })); }); }); this.dropZone.addEventListener('dragover', (e) => { e.preventDefault(); this.dropZone.classList.add('drag-over'); }); this.dropZone.addEventListener('dragleave', (e) => { if (!this.dropZone.contains(e.relatedTarget)) { this.dropZone.classList.remove('drag-over'); } }); this.dropZone.addEventListener('drop', (e) => { e.preventDefault(); this.dropZone.classList.remove('drag-over'); try { const blockData = JSON.parse(e.dataTransfer.getData('text/plain')); this.addBlock(blockData); } catch (error) { console.error('Error parsing block data:', error); } }); } addBlock(blockData) { this.hideDropHint(); const blockElement = document.createElement('div'); blockElement.innerHTML = blockData.html; const block = blockElement.firstChild; block.classList.add('code-block'); block.draggable = false; const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-btn'; deleteBtn.innerHTML = 'โœ•'; deleteBtn.addEventListener('click', () => { this.removeBlock(block); }); block.appendChild(deleteBtn); this.dropZone.appendChild(block); this.codeBlocks.push({ element: block, type: blockData.type }); this.addBlockConnector(); } addBlockConnector() { if (this.codeBlocks.length > 1) { const connector = document.createElement('div'); connector.className = 'block-connector'; connector.innerHTML = 'โฌ‡๏ธ'; connector.style.cssText = ` text-align: center; font-size: 20px; margin: 5px 0; color: #a0aec0; `; const lastBlock = this.codeBlocks[this.codeBlocks.length - 2].element; lastBlock.insertAdjacentElement('afterend', connector); } } removeBlock(blockElement) { const index = this.codeBlocks.findIndex(block => block.element === blockElement); if (index !== -1) { this.codeBlocks.splice(index, 1); const nextElement = blockElement.nextElementSibling; if (nextElement && nextElement.classList.contains('block-connector')) { nextElement.remove(); } const prevElement = blockElement.previousElementSibling; if (prevElement && prevElement.classList.contains('block-connector') && (!blockElement.nextElementSibling || !blockElement.nextElementSibling.classList.contains('code-block'))) { prevElement.remove(); } blockElement.remove(); if (this.codeBlocks.length === 0) { this.showDropHint(); } } } hideDropHint() { const dropHint = this.dropZone.querySelector('.drop-hint'); if (dropHint) { dropHint.style.display = 'none'; } } showDropHint() { const dropHint = this.dropZone.querySelector('.drop-hint'); if (dropHint) { dropHint.style.display = 'block'; } } setupButtons() { this.runButton.addEventListener('click', () => { if (!this.isRunning) { this.runCode(); } }); this.checkButton.addEventListener('click', () => { if (!this.isRunning) { this.checkAssignment(); } }); this.clearButton.addEventListener('click', () => { this.clearCode(); }); } async runCode() { if (this.codeBlocks.length === 0) { this.showMessage('Add some blocks first! ๐Ÿงฉ'); return; } this.isRunning = true; this.runButton.disabled = true; this.runButton.querySelector('.button-text').textContent = 'Running...'; try { await this.executeBlocks(); this.sounds.success(); this.showMessage('Great job! ๐ŸŽ‰'); } catch (error) { console.error('Error running code:', error); this.showMessage('Oops! Something went wrong ๐Ÿ˜…'); } finally { this.isRunning = false; this.runButton.disabled = false; this.runButton.querySelector('.button-text').textContent = 'Play'; } } async executeBlocks() { for (let i = 0; i < this.codeBlocks.length; i++) { const block = this.codeBlocks[i]; block.element.style.outline = '3px solid #4facfe'; block.element.style.boxShadow = '0 0 20px rgba(79, 172, 254, 0.5)'; await this.executeBlock(block); block.element.style.outline = ''; block.element.style.boxShadow = ''; await this.wait(200); } } async executeBlock(block) { switch (block.type) { case 'dance': await this.makeDance(); break; case 'jump': await this.makeJump(); break; case 'wave': await this.makeWave(); break; case 'sound': await this.makeSound(); break; case 'spin': await this.makeSpin(); break; case 'sleep': await this.makeSleep(); break; case 'grow': await this.makeGrow(); break; case 'shrink': await this.makeShrink(); break; case 'color': await this.changeColor(); break; case 'repeat': await this.executeRepeat(block); break; case 'wait': await this.wait(1000); break; } } async executeRepeat(repeatBlock) { const repeatCount = 1; const startIndex = this.codeBlocks.indexOf(repeatBlock); const blocksToRepeat = []; for (let i = startIndex + 1; i < this.codeBlocks.length; i++) { const nextBlock = this.codeBlocks[i]; if (nextBlock.type === 'repeat') break; blocksToRepeat.push(nextBlock); } for (let rep = 0; rep < repeatCount; rep++) { for (const blockToRepeat of blocksToRepeat) { await this.executeBlock(blockToRepeat); await this.wait(200); } } } async makeDance() { this.character.classList.add('dancing'); this.sounds.dance(); await this.wait(1000); this.character.classList.remove('dancing'); } async makeJump() { this.character.classList.add('jumping'); this.sounds.jump(); await this.wait(600); this.character.classList.remove('jumping'); } async makeWave() { this.character.classList.add('waving'); this.sounds.wave(); await this.wait(1000); this.character.classList.remove('waving'); } async makeSound() { this.character.classList.add('bouncing'); this.sounds.sound(); await this.wait(500); this.character.classList.remove('bouncing'); } async makeSpin() { this.character.classList.add('spinning'); this.sounds.spin(); await this.wait(800); this.character.classList.remove('spinning'); } async makeSleep() { this.character.classList.add('sleeping'); this.sounds.sleep(); await this.wait(1200); this.character.classList.remove('sleeping'); } async makeGrow() { this.character.classList.add('growing'); this.sounds.grow(); await this.wait(600); this.character.classList.remove('growing'); } async makeShrink() { this.character.classList.add('shrinking'); this.sounds.shrink(); await this.wait(600); this.character.classList.remove('shrinking'); } async changeColor() { this.currentColorIndex = (this.currentColorIndex + 1) % this.characterColors.length; const characterBody = this.character.querySelector('.character-body'); characterBody.textContent = this.characterColors[this.currentColorIndex]; this.sounds.color(); await this.wait(300); } clearCode() { this.codeBlocks.forEach(block => { block.element.remove(); }); const connectors = this.dropZone.querySelectorAll('.block-connector'); connectors.forEach(connector => connector.remove()); this.codeBlocks = []; this.showDropHint(); this.showMessage('All cleared! Ready for new code ๐Ÿงน'); } showMessage(text) { const existingMessage = document.querySelector('.message-popup'); if (existingMessage) { existingMessage.remove(); } const message = document.createElement('div'); message.className = 'message-popup'; message.textContent = text; message.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 20px 30px; border-radius: 20px; font-size: 24px; font-weight: bold; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); z-index: 1000; animation: messagePopup 0.3s ease-out; `; const style = document.createElement('style'); style.textContent = ` @keyframes messagePopup { 0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 1; } } `; document.head.appendChild(style); document.body.appendChild(message); setTimeout(() => { message.style.animation = 'messagePopup 0.3s ease-out reverse'; setTimeout(() => { message.remove(); style.remove(); }, 300); }, 2000); } setupAssignmentPanel() { const showBtn = document.getElementById('show-assignment'); const closeBtn = document.getElementById('close-assignment'); const prevBtn = document.getElementById('prev-assignment'); const nextBtn = document.getElementById('next-assignment'); const panel = document.getElementById('assignment-panel'); showBtn.addEventListener('click', () => { panel.style.display = 'block'; this.checkButton.style.display = 'inline-flex'; }); closeBtn.addEventListener('click', () => { panel.style.display = 'none'; this.checkButton.style.display = 'none'; }); prevBtn.addEventListener('click', () => { if (this.currentAssignment > 0) { this.currentAssignment--; this.loadAssignment(this.currentAssignment); } }); nextBtn.addEventListener('click', () => { if (this.currentAssignment < this.assignments.length - 1) { this.currentAssignment++; this.loadAssignment(this.currentAssignment); } }); } loadAssignment(index) { const assignment = this.assignments[index]; if (!assignment) return; document.querySelector('.assignment-title').textContent = assignment.title; document.querySelector('.assignment-description').textContent = assignment.description; document.querySelector('.assignment-goal').textContent = `๐ŸŽฏ Goal: ${assignment.goal}`; document.querySelector('.assignment-counter').textContent = `${index + 1} / ${this.assignments.length}`; const prevBtn = document.getElementById('prev-assignment'); const nextBtn = document.getElementById('next-assignment'); prevBtn.disabled = index === 0; nextBtn.disabled = index === this.assignments.length - 1; this.clearCode(); } checkAssignment() { const assignment = this.assignments[this.currentAssignment]; if (!assignment) return; const currentBlocks = this.codeBlocks.map(block => block.type); let isCorrect = false; if (assignment.flexible && assignment.title.includes('Grand Finale')) { const uniqueActionBlocks = currentBlocks.filter(block => ['dance', 'jump', 'wave', 'sound', 'spin', 'sleep', 'grow', 'shrink', 'color'].includes(block) ); const uniqueActions = new Set(uniqueActionBlocks); isCorrect = uniqueActions.size >= 4; } else { isCorrect = JSON.stringify(currentBlocks) === JSON.stringify(assignment.solution); } if (isCorrect) { this.sounds.complete(); this.showMessage(`๐ŸŽ‰ Amazing! You completed: ${assignment.title}!`); setTimeout(() => { if (this.currentAssignment < this.assignments.length - 1) { this.showMessage('๐Ÿš€ Ready for the next challenge?'); } else { this.showMessage('๐Ÿ† Congratulations! You completed all missions!'); } }, 2500); } else { this.showMessage(`๐Ÿ’ก Hint: ${assignment.hint}`); } } wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } document.addEventListener('DOMContentLoaded', () => { new KidsCodeApp(); });