Browse Source

feat : main section 변경

remotes/origin/main
이시연 2 weeks ago
parent
commit
261a5bb86e
  1. BIN
      public/images/main_utm_img.png
  2. 505
      src/components/main/MainUtm.jsx
  3. 268
      src/css/main.css

BIN
public/images/main_utm_img.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

505
src/components/main/MainUtm.jsx

@ -1,200 +1,359 @@
import { useEffect, useRef } from "react"; import { useRef, useEffect, useState } from "react";
import { gsap } from "gsap"; import { motion, useInView, AnimatePresence } from "framer-motion";
import { ScrollTrigger } from "gsap/ScrollTrigger"; import utmMapImg from "../../../public/images/main_utm_img.png";
const ease = [0.22, 1, 0.36, 1];
const FEATURES = [
{ num: "01", label: "비행 계획" },
{ num: "02", label: "비행 승인" },
{ num: "03", label: "실시간 관제" },
{ num: "04", label: "데이터 관리" },
];
const FLIGHT_PLANS = [
{ id: "FP-001", pilot: "홍길동", route: "서울 → 김포", status: "대기" },
{ id: "FP-002", pilot: "김철수", route: "인천 → 수원", status: "대기" },
{ id: "FP-003", pilot: "이영희", route: "김포 → 부천", status: "승인" },
];
const DETAIL = {
id: "FP-001",
pilot: "홍길동",
pilotId: "hong001",
route: "서울 → 김포",
altitude: "100m",
speed: "13m/s",
date: "2025-06-10 14:30",
};
const PHASES = ["list", "detail", "confirm", "done"];
const PHASE_DURATION = { list: 1800, detail: 2200, confirm: 1800, done: 2000 };
function UtmSystemPanel({ phase, activeRow }) {
return (
<div className="utm-panel">
<div className="utm-panel__header">
<span className="utm-panel__title">비행계획 승인관리</span>
<span className="utm-panel__badge">
검색결과 {FLIGHT_PLANS.length}
</span>
</div>
<div className="utm-panel__table">
<div className="utm-panel__thead">
<span>계획 ID</span>
<span>신청자</span>
<span>경로</span>
<span>상태</span>
<span></span>
</div>
{FLIGHT_PLANS.map((row, i) => (
<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__cell">
<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-title">비행계획 상세</div>
<div className="utm-panel__detail-rows">
<div className="utm-panel__detail-row">
<span>계획 ID</span>
<span>{DETAIL.id}</span>
</div>
<div className="utm-panel__detail-row">
<span>신청자</span>
<span>
{DETAIL.pilot} ({DETAIL.pilotId})
</span>
</div>
<div className="utm-panel__detail-row">
<span>경로</span>
<span>{DETAIL.route}</span>
</div>
<div className="utm-panel__detail-row">
<span>고도</span>
<span>
{DETAIL.altitude} · {DETAIL.speed}
</span>
</div>
<div className="utm-panel__detail-row">
<span>신청일시</span>
<span>{DETAIL.date}</span>
</div>
<div className="utm-panel__detail-row">
<span>상태</span>
<span
className={`utm-panel__status utm-panel__status--${
phase === "done" ? "done" : "wait"
}`}
>
{phase === "done" ? "승인" : "대기"}
</span>
</div>
</div>
gsap.registerPlugin(ScrollTrigger); {phase === "detail" && (
<div className="utm-panel__detail-actions">
<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 }}
>
<span className="utm-panel__toast-icon"></span>
FP-001 승인이 완료되었습니다.
</motion.div>
)}
</AnimatePresence>
</div>
{/* 컨펌: 패널 안에서 블러 오버레이 */}
<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 }}
>
<div className="utm-confirm__icon"></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>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
function MainUtm() { function MainUtm() {
const sectionRef = useRef(null); const ref = useRef(null);
const cardsRef = useRef([]); const inView = useInView(ref, { once: false, margin: "-80px" });
const [phase, setPhase] = useState("list");
const items = [ const [activeRow] = useState(0);
{ const cursorRef = useRef(null);
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(() => { if (!inView) return;
const cards = cardsRef.current.filter(Boolean); let t;
const run = (current) => {
gsap.set(cards, { const next = PHASES[(PHASES.indexOf(current) + 1) % PHASES.length];
yPercent: 110, t = setTimeout(() => {
scale: 1, setPhase(next);
opacity: 1, run(next);
rotateX: 0, }, PHASE_DURATION[current]);
transformOrigin: "center bottom", };
t = setTimeout(() => run("list"), 1000);
return () => clearTimeout(t);
}, [inView]);
//
useEffect(() => {
if (!inView) return;
const showcase = ref.current?.querySelector(".utm-hero__showcase");
const cursor = cursorRef.current;
if (!showcase || !cursor) return;
let timers = [];
function getPos(selector) {
const el = ref.current?.querySelector(selector);
if (!el) return null;
const sr = showcase.getBoundingClientRect();
const er = el.getBoundingClientRect();
return {
x: er.left - sr.left + er.width / 2,
y: er.top - sr.top + er.height / 2,
};
}
function moveTo(x, y) {
cursor.style.transform = `translate(${x}px, ${y}px)`;
}
function click() {
cursor.classList.add("utm-cursor--click");
setTimeout(() => cursor.classList.remove("utm-cursor--click"), 250);
}
function after(ms, fn) {
const t = setTimeout(fn, ms);
timers.push(t);
}
function loop() {
timers.forEach(clearTimeout);
timers = [];
setPhase("list");
after(1000, () => {
const p = getPos(".utm-panel__row--active .utm-panel__btn");
if (p) moveTo(p.x, p.y);
}); });
gsap.set(cards[0], { after(2000, () => {
yPercent: 0, click();
after(200, () => setPhase("detail"));
}); });
const tl = gsap.timeline({ after(3200, () => {
scrollTrigger: { const p = getPos(".utm-panel__approve-btn");
trigger: sectionRef.current, if (p) moveTo(p.x, p.y);
start: "top top",
end: `+=${items.length * 900}`,
scrub: 1,
pin: true,
anticipatePin: 1,
},
}); });
cards.forEach((card, index) => { after(4200, () => {
if (index === 0) return; click();
after(200, () => setPhase("confirm"));
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,
);
tl.to(
card,
{
yPercent: 0,
duration: 1,
ease: "none",
},
index - 0.85,
);
}); });
tl.to(cards[cards.length - 1], { after(5000, () => {
yPercent: -10, const p = getPos(".utm-confirm__ok");
scale: 0.94, if (p) moveTo(p.x, p.y);
duration: 0.8,
ease: "none",
}); });
}, sectionRef);
return () => ctx.revert();
}, [items.length]);
return (
<section className="main-utm-section" ref={sectionRef}>
<div className="main-utm-bg-glow main-utm-bg-glow--a" />
<div className="main-utm-bg-glow main-utm-bg-glow--b" />
<div className="utm-floating-air" aria-hidden="true">
<div className="utm-floating-drone utm-floating-drone--a">
<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 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 className="utm-floating-drone utm-floating-drone--c">
<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" /> after(5800, () => {
</svg> click();
after(200, () => setPhase("done"));
<span /> });
</div>
</div>
<div className="main-utm-inner"> after(7500, () => loop());
<div className="main-utm-head"> }
<p className="main-utm-eyebrow">UTM SYSTEM</p>
<h2 className="main-utm-title">드론교통관리 (UTM)</h2> moveTo(180, 160);
after(500, () => loop());
<p className="main-utm-desc">드론 하늘길을 통제하는 관제 시스템</p> return () => {
</div> timers.forEach(clearTimeout);
timers = [];
<div className="main-utm-stack"> cursor.classList.remove("utm-cursor--click");
{items.map((item, index) => ( setPhase("list"); // cleanup
<article };
className="main-utm-card" }, [inView]);
key={item.step} return (
ref={(el) => { <section className="utm-hero" ref={ref}>
cardsRef.current[index] = el; <div className="utm-hero__blob utm-hero__blob--1" />
}} <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 }}
> >
<div className="main-utm-card-text"> UTM / UATM PLATFORM
<span className="main-utm-step">{item.step}</span> </motion.span>
<p className="main-utm-label">{item.label}</p>
<h3>{item.title}</h3> <motion.h2
<p>{item.desc}</p> className="utm-hero__title"
</div> 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 }}
>
비행 계획부터 승인, 실시간 관제, 데이터 관리까지
<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 }}
>
{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>
))}
</motion.ul>
<div className="main-utm-card-visual"> <motion.div
<div className="main-utm-card-visual"> 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 <img
className={`main-utm-img main-utm-img${index + 1}`} src={utmMapImg}
src={`./images/main-utm-img${index + 1}.png`} alt="UTM 관제 지도"
alt="" className="utm-hero__map-img"
draggable="false"
/> />
</div> </div>
<UtmSystemPanel phase={phase} activeRow={activeRow} />
{/* 커서 */}
<div className="utm-cursor" ref={cursorRef}>
<div className="utm-cursor__dot" />
</div> </div>
</article> </motion.div>
))}
</div>
</div> </div>
</section> </section>
); );

