Browse Source

feat : ibe 페이지 추가 반응형 수정

remotes/origin/main
이시연 3 weeks ago
parent
commit
f57404a669
  1. 91
      src/css/common.css
  2. 242
      src/pages/solution/IbePage.jsx

91
src/css/common.css

@ -886,7 +886,7 @@ body{overflow-x:hidden;}
.ibe-channel__card { width: 285px; height: 82px; display: flex; align-items: center; gap: 18px; padding: 0 28px; border: 1px solid rgba(232,234,243,0.9); border-radius: 18px; background: rgba(255,255,255,0.92); box-shadow: 0 8px 28px rgba(31,34,64,0.11); }
.ibe-channel__card img { width: 44px; height: 44px; object-fit: contain; }
.ibe-channel__card span { font-size: 1rem; font-weight: 700; color: #1a1f3a; }
.ibe-channel__card span { font-size: 1rem; font-weight: 700; color: var(--color-primary); }
.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)); }
@ -927,7 +927,7 @@ body{overflow-x:hidden;}
@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-section { padding: 60px 0 80px; }
.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; }
@ -935,24 +935,85 @@ body{overflow-x:hidden;}
.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 { width: 88px; height: 88px; border-radius: 50%; background: #f8faff; display: flex; align-items: center; justify-content: center; box-shadow: 5px 5px 12px rgba(0,0,0,0.06), -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__flow {
flex-direction: column;
align-items: flex-start;
gap: 0;
padding-left: 16px;
}
.ibe-booking__item {
flex-direction: row;
align-items: center;
gap: 20px;
width: 100%;
}
.ibe-booking__circle {
flex-shrink: 0;
width: 64px;
height: 64px;
margin-bottom: 0;
}
.ibe-booking__text-wrap {
display: flex;
flex-direction: column;
}
.ibe-booking__num { margin-bottom: 2px; }
.ibe-booking__desc { text-align: left; }
}
/* 커넥터: 원(64px) 중심(32px)에 맞춰 세로선 */
.ibe-booking__connector {
flex: none;
width: 2px;
height: 40px;
margin: 4px 0 4px 31px; /* 31px = 원 중심 32px - 선 두께 1px */
position: relative;
}
.ibe-booking__line-bg {
position: absolute;
top: 0; left: 0;
width: 1px; height: 100%;
right: auto;
background: rgba(139, 92, 246, 0.15);
}
.ibe-booking__line-flow {
position: absolute;
top: 0; left: 0;
width: 2px; height: 100%;
right: auto;
overflow: hidden;
}
.ibe-booking__line-flow::after {
content: '';
position: absolute;
left: 0;
top: -40%;
width: 100%;
height: 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%; }
}
@keyframes ibe-booking-flow { from { left: -40%; } to { left: 100%; } }

242
src/pages/solution/IbePage.jsx

