Browse Source

feat : what is utm section 변경

remotes/origin/main
이시연 2 weeks ago
parent
commit
da06e1c91c
  1. BIN
      public/images/blue_drone.png
  2. BIN
      public/images/orange_drone.png
  3. BIN
      public/images/red_drone.png
  4. BIN
      public/images/utm_what_img.png
  5. 79
      src/css/common.css
  6. 292
      src/pages/utm/IntroPage.jsx

BIN
public/images/blue_drone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

BIN
public/images/orange_drone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

BIN
public/images/red_drone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

BIN
public/images/utm_what_img.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

79
src/css/common.css

@ -1111,8 +1111,8 @@ body{overflow-x:hidden;}
.utm-intro { display: flex; flex-direction: column; gap: 52px; padding: 80px 0;}
.utm-intro__left { flex: 1; }
.utm-intro__top { max-width: 560px; }
.utm-intro__title { font-size: 38px; font-weight: 700; line-height: 1.4; color: #111; margin: 16px 0 18px; word-break: keep-all; }
.utm-intro__title em { color: var(--color-primary); font-style: normal; }
.utm-what__title { font-size: 40px; font-weight: 700; line-height: 1.35; color: #fff; margin-bottom: 20px; word-break: keep-all; }
.utm-what__title em { background: linear-gradient(90deg, #ff6b6b, #c084fc); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-style: normal; }
.utm-intro__desc { font-size: 14px; color: #555; line-height: 1.85; word-break: keep-all; }
.utm-intro__desc strong { font-weight: 600; color: #111; }
.utm-intro__cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; list-style: none; margin-left: auto; }
@ -1122,44 +1122,77 @@ body{overflow-x:hidden;}
.utm-intro__card-label { font-size: 18px; font-weight: 600; color: #222; line-height: 1.4; word-break: keep-all; }
.utm-what { background: linear-gradient(135deg, #1A0A2E 0%, #0D0D1A 50%, #1A0820 100%); padding: 100px 0; }
.utm-what__top { text-align: center; margin-bottom: 64px; }
.utm-what__inner { display: flex; align-items: center; justify-content: space-between; gap: 80px; min-height: 500px; }
.utm-what__left { flex: 0 0 480px; }
.utm-what__eyebrow { display: block; font-size: 12px; font-weight: 500; letter-spacing: 2px; text-transform: uppercase; color: #E8193C; margin-bottom: 20px; }
.utm-what__title { font-size: 40px; font-weight: 700; line-height: 1.35; color: #fff; margin-bottom: 24px; word-break: keep-all; }
.utm-what__desc { font-size: 14px; color: rgba(255,255,255,0.55); line-height: 1.9; margin-bottom: 36px; word-break: keep-all; }
.utm-what__eyebrow { display: inline-block; font-size: 12px; font-weight: 500; letter-spacing: 2px; text-transform: uppercase; color: rgba(255,255,255,0.4); margin-bottom: 20px; }
.utm-what__desc { font-size: 14px; color: rgba(255,255,255,0.5); line-height: 1.9; max-width: 600px; margin: 0 auto; word-break: keep-all; }
.utm-what__body { display: grid; grid-template-columns: 220px 1fr 220px; gap: 20px; align-items: center; }
.utm-what__cards { list-style: none; display: flex; flex-direction: column; justify-content: center; gap: 40px; min-width: 220px; }
.utm-what__card { display: flex; align-items: center; justify-content: center; gap: 12px; background: rgba(255,255,255,0.04); border: 0.5px solid rgba(255,255,255,0.1); border-radius: 10px; padding: 0 16px; height: 80px; transition: background 0.2s, border-color 0.2s, color 0.2s; white-space: nowrap; }
.utm-what__card:hover { background: #fff; border-color: #fff; }
.utm-what__card--right { flex-direction: row-reverse; }
.utm-what__card-icon { width: 28px; height: 28px; border-radius: 6px; background: rgba(232,25,60,0.15); border: 0.5px solid rgba(232,25,60,0.35); display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: #E8193C; }
.utm-what__card-label { font-size: 15px; font-weight: 500; color: rgba(255,255,255,0.75); line-height: 1; white-space: nowrap; text-align: center; }
.utm-what__card--right { flex-direction: row-reverse; }
.utm-what__card:hover { background: #fff; border-color: #fff; }
.utm-what__card:hover .utm-what__card-label { color: #E8193C; }
.utm-what__card:hover .utm-what__card-icon { color: #E8193C; border-color: rgba(232,25,60,0.2); background: rgba(232,25,60,0.08); }
.utm-what__card:hover .utm-what__card-icon { color: #111; border-color: rgba(0,0,0,0.15); background: rgba(0,0,0,0.06); }
.utm-what__mockup { flex: 1; display: flex; align-items: center; }
.utm-what__list { list-style: none; display: flex; flex-direction: column; gap: 14px; }
.utm-what__list li { display: flex; align-items: center; gap: 12px; font-size: 14px; color: rgba(255,255,255,0.75); font-weight: 500; }
.utm-what__list-dot { width: 6px; height: 6px; border-radius: 50%; background: #E8193C; flex-shrink: 0; }
.utm-what__right { flex: 1; background-size: contain; background-position: center right; background-repeat: no-repeat; min-height: 620px; }
.utm-what__img { width: 100%; max-width: 480px; object-fit: cover; }
.utm-what__img-wrap { position: relative; width: 100%; min-height: 620px; background-size: contain; background-position: center right; background-repeat: no-repeat; }
.utm-what__img-wrap { position: relative; width: 100%; min-height: 560px; background-size: contain; background-position: center; background-repeat: no-repeat; }
.utm-what__paths { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.drone { position: absolute; display: flex; align-items: center; justify-content: center; }
.drone--1 { top: 28%; left: 38%; animation: dronefloat1 6s ease-in-out infinite; }
.drone--2 { top: 55%; left: 60%; animation: dronefloat2 8s ease-in-out infinite; }
.drone--3 { top: 40%; left: 52%; animation: dronefloat3 7s ease-in-out infinite; }
.drone__bg { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; position: relative; z-index: 2; }
.drone__bg--red { background: rgba(232,25,60,0.25); border: 1.5px solid rgba(232,25,60,0.8); box-shadow: 0 0 16px rgba(232,25,60,0.6), 0 0 32px rgba(232,25,60,0.3); }
.drone__bg--blue { background: rgba(59,130,246,0.25); border: 1.5px solid rgba(59,130,246,0.8); box-shadow: 0 0 16px rgba(59,130,246,0.6), 0 0 32px rgba(59,130,246,0.3); }
.drone__bg--purple { background: rgba(167,139,250,0.25); border: 1.5px solid rgba(59,130,246,0.8); box-shadow: 0 0 16px rgba(167,139,250,0.6), 0 0 32px rgba(167,139,250,0.3); }
.drone__ping { position: absolute; width: 48px; height: 48px; border-radius: 50%; z-index: 1; animation: ping 2.5s ease-out infinite; }
.drone--1 { animation: dronefloat1 6s ease-in-out infinite; }
.drone--2 { animation: dronefloat2 8s ease-in-out infinite; }
.drone--3 { animation: dronefloat3 7s ease-in-out infinite; }
.drone__badge { display: flex; flex-direction: column; gap: 3px; padding: 8px 10px; border-radius: 4px; position: relative; z-index: 2; min-width: 140px; }
.drone__badge::after { content: ''; position: absolute; bottom: -6px; right: -6px; left: auto; width: 10px; height: 10px; border-radius: 50%; background: #FF8800; z-index: 3; }
.drone__badge--red { background: rgba(0,0,0,0.7); border: 2px solid #FF5656; }
.drone__badge--red::after { background: #FF5656; }
.drone__badge--blue { background: rgba(0,0,0,0.7); border: 2px solid #84D6FF; }
.drone__badge--blue::after { background: #84D6FF; }
.drone__badge--orange { background: rgba(0,0,0,0.7); border: 2px solid #FF8800; }
.drone__badge--orange::after { background: #FF8800; }
.drone__badge-top { display: flex; align-items: center; gap: 6px; }
.drone__badge--red .drone__badge-conn { color: #FF5656; }
.drone__badge--blue .drone__badge-conn { color: #84D6FF; }
.drone__badge--orange .drone__badge-conn { color: #FF8800; }
.drone__badge--red .drone__badge-id { color: #FF5656; }
.drone__badge--blue .drone__badge-id { color: #84D6FF; }
.drone__badge--orange .drone__badge-id { color: #FF8800; }
.drone__badge-conn { font-size: 9px; font-weight: 700; letter-spacing: 0.5px; }
.drone__badge-id { font-size: 12px; font-weight: 700; }
.drone__badge-info { display: flex; flex-direction: column; gap: 2px; }
.drone__badge-status { font-size: 10px; color: rgba(255,255,255,0.7); line-height: 1.5; }
.drone__ping { display: none; }
.drone__ping--red { border: 1.5px solid rgba(232,25,60,0.6); }
.drone__ping--blue { border: 1.5px solid rgba(59,130,246,0.6); }
.drone__ping--purple { border: 1.5px solid rgba(167,139,250,0.6); }
.drone__ping--orange { border: 1.5px solid rgba(249,115,22,0.6); }
.drone__ping--delay { animation-delay: 1s; }
.drone-alert { position: absolute; bottom: 120px; left: 198px; width: 200px; background: rgba(255,255,255,0.95); border-radius: 10px; padding: 12px 14px; backdrop-filter: blur(8px); display: flex; gap: 10px; align-items: flex-start; animation: alertAppear 0.5s ease forwards; opacity: 0; animation-delay: 1s; box-shadow: 0 0 20px rgba(232,25,60,0.2); }
.drone-alert__dot { width: 8px; height: 8px; border-radius: 50%; background: #E8193C; flex-shrink: 0; margin-top: 3px; animation: dotBlink 1s ease-in-out infinite; }
.drone-alert__title { font-size: 12px; font-weight: 600; color: #E8193C; margin-bottom: 4px; }
.drone-alert__desc { font-size: 11px; color: #444; line-height: 1.5; margin-bottom: 4px; }
.drone-alert__pos { font-size: 10px; color: #888; font-variant-numeric: tabular-nums; }
@keyframes alertAppear { from{opacity:0; transform:translateY(-8px);} to{opacity:1; transform:translateY(0);} }
@keyframes dotBlink { 0%,100%{opacity:1;} 50%{opacity:0.2;} }
@keyframes dronefloat1 { 0%,100%{transform:translate(0,0) rotate(0deg);} 33%{transform:translate(12px,-10px) rotate(3deg);} 66%{transform:translate(-8px,6px) rotate(-2deg);} }
@keyframes dronefloat2 { 0%,100%{transform:translate(0,0) rotate(0deg);} 33%{transform:translate(-10px,-14px) rotate(-3deg);} 66%{transform:translate(8px,8px) rotate(2deg);} }
@keyframes dronefloat3 { 0%,100%{transform:translate(0,0) rotate(0deg);} 50%{transform:translate(10px,-12px) rotate(4deg);} }
@keyframes dronefloat1 { 0%,100%{transform:translate(0,0);} 33%{transform:translate(12px,-10px);} 66%{transform:translate(-8px,6px);} }
@keyframes dronefloat2 { 0%,100%{transform:translate(0,0);} 33%{transform:translate(-10px,-14px);} 66%{transform:translate(8px,8px);} }
@keyframes dronefloat3 { 0%,100%{transform:translate(0,0);} 50%{transform:translate(10px,-12px);} }
@keyframes ping { 0%{transform:scale(1);opacity:0.8;} 100%{transform:scale(2.8);opacity:0;} }
.utm-evo { display: flex; gap: 80px; align-items: flex-start; padding: 100px 48px; }

292
src/pages/utm/IntroPage.jsx

@ -3,7 +3,16 @@ import SubHero from "../../components/SubHero";
import useFadeIn from "../../hooks/useFadeIn";
import { motion, useInView } from "framer-motion";
import { MapPin, ShieldAlert, Network, BadgeCheck } from "lucide-react";
import {
MapPin,
ShieldAlert,
Network,
BadgeCheck,
Cloud,
FileCheck,
Database,
Layers,
} from "lucide-react";
const ease = [0.22, 1, 0.36, 1];
const basePath = import.meta.env.BASE_URL;
@ -21,6 +30,20 @@ const UTM_WHAT_LIST = [
"기상청 연계 기상 정보 자동 반영",
"동적 공역 설정 및 지오펜싱 관리",
];
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·드론 시스템 연동" },
];
function IntroPage() {
const ref = useFadeIn();
const UTM_NAV = [
@ -91,9 +114,9 @@ function IntroPage() {
<motion.li
key={card.label}
className="utm-intro__card"
initial={{ opacity: 0, y: 24 }}
initial={{ opacity: 0, y: 60 }}
animate={introInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.15 + i * 0.08, ease }}
transition={{ duration: 0.8, delay: 0.2 + i * 0.12, ease }}
>
<span className="utm-intro__card-num">
{String(i + 1).padStart(2, "0")}
@ -113,58 +136,63 @@ function IntroPage() {
{/* ── UTM 소개 섹션 ── */}
<section className="utm-what" ref={whatRef}>
<div className="inner-wrap">
<div className="utm-what__inner">
<div className="utm-what__left">
<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>
{/* 상단 텍스트 */}
<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,
<br />
무엇을 있나요?
</motion.h2>
<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은 단순한 관제 시스템을 넘어, 도심 저고도 공역 전체를
디지털로 운영하는 통합 플랫폼입니다. 실시간 감시부터 충돌 위험
분석, 기상 연계, 공역 관리까지 비행의 모든 순간을 안전하게
연결합니다.
</motion.p>
<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은 단순한 관제 시스템을 넘어, 도심 저고도 공역 전체를
디지털로 운영하는 통합 플랫폼입니다. 실시간 감시부터 충돌 위험
분석, 기상 연계, 공역 관리까지 비행의 모든 순간을 안전하게
연결합니다.
</motion.p>
</div>
<motion.ul
className="utm-what__list"
initial={{ opacity: 0, y: 16 }}
animate={whatInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.3, ease }}
>
{UTM_WHAT_LIST.map((item) => (
<li key={item}>
<span className="utm-what__list-dot" />
{item}
</li>
))}
</motion.ul>
</div>
<motion.div
className="utm-what__right"
initial={{ opacity: 0, x: 32 }}
{/* 3열 바디 */}
<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
@ -173,108 +201,104 @@ function IntroPage() {
backgroundImage: `url(${basePath}images/utm_what_img.png)`,
}}
>
{/* 드론들 */}
<div className="drone drone--1">
<div className="drone__bg drone__bg--red">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M12 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"
fill="#fff"
{/* 빨간 드론 - 공역 빨간 구역 (중앙) */}
<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"
/>
<path
d="M4 4l4 4M20 4l-4 4M4 20l4-4M20 20l-4-4"
stroke="#fff"
strokeWidth="2"
strokeLinecap="round"
/>
<circle cx="4" cy="4" r="2" fill="#fff" />
<circle cx="20" cy="4" r="2" fill="#fff" />
<circle cx="4" cy="20" r="2" fill="#fff" />
<circle cx="20" cy="20" r="2" fill="#fff" />
</svg>
<span className="drone__badge-conn">BLE</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">
<div className="drone__bg drone__bg--blue">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M12 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"
fill="#fff"
/>
<path
d="M4 4l4 4M20 4l-4 4M4 20l4-4M20 20l-4-4"
stroke="#fff"
strokeWidth="2"
strokeLinecap="round"
/>
<circle cx="4" cy="4" r="2" fill="#fff" />
<circle cx="20" cy="4" r="2" fill="#fff" />
<circle cx="4" cy="20" r="2" fill="#fff" />
<circle cx="20" cy="20" r="2" fill="#fff" />
</svg>
</div>
<div className="drone__ping drone__ping--blue" />
<div className="drone__ping drone__ping--blue drone__ping--delay" />
</div>
<div className="drone drone--3">
<div className="drone__bg drone__bg--blue">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M12 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"
fill="#fff"
{/* 파란 드론 - 왼쪽 검은 구역 주위 */}
<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"
/>
<path
d="M4 4l4 4M20 4l-4 4M4 20l4-4M20 20l-4-4"
stroke="#fff"
strokeWidth="2"
strokeLinecap="round"
/>
<circle cx="4" cy="4" r="2" fill="#fff" />
<circle cx="20" cy="4" r="2" fill="#fff" />
<circle cx="4" cy="20" r="2" fill="#fff" />
<circle cx="20" cy="20" r="2" fill="#fff" />
</svg>
<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-alert">
<div className="drone-alert__dot" />
<div className="drone-alert__content">
<p className="drone-alert__title"> 불법 드론 감지</p>
<p className="drone-alert__desc">
식별 불가 기체 접근 <br /> GND-150m
</p>
<p className="drone-alert__pos">위치 37.5°N 126.9°E</p>
{/* 주황 드론 - 오른쪽 분홍 구역 근처 */}
<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`}
alt="drone"
width="16"
height="16"
/>
<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°
<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>
</div>
</section>
<div className="inner-wrap">
{/* ── DRONE → UATM 진화 섹션 ── */}
<section className="utm-evo">

Loading…
Cancel
Save