'use client' import { useEffect, useRef, useState } from 'react' import { useTranslations } from 'next-intl' import { motion, useScroll, useTransform, useMotionValueEvent, useReducedMotion, } from 'framer-motion' import UnitRevealOverlay from './UnitRevealOverlay' const TOTAL_FRAMES = 299 const FRAME_PATH = (n: number) => `/frames/ezgif-frame-${String(n).padStart(3, '0')}.jpg` // Scroll aralığına göre opacity döndüren helper function usePhaseOpacity( scrollYProgress: ReturnType['scrollYProgress'], inStart: number, inEnd: number, outStart: number, outEnd: number ) { return useTransform( scrollYProgress, [inStart, inEnd, outStart, outEnd], [0, 1, 1, 0] ) } export default function CloudRevealSection() { const t = useTranslations('hero') const prefersReducedMotion = useReducedMotion() const sectionRef = useRef(null) const canvasRef = useRef(null) const framesRef = useRef([]) const currentFrameRef = useRef(0) const [loaded, setLoaded] = useState(false) const [loadProgress, setLoadProgress] = useState(0) const hasSnapped = useRef(false) const [activeUnit, setActiveUnit] = useState<{ id: string; label: string; sub: string } | null>(null) const { scrollYProgress } = useScroll({ target: sectionRef, offset: ['start start', 'end end'], }) // Progress bar const progressScaleX = useTransform(scrollYProgress, [0, 1], [0, 1]) // --- Text phase opacities --- // Phase 1: 0–15% → tagline (sol alt) const phase1Opacity = useTransform(scrollYProgress, [0, 0.05, 0.12, 0.18], [0, 1, 1, 0]) const phase1Y = useTransform(scrollYProgress, [0, 0.05], [20, 0]) // Phase 2: 20–45% → orta metin (merkez) const phase2Opacity = useTransform(scrollYProgress, [0.20, 0.28, 0.40, 0.48], [0, 1, 1, 0]) const phase2Y = useTransform(scrollYProgress, [0.20, 0.28], [30, 0]) // Phase 3: 50–72% → stat kartları (sağ) const phase3Opacity = useTransform(scrollYProgress, [0.50, 0.57, 0.68, 0.75], [0, 1, 1, 0]) const phase3Y = useTransform(scrollYProgress, [0.50, 0.57], [30, 0]) // Phase 4: 78–100% → CTA (merkez alt) const phase4Opacity = useTransform(scrollYProgress, [0.78, 0.85, 0.95, 1], [0, 1, 1, 0]) const phase4Y = useTransform(scrollYProgress, [0.78, 0.85], [30, 0]) // 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) } }) // Scroll pozisyonunu hero sonunda kilitle useEffect(() => { const lockScroll = () => { const section = sectionRef.current if (!section) return // Hero'nun scroll edebileceği maksimum pozisyon const maxY = section.offsetTop + section.offsetHeight - window.innerHeight if (window.scrollY > maxY) { window.scrollTo({ top: maxY, behavior: 'instant' }) } } window.addEventListener('scroll', lockScroll, { passive: true }) return () => window.removeEventListener('scroll', lockScroll) }, []) 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 (

Vesta Muğla

{t('tagline')}

{t('subtitle')}

) } return ( <>
{/* Canvas */} {/* Alt gradient */}
{/* Loading */} {!loaded && (

{loadProgress}%

)} {/* PHASE 1 — Sol alt: Ana tagline */}

Vesta Muğla

{t('tagline')}

{t('subtitle')}

{/* PHASE 2 — Merkez: Konum & kimlik */}

Muğla · Türkiye

Bulutların altında,
ormanın içinde

{/* PHASE 3 — Sağ: Stat rakamları */} {[ { value: '120', label: 'Konut' }, { value: '15.000', label: 'm² Alan' }, { value: '%60', label: 'Yeşil Alan' }, ].map((stat) => (

{stat.value}

{stat.label}

))}
{/* PHASE 4 — Merkez alt: CTA */}

Projeyi Keşfet

Hayalinizdeki yaşam
bir adım uzağınızda

{/* HOTSPOTS — son frame'de görünür */} setActiveUnit(unit)} /> {/* Progress bar */}
{/* Unit reveal overlay */} setActiveUnit(null)} /> ) } // ─── Hotspot bileşeni ──────────────────────────────────────────────────────── const HOTSPOTS: { id: string label: string sub: string x: number // dot pozisyonu — ekranın % yatay y: number // dot pozisyonu — ekranın % dikey dir: 'left' | 'right' // kartın hangi tarafa uzayacağı }[] = [ { id: 'blok-a', label: '2+1 Daire', sub: 'Blok A · 89 m²', x: 34, y: 22, dir: 'left' }, { id: 'blok-b', label: '2+1 Daire', sub: 'Blok B · 84 m²', x: 70, y: 19, dir: 'right' }, { id: 'blok-c', label: '1+1 Daire', sub: 'Blok C · 60 m²', x: 50, y: 55, dir: 'right' }, { id: 'blok-d', label: '1+1 Daire', sub: 'Blok D · 47 m²', x: 20, y: 65, dir: 'left' }, ] function UnitHotspots({ scrollYProgress, onSelect, }: { scrollYProgress: ReturnType['scrollYProgress'] onSelect: (unit: { id: string; label: string; sub: string }) => void }) { const containerOpacity = useTransform(scrollYProgress, [0.92, 0.98], [0, 1]) return ( {HOTSPOTS.map((spot, i) => (
{/* Dot + çizgi + kart — yatay flex */}
{/* Pulsing dot */} {/* Animated çizgi */} {/* Ok ucu */} {spot.dir === 'right' ? : } {/* Kart */} onSelect({ id: spot.id, label: spot.label, sub: spot.sub })} >

{spot.label}

{spot.sub}

İncele
))}
) }