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 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | '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,
}
}
|