Browse Source

feat : ibe 색상 톤 작업중

remotes/origin/main
이시연 3 weeks ago
parent
commit
5d39047e49
  1. 40
      src/css/common.css
  2. 285
      src/pages/solution/IbePage.jsx

40
src/css/common.css

@ -872,11 +872,8 @@ body{overflow-x:hidden;}
IBEPAGE common.css 추가분 IBEPAGE common.css 추가분
*/ */
.ibe-channel-section { padding: 60px 0; } .ibe-channel-section { padding: 60px 0; }
.ibe-channel__title { font-size: 2rem; font-weight: 800; color: #1a1f3a; line-height: 1.35; letter-spacing: -0.02em; margin: 12px 0 16px; } .ibe-channel__title { font-size: 2rem; font-weight: 800; color: #1a1f3a; line-height: 1.35; letter-spacing: -0.02em; margin: 12px 0 16px; }
.ibe-channel__desc { font-size: 0.9rem; color: #666; line-height: 1.9; margin-bottom: 56px; } .ibe-channel__desc { font-size: 0.9rem; color: #666; line-height: 1.9; margin-bottom: 56px; }
.ibe-channel__diagram { position: relative; width: min(1540px, 100%); height: 430px; margin: 0 auto; display: grid; grid-template-columns: 420px 1fr 420px; align-items: center; } .ibe-channel__diagram { position: relative; width: min(1540px, 100%); height: 430px; margin: 0 auto; display: grid; grid-template-columns: 420px 1fr 420px; align-items: center; }
.ibe-channel__svg { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; } .ibe-channel__svg { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }
@ -892,22 +889,16 @@ body{overflow-x:hidden;}
.ibe-channel__card span { font-size: 1rem; font-weight: 700; color: #1a1f3a; } .ibe-channel__card span { font-size: 1rem; font-weight: 700; color: #1a1f3a; }
.ibe-channel__center { position: relative; z-index: 3; display: flex; justify-content: center; align-items: center; } .ibe-channel__center { position: relative; z-index: 3; display: flex; justify-content: center; align-items: center; }
.ibe-channel__center-img { width: 310px; object-fit: contain; filter: drop-shadow(0 24px 35px rgba(93,47,151,0.22)); } .ibe-channel__center-img { width: 310px; object-fit: contain; filter: drop-shadow(0 24px 35px rgba(93,47,151,0.22)); }
.ibe-channel__svg path { fill: none; stroke: rgba(139,92,246,0.35); stroke-width: 2; stroke-dasharray: 12 6; animation: ibe-flow 2s linear infinite; } .ibe-channel__svg path { fill: none; stroke: rgba(139,92,246,0.35); stroke-width: 2; stroke-dasharray: 12 6; animation: ibe-flow 2s linear infinite; }
.ibe-channel__svg path { fill: none; stroke: rgba(139,92,246,0.35); stroke-width: 2; stroke-dasharray: 12 6; animation: ibe-flow 2s linear infinite; } .ibe-channel__svg path { fill: none; stroke: rgba(139,92,246,0.35); stroke-width: 2; stroke-dasharray: 12 6; animation: ibe-flow 2s linear infinite; }
.ibe-channel__svg path:nth-child(1), .ibe-channel__svg path:nth-child(1),
.ibe-channel__svg path:nth-child(2), .ibe-channel__svg path:nth-child(2),
.ibe-channel__svg path:nth-child(3) { animation-name: ibe-flow-right; } .ibe-channel__svg path:nth-child(3) { animation-name: ibe-flow-right; }
.ibe-channel__svg path:nth-child(4), .ibe-channel__svg path:nth-child(4),
.ibe-channel__svg path:nth-child(5), .ibe-channel__svg path:nth-child(5),
.ibe-channel__svg path:nth-child(6) { animation-name: ibe-flow-left; } .ibe-channel__svg path:nth-child(6) { animation-name: ibe-flow-left; }
.ibe-channel__svg path:nth-child(2) { animation-delay: 0.3s; } .ibe-channel__svg path:nth-child(2) { animation-delay: 0.3s; }
.ibe-channel__svg path:nth-child(3) { animation-delay: 0.6s; } .ibe-channel__svg path:nth-child(3) { animation-delay: 0.6s; }
.ibe-channel__svg path:nth-child(5) { animation-delay: 0.3s; } .ibe-channel__svg path:nth-child(5) { animation-delay: 0.3s; }
@ -934,3 +925,34 @@ body{overflow-x:hidden;}
} }
@keyframes ibe-flow-right { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -36; } } @keyframes ibe-flow-right { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -36; } }
@keyframes ibe-flow-left { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 36; } } @keyframes ibe-flow-left { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 36; } }
.ibe-booking-section { padding: 60px 0 80px; border-top: 1px solid #f0f0f0; }
.ibe-booking__title { font-size: 2rem; font-weight: 800; color: #1a1f3a; margin: 12px 0 56px; letter-spacing: -0.02em; }
.ibe-booking__flow { display: flex; align-items: flex-start; justify-content: center; }
.ibe-booking__item { display: flex; flex-direction: column; align-items: center; flex: 1; }
.ibe-booking__connector { flex: 1; position: relative; height: 4px; margin-top: 40px; }
.ibe-booking__line-bg { position: absolute; top: 50%; left: 0; right: 0; height: 1px; background: rgba(139,92,246,0.15); }
.ibe-booking__line-flow { position: absolute; top: 50%; left: 0; right: 0; height: 2px; overflow: hidden; }
.ibe-booking__line-flow::after { content: ''; position: absolute; top: 0; left: -40%; width: 40%; height: 100%; background: linear-gradient(90deg, transparent, rgba(139,92,246,0.8), transparent); animation: ibe-booking-flow 1.8s linear infinite; animation-delay: var(--delay, 0s); }
.ibe-booking__icon { font-size: 32px; background: linear-gradient(135deg, #a78bfa, #ec4899); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
@keyframes ibe-booking-flow { from { left: -40%; } to { left: 100%; } }
.ibe-booking__circle { width: 88px; height: 88px; border-radius: 50%; background: #f4f5fb; display: flex; align-items: center; justify-content: center; box-shadow: 5px 5px 12px rgba(0,0,0,0.08), -5px -5px 12px #fff; margin-bottom: 20px; }
.ibe-booking__circle img { width: 48px; height: 48px; object-fit: contain; }
.ibe-booking__num { font-size: 10px; font-weight: 700; color: var(--color-primary-light); letter-spacing: 0.1em; margin-bottom: 6px; }
.ibe-booking__label { font-size: 14px; font-weight: 700; color: var(--color-primary); margin-bottom: 8px; }
.ibe-booking__desc { font-size: 12px; color: #888; line-height: 1.7; text-align: center; }
@media (max-width: 768px) {
.ibe-booking__flow { flex-direction: column; align-items: flex-start; gap: 24px; }
.ibe-booking__item { flex-direction: row; align-items: center; gap: 16px; text-align: left; }
.ibe-booking__circle { flex-shrink: 0; width: 64px; height: 64px; margin-bottom: 0; }
.ibe-booking__circle img { width: 36px; height: 36px; }
.ibe-booking__connector { width: 2px; height: 32px; margin: 0 0 0 32px; }
.ibe-booking__line-bg { top: 0; bottom: 0; left: 50%; width: 1px; height: 100%; }
.ibe-booking__line-flow { top: 0; left: 50%; width: 2px; height: 100%; }
.ibe-booking__line-flow::after { width: 100%; height: 40%; left: 0; top: -40%; background: linear-gradient(180deg, transparent, rgba(139,92,246,0.8), transparent); animation: ibe-booking-flow-v 1.8s linear infinite; animation-delay: var(--delay, 0s); }
@keyframes ibe-booking-flow-v { from { top: -40%; } to { top: 100%; } }
.ibe-booking__desc { text-align: left; }
}

285
src/pages/solution/IbePage.jsx

@ -1,10 +1,46 @@
import useFadeIn from "../../hooks/useFadeIn"; import useFadeIn from "../../hooks/useFadeIn";
import SubHero from "../../components/SubHero"; import SubHero from "../../components/SubHero";
import { useRef } from "react"; import { useRef, useState } from "react";
import { motion, useInView, AnimatePresence } from "framer-motion"; import { motion, useInView, AnimatePresence } from "framer-motion";
import {
Search,
Armchair,
CalendarClock,
Wallet,
Ticket,
ArrowUpRight,
} from "lucide-react";
const ease = [0.22, 1, 0.36, 1]; const ease = [0.22, 1, 0.36, 1];
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: "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: "03", label: "관제 서버", desc: "통합 관제 서버 분석 및 상황 판단" },
{ step: "04", label: "모니터링", desc: "관제사 화면 실시간 현황 표출" },
{ step: "05", label: "경보·대응", desc: "이상 상황 감지 즉시 경보 및 대응" },
];
function IbePage() { function IbePage() {
const basePath = import.meta.env.BASE_URL; const basePath = import.meta.env.BASE_URL;
@ -13,12 +49,22 @@ function IbePage() {
const introInView = useInView(introRef, { once: true, margin: "-60px" }); const introInView = useInView(introRef, { once: true, margin: "-60px" });
const channelRef = useRef(null); const channelRef = useRef(null);
const channelInView = useInView(channelRef, { once: true, margin: "-60px" }); const channelInView = useInView(channelRef, { once: true, margin: "-60px" });
const bookingRef = useRef(null);
const bookingInView = useInView(bookingRef, { once: true, margin: "-60px" });
const funcRef = useRef(null);
const funcInView = useInView(funcRef, { once: true, margin: "-80px" });
const flowRef = useRef(null);
const flowInView = useInView(flowRef, { once: true, margin: "-60px" });
const SOLUTION_NAV = [ const SOLUTION_NAV = [
{ label: "비행상황관리 시스템", to: "/solution/flight-control" }, { label: "비행상황관리 시스템", to: "/solution/flight-control" },
{ label: "IBE", to: "/solution/ibe" }, { label: "IBE", to: "/solution/ibe" },
// { label: " ", to: "/solution/smart-tour" }, // { label: " ", to: "/solution/smart-tour" },
// { label: "KT G-cloud ", to: "/solution/kt-gcloud" }, // { label: "KT G-cloud ", to: "/solution/kt-gcloud" },
]; ];
const [activeIdx, setActiveIdx] = useState(0);
return ( return (
<article ref={ref}> <article ref={ref}>
<SubHero <SubHero
@ -108,6 +154,118 @@ function IbePage() {
</motion.div> </motion.div>
</section> </section>
{/* BOOKING PROCESS */}
<section className="ibe-booking-section" ref={bookingRef}>
<motion.span
className="fc-eyebrow"
initial={{ opacity: 0, y: 16 }}
animate={bookingInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease }}
>
Booking Process
</motion.span>
<motion.h2
className="ibe-booking__title"
initial={{ opacity: 0, y: 24 }}
animate={bookingInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.7, ease, delay: 0.1 }}
>
직관적이고 간편한 예약 프로세스
</motion.h2>
<div className="ibe-booking__flow">
{[
{
num: "01",
icon: Search,
label: "검색",
desc: "출발지, 도착지, 일정,\n탑승객 정보를 입력하여\n최적의 항공편 검색",
},
{
num: "02",
icon: Armchair,
label: "선택",
desc: "운임, 스케줄, 좌석 등\n다양한 옵션을 비교하고\n원하는 항공편 선택",
},
{
num: "03",
icon: CalendarClock,
label: "예약",
desc: "승객 정보 입력 및\n부가 서비스 선택을 통해\n예약을 진행",
},
{
num: "04",
icon: Wallet,
label: "결제",
desc: "다양한 결제 수단을 지원하여\n안전하고 간편하게\n결제 진행",
},
{
num: "05",
icon: Ticket,
label: "발권",
desc: "E-ticket 발행 및\n예약 확인을 통해\n예약 완료",
},
].map((item, i) => {
const Icon = item.icon;
return (
<>
<motion.div
key={i}
className="ibe-booking__item"
initial={{ opacity: 0, y: 24 }}
animate={bookingInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.6, ease, delay: 0.2 + i * 0.1 }}
>
<div className="ibe-booking__circle">
<svg
width="0"
height="0"
style={{ position: "absolute" }}
>
<defs>
<linearGradient
id="ibe-icon-grad"
x1="0%"
y1="0%"
x2="100%"
y2="100%"
>
<stop offset="0%" stopColor="#a78bfa" />
<stop offset="100%" stopColor="#ec4899" />
</linearGradient>
</defs>
</svg>
<Icon
size={32}
strokeWidth={1.5}
stroke="url(#ibe-icon-grad)"
/>
</div>
<span className="ibe-booking__num">{item.num}</span>
<span className="ibe-booking__label">{item.label}</span>
<p className="ibe-booking__desc">
{item.desc.split("\n").map((line, j) => (
<span key={j}>
{line}
<br />
</span>
))}
</p>
</motion.div>
{i < 4 && (
<div
className="ibe-booking__connector"
style={{ "--delay": `${i * 0.4}s` }}
>
<div className="ibe-booking__line-bg" />
<div className="ibe-booking__line-flow" />
</div>
)}
</>
);
})}
</div>
</section>
{/* MULTI CHANNEL INTEGRATION */} {/* MULTI CHANNEL INTEGRATION */}
<section className="ibe-channel-section" ref={channelRef}> <section className="ibe-channel-section" ref={channelRef}>
<motion.span <motion.span
@ -222,6 +380,129 @@ function IbePage() {
</div> </div>
</motion.div> </motion.div>
</section> </section>
{/* 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>
<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 }}
>
<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 }}
>
<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.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"
/>
<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 }}
>
{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 }}
>
{FUNCTIONS[activeIdx].label}
</motion.span>
</div>
</motion.div>
</AnimatePresence>
</div>
</div>
</section>
{/* 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>
<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 }}
>
<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,
}}
/>
)}
</motion.div>
))}
</div>
</section>
</div> </div>
</div> </div>
</article> </article>

Loading…
Cancel
Save