soroban-abacus-flashcards/apps/web/src/components/HeroAbacus.tsx

190 lines
4.8 KiB
TypeScript

'use client'
import { useEffect, useRef } from 'react'
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
import { css } from '../../styled-system/css'
import { useHomeHero } from '../contexts/HomeHeroContext'
export function HeroAbacus() {
const {
subtitle,
abacusValue,
setAbacusValue,
setIsHeroVisible,
isAbacusLoaded,
isSubtitleLoaded,
} = useHomeHero()
const appConfig = useAbacusConfig()
const heroRef = useRef<HTMLDivElement>(null)
// Styling for structural elements (solid, no translucency)
const structuralStyles = {
columnPosts: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 2,
},
reckoningBar: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 3,
},
}
// Detect when hero scrolls out of view
useEffect(() => {
if (!heroRef.current) return
const observer = new IntersectionObserver(
([entry]) => {
// Hero is visible if more than 20% is in viewport
setIsHeroVisible(entry.intersectionRatio > 0.2)
},
{
threshold: [0, 0.2, 0.5, 1],
}
)
observer.observe(heroRef.current)
return () => observer.disconnect()
}, [setIsHeroVisible])
return (
<div
ref={heroRef}
className={css({
height: '100vh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
bg: 'gray.900',
position: 'relative',
overflow: 'hidden',
px: '4',
py: '12',
})}
>
{/* Background pattern */}
<div
className={css({
position: 'absolute',
inset: 0,
opacity: 0.1,
backgroundImage:
'radial-gradient(circle at 2px 2px, rgba(255, 255, 255, 0.15) 1px, transparent 0)',
backgroundSize: '40px 40px',
})}
/>
{/* Title and Subtitle Section - DIRECT CHILD */}
<div
className={css({
position: 'relative',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '2',
zIndex: 10,
})}
>
<h1
className={css({
fontSize: { base: '4xl', md: '6xl', lg: '7xl' },
fontWeight: 'bold',
background: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 50%, #fbbf24 100%)',
backgroundClip: 'text',
color: 'transparent',
})}
>
Abaci One
</h1>
<p
className={css({
fontSize: { base: 'xl', md: '2xl' },
fontWeight: 'medium',
color: 'purple.300',
fontStyle: 'italic',
marginBottom: '8',
opacity: isSubtitleLoaded ? 1 : 0,
transition: 'opacity 0.5s ease-in-out',
})}
>
{subtitle.text}
</p>
</div>
{/* Large Interactive Abacus - DIRECT CHILD */}
<div
className={css({
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flex: '1',
width: '100%',
zIndex: 10,
opacity: isAbacusLoaded ? 1 : 0,
transition: 'opacity 0.5s ease-in-out',
})}
>
<div
className={css({
transform: { base: 'scale(2)', md: 'scale(3)', lg: 'scale(4)' },
transformOrigin: 'center center',
transition: 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)',
})}
>
<AbacusReact
value={abacusValue}
columns={4}
beadShape={appConfig.beadShape}
showNumbers={true}
interactive={true}
animated={true}
customStyles={structuralStyles}
onValueChange={setAbacusValue}
/>
</div>
</div>
{/* Subtle hint to scroll - DIRECT CHILD */}
<div
className={css({
position: 'relative',
fontSize: 'sm',
color: 'gray.400',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '2',
animation: 'bounce 2s ease-in-out infinite',
zIndex: 10,
})}
>
<span>Scroll to explore</span>
<span></span>
</div>
{/* Keyframes for bounce animation */}
<style
dangerouslySetInnerHTML={{
__html: `
@keyframes bounce {
0%, 100% {
transform: translateY(0);
opacity: 0.7;
}
50% {
transform: translateY(-10px);
opacity: 1;
}
}
`,
}}
/>
</div>
)
}