You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

624 lines
22 KiB

import { useRef } from "react";
import SubHero from "../../components/SubHero";
import useFadeIn from "../../hooks/useFadeIn";
import { motion, useInView, useScroll, useTransform } from "framer-motion";
import {
MapPin,
ShieldAlert,
Network,
BadgeCheck,
Cloud,
FileCheck,
Database,
Layers,
Navigation,
CheckCircle,
Radio,
Plane,
Bell,
} from "lucide-react";
const ease = [0.22, 1, 0.36, 1];
const basePath = import.meta.env.BASE_URL;
const INTRO_CARDS = [
{ label: "비행 계획", icon: Navigation },
{ label: "비행 승인", icon: CheckCircle },
{ label: "실시간 관제", icon: Radio },
{ label: "데이터 관리", icon: Database },
];
const UTM_WHAT_LEFT = [
{ icon: MapPin, label: "실시간 위치 감시" },
{ icon: ShieldAlert, label: "충돌 위험 사전 분석" },
{ icon: Cloud, label: "기상 정보 자동 연계" },
{ icon: Network, label: "공역 설정·지오펜싱" },
];
const UTM_WHAT_RIGHT = [
{ icon: FileCheck, label: "비행 계획 및 경로 승인" },
{ icon: BadgeCheck, label: "불법 기체 실시간 감지" },
{ icon: Database, label: "비행 데이터 통합 관리" },
{ icon: Layers, label: "UAM·드론 시스템 연동" },
];
const EVO_ITEMS = [
{
img: `${basePath}images/utm_intro_drone.png`,
alt: "DRONE",
name: "DRONE",
desc: (
<>
개별 드론 운용 단계.
<br />
단순 비행 임무 수행 중심으로 체계적 관리 체계 부재.
</>
),
tag: "단일 기체 운용",
side: "left",
active: false,
},
{
img: `${basePath}images/utm_intro_utm.png`,
alt: "UTM",
name: "UTM",
desc: (
<>
무인기 교통관리 시스템 도입.
<br />
비행 경로 승인·충돌 방지·공역 관리 체계화.
</>
),
tag: "공역 관리 시스템",
side: "right",
active: false,
},
{
img: `${basePath}images/utm_intro_uam.png`,
alt: "UAM",
name: "UAM",
desc: (
<>
도심항공모빌리티 등장.
<br />
유인 eVTOL 기체가 도심 저고도 공역을 비행.
</>
),
tag: "도심 항공 모빌리티",
side: "left",
active: false,
},
{
img: `${basePath}images/utm_intro_uatm.png`,
alt: "UATM",
name: "UATM",
desc: (
<>
UAM과 UTM의 완전한 통합.
<br />
기체 운용부터 관제까지 하나의 플랫폼으로 실현.
</>
),
tag: "통합 관제 플랫폼",
side: "right",
active: true,
},
];
const SECTORS = [
{
id: 0,
num: "01",
name: "UAM 기체 운용",
color: "#E8193C",
tags: [
"eVTOL 기체 직접 운용",
"실 운용 데이터 기반 검증",
"국내 유일 통합 사업자",
"안전 관리 체계 구축",
],
},
{
id: 1,
num: "02",
name: "UTM 관제 기술",
color: "#1A1F5E",
tags: [
"실시간 공역 감시·관제",
"충돌 위험 사전 분석",
"비행 경로 승인 시스템",
"지오펜싱·공역 설정",
],
},
{
id: 2,
num: "03",
name: "통합 항공 데이터 플랫폼",
color: "#3D3D3D",
tags: [
"비행 데이터 수집·분석",
"UAM·드론 시스템 연동",
"기상 정보 자동 연계",
"통합 데이터 관리",
],
},
];
/* ────────────────────────────────────────
EvoItem
──────────────────────────────────────── */
function EvoItem({ item, index }) {
const itemRef = useRef(null);
const itemInView = useInView(itemRef, { once: true, margin: "-80px" });
const itemActive = useInView(itemRef, { margin: "-45% 0px -45% 0px" });
return (
<motion.div
ref={itemRef}
className={`utm-evo__item${item.active ? " utm-evo__item--active" : ""}${itemActive ? " utm-evo__item--highlight" : ""}`}
initial={{ opacity: 0, y: 40 }}
animate={itemInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
{item.side === "left" ? (
<>
<div className="utm-evo__card-wrap utm-evo__card-wrap--left">
<div className="utm-evo__card">
<img
src={item.img}
alt={item.alt}
className="utm-evo__card-img"
/>
<div className="utm-evo__card-body">
<div className="utm-evo__card-name">{item.name}</div>
<div className="utm-evo__card-desc">{item.desc}</div>
<span className="utm-evo__card-tag">{item.tag}</span>
</div>
</div>
</div>
<div className="utm-evo__dot-wrap">
<div className="utm-evo__dot">
<span>{String(index + 1).padStart(2, "0")}</span>
</div>
</div>
<div className="utm-evo__empty" />
</>
) : (
<>
<div className="utm-evo__empty" />
<div className="utm-evo__dot-wrap">
<div className="utm-evo__dot">
<span>{String(index + 1).padStart(2, "0")}</span>
</div>
</div>
<div className="utm-evo__card-wrap utm-evo__card-wrap--right">
<div className="utm-evo__card">
<img
src={item.img}
alt={item.alt}
className="utm-evo__card-img"
/>
<div className="utm-evo__card-body">
<div className="utm-evo__card-name">{item.name}</div>
<div className="utm-evo__card-desc">{item.desc}</div>
<span className="utm-evo__card-tag">{item.tag}</span>
</div>
</div>
</div>
</>
)}
</motion.div>
);
}
/* ────────────────────────────────────────
IntroPage
──────────────────────────────────────── */
function IntroPage() {
const ref = useFadeIn();
const UTM_NAV = [
{ label: "UTM/UATM 소개", to: "/utm/intro" },
{ label: "도입사례", to: "/utm/case" },
];
const introRef = useRef(null);
const introInView = useInView(introRef, { once: true, margin: "-60px" });
const whatRef = useRef(null);
const whatInView = useInView(whatRef, { once: true, margin: "-100px" });
const evoRef = useRef(null);
const evoInView = useInView(evoRef, { once: true, margin: "-100px" });
const evoTrackRef = useRef(null);
const capRef = useRef(null);
const capInView = useInView(capRef, { once: true, margin: "-80px" });
const { scrollYProgress } = useScroll({
target: evoTrackRef,
offset: ["start center", "end center"],
});
const lineHeight = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
return (
<article ref={ref}>
<SubHero
label="UTM/UATM"
title={
<>
<em>UTM/UATM</em>
</>
}
navItems={UTM_NAV}
/>
<div className="sub-content">
{/* ── 01 Overview ── */}
<div className="inner-wrap">
<section className="utm-intro" ref={introRef}>
<div className="utm-intro__top">
<motion.span
className="fc-eyebrow"
initial={{ opacity: 0, y: 16 }}
animate={introInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
Overview
</motion.span>
<motion.h2
className="utm-intro__title"
initial={{ opacity: 0, y: 20 }}
animate={introInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.7, delay: 0.1, ease }}
>
PAL Networks의 UTM/UATM은
<br />
<em>도심 항공 관제의 {""}</em>
<em>새로운 기준입니다.</em>
</motion.h2>
<motion.p
className="utm-intro__desc"
initial={{ opacity: 0, y: 16 }}
animate={introInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.2, ease }}
>
UAM 기체 운용과 UTM 관제 플랫폼을 동시에 보유한 국내 유일의 통합
사업자입니다.
<br /> 운용 데이터로 검증된 시스템으로{" "}
<strong>안전한 저고도 공역 관리</strong> .
</motion.p>
</div>
<ul className="utm-intro__cards">
{INTRO_CARDS.map((card, i) => (
<motion.li
key={card.label}
className="utm-intro__card"
initial={{ opacity: 0, y: 60 }}
animate={introInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.2 + i * 0.12, ease }}
>
<span className="utm-intro__card-num">
{String(i + 1).padStart(2, "0")}
</span>
<span className="utm-intro__card-icon">
<svg width="0" height="0">
<defs>
<linearGradient
id="icon-grad"
x1="0%"
y1="0%"
x2="100%"
y2="100%"
>
<stop offset="0%" stopColor="#C850C0" />
<stop offset="100%" stopColor="#4158D0" />
</linearGradient>
</defs>
</svg>
<card.icon
size={36}
strokeWidth={1.5}
stroke="url(#icon-grad)"
/>
</span>
<p className="utm-intro__card-label">{card.label}</p>
</motion.li>
))}
</ul>
</section>
</div>
{/* ── 02 What is UTM ── */}
<section className="utm-what" ref={whatRef}>
<div className="inner-wrap">
<motion.div
initial={{ opacity: 0, y: 40 }}
animate={whatInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, ease }}
>
<div className="utm-what__top">
<motion.span
className="utm-what__eyebrow"
initial={{ opacity: 0, y: 16 }}
animate={whatInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
What is UTM
</motion.span>
<motion.h2
className="utm-what__title"
initial={{ opacity: 0, y: 20 }}
animate={whatInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.7, delay: 0.1, ease }}
>
UTM, <em>무엇을 있나요?</em>
</motion.h2>
<motion.p
className="utm-what__desc"
initial={{ opacity: 0, y: 16 }}
animate={whatInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.2, ease }}
>
UTM은 단순한 관제 시스템을 넘어, 도심 저고도 공역 전체를
디지털로 운영하는 통합 플랫폼입니다.
<br />
실시간 감시부터 충돌 위험 분석, 기상 연계, 공역 관리까지
비행의 모든 순간을 안전하게 연결합니다.
</motion.p>
</div>
<div className="utm-what__body">
<motion.ul
className="utm-what__cards"
initial={{ opacity: 0, x: -24 }}
animate={whatInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.7, delay: 0.3, ease }}
>
{UTM_WHAT_LEFT.map(({ icon: Icon, label }) => (
<li key={label} className="utm-what__card">
<span className="utm-what__card-icon">
<Icon size={16} />
</span>
<span className="utm-what__card-label">{label}</span>
</li>
))}
</motion.ul>
<motion.div
className="utm-what__mockup"
initial={{ opacity: 0, y: 24 }}
animate={whatInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, delay: 0.2, ease }}
>
<div
className="utm-what__img-wrap"
style={{
backgroundImage: `url(${basePath}images/what_utm_img2.png)`,
}}
>
<div className="utm-map-menu">
<button className="utm-map-menu__btn">
<Plane size={16} />
</button>
<button className="utm-map-menu__btn">
<ShieldAlert size={16} />
</button>
<button className="utm-map-menu__btn">
<Cloud size={16} />
</button>
<button className="utm-map-menu__btn">
<Layers size={16} />
</button>
<button className="utm-map-menu__btn">
<Radio size={16} />
</button>
<button className="utm-map-menu__btn">
<Database size={16} />
</button>
<button className="utm-map-menu__btn">
<Bell size={16} />
</button>
</div>
<div
className="drone drone--1"
style={{ top: "35%", left: "48%" }}
>
<div className="drone__badge drone__badge--red">
<div className="drone__badge-top">
<img
src={`${basePath}images/red_drone.png`}
alt="drone"
width="16"
height="16"
/>
<span className="drone__badge-conn">PA001</span>
</div>
<span className="drone__badge-id">COLL-ACR-001</span>
<span className="drone__badge-status">
13m/s / 100m
</span>
</div>
<div className="drone__ping drone__ping--red" />
<div className="drone__ping drone__ping--red drone__ping--delay" />
</div>
<div
className="drone drone--2"
style={{ top: "25%", left: "22%" }}
>
<div className="drone__badge drone__badge--blue">
<div className="drone__badge-top">
<img
src={`${basePath}images/blue_drone.png`}
alt="drone"
width="16"
height="16"
/>
<span className="drone__badge-conn">BLE</span>
</div>
<span className="drone__badge-id">DJI-TEST-0005</span>
<span className="drone__badge-status">
LTE · 9m/s / 100m
</span>
</div>
<div className="drone__ping drone__ping--blue" />
<div className="drone__ping drone__ping--blue drone__ping--delay" />
</div>
<div
className="drone drone--3"
style={{ top: "58%", left: "72%" }}
>
<div className="drone__badge drone__badge--orange">
<div className="drone__badge-top">
<img
src={`${basePath}images/orange_drone.png`}
alt="drone"
width="16"
height="16"
/>
<span className="drone__badge-conn">LTE</span>
</div>
<span className="drone__badge-id">FPA502</span>
<span className="drone__badge-status">
338m / 172k/m / 81°
<br />
126.745021/37.287339
</span>
</div>
<div className="drone__ping drone__ping--orange" />
<div className="drone__ping drone__ping--orange drone__ping--delay" />
</div>
</div>
</motion.div>
<motion.ul
className="utm-what__cards utm-what__cards--right"
initial={{ opacity: 0, x: 24 }}
animate={whatInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.7, delay: 0.3, ease }}
>
{UTM_WHAT_RIGHT.map(({ icon: Icon, label }) => (
<li
key={label}
className="utm-what__card utm-what__card--right"
>
<span className="utm-what__card-label">{label}</span>
<span className="utm-what__card-icon">
<Icon size={16} />
</span>
</li>
))}
</motion.ul>
</div>
</motion.div>
</div>
</section>
{/* ── 03 Evolution ── */}
<div className="inner-wrap">
<section className="utm-evo" ref={evoRef}>
<motion.div
className="utm-evo__left"
initial={{ opacity: 0, x: -32 }}
animate={evoInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.7, ease }}
>
<motion.span
className="fc-eyebrow"
initial={{ opacity: 0, y: 16 }}
animate={evoInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
Evolution
</motion.span>
<h2 className="utm-evo__title">
UTM에서 UATM으로,
<br />
<em>하늘길의 패러다임이 바뀝니다</em>
</h2>
<p className="utm-evo__desc">
드론 대의 비행에서 시작된 기술이, 이제 도심 전체의 하늘을
통합 관제하는 UATM으로 진화했습니다. PAL Networks는 변화의
최전선에서 기체와 관제를 함께 운영합니다.
</p>
</motion.div>
<div className="utm-evo__right">
<div className="utm-evo__track" ref={evoTrackRef}>
<div className="utm-evo__line-bg" />
<motion.div
className="utm-evo__line-fill"
style={{ height: lineHeight }}
/>
{EVO_ITEMS.map((item, i) => (
<EvoItem key={item.name} item={item} index={i} />
))}
</div>
</div>
</section>
</div>
{/* ── 04 Capabilities ── */}
<section className="utm-cap" ref={capRef}>
<div className="inner-wrap">
<div className="utm-cap__top">
<motion.span
className="fc-eyebrow"
initial={{ opacity: 0, y: 16 }}
animate={capInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
Capabilities
</motion.span>
<motion.h2
className="utm-cap__title"
initial={{ opacity: 0, y: 20 }}
animate={capInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.7, delay: 0.1, ease }}
>
PAL Networks의 <br />
<em> 가지 핵심 역량</em>
</motion.h2>
<motion.p
className="utm-cap__desc"
initial={{ opacity: 0, y: 16 }}
animate={capInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.2, ease }}
>
기체 운용부터 관제, 데이터까지 하나의 사업자가 모두 보유한
통합 역량입니다.
</motion.p>
</div>
<ul className="utm-cap__cards">
{SECTORS.map((s, i) => (
<motion.li
key={s.id}
className="utm-cap__card"
style={{ "--cap-color": s.color }}
initial={{ opacity: 0, y: 48 }}
animate={capInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.7, delay: 0.2 + i * 0.12, ease }}
>
<span className="utm-cap__card-num">{s.num}</span>
<div className="utm-cap__card-body">
<strong className="utm-cap__card-name">{s.name}</strong>
<ul className="utm-cap__card-tags">
{s.tags.map((tag) => (
<li key={tag}>{tag}</li>
))}
</ul>
</div>
</motion.li>
))}
</ul>
</div>
</section>
</div>
</article>
);
}
export default IntroPage;