feat: blok-a video, zigzag hotspots, logo intro, updated positions
This commit is contained in:
@@ -6,6 +6,11 @@ import { X } from 'lucide-react'
|
||||
|
||||
const UNIT_FRAMES = 120
|
||||
|
||||
// Video kullanan birimler
|
||||
const VIDEO_UNITS: Record<string, string> = {
|
||||
'blok-a': '/unit-videos/blok-a.mp4',
|
||||
}
|
||||
|
||||
interface UnitRevealOverlayProps {
|
||||
unitId: string | null
|
||||
unitLabel: string
|
||||
@@ -19,6 +24,22 @@ export default function UnitRevealOverlay({
|
||||
unitSub,
|
||||
onClose,
|
||||
}: UnitRevealOverlayProps) {
|
||||
const isVideo = unitId ? !!VIDEO_UNITS[unitId] : false
|
||||
|
||||
// ── Video mode ──────────────────────────────────────────────────
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
const [videoFinished, setVideoFinished] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!unitId || !isVideo) return
|
||||
setVideoFinished(false)
|
||||
const v = videoRef.current
|
||||
if (!v) return
|
||||
v.currentTime = 0
|
||||
v.play().catch(() => {})
|
||||
}, [unitId, isVideo])
|
||||
|
||||
// ── Frame mode ───────────────────────────────────────────────────
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const framesRef = useRef<HTMLImageElement[]>([])
|
||||
const rafRef = useRef<number | null>(null)
|
||||
@@ -26,9 +47,8 @@ export default function UnitRevealOverlay({
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [finished, setFinished] = useState(false)
|
||||
|
||||
// Frame'leri preload et
|
||||
useEffect(() => {
|
||||
if (!unitId) return
|
||||
if (!unitId || isVideo) return
|
||||
|
||||
setLoaded(false)
|
||||
setFinished(false)
|
||||
@@ -55,9 +75,8 @@ export default function UnitRevealOverlay({
|
||||
}
|
||||
|
||||
return () => { cancelled = true }
|
||||
}, [unitId])
|
||||
}, [unitId, isVideo])
|
||||
|
||||
// Canvas boyutu
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
@@ -70,9 +89,8 @@ export default function UnitRevealOverlay({
|
||||
return () => window.removeEventListener('resize', resize)
|
||||
}, [])
|
||||
|
||||
// Yüklenince otomatik oynat
|
||||
useEffect(() => {
|
||||
if (!loaded) return
|
||||
if (!loaded || isVideo) return
|
||||
|
||||
const canvas = canvasRef.current
|
||||
const ctx = canvas?.getContext('2d')
|
||||
@@ -83,22 +101,18 @@ export default function UnitRevealOverlay({
|
||||
const interval = 1000 / fps
|
||||
let last = 0
|
||||
|
||||
const draw = (imgs: HTMLImageElement[], index: number) => {
|
||||
const img = imgs[index]
|
||||
if (img?.complete && img.naturalWidth > 0) {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
||||
}
|
||||
}
|
||||
|
||||
const animate = (ts: number) => {
|
||||
if (ts - last >= interval) {
|
||||
draw(framesRef.current, frame)
|
||||
const img = framesRef.current[frame]
|
||||
if (img?.complete && img.naturalWidth > 0) {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
||||
}
|
||||
last = ts
|
||||
if (frame < UNIT_FRAMES - 1) {
|
||||
frame++
|
||||
} else {
|
||||
setFinished(true)
|
||||
return // son frame'de dur
|
||||
return
|
||||
}
|
||||
}
|
||||
rafRef.current = requestAnimationFrame(animate)
|
||||
@@ -108,9 +122,9 @@ export default function UnitRevealOverlay({
|
||||
return () => {
|
||||
if (rafRef.current) cancelAnimationFrame(rafRef.current)
|
||||
}
|
||||
}, [loaded])
|
||||
}, [loaded, isVideo])
|
||||
|
||||
// ESC ile kapat
|
||||
// ESC kapat
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') onClose()
|
||||
@@ -119,6 +133,8 @@ export default function UnitRevealOverlay({
|
||||
return () => window.removeEventListener('keydown', onKey)
|
||||
}, [onClose])
|
||||
|
||||
const showInfo = isVideo ? videoFinished : finished
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{unitId && (
|
||||
@@ -129,20 +145,34 @@ export default function UnitRevealOverlay({
|
||||
transition={{ duration: 0.4 }}
|
||||
className="fixed inset-0 z-50 bg-black"
|
||||
>
|
||||
{/* Canvas */}
|
||||
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
|
||||
{/* Video modu */}
|
||||
{isVideo && (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={VIDEO_UNITS[unitId!]}
|
||||
className="absolute inset-0 w-full h-full object-cover"
|
||||
playsInline
|
||||
muted
|
||||
onEnded={() => setVideoFinished(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Loading */}
|
||||
{!loaded && (
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black z-10">
|
||||
<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>
|
||||
{/* Frame modu */}
|
||||
{!isVideo && (
|
||||
<>
|
||||
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
|
||||
{!loaded && (
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black z-10">
|
||||
<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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Kapat */}
|
||||
@@ -154,9 +184,9 @@ export default function UnitRevealOverlay({
|
||||
Geri
|
||||
</button>
|
||||
|
||||
{/* Bitiş bilgisi — son frame'de */}
|
||||
{/* Bitiş bilgisi */}
|
||||
<AnimatePresence>
|
||||
{finished && (
|
||||
{showInfo && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
|
||||
Reference in New Issue
Block a user