'use client' import { useEffect, useRef, useState } from 'react' import { useTranslations } from 'next-intl' import { motion, useScroll, useTransform, useReducedMotion, } from 'framer-motion' import UnitRevealOverlay from './UnitRevealOverlay' const TOTAL_FRAMES = 299 const OPENINARY_BASE = process.env.NEXT_PUBLIC_OPENINARY_URL ?? 'https://media.ayris.tech' function frameSrc(i: number) { const path = `vesta/frames/ezgif-frame-${String(i + 1).padStart(3, '0')}.jpg` return `${OPENINARY_BASE}/t/w_1920,f_webp,q_65/${path}` } export default function CloudRevealSection() { const t = useTranslations('hero') const prefersReducedMotion = useReducedMotion() const sectionRef = useRef(null) const canvasRef = useRef(null) const framesRef = useRef<(HTMLImageElement | null)[]>([]) const [loaded, setLoaded] = useState(false) const [loadProgress, setLoadProgress] = useState(0) 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]) // Logo: 0–8% görünür, sonra kaybolur const logoOpacity = useTransform(scrollYProgress, [0, 0.02, 0.08, 0.14], [0, 1, 1, 0]) const logoScale = useTransform(scrollYProgress, [0, 0.02], [0.92, 1]) // Phase 1: 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: orta metin 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: stat rakamları 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: CTA 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]) // Canvas boyutu useEffect(() => { const canvas = canvasRef.current if (!canvas) return const resize = () => { canvas.width = window.innerWidth canvas.height = window.innerHeight } resize() window.addEventListener('resize', resize) return () => window.removeEventListener('resize', resize) }, []) // RAF loop: DOM scroll → frame index → canvas (Framer Motion bağımsız) useEffect(() => { const canvas = canvasRef.current const section = sectionRef.current if (!canvas || !section) return const ctx = canvas.getContext('2d') if (!ctx) return let rafId: number const loop = () => { const totalHeight = section.offsetHeight - window.innerHeight const scrolled = window.scrollY - section.offsetTop const progress = Math.max(0, Math.min(1, scrolled / totalHeight)) const targetIdx = Math.min( Math.floor(progress * (TOTAL_FRAMES - 1)), TOTAL_FRAMES - 1 ) for (let i = targetIdx; i >= 0; i--) { const img = framesRef.current[i] if (img && canvas.width > 0) { ctx.drawImage(img, 0, 0, canvas.width, canvas.height) break } } rafId = requestAnimationFrame(loop) } rafId = requestAnimationFrame(loop) return () => cancelAnimationFrame(rafId) }, []) // Frame preload useEffect(() => { if (prefersReducedMotion) return framesRef.current = new Array(TOTAL_FRAMES).fill(null) let loadedCount = 0 let cancelled = false for (let i = 0; i < TOTAL_FRAMES; i++) { const img = new Image() img.src = frameSrc(i) img.onload = () => { if (cancelled) return framesRef.current[i] = img loadedCount++ setLoadProgress(Math.round((loadedCount / TOTAL_FRAMES) * 100)) // İlk frame yüklenince ekranı aç if (i === 0 && !loaded) { const canvas = canvasRef.current const ctx = canvas?.getContext('2d') if (canvas && ctx) { ctx.drawImage(img, 0, 0, canvas.width, canvas.height) } setLoaded(true) } } img.onerror = () => { if (cancelled) return loadedCount++ if (loadedCount === TOTAL_FRAMES) setLoadProgress(100) } } return () => { cancelled = true } }, [prefersReducedMotion]) // Scroll kilidi useEffect(() => { const lockScroll = () => { const section = sectionRef.current if (!section) return 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) }, []) if (prefersReducedMotion) { return (

Vesta Muğla

{t('tagline')}

{t('subtitle')}

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

{loadProgress}%

)} {/* LOGO — merkez, ilk frame'de görünür, scroll ile kaybolur */}

VESTA

Muğla

{/* 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 y: number dir: 'left' | 'right' }[] = [ { id: 'blok-a', label: '2+1 Tip A', sub: 'Blok A · 83.35 m²', x: 52, y: 28, dir: 'left' }, { id: 'blok-b', label: '2+1 Tip B', sub: 'Blok B · 84 m²', x: 93, y: 30, dir: 'left' }, { id: 'blok-c', label: '1+1', sub: 'Blok C · 60 m²', x: 11, y: 42, dir: 'right' }, ] function ZigzagLine({ dir, delay }: { dir: 'left' | 'right'; delay: number }) { const w = 72 const h = 24 const path = dir === 'right' ? `M0,${h/2} L${w*0.3},${h/2} L${w*0.45},4 L${w*0.6},${h-4} L${w*0.75},${h/2} L${w},${h/2}` : `M${w},${h/2} L${w*0.7},${h/2} L${w*0.55},4 L${w*0.4},${h-4} L${w*0.25},${h/2} L0,${h/2}` return ( ) } 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) => (
{spot.dir === 'right' ? : } onSelect({ id: spot.id, label: spot.label, sub: spot.sub })} >

{spot.label}

{spot.sub}

İncele
))}
) }