@ -1,49 +1,13 @@
import useFadeIn from "../../hooks/useFadeIn";
import SubHero from "../../components/SubHero";
import { useRef, useState } from "react";
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 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: "비정상 상황의 경보 표출" },
];
import { useRef } from "react";
import { motion, useInView } from "framer-motion";
import { Search, Armchair, CalendarClock, Wallet, Ticket } from "lucide-react";
const FLOW = [
{
step: "01",
label: "비행체 식별",
desc: "항공기·드론·선박 실시간 신호 수집",
},
{
step: "02",
label: "데이터 수집",
desc: "위치·속도·고도·식별코드 통합 처리",
},
{ step: "03", label: "관제 서버", desc: "통합 관제 서버 분석 및 상황 판단" },
{ step: "04", label: "모니터링", desc: "관제사 화면 실시간 현황 표출" },
{ step: "05", label: "경보·대응", desc: "이상 상황 감지 즉시 경보 및 대응" },
];
const ease = [0.22, 1, 0.36, 1];
function IbePage() {
const basePath = import.meta.env.BASE_URL;
const ref = useFadeIn();
const introRef = useRef(null);
const introInView = useInView(introRef, { once: true, margin: "-60px" });
@ -51,33 +15,21 @@ function IbePage() {
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 = [
{ label: "비행상황관리 시스템", to: "/solution/flight-control" },
{ label: "IBE", to: "/solution/ibe" },
// { label: " ", to: "/solution/smart-tour" },
// { label: "KT G-cloud ", to: "/solution/kt-gcloud" },
];
const [activeIdx, setActiveIdx] = useState(0);
return (
<article ref={ref}>
<SubHero
label="SOLUTION"
title={
<>
{/* <span style={{ color: "#111" }}> </span>
<br /> */}
<em>IBE</em>
</>
}
// desc="
// ."
navItems={SOLUTION_NAV}
/>
@ -112,7 +64,7 @@ function IbePage() {
>
IBE(Internet Booking Engine) 항공권 검색부터 예약, 결제,
<br />
발권까지 모든 프로세스를 지원하는 차세대 예약 엔진 입니다.{" "}
발권까지 모든 프로세스를 지원하는 차세대 예약 엔진 입니다.
<br />
다양한 채널과 시스템을 유연하게 연동하여 최적의 예약 경험을
제공합니다.
@ -139,7 +91,6 @@ function IbePage() {
))}
</motion.div>
</div>
<motion.div
className="fc-intro__right"
initial={{ opacity: 0, x: 40 }}
@ -148,7 +99,7 @@ function IbePage() {
>
<img
src={`${basePath}images/ibe_computer.png`}
alt="비행상황관리 시스템"
alt="IBE"
className="fc-intro__monitor"
/>
</motion.div>
@ -174,6 +125,20 @@ function IbePage() {
</motion.h2>
<div className="ibe-booking__flow">
<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="#60a5fa" />
<stop offset="100%" stopColor="#f472b6" />
</linearGradient>
</defs>
</svg>
{[
{
num: "01",
@ -203,7 +168,7 @@ function IbePage() {
num: "05",
icon: Ticket,
label: "발권",
desc: "E-ticket 발행 및\n예약 확인을 통해\n예약 완료",
desc: "E-Ticket 발행 후\n예약 정보를 확인하고\n예약 완료",
},
].map((item, i) => {
const Icon = item.icon;
@ -217,24 +182,6 @@ function IbePage() {
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}
@ -266,6 +213,7 @@ function IbePage() {
})}
</div>
</section>
{/* MULTI CHANNEL INTEGRATION */}
<section className="ibe-channel-section" ref={channelRef}>
<motion.span
@ -329,7 +277,6 @@ function IbePage() {
className="ibe-channel__path"
d="M830 245 C980 245 1020 335 1190 335"
/>
<circle cx="350" cy="95" r="4" className="ibe-channel__dot" />
<circle cx="350" cy="215" r="4" className="ibe-channel__dot" />
<circle cx="350" cy="335" r="4" className="ibe-channel__dot" />
@ -337,6 +284,7 @@ function IbePage() {
<circle cx="1190" cy="215" r="4" className="ibe-channel__dot" />
<circle cx="1190" cy="335" r="4" className="ibe-channel__dot" />
</svg>
<div className="ibe-channel__cols-wrap">
<div className="ibe-channel__col ibe-channel__col--left">
{[
@ -344,7 +292,13 @@ function IbePage() {
{ img: "ibe_pal_icon2.png", label: "GDS" },
{ img: "ibe_pal_icon3.png", label: "호텔" },
].map((item, i) => (
<motion.div key={i} className="ibe-channel__card">
<motion.div
key={i}
className="ibe-channel__card"
initial={{ opacity: 0, x: -24 }}
animate={channelInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.6, ease, delay: 0.4 + i * 0.1 }}
>
<img
src={`${basePath}images/${item.img}`}
alt={item.label}
@ -355,10 +309,13 @@ function IbePage() {
</div>
<div className="ibe-channel__center">
<img
<motion.img
src={`${basePath}images/ibe_pal_item.png`}
alt="PAL IBE"
className="ibe-channel__center-img"
initial={{ opacity: 0, scale: 0.8 }}
animate={channelInView ? { opacity: 1, scale: 1 } : {}}
transition={{ duration: 0.8, ease, delay: 0.5 }}
/>
</div>
@ -368,7 +325,13 @@ function IbePage() {
{ img: "ibe_pal_icon5.png", label: "여행사/OTA" },
{ img: "ibe_pal_icon6.png", label: "보험" },
].map((item, i) => (
<motion.div key={i} className="ibe-channel__card">
<motion.div
key={i}
className="ibe-channel__card"
initial={{ opacity: 0, x: 24 }}
animate={channelInView ? { opacity: 1, x: 0 } : {}}
transition={{ duration: 0.6, ease, delay: 0.4 + i * 0.1 }}
>
<img
src={`${basePath}images/${item.img}`}
alt={item.label}
@ -380,129 +343,6 @@ function IbePage() {
</div>
</motion.div>
</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>
</article>

Loading…
Cancel
Save