Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | 'use client' import { useCallback, useRef, useState } from 'react' import { useTTS } from '@/hooks/useTTS' /** * Smart TTS for the typing game. * * - `announceWord` says the word aloud, then (for the first few words) spells * the letters. Chained via await so they don't talk over each other. * - Encouragement messages suppress after repeated use. * - A global mute toggle silences everything. */ export function useTypingTTS() { const speak = useTTS({ tone: 'Gently encouraging a young child learning to type.', say: { en: 'Type each letter!' }, }) const [muted, setMuted] = useState(false) const instructionCount = useRef(0) const suppressedRef = useRef(false) const MAX_INSTRUCTIONS = 6 /** * Announce a new word: say it aloud, then spell the letters (first few only). * Chained sequentially so they don't overlap. */ const announceWord = useCallback( async (word: string) => { if (muted) return // Always say the word await speak({ say: { en: word } }) // Spell the letters for the first few words (subject to suppression) if (!suppressedRef.current && instructionCount.current < MAX_INSTRUCTIONS) { instructionCount.current++ const spelled = word.toUpperCase().split('').join('. ') await speak({ say: { en: `${spelled}.` } }) } }, [speak, muted] ) const speakGameStart = useCallback(async () => { if (muted) return await speak({ say: { en: 'Type each letter!' } }) }, [speak, muted]) const speakWordComplete = useCallback(async () => { if (muted) return if (suppressedRef.current) return if (instructionCount.current >= MAX_INSTRUCTIONS) { suppressedRef.current = true return } instructionCount.current++ await speak({ say: { en: 'Great job!' } }) }, [speak, muted]) const speakDifficultyAdvance = useCallback(async () => { if (muted) return // Reset suppression so the child hears encouragement again instructionCount.current = Math.min(instructionCount.current, MAX_INSTRUCTIONS - 1) suppressedRef.current = false await speak({ say: { en: 'Wow, bigger words!' } }) }, [speak, muted]) const speakTimerWarning = useCallback(async () => { if (muted) return await speak({ say: { en: 'Ten seconds left!' } }) }, [speak, muted]) const toggleMute = useCallback(() => { setMuted((prev) => !prev) }, []) const reset = useCallback(() => { instructionCount.current = 0 suppressedRef.current = false }, []) return { announceWord, speakGameStart, speakWordComplete, speakDifficultyAdvance, speakTimerWarning, toggleMute, isMuted: muted, reset, } } |