Browse Source

메인 끝

remotes/origin/main
김지은 2 months ago
parent
commit
230a1366d2
  1. 40
      src/components/main/MainUtm.jsx
  2. 145
      src/components/main/MainVisual.jsx
  3. 12
      src/css/common.css
  4. 142
      src/pages/MainPage.jsx

40
src/components/main/MainUtm.jsx

@ -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;

145
src/components/main/MainVisual.jsx

@ -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;

12
src/css/common.css

@ -20,12 +20,13 @@ body{overflow-x:hidden;}
.main-page{width:100%;overflow:hidden;}
.main-section{position:relative;height:100vh;}
.main-bg-wrap{position:relative;width:100%;height:100vh;display:flex;align-items:flex-start;justify-content:center;overflow:hidden;}
.main-bg{position:relative;width:min(1440px,calc(100vw - 80px));height:600px;border-radius:24px;overflow:hidden;transform-origin:center center;will-change:transform,width,height,border-radius;background:#050b17;}
.main-bg{position:relative;width:min(1440px,calc(100vw - 80px));height:750px;border-radius:24px;overflow:hidden;transform-origin:center center;will-change:transform,width,height,border-radius;background:#050b17;}
.main-bg-hero1{position:absolute;inset:0;background:url('/images/hero1.png') no-repeat 50% 50%/cover;transform:scale(1.04);}
.main-bg-hero2{position:absolute;inset:0;opacity:0;overflow:hidden;will-change:transform,opacity,filter;}
.main-bg-hero2::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(3,8,20,.18) 0%,rgba(3,8,20,.28) 35%,rgba(3,8,20,.42) 100%),rgba(0,0,0,.18);pointer-events:none;}
.hero-video{width:100%;height:100%;object-fit:cover;filter:brightness(.82) contrast(1.06) saturate(.96);transform:scale(1.02);}
/* .main-bg-hero2{position:absolute;inset:0;background:url('/images/hero2.png') no-repeat 50% 50%/cover;opacity:0;} */
.main-bg-hero2{position:absolute;inset:0;opacity:0;overflow:hidden;}
.hero-video{width:100%;height:100%;object-fit:cover;}
.main-bg-hero3{position:absolute;inset:0;background:url('/images/hero3.png') no-repeat 50% 50%/cover;opacity:0;}
.main-bg::before{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(4,10,24,.12) 0%,rgba(4,10,24,.24) 38%,rgba(4,10,24,.56) 100%),linear-gradient(90deg,rgba(0,0,0,.16) 0%,rgba(0,0,0,.05) 42%,rgba(0,0,0,.12) 100%);z-index:1;pointer-events:none;}
.main-bg::after{content:"";position:absolute;inset:0;background:radial-gradient(circle at 50% 58%,rgba(255,255,255,.06) 0%,rgba(255,255,255,.024) 18%,rgba(255,255,255,0) 44%),radial-gradient(circle at 50% 78%,rgba(58,64,129,.16) 0%,rgba(58,64,129,.08) 20%,rgba(58,64,129,0) 44%);z-index:1;pointer-events:none;}
@ -43,14 +44,17 @@ body{overflow-x:hidden;}
.main-grid{position:absolute;inset:0;z-index:2;pointer-events:none;opacity:.18;background-image:linear-gradient(rgba(174,206,255,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(174,206,255,.08) 1px,transparent 1px);background-size:64px 64px;mask-image:linear-gradient(180deg,rgba(0,0,0,0) 0%,rgba(0,0,0,.22) 20%,rgba(0,0,0,.72) 52%,rgba(0,0,0,.18) 100%);animation:gridShift 10s linear infinite;}
.main-text{position:absolute;inset:0;z-index:3;pointer-events:none;}
.main-text .text{position:absolute;color:#fff;text-shadow:0 10px 30px rgba(0,0,0,.22);will-change:transform,opacity,filter;}
.text-left-bottom{left:92px;bottom:124px;width:100%;max-width:760px;text-align:left;font-size:64px;line-height:1.12;font-weight:700;letter-spacing:-.05em;}
.text-center-hero{left:50%;top:45%;transform:translate(-50%,-50%);width:min(1080px,calc(100% - 120px));text-align:center;font-size:56px;line-height:1.24;font-weight:700;letter-spacing:-.045em;}
.main-text .text-change{opacity:0;}
.main-kicker{display:block;margin-bottom:18px;font-size:12px;letter-spacing:.22em;font-weight:600;color:rgba(255,255,255,.68);}
.main-text .text-fill .fill-line{display:inline-block;color:rgba(255,255,255,.28);background:linear-gradient(90deg,#fff 0%,#fff 50%,rgba(255,255,255,.28) 50%,rgba(255,255,255,.28) 100%);background-size:220% 100%;background-position:100% 0;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
.main-progress{position:absolute;left:50%;bottom:42px;transform:translateX(-50%);width:min(240px,calc(100% - 48px));height:2px;background:rgba(255,255,255,.22);z-index:3;overflow:hidden;border-radius:999px;}

142
src/pages/MainPage.jsx

@ -1,145 +1,11 @@
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
import MainVisual from "../components/main/MainVisual";
import MainUtm from "../components/main/MainUtm";
function MainPage() {
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 (
<main className="main-page">
<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>
<MainVisual />
<MainUtm />
</main>
);
}

Loading…
Cancel
Save