Browse Source

feat : utm/uatm 3번째 섹션 완료

remotes/origin/main
이시연 2 weeks ago
parent
commit
be59569587
  1. BIN
      public/images/utm_intro_drone.png
  2. BIN
      public/images/utm_intro_uam.png
  3. BIN
      public/images/utm_intro_uatm.png
  4. BIN
      public/images/utm_intro_utm.png
  5. 38
      src/css/common.css
  6. 249
      src/pages/utm/IntroPage.jsx

BIN
public/images/utm_intro_drone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
public/images/utm_intro_uam.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
public/images/utm_intro_uatm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
public/images/utm_intro_utm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

38
src/css/common.css

@ -1197,6 +1197,7 @@ body{overflow-x:hidden;}
.utm-evo { display: flex; gap: 80px; align-items: flex-start; padding: 100px 48px; }
.utm-evo__left { flex: 0 0 420px; }
/* .utm-evo__left { flex: 0 0 420px; position: sticky; top: 120px; align-self: flex-start; } */
.utm-evo__eyebrow { font-size: 11px; font-weight: 500; letter-spacing: 2px; color: #E8193C; text-transform: uppercase; display: flex; align-items: center; gap: 8px; margin-bottom: 20px; }
.utm-evo__eyebrow::before { content: ''; width: 20px; height: 1.5px; background: #E8193C; }
.utm-evo__title { font-size: 36px; font-weight: 700; line-height: 1.35; color: #111; margin-bottom: 20px; word-break: keep-all; }
@ -1206,24 +1207,31 @@ body{overflow-x:hidden;}
.utm-evo__chip-dot { width: 7px; height: 7px; border-radius: 50%; background: #E8193C; flex-shrink: 0; }
.utm-evo__right { flex: 1; }
.utm-evo__track { display: flex; flex-direction: column; position: relative; }
.utm-evo__track::before { content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: #EBEBEB; transform: translateX(-50%); z-index: 0; }
.utm-evo__track { position: relative; }
.utm-evo__line-bg { position: absolute; left: 50%; transform: translateX(-50%); top: 0; bottom: 0; width: 1px; background: #e5e5e5; z-index: 0; }
.utm-evo__item { display: grid; grid-template-columns: 1fr 56px 1fr; align-items: center; padding: 20px 0; }
.utm-evo__item { display: grid; grid-template-columns: 1fr 56px 1fr; align-items: center; padding: 40px 0; }
.utm-evo__empty { flex: 1; }
.utm-evo__card-wrap--left { padding-right: 28px; }
.utm-evo__card-wrap--right { padding-left: 28px; }
.utm-evo__line-fill { position: absolute; left: 50%; transform: translateX(-50%); top: 0; width: 2px; background: linear-gradient(180deg, #C850C0, #4158D0); z-index: 1; }
.utm-evo__dot-wrap { display: flex; justify-content: center; align-items: center; z-index: 1; position: relative; }
.utm-evo__dot { width: 44px; height: 44px; border-radius: 50%; background: #F4F4F6; border: 3px solid #fff; box-shadow: 0 0 0 1.5px #E0E0E0; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; color: #bbb; }
.utm-evo__item--active .utm-evo__dot { background: #E8193C; box-shadow: 0 0 0 8px rgba(232,25,60,0.1); color: #fff; }
.utm-evo__card { background: #F8F8FC; border-radius: 14px; padding: 22px 20px; border: 1px solid #EBEBEB; }
.utm-evo__item--active .utm-evo__card { background: #fff; border-color: #E8193C; box-shadow: 0 6px 32px rgba(232,25,60,0.1); }
.utm-evo__card-name { font-size: 17px; font-weight: 700; color: #111; margin-bottom: 6px; }
.utm-evo__item--active .utm-evo__card-name { color: #E8193C; }
.utm-evo__card-desc { font-size: 12px; color: #999; line-height: 1.7; }
.utm-evo__item--active .utm-evo__card-desc { color: #555; }
.utm-evo__card-tag { display: inline-block; margin-top: 10px; font-size: 11px; font-weight: 500; color: #bbb; background: #EFEFEF; padding: 3px 10px; border-radius: 20px; }
.utm-evo__item--active .utm-evo__card-tag { background: rgba(232,25,60,0.08); color: #E8193C; }
.utm-evo__pal-flag { display: flex; align-items: center; gap: 6px; margin-top: 10px; font-size: 11px; font-weight: 600; color: #E8193C; }
.utm-evo__dot { width: 44px; height: 44px; border-radius: 50%; background: #fff; border: 1.5px solid #ddd; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; color: #999; position: relative; transition: border-color 0.5s ease, color 0.5s ease; }
.utm-evo__dot::before { content: ''; position: absolute; inset: -2px; border-radius: 50%; background: linear-gradient(135deg, #C850C0, #4158D0); opacity: 0; transition: opacity 0.5s ease; z-index: 0; }
.utm-evo__dot span { position: relative; z-index: 2; }
.utm-evo__card { display: flex; flex-direction: row; align-items: center; gap: 20px; border-radius: 12px; border: 1px solid #eee; padding: 20px; transition: border-color 0.4s ease, box-shadow 0.4s ease; }
.utm-evo__card-img { width: 120px; height: 120px; object-fit: contain; flex-shrink: 0; }
.utm-evo__card-body { display: flex; flex-direction: column; gap: 8px; }
.utm-evo__card-name { font-size: 15px; font-weight: 700; color: #111; }
.utm-evo__card-desc { font-size: 13px; color: #666; line-height: 1.6; }
.utm-evo__card-tag { display: inline-block; font-size: 11px; padding: 4px 10px; border-radius: 4px; background: #f5f5f5; color: #666; }
.utm-evo__item--highlight .utm-evo__card { border: 1.5px solid var(--navy); box-shadow: 0 4px 24px rgba(26,31,94,0.1); }
.utm-evo__item--highlight .utm-evo__card-name { background: none; -webkit-text-fill-color: var(--navy); color: var(--navy); }
.utm-evo__item--highlight .utm-evo__dot { border-color: transparent; color: #fff; }
.utm-evo__item--highlight .utm-evo__dot::before { opacity: 1; }
.utm-evo__item--highlight .utm-evo__card-tag { background: rgba(26,31,94,0.08); color: var(--navy); }

249
src/pages/utm/IntroPage.jsx

@ -1,8 +1,7 @@
import { useRef } from "react";
import SubHero from "../../components/SubHero";
import useFadeIn from "../../hooks/useFadeIn";
import { motion, useInView } from "framer-motion";
import { motion, useInView, useScroll, useTransform } from "framer-motion";
import {
MapPin,
ShieldAlert,
@ -24,19 +23,13 @@ const INTRO_CARDS = [
{ label: "데이터 관리", img: `${basePath}/images/utm_intro_icon4.png` },
];
const UTM_WHAT_LIST = [
"실시간 비행체 위치 감시 및 추적",
"AI 기반 충돌 위험 사전 분석·경보",
"기상청 연계 기상 정보 자동 반영",
"동적 공역 설정 및 지오펜싱 관리",
];
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: "불법 기체 실시간 감지" },
@ -44,16 +37,128 @@ const UTM_WHAT_RIGHT = [
{ icon: Layers, label: "UAM·드론 시스템 연동" },
];
const EVO_ITEMS = [
{
img: `${basePath}images/utm_intro_drone.png`,
alt: "DRONE",
name: "DRONE",
desc: "개별 드론 운용 단계. 단순 비행 임무 수행 중심으로 체계적 관리 체계 부재.",
tag: "단일 기체 운용",
side: "left",
active: false,
},
{
img: `${basePath}images/utm_intro_utm.png`,
alt: "UTM",
name: "UTM",
desc: "무인기 교통관리 시스템 도입. 비행 경로 승인·충돌 방지·공역 관리 체계화.",
tag: "공역 관리 시스템",
side: "right",
active: false,
},
{
img: `${basePath}images/utm_intro_uam.png`,
alt: "UAM",
name: "UAM",
desc: "도심항공모빌리티 등장. 유인 eVTOL 기체가 도심 저고도 공역을 비행.",
tag: "도심 항공 모빌리티",
side: "left",
active: false,
},
{
img: `${basePath}images/utm_intro_uatm.png`,
alt: "UATM",
name: "UATM",
desc: "UAM과 UTM의 완전한 통합. 기체 운용부터 관제까지 하나의 플랫폼으로 실현.",
tag: "통합 관제 플랫폼",
side: "right",
active: true,
},
];
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>{" "}
{/* 여기 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>
);
}
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: "-200px" });
const evoRef = useRef(null);
const evoInView = useInView(evoRef, { once: true, margin: "-100px" });
const evoTrackRef = useRef(null);
const { scrollYProgress } = useScroll({
target: evoTrackRef,
offset: ["start center", "end center"],
});
const lineHeight = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);
return (
<article ref={ref}>
@ -61,19 +166,15 @@ function IntroPage() {
label="UTM/UATM"
title={
<>
{/* <span style={{ color: "#111" }}> </span>
<br /> */}
<em>UTM/UATM</em>
</>
}
// desc=" PAL Networks UTM/UATM ."
navItems={UTM_NAV}
/>
<div className="sub-content">
<div className="inner-wrap">
<section className="utm-intro" ref={introRef}>
{/* ── 상단 텍스트 블록 ── */}
<div className="utm-intro__top">
<motion.span
className="fc-eyebrow"
@ -83,7 +184,6 @@ function IntroPage() {
>
Overview
</motion.span>
<motion.h2
className="utm-intro__title"
initial={{ opacity: 0, y: 20 }}
@ -94,7 +194,6 @@ function IntroPage() {
<br />
<em>도심 항공 관제의 새로운 기준</em>입니다.
</motion.h2>
<motion.p
className="utm-intro__desc"
initial={{ opacity: 0, y: 16 }}
@ -108,7 +207,6 @@ function IntroPage() {
</motion.p>
</div>
{/* ── 4개 카드 그리드 ── */}
<ul className="utm-intro__cards">
{INTRO_CARDS.map((card, i) => (
<motion.li
@ -141,7 +239,6 @@ function IntroPage() {
animate={whatInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, ease }}
>
{/* 상단 텍스트 */}
<div className="utm-what__top">
<motion.span
className="utm-what__eyebrow"
@ -151,7 +248,6 @@ function IntroPage() {
>
What is UTM
</motion.span>
<motion.h2
className="utm-what__title"
initial={{ opacity: 0, y: 20 }}
@ -160,7 +256,6 @@ function IntroPage() {
>
UTM, <em>무엇을 있나요?</em>
</motion.h2>
<motion.p
className="utm-what__desc"
initial={{ opacity: 0, y: 16 }}
@ -168,15 +263,13 @@ function IntroPage() {
transition={{ duration: 0.6, delay: 0.2, ease }}
>
UTM은 단순한 관제 시스템을 넘어, 도심 저고도 공역 전체를
디지털로 운영하는 통합 플랫폼입니다. 실시간 감시부터 충돌 위험
분석, 기상 연계, 공역 관리까지 비행의 모든 순간을 안전하게
연결합니다.
디지털로 운영하는 통합 플랫폼입니다. <br />
실시간 감시부터 충돌 위험 분석, 기상 연계, 공역 관리까지
비행의 모든 순간을 안전하게 연결합니다.
</motion.p>
</div>
{/* 3열 바디 */}
<div className="utm-what__body">
{/* 왼쪽 카드 */}
<motion.ul
className="utm-what__cards"
initial={{ opacity: 0, x: -24 }}
@ -193,7 +286,6 @@ function IntroPage() {
))}
</motion.ul>
{/* 가운데 목업 (기존 코드 유지) */}
<motion.div
className="utm-what__mockup"
initial={{ opacity: 0, y: 24 }}
@ -206,7 +298,6 @@ function IntroPage() {
backgroundImage: `url(${basePath}images/utm_what_img.png)`,
}}
>
{/* 빨간 드론 - 공역 빨간 구역 (중앙) */}
<div
className="drone drone--1"
style={{ top: "35%", left: "48%" }}
@ -230,7 +321,6 @@ function IntroPage() {
<div className="drone__ping drone__ping--red drone__ping--delay" />
</div>
{/* 파란 드론 - 왼쪽 검은 구역 주위 */}
<div
className="drone drone--2"
style={{ top: "25%", left: "22%" }}
@ -254,13 +344,11 @@ function IntroPage() {
<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">
<div className="drone__badge-top">
<img
src={`${basePath}images/orange_drone.png`}
@ -270,7 +358,6 @@ function IntroPage() {
/>
<span className="drone__badge-conn">LTE</span>
</div>
</div>
<span className="drone__badge-id">FPA502</span>
<span className="drone__badge-status">
338m / 172k/m / 81°
@ -284,7 +371,6 @@ function IntroPage() {
</div>
</motion.div>
{/* 오른쪽 카드 */}
<motion.ul
className="utm-what__cards utm-what__cards--right"
initial={{ opacity: 0, x: 24 }}
@ -307,15 +393,20 @@ function IntroPage() {
</motion.div>
</div>
</section>
<div className="inner-wrap">
{/* ── DRONE → UATM 진화 섹션 ── */}
<section className="utm-evo">
<div className="utm-evo__left">
{" "}
<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={introInView ? { opacity: 1, y: 0 } : {}}
animate={evoInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
Evolution
@ -330,86 +421,18 @@ function IntroPage() {
통합 관제하는 UATM으로 진화했습니다. PAL Networks는 변화의
최전선에서 기체와 관제를 함께 운영합니다.
</p>
</div>
</motion.div>
<div className="utm-evo__right">
<div className="utm-evo__track">
<div className="utm-evo__item">
<div className="utm-evo__card-wrap utm-evo__card-wrap--left">
<div className="utm-evo__card">
<div className="utm-evo__card-name">🚁 DRONE</div>
<div className="utm-evo__card-desc">
개별 드론 운용 단계. 단순 비행 임무 수행 중심으로 체계적
관리 체계 부재.
</div>
<span className="utm-evo__card-tag">단일 기체 운용</span>
</div>
</div>
<div className="utm-evo__dot-wrap">
<div className="utm-evo__dot">01</div>
</div>
<div className="utm-evo__empty" />
</div>
<div className="utm-evo__item">
<div className="utm-evo__empty" />
<div className="utm-evo__dot-wrap">
<div className="utm-evo__dot">02</div>
</div>
<div className="utm-evo__card-wrap utm-evo__card-wrap--right">
<div className="utm-evo__card">
<div className="utm-evo__card-name">📡 UTM</div>
<div className="utm-evo__card-desc">
무인기 교통관리 시스템 도입. 비행 경로 승인·충돌
방지·공역 관리 체계화.
</div>
<span className="utm-evo__card-tag">
공역 관리 시스템
</span>
</div>
</div>
</div>
<div className="utm-evo__item">
<div className="utm-evo__card-wrap utm-evo__card-wrap--left">
<div className="utm-evo__card">
<div className="utm-evo__card-name"> UAM</div>
<div className="utm-evo__card-desc">
도심항공모빌리티 등장. 유인 eVTOL 기체가 도심 저고도
공역을 비행.
</div>
<span className="utm-evo__card-tag">
도심 항공 모빌리티
</span>
</div>
</div>
<div className="utm-evo__dot-wrap">
<div className="utm-evo__dot">03</div>
</div>
<div className="utm-evo__empty" />
</div>
<div className="utm-evo__item utm-evo__item--active">
<div className="utm-evo__empty" />
<div className="utm-evo__dot-wrap">
<div className="utm-evo__dot">04</div>
</div>
<div className="utm-evo__card-wrap utm-evo__card-wrap--right">
<div className="utm-evo__card">
<div className="utm-evo__card-name">🛸 UATM</div>
<div className="utm-evo__card-desc">
UAM과 UTM의 완전한 통합. 기체 운용부터 관제까지 하나의
플랫폼으로 실현.
</div>
<span className="utm-evo__card-tag">
통합 관제 플랫폼
</span>
<div className="utm-evo__pal-flag">
PAL Networks가 실현합니다
</div>
</div>
</div>
</div>
<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>

Loading…
Cancel
Save