Browse Source

feat : utm 배경 변경

remotes/origin/main
이시연 2 weeks ago
parent
commit
26599786ae
  1. BIN
      public/images/what_utm_img2.png
  2. 116
      src/components/main/MainUtm.jsx
  3. 40
      src/css/common.css
  4. 4
      src/css/main.css
  5. 28
      src/pages/utm/IntroPage.jsx

BIN
public/images/what_utm_img2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

116
src/components/main/MainUtm.jsx

@ -1,6 +1,7 @@
import { useRef, useEffect, useState } from "react";
import { motion, useInView, AnimatePresence } from "framer-motion";
import utmMapImg from "../../../public/images/main_utm_img.png";
import { Link } from "react-router-dom";
const ease = [0.22, 1, 0.36, 1];
@ -35,7 +36,9 @@ function UtmSystemPanel({ phase, activeRow }) {
<div className="utm-panel">
<div className="utm-panel__header">
<span className="utm-panel__title">비행계획 승인관리</span>
<span className="utm-panel__badge">검색결과 {FLIGHT_PLANS.length}</span>
<span className="utm-panel__badge">
검색결과 {FLIGHT_PLANS.length}
</span>
</div>
<div className="utm-panel__table">
@ -47,20 +50,33 @@ function UtmSystemPanel({ phase, activeRow }) {
<span></span>
</div>
{FLIGHT_PLANS.map((row, i) => (
<div key={row.id} className={`utm-panel__row${activeRow === i ? " utm-panel__row--active" : ""}`}>
<div
key={row.id}
className={`utm-panel__row${activeRow === i ? " utm-panel__row--active" : ""}`}
>
<span className="utm-panel__cell">{row.id}</span>
<span className="utm-panel__cell">{row.pilot}</span>
<span className="utm-panel__cell">{row.route}</span>
<span className={`utm-panel__status utm-panel__status--${row.status === "승인" ? "done" : "wait"}`}>{row.status}</span>
<span
className={`utm-panel__status utm-panel__status--${row.status === "승인" ? "done" : "wait"}`}
>
{row.status}
</span>
<span className="utm-panel__cell">
<span className={`utm-panel__btn${activeRow === i && phase === "list" ? " utm-panel__btn--hover" : ""}`}>상세보기</span>
<span
className={`utm-panel__btn${activeRow === i && phase === "list" ? " utm-panel__btn--hover" : ""}`}
>
상세보기
</span>
</span>
</div>
))}
</div>
{/* 상세: 아래로 펼쳐짐 */}
<div className={`utm-panel__detail${phase === "detail" || phase === "confirm" || phase === "done" ? " utm-panel__detail--show" : ""}`}>
<div
className={`utm-panel__detail${phase === "detail" || phase === "confirm" || phase === "done" ? " utm-panel__detail--show" : ""}`}
>
<div className="utm-panel__detail-title">비행계획 상세</div>
<div className="utm-panel__detail-rows">
<div className="utm-panel__detail-row">
@ -89,19 +105,31 @@ function UtmSystemPanel({ phase, activeRow }) {
</div>
<div className="utm-panel__detail-row">
<span>상태</span>
<span className={`utm-panel__status utm-panel__status--${phase === "done" ? "done" : "wait"}`}>{phase === "done" ? "승인" : "대기"}</span>
<span
className={`utm-panel__status utm-panel__status--${phase === "done" ? "done" : "wait"}`}
>
{phase === "done" ? "승인" : "대기"}
</span>
</div>
</div>
{phase === "detail" && (
<div className="utm-panel__detail-actions">
<span className="utm-panel__approve-btn utm-panel__approve-btn--hover">승인처리</span>
<span className="utm-panel__approve-btn utm-panel__approve-btn--hover">
승인처리
</span>
</div>
)}
<AnimatePresence>
{phase === "done" && (
<motion.div className="utm-panel__toast" initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0 }} transition={{ duration: 0.35, ease }}>
<motion.div
className="utm-panel__toast"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.35, ease }}
>
<span className="utm-panel__toast-icon"></span>
FP-001 승인이 완료되었습니다.
</motion.div>
@ -112,11 +140,27 @@ function UtmSystemPanel({ phase, activeRow }) {
{/* 컨펌: 패널 안에서 블러 오버레이 */}
<AnimatePresence>
{phase === "confirm" && (
<motion.div className="utm-panel__confirm-overlay" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.25 }}>
<motion.div className="utm-confirm" initial={{ opacity: 0, scale: 0.92, y: 16 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.95 }} transition={{ duration: 0.35, ease }}>
<motion.div
className="utm-panel__confirm-overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.25 }}
>
<motion.div
className="utm-confirm"
initial={{ opacity: 0, scale: 0.92, y: 16 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.35, ease }}
>
<div className="utm-confirm__icon"></div>
<div className="utm-confirm__title">비행계획을 승인하시겠습니까?</div>
<div className="utm-confirm__desc">FP-001 · 홍길동 · 서울 김포</div>
<div className="utm-confirm__title">
비행계획을 승인하시겠습니까?
</div>
<div className="utm-confirm__desc">
FP-001 · 홍길동 · 서울 김포
</div>
<div className="utm-confirm__btns">
<span className="utm-confirm__cancel">취소</span>
<span className="utm-confirm__ok">확인</span>
@ -239,33 +283,69 @@ function MainUtm() {
<div className="utm-hero__blob utm-hero__blob--2" />
<div className="utm-hero__inner">
<motion.span className="utm-hero__eyebrow" initial={{ opacity: 0, y: 12 }} animate={inView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, ease }}>
<motion.span
className="utm-hero__eyebrow"
initial={{ opacity: 0, y: 12 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
UTM / UATM PLATFORM
</motion.span>
<motion.h2 className="utm-hero__title" initial={{ opacity: 0, y: 24 }} animate={inView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.7, delay: 0.08, ease }}>
<motion.h2
className="utm-hero__title"
initial={{ opacity: 0, y: 24 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.7, delay: 0.08, ease }}
>
드론 비행의 모든 과정을
<br />
<em>하나의 플랫폼에서</em>
</motion.h2>
<motion.p className="utm-hero__desc" initial={{ opacity: 0, y: 16 }} animate={inView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, delay: 0.16, ease }}>
<motion.p
className="utm-hero__desc"
initial={{ opacity: 0, y: 16 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.16, ease }}
>
비행 계획부터 승인, 실시간 관제, 데이터 관리까지
<br /> 안전하고 효율적인 하늘길을 만듭니다.
</motion.p>
<motion.ul className="utm-hero__chips" initial={{ opacity: 0, y: 16 }} animate={inView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, delay: 0.24, ease }}>
<motion.ul
className="utm-hero__chips"
initial={{ opacity: 0, y: 16 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, delay: 0.24, ease }}
>
{FEATURES.map((f) => (
<li key={f.num} className="utm-hero__chip">
<span className="utm-hero__chip-num">{f.num}</span>
<span className="utm-hero__chip-label">{f.label}</span>
</li>
))}
<li>
<Link to="/utm/intro" className="utm-hero__link">
자세히 보기 <span></span>
</Link>
</li>
</motion.ul>
<motion.div className="utm-hero__showcase" initial={{ opacity: 0, y: 48 }} animate={inView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.9, delay: 0.32, ease }}>
<motion.div
className="utm-hero__showcase"
initial={{ opacity: 0, y: 48 }}
animate={inView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.9, delay: 0.32, ease }}
>
<div className="utm-hero__map-wrap">
<img src={utmMapImg} alt="UTM 관제 지도" className="utm-hero__map-img" draggable="false" />
<img
src={utmMapImg}
alt="UTM 관제 지도"
className="utm-hero__map-img"
draggable="false"
/>
</div>
<UtmSystemPanel phase={phase} activeRow={activeRow} />

