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 97 98 99 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 25x 25x 25x 25x 25x 10x 10x 10x 10x 10x 10x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 10x 10x 10x 10x 10x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x 10x 25x 25x 25x 25x 13x 1x 1x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 25x 1x 1x 29x 29x 4x 4x 25x 25x | 'use client'
import type React from 'react'
import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'
import type { Subtitle } from '../data/abaciOneSubtitles'
import { subtitles } from '../data/abaciOneSubtitles'
const ROTATION_INTERVAL = 5000 // 5 seconds
interface HomeHeroContextValue {
subtitle: Subtitle
abacusValue: number
setAbacusValue: (value: number) => void
isHeroVisible: boolean
setIsHeroVisible: (visible: boolean) => void
isAbacusLoaded: boolean
isSubtitleLoaded: boolean
}
const HomeHeroContext = createContext<HomeHeroContextValue | null>(null)
export { HomeHeroContext }
export function HomeHeroProvider({ children }: { children: React.ReactNode }) {
const [subtitleIndex, setSubtitleIndex] = useState(0)
const [isSubtitleLoaded, setIsSubtitleLoaded] = useState(false)
// Rotate subtitles on a 5-second interval
useEffect(() => {
setIsSubtitleLoaded(true)
const timer = setInterval(() => {
setSubtitleIndex((prev) => (prev + 1) % subtitles.length)
}, ROTATION_INTERVAL)
return () => clearInterval(timer)
}, [])
const subtitle = subtitles[subtitleIndex]
// Shared abacus value - always start at 0 for SSR/hydration consistency
const [abacusValue, setAbacusValue] = useState(0)
const [isAbacusLoaded, setIsAbacusLoaded] = useState(false)
const isLoadingFromStorage = useRef(false)
// Load from sessionStorage after mount (client-only, no hydration mismatch)
useEffect(() => {
isLoadingFromStorage.current = true // Block saves during load
const saved = sessionStorage.getItem('heroAbacusValue')
if (saved) {
const parsedValue = parseInt(saved, 10)
if (!Number.isNaN(parsedValue)) {
setAbacusValue(parsedValue)
}
}
// Use setTimeout to ensure the value has been set before we allow saves
setTimeout(() => {
isLoadingFromStorage.current = false
setIsAbacusLoaded(true)
}, 0)
}, [])
// Persist value to sessionStorage when it changes (but skip during load)
useEffect(() => {
if (!isLoadingFromStorage.current) {
sessionStorage.setItem('heroAbacusValue', abacusValue.toString())
}
}, [abacusValue])
// Track hero visibility for nav branding
const [isHeroVisible, setIsHeroVisible] = useState(true)
const value = useMemo(
() => ({
subtitle,
abacusValue,
setAbacusValue,
isHeroVisible,
setIsHeroVisible,
isAbacusLoaded,
isSubtitleLoaded,
}),
[subtitle, abacusValue, isHeroVisible, isAbacusLoaded, isSubtitleLoaded]
)
return <HomeHeroContext.Provider value={value}>{children}</HomeHeroContext.Provider>
}
export function useHomeHero() {
const context = useContext(HomeHeroContext)
if (!context) {
throw new Error('useHomeHero must be used within HomeHeroProvider')
}
return context
}
|