// DOM element references const leftEye = document.getElementById('leftEye'); const rightEye = document.getElementById('rightEye'); const listeningContainer = document.getElementById('listeningContainer'); const eyesContainer = document.querySelector('.eyes-wrapper'); const audioBars = Array.from(document.querySelectorAll('.audio-bar')); const recordingIndicator = document.getElementById('recordingIndicator'); const toggle = document.getElementById('toggle'); const longpressIndicator = document.getElementById('longpressIndicator'); const body = document.body; const transcriptContainer = document.getElementById('transcript-container'); const transcriptText = document.getElementById('transcript-text'); // UI configuration const emotionColors = { default: '#00FFFF', thinking: '#00FFFF', happy: '#00FFFF', curious: '#00FFFF', surprised: '#00FFFF', blinking: '#00FFFF', angry: '#f87171', fear: '#c084fc', disgust: '#4ade80', listening: '#00FFFF' }; // UI state variables let currentEmotion = 'default'; let audioVisualizationInterval = null; let currentAmplitude = 3; let isRecording = false; let longPressTimer = null; let longPressActive = false; const LONG_PRESS_DURATION = 1500; // 1.5 seconds for long press // Animation variables const noiseSeed = Array(20).fill(0).map(() => Math.random() * 2 - 1); let seedPosition = 0; // UI animation & interaction functions function applyEmotion(emotion) { const emotionClasses = [ 'emotion-blinking', 'emotion-thinking', 'emotion-happy', 'emotion-surprised', 'emotion-curious', 'emotion-angry', 'emotion-fear', 'emotion-disgust', 'emotion-listening' ]; leftEye.classList.remove(...emotionClasses); rightEye.classList.remove(...emotionClasses); if (emotion !== 'default' && emotion !== 'listening') { leftEye.classList.add(`emotion-${emotion}`); rightEye.classList.add(`emotion-${emotion}`); } const color = emotionColors[emotion]; leftEye.style.backgroundColor = color; rightEye.style.backgroundColor = color; audioBars.forEach(bar => { bar.style.backgroundColor = color; }); if (emotion === 'listening') { eyesContainer.style.opacity = '0'; isRecording = true; recordingIndicator.classList.add('active'); listeningContainer.classList.add('active'); startRecordingVisualization(); } else { clearInterval(audioVisualizationInterval); isRecording = false; recordingIndicator.classList.remove('active'); listeningContainer.classList.remove('active'); eyesContainer.style.opacity = '1'; } } function setEmotion(emotion) { currentEmotion = emotion; applyEmotion(emotion); if (emotion !== 'listening' && emotion !== 'default' && emotion !== 'thinking') { setTimeout(() => { currentEmotion = 'default'; applyEmotion('default'); }, 1000); } } function getNoise() { seedPosition = (seedPosition + 1) % noiseSeed.length; return noiseSeed[seedPosition]; } function startRecordingVisualization() { clearInterval(audioVisualizationInterval); const centerValues = [2.5, 3, 4, 4.5, 4, 3, 2.5]; audioBars.forEach((bar, index) => { bar.style.height = `${centerValues[index]}rem`; }); setInterval(() => { if (Math.random() < 0.3) { currentAmplitude = 2 + Math.random() * 2.5; } }, 800); audioVisualizationInterval = setInterval(() => { audioBars.forEach((bar, index) => { const centerFactor = 1 - Math.abs(index - 3) / 3.5; const baseHeight = 2 + (centerFactor * currentAmplitude); const noise = getNoise() * 0.7; const height = Math.max(1.5, baseHeight + noise); bar.style.height = `${height}rem`; bar.style.opacity = 0.7 + (height - 2) * 0.1; }); }, 80); } function initBlinking() { setInterval(() => { if (currentEmotion === 'default' && toggle.checked) { setEmotion('blinking'); } }, 4000); } function initRandomEmotions() { setInterval(() => { if (currentEmotion === 'default' && Math.random() < 0.3 && toggle.checked) { const emotions = ['thinking', 'happy', 'curious', 'surprised']; const randomEmotion = emotions[Math.floor(Math.random() * emotions.length)]; setEmotion(randomEmotion); } }, 3000); } function simulateSpeechPattern() { setInterval(() => { if (isRecording) { if (Math.random() < 0.1) { currentAmplitude = 1; setTimeout(() => { currentAmplitude = 2 + Math.random() * 2.5; }, 300 + Math.random() * 400); } if (Math.random() < 0.15) { currentAmplitude = 4 + Math.random(); setTimeout(() => { currentAmplitude = 2 + Math.random() * 2.5; }, 200 + Math.random() * 300); } } }, 1000); } // Long Press Handlers function startLongPress(event) { if (longPressActive || !toggle.checked || isProcessingAPI) return; longPressActive = true; // Position the indicator near where the user is pressing const x = event.type.includes('touch') ? event.touches[0].clientX : event.clientX; const y = event.type.includes('touch') ? event.touches[0].clientY : event.clientY; longpressIndicator.style.left = (x - 60) + 'px'; // Center the 120px wide indicator longpressIndicator.style.top = (y - 60) + 'px'; // Center the 120px tall indicator // Start long press timer longPressTimer = setTimeout(() => { // Toggle between listening and default if (currentEmotion === 'listening') { setEmotion('default'); stopListening(); } else { setEmotion('listening'); startListening(); } longpressIndicator.classList.remove('active'); longPressActive = false; }, LONG_PRESS_DURATION); // Show the indicator longpressIndicator.classList.add('active'); } function cancelLongPress() { if (!longPressActive) return; clearTimeout(longPressTimer); longpressIndicator.classList.remove('active'); longPressActive = false; } // Eye movement functions function moveEyes(x, y) { // Get the dimensions of the screen const screenWidth = window.innerWidth; const screenHeight = window.innerHeight; // Calculate the center of the screen const centerX = screenWidth / 2; const centerY = screenHeight / 2; // Normalize the (x, y) coordinates relative to the center of the screen const normalizedX = (x - centerX) / centerX; // Range: [-1, 1] const normalizedY = (y - centerY) / centerY; // Range: [-1, 1] // Define the maximum movement range for the eyes (in pixels) const maxMovementX = 20; // Maximum horizontal movement const maxMovementY = 20; // Maximum vertical movement // Calculate the new position for the eyes const leftEyeX = normalizedX * maxMovementX; const leftEyeY = normalizedY * maxMovementY; const rightEyeX = normalizedX * maxMovementX; const rightEyeY = normalizedY * maxMovementY; // Use requestAnimationFrame for smoother animation requestAnimationFrame(() => { leftEye.style.transform = `translate(${leftEyeX}px, ${leftEyeY}px)`; rightEye.style.transform = `translate(${rightEyeX}px, ${rightEyeY}px)`; }); } // Eye tracking setup let isAnimating = false; document.addEventListener('mousemove', (event) => { if (!isAnimating) { isAnimating = true; requestAnimationFrame(() => { moveEyes(event.clientX, event.clientY); isAnimating = false; }); } }); document.addEventListener('touchmove', (event) => { if (!isAnimating) { isAnimating = true; const touch = event.touches[0]; requestAnimationFrame(() => { moveEyes(touch.clientX, touch.clientY); isAnimating = false; }); } }); // UI Event listeners document.addEventListener('touchstart', (e) => { startLongPress(e); }, { passive: false }); document.addEventListener('touchend', () => { cancelLongPress(); }); document.addEventListener('touchcancel', () => { cancelLongPress(); }); document.addEventListener('mousedown', (e) => { startLongPress(e); }); document.addEventListener('mouseup', () => { cancelLongPress(); }); document.addEventListener('mouseleave', () => { cancelLongPress(); }); // For mobile devices, ensure audio context is resumed after user interaction document.addEventListener('click', function() { if (window.audioContext && window.audioContext.state === 'suspended') { window.audioContext.resume(); } }, { once: true }); // Initialize UI animations initBlinking(); initRandomEmotions(); simulateSpeechPattern();