Files
vesta/components/sections/CloudRevealSection.tsx
T

189 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useRef, useState } from 'react'
import { useTranslations } from 'next-intl'
import {
motion,
useScroll,
useTransform,
useMotionValueEvent,
useReducedMotion,
} from 'framer-motion'
const TOTAL_FRAMES = 298
const FRAME_PATH = (n: number) =>
`/frames/ezgif-frame-${String(n).padStart(3, '0')}.jpg`
export default function CloudRevealSection() {
const t = useTranslations('hero')
const prefersReducedMotion = useReducedMotion()
const sectionRef = useRef<HTMLDivElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)
const framesRef = useRef<HTMLImageElement[]>([])
const currentFrameRef = useRef(0)
const [loaded, setLoaded] = useState(false)
const [loadProgress, setLoadProgress] = useState(0)
const { scrollYProgress } = useScroll({
target: sectionRef,
offset: ['start start', 'end end'],
})
// Tagline: ilk %15'te solar, aşağı kayar
const taglineOpacity = useTransform(scrollYProgress, [0, 0.15], [1, 0])
const taglineY = useTransform(scrollYProgress, [0, 0.15], [0, 40])
// Progress bar: scroll'a göre genişler (0 → 100%)
const progressScaleX = useTransform(scrollYProgress, [0, 1], [0, 1])
// Frame preload
useEffect(() => {
if (prefersReducedMotion) return
const images: HTMLImageElement[] = new Array(TOTAL_FRAMES)
let loadedCount = 0
let cancelled = false
for (let i = 0; i < TOTAL_FRAMES; i++) {
const img = new Image()
img.src = FRAME_PATH(i + 1)
img.onload = () => {
if (cancelled) return
loadedCount++
setLoadProgress(Math.round((loadedCount / TOTAL_FRAMES) * 100))
if (loadedCount === TOTAL_FRAMES) {
framesRef.current = images
setLoaded(true)
drawFrame(0, images)
}
}
images[i] = img
}
return () => { cancelled = true }
}, [prefersReducedMotion])
function drawFrame(index: number, images?: HTMLImageElement[]) {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
const imgs = images || framesRef.current
const img = imgs[index]
if (!img || !img.complete || img.naturalWidth === 0) return
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
}
useMotionValueEvent(scrollYProgress, 'change', (progress) => {
if (!loaded) return
const frameIndex = Math.min(
Math.floor(progress * (TOTAL_FRAMES - 1)),
TOTAL_FRAMES - 1
)
if (frameIndex !== currentFrameRef.current) {
currentFrameRef.current = frameIndex
drawFrame(frameIndex)
}
})
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const resize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
drawFrame(currentFrameRef.current)
}
resize()
window.addEventListener('resize', resize)
return () => window.removeEventListener('resize', resize)
}, [loaded])
if (prefersReducedMotion) {
return (
<div className="relative min-h-[100dvh] w-full overflow-hidden bg-black flex items-end">
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/60 to-transparent" />
<div className="relative z-10 w-full px-10 pb-24">
<p className="text-white/50 text-xs tracking-[0.4em] uppercase mb-4">
Vesta Muğla
</p>
<h1 className="text-white text-5xl md:text-7xl font-serif font-light leading-tight">
{t('tagline')}
</h1>
<p className="text-white/50 mt-4 text-base md:text-lg font-light max-w-md">
{t('subtitle')}
</p>
</div>
</div>
)
}
return (
<div ref={sectionRef} style={{ height: '500vh' }}>
<div className="sticky top-0 min-h-[100dvh] w-full overflow-hidden bg-black">
{/* Canvas */}
<canvas
ref={canvasRef}
className="absolute inset-0 w-full h-full"
/>
{/* Gradient — alt kısım okunabilir olsun */}
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent pointer-events-none" />
{/* Loading */}
{!loaded && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black z-20">
<div className="w-48 h-px bg-white/10 mb-3">
<div
className="h-full bg-white/60 transition-all duration-150"
style={{ width: `${loadProgress}%` }}
/>
</div>
<p className="text-white/30 text-xs tracking-widest uppercase">
{loadProgress}%
</p>
</div>
)}
{/* Sol alt — tagline metni */}
<motion.div
style={{ opacity: taglineOpacity, y: taglineY }}
className="absolute bottom-20 left-0 z-10 px-10 max-w-xl"
>
<p className="text-white/50 text-xs tracking-[0.4em] uppercase mb-4">
Vesta Muğla
</p>
<h1 className="text-white text-4xl md:text-6xl font-serif font-light leading-tight drop-shadow-2xl">
{t('tagline')}
</h1>
<p className="text-white/50 mt-3 text-sm md:text-base font-light">
{t('subtitle')}
</p>
</motion.div>
{/* Sağ alt — scroll göstergesi */}
{loaded && (
<motion.div
style={{ opacity: taglineOpacity }}
className="absolute bottom-20 right-10 z-10 flex flex-col items-center gap-2"
>
<span className="text-white/30 text-[10px] tracking-widest uppercase rotate-90 mb-2">
{t('scroll')}
</span>
</motion.div>
)}
{/* Alt progress bar */}
<div className="absolute bottom-0 left-0 right-0 z-10 h-[2px] bg-white/10">
<motion.div
className="h-full bg-white/60 origin-left"
style={{ scaleX: progressScaleX }}
/>
</div>
</div>
</div>
)
}