|
|
|
|
@ -1,13 +1,264 @@
|
|
|
|
|
import { useEffect, useRef, useState } from "react"; |
|
|
|
|
import SubHero from "../../components/SubHero"; |
|
|
|
|
import useFadeIn from "../../hooks/useFadeIn"; |
|
|
|
|
import { motion, AnimatePresence, useInView } from "framer-motion"; |
|
|
|
|
|
|
|
|
|
import { |
|
|
|
|
Shield, |
|
|
|
|
Radio, |
|
|
|
|
Cpu, |
|
|
|
|
Wind, |
|
|
|
|
Eye, |
|
|
|
|
BarChart2, |
|
|
|
|
Globe, |
|
|
|
|
Users, |
|
|
|
|
Layers, |
|
|
|
|
GraduationCap, |
|
|
|
|
Wrench, |
|
|
|
|
Building2, |
|
|
|
|
Navigation, |
|
|
|
|
MapPin, |
|
|
|
|
AlertTriangle, |
|
|
|
|
Plane, |
|
|
|
|
Network, |
|
|
|
|
Settings, |
|
|
|
|
} from "lucide-react"; |
|
|
|
|
const ease = [0.25, 0.1, 0.25, 1]; |
|
|
|
|
|
|
|
|
|
const PROJECTS = [ |
|
|
|
|
{ |
|
|
|
|
id: "01", |
|
|
|
|
title: "드론 규제 샌드박스", |
|
|
|
|
tags: ["드론", "규제/실증"], |
|
|
|
|
image: "/images/rnd_img1.png", |
|
|
|
|
desc: [ |
|
|
|
|
{ |
|
|
|
|
icon: <Shield size={14} />, |
|
|
|
|
title: "안전 관리 체계", |
|
|
|
|
text: "드론 이용 증가 대비 안전 관리 기반 마련", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Radio size={14} />, |
|
|
|
|
title: "식별 장치 구축", |
|
|
|
|
text: "드론 식별 장치 및 관리 체계 구축·실증", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Cpu size={14} />, |
|
|
|
|
title: "비행 관제 시스템", |
|
|
|
|
text: "드론 비행 관제 시스템 연구개발 수행", |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
id: "02", |
|
|
|
|
title: "환경 드론 관제 시스템 구축", |
|
|
|
|
tags: ["드론", "환경/대기"], |
|
|
|
|
image: "/images/rnd_img2.png", |
|
|
|
|
desc: [ |
|
|
|
|
{ |
|
|
|
|
icon: <Wind size={14} />, |
|
|
|
|
title: "대기오염 감시", |
|
|
|
|
text: "미세먼지·악취 등 대기환경 오염 감시 체계", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Eye size={14} />, |
|
|
|
|
title: "실시간 모니터링", |
|
|
|
|
text: "드론 탑재 센서로 실시간 오염 정보 수집", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <BarChart2 size={14} />, |
|
|
|
|
title: "정보 시스템 구축", |
|
|
|
|
text: "대기오염 모니터링 시스템 구축 및 실증", |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
id: "03", |
|
|
|
|
title: "메타버스 시스템 구축", |
|
|
|
|
tags: ["메타버스", "디지털전환"], |
|
|
|
|
image: "/images/rnd_img3.png", |
|
|
|
|
desc: [ |
|
|
|
|
{ |
|
|
|
|
icon: <Globe size={14} />, |
|
|
|
|
title: "디지털 전환", |
|
|
|
|
text: "현실을 디지털 기반 가상 세계로 확장", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Users size={14} />, |
|
|
|
|
title: "비대면 환경", |
|
|
|
|
text: "코로나 대비 비대면 활동 지원 시스템", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Layers size={14} />, |
|
|
|
|
title: "가상공간 구축", |
|
|
|
|
text: "가상공간 내 모든 활동 가능 시스템 실증", |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
id: "04", |
|
|
|
|
title: "항공인력양성팀 운영 시스템", |
|
|
|
|
tags: ["항공", "인력양성"], |
|
|
|
|
image: "/images/rnd_img4.png", |
|
|
|
|
desc: [ |
|
|
|
|
{ |
|
|
|
|
icon: <GraduationCap size={14} />, |
|
|
|
|
title: "교육 훈련 기반", |
|
|
|
|
text: "고교생 중심 항공정비 인력 교육 훈련 체계", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Wrench size={14} />, |
|
|
|
|
title: "현장실무 인력", |
|
|
|
|
text: "항공정비 현장실무 인력 안정적 공급 지원", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Building2 size={14} />, |
|
|
|
|
title: "민간 기반 조성", |
|
|
|
|
text: "항공기초인력양성 민간분야 기반 구축·실증", |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
id: "05", |
|
|
|
|
title: "경량항공기 내비게이션 및 비행상황관리 시스템", |
|
|
|
|
tags: ["경량항공기", "비행안전"], |
|
|
|
|
image: "/images/rnd_img5.png", |
|
|
|
|
desc: [ |
|
|
|
|
{ |
|
|
|
|
icon: <Navigation size={14} />, |
|
|
|
|
title: "전용 내비게이션", |
|
|
|
|
text: "경량항공기 전용 실시간 안전정보 내비게이션", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <MapPin size={14} />, |
|
|
|
|
title: "위치 모니터링", |
|
|
|
|
text: "경량항공기 위치 기반 모니터링 체계 구축", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <AlertTriangle size={14} />, |
|
|
|
|
title: "사고 예방", |
|
|
|
|
text: "항공레저 증가에 따른 비행안전 서비스 실증", |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
id: "06", |
|
|
|
|
title: "UTM 드론 교통관리체계", |
|
|
|
|
tags: ["드론", "교통관리"], |
|
|
|
|
image: "/images/rnd_img6.png", |
|
|
|
|
desc: [ |
|
|
|
|
{ |
|
|
|
|
icon: <Plane size={14} />, |
|
|
|
|
title: "저고도 공역 관리", |
|
|
|
|
text: "150m 이하 저고도 공역 드론 운항 체계 연구", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Network size={14} />, |
|
|
|
|
title: "교통관리 시스템", |
|
|
|
|
text: "드론 안전하고 효율적인 교통관리체계 개발", |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
icon: <Settings size={14} />, |
|
|
|
|
title: "운항 최적화", |
|
|
|
|
text: "무인비행장치 운항 안전·효율 극대화 실증", |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, |
|
|
|
|
]; |
|
|
|
|
const AUTO_DELAY = 5000; |
|
|
|
|
|
|
|
|
|
function RndPage() { |
|
|
|
|
const basePath = import.meta.env.BASE_URL; |
|
|
|
|
const ref = useFadeIn(); |
|
|
|
|
const sectionRef = useRef(null); |
|
|
|
|
const sliderRef = useRef(null); |
|
|
|
|
const cardRef = useRef(null); |
|
|
|
|
const timerRef = useRef(null); |
|
|
|
|
const dragStartX = useRef(null); |
|
|
|
|
const dragStartCurrent = useRef(null); |
|
|
|
|
const inView = useInView(sectionRef, { once: true, margin: "-100px" }); |
|
|
|
|
const [current, setCurrent] = useState(0); |
|
|
|
|
const total = PROJECTS.length; |
|
|
|
|
const [cardWidth, setCardWidth] = useState(0); |
|
|
|
|
|
|
|
|
|
const scrollToCard = (index) => { |
|
|
|
|
if (window.innerWidth <= 768 && sliderRef.current && cardRef.current) { |
|
|
|
|
const cardW = cardRef.current.offsetWidth; |
|
|
|
|
const gap = window.innerWidth <= 480 ? 14 : 14; |
|
|
|
|
sliderRef.current.scrollTo({ |
|
|
|
|
left: index * (cardW + gap), |
|
|
|
|
behavior: "smooth", |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const resetTimer = () => { |
|
|
|
|
if (timerRef.current) clearInterval(timerRef.current); |
|
|
|
|
timerRef.current = setInterval(() => { |
|
|
|
|
setCurrent((c) => { |
|
|
|
|
const nextIdx = (c + 1) % total; |
|
|
|
|
scrollToCard(nextIdx); |
|
|
|
|
return nextIdx; |
|
|
|
|
}); |
|
|
|
|
}, AUTO_DELAY); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const prev = () => { |
|
|
|
|
resetTimer(); |
|
|
|
|
setCurrent((c) => { |
|
|
|
|
const nextIdx = c <= 0 ? total - 1 : c - 1; |
|
|
|
|
scrollToCard(nextIdx); |
|
|
|
|
return nextIdx; |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const next = () => { |
|
|
|
|
resetTimer(); |
|
|
|
|
setCurrent((c) => { |
|
|
|
|
const nextIdx = c >= total - 1 ? 0 : c + 1; |
|
|
|
|
scrollToCard(nextIdx); |
|
|
|
|
return nextIdx; |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const handleDragStart = (e) => { |
|
|
|
|
dragStartX.current = |
|
|
|
|
e.type === "touchstart" ? e.touches[0].clientX : e.clientX; |
|
|
|
|
dragStartCurrent.current = current; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const handleDragEnd = (e) => { |
|
|
|
|
if (dragStartX.current === null) return; |
|
|
|
|
const endX = |
|
|
|
|
e.type === "touchend" ? e.changedTouches[0].clientX : e.clientX; |
|
|
|
|
const diff = dragStartX.current - endX; |
|
|
|
|
if (diff > 50) next(); |
|
|
|
|
else if (diff < -50) prev(); |
|
|
|
|
dragStartX.current = null; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
const update = () => { |
|
|
|
|
if (cardRef.current) { |
|
|
|
|
setCardWidth(cardRef.current.offsetWidth); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
update(); |
|
|
|
|
window.addEventListener("resize", update); |
|
|
|
|
return () => window.removeEventListener("resize", update); |
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
resetTimer(); |
|
|
|
|
return () => clearInterval(timerRef.current); |
|
|
|
|
}, [total]); |
|
|
|
|
|
|
|
|
|
const BUSINESS_NAV = [ |
|
|
|
|
{ label: "System Integration", to: "/business/si" }, |
|
|
|
|
{ label: "R&D", to: "/business/rnd" }, |
|
|
|
|
{ label: "운영 · 유지보수", to: "/business/maintenance" }, |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<article ref={ref}> |
|
|
|
|
<SubHero |
|
|
|
|
@ -23,29 +274,180 @@ function RndPage() {
|
|
|
|
|
navItems={BUSINESS_NAV} |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
<div className="sub-content"> |
|
|
|
|
<section className="sub-section"> |
|
|
|
|
<span className="sub-section-eyebrow sub-fade-in">UTM/UATM 소개</span> |
|
|
|
|
{/* <main className="sub-page"> |
|
|
|
|
<section className="sub-visual"> |
|
|
|
|
<div className="inner"> |
|
|
|
|
<h2>R&D</h2> |
|
|
|
|
<p>Research & Development</p> |
|
|
|
|
<div className="sub-content si-archive-wrap"> |
|
|
|
|
<div className="inner-wrap"> |
|
|
|
|
<section className="si_archive" ref={sectionRef}> |
|
|
|
|
<div className="si_archive__main"> |
|
|
|
|
{/* 헤더 */} |
|
|
|
|
<div className="si_archive__header"> |
|
|
|
|
<motion.span |
|
|
|
|
className="fc-eyebrow" |
|
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
|
transition={{ duration: 0.6, ease }} |
|
|
|
|
> |
|
|
|
|
PROJECT ARCHIVE |
|
|
|
|
</motion.span> |
|
|
|
|
<motion.h2 |
|
|
|
|
className="si_archive__title" |
|
|
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
|
transition={{ duration: 0.6, delay: 0.1, ease }} |
|
|
|
|
> |
|
|
|
|
{window.innerWidth <= 768 ? ( |
|
|
|
|
"연구개발 수행실적" |
|
|
|
|
) : ( |
|
|
|
|
<> |
|
|
|
|
연구개발 |
|
|
|
|
<br /> |
|
|
|
|
수행실적 |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</motion.h2> |
|
|
|
|
<motion.p |
|
|
|
|
className="si_archive__desc" |
|
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
|
transition={{ duration: 0.6, delay: 0.2, ease }} |
|
|
|
|
> |
|
|
|
|
{window.innerWidth <= 768 ? ( |
|
|
|
|
"미래 항공 모빌리티를 위한 연구개발 활동과 성과를 기록합니다." |
|
|
|
|
) : ( |
|
|
|
|
<> |
|
|
|
|
미래 항공 모빌리티를 위한 |
|
|
|
|
<br /> |
|
|
|
|
연구개발 활동과 성과를 기록합니다. |
|
|
|
|
</> |
|
|
|
|
)} |
|
|
|
|
</motion.p> |
|
|
|
|
|
|
|
|
|
{/* 네비게이션 */} |
|
|
|
|
<div className="si_archive__nav"> |
|
|
|
|
<motion.button |
|
|
|
|
className="si_archive__nav-btn" |
|
|
|
|
onClick={prev} |
|
|
|
|
aria-label="이전" |
|
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
|
transition={{ duration: 0.6, delay: 0.4, ease }} |
|
|
|
|
> |
|
|
|
|
← |
|
|
|
|
</motion.button> |
|
|
|
|
|
|
|
|
|
<motion.div |
|
|
|
|
className="si_archive__progress" |
|
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
|
transition={{ duration: 0.6, delay: 0.4, ease }} |
|
|
|
|
> |
|
|
|
|
<span className="si_archive__progress-cur"> |
|
|
|
|
{String(current + 1).padStart(2, "0")} |
|
|
|
|
</span> |
|
|
|
|
<span className="si_archive__progress-divider">/</span> |
|
|
|
|
<span className="si_archive__progress-total"> |
|
|
|
|
{String(total).padStart(2, "0")} |
|
|
|
|
</span> |
|
|
|
|
</motion.div> |
|
|
|
|
|
|
|
|
|
<motion.button |
|
|
|
|
className="si_archive__nav-btn" |
|
|
|
|
onClick={next} |
|
|
|
|
aria-label="다음" |
|
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
|
transition={{ duration: 0.6, delay: 0.4, ease }} |
|
|
|
|
> |
|
|
|
|
→ |
|
|
|
|
</motion.button> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</section> |
|
|
|
|
|
|
|
|
|
<section className="sub-content-section"> |
|
|
|
|
<div className="inner"> |
|
|
|
|
<h3>연구 개발 및 기술 고도화</h3> |
|
|
|
|
<p> |
|
|
|
|
항공·관제 도메인 특화 기술 연구로 |
|
|
|
|
<br /> |
|
|
|
|
차세대 솔루션을 만들어갑니다. |
|
|
|
|
</p> |
|
|
|
|
{/* 슬라이더 */} |
|
|
|
|
<div className="si_archive__right"> |
|
|
|
|
<motion.div |
|
|
|
|
initial={{ opacity: 0, y: 30 }} |
|
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
|
transition={{ duration: 0.6, delay: 0.3, ease }} |
|
|
|
|
> |
|
|
|
|
<div |
|
|
|
|
className="si_archive__slider" |
|
|
|
|
style={{ display: "flex" }} |
|
|
|
|
onMouseDown={handleDragStart} |
|
|
|
|
onMouseUp={handleDragEnd} |
|
|
|
|
onTouchStart={handleDragStart} |
|
|
|
|
onTouchEnd={handleDragEnd} |
|
|
|
|
ref={sliderRef} |
|
|
|
|
> |
|
|
|
|
<motion.div |
|
|
|
|
className="si_archive__track" |
|
|
|
|
animate={{ |
|
|
|
|
x: |
|
|
|
|
window.innerWidth <= 768 |
|
|
|
|
? 0 |
|
|
|
|
: cardWidth |
|
|
|
|
? -(current * (cardWidth + 18)) |
|
|
|
|
: 0, |
|
|
|
|
}} |
|
|
|
|
transition={{ duration: 0.55, ease }} |
|
|
|
|
style={{ alignItems: "stretch" }} |
|
|
|
|
> |
|
|
|
|
{PROJECTS.map((project, idx) => ( |
|
|
|
|
<div |
|
|
|
|
key={project.id} |
|
|
|
|
className="si_archive__card" |
|
|
|
|
ref={idx === 0 ? cardRef : null} |
|
|
|
|
> |
|
|
|
|
<div className="si_archive__card-img"> |
|
|
|
|
<img |
|
|
|
|
src={`${basePath}images/rnd_img${parseInt(project.id)}.png`} |
|
|
|
|
alt={project.title} |
|
|
|
|
/> |
|
|
|
|
<div className="si_archive__card-img-placeholder" /> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<div className="si_archive__card-body"> |
|
|
|
|
<div className="si_archive__card-header"> |
|
|
|
|
<div className="si_archive__card-num"> |
|
|
|
|
{project.id} |
|
|
|
|
</div> |
|
|
|
|
<h3 className="si_archive__card-title"> |
|
|
|
|
{project.title} |
|
|
|
|
</h3> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<div className="si_archive__card-tags"> |
|
|
|
|
{project.tags.map((tag) => ( |
|
|
|
|
<span key={tag} className="si_archive__tag"> |
|
|
|
|
{tag} |
|
|
|
|
</span> |
|
|
|
|
))} |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
<ul className="si_archive__card-desc"> |
|
|
|
|
{project.desc.map((item, i) => ( |
|
|
|
|
<li key={i}> |
|
|
|
|
<div className="si_archive__card-desc-icon"> |
|
|
|
|
{item.icon} |
|
|
|
|
</div> |
|
|
|
|
<div className="si_archive__card-desc-title"> |
|
|
|
|
{item.title} |
|
|
|
|
</div> |
|
|
|
|
<div className="si_archive__card-desc-text"> |
|
|
|
|
{item.text} |
|
|
|
|
</div> |
|
|
|
|
</li> |
|
|
|
|
))} |
|
|
|
|
</ul> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
))} |
|
|
|
|
</motion.div> |
|
|
|
|
</div> |
|
|
|
|
</motion.div> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</section> |
|
|
|
|
</main> */} |
|
|
|
|
</section> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</article> |
|
|
|
|
); |
|
|
|
|
|