Browse Source

feat : 메인 페이지 utm + uam

remotes/origin/main
이시연 2 months ago
parent
commit
f64c2fd58a
  1. BIN
      public/images/main-utm-background.png
  2. BIN
      public/images/main-utm-img1.png
  3. BIN
      public/images/main-utm-img2.png
  4. BIN
      public/images/main-utm-img3.png
  5. BIN
      public/images/main-utm-img4.png
  6. 98
      src/components/main/MainCameraTransition.jsx
  7. 353
      src/components/main/MainUam.jsx
  8. 227
      src/components/main/MainUtm.jsx
  9. 381
      src/css/main.css
  10. 2
      src/pages/MainPage.jsx

BIN
public/images/main-utm-background.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
public/images/main-utm-img1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

BIN
public/images/main-utm-img2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
public/images/main-utm-img3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
public/images/main-utm-img4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

98
src/components/main/MainCameraTransition.jsx

@ -0,0 +1,98 @@
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
function MainAirspaceTransition() {
const sectionRef = useRef(null);
const leftRef = useRef(null);
const rightRef = useRef(null);
const lineRef = useRef([]);
const dotRef = useRef(null);
useEffect(() => {
const ctx = gsap.context(() => {
gsap.set(rightRef.current, { xPercent: 100 });
gsap.set(lineRef.current, { scaleX: 0, transformOrigin: "left center" });
gsap.set(dotRef.current, { x: -220, opacity: 0 });
const tl = gsap.timeline({
scrollTrigger: {
trigger: sectionRef.current,
start: "top top",
end: "+=1500",
scrub: 1,
pin: true,
anticipatePin: 1,
},
});
tl.to(lineRef.current, {
scaleX: 1,
stagger: 0.08,
duration: 0.7,
ease: "none",
});
tl.to(
dotRef.current,
{
x: 0,
opacity: 1,
duration: 0.7,
ease: "none",
},
"<0.1",
);
tl.to(leftRef.current, {
xPercent: -100,
duration: 1,
ease: "none",
});
tl.to(
rightRef.current,
{
xPercent: 0,
duration: 1,
ease: "none",
},
"<",
);
}, sectionRef);
return () => ctx.revert();
}, []);
return (
<section className="airspace-transition-section" ref={sectionRef}>
<div className="airspace-panel airspace-panel--utm" ref={leftRef}>
<p>UTM SYSTEM</p>
<h2>드론 하늘길에서</h2>
</div>
<div className="airspace-panel airspace-panel--uam" ref={rightRef}>
<p>UAM SYSTEM</p>
<h2>도심 항공 네트워크로</h2>
</div>
<div className="airspace-lines">
{[0, 1, 2, 3].map((item) => (
<span
key={item}
className={`airspace-line airspace-line--${item + 1}`}
ref={(el) => {
lineRef.current[item] = el;
}}
/>
))}
</div>
<div className="airspace-moving-dot" ref={dotRef} />
</section>
);
}
export default MainAirspaceTransition;

353
src/components/main/MainUam.jsx