268
src/css/main.css

@ -115,115 +115,191 @@ body{overflow-x:hidden;}
.main-scroll-text{font-size:10px;letter-spacing:.22em;} .main-scroll-text{font-size:10px;letter-spacing:.22em;}
} }
/* ─── utm ─── */ /* UTM */
.main-utm-section{position:relative;width:100%;min-height:100vh;padding-top:86px;overflow:hidden;background:radial-gradient(circle at 12% 18%,rgba(26,31,94,.08),transparent 32%),radial-gradient(circle at 88% 18%,rgba(26,31,94,.06),transparent 34%),linear-gradient(180deg,#f7f9ff 0%,#ffffff 100%);} .utm-hero { position: relative; overflow: hidden; background: linear-gradient(180deg, #f0f4ff 0%, #f7f9ff 60%, #ffffff 100%); min-height: 100vh; display: flex; align-items: center; padding: 80px 0; }
.main-utm-section::before{content:"";position:absolute;top:0;right:0;width:68%;height:58%;background:url("/images/main-utm-background.png") no-repeat center top/cover;opacity:.5;pointer-events:none;z-index:0;-webkit-mask-image:linear-gradient(180deg,#000 0%,#000 58%,rgba(0,0,0,.45) 78%,transparent 100%);mask-image:linear-gradient(180deg,#000 0%,#000 58%,rgba(0,0,0,.45) 78%,transparent 100%);}
.main-utm-section::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(247,249,255,.86) 0%,rgba(247,249,255,.42) 22%,rgba(247,249,255,.08) 52%,rgba(247,249,255,.42) 78%,rgba(247,249,255,.86) 100%);pointer-events:none;z-index:0;} .utm-hero__blob { position: absolute; border-radius: 50%; pointer-events: none; filter: blur(72px); }
.main-utm-bg-glow{position:absolute;border-radius:999px;filter:blur(90px);pointer-events:none;} .utm-hero__blob--1 { width: 480px; height: 480px; top: -120px; left: -80px; background: rgba(99,102,241,0.07); }
.main-utm-bg-glow--a{width:520px;height:520px;top:-160px;right:-120px;background:rgba(26,31,94,.13);}
.main-utm-bg-glow--b{width:460px;height:460px;left:-140px;bottom:-160px;background:rgba(112,180,255,.12);} .utm-hero__blob--2 { width: 360px; height: 360px; bottom: -80px; right: 10%; background: rgba(139,92,246,0.06); }
.main-utm-inner{position:relative;z-index:2;width:min(1660px,calc(100% - 120px));min-height:calc(100vh - 86px);margin:0 auto;padding-top:72px;padding-bottom:64px;} .utm-hero__inner { max-width: 1200px; margin: 0 auto; padding: 0 40px; display: flex; flex-direction: column; align-items: center; text-align: center; position: relative; z-index: 1; width: 100%; }
.main-utm-head{position:relative;z-index:5;max-width:820px;margin-bottom:64px;} .utm-hero__eyebrow { display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 2.5px; text-transform: uppercase; color: #6366F1; margin-bottom: 24px; }
.main-utm-eyebrow{margin:0 0 14px;font-size:12px;font-weight:800;letter-spacing:.24em;color:#1a1f5e;}
.main-utm-title{margin:0;font-size:clamp(40px,4vw,64px);font-weight:800;line-height:.92;letter-spacing:-0.08em;color:#0e1120;} .utm-hero__title { font-size: clamp(36px, 4.5vw, 64px); font-weight: 800; line-height: 1.15; letter-spacing: -0.03em; color: #0e1120; margin: 0 0 24px; word-break: keep-all; }
.main-utm-desc{margin:22px 0 0;font-size:16px;line-height:1.6;letter-spacing:-.02em;color:#697083;}
.utm-hero__title em { font-style: normal; color: #1A1F5E; -webkit-text-fill-color: #1A1F5E; }
.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(26,31,94,.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;} .utm-hero__desc { font-size: 16px; color: #666; line-height: 1.85; margin: 0 0 36px; word-break: keep-all; }
.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(26,31,94,.11),transparent 34%);pointer-events:none;}
.main-utm-card-text,.main-utm-card-visual{position:relative;z-index:2;} .utm-hero__chips { list-style: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 0 0 48px; padding: 0; }
.main-utm-step{display:inline-flex;align-items:center;justify-content:center;width:58px;height:32px;margin-bottom:24px;border-radius:999px;background:rgba(26,31,94,.1);color:#1a1f5e;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(26,31,94,.62);} .utm-hero__chip { display: flex; align-items: center; gap: 8px; background: rgba(255,255,255,0.75); border: 1px solid rgba(99,102,241,0.18); border-radius: 999px; padding: 7px 16px 7px 10px; backdrop-filter: blur(8px); }
.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;} .utm-hero__chip-num { font-size: 10px; font-weight: 700; color: #6366F1; letter-spacing: 0.5px; }
.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;} .utm-hero__chip-label { font-size: 13px; font-weight: 600; color: #1A1F5E; }
.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(26,31,94,.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);} .utm-hero__showcase {
.main-utm-orbit{position:absolute;inset:32px;border-radius:28px;} position: relative;
.main-utm-orbit span{position:absolute;border:1px solid rgba(26,31,94,.14);border-radius:50%;} width: 100%;
.main-utm-orbit span:nth-child(1){width:340px;height:340px;left:50%;top:50%;transform:translate(-50%,-50%);} border-radius: 20px;
.main-utm-orbit span:nth-child(2){width:230px;height:230px;left:18%;top:16%;} overflow: visible; /* ← 핵심: 패널이 밖으로 나올 수 있게 */
.main-utm-orbit span:nth-child(3){width:170px;height:170px;right:10%;bottom:10%;} box-shadow: 0 32px 80px rgba(26,31,94,0.16), 0 8px 24px rgba(26,31,94,0.08);
.main-utm-map{position:absolute;inset:0;}
.main-utm-route{position:absolute;height:2px;border-radius:999px;background:linear-gradient(90deg,transparent,#1a1f5e,transparent);box-shadow:0 0 16px rgba(26,31,94,.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);}
.utm-floating-air{position:absolute;top:120px;right:2%;width:42vw;height:210px;pointer-events:none;z-index:1;overflow:visible;opacity:.78;}
.utm-floating-drone{position:absolute;width:46px;height:46px;border-radius:18px;background:rgba(255,255,255,.52);border:1px solid rgba(26,31,94,.1);backdrop-filter:blur(12px);box-shadow:0 14px 30px rgba(31,41,55,.08);}
.utm-floating-drone::before{content:"";position:absolute;inset:-10px;border-radius:24px;border:1px solid rgba(91,108,255,.08);opacity:.6;animation:utmPulse 4s ease-out infinite;}
.utm-floating-drone svg{position:absolute;inset:10px;width:26px;height:26px;overflow:visible;}
.utm-floating-drone svg circle{fill:none;stroke:#1a1f5e;stroke-width:1.7;opacity:.72;}
.utm-floating-drone svg path{fill:none;stroke:#1a1f5e;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;opacity:.72;}
.utm-floating-drone span{display:none;}
.utm-floating-drone--a{left:10%;top:122px;animation:utmDroneMoveA 9s ease-in-out infinite;}
.utm-floating-drone--b{left:44%;top:72px;animation:utmDroneMoveB 11s ease-in-out infinite;animation-delay:-2s;}
.utm-floating-drone--c{right:4%;top:138px;animation:utmDroneMoveC 10s ease-in-out infinite;animation-delay:-4s;}
.utm-floating-drone--a svg circle,.utm-floating-drone--a svg path{stroke:#5B6CFF;}
.utm-floating-drone--b svg circle,.utm-floating-drone--b svg path{stroke:#8F1700;}
.utm-floating-drone--c svg circle,.utm-floating-drone--c svg path{stroke:#4F7DF3;}
.utm-floating-drone--a svg circle,.utm-floating-drone--a svg path{stroke:#5B6CFF;}
.utm-floating-drone--b svg circle,.utm-floating-drone--b svg path{stroke:#8F1700;}
.utm-floating-drone--c svg circle,.utm-floating-drone--c svg path{stroke:#4F7DF3;}
@keyframes utmDroneMoveA{
0%{transform:translate3d(0,0,0) rotate(-6deg);}
25%{transform:translate3d(12px,-10px,0) rotate(-2deg);}
50%{transform:translate3d(-6px,-18px,0) rotate(2deg);}
75%{transform:translate3d(-14px,-6px,0) rotate(-4deg);}
100%{transform:translate3d(0,0,0) rotate(-6deg);}
} }
@keyframes utmDroneMoveB{ /* 지도 full width */
0%{transform:translate3d(0,0,0) rotate(5deg);} .utm-hero__map-wrap {
25%{transform:translate3d(-10px,8px,0) rotate(1deg);} position: relative;
50%{transform:translate3d(14px,-12px,0) rotate(-3deg);} width: 100%;
75%{transform:translate3d(6px,10px,0) rotate(3deg);} border-radius: 20px;
100%{transform:translate3d(0,0,0) rotate(5deg);} overflow: hidden;
}
.utm-hero__map-img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.utm-panel {
position: absolute;
bottom: -32px;
left: -140px;
width: 340px;
background: #1a1d2e;
padding: 24px 20px;
display: flex;
flex-direction: column;
gap: 0;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 24px 60px rgba(0,0,0,0.45), 0 0 0 1px rgba(99,102,241,0.2);
z-index: 5;
}
.utm-panel__header { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.utm-panel__title { font-size: 14px; font-weight: 700; color: #fff; }
.utm-panel__badge { font-size: 11px; font-weight: 600; background: rgba(249,115,22,0.2); color: #f97316; border-radius: 4px; padding: 2px 8px; }
.utm-panel__table { display: flex; flex-direction: column; border: 1px solid rgba(255,255,255,0.07); border-radius: 10px; overflow: hidden; }
.utm-panel__thead { display: grid; grid-template-columns: 1.1fr 0.8fr 1.2fr 0.7fr 1fr; padding: 8px 12px; background: rgba(255,255,255,0.05); font-size: 11px; color: rgba(255,255,255,0.4); font-weight: 600; }
.utm-panel__row { display: grid; grid-template-columns: 1.1fr 0.8fr 1.2fr 0.7fr 1fr; padding: 10px 12px; border-top: 1px solid rgba(255,255,255,0.05); align-items: center; transition: background 0.2s; }
.utm-panel__row--active { background: rgba(99,102,241,0.1); }
.utm-panel__cell { font-size: 12px; color: rgba(255,255,255,0.75); }
.utm-panel__status { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 4px; display: inline-block; }
.utm-panel__status--wait { background: rgba(251,191,36,0.15); color: #fbbf24; }
.utm-panel__status--done { background: rgba(52,211,153,0.15); color: #34d399; }
.utm-panel__btn { font-size: 11px; margin-left: 8px; font-weight: 600; background: #6366F1; color: #fff; border-radius: 5px; padding: 4px 10px; display: inline-block; }
.utm-panel__btn--hover { background: #4f46e5; box-shadow: 0 0 0 3px rgba(99,102,241,0.3); }
/* 상세박스: 아래로 펼쳐지는 애니메이션 */
.utm-panel__detail {
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 10px;
padding: 0 16px;
max-height: 0;
overflow: hidden;
transition: max-height 0.5s cubic-bezier(0.22, 1, 0.36, 1),
padding 0.3s ease,
margin-top 0.3s ease;
margin-top: 0;
} }
@keyframes utmDroneMoveC{ .utm-panel__detail--show {
0%{transform:translate3d(0,0,0) rotate(-4deg);} max-height: 400px;
25%{transform:translate3d(8px,-14px,0) rotate(0deg);} padding: 14px 16px;
50%{transform:translate3d(-12px,-4px,0) rotate(4deg);} margin-top: 16px;
75%{transform:translate3d(10px,12px,0) rotate(-2deg);}
100%{transform:translate3d(0,0,0) rotate(-4deg);}
} }
@keyframes utmPulse{ .utm-panel__detail-title { font-size: 13px; font-weight: 700; color: #fff; margin-bottom: 2px; }
0%{transform:scale(.9);opacity:.4;}
70%{opacity:0;} .utm-panel__detail-rows { display: flex; flex-direction: column; gap: 6px; }
100%{transform:scale(1.35);opacity:0;}
.utm-panel__detail-row { display: flex; justify-content: space-between; align-items: center; font-size: 12px; }
.utm-panel__detail-row span:first-child { color: rgba(255,255,255,0.4); }
.utm-panel__detail-row span:last-child { color: rgba(255,255,255,0.85); font-weight: 500; }
.utm-panel__detail-actions { display: flex; justify-content: flex-end; margin-top: 14px; }
.utm-panel__approve-btn { font-size: 12px; font-weight: 700; background: #6366F1; color: #fff; border-radius: 6px; padding: 7px 16px; display: inline-block; }
.utm-panel__approve-btn--hover { background: #4f46e5; box-shadow: 0 0 0 3px rgba(99,102,241,0.35); }
.utm-panel__toast { display: flex; align-items: center; gap: 8px; background: rgba(52,211,153,0.15); border: 1px solid rgba(52,211,153,0.3); border-radius: 8px; padding: 10px 14px; font-size: 12px; font-weight: 600; color: #34d399; margin-top: 4px; }
.utm-panel__toast-icon { font-size: 14px; font-weight: 800; }
.utm-panel__confirm-overlay {
position: absolute;
inset: 0;
background: rgba(10, 10, 30, 0.6);
backdrop-filter: blur(6px);
display: flex;
align-items: center;
justify-content: center;
z-index: 20;
border-radius: 16px;
} }
.utm-confirm { background: #22253a; border: 1px solid rgba(255,255,255,0.12); border-radius: 16px; padding: 28px 24px; display: flex; flex-direction: column; align-items: center; gap: 12px; width: 260px; text-align: center; box-shadow: 0 32px 80px rgba(0,0,0,0.5), 0 0 0 1px rgba(99,102,241,0.2); gap: 14px;}
.utm-confirm__icon { font-size: 32px; }
.utm-confirm__title { font-size: 16px; font-weight: 700; color: #fff; line-height: 1.4; }
.utm-confirm__desc { font-size: 13px; color: rgba(255,255,255,0.5); }
/* ─── Responsive ─── */ .utm-confirm__btns { display: flex; gap: 10px; margin-top: 8px; width: 100%; }
@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;} .utm-confirm__cancel { flex: 1; text-align: center; font-size: 14px; font-weight: 600; padding: 11px; border-radius: 10px; background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.6); }
.main-utm-head{margin-bottom:56px;}
.main-utm-card{grid-template-columns:1fr;gap:28px;padding:34px;} .utm-confirm__ok { flex: 1; text-align: center; font-size: 14px; font-weight: 700; padding: 11px; border-radius: 10px; background: #6366F1; color: #fff; box-shadow: 0 4px 16px rgba(99,102,241,0.4); }
.main-utm-card-visual{min-height:240px;}
.utm-floating-air{width:58vw;top:160px;opacity:.72;} .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);
} }
@media(max-width:768px){.main-utm-inner{width:calc(100% - 32px);padding-top:42px;} .utm-cursor__dot {
.main-utm-title{font-size:38px;} width: 20px;
.main-utm-desc{font-size:14px;} height: 20px;
.main-utm-stack{height:64vh;} background: #fff;
.main-utm-card{padding:26px 22px;border-radius:24px;} border: 2px solid #6366F1;
.main-utm-card h3{font-size:32px;} border-radius: 50%;
.main-utm-card-text>p:last-child{font-size:14px;} box-shadow: 0 2px 10px rgba(99, 102, 241, 0.45);
.main-utm-card-visual{min-height:210px;} transition: transform 0.15s ease;
.utm-floating-air{display:none;}
} }
/* ─── Keyframes ─── */ .utm-cursor--click .utm-cursor__dot {
transform: scale(0.65);
}
@media (max-width: 1024px) { .utm-hero__showcase { grid-template-columns: 1fr; } .utm-hero__map-wrap { height: 280px; } }
@media (max-width: 768px) { .utm-hero { padding: 72px 0 60px; } .utm-hero__inner { padding: 0 20px; } .utm-hero__title { font-size: clamp(28px, 7vw, 42px); } .utm-hero__desc { font-size: 14px; } .utm-hero__chips { gap: 8px; margin-bottom: 36px; } .utm-hero__map-wrap { height: 220px; } .utm-panel { padding: 16px 14px; min-height: auto; } }
@keyframes utmWaveMove{0%{transform:translateX(-120%);}100%{transform:translateX(120%);}} @media (max-width: 480px) { .utm-hero__inner { padding: 0 16px; } .utm-hero__chip { padding: 6px 12px 6px 8px; } .utm-hero__chip-label { font-size: 12px; } }
/* ─── UTM ─── */ /* ─── UAM ─── */
.aurora-canvas {position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; } .aurora-canvas {position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; }

Loading…
Cancel
Save