|
|
|
@ -2,7 +2,24 @@ import { useRef, useState, useEffect } from "react"; |
|
|
|
import { motion, AnimatePresence, useInView } from "framer-motion"; |
|
|
|
import { motion, AnimatePresence, useInView } from "framer-motion"; |
|
|
|
import SubHero from "../../components/SubHero"; |
|
|
|
import SubHero from "../../components/SubHero"; |
|
|
|
import useFadeIn from "../../hooks/useFadeIn"; |
|
|
|
import useFadeIn from "../../hooks/useFadeIn"; |
|
|
|
|
|
|
|
import { |
|
|
|
|
|
|
|
Plane, |
|
|
|
|
|
|
|
Globe, |
|
|
|
|
|
|
|
UtensilsCrossed, |
|
|
|
|
|
|
|
Thermometer, |
|
|
|
|
|
|
|
MapPin, |
|
|
|
|
|
|
|
Link, |
|
|
|
|
|
|
|
QrCode, |
|
|
|
|
|
|
|
Users, |
|
|
|
|
|
|
|
Siren, |
|
|
|
|
|
|
|
Wrench, |
|
|
|
|
|
|
|
PlusCircle, |
|
|
|
|
|
|
|
Settings, |
|
|
|
|
|
|
|
ShieldCheck, |
|
|
|
|
|
|
|
Monitor, |
|
|
|
|
|
|
|
Building2, |
|
|
|
|
|
|
|
ClipboardList, |
|
|
|
|
|
|
|
} from "lucide-react"; |
|
|
|
const ease = [0.25, 0.1, 0.25, 1]; |
|
|
|
const ease = [0.25, 0.1, 0.25, 1]; |
|
|
|
|
|
|
|
|
|
|
|
const PROJECTS = [ |
|
|
|
const PROJECTS = [ |
|
|
|
@ -13,17 +30,17 @@ const PROJECTS = [ |
|
|
|
image: "/images/si_img1.png", |
|
|
|
image: "/images/si_img1.png", |
|
|
|
desc: [ |
|
|
|
desc: [ |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "✈️", |
|
|
|
icon: <Plane size={14} />, |
|
|
|
title: "통합 여행 포털", |
|
|
|
title: "통합 여행 포털", |
|
|
|
text: "렌터카·숙박·항공 통합 서비스 구축", |
|
|
|
text: "렌터카·숙박·항공 통합 서비스 구축", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🌏", |
|
|
|
icon: <Globe size={14} />, |
|
|
|
title: "해외시장 진출", |
|
|
|
title: "해외시장 진출", |
|
|
|
text: "국제선 항공으로 해외시장 초석 마련", |
|
|
|
text: "국제선 항공으로 해외시장 초석 마련", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🍽️", |
|
|
|
icon: <UtensilsCrossed size={14} />, |
|
|
|
title: "종합 서비스", |
|
|
|
title: "종합 서비스", |
|
|
|
text: "맛집·카페로 이어지는 제주패스 구현", |
|
|
|
text: "맛집·카페로 이어지는 제주패스 구현", |
|
|
|
}, |
|
|
|
}, |
|
|
|
@ -36,17 +53,17 @@ const PROJECTS = [ |
|
|
|
image: "/images/si_img2.png", |
|
|
|
image: "/images/si_img2.png", |
|
|
|
desc: [ |
|
|
|
desc: [ |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🌡️", |
|
|
|
icon: <Thermometer size={14} />, |
|
|
|
title: "건강 상태 관리", |
|
|
|
title: "건강 상태 관리", |
|
|
|
text: "체류 기간 중 건강 상태 체크 및 관리", |
|
|
|
text: "체류 기간 중 건강 상태 체크 및 관리", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "📍", |
|
|
|
icon: <MapPin size={14} />, |
|
|
|
title: "동선 파악", |
|
|
|
title: "동선 파악", |
|
|
|
text: "이동 동선 파악 및 정보 관리 시스템", |
|
|
|
text: "이동 동선 파악 및 정보 관리 시스템", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🔗", |
|
|
|
icon: <Link size={14} />, |
|
|
|
title: "시스템 연계", |
|
|
|
title: "시스템 연계", |
|
|
|
text: "중국전담여행사 전자관리시스템 연계", |
|
|
|
text: "중국전담여행사 전자관리시스템 연계", |
|
|
|
}, |
|
|
|
}, |
|
|
|
@ -59,17 +76,17 @@ const PROJECTS = [ |
|
|
|
image: "/images/si_img3.png", |
|
|
|
image: "/images/si_img3.png", |
|
|
|
desc: [ |
|
|
|
desc: [ |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "📱", |
|
|
|
icon: <QrCode size={14} />, |
|
|
|
title: "QR 방역 관리", |
|
|
|
title: "QR 방역 관리", |
|
|
|
text: "QR코드 활용 방문자 방역 관리 구축", |
|
|
|
text: "QR코드 활용 방문자 방역 관리 구축", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "👥", |
|
|
|
icon: <Users size={14} />, |
|
|
|
title: "방문자 관리", |
|
|
|
title: "방문자 관리", |
|
|
|
text: "체계적 방문자 관리 및 출입 정보 제공", |
|
|
|
text: "체계적 방문자 관리 및 출입 정보 제공", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🚨", |
|
|
|
icon: <Siren size={14} />, |
|
|
|
title: "신속 대응", |
|
|
|
title: "신속 대응", |
|
|
|
text: "확진자 발생 시 신속한 방역 대응 지원", |
|
|
|
text: "확진자 발생 시 신속한 방역 대응 지원", |
|
|
|
}, |
|
|
|
}, |
|
|
|
@ -82,17 +99,17 @@ const PROJECTS = [ |
|
|
|
image: "/images/si_img4.png", |
|
|
|
image: "/images/si_img4.png", |
|
|
|
desc: [ |
|
|
|
desc: [ |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🔧", |
|
|
|
icon: <Wrench size={14} />, |
|
|
|
title: "오류 수정", |
|
|
|
title: "오류 수정", |
|
|
|
text: "시스템 오류 수정 및 불편 요소 개선", |
|
|
|
text: "시스템 오류 수정 및 불편 요소 개선", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "➕", |
|
|
|
icon: <PlusCircle size={14} />, |
|
|
|
title: "기능 추가", |
|
|
|
title: "기능 추가", |
|
|
|
text: "필요 기능 추가 개발 및 데이터 추출", |
|
|
|
text: "필요 기능 추가 개발 및 데이터 추출", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "⚙️", |
|
|
|
icon: <Settings size={14} />, |
|
|
|
title: "운영 안정화", |
|
|
|
title: "운영 안정화", |
|
|
|
text: "시스템 최적화를 통한 운영 안정화", |
|
|
|
text: "시스템 최적화를 통한 운영 안정화", |
|
|
|
}, |
|
|
|
}, |
|
|
|
@ -105,17 +122,17 @@ const PROJECTS = [ |
|
|
|
image: "/images/si_img5.png", |
|
|
|
image: "/images/si_img5.png", |
|
|
|
desc: [ |
|
|
|
desc: [ |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "📱", |
|
|
|
icon: <QrCode size={14} />, |
|
|
|
title: "QR 방문자 관리", |
|
|
|
title: "QR 방문자 관리", |
|
|
|
text: "QR코드 활용 방문자 방역 관리 시스템", |
|
|
|
text: "QR코드 활용 방문자 방역 관리 시스템", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🚨", |
|
|
|
icon: <Siren size={14} />, |
|
|
|
title: "출입 정보 제공", |
|
|
|
title: "출입 정보 제공", |
|
|
|
text: "확진자 발생 시 정확한 출입 정보 제공", |
|
|
|
text: "확진자 발생 시 정확한 출입 정보 제공", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🖥️", |
|
|
|
icon: <Monitor size={14} />, |
|
|
|
title: "미디어 월 연동", |
|
|
|
title: "미디어 월 연동", |
|
|
|
text: "미디어 월 연동을 통한 고객 응대", |
|
|
|
text: "미디어 월 연동을 통한 고객 응대", |
|
|
|
}, |
|
|
|
}, |
|
|
|
@ -128,52 +145,32 @@ const PROJECTS = [ |
|
|
|
image: "/images/si_img6.png", |
|
|
|
image: "/images/si_img6.png", |
|
|
|
desc: [ |
|
|
|
desc: [ |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "🏗️", |
|
|
|
icon: <Building2 size={14} />, |
|
|
|
title: "인프라 구축", |
|
|
|
title: "인프라 구축", |
|
|
|
text: "항공 운송사업자 필수 서비스 및 인프라", |
|
|
|
text: "항공 운송사업자 필수 서비스 및 인프라", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "✈️", |
|
|
|
icon: <Plane size={14} />, |
|
|
|
title: "국제선 대응", |
|
|
|
title: "국제선 대응", |
|
|
|
text: "국제선 취항 대응 및 부가서비스 매출 증대", |
|
|
|
text: "국제선 취항 대응 및 부가서비스 매출 증대", |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
icon: "📋", |
|
|
|
icon: <ClipboardList size={14} />, |
|
|
|
title: "고도화 체계", |
|
|
|
title: "고도화 체계", |
|
|
|
text: "국토부 필수 시스템 및 서비스 고도화", |
|
|
|
text: "국토부 필수 시스템 및 서비스 고도화", |
|
|
|
}, |
|
|
|
}, |
|
|
|
], |
|
|
|
], |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
|
|
|
|
id: "07", |
|
|
|
|
|
|
|
title: "하이에어 항공 시스템 운영", |
|
|
|
|
|
|
|
tags: ["항공", "운영/유지보수"], |
|
|
|
|
|
|
|
image: "/images/si_img7.png", |
|
|
|
|
|
|
|
desc: [ |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
icon: "🔧", |
|
|
|
|
|
|
|
title: "오류 수정", |
|
|
|
|
|
|
|
text: "시스템 오류 수정 및 불편 요소 개선", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
icon: "➕", |
|
|
|
|
|
|
|
title: "기능 추가", |
|
|
|
|
|
|
|
text: "필요 기능 추가 개발 및 데이터 추출", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
icon: "⚙️", |
|
|
|
|
|
|
|
title: "운영 안정화", |
|
|
|
|
|
|
|
text: "시스템 최적화를 통한 운영 안정화", |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
]; |
|
|
|
]; |
|
|
|
const AUTO_DELAY = 4000; |
|
|
|
|
|
|
|
|
|
|
|
const AUTO_DELAY = 3000; |
|
|
|
|
|
|
|
|
|
|
|
function SiPage() { |
|
|
|
function SiPage() { |
|
|
|
const basePath = import.meta.env.BASE_URL; |
|
|
|
const basePath = import.meta.env.BASE_URL; |
|
|
|
const ref = useFadeIn(); |
|
|
|
const ref = useFadeIn(); |
|
|
|
const sectionRef = useRef(null); |
|
|
|
const sectionRef = useRef(null); |
|
|
|
|
|
|
|
const sliderRef = useRef(null); |
|
|
|
|
|
|
|
const cardRef = useRef(null); |
|
|
|
const dragStartX = useRef(null); |
|
|
|
const dragStartX = useRef(null); |
|
|
|
const dragStartCurrent = useRef(null); |
|
|
|
const dragStartCurrent = useRef(null); |
|
|
|
const inView = useInView(sectionRef, { once: true, margin: "-100px" }); |
|
|
|
const inView = useInView(sectionRef, { once: true, margin: "-100px" }); |
|
|
|
@ -181,25 +178,55 @@ function SiPage() { |
|
|
|
const [paused, setPaused] = useState(false); |
|
|
|
const [paused, setPaused] = useState(false); |
|
|
|
const total = PROJECTS.length; |
|
|
|
const total = PROJECTS.length; |
|
|
|
|
|
|
|
|
|
|
|
const canPrev = current > 0; |
|
|
|
const [cardWidth, setCardWidth] = useState(0); |
|
|
|
const canNext = current < total - 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
|
|
const update = () => { |
|
|
|
|
|
|
|
if (cardRef.current) { |
|
|
|
|
|
|
|
setCardWidth(cardRef.current.offsetWidth); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
update(); |
|
|
|
|
|
|
|
window.addEventListener("resize", update); |
|
|
|
|
|
|
|
return () => window.removeEventListener("resize", update); |
|
|
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 prev = () => { |
|
|
|
const prev = () => { |
|
|
|
if (!canPrev) return; |
|
|
|
setCurrent((c) => { |
|
|
|
setCurrent((c) => c - 1); |
|
|
|
const nextIdx = c <= 0 ? total - 1 : c - 1; |
|
|
|
|
|
|
|
scrollToCard(nextIdx); |
|
|
|
|
|
|
|
return nextIdx; |
|
|
|
|
|
|
|
}); |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const next = () => { |
|
|
|
const next = () => { |
|
|
|
if (!canNext) return; |
|
|
|
setCurrent((c) => { |
|
|
|
setCurrent((c) => c + 1); |
|
|
|
const nextIdx = c >= total - 1 ? 0 : c + 1; |
|
|
|
|
|
|
|
scrollToCard(nextIdx); |
|
|
|
|
|
|
|
return nextIdx; |
|
|
|
|
|
|
|
}); |
|
|
|
}; |
|
|
|
}; |
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
if (paused) return; |
|
|
|
if (paused) return; |
|
|
|
const timer = setInterval(() => { |
|
|
|
const timer = setInterval(() => { |
|
|
|
setCurrent((c) => (c + 1) % total); |
|
|
|
setCurrent((c) => { |
|
|
|
|
|
|
|
const nextIdx = (c + 1) % total; |
|
|
|
|
|
|
|
scrollToCard(nextIdx); |
|
|
|
|
|
|
|
return nextIdx; |
|
|
|
|
|
|
|
}); |
|
|
|
}, AUTO_DELAY); |
|
|
|
}, AUTO_DELAY); |
|
|
|
return () => clearInterval(timer); |
|
|
|
return () => clearInterval(timer); |
|
|
|
}, [paused, total]); |
|
|
|
}, [paused, total]); |
|
|
|
|
|
|
|
|
|
|
|
const handleDragStart = (e) => { |
|
|
|
const handleDragStart = (e) => { |
|
|
|
dragStartX.current = |
|
|
|
dragStartX.current = |
|
|
|
e.type === "touchstart" ? e.touches[0].clientX : e.clientX; |
|
|
|
e.type === "touchstart" ? e.touches[0].clientX : e.clientX; |
|
|
|
@ -233,144 +260,185 @@ function SiPage() { |
|
|
|
navItems={BUSINESS_NAV} |
|
|
|
navItems={BUSINESS_NAV} |
|
|
|
/> |
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<div className="sub-content"> |
|
|
|
<div className="sub-content si-archive-wrap"> |
|
|
|
<section |
|
|
|
<div className="inner-wrap"> |
|
|
|
className="si_archive" |
|
|
|
<section |
|
|
|
ref={sectionRef} |
|
|
|
className="si_archive" |
|
|
|
onMouseEnter={() => setPaused(true)} |
|
|
|
ref={sectionRef} |
|
|
|
onMouseLeave={() => setPaused(false)} |
|
|
|
onMouseEnter={() => setPaused(true)} |
|
|
|
> |
|
|
|
onMouseLeave={() => setPaused(false)} |
|
|
|
<div className="si_archive__main"> |
|
|
|
> |
|
|
|
{/* 헤더 */} |
|
|
|
<div className="si_archive__main"> |
|
|
|
<div className="si_archive__header"> |
|
|
|
{/* 헤더 */} |
|
|
|
<motion.span |
|
|
|
<div className="si_archive__header"> |
|
|
|
className="fc-eyebrow" |
|
|
|
<motion.span |
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
className="fc-eyebrow" |
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
transition={{ duration: 0.6, ease }} |
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
> |
|
|
|
transition={{ duration: 0.6, ease }} |
|
|
|
PROJECT ARCHIVE |
|
|
|
> |
|
|
|
</motion.span> |
|
|
|
PROJECT ARCHIVE |
|
|
|
<motion.h2 |
|
|
|
</motion.span> |
|
|
|
className="si_archive__title" |
|
|
|
<motion.h2 |
|
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
|
className="si_archive__title" |
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
initial={{ opacity: 0, y: 20 }} |
|
|
|
transition={{ duration: 0.6, delay: 0.1, ease }} |
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
> |
|
|
|
transition={{ duration: 0.6, delay: 0.1, ease }} |
|
|
|
수행사업 |
|
|
|
> |
|
|
|
<br /> |
|
|
|
{window.innerWidth <= 768 ? ( |
|
|
|
아카이브 |
|
|
|
"수행사업 아카이브" |
|
|
|
</motion.h2> |
|
|
|
) : ( |
|
|
|
<motion.p |
|
|
|
<> |
|
|
|
className="si_archive__desc" |
|
|
|
수행사업 |
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
<br /> |
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
아카이브 |
|
|
|
transition={{ duration: 0.6, delay: 0.2, ease }} |
|
|
|
</> |
|
|
|
> |
|
|
|
)} |
|
|
|
PAL Networks가 구축한 |
|
|
|
</motion.h2> |
|
|
|
<br /> |
|
|
|
<motion.p |
|
|
|
주요 프로젝트를 소개합니다. |
|
|
|
className="si_archive__desc" |
|
|
|
</motion.p> |
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
|
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
{/* 네비게이션 */} |
|
|
|
transition={{ duration: 0.6, delay: 0.2, ease }} |
|
|
|
<div className="si_archive__nav"> |
|
|
|
|
|
|
|
<button |
|
|
|
|
|
|
|
className="si_archive__nav-btn" |
|
|
|
|
|
|
|
onClick={prev} |
|
|
|
|
|
|
|
disabled={!canPrev} |
|
|
|
|
|
|
|
aria-label="이전" |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
← |
|
|
|
{window.innerWidth <= 768 ? ( |
|
|
|
</button> |
|
|
|
"PAL Networks가 구축한 주요 프로젝트를 소개합니다." |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
<> |
|
|
|
|
|
|
|
PAL Networks가 구축한 |
|
|
|
|
|
|
|
<br /> |
|
|
|
|
|
|
|
주요 프로젝트를 소개합니다. |
|
|
|
|
|
|
|
</> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</motion.p> |
|
|
|
|
|
|
|
|
|
|
|
<div className="si_archive__progress"> |
|
|
|
{/* 네비게이션 */} |
|
|
|
<span className="si_archive__progress-cur"> |
|
|
|
<div className="si_archive__nav"> |
|
|
|
{String(current + 1).padStart(2, "0")} |
|
|
|
<motion.button |
|
|
|
</span> |
|
|
|
className="si_archive__nav-btn" |
|
|
|
<span className="si_archive__progress-divider">/</span> |
|
|
|
onClick={prev} |
|
|
|
<span className="si_archive__progress-total"> |
|
|
|
aria-label="이전" |
|
|
|
{String(total).padStart(2, "0")} |
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
</span> |
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
</div> |
|
|
|
transition={{ duration: 0.6, delay: 0.4, ease }} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
← |
|
|
|
|
|
|
|
</motion.button> |
|
|
|
|
|
|
|
|
|
|
|
<button |
|
|
|
<motion.div |
|
|
|
className="si_archive__nav-btn" |
|
|
|
className="si_archive__progress" |
|
|
|
onClick={next} |
|
|
|
initial={{ opacity: 0, y: 16 }} |
|
|
|
disabled={!canNext} |
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
aria-label="다음" |
|
|
|
transition={{ duration: 0.6, delay: 0.4, ease }} |
|
|
|
> |
|
|
|
> |
|
|
|
→ |
|
|
|
<span className="si_archive__progress-cur"> |
|
|
|
</button> |
|
|
|
{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> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 슬라이더 */} |
|
|
|
{/* 슬라이더 */} |
|
|
|
<div className="si_archive__right"> |
|
|
|
<div className="si_archive__right"> |
|
|
|
<div |
|
|
|
|
|
|
|
className="si_archive__slider" |
|
|
|
|
|
|
|
style={{ display: "flex" }} |
|
|
|
|
|
|
|
onMouseDown={handleDragStart} |
|
|
|
|
|
|
|
onMouseUp={handleDragEnd} |
|
|
|
|
|
|
|
onTouchStart={handleDragStart} |
|
|
|
|
|
|
|
onTouchEnd={handleDragEnd} |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
<motion.div |
|
|
|
<motion.div |
|
|
|
className="si_archive__track" |
|
|
|
initial={{ opacity: 0, y: 30 }} |
|
|
|
animate={{ x: `calc(-${current} * (65% + 18px))` }} |
|
|
|
animate={inView ? { opacity: 1, y: 0 } : {}} |
|
|
|
transition={{ duration: 0.55, ease }} |
|
|
|
transition={{ duration: 0.6, delay: 0.3, ease }} |
|
|
|
style={{ alignItems: "stretch" }} |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
{PROJECTS.map((project) => ( |
|
|
|
<div |
|
|
|
<div key={project.id} className="si_archive__card"> |
|
|
|
className="si_archive__slider" |
|
|
|
<div className="si_archive__card-img"> |
|
|
|
style={{ display: "flex" }} |
|
|
|
<img |
|
|
|
onMouseDown={handleDragStart} |
|
|
|
src={`${basePath}images/si_img${parseInt(project.id)}.png`} |
|
|
|
onMouseUp={handleDragEnd} |
|
|
|
alt={project.title} |
|
|
|
onTouchStart={handleDragStart} |
|
|
|
/> |
|
|
|
onTouchEnd={handleDragEnd} |
|
|
|
<div className="si_archive__card-img-placeholder" /> |
|
|
|
ref={sliderRef} |
|
|
|
</div> |
|
|
|
> |
|
|
|
|
|
|
|
<motion.div |
|
|
|
<div className="si_archive__card-body"> |
|
|
|
className="si_archive__track" |
|
|
|
<div className="si_archive__card-header"> |
|
|
|
animate={{ |
|
|
|
<div className="si_archive__card-num"> |
|
|
|
x: |
|
|
|
{project.id} |
|
|
|
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/si_img${parseInt(project.id)}.png`} |
|
|
|
|
|
|
|
alt={project.title} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
<div className="si_archive__card-img-placeholder" /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<h3 className="si_archive__card-title"> |
|
|
|
|
|
|
|
{project.title} |
|
|
|
|
|
|
|
</h3> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div className="si_archive__card-tags"> |
|
|
|
<div className="si_archive__card-body"> |
|
|
|
{project.tags.map((tag) => ( |
|
|
|
<div className="si_archive__card-header"> |
|
|
|
<span key={tag} className="si_archive__tag"> |
|
|
|
<div className="si_archive__card-num"> |
|
|
|
{tag} |
|
|
|
{project.id} |
|
|
|
</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> |
|
|
|
<div className="si_archive__card-desc-text"> |
|
|
|
<h3 className="si_archive__card-title"> |
|
|
|
{item.text} |
|
|
|
{project.title} |
|
|
|
</div> |
|
|
|
</h3> |
|
|
|
</li> |
|
|
|
</div> |
|
|
|
))} |
|
|
|
|
|
|
|
</ul> |
|
|
|
<div className="si_archive__card-tags"> |
|
|
|
</div> |
|
|
|
{project.tags.map((tag) => ( |
|
|
|
</div> |
|
|
|
<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> |
|
|
|
</motion.div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
</section> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</article> |
|
|
|
</article> |
|
|
|
); |
|
|
|
); |
|
|
|
|