40
src/css/common.css

@ -1122,27 +1122,24 @@ 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 { background: linear-gradient(135deg, #0f1729 0%, #1a1040 40%, #0d1f3c 70%, #0f1729 100%); padding: 100px 0; }
.utm-what__top { text-align: center; margin-bottom: 64px; }
.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-what__title em { background: linear-gradient(90deg, #6366f1, #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-style: normal; }
.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: 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__eyebrow { display: inline-block; font-size: 12px; font-weight: 500; letter-spacing: 2px; text-transform: uppercase; color: #6366f1; 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: 25px; 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 { display: flex; align-items: center; justify-content: center; gap: 12px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); border-radius: 10px; padding: 0 16px; height: 80px; transition: background 0.2s, border-color 0.2s; white-space: nowrap; }
.utm-what__card:hover { background: #6366f1; border-color: #6366f1; }
.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-icon { width: 28px; height: 28px; border-radius: 6px; background: rgba(99,102,241,0.15); border: 0.5px solid rgba(99,102,241,0.3); display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: #a78bfa; }
.utm-what__card-label { font-size: 15px; font-weight: 500; color: rgba(255,255,255,0.7); 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__card:hover .utm-what__card-label { color: #fff; }
.utm-what__card:hover .utm-what__card-icon { background: rgba(255,255,255,0.2); border-color: rgba(255,255,255,0.3); color: #fff; }
.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; }
@ -1152,37 +1149,40 @@ body{overflow-x:hidden;}
.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; }
.utm-map-menu { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; gap: 6px; z-index: 10; }
.utm-map-menu__btn { width: 36px; height: 36px; background: rgba(15,18,32,0.75); backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; display: flex; align-items: center; justify-content: center; color: rgba(255,255,255,0.6); transition: background 0.2s, color 0.2s, border-color 0.2s; }
.utm-map-menu__btn--alert { color: #FF5656; border-color: rgba(255,86,86,0.3); background: rgba(255,86,86,0.15); }
.drone { position: absolute; display: flex; align-items: center; justify-content: center; }
.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 { display: flex; flex-direction: column; gap: 3px; padding: 8px 10px; border-radius: 8px; position: relative; z-index: 2; min-width: 140px; box-shadow: 0 4px 20px rgba(0,0,0,0.25); backdrop-filter: blur(8px); }
.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 { background: rgba(15,18,32,0.85); border: 1.5px 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--blue { background: rgba(15,18,32,0.85); border: 1.5px solid #6366f1; }
.drone__badge--blue::after { background: #6366f1; }
.drone__badge--orange { background: rgba(0,0,0,0.7); border: 2px solid #FF8800; }
.drone__badge--orange { background: rgba(15,18,32,0.85); border: 1.5px 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--blue .drone__badge-conn { color: #6366f1; }
.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--blue .drone__badge-id { color: #6366f1; }
.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__badge-status { font-size: 10px; color: rgba(255,255,255,0.55); 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); }

4
src/css/main.css

@ -169,7 +169,9 @@ body{overflow-x:hidden;}
.utm-cursor { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 100; transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1); }
.utm-cursor__dot { width: 20px; height: 20px; background: #fff; border: 2px solid #6366F1; border-radius: 50%; box-shadow: 0 2px 10px rgba(99,102,241,0.45); transition: transform 0.15s ease; }
.utm-cursor--click .utm-cursor__dot { transform: scale(0.65); }
.utm-hero__chips { list-style: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 0 0 16px; padding: 0; }
.utm-hero__link { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; color: #fff; background: #1a1f5e; padding: 7px 16px; border-radius: 999px; text-decoration: none; transition: gap 0.2s; }
.utm-hero__link:hover { gap: 12px; }
/* UTM 반응형 */
@media (max-width: 1280px) {
.utm-panel { left: -80px; width: 320px; }

28
src/pages/utm/IntroPage.jsx

@ -14,6 +14,8 @@ import {
Navigation,
CheckCircle,
Radio,
Plane,
Bell,
} from "lucide-react";
const ease = [0.22, 1, 0.36, 1];
@ -391,9 +393,33 @@ function IntroPage() {
<div
className="utm-what__img-wrap"
style={{
backgroundImage: `url(${basePath}images/utm_what_img.png)`,
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%" }}

Loading…
Cancel
Save