Dual-Screen ASL Translator - MCHS
`);
setTimeout(() => {
const displayRoot = newWindow.document.getElementById('display-root');
if (displayRoot) {
const state = {
currentSign: '',
currentDescription: '',
useTwoHands: false,
leftHandEmoji: '',
rightHandEmoji: '',
leftHandAnimation: '',
rightHandAnimation: '',
handAnimation: '',
skinTone,
detectedText: '',
cameraStream: null,
isRecording: false
};
newWindow.updateDisplay = (newState) => {
Object.assign(state, newState);
renderDisplay();
};
const renderDisplay = () => {
displayRoot.innerHTML = `
🐾
ASL Translation Display
Middle College High School @ LaGuardia
${state.isRecording && state.cameraStream ? `
${state.detectedText ? `
DETECTED SIGN:
${state.detectedText}
` : ''}
` : state.currentSign ? `
${state.useTwoHands ? `
${state.leftHandEmoji}
${state.rightHandEmoji}
` : `
${state.currentSign}
`}
${state.currentSign}
${state.useTwoHands ? '🤲' : ''}
${state.currentDescription ? `
${state.currentDescription}
${state.useTwoHands ? '
⚡ Two-hand sign required
' : ''}
` : ''}
` : `
🤟
Ready to Display ASL Signs
Use the control panel to translate text or show camera feed
`}
45-35 Van Dam Street, Long Island City, NY 11101
`;
// Update video stream if recording
if (state.isRecording && state.cameraStream) {
const video = displayRoot.querySelector('#display-video');
if (video) {
video.srcObject = state.cameraStream;
}
}
};
renderDisplay();
}
}, 100);
}
};
const playTone = (frequency, duration) => {
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = frequency;
oscillator.type = 'sine';
gainNode.gain.value = volume * 0.1;
oscillator.start();
oscillator.stop(audioContext.currentTime + duration);
} catch (e) {}
};
const getASLSign = (text) => {
const normalized = text.toLowerCase().trim();
if (ASL_DATABASE[normalized]) {
return { sign: normalized, data: ASL_DATABASE[normalized] };
}
const firstChar = normalized[0];
if (firstChar && ASL_DATABASE[firstChar]) {
return { sign: firstChar, data: ASL_DATABASE[firstChar] };
}
return null;
};
const animateSign = (text) => {
const aslSign = getASLSign(text);
if (!aslSign) {
return false;
}
setIsAnimating(true);
setCurrentSign(aslSign.sign.toUpperCase());
setCurrentDescription(aslSign.data.description);
if (aslSign.data.hands === 'two') {
setUseTwoHands(true);
setLeftHandEmoji(aslSign.data.leftEmoji || '✋');
setRightHandEmoji(aslSign.data.rightEmoji || '✋');
setLeftHandAnimation(aslSign.data.leftAnimation || aslSign.data.animation);
setRightHandAnimation(aslSign.data.rightAnimation || aslSign.data.animation);
setHandAnimation('');
} else {
setUseTwoHands(false);
setLeftHandEmoji('');
setRightHandEmoji('');
setLeftHandAnimation('');
setRightHandAnimation('');
setHandAnimation(aslSign.data.animation);
}
playTone(440, 0.15);
setTimeout(() => {
setIsAnimating(false);
setCurrentSign('');
setCurrentDescription('');
setHandAnimation('');
setUseTwoHands(false);
setLeftHandEmoji('');
setRightHandEmoji('');
setLeftHandAnimation('');
setRightHandAnimation('');
}, 1500 / animationSpeed);
return true;
};
const handleTextSubmit = () => {
if (!inputText.trim()) return;
const text = inputText.toLowerCase().trim();
const words = text.split(/\s+/);
let index = 0;
const animateSequence = () => {
if (index >= words.length) return;
for (let phraseLength = 4; phraseLength >= 2; phraseLength--) {
if (index + phraseLength <= words.length) {
const phrase = words.slice(index, index + phraseLength).join(' ');
if (ASL_DATABASE[phrase]) {
animateSign(phrase);
index += phraseLength;
setTimeout(animateSequence, 1800 / animationSpeed);
return;
}
}
}
const word = words[index];
if (ASL_DATABASE[word]) {
animateSign(word);
index++;
setTimeout(animateSequence, 1800 / animationSpeed);
return;
}
const letters = word.split('');
let letterIndex = 0;
const spellLetter = () => {
if (letterIndex < letters.length) {
animateSign(letters[letterIndex]);
letterIndex++;
setTimeout(spellLetter, 1200 / animationSpeed);
} else {
index++;
setTimeout(animateSequence, 300 / animationSpeed);
}
};
spellLetter();
};
animateSequence();
};
const handleSpeech = () => {
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
const SpeechRecognition = window.webkitSpeechRecognition || window.SpeechRecognition;
const recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = false;
recognition.lang = 'en-US';
recognition.onstart = () => setIsSpeaking(true);
recognition.onend = () => setIsSpeaking(false);
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
setInputText(transcript);
playTone(880, 0.1);
setTimeout(() => {
if (transcript.trim()) {
const text = transcript.toLowerCase().trim();
const words = text.split(/\s+/);
let index = 0;
const animateSequence = () => {
if (index >= words.length) return;
for (let phraseLength = 4; phraseLength >= 2; phraseLength--) {
if (index + phraseLength <= words.length) {
const phrase = words.slice(index, index + phraseLength).join(' ');
if (ASL_DATABASE[phrase]) {
animateSign(phrase);
index += phraseLength;
setTimeout(animateSequence, 1800 / animationSpeed);
return;
}
}
}
const word = words[index];
if (ASL_DATABASE[word]) {
animateSign(word);
index++;
setTimeout(animateSequence, 1800 / animationSpeed);
return;
}
const letters = word.split('');
let letterIndex = 0;
const spellLetter = () => {
if (letterIndex < letters.length) {
animateSign(letters[letterIndex]);
letterIndex++;
setTimeout(spellLetter, 1200 / animationSpeed);
} else {
index++;
setTimeout(animateSequence, 300 / animationSpeed);
}
};
spellLetter();
};
animateSequence();
}
}, 500);
};
recognition.onerror = (event) => {
setIsSpeaking(false);
if (event.error === 'not-allowed') {
alert('🎤 Microphone access denied. Please allow microphone access.');
}
};
try {
recognition.start();
} catch (err) {
setIsSpeaking(false);
alert('⚠️ Could not start voice recognition.');
}
} else {
alert('⚠️ Voice recognition not supported in this browser.');
}
};
const startCamera = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } }
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
videoRef.current.onloadedmetadata = () => {
videoRef.current.play();
setIsRecording(true);
setCameraStream(stream);
detectIntervalRef.current = setInterval(() => {
const demoSigns = [
{ sign: 'Hello', confidence: 0.95 },
{ sign: 'Thank you', confidence: 0.88 },
{ sign: 'Love', confidence: 0.93 },
{ sign: 'Help', confidence: 0.87 },
{ sign: 'Friend', confidence: 0.91 }
];
const detected = demoSigns[Math.floor(Math.random() * demoSigns.length)];
setDetectedText(detected.sign);
playTone(660, 0.1);
}, 3000);
};
}
} catch (err) {
alert('📷 Camera access failed. Please allow camera access.');
}
};
const stopCamera = () => {
if (videoRef.current && videoRef.current.srcObject) {
const tracks = videoRef.current.srcObject.getTracks();
tracks.forEach(track => track.stop());
videoRef.current.srcObject = null;
setIsRecording(false);
setCameraStream(null);
setDetectedText('');
if (detectIntervalRef.current) {
clearInterval(detectIntervalRef.current);
}
}
};
const skinTones = [
{ name: 'Light', color: '#F5D7C4' },
{ name: 'Medium Light', color: '#E8B59B' },
{ name: 'Medium', color: '#D4956C' },
{ name: 'Medium Dark', color: '#B87952' },
{ name: 'Dark', color: '#8D5524' },
{ name: 'Deep', color: '#5C3317' }
];
useEffect(() => {
return () => {
stopCamera();
if (displayWindow && !displayWindow.closed) {
displayWindow.close();
}
};
}, []);
return (
{/* Header */}
{/* Mode Selection */}
{/* Control Panel Content */}
{mode === 'text-to-sign' && (
Input Controls
{/* Settings */}
{skinTones.map((tone) => (
)}
{mode === 'sign-to-text' && (
Camera Controls
{isRecording && (
✅ Camera feed streaming to display window
)}
)}
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);