Browse Source

운영 유지보수 이미지 추가

remotes/origin/main
김지은 3 weeks ago
parent
commit
cd95dd5131
  1. BIN
      public/images/mt_icon01.png
  2. BIN
      public/images/mt_icon02.png
  3. BIN
      public/images/mt_icon03.png
  4. BIN
      public/images/mt_icon04.png
  5. BIN
      public/images/mt_icon05.png
  6. 116
      src/css/common.css
  7. 196
      src/pages/business/MaintenancePage.jsx

BIN
public/images/mt_icon01.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
public/images/mt_icon02.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
public/images/mt_icon03.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
public/images/mt_icon04.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
public/images/mt_icon05.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

116
src/css/common.css

@ -1010,3 +1010,119 @@ body{overflow-x:hidden;}
.si_archive__desc { font-size: 13px; }
.si_archive__progress { gap: 8px; }
}
/*
MaintenancePage (mt-)
*/
/* ── 인트로 ── */
.mt-intro {display:flex;gap:80px;align-items:flex-start;padding:80px 0 100px;}
.mt-intro__left {flex:0 0 420px;}
.mt-intro__right {flex:1;min-width:0;padding-top:8px;}
.mt-title-line {overflow:hidden;padding-bottom:.06em;margin-bottom:-.06em;}
.mt-intro__title {font-size:clamp(26px,2.8vw,38px);font-weight:800;color:var(--color-primary);line-height:1.3;letter-spacing:-.03em;margin:0;}
.mt-intro__desc {font-size:.9rem;color:#666;line-height:1.9;margin:28px 0 36px;word-break:keep-all;}
.mt-intro__cta {display:inline-flex;align-items:center;gap:8px;font-size:.88rem;font-weight:700;color:var(--pink);text-decoration:none;letter-spacing:-.01em;transition:gap .25s;}
.mt-intro__cta:hover {gap:14px;}
/* ── Quote 카드 ── */
.mt-quote {position:relative;margin-top:40px;padding:32px 28px 28px;border-radius:20px;background:var(--color-primary);overflow:hidden;}
.mt-quote::before {content:'';position:absolute;inset:0;background:var(--grad-brand);opacity:.18;pointer-events:none;}
.mt-quote::after {content:'';position:absolute;inset:0;background-image:radial-gradient(rgba(255,255,255,.07) 1px,transparent 1px);background-size:18px 18px;pointer-events:none;}
.mt-quote__mark {position:relative;z-index:1;font-size:2.5rem;font-weight:900;color:var(--pink);line-height:1;margin-bottom:12px;}
.mt-quote__text {position:relative;z-index:1;font-size:1.05rem;font-weight:700;color:#fff;line-height:1.65;letter-spacing:-.02em;margin:0 0 20px;}
.mt-quote__brand {position:relative;z-index:1;font-size:.72rem;font-weight:600;color:rgba(255,255,255,.35);letter-spacing:.12em;text-transform:uppercase;}
/* ── 서비스 리스트 ── */
.mt-svc-list {list-style:none;margin:0;padding:0;}
.mt-svc-item {position:relative;display:flex;align-items:center;gap:24px;padding:20px 0;border-top:1px solid var(--color-primary-soft-border);cursor:default;overflow:hidden;transition:padding-left .35s cubic-bezier(.22,1,.36,1);}
.mt-svc-list li:last-child {border-bottom:1px solid var(--color-primary-soft-border);}
.mt-svc-item:hover, .mt-svc-item.is-active {padding-left:12px;}
.mt-svc-item__num {font-size:.7rem;font-weight:800;letter-spacing:.1em;color:var(--color-primary-border-strong);flex-shrink:0;width:28px;transition:color .3s;}
.mt-svc-item:hover .mt-svc-item__num, .mt-svc-item.is-active .mt-svc-item__num {color:var(--pink);}
.mt-svc-item__body {flex:1;min-width:0;}
.mt-svc-item__title {display:block;font-size:1rem;font-weight:700;color:#bbb;letter-spacing:-.01em;transition:color .3s;}
.mt-svc-item:hover .mt-svc-item__title, .mt-svc-item.is-active .mt-svc-item__title {color:var(--color-primary);}
.mt-svc-item__desc {display:block;font-size:.78rem;color:#bbb;line-height:1.5;margin-top:4px;max-height:0;opacity:0;overflow:hidden;transform:translateY(4px);transition:max-height .35s ease, opacity .3s ease, transform .3s ease;}
.mt-svc-item:hover .mt-svc-item__desc, .mt-svc-item.is-active .mt-svc-item__desc {max-height:3rem;opacity:1;transform:translateY(0);}
.mt-svc-item__icon-wrap {flex-shrink:0;width:64px;height:64px;display:flex;align-items:center;justify-content:center;opacity:0;transform:scale(.85) translateX(8px);transition:opacity .35s ease, transform .35s cubic-bezier(.22,1,.36,1);}
.mt-svc-item:hover .mt-svc-item__icon-wrap, .mt-svc-item.is-active .mt-svc-item__icon-wrap {opacity:1;transform:scale(1) translateX(0);}
.mt-svc-item__icon {width:100%;height:100%;object-fit:contain;}
.mt-svc-item__bar {position:absolute;bottom:0;left:0;right:0;height:1px;background:var(--grad-brand-h);transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.22,1,.36,1);}
.mt-svc-item:hover .mt-svc-item__bar, .mt-svc-item.is-active .mt-svc-item__bar {transform:scaleX(1);}
/* ── KPI 바 ── */
.mt-kpi {background:var(--color-primary);padding:56px 0;margin:0 0 100px;}
.mt-kpi__grid {display:grid;grid-template-columns:repeat(4,1fr);gap:0;}
.mt-kpi__item {display:flex;flex-direction:column;align-items:center;gap:8px;padding:20px 16px;border-right:1px solid rgba(255,255,255,.1);text-align:center;transition:background .2s;}
.mt-kpi__item:last-child {border-right:none;}
.mt-kpi__item:hover {background:rgba(255,255,255,.05);}
.mt-kpi__icon {font-size:1.4rem;margin-bottom:4px;opacity:.7;}
.mt-kpi__value {font-size:clamp(28px,3.5vw,48px);font-weight:900;color:#fff;letter-spacing:-.04em;line-height:1;}
.mt-kpi__label {font-size:.78rem;font-weight:500;color:rgba(255,255,255,.45);line-height:1.4;}
/* ── 5컬럼 서비스 ── */
.mt-services {padding-bottom:120px;}
.mt-services__grid {display:grid;grid-template-columns:repeat(5,1fr);gap:0;border:1px solid var(--color-primary-soft-border);border-radius:16px;overflow:hidden;margin-top:40px;}
.mt-service-card {position:relative;display:flex;flex-direction:column;gap:12px;padding:32px 24px 28px;border-right:1px solid var(--color-primary-soft-border);overflow:hidden;transition:background .25s;}
.mt-service-card:last-child {border-right:none;}
.mt-service-card:hover {background:var(--color-primary-soft);}
.mt-service-card__img-wrap {width:72px;height:72px;margin-bottom:8px;flex-shrink:0;transition:transform .4s cubic-bezier(.22,1,.36,1);}
.mt-service-card:hover .mt-service-card__img-wrap {transform:translateY(-6px) scale(1.06);}
.mt-service-card__img {width:100%;height:100%;object-fit:contain;}
/* 이미지 파일명: mt_icon01.png ~ mt_icon05.png */
.mt-service-card__num {font-size:.7rem;font-weight:800;letter-spacing:.12em;color:var(--pink);opacity:.7;}
.mt-service-card__title {font-size:1rem;font-weight:800;color:var(--color-primary);letter-spacing:-.02em;margin:0;}
.mt-service-card__desc {font-size:.78rem;color:#888;line-height:1.7;word-break:keep-all;margin:0;}
.mt-service-card__line {position:absolute;bottom:0;left:0;right:0;height:2px;background:var(--grad-brand-h);transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.22,1,.36,1);}
.mt-service-card:hover .mt-service-card__line {transform:scaleX(1);}
/* ── Responsive ── */
@media (max-width: 1280px) {
.mt-intro {gap:56px;}
.mt-intro__left {flex:0 0 360px;}
}
@media (max-width: 1024px) {
.mt-intro {flex-direction:column;gap:48px;padding:60px 0 80px;}
.mt-intro__left {flex:none;width:100%;}
.mt-services__grid {grid-template-columns:repeat(3,1fr);}
.mt-service-card:nth-child(3) {border-right:none;}
.mt-service-card:nth-child(4), .mt-service-card:nth-child(5) {border-top:1px solid var(--color-primary-soft-border);}
.mt-service-card:nth-child(5) {border-right:none;}
.mt-kpi__grid {grid-template-columns:repeat(2,1fr);}
.mt-kpi__item:nth-child(2) {border-right:none;}
.mt-kpi__item:nth-child(3), .mt-kpi__item:nth-child(4) {border-top:1px solid rgba(255,255,255,.1);}
.mt-kpi__item:nth-child(4) {border-right:none;}
}
@media (max-width: 768px) {
.mt-intro {padding:48px 0 64px;}
.mt-intro__title {font-size:clamp(22px,6vw,30px);}
.mt-services__grid {grid-template-columns:repeat(2,1fr);}
.mt-service-card:nth-child(2n) {border-right:none;}
.mt-service-card:nth-child(3) {border-right:1px solid var(--color-primary-soft-border);}
.mt-service-card:nth-child(n+3) {border-top:1px solid var(--color-primary-soft-border);}
.mt-service-card:nth-child(5) {border-right:none;grid-column:span 2;}
.mt-kpi {margin-bottom:64px;}
}
@media (max-width: 480px) {
.mt-services__grid {grid-template-columns:1fr;}
.mt-service-card {border-right:none !important;}
.mt-service-card:nth-child(n+2) {border-top:1px solid var(--color-primary-soft-border);}
.mt-service-card:nth-child(5) {grid-column:span 1;}
.mt-kpi__grid {grid-template-columns:repeat(2,1fr);}
}

196
src/pages/business/MaintenancePage.jsx

@ -1,47 +1,201 @@
import { useRef, useState, useEffect } from "react";
import { motion, useInView, animate } from "framer-motion";
import SubHero from "../../components/SubHero";
import useFadeIn from "../../hooks/useFadeIn";
import { useRef } from "react";
import { motion, useInView } from "framer-motion";
function MaintenancePage() {
const ref = useFadeIn();
const introRef = useRef(null);
const introInView = useInView(introRef, { once: true, margin: "-60px" });
const ease = [0.22, 1, 0.36, 1];
const BUSINESS_NAV = [
const ease = [0.22, 1, 0.36, 1];
const BUSINESS_NAV = [
{ label: "System Integration", to: "/business/si" },
{ label: "R&D", to: "/business/rnd" },
{ label: "운영 · 유지보수", to: "/business/maintenance" },
];
];
const SERVICES = [
{
num: "01",
title: "모니터링",
desc: "시스템 및 네트워크 상태를 24/7 실시간 모니터링",
img: "./images/mt_icon01.png",
},
{
num: "02",
title: "장애 대응",
desc: "이상 감지 시 전문 인력이 신속하게 원인 분석 및 복구",
img: "./images/mt_icon02.png",
},
{
num: "03",
title: "보안 관리",
desc: "취약점 점검 및 패치 관리로 안전한 시스템 운영",
img: "./images/mt_icon03.png",
},
{
num: "04",
title: "기술 지원",
desc: "운영 가이드, 문의 응대 및 기술 지원 제공",
img: "./images/mt_icon04.png",
},
{
num: "05",
title: "지속적 개선",
desc: "정기 리포트 및 분석을 통한 지속적인 서비스 개선",
img: "./images/mt_icon05.png",
},
];
const KPI = [
{ value: 99.9, suffix: "%", label: "서비스 가용성", icon: "⏱" },
{ value: 24, suffix: "/7", label: "365일 실시간 운영", icon: "📡" },
{ value: 10, suffix: "m 24s", label: "평균 응답 시간", icon: "⚡" },
{ value: 100, suffix: "%", label: "SLA 준수율", icon: "✓" },
];
function CountUp({ target, suffix, inView }) {
const [display, setDisplay] = useState(0);
useEffect(() => {
if (!inView) return;
const controls = animate(0, target, {
duration: 1.8,
ease: "easeOut",
onUpdate: (v) => setDisplay(Math.round(v * 10) / 10),
});
return controls.stop;
}, [inView, target]);
return (
<span>
{display}
{suffix}
</span>
);
}
function MaintenancePage() {
const ref = useFadeIn();
const introRef = useRef(null);
const kpiRef = useRef(null);
const servicesRef = useRef(null);
const introInView = useInView(introRef, { once: true, margin: "-80px" });
const kpiInView = useInView(kpiRef, { once: true, margin: "-80px" });
const servicesInView = useInView(servicesRef, { once: true, margin: "-60px" });
const [activeIdx, setActiveIdx] = useState(0);
return (
<article ref={ref}>
<SubHero
label="BUSINESS"
title={
<>
{/* <span style={{ color: "#111" }}> </span>
<br /> */}
<em>Maintenance</em>
</>
}
// desc=" ."
navItems={BUSINESS_NAV}
/>
<div className="sub-content">
<div className="inner-wrap">
{/* 개요 인트로 */}
<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
{/* ── 인트로 섹션 ── */}
<section className="mt-intro" ref={introRef}>
{/* 좌측 */}
<div className="mt-intro__left">
<motion.span className="fc-eyebrow" initial={{ opacity: 0, y: 14 }} animate={introInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, ease }}>
OUR SERVICE
</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 />
전문적인 관리 서비스
<div className="mt-intro__title-wrap">
{["운영부터 개선까지", "전문적인 관리 서비스"].map((line, i) => (
<div className="mt-title-line" key={i}>
<motion.h2 className="mt-intro__title" initial={{ y: "105%" }} animate={introInView ? { y: "0%" } : {}} transition={{ duration: 1.1, ease, delay: 0.15 + i * 0.1 }}>
{line}
</motion.h2>
</div>
))}
</div>
<motion.p className="mt-intro__desc" initial={{ opacity: 0, y: 16 }} animate={introInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.7, ease, delay: 0.45 }}>
전문 엔지니어가 시스템 전반을 관리하고,
<br />
문제 발생 신속하게 대응하여 서비스 안정성을 유지합니다.
</motion.p>
<motion.a className="mt-intro__cta" href="/contact" initial={{ opacity: 0, y: 12 }} animate={introInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, ease, delay: 0.58 }}>
서비스 자세히 보기
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</motion.a>
{/* quote 카드 */}
<motion.div className="mt-quote" initial={{ opacity: 0, y: 20 }} animate={introInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.8, ease, delay: 0.7 }}>
<div className="mt-quote__mark">"</div>
<p className="mt-quote__text">
안정적인 운영은
<br />
지속 가능한 성장의
<br />
기반입니다.
</p>
<span className="mt-quote__brand">PAL Networks</span>
</motion.div>
</div>
{/* 우측 서비스 리스트 */}
<div className="mt-intro__right">
<ul className="mt-svc-list">
{SERVICES.map((svc, i) => (
<motion.li key={i} className={`mt-svc-item${activeIdx === i ? " is-active" : ""}`} initial={{ opacity: 0, x: 24 }} animate={introInView ? { opacity: 1, x: 0 } : {}} transition={{ duration: 0.6, ease, delay: 0.3 + i * 0.08 }} onMouseEnter={() => setActiveIdx(i)}>
<span className="mt-svc-item__num">{svc.num}</span>
<div className="mt-svc-item__body">
<span className="mt-svc-item__title">{svc.title}</span>
<span className="mt-svc-item__desc">{svc.desc}</span>
</div>
<div className="mt-svc-item__icon-wrap">
<img src={svc.img} alt={svc.title} className="mt-svc-item__icon" />
</div>
<div className="mt-svc-item__bar" />
</motion.li>
))}
</ul>
</div>
</section>
</div>
{/* ── KPI 바 ── */}
<section className="mt-kpi" ref={kpiRef}>
<div className="inner-wrap">
<div className="mt-kpi__grid">
{KPI.map((k, i) => (
<motion.div key={i} className="mt-kpi__item" initial={{ opacity: 0, y: 20 }} animate={kpiInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, ease, delay: i * 0.1 }}>
<span className="mt-kpi__icon">{k.icon}</span>
<span className="mt-kpi__value">
<CountUp target={k.value} suffix={k.suffix} inView={kpiInView} />
</span>
<span className="mt-kpi__label">{k.label}</span>
</motion.div>
))}
</div>
</div>
</section>
{/* ── 5컬럼 서비스 ── */}
<div className="inner-wrap">
<section className="mt-services" ref={servicesRef}>
<motion.span className="fc-eyebrow" initial={{ opacity: 0, y: 14 }} animate={servicesInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.6, ease }}>
SERVICE DETAIL
</motion.span>
<div className="mt-services__grid">
{SERVICES.map((svc, i) => (
<motion.div key={i} className="mt-service-card" initial={{ opacity: 0, y: 32 }} animate={servicesInView ? { opacity: 1, y: 0 } : {}} transition={{ duration: 0.65, ease, delay: i * 0.1 }}>
<div className="mt-service-card__img-wrap">
<img src={svc.img} alt={svc.title} className="mt-service-card__img" />
</div>
<span className="mt-service-card__num">{svc.num}</span>
<h3 className="mt-service-card__title">{svc.title}</h3>
<p className="mt-service-card__desc">{svc.desc}</p>
<div className="mt-service-card__line" />
</motion.div>
))}
</div>
</section>
</div>
</div>

Loading…
Cancel
Save