1document.addEventListener('DOMContentLoaded', () => { 2 const currentAnimalNameText = document.getElementById('current-animal-name'); 3 const animalImageContainer = document.getElementById('animal-image-container'); 4 const prevAnimalButton = document.getElementById('prev-animal'); 5 const nextAnimalButton = document.getElementById('next-animal'); 6 7 const canvas = document.getElementById('drawing-canvas'); 8 const ctx = canvas.getContext('2d', { willReadFrequently: true }); 9 10 const animals = [ 11 "Lion", "Elephant", "Monkey", "Zebra", "Giraffe", 12 "Tiger", "Bear", "Kangaroo", "Dolphin", "Penguin", 13 "Dog", "Cat", "Cow", "Horse", "Donkey" 14 ]; 15 let currentIndex = 0; 16 let isDrawing = false; 17 let isRevealed = false; 18 19 // Base paths for assets 20 const imageBasePath = CDN + 'assets/images/animals/'; 21 const audioNameBasePath = CDN + 'assets/audio/animals/'; 22 const audioSoundBasePath = CDN + 'assets/audio/animal_sounds/'; 23 const audioFormat = '.mp3'; 24 const imageFormat = '.png'; 25 26 function setupCanvas() { 27 canvas.width = animalImageContainer.clientWidth; 28 canvas.height = animalImageContainer.clientHeight; 29 30 ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; 31 ctx.fillRect(0, 0, canvas.width, canvas.height); 32 33 ctx.lineWidth = canvas.width * 0.15; 34 ctx.lineCap = 'round'; 35 ctx.lineJoin = 'round'; 36 ctx.globalCompositeOperation = 'destination-out'; 37 38 canvas.style.opacity = '1'; 39 canvas.style.transition = 'none'; 40 isRevealed = false; 41 42 // --- Add "Scratch Here" text --- 43 ctx.globalCompositeOperation = 'source-over'; 44 ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; 45 // CORRECTED: Using string concatenation instead of template literal 46 ctx.font = 'bold ' + (canvas.width * 0.1) + 'px Poppins'; 47 ctx.textAlign = 'center'; 48 ctx.textBaseline = 'middle'; 49 ctx.fillText('SCRATCH HERE', canvas.width / 2, canvas.height / 2); 50 ctx.globalCompositeOperation = 'destination-out'; 51 } 52 53 function getMousePos(e) { 54 const rect = canvas.getBoundingClientRect(); 55 return { 56 x: e.clientX - rect.left, 57 y: e.clientY - rect.top 58 }; 59 } 60 61 function getTouchPos(e) { 62 const rect = canvas.getBoundingClientRect(); 63 return { 64 x: e.touches[0].clientX - rect.left, 65 y: e.touches[0].clientY - rect.top 66 }; 67 } 68 69 function startDrawing(e) { 70 if (isRevealed) return; 71 isDrawing = true; 72 ctx.beginPath(); 73 const pos = e.touches ? getTouchPos(e) : getMousePos(e); 74 ctx.moveTo(pos.x, pos.y); 75 } 76 77 function draw(e) { 78 if (!isDrawing || isRevealed) return; 79 e.preventDefault(); 80 const pos = e.touches ? getTouchPos(e) : getMousePos(e); 81 ctx.lineTo(pos.x, pos.y); 82 ctx.stroke(); 83 } 84 85 function checkRevealProgress() { 86 const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 87 const data = imageData.data; 88 let transparentPixels = 0; 89 const totalPixels = data.length / 4; 90 91 const sampleStep = 40; 92 for (let i = 3; i < data.length; i += 4 * sampleStep) { 93 if (data[i] < 50) { 94 transparentPixels++; 95 } 96 } 97 98 const sampledTotalPixels = Math.floor(totalPixels / sampleStep); 99 const revealPercentage = (transparentPixels / sampledTotalPixels) * 100; 100 101 console.log('Revealed: ' + revealPercentage.toFixed(2) + '%'); 102 103 const REVEAL_THRESHOLD = 50; 104 105 if (revealPercentage >= REVEAL_THRESHOLD && !isRevealed) { 106 isRevealed = true; 107 canvas.style.opacity = '0'; 108 canvas.style.transition = 'opacity 1s ease-out'; 109 console.log('Image fully revealed!'); 110 playAnimalAudio(); 111 } 112 } 113 114 function stopDrawing() { 115 isDrawing = false; 116 if (!isRevealed) { 117 checkRevealProgress(); 118 } 119 } 120 121 canvas.addEventListener('mousedown', startDrawing); 122 canvas.addEventListener('mousemove', draw); 123 canvas.addEventListener('mouseup', stopDrawing); 124 canvas.addEventListener('mouseleave', stopDrawing); 125 126 canvas.addEventListener('touchstart', startDrawing, { passive: false }); 127 canvas.addEventListener('touchmove', draw, { passive: false }); 128 canvas.addEventListener('touchend', stopDrawing); 129 canvas.addEventListener('touchcancel', stopDrawing); 130 131 function updateAnimalDisplay() { 132 const currentAnimal = animals[currentIndex]; 133 currentAnimalNameText.textContent = currentAnimal; 134 135 animalImageContainer.innerHTML = ''; 136 137 const img = document.createElement('img'); 138 const imageSrc = imageBasePath + currentAnimal.toLowerCase() + imageFormat; 139 img.src = imageSrc; 140 img.alt = 'Image for ' + currentAnimal; 141 console.log('Attempting to load image: ' + imageSrc); 142 img.onload = () => { 143 console.log('Image loaded successfully: ' + imageSrc); 144 animalImageContainer.appendChild(canvas); 145 setupCanvas(); 146 }; 147 img.onerror = () => { 148 animalImageContainer.textContent = 'Image for ' + currentAnimal + ' not found. Please add ' + currentAnimal.toLowerCase() + imageFormat + ' to the assets/images/animals/ folder.'; 149 console.error('Error loading image: ' + img.src); 150 animalImageContainer.appendChild(canvas); 151 setupCanvas(); 152 }; 153 animalImageContainer.appendChild(img); 154 155 console.log('Displaying animal: ' + currentAnimal); 156 } 157 158 function playAnimalAudio() { 159 const currentAnimal = animals[currentIndex]; 160 const animalNameAudioSrc = audioNameBasePath + currentAnimal.toLowerCase() + audioFormat; 161 const animalSoundAudioSrc = audioSoundBasePath + currentAnimal.toLowerCase() + '_sound' + audioFormat; 162 163 console.log('Attempting to play animal name audio: ' + animalNameAudioSrc); 164 const nameAudio = new Audio(animalNameAudioSrc); 165
166 if (window.pauseBackgroundMusic) { 167 window.pauseBackgroundMusic(); 168 } 169 170 nameAudio.play().catch(e => { 171 console.error('Error playing animal name audio for ' + currentAnimal + ':', e); 172 if (window.resumeBackgroundMusic) { 173 window.resumeBackgroundMusic(); 174 } 175 }); 176 177 nameAudio.onended = () => { 178 console.log('Animal name audio finished. Attempting to play animal sound: ' + animalSoundAudioSrc); 179 const soundAudio = new Audio(animalSoundAudioSrc); 180 soundAudio.play().catch(e => { 181 console.error('Error playing animal sound for ' + currentAnimal + ':', e); 182 }).finally(() => { 183 if (window.resumeBackgroundMusic) { 184 window.resumeBackgroundMusic(); 185 } 186 }); 187 }; 188 } 189 190 prevAnimalButton.addEventListener('click', () => { 191 currentIndex = (currentIndex - 1 + animals.length) % animals.length; 192 updateAnimalDisplay(); 193 }); 194 195 nextAnimalButton.addEventListener('click', () => { 196 currentIndex = (currentIndex + 1) % animals.length; 197 updateAnimalDisplay(); 198 }); 199 200 // Initial display 201 updateAnimalDisplay(); 202});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.