Browse Source

feat : 비행관리시스템 추가

remotes/origin/main
이시연 3 weeks ago
parent
commit
4143cc36a3
  1. BIN
      public/images/fc_aircraft.png
  2. BIN
      public/images/fc_bottom_tab.png
  3. BIN
      public/images/fc_car.png
  4. BIN
      public/images/fc_computer.png
  5. BIN
      public/images/fc_drone.png
  6. BIN
      public/images/fc_left_tab.png
  7. BIN
      public/images/fc_right_tab.png
  8. BIN
      public/images/fc_tab_airplane.png
  9. BIN
      public/images/fc_tab_car.png
  10. BIN
      public/images/fc_tab_drone.png
  11. BIN
      public/images/fc_tab_vessel.png
  12. BIN
      public/images/fc_tablet.png
  13. BIN
      public/images/fc_tablet2.png
  14. BIN
      public/images/fc_vessel.png
  15. 83
      src/css/common.css
  16. 472
      src/pages/solution/FlightControlPage.jsx

BIN
public/images/fc_aircraft.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
public/images/fc_bottom_tab.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
public/images/fc_car.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
public/images/fc_computer.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 KiB

BIN
public/images/fc_drone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
public/images/fc_left_tab.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
public/images/fc_right_tab.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 KiB

BIN
public/images/fc_tab_airplane.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
public/images/fc_tab_car.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
public/images/fc_tab_drone.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

BIN
public/images/fc_tab_vessel.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
public/images/fc_tablet.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/images/fc_tablet2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
public/images/fc_vessel.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

83
src/css/common.css

