feat: interactive unit hotspots on final hero frame
This commit is contained in:
@@ -227,6 +227,9 @@ export default function CloudRevealSection() {
|
||||
<div className="mt-2 w-px h-10 bg-white/30 animate-pulse" />
|
||||
</motion.div>
|
||||
|
||||
{/* HOTSPOTS — son frame'de görünür */}
|
||||
<UnitHotspots scrollYProgress={scrollYProgress} />
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className="absolute bottom-8 left-10 right-10 z-10 h-[2px] bg-white/10">
|
||||
<motion.div className="h-full bg-white/60 origin-left" style={{ scaleX: progressScaleX }} />
|
||||
@@ -236,3 +239,99 @@ export default function CloudRevealSection() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Hotspot bileşeni ────────────────────────────────────────────────────────
|
||||
|
||||
const HOTSPOTS = [
|
||||
{
|
||||
id: 'blok-a',
|
||||
label: '2+1 Daire',
|
||||
sub: 'Blok A · 89 m²',
|
||||
// Ekranın yüzde konumu (sol-üst köşeden)
|
||||
x: 34,
|
||||
y: 22,
|
||||
},
|
||||
{
|
||||
id: 'blok-b',
|
||||
label: '2+1 Daire',
|
||||
sub: 'Blok B · 84 m²',
|
||||
x: 70,
|
||||
y: 19,
|
||||
},
|
||||
{
|
||||
id: 'blok-c',
|
||||
label: '1+1 Daire',
|
||||
sub: 'Blok C · 60 m²',
|
||||
x: 50,
|
||||
y: 55,
|
||||
},
|
||||
{
|
||||
id: 'blok-d',
|
||||
label: '1+1 Daire',
|
||||
sub: 'Blok D · 47 m²',
|
||||
x: 20,
|
||||
y: 65,
|
||||
},
|
||||
]
|
||||
|
||||
function UnitHotspots({
|
||||
scrollYProgress,
|
||||
}: {
|
||||
scrollYProgress: ReturnType<typeof useScroll>['scrollYProgress']
|
||||
}) {
|
||||
const opacity = useTransform(scrollYProgress, [0.92, 0.98], [0, 1])
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
style={{ opacity }}
|
||||
className="absolute inset-0 z-20 pointer-events-none"
|
||||
>
|
||||
{HOTSPOTS.map((spot, i) => (
|
||||
<motion.button
|
||||
key={spot.id}
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ delay: i * 0.15, duration: 0.4, ease: 'easeOut' }}
|
||||
className="absolute pointer-events-auto group"
|
||||
style={{ left: `${spot.x}%`, top: `${spot.y}%` }}
|
||||
onClick={() => {
|
||||
// İleride: daire içi frame animasyonunu tetikle
|
||||
console.log('clicked:', spot.id)
|
||||
}}
|
||||
>
|
||||
{/* Pulsing dot */}
|
||||
<span className="relative flex h-4 w-4 -translate-x-1/2 -translate-y-1/2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-60" />
|
||||
<span className="relative inline-flex rounded-full h-4 w-4 bg-white shadow-lg" />
|
||||
</span>
|
||||
|
||||
{/* Label kartı — hover'da görünür, mobilde her zaman */}
|
||||
<div className="absolute left-5 -top-8 min-w-max bg-black/70 backdrop-blur-sm border border-white/20 rounded-xl px-3 py-2 text-left opacity-0 group-hover:opacity-100 transition-opacity duration-200 md:opacity-0 max-md:opacity-100 pointer-events-none">
|
||||
{/* Animated ok çizgisi */}
|
||||
<svg
|
||||
className="absolute -left-5 top-1/2 -translate-y-1/2"
|
||||
width="20" height="2"
|
||||
viewBox="0 0 20 2"
|
||||
>
|
||||
<motion.line
|
||||
x1="0" y1="1" x2="20" y2="1"
|
||||
stroke="white" strokeWidth="1" strokeOpacity="0.6"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ delay: i * 0.15 + 0.3, duration: 0.4 }}
|
||||
/>
|
||||
</svg>
|
||||
<p className="text-white text-xs font-medium tracking-wide">{spot.label}</p>
|
||||
<p className="text-white/50 text-[10px] mt-0.5">{spot.sub}</p>
|
||||
<div className="mt-1.5 flex items-center gap-1 text-white/60 text-[10px] tracking-widest uppercase">
|
||||
<span>Detaylar</span>
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none">
|
||||
<path d="M1 4h6M4 1l3 3-3 3" stroke="currentColor" strokeWidth="1" strokeLinecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</motion.button>
|
||||
))}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user