@ -4,322 +4,149 @@ import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(ScrollTrigger);
const UAM_SPECS = [ function MainUam() {
{ const sectionRef = useRef(null);
id: "01", const overlayRef = useRef(null);
anchor: "navigation", const titleRef = useRef(null);
label: "자율 항법 시스템", const vehicleRef = useRef(null);
title: "AI 기반\n정밀 항법", const panelsRef = useRef([]);
desc: "다중 센서 융합과 실시간 AI 연산으로 도심 상공에서도 센티미터 단위의 정밀 경로 제어를 실현합니다.",
stat: "±0.03m", const items = [
statLabel: "위치 정확도",
cx: "28%",
cy: "38%",
cardSide: "left",
},
{ {
id: "02", label: "UAM NETWORK",
anchor: "comm", title: "Urban Air Mobility",
label: "통합 통신 모듈", desc: "도심 내 버티포트, 운항 경로, 항공 교통 데이터를 연결해 미래형 도심 항공 이동 환경을 구축합니다.",
title: "5G·위성\n이중 통신",
desc: "5G와 위성 통신을 동시에 운용하는 이중 링크 구조로 어떤 환경에서도 끊김 없는 데이터 채널을 보장합니다.",
stat: "<8ms",
statLabel: "지연 시간",
cx: "72%",
cy: "30%",
cardSide: "right",
}, },
{ {
id: "03", label: "VERTIPORT",
anchor: "power", title: "Vertiport Connection",
label: "하이브리드 추진", desc: "도심 곳곳의 이착륙 거점을 연결해 사람과 도시의 이동 흐름을 확장합니다.",
title: "전기·수소\n하이브리드",
desc: "전기 모터와 수소 셀을 결합한 하이브리드 추진 시스템으로 항속 거리를 극대화하고 탄소 배출을 최소화합니다.",
stat: "320km",
statLabel: "최대 항속",
cx: "50%",
cy: "72%",
cardSide: "left",
}, },
{ {
id: "04", label: "AIR CORRIDOR",
anchor: "sensor", title: "Smart Air Corridor",
label: "능동 안전 센서", desc: "복잡한 도심 상공에서도 안전한 항로를 구성하고 실시간 운항 흐름을 관리합니다.",
title: "360° 장애물\n회피",
desc: "LiDAR·레이더·광학 카메라 트리플 센서가 360도 전방위를 실시간 스캔해 돌발 장애물에 즉각 대응합니다.",
stat: "0.12s",
statLabel: "반응 속도",
cx: "78%",
cy: "65%",
cardSide: "right",
}, },
]; ];
function MainUam() {
const sectionRef = useRef(null);
const eyebrowRef = useRef(null);
const titleRef = useRef(null);
const subRef = useRef(null);
const aircraftRef = useRef(null);
const dotRefs = useRef([]);
const lineRefs = useRef([]);
const cardRefs = useRef([]);
useEffect(() => { useEffect(() => {
const ctx = gsap.context(() => { const ctx = gsap.context(() => {
dotRefs.current = dotRefs.current.slice(0, UAM_SPECS.length); gsap.set(overlayRef.current, { yPercent: 0 });
lineRefs.current = lineRefs.current.slice(0, UAM_SPECS.length); gsap.set(titleRef.current, { y: 80, opacity: 0 });
cardRefs.current = cardRefs.current.slice(0, UAM_SPECS.length); gsap.set(vehicleRef.current, {
xPercent: -120,
gsap.set(aircraftRef.current, { yPercent: 80,
opacity: 0, opacity: 0,
scale: 0.72,
x: "-18vw",
y: "12vh",
});
gsap.set(eyebrowRef.current, {
opacity: 0,
y: 28,
filter: "blur(8px)",
});
gsap.set(titleRef.current, {
opacity: 0,
y: 36,
filter: "blur(8px)",
});
gsap.set(subRef.current, {
opacity: 0,
y: 24,
});
cardRefs.current.forEach((el) => {
if (el) gsap.set(el, { opacity: 0, y: 28, scale: 0.96, filter: "blur(6px)" });
}); });
gsap.set(panelsRef.current, { y: 40, opacity: 0 });
dotRefs.current.forEach((el) => {
if (el) gsap.set(el, { scale: 0, opacity: 0 });
});
lineRefs.current.forEach((el) => {
if (el) gsap.set(el, { scaleX: 0, opacity: 0 });
});
ScrollTrigger.matchMedia({
"(min-width: 992px)": () => {
const tl = gsap.timeline({ const tl = gsap.timeline({
scrollTrigger: { scrollTrigger: {
trigger: sectionRef.current, trigger: sectionRef.current,
start: "top top", start: "top bottom",
end: "+=3200", end: "+=1400",
scrub: 1.05, scrub: 1,
pin: true,
anticipatePin: 1,
invalidateOnRefresh: true,
}, },
}); });
tl.to(eyebrowRef.current, { opacity: 1, y: 0, filter: "blur(0px)", ease: "none", duration: 0.5 }, 0).to(titleRef.current, { opacity: 1, y: 0, filter: "blur(0px)", ease: "none", duration: 0.65 }, 0.08).to(subRef.current, { opacity: 1, y: 0, ease: "none", duration: 0.45 }, 0.22).to( tl.to(overlayRef.current, {
aircraftRef.current, yPercent: -100,
{ duration: 1,
opacity: 1,
scale: 1,
x: 0,
y: 0,
ease: "none", ease: "none",
duration: 1.8, });
},
0.12,
);
UAM_SPECS.forEach((spec, i) => {
const baseTime = 0.5 + i * 0.52;
const origin = spec.cardSide === "left" ? "right center" : "left center";
tl.to( tl.to(
dotRefs.current[i], titleRef.current,
{ {
scale: 1,
opacity: 1,
ease: "none",
duration: 0.18,
},
baseTime,
)
.to(
lineRefs.current[i],
{
scaleX: 1,
opacity: 1,
transformOrigin: origin,
ease: "none",
duration: 0.24,
},
baseTime + 0.08,
)
.to(
cardRefs.current[i],
{
opacity: 1,
y: 0, y: 0,
scale: 1, opacity: 1,
filter: "blur(0px)", duration: 0.8,
ease: "none", ease: "none",
duration: 0.32,
}, },
baseTime + 0.18, "<0.25",
); );
});
tl.to( tl.to(
aircraftRef.current, vehicleRef.current,
{ {
scale: 1.05, xPercent: 0,
yPercent: 0,
opacity: 1,
duration: 1,
ease: "none", ease: "none",
duration: 0.45,
}, },
2.7, "<0.2",
); );
},
"(max-width: 991px)": () => { tl.to(
const tl = gsap.timeline({ panelsRef.current,
scrollTrigger: {
trigger: sectionRef.current,
start: "top 80%",
end: "bottom 30%",
scrub: 0.8,
invalidateOnRefresh: true,
},
});
tl.to(eyebrowRef.current, { opacity: 1, y: 0, filter: "blur(0px)", ease: "none", duration: 0.22 }, 0)
.to(titleRef.current, { opacity: 1, y: 0, filter: "blur(0px)", ease: "none", duration: 0.24 }, 0.05)
.to(subRef.current, { opacity: 1, y: 0, ease: "none", duration: 0.2 }, 0.1)
.to(
aircraftRef.current,
{ {
opacity: 1,
scale: 1,
x: 0,
y: 0, y: 0,
ease: "none",
duration: 0.28,
},
0.16,
)
.to(
dotRefs.current,
{
scale: 1,
opacity: 1,
stagger: 0.05,
ease: "none",
duration: 0.18,
},
0.24,
)
.to(
lineRefs.current,
{
scaleX: 1,
opacity: 1, opacity: 1,
stagger: 0.05, stagger: 0.16,
ease: "none", duration: 0.8,
duration: 0.18,
},
0.3,
)
.to(
cardRefs.current,
{
opacity: 1,
y: 0,
scale: 1,
filter: "blur(0px)",
stagger: 0.06,
ease: "none", ease: "none",
duration: 0.2,
}, },
0.36, "<0.35",
); );
},
});
gsap.to(aircraftRef.current, {
y: -8,
duration: 2.8,
ease: "sine.inOut",
repeat: -1,
yoyo: true,
});
}, sectionRef); }, sectionRef);
return () => ctx.revert(); return () => ctx.revert();
}, []); }, []);
return ( return (
<section className="uam-section" ref={sectionRef}> <section className="main-uam-section" ref={sectionRef}>
<div className="uam-bg-grid"></div> <div className="main-uam-screen-wipe" ref={overlayRef}>
<div className="uam-bg-glow uam-bg-glow--a"></div> <span>FROM UTM</span>
<div className="uam-bg-glow uam-bg-glow--b"></div> <strong>TO UAM</strong>
<div className="uam-inner">
<div className="uam-header">
<div className="uam-eyebrow" ref={eyebrowRef}>
<span className="uam-eyebrow-dot"></span>
UAM TECHNOLOGY
</div>
<h2 className="uam-title" ref={titleRef}>
도심 항공 모빌리티,
<br />
<em>기술로 완성합니다</em>
</h2>
<p className="uam-sub" ref={subRef}>
PAL NETWORKS의 4 핵심 기술이 안전한 하늘길을 만듭니다
</p>
</div> </div>
<div className="uam-stage"> <div className="main-uam-bg-grid" />
<div className="uam-aircraft-wrap" ref={aircraftRef}> <div className="main-uam-light main-uam-light--a" />
<img className="uam-aircraft-img" src="./images/uam.png" alt="UAM 기체" draggable={false} /> <div className="main-uam-light main-uam-light--b" />
<div className="uam-aircraft-shadow"></div>
</div>
{UAM_SPECS.map((spec, i) => (
<div key={spec.id} className="uam-point-group">
<div className="uam-dot" ref={(el) => (dotRefs.current[i] = el)} style={{ left: spec.cx, top: spec.cy }}>
<span className="uam-dot-ring"></span>
<span className="uam-dot-core"></span>
</div>
<div className={`uam-line uam-line--${spec.cardSide}`} ref={(el) => (lineRefs.current[i] = el)} style={{ left: spec.cx, top: spec.cy }}></div> <div className="main-uam-inner">
<div className="main-uam-visual">
<div className={`uam-card uam-card--${spec.cardSide}`} ref={(el) => (cardRefs.current[i] = el)} style={{ left: spec.cx, top: `calc(${spec.cy} - 10px)` }}> <div className="main-uam-route main-uam-route--a" />
<div className="uam-card-index">{spec.id}</div> <div className="main-uam-route main-uam-route--b" />
<div className="uam-card-label">{spec.label}</div> <div className="main-uam-route main-uam-route--c" />
<strong className="uam-card-title">
{spec.title.split("\n").map((line, j) => (
<span key={j}>
{line}
{j === 0 && <br />}
</span>
))}
</strong>
<p className="uam-card-desc">{spec.desc}</p> <div className="main-uam-node main-uam-node--a">V</div>
<div className="main-uam-node main-uam-node--b">P</div>
<div className="main-uam-node main-uam-node--c">C</div>
<div className="uam-card-stat"> <div className="main-uam-vehicle" ref={vehicleRef}>
<span className="uam-card-stat-value">{spec.stat}</span> <span />
<span className="uam-card-stat-label">{spec.statLabel}</span>
</div> </div>
</div> </div>
<div className="main-uam-content" ref={titleRef}>
<p className="main-uam-eyebrow">UAM SYSTEM</p>
<h2>
도심항공교통
<br />
UAM
</h2>
<p>
드론 관제에서 확장된 하늘길은 이제 도시의 이동 네트워크로
이어집니다. UAM은 버티포트, 항로, 운항 데이터를 하나의 흐름으로
연결합니다.
</p>
</div> </div>
<div className="main-uam-panels">
{items.map((item, index) => (
<article
className="main-uam-panel"
key={item.label}
ref={(el) => {
panelsRef.current[index] = el;
}}
>
<span>{item.label}</span>
<h3>{item.title}</h3>
<p>{item.desc}</p>
</article>
))} ))}
</div> </div>
</div> </div>

227
src/components/main/MainUtm.jsx

@ -6,109 +6,194 @@ gsap.registerPlugin(ScrollTrigger);
function MainUtm() { function MainUtm() {
const sectionRef = useRef(null); const sectionRef = useRef(null);
const itemsRef = useRef([]); const cardsRef = useRef([]);
const items = [
{
step: "01",
label: "비행 검토",
title: "비행가능여부 확인",
desc: "선택한 공역에서 드론 비행 가능 여부와 제한 조건을 실시간으로 확인합니다.",
},
{
step: "02",
label: "승인 신청",
title: "자동비행승인 신청",
desc: "복잡한 승인 절차 없이 간편하게 비행 자동 승인을 신청할 수 있습니다.",
},
{
step: "03",
label: "비행 관제",
title: "실시간 모니터링",
desc: "드론의 위치, 비행 경로, 운항 상태를 실시간으로 확인하고 모니터링합니다.",
},
{
step: "04",
label: "통합 관리",
title: "비행 데이터 관리",
desc: "비행 이력과 운항 스케줄 데이터를 통합 관리하여 효율적인 드론 운영을 지원합니다.",
},
];
useEffect(() => { useEffect(() => {
const ctx = gsap.context(() => { const ctx = gsap.context(() => {
itemsRef.current.forEach((el) => { const cards = cardsRef.current.filter(Boolean);
const text = el.querySelector(".main-utm-text");
const visual = el.querySelector(".main-utm-visual");
gsap.fromTo( gsap.set(cards, {
text, yPercent: 110,
{ y: 50, opacity: 0 }, scale: 1,
{
y: 0,
opacity: 1, opacity: 1,
duration: 0.8, rotateX: 0,
ease: "power3.out", transformOrigin: "center bottom",
});
gsap.set(cards[0], {
yPercent: 0,
});
const tl = gsap.timeline({
scrollTrigger: { scrollTrigger: {
trigger: el, trigger: sectionRef.current,
start: "top 80%", start: "top top",
end: `+=${items.length * 900}`,
scrub: 1,
pin: true,
anticipatePin: 1,
}, },
});
cards.forEach((card, index) => {
if (index === 0) return;
const prevCard = cards[index - 1];
tl.to(
prevCard,
{
yPercent: -18,
scale: 0.88,
opacity: 0.35,
rotateX: 8,
filter: "blur(3px)",
duration: 1,
ease: "none",
}, },
index - 0.85,
); );
gsap.fromTo( tl.to(
visual, card,
{ x: el.classList.contains("reverse") ? -80 : 80, opacity: 0 },
{ {
x: 0, yPercent: 0,
opacity: 1, duration: 1,
duration: 0.9, ease: "none",
ease: "power3.out",
scrollTrigger: {
trigger: el,
start: "top 80%",
},
}, },
index - 0.85,
); );
}); });
tl.to(cards[cards.length - 1], {
yPercent: -10,
scale: 0.94,
duration: 0.8,
ease: "none",
});
}, sectionRef); }, sectionRef);
return () => ctx.revert(); return () => ctx.revert();
}, []); }, [items.length]);
return ( return (
<section className="main-utm-section" ref={sectionRef}> <section className="main-utm-section" ref={sectionRef}>
<div className="main-utm-inner"> <div className="main-utm-bg-glow main-utm-bg-glow--a" />
<div className="main-utm-head"> <div className="main-utm-bg-glow main-utm-bg-glow--b" />
<p className="main-utm-eyebrow">UTM SYSTEM</p>
<h2 className="main-utm-title">Unmanned Aircraft Traffic Management</h2>
<p className="main-utm-desc">드론 하늘길을 통제하는 관제 시스템</p>
</div>
{/* STEP 1 */} <div className="utm-floating-air" aria-hidden="true">
<div className="main-utm-item" ref={(el) => (itemsRef.current[0] = el)}> <div className="utm-floating-drone utm-floating-drone--a">
<div className="main-utm-text"> <svg viewBox="0 0 64 64" aria-hidden="true">
<span className="main-utm-step">01</span> <circle cx="18" cy="18" r="10" />
<h3>Flight Planning</h3> <circle cx="46" cy="18" r="10" />
<p>비행 계획을 등록하고 승인 가능 여부를 자동으로 판단합니다</p> <circle cx="18" cy="46" r="10" />
</div> <circle cx="46" cy="46" r="10" />
<div className="main-utm-visual">
<img src="./images/test.png" alt="" />
</div>
</div>
{/* STEP 2 */} <path d="M25 25L39 39" />
<div className="main-utm-item reverse" ref={(el) => (itemsRef.current[1] = el)}> <path d="M39 25L25 39" />
<div className="main-utm-text">
<span className="main-utm-step">02</span> <circle cx="32" cy="32" r="3.5" />
<h3>Auto Approval</h3> </svg>
<p>공역, 장애물, 규제 조건을 기반으로 비행 승인 여부를 결정합니다</p>
</div> <span />
<div className="main-utm-visual">
<img src="./images/test.png" alt="" />
</div> </div>
<div className="utm-floating-drone utm-floating-drone--b">
<svg viewBox="0 0 64 64" aria-hidden="true">
<circle cx="18" cy="18" r="10" />
<circle cx="46" cy="18" r="10" />
<circle cx="18" cy="46" r="10" />
<circle cx="46" cy="46" r="10" />
<path d="M25 25L39 39" />
<path d="M39 25L25 39" />
<circle cx="32" cy="32" r="3.5" />
</svg>
<span />
</div> </div>
{/* STEP 3 */} <div className="utm-floating-drone utm-floating-drone--c">
<div className="main-utm-item" ref={(el) => (itemsRef.current[2] = el)}> <svg viewBox="0 0 64 64" aria-hidden="true">
<div className="main-utm-text"> <circle cx="18" cy="18" r="10" />
<span className="main-utm-step">03</span> <circle cx="46" cy="18" r="10" />
<h3>Real-time Monitoring</h3> <circle cx="18" cy="46" r="10" />
<p>비행 상태를 실시간으로 추적하고 상황을 시각화합니다</p> <circle cx="46" cy="46" r="10" />
<path d="M25 25L39 39" />
<path d="M39 25L25 39" />
<circle cx="32" cy="32" r="3.5" />
</svg>
<span />
</div> </div>
<div className="main-utm-visual">
<img src="./images/test.png" alt="" />
</div> </div>
<div className="main-utm-inner">
<div className="main-utm-head">
<p className="main-utm-eyebrow">UTM SYSTEM</p>
<h2 className="main-utm-title">드론교통관리 (UTM)</h2>
<p className="main-utm-desc">드론 하늘길을 통제하는 관제 시스템</p>
</div> </div>
{/* STEP 4 */} <div className="main-utm-stack">
<div className="main-utm-item reverse" ref={(el) => (itemsRef.current[3] = el)}> {items.map((item, index) => (
<div className="main-utm-text"> <article
<span className="main-utm-step">04</span> className="main-utm-card"
<h3>Alert & Response</h3> key={item.step}
<p>비정상 상황 발생 즉시 대응할 있도록 지원합니다</p> ref={(el) => {
cardsRef.current[index] = el;
}}
>
<div className="main-utm-card-text">
<span className="main-utm-step">{item.step}</span>
<p className="main-utm-label">{item.label}</p>
<h3>{item.title}</h3>
<p>{item.desc}</p>
</div> </div>
<div className="main-utm-visual">
<img src="./images/test.png" alt="" /> <div className="main-utm-card-visual">
<div className="main-utm-card-visual">
<img
className={`main-utm-img main-utm-img${index + 1}`}
src={`./images/main-utm-img${index + 1}.png`}
alt=""
/>
</div> </div>
</div> </div>
</article>
<div className="main-utm-final"> ))}
<p>복잡한 공역 관리부터 실시간 대응까지</p>
<h3>UTM은 드론 운영의 모든 과정을 하나의 시스템으로 통합합니다</h3>
</div> </div>
</div> </div>
</section> </section>

381
src/css/main.css

@ -127,3 +127,384 @@ body{overflow-x:hidden;}
.main-scroll-line{height:22px;} .main-scroll-line{height:22px;}
.main-scroll-text{font-size:10px;letter-spacing:.22em;} .main-scroll-text{font-size:10px;letter-spacing:.22em;}
} }
/* ─── Section ─── */
.main-utm-section{position:relative;width:100%;min-height:100vh;padding-top:86px;overflow:hidden;background:radial-gradient(circle at 12% 18%,rgba(58,64,129,.08),transparent 32%),radial-gradient(circle at 88% 18%,rgba(58,64,129,.06),transparent 34%),linear-gradient(180deg,#f7f9ff 0%,#fff 100%);}
.main-utm-section::before{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(247,249,255,.82) 0%,rgba(247,249,255,.42) 24%,rgba(247,249,255,.12) 52%,rgba(247,249,255,.42) 78%,rgba(247,249,255,.82) 100%),url("/images/main-utm-background.png") no-repeat 50% 50% / cover;opacity:.52;pointer-events:none;z-index:0;}
/* ─── Glow ─── */
.main-utm-bg-glow{position:absolute;border-radius:999px;filter:blur(90px);pointer-events:none;}
.main-utm-bg-glow--a{width:520px;height:520px;top:-160px;right:-120px;background:rgba(58,64,129,.13);}
.main-utm-bg-glow--b{width:460px;height:460px;left:-140px;bottom:-160px;background:rgba(112,180,255,.12);}
/* ─── Inner ─── */
.main-utm-inner{position:relative;z-index:2;width:min(1440px,calc(100% - 120px));min-height:calc(100vh - 86px);margin:0 auto;padding-top:72px;padding-bottom:64px;}
/* ─── Head ─── */
.main-utm-head{position:relative;z-index:5;max-width:820px;margin-bottom:64px;}
.main-utm-eyebrow{margin:0 0 14px;font-size:12px;font-weight:800;letter-spacing:.24em;color:#3a4081;}
.main-utm-title{
margin:0;
font-size:clamp(40px,4vw,64px);
font-weight:800;
line-height:.92;
letter-spacing:-0.08em;
color:#0e1120;
}.main-utm-desc{margin:22px 0 0;font-size:16px;line-height:1.6;letter-spacing:-.02em;color:#697083;}
/* ─── Card Stack ─── */
.main-utm-stack{position:relative;width:100%;height:min(430px,50vh);perspective:1400px;}
.main-utm-card{position:absolute;inset:0;display:grid;grid-template-columns:.9fr 1.1fr;align-items:center;gap:64px;padding:54px 62px;border-radius:34px;overflow:hidden;background:rgba(255,255,255,.86);border:1px solid rgba(58,64,129,.1);box-shadow:0 30px 80px rgba(14,17,32,.1),inset 0 1px 0 rgba(255,255,255,.9);backdrop-filter:blur(22px);will-change:transform,opacity,filter;}
.main-utm-card::before{content:"";position:absolute;inset:0;background:linear-gradient(135deg,rgba(255,255,255,.78),transparent 46%),radial-gradient(circle at 82% 28%,rgba(58,64,129,.11),transparent 34%);pointer-events:none;}
.main-utm-card-text,.main-utm-card-visual{position:relative;z-index:2;}
.main-utm-step{display:inline-flex;align-items:center;justify-content:center;width:58px;height:32px;margin-bottom:24px;border-radius:999px;background:rgba(58,64,129,.1);color:#3a4081;font-size:13px;font-weight:800;letter-spacing:.1em;}
.main-utm-label{margin:0 0 10px;font-size:12px;font-weight:800;letter-spacing:.18em;color:rgba(58,64,129,.62);}
.main-utm-card h3{margin:0;font-size:clamp(34px,3.2vw,54px);line-height:1.06;letter-spacing:-.055em;color:#101322;}
.main-utm-card-text > p:last-child{max-width:430px;margin:22px 0 0;font-size:17px;line-height:1.72;word-break:keep-all;color:#636b7e;}
/* ─── Card Visual ─── */
.main-utm-card-visual{
position:relative;
height:100%;
min-height:300px;
border-radius:28px;
overflow:hidden;
background:#f7f8fc;
align-items:center;
overflow:hidden;
justify-content:center;
}
.main-utm-card-visual::after{
content:"";
position:absolute;
inset:18px;
border-radius:24px;
background:linear-gradient(180deg,rgba(255,255,255,.12),transparent 30%);
pointer-events:none;
}
.main-utm-img{
width:100%;
height:100%;
object-fit:cover;
display:block;
border-radius:24px;
box-shadow:
inset 0 0 0 1px rgba(58,64,129,.06),
0 18px 40px rgba(17,22,40,.08),
0 0 0 8px rgba(255,255,255,.42);
background:#fff;
overflow:hidden;
transform:translateZ(0);
}
.main-utm-orbit{position:absolute;inset:32px;border-radius:28px;}
.main-utm-orbit span{position:absolute;border:1px solid rgba(58,64,129,.14);border-radius:50%;}
.main-utm-orbit span:nth-child(1){width:340px;height:340px;left:50%;top:50%;transform:translate(-50%,-50%);}
.main-utm-orbit span:nth-child(2){width:230px;height:230px;left:18%;top:16%;}
.main-utm-orbit span:nth-child(3){width:170px;height:170px;right:10%;bottom:10%;}
.main-utm-map{position:absolute;inset:0;}
.main-utm-route{position:absolute;height:2px;border-radius:999px;background:linear-gradient(90deg,transparent,#3a4081,transparent);box-shadow:0 0 16px rgba(58,64,129,.32);opacity:.7;}
.main-utm-route--a{width:62%;left:18%;top:42%;transform:rotate(-18deg);}
.main-utm-route--b{width:48%;right:12%;top:62%;transform:rotate(22deg);}
/* ─── Floating Drones ─── */
.utm-floating-air{position:absolute;top:120px;right:2%;width:42vw;height:210px;pointer-events:none;z-index:1;overflow:visible;animation:utmAirFlow 18s linear infinite;}
.utm-floating-drone{position:absolute;width:52px;height:52px;border-radius:50%;background:rgba(255,255,255,.42);backdrop-filter:blur(12px);box-shadow:0 0 0 8px rgba(58,64,129,.04),0 10px 24px rgba(58,64,129,.1);}
.utm-floating-drone svg{position:absolute;inset:9px;width:34px;height:34px;overflow:visible;}
.utm-floating-drone svg circle{fill:none;stroke:#3a4081;stroke-width:2.4;}
.utm-floating-drone svg circle:first-child{filter:drop-shadow(0 0 10px rgba(58,64,129,.34));}
.utm-floating-drone svg path{fill:none;stroke:#3a4081;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round;}
.utm-floating-drone span{position:absolute;inset:-12px;border-radius:50%;border:1px solid rgba(58,64,129,.2);animation:utmDronePulse 2.8s ease-out infinite;}
.utm-floating-drone--a{left:10%;top:122px;animation:utmDroneMoveB 11s ease-in-out infinite,utmDronePulseFloat 6s ease-in-out infinite;}
.utm-floating-drone--a svg circle,
.utm-floating-drone--a svg path{
stroke:#5B6CFF;
}
.utm-floating-drone--a svg circle:first-child{
filter:drop-shadow(0 0 10px rgba(91,108,255,.34));
}
.utm-floating-drone--b{
left:44%;
top:72px;
box-shadow:
0 0 0 8px rgba(243,199,104,.08),
0 10px 24px rgba(243,199,104,.14);
animation:
utmDroneMoveB 11s ease-in-out infinite,
utmDronePulseFloat 6s ease-in-out infinite;
}
.utm-floating-drone--b svg{
animation:utmDroneColorShift 6s ease-in-out infinite;
}
.utm-floating-drone--b svg circle,
.utm-floating-drone--b svg path{
stroke:#F3C768;
}
.utm-floating-drone--b svg circle:first-child{
filter:drop-shadow(0 0 10px rgba(243,199,104,.34));
}
.utm-floating-drone--c{right:4%;top:138px;animation:utmDroneMoveB 11s ease-in-out infinite,utmDronePulseFloat 6s ease-in-out infinite;animation-delay:-2.4s; box-shadow:
0 0 0 8px rgba(255,123,123,.06),
0 10px 24px rgba(255,123,123,.12);
}
.utm-floating-drone--c svg circle,
.utm-floating-drone--c svg path{
stroke:#FF7B7B;
}
.utm-floating-drone--c svg circle:first-child{
filter:drop-shadow(0 0 10px rgba(255,123,123,.32));
}
/* ─── Responsive ─── */
@media(max-width:1024px){
.main-utm-section{padding-top:76px;}
.main-utm-inner{width:calc(100% - 56px);min-height:calc(100vh - 76px);padding-top:56px;}
.main-utm-head{margin-bottom:56px;}
.main-utm-card{grid-template-columns:1fr;gap:28px;padding:34px;}
.main-utm-card-visual{min-height:240px;}
.utm-floating-air{width:58vw;top:160px;opacity:.72;}
}
@media(max-width:768px){
.main-utm-inner{width:calc(100% - 32px);padding-top:42px;}
.main-utm-title{font-size:38px;}
.main-utm-desc{font-size:14px;}
.main-utm-stack{height:64vh;}
.main-utm-card{padding:26px 22px;border-radius:24px;}
.main-utm-card h3{font-size:32px;}
.main-utm-card-text > p:last-child{font-size:14px;}
.main-utm-card-visual{min-height:210px;}
.utm-floating-air{display:none;}
}
/* ─── Keyframes ─── */
@keyframes utmAirFlow{0%,100%{transform:translateX(0);}50%{transform:translateX(-18px);}}
@keyframes utmDroneMoveA{0%,100%{transform:translate3d(0,0,0) scale(1);}50%{transform:translate3d(26px,-12px,0) scale(1.04);}}
@keyframes utmDroneMoveB{0%,100%{transform:translate3d(0,0,0) scale(.82);}50%{transform:translate3d(-20px,10px,0) scale(.88);}}
@keyframes utmDronePulse{0%{transform:scale(.7);opacity:.62;}100%{transform:scale(1.9);opacity:0;}}
@keyframes utmDronePulseFloat{0%,100%{filter:drop-shadow(0 0 0 rgba(58,64,129,0));}50%{filter:drop-shadow(0 0 18px rgba(58,64,129,.18));}}
@keyframes utmDroneColorShift{
0%{
filter:none;
}
45%{
filter:none;
}
50%{
filter:drop-shadow(0 0 10px rgba(243,199,104,.42));
}
55%{
filter:drop-shadow(0 0 10px rgba(243,199,104,.42));
}
100%{
filter:none;
}
}
.airspace-transition-section {
position: relative;
height: 100vh;
overflow: hidden;
background:
radial-gradient(circle at 12% 18%, rgba(58,64,129,.08), transparent 32%),
radial-gradient(circle at 88% 18%, rgba(58,64,129,.06), transparent 34%),
linear-gradient(180deg, #f7f9ff 0%, #ffffff 100%);
}
.airspace-transition-section::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(
90deg,
rgba(255,255,255,.72) 0%,
rgba(255,255,255,.24) 20%,
rgba(255,255,255,.08) 50%,
rgba(255,255,255,.24) 80%,
rgba(255,255,255,.72) 100%
);
pointer-events: none;
}
.airspace-panel {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 8vw;
}
.airspace-panel--utm {
background: transparent;
}
.airspace-panel--uam {
background:
radial-gradient(circle at 72% 34%, rgba(112,180,255,.12), transparent 34%),
radial-gradient(circle at 42% 78%, rgba(58,64,129,.08), transparent 28%);
}
.airspace-panel p {
margin: 0 0 18px;
font-size: 12px;
font-weight: 800;
letter-spacing: .26em;
color: #3a4081;
}
.airspace-panel h2 {
margin: 0;
max-width: 760px;
font-size: clamp(48px, 6vw, 96px);
font-weight: 800;
line-height: .96;
letter-spacing: -.08em;
color: #0e1120;
}
.airspace-lines {
position: absolute;
inset: 0;
z-index: 5;
pointer-events: none;
}
/* .airspace-line {
position: absolute;
left: 8vw;
height: 1px;
border-radius: 999px;
background:
linear-gradient(
90deg,
rgba(58,64,129,0),
rgba(58,64,129,.06),
rgba(58,64,129,.14),
rgba(58,64,129,.06),
rgba(58,64,129,0)
);
filter: blur(.4px);
opacity: .55;
mix-blend-mode: multiply;
transform-origin: left center;
}
.airspace-line::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(
90deg,
transparent 0%,
rgba(255,255,255,.65) 18%,
rgba(255,255,255,0) 42%
);
mix-blend-mode: screen;
}
.airspace-line--1 {
top: 36%;
width: 62vw;
transform: rotate(-8deg);
}
.airspace-line--2 {
top: 48%;
width: 74vw;
transform: rotate(3deg);
}
.airspace-line--3 {
top: 60%;
width: 58vw;
transform: rotate(10deg);
}
.airspace-line--4 {
top: 70%;
width: 46vw;
transform: rotate(-4deg);
opacity: .52;
} */
.airspace-moving-dot {
position: absolute;
left: 50%;
top: 49%;
z-index: 6;
width: 54px;
height: 54px;
border-radius: 50%;
background: rgba(255,255,255,.78);
border: 1px solid rgba(58,64,129,.14);
box-shadow:
0 0 0 12px rgba(58,64,129,.06),
0 20px 50px rgba(58,64,129,.18);
}
.airspace-moving-dot::before {
content: "";
position: absolute;
inset: 16px;
border-radius: 50%;
background: #3a4081;
box-shadow: 0 0 18px rgba(58,64,129,.42);
}
@media (max-width: 768px) {
.airspace-panel {
padding: 0 28px;
}
.airspace-panel h2 {
font-size: 44px;
}
.airspace-line {
left: 28px;
}
}

2
src/pages/MainPage.jsx

@ -1,12 +1,14 @@
import MainVisual from "../components/main/MainVisual"; import MainVisual from "../components/main/MainVisual";
import MainUtm from "../components/main/MainUtm"; import MainUtm from "../components/main/MainUtm";
import MainUam from "../components/main/MainUam"; import MainUam from "../components/main/MainUam";
import MainCameraTransition from "../components/main/MainCameraTransition";
function MainPage() { function MainPage() {
return ( return (
<main className="main-page"> <main className="main-page">
<MainVisual /> <MainVisual />
<MainUtm /> <MainUtm />
<MainCameraTransition />
{/* <MainUam /> */} {/* <MainUam /> */}
</main> </main>
); );

Loading…
Cancel
Save