4 changed files with 197 additions and 142 deletions
@ -0,0 +1,40 @@
|
||||
function MainUtm() { |
||||
return ( |
||||
<section className="main-utm-section"> |
||||
<div className="main-utm-inner"> |
||||
<div className="main-utm-head"> |
||||
<h2>UTM SYSTEM</h2> |
||||
<p>드론 교통을 안전하게 관리하는 통합 관제 시스템</p> |
||||
</div> |
||||
|
||||
<div className="main-utm-flow"> |
||||
<div className="utm-item"> |
||||
<span className="num">01</span> |
||||
<h3>비행 요청</h3> |
||||
<p>운영자는 비행 계획을 등록합니다</p> |
||||
</div> |
||||
|
||||
<div className="utm-item"> |
||||
<span className="num">02</span> |
||||
<h3>자동 승인</h3> |
||||
<p>공역과 규제 기준을 기반으로 승인됩니다</p> |
||||
</div> |
||||
|
||||
<div className="utm-item"> |
||||
<span className="num">03</span> |
||||
<h3>경로 관리</h3> |
||||
<p>비행 경로를 실시간으로 추적합니다</p> |
||||
</div> |
||||
|
||||
<div className="utm-item"> |
||||
<span className="num">04</span> |
||||
<h3>실시간 관제</h3> |
||||
<p>이상 상황을 즉시 감지하고 대응합니다</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
); |
||||
} |
||||
|
||||
export default MainUtm; |
||||
@ -0,0 +1,145 @@
|
||||
import { useEffect, useRef } from "react"; |
||||
import { gsap } from "gsap"; |
||||
import { ScrollTrigger } from "gsap/ScrollTrigger"; |
||||
|
||||
gsap.registerPlugin(ScrollTrigger); |
||||
|
||||
function MainVisual() { |
||||
const sectionRef = useRef(null); |
||||
const bgRef = useRef(null); |
||||
const hero2Ref = useRef(null); |
||||
const videoRef = useRef(null); |
||||
const text1Ref = useRef(null); |
||||
const text2Ref = useRef(null); |
||||
const progressBarRef = useRef(null); |
||||
|
||||
useEffect(() => { |
||||
const ctx = gsap.context(() => { |
||||
const videoEl = videoRef.current; |
||||
|
||||
if (videoEl) { |
||||
videoEl.pause(); |
||||
videoEl.currentTime = 0; |
||||
} |
||||
|
||||
gsap.set(hero2Ref.current, { opacity: 0, scale: 1.12, filter: "blur(12px)" }); |
||||
|
||||
gsap.set(text2Ref.current, { |
||||
opacity: 0, |
||||
y: 54, |
||||
x: 0, |
||||
scale: 0.965, |
||||
filter: "blur(10px)", |
||||
}); |
||||
|
||||
gsap.set(progressBarRef.current, { width: "0%" }); |
||||
gsap.set(".fill-line", { backgroundPosition: "100% 0%" }); |
||||
|
||||
const darkHeroTrigger = ScrollTrigger.create({ |
||||
trigger: sectionRef.current, |
||||
start: "top top", |
||||
end: "+=3000", |
||||
onEnter: () => document.body.classList.add("is-dark-hero"), |
||||
onEnterBack: () => document.body.classList.add("is-dark-hero"), |
||||
onLeave: () => document.body.classList.remove("is-dark-hero"), |
||||
onLeaveBack: () => document.body.classList.remove("is-dark-hero"), |
||||
}); |
||||
|
||||
let hero2Started = false; |
||||
|
||||
const tl = gsap.timeline({ |
||||
scrollTrigger: { |
||||
trigger: sectionRef.current, |
||||
start: "top top", |
||||
end: "+=3000", |
||||
scrub: 1.15, |
||||
pin: true, |
||||
anticipatePin: 1, |
||||
onUpdate: (self) => { |
||||
if (!videoEl) return; |
||||
|
||||
if (self.progress >= 0.42 && !hero2Started) { |
||||
hero2Started = true; |
||||
videoEl.currentTime = 0; |
||||
videoEl.play().catch(() => {}); |
||||
} |
||||
|
||||
if (self.progress < 0.42 && hero2Started) { |
||||
hero2Started = false; |
||||
videoEl.pause(); |
||||
videoEl.currentTime = 0; |
||||
} |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
tl.to(bgRef.current, { width: "100vw", height: "100vh", borderRadius: 0, ease: "none" }, 0) |
||||
.to(bgRef.current, { scale: 1.08, ease: "none" }, 0) |
||||
.to(progressBarRef.current, { width: "100%", ease: "none" }, 0) |
||||
.to(".fill-line", { backgroundPosition: "0% 0%", ease: "none", stagger: 0.14, duration: 1.35 }, 0.1) |
||||
|
||||
.to(hero2Ref.current, { opacity: 1, scale: 1.035, filter: "blur(0px)", ease: "none" }, 1.55) |
||||
.to(text1Ref.current, { opacity: 0, y: -34, scale: 0.985, filter: "blur(8px)", ease: "none" }, 1.78) |
||||
.to(text2Ref.current, { opacity: 1, y: 0, scale: 1, filter: "blur(0px)", ease: "none" }, 1.95) |
||||
.to(hero2Ref.current, { scale: 1.095, ease: "none" }, 2.5); |
||||
|
||||
return () => { |
||||
if (videoEl) { |
||||
videoEl.pause(); |
||||
videoEl.currentTime = 0; |
||||
} |
||||
darkHeroTrigger.kill(); |
||||
document.body.classList.remove("is-dark-hero"); |
||||
}; |
||||
}, sectionRef); |
||||
|
||||
return () => ctx.revert(); |
||||
}, []); |
||||
|
||||
return ( |
||||
<section className="main-section" ref={sectionRef}> |
||||
<div className="main-bg-wrap"> |
||||
<div className="main-bg" ref={bgRef}> |
||||
<div className="main-bg-hero1"></div> |
||||
|
||||
<div className="main-bg-hero2" ref={hero2Ref}> |
||||
<video ref={videoRef} className="hero-video" src="./images/test.mp4" muted loop playsInline preload="auto" /> |
||||
</div> |
||||
|
||||
<div className="main-visible-atmo main-visible-atmo1"></div> |
||||
<div className="main-visible-atmo main-visible-atmo2"></div> |
||||
<div className="main-visible-beam main-visible-beam1"></div> |
||||
<div className="main-visible-beam main-visible-beam2"></div> |
||||
<div className="main-visible-haze"></div> |
||||
<div className="main-grid"></div> |
||||
|
||||
<div className="main-text"> |
||||
<div className="text text-fill text-left-bottom" ref={text1Ref}> |
||||
<span className="main-kicker">PAL NETWORKS</span> |
||||
<span className="fill-line">Technology Partner for</span> |
||||
<br /> |
||||
<span className="fill-line">Advanced Air Mobility</span> |
||||
</div> |
||||
|
||||
<div className="text text-change text-center-hero" ref={text2Ref}> |
||||
항공 데이터와 통합 관제 기술로 |
||||
<br /> |
||||
안전한 하늘길을 설계합니다 |
||||
</div> |
||||
</div> |
||||
|
||||
<div className="main-progress"> |
||||
<span className="main-progress-bar" ref={progressBarRef}></span> |
||||
</div> |
||||
|
||||
<div className="main-scroll-indicator"> |
||||
<span className="main-scroll-line"></span> |
||||
<span className="main-scroll-text">SCROLL</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
); |
||||
} |
||||
|
||||
export default MainVisual; |
||||
Loading…
Reference in new issue