@ -655,14 +655,17 @@ body{overflow-x:hidden;}
FlightControlPage common.css 추가분
*/
.fc-eyebrow {display:block;font-size:.72rem;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:var(--color-primary);margin-bottom:1.5rem;}
.fc-eyebrow--light {color:rgba(255,255,255,.45);}
.fc-eyebrow { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase; color: #8895c0; margin-bottom: 1.5rem; }
.fc-eyebrow::before { content: ''; display: block; width: 20px; height: 2px; background: linear-gradient(90deg, #e91e8c, #6b21a8); }
.fc-eyebrow--light { color: rgba(255,255,255,0.45); }
.fc-eyebrow--light::before { background: rgba(255,255,255,0.4); }
.fc-section-title {display:block;font-size:1.5rem;font-weight:700;color:var(--color-primary);margin-bottom:2.5rem;letter-spacing:-.01em;}
/*
1. 개요
*/
.fc-overview {position:relative;border-radius:1.25rem;overflow:hidden;margin:3rem 0 4rem;min-height:520px;display:flex;align-items:flex-end;}
.fc-overview { position: relative; border-radius: 1.25rem; overflow: hidden; margin: 3rem 0 6rem; min-height: 520px; display: flex; align-items: flex-end; }
.fc-overview__bg {position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;filter:brightness(.38) saturate(.6);}
.fc-overview__overlay {position:absolute;inset:0;background:linear-gradient(170deg, rgba(26,31,94,.6) 0%, rgba(26,31,94,.92) 60%);pointer-events:none;}
.fc-overview__content {position:relative;z-index:1;padding:3.5rem 3.5rem 4rem;width:100%;}
@ -678,9 +681,50 @@ body{overflow-x:hidden;}
.fc-badge--light:hover {background:rgba(255,255,255,.18);border-color:rgba(255,255,255,.3);color:#fff;}
/*
2. 수치 지표
2. intro
*/
.fc-stats {display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin-bottom:6rem;border:1px solid var(--color-primary-soft-border);border-radius:1rem;overflow:hidden;}
.fc-intro { display: flex; gap: 48px; align-items: center; margin: 0 0 8rem; }
.fc-intro__left { flex: 1; }
.fc-intro__title { font-size: 2rem; font-weight: 800; color: #1a1f3a; line-height: 1.35; letter-spacing: -0.02em; margin: 16px 0 28px; }
.fc-intro__desc { font-size: 0.9rem; color: #666; line-height: 1.9; margin-bottom: 48px; }
.fc-intro__icons { display: flex; gap: 32px; }
.fc-intro__icon-item { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.fc-intro__icon-item img { width: 80px; height: 80px; object-fit: contain; }
.fc-intro__icon-item span { font-size: 0.75rem; font-weight: 600; color: #888; }
.fc-intro__right { flex: 0 0 52%; }
.fc-intro__monitor { width: 100%; object-fit: contain; display: block; }
/*
3. 기능 하이라이트
*/
.fc-highlight { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; margin-bottom: 8rem; align-items: center; }
.fc-highlight__item { display: flex; gap: 24px; align-items: center; }
.fc-highlight__text { flex: 0 0 40%; }
.fc-highlight__title { font-size: 1.75rem; font-weight: 800; color: #1a1f3a; line-height: 1.35; letter-spacing: -0.02em; margin: 10px 0 16px; }
.fc-highlight__desc { font-size: 0.88rem; color: #666; line-height: 1.9; }
.fc-highlight__img-wrap { flex: 1; }
.fc-highlight__img-wrap img { width: 100%; object-fit: contain; display: block; }
.fc-highlight__img-scene { position: relative; width: 100%; }
.fc-highlight__tablet { width: 100%; object-fit: contain; display: block; }
.fc-highlight__float-icon { position: absolute; width: 72px; height: 72px; object-fit: contain; filter: drop-shadow(0 8px 16px rgba(0,0,0,0.15)); }
.fc-situation { position: relative; width: 100%; }
/* 메인 이미지 크기 */
.fc-situation__main { width: 100%; object-fit: contain; display: block; }
/* 왼쪽 탭 위치/크기 */
.fc-situation__left { position: absolute; left: -25%; top: 10%; width: 70% !important; object-fit: contain; }
/* 오른쪽 탭 위치/크기 */
.fc-situation__right { position: absolute; right: -20%; top: 20%; width: 70% !important; object-fit: contain; }
/* 아래 탭 위치/크기 */
.fc-situation__bottom { position: absolute; bottom: -40%; left: -5%; width: 60%; object-fit: contain; }
/*
4. 수치 지표
*/
.fc-stats { display: grid; grid-template-columns: repeat(4,1fr); gap: 0; margin-bottom: 8rem; border: 1px solid var(--color-primary-soft-border); border-radius: 1rem; overflow: hidden; }
.fc-stat-item {display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;padding:2.5rem 1.5rem;text-align:center;border-right:1px solid var(--color-primary-soft-border);transition:background .2s;}
.fc-stat-item:last-child {border-right:none;}
.fc-stat-item:hover {background:var(--color-primary-soft);}
@ -688,9 +732,9 @@ body{overflow-x:hidden;}
.fc-stat-item__label {font-size:.8rem;font-weight:500;color:#888;line-height:1.4;}
/*
3. 적용 분야
5. 적용 분야
*/
.fc-domains {margin-bottom:6rem;}
.fc-domains { margin-bottom: 8rem; }
.fc-domains__grid {display:grid;grid-template-columns:repeat(5,1fr);gap:1.25rem;}
.fc-domain-card {display:flex;flex-direction:column;gap:.75rem;padding:1.75rem 1.5rem;border:1px solid var(--color-primary-soft-border);border-radius:1rem;background:#fff;transition:border-color .2s,box-shadow .2s;cursor:default;}
.fc-domain-card:hover {border-color:var(--color-primary-border);box-shadow:0 8px 28px var(--color-primary-shadow);}
@ -700,9 +744,9 @@ body{overflow-x:hidden;}
.fc-domain-card__desc {font-size:.78rem;line-height:1.65;color:#888;margin:0;}
/*
4. 주요기능
6. 주요기능
*/
.fc-functions {margin-bottom:6rem;}
.fc-functions { margin-bottom: 8rem; }
.fc-functions__body {display:flex;gap:5rem;align-items:flex-start;}
.fc-func-list {flex:1;list-style:none;margin:0;padding:0;}
@ -723,9 +767,9 @@ body{overflow-x:hidden;}
.fc-func-display__label {font-size:.78rem;font-weight:600;color:#fff;}
/*
5. 시스템 구성
7. 시스템 구성
*/
.fc-flow {padding-bottom:8rem;}
.fc-flow { padding-bottom: 10rem; }
.fc-flow__row {display:flex;align-items:flex-start;gap:0;}
.fc-flow__item {position:relative;flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;gap:.6rem;padding:2rem 1rem;}
.fc-flow__step {font-size:.68rem;font-weight:800;letter-spacing:.12em;color:var(--color-primary-border-strong);}
@ -786,8 +830,14 @@ body{overflow-x:hidden;}
.fc-flow__arrow {
display: none;
}
.fc-highlight { grid-template-columns: 1fr; gap: 48px; }
.fc-highlight__item { flex-direction: column; }
.fc-highlight__img-wrap { width: 100%; }
}
@media (max-width: 640px) {
.fc-overview {
border-radius: .875rem;
@ -805,4 +855,15 @@ body{overflow-x:hidden;}
.fc-func-item__label {
font-size: .9rem;
}
.fc-intro { flex-direction: column; }
.fc-intro__right { width: 100%; }
.fc-intro__title { font-size: 1.5rem; }
.fc-highlight { gap: 32px; margin-bottom: 5rem; }
.fc-highlight__item { gap: 16px; }
.fc-highlight__title { font-size: 1.3rem; }
.fc-highlight__desc { font-size: 0.82rem; }
.fc-highlight__float-icon { width: 48px; height: 48px; }
.fc-highlight__img-scene { transform: scale(0.75); transform-origin: top center; }
}

472
src/pages/solution/FlightControlPage.jsx

@ -1,12 +1,24 @@
import { useRef, useState } from "react";
import { motion, useInView, AnimatePresence } from "framer-motion";
import { Radio, Puzzle, Network, Expand, Shield, ArrowUpRight, Package, Wind, AlertTriangle, Ship, Plane } from "lucide-react";
import {
Radio,
Puzzle,
Network,
Expand,
Shield,
ArrowUpRight,
Package,
Wind,
AlertTriangle,
Ship,
Plane,
} from "lucide-react";
import SubHero from "../../components/SubHero";
import useFadeIn from "../../hooks/useFadeIn";
const SOLUTION_NAV = [
{ label: "비행상황관리 시스템", to: "/solution/flight-control" },
{ label: "IBE", to: "/solution/ibe" },
{ label: "IBE (Internet Booking Engine)", to: "/solution/ibe" },
{ label: "스마트 관광 예약 플랫폼", to: "/solution/smart-tour" },
{ label: "KT G-cloud 인천총판", to: "/solution/kt-gcloud" },
];
@ -27,25 +39,57 @@ const STATS = [
];
const DOMAINS = [
{ icon: Package, label: "드론 물류·배송 관제", desc: "도심 및 광역 드론 배송 경로 최적화 및 실시간 위치 관제" },
{ icon: Plane, label: "UAM 도심항공 모빌리티", desc: "미래형 도심 항공 교통 인프라 연계 관제 시스템" },
{ icon: Wind, label: "무인기 환경측정", desc: "대기질·기상 데이터 수집 드론 운용 및 모니터링" },
{ icon: AlertTriangle, label: "안티드론 보안관제", desc: "비인가 드론 탐지·추적 및 무력화 대응 지원" },
{ icon: Ship, label: "선박·항공기 통합관제", desc: "해상·공중 복합 식별정보 수집 및 통합 상황관리" },
{
icon: Package,
label: "드론 물류·배송 관제",
desc: "도심 및 광역 드론 배송 경로 최적화 및 실시간 위치 관제",
},
{
icon: Plane,
label: "UAM 도심항공 모빌리티",
desc: "미래형 도심 항공 교통 인프라 연계 관제 시스템",
},
{
icon: Wind,
label: "무인기 환경측정",
desc: "대기질·기상 데이터 수집 드론 운용 및 모니터링",
},
{
icon: AlertTriangle,
label: "안티드론 보안관제",
desc: "비인가 드론 탐지·추적 및 무력화 대응 지원",
},
{
icon: Ship,
label: "선박·항공기 통합관제",
desc: "해상·공중 복합 식별정보 수집 및 통합 상황관리",
},
];
const FUNCTIONS = [
{ num: "01", img: "./images/s1-01.jpg", label: "비행가능 지역 및 공역표출" },
{ num: "02", img: "./images/s1-02.jpg", label: "비행체 위치 표출" },
{ num: "03", img: "./images/s1-03.jpg", label: "항공기 비행경로 및 히스토리 표출" },
{
num: "03",
img: "./images/s1-03.jpg",
label: "항공기 비행경로 및 히스토리 표출",
},
{ num: "04", img: "./images/s1-04.jpg", label: "항공기 비행정보 표출" },
{ num: "05", img: "./images/s1-05.jpg", label: "비행계획서 조회" },
{ num: "06", img: "./images/s1-06.jpg", label: "비정상 상황의 경보 표출" },
];
const FLOW = [
{ step: "01", label: "비행체 식별", desc: "항공기·드론·선박 실시간 신호 수집" },
{ step: "02", label: "데이터 수집", desc: "위치·속도·고도·식별코드 통합 처리" },
{
step: "01",
label: "비행체 식별",
desc: "항공기·드론·선박 실시간 신호 수집",
},
{
step: "02",
label: "데이터 수집",
desc: "위치·속도·고도·식별코드 통합 처리",
},
{ step: "03", label: "관제 서버", desc: "통합 관제 서버 분석 및 상황 판단" },
{ step: "04", label: "모니터링", desc: "관제사 화면 실시간 현황 표출" },
{ step: "05", label: "경보·대응", desc: "이상 상황 감지 즉시 경보 및 대응" },
@ -56,7 +100,12 @@ const ease = [0.22, 1, 0.36, 1];
function RevealText({ children, delay = 0, className }) {
return (
<div style={{ overflow: "hidden" }}>
<motion.div className={className} initial={{ y: "105%", opacity: 0 }} animate={{ y: "0%", opacity: 1 }} transition={{ duration: 0.75, ease: [0.22, 1, 0.36, 1], delay }}>
<motion.div
className={className}
initial={{ y: "105%", opacity: 0 }}
animate={{ y: "0%", opacity: 1 }}
transition={{ duration: 0.75, ease: [0.22, 1, 0.36, 1], delay }}
>
{children}
</motion.div>
</div>
@ -66,10 +115,22 @@ function RevealText({ children, delay = 0, className }) {
function StaggerWords({ text, delay = 0, className }) {
const words = text.split(" ");
return (
<span className={className} style={{ display: "flex", flexWrap: "wrap", gap: "0 .3em" }}>
<span
className={className}
style={{ display: "flex", flexWrap: "wrap", gap: "0 .3em" }}
>
{words.map((word, i) => (
<span key={i} style={{ overflow: "hidden", display: "inline-block" }}>
<motion.span style={{ display: "inline-block" }} initial={{ y: "110%", opacity: 0 }} animate={{ y: "0%", opacity: 1 }} transition={{ duration: 0.65, ease: [0.22, 1, 0.36, 1], delay: delay + i * 0.055 }}>
<motion.span
style={{ display: "inline-block" }}
initial={{ y: "110%", opacity: 0 }}
animate={{ y: "0%", opacity: 1 }}
transition={{
duration: 0.65,
ease: [0.22, 1, 0.36, 1],
delay: delay + i * 0.055,
}}
>
{word}
</motion.span>
</span>
@ -79,6 +140,8 @@ function StaggerWords({ text, delay = 0, className }) {
}
function FlightControlPage() {
const basePath = import.meta.env.BASE_URL;
const ref = useFadeIn();
const [activeIdx, setActiveIdx] = useState(0);
@ -87,8 +150,17 @@ function FlightControlPage() {
const domainsRef = useRef(null);
const funcRef = useRef(null);
const flowRef = useRef(null);
const overviewInView = useInView(overviewRef, { once: true, margin: "-60px" });
const introRef = useRef(null);
const introInView = useInView(introRef, { once: true, margin: "-60px" });
const highlightRef = useRef(null);
const highlightInView = useInView(highlightRef, {
once: true,
margin: "-60px",
});
const overviewInView = useInView(overviewRef, {
once: true,
margin: "-60px",
});
const statsInView = useInView(statsRef, { once: true, margin: "-60px" });
const domainsInView = useInView(domainsRef, { once: true, margin: "-60px" });
const funcInView = useInView(funcRef, { once: true, margin: "-80px" });
@ -96,30 +168,71 @@ function FlightControlPage() {
return (
<article ref={ref}>
<SubHero label="SOLUTION" title={<em>Flight Management</em>} navItems={SOLUTION_NAV} />
<SubHero
label="SOLUTION"
title={<em>Flight Management</em>}
navItems={SOLUTION_NAV}
/>
<div className="sub-content">
<div className="inner-wrap">
{/* 1. 개요 */}
<section className="fc-overview" ref={overviewRef}>
<motion.img src="./images/test111.png" alt="" className="fc-overview__bg" initial={{ scale: 1.08 }} animate={overviewInView ? { scale: 1 } : {}} transition={{ duration: 2.2, ease: [0.22, 1, 0.36, 1] }} />
<motion.img
src="./images/test111.png"
alt=""
className="fc-overview__bg"
initial={{ scale: 1.08 }}
animate={overviewInView ? { scale: 1 } : {}}
transition={{ duration: 2.2, ease: [0.22, 1, 0.36, 1] }}
/>
<div className="fc-overview__overlay" />
<div className="fc-overview__content">
{overviewInView && (
<RevealText delay={0.1} className="fc-eyebrow fc-eyebrow--light">
<RevealText
delay={0.1}
className="fc-eyebrow fc-eyebrow--light"
>
비행상황 관리 시스템
</RevealText>
)}
{overviewInView && (
<div className="fc-overview__title">
<StaggerWords text="항공기, 무인기, 선박 등 실시간 식별정보를 통하여 모니터링 및 상황관리를 할 수 있는 시스템" delay={0.18} className="fc-overview__title-inner" />
<StaggerWords
text="항공기, 무인기, 선박 등 실시간 식별정보를 통하여 모니터링 및 상황관리를 할 수 있는 시스템"
delay={0.18}
className="fc-overview__title-inner"
/>
</div>
)}
<motion.div className="fc-overview__bottom" initial={{ opacity: 0, filter: "blur(8px)", y: 12 }} animate={overviewInView ? { opacity: 1, filter: "blur(0px)", y: 0 } : {}} transition={{ duration: 0.85, ease, delay: 0.85 }}>
<p className="fc-overview__sub">각종 드론 상황관제, 환경측정, 미래산업인 드론/PAV/UAM을 활용한 사업들을 지원하기 위한 환경/물류/안티 드론 등의 상황관제 시스템을 연구하고 솔루션을 제공합니다.</p>
<motion.div
className="fc-overview__bottom"
initial={{ opacity: 0, filter: "blur(8px)", y: 12 }}
animate={
overviewInView
? { opacity: 1, filter: "blur(0px)", y: 0 }
: {}
}
transition={{ duration: 0.85, ease, delay: 0.85 }}
>
<p className="fc-overview__sub">
각종 드론 상황관제, 환경측정, 미래산업인 드론/PAV/UAM을 활용한
사업들을 지원하기 위한 환경/물류/안티 드론 등의 상황관제
시스템을 연구하고 솔루션을 제공합니다.
</p>
<div className="fc-badges">
{FEATURES.map(({ icon: Icon, label }, i) => (
<motion.span key={label} className="fc-badge fc-badge--light" initial={{ opacity: 0, y: 8 }} animate={overviewInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.4, ease, delay: 0.95 + i * 0.07 }}>
<motion.span
key={label}
className="fc-badge fc-badge--light"
initial={{ opacity: 0, y: 8 }}
animate={overviewInView ? { opacity: 1, y: 0 } : {}}
transition={{
duration: 0.4,
ease,
delay: 0.95 + i * 0.07,
}}
>
<Icon size={12} strokeWidth={2} />
{label}
</motion.span>
@ -129,10 +242,215 @@ function FlightControlPage() {
</div>
</section>
{/* 개요 인트로 */}
<section className="fc-intro" ref={introRef}>
<div className="fc-intro__left">
<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="fc-intro__title"
initial={{ opacity: 0, y: 24 }}
animate={introInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.7, ease, delay: 0.1 }}
>
모든 이동체 정보를
<br />
하나의 플랫폼에서
</motion.h2>
<motion.p
className="fc-intro__desc"
initial={{ opacity: 0, y: 16 }}
animate={introInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease, delay: 0.2 }}
>
항공기, 무인기, 선박, 지상 이동체의 실시간 정보를 통합
모니터링하고 <br />
상황을 인식하여 신속하고 안전한 운영 의사결정을 지원합니다.
</motion.p>
<motion.div
className="fc-intro__icons"
initial={{ opacity: 0, y: 16 }}
animate={introInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease, delay: 0.3 }}
>
{[
{ img: "fc_aircraft.png", label: "항공기" },
{ img: "fc_drone.png", label: "무인기" },
{ img: "fc_vessel.png", label: "선박" },
{ img: "fc_car.png", label: "차량 등" },
].map((item, i) => (
<div key={i} className="fc-intro__icon-item">
<img
src={`${basePath}images/${item.img}`}
alt={item.label}
/>
<span>{item.label}</span>
</div>
))}
</motion.div>
</div>
<motion.div
className="fc-intro__right"
initial={{ opacity: 0, x: 40 }}
animate={introInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.9, ease, delay: 0.15 }}
>
<img
src={`${basePath}images/fc_computer.png`}
alt="비행상황관리 시스템"
className="fc-intro__monitor"
/>
</motion.div>
</section>
{/* 기능 하이라이트 */}
<section className="fc-highlight" ref={highlightRef}>
{[
{
tag: "Real-Time Monitoring",
title: "실시간 이동체 정보 기반\n통합 모니터링",
desc: "전체에 분산된 이동체의 위치와 상태를 실시간으로 추적하여 안전하고 효율적인 운영을 지원합니다.",
img: "fc_tablet.png",
icons: [
{
img: "fc_tab_airplane.png",
x: "0%",
y: "8%",
delay: 0,
move: { x: [-40, -120, 0], y: [0, 90, 0] }, // ( )
},
{
img: "fc_tab_vessel.png",
x: "-30%",
y: "42%",
delay: 0,
move: { x: [0, 110, 0], y: [0, 60, 0] }, // ( )
},
{
img: "fc_tab_car.png",
x: "0%",
y: "64%",
delay: 0,
move: { x: [0, 120, 0], y: [0, -65, 0] }, // ( )
},
{
img: "fc_tab_drone.png",
x: "30%",
y: "40%",
delay: 0,
move: { x: [0, -100, 0], y: [0, -50, 0] }, // ( )
},
],
},
{
tag: "Situational Awareness",
title: "상황 인식 및\n의사결정 지원",
desc: "다양한 데이터와 고도화된 분석을 통해 상황을 예측하고 최적의 의사결정을 지원합니다.",
img: "fc_tablet2.png",
icons: null,
},
].map((item, i) => (
<motion.div
key={i}
className="fc-highlight__item"
initial={{ opacity: 0, y: 40 }}
animate={highlightInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.7, ease, delay: i * 0.15 }}
>
<div className="fc-highlight__text">
<span className="fc-eyebrow">{item.tag}</span>
<h3 className="fc-highlight__title">
{item.title.split("\n").map((line, j) => (
<span key={j}>
{line}
<br />
</span>
))}
</h3>
<p className="fc-highlight__desc">{item.desc}</p>
</div>
<div className="fc-highlight__img-wrap">
<div className="fc-highlight__img-scene">
<img
src={`${basePath}images/${item.img}`}
alt={item.tag}
className="fc-highlight__tablet"
/>
{item.icons &&
item.icons.map((icon, j) => (
<motion.img
key={j}
src={`${basePath}images/${icon.img}`}
alt=""
className="fc-highlight__float-icon"
style={{ left: icon.x, top: icon.y }}
initial={{ x: 0, y: 0 }}
animate={
highlightInView
? { x: icon.move.x[1], y: icon.move.y[1] }
: { x: 0, y: 0 }
}
transition={{
duration: 2.5,
repeat: 0,
delay: icon.delay,
ease: "easeOut",
}}
/>
))}
{i === 1 && (
<>
<motion.img
src={`${basePath}images/fc_left_tab.png`}
alt=""
className="fc-situation__left"
initial={{ opacity: 0, x: -30 }}
animate={highlightInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.8, ease, delay: 0.3 }}
/>
<motion.img
src={`${basePath}images/fc_right_tab.png`}
alt=""
className="fc-situation__right"
initial={{ opacity: 0, x: 30 }}
animate={highlightInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.8, ease, delay: 0.5 }}
/>
<motion.img
src={`${basePath}images/fc_bottom_tab.png`}
alt=""
className="fc-situation__bottom"
initial={{ opacity: 0, y: 30 }}
animate={highlightInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.8, ease, delay: 0.7 }}
/>
</>
)}
</div>
</div>
</motion.div>
))}
</section>
{/* 2. 수치 지표 */}
<section className="fc-stats" ref={statsRef}>
{STATS.map(({ value, label }, i) => (
<motion.div key={label} className="fc-stat-item" initial={{ opacity: 0, y: 24 }} animate={statsInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.55, ease, delay: 0.08 * i }}>
<motion.div
key={label}
className="fc-stat-item"
initial={{ opacity: 0, y: 24 }}
animate={statsInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.55, ease, delay: 0.08 * i }}
>
<span className="fc-stat-item__value">{value}</span>
<span className="fc-stat-item__label">{label}</span>
</motion.div>
@ -141,12 +459,24 @@ function FlightControlPage() {
{/* 3. 적용 분야 */}
<section className="fc-domains" ref={domainsRef}>
<motion.span className="fc-section-title" initial={{ opacity: 0, y: 20 }} animate={domainsInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, ease }}>
<motion.span
className="fc-section-title"
initial={{ opacity: 0, y: 20 }}
animate={domainsInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
적용 분야
</motion.span>
<div className="fc-domains__grid">
{DOMAINS.map(({ icon: Icon, label, desc }, i) => (
<motion.div key={label} className="fc-domain-card" initial={{ opacity: 0, y: 24 }} animate={domainsInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.55, ease, delay: 0.07 * i }} whileHover={{ y: -4, transition: { duration: 0.2 } }}>
<motion.div
key={label}
className="fc-domain-card"
initial={{ opacity: 0, y: 24 }}
animate={domainsInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.55, ease, delay: 0.07 * i }}
whileHover={{ y: -4, transition: { duration: 0.2 } }}
>
<div className="fc-domain-card__icon">
<Icon size={22} strokeWidth={1.5} />
</div>
@ -159,31 +489,79 @@ function FlightControlPage() {
{/* 4. 주요기능 */}
<section className="fc-functions" ref={funcRef}>
<motion.span className="fc-section-title" initial={{ opacity: 0, y: 20 }} animate={funcInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, ease }}>
<motion.span
className="fc-section-title"
initial={{ opacity: 0, y: 20 }}
animate={funcInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
주요기능
</motion.span>
<div className="fc-functions__body">
<ul className="fc-func-list">
{FUNCTIONS.map(({ num, label }, i) => (
<motion.li key={num} className={`fc-func-item${activeIdx === i ? " is-active" : ""}`} onMouseEnter={() => setActiveIdx(i)} initial={{ opacity: 0, y: 16 }} animate={funcInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.5, ease, delay: 0.05 * i }}>
<motion.li
key={num}
className={`fc-func-item${activeIdx === i ? " is-active" : ""}`}
onMouseEnter={() => setActiveIdx(i)}
initial={{ opacity: 0, y: 16 }}
animate={funcInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, ease, delay: 0.05 * i }}
>
<span className="fc-func-item__num">{num}</span>
<span className="fc-func-item__label">{label}</span>
<motion.span className="fc-func-item__arrow" initial={{ opacity: 0, x: -6 }} animate={activeIdx === i ? { opacity: 1, x: 0 } : { opacity: 0, x: -6 }} transition={{ duration: 0.2 }}>
<motion.span
className="fc-func-item__arrow"
initial={{ opacity: 0, x: -6 }}
animate={
activeIdx === i
? { opacity: 1, x: 0 }
: { opacity: 0, x: -6 }
}
transition={{ duration: 0.2 }}
>
<ArrowUpRight size={16} strokeWidth={1.5} />
</motion.span>
<motion.div className="fc-func-item__line" animate={{ scaleX: activeIdx === i ? 1 : 0 }} transition={{ duration: 0.35, ease }} />
<motion.div
className="fc-func-item__line"
animate={{ scaleX: activeIdx === i ? 1 : 0 }}
transition={{ duration: 0.35, ease }}
/>
</motion.li>
))}
</ul>
<div className="fc-func-display">
<AnimatePresence mode="wait">
<motion.div key={activeIdx} className="fc-func-display__inner" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.35, ease: "easeInOut" }}>
<img src={FUNCTIONS[activeIdx].img} alt={FUNCTIONS[activeIdx].label} className="fc-func-display__img" />
<motion.div
key={activeIdx}
className="fc-func-display__inner"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.35, ease: "easeInOut" }}
>
<img
src={FUNCTIONS[activeIdx].img}
alt={FUNCTIONS[activeIdx].label}
className="fc-func-display__img"
/>
<div className="fc-func-display__caption">
<motion.span key={`num-${activeIdx}`} className="fc-func-display__num" initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25 }}>
<motion.span
key={`num-${activeIdx}`}
className="fc-func-display__num"
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
>
{FUNCTIONS[activeIdx].num}
</motion.span>
<motion.span key={`label-${activeIdx}`} className="fc-func-display__label" initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25, delay: 0.05 }}>
<motion.span
key={`label-${activeIdx}`}
className="fc-func-display__label"
initial={{ opacity: 0, y: 6 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: 0.05 }}
>
{FUNCTIONS[activeIdx].label}
</motion.span>
</div>
@ -195,16 +573,38 @@ function FlightControlPage() {
{/* 5. 시스템 구성 흐름 */}
<section className="fc-flow" ref={flowRef}>
<motion.span className="fc-section-title" initial={{ opacity: 0, y: 20 }} animate={flowInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, ease }}>
<motion.span
className="fc-section-title"
initial={{ opacity: 0, y: 20 }}
animate={flowInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
시스템 구성
</motion.span>
<div className="fc-flow__row">
{FLOW.map(({ step, label, desc }, i) => (
<motion.div key={step} className="fc-flow__item" initial={{ opacity: 0, y: 20 }} animate={flowInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.5, ease, delay: 0.08 * i }}>
<motion.div
key={step}
className="fc-flow__item"
initial={{ opacity: 0, y: 20 }}
animate={flowInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, ease, delay: 0.08 * i }}
>
<span className="fc-flow__step">{step}</span>
<span className="fc-flow__label">{label}</span>
<p className="fc-flow__desc">{desc}</p>
{i < FLOW.length - 1 && <motion.div className="fc-flow__arrow" initial={{ opacity: 0, scaleX: 0 }} animate={flowInView ? { opacity: 1, scaleX: 1 } : {}} transition={{ duration: 0.4, ease, delay: 0.08 * i + 0.3 }} />}
{i < FLOW.length - 1 && (
<motion.div
className="fc-flow__arrow"
initial={{ opacity: 0, scaleX: 0 }}
animate={flowInView ? { opacity: 1, scaleX: 1 } : {}}
transition={{
duration: 0.4,
ease,
delay: 0.08 * i + 0.3,
}}
/>
)}
</motion.div>
))}
</div>

Loading…
Cancel
Save