diff --git a/app/components/deepfield/ScrollReveal.tsx b/app/components/deepfield/ScrollReveal.tsx new file mode 100644 index 0000000..14c7fd1 --- /dev/null +++ b/app/components/deepfield/ScrollReveal.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; + +interface Props { + children: React.ReactNode; + /** 등장 지연(ms) — 연속 항목 스태거용 */ + delay?: number; + /** 'fade-up'(기본) | 'fade' | 'draw'(선 그리기용 — width 확장) */ + variant?: 'fade-up' | 'fade' | 'draw'; + className?: string; +} + +export default function ScrollReveal({ children, delay = 0, variant = 'fade-up', className }: Props) { + const ref = useRef(null); + const [shown, setShown] = useState(false); + + useEffect(() => { + // reduced-motion: 즉시 표시 (연출 생략) + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + setShown(true); + return; + } + const el = ref.current; + if (!el) return; + const io = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting) { + setShown(true); + io.disconnect(); + } + }, + { threshold: 0.2 }, + ); + io.observe(el); + return () => io.disconnect(); + }, []); + + const hidden = + variant === 'fade' ? 'opacity-0' : + variant === 'draw' ? 'opacity-0 [transform:scaleX(0)] origin-left' : + 'opacity-0 translate-y-6'; + + return ( +
+ {children} +
+ ); +}