From 1f5eb909b189ba5850038b7cf2b5ac0bdc097dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?siyeon00=28=EC=9D=B4=EC=8B=9C=EC=97=B0=29?= Date: Fri, 5 Jun 2026 13:26:35 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=EC=9A=B4=EC=98=81=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80=EB=B3=B4=EC=88=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/css/common.css | 175 ++++---- src/pages/business/MaintenancePage.jsx | 589 ++++++++++++++++++++----- 2 files changed, 552 insertions(+), 212 deletions(-) diff --git a/src/css/common.css b/src/css/common.css index d091492..0e098a2 100644 --- a/src/css/common.css +++ b/src/css/common.css @@ -915,7 +915,6 @@ body{overflow-x:hidden;} /* ======================================== SI PAGE, RND - PROJECT ARCHIVE SECTION ======================================== */ -.si-archive-wrap .inner-wrap { max-width: none; padding: 0; margin: 0; } .si_archive { padding:100px 0 120px; max-width:1660px; margin-left: auto; margin-right: 0;} .si_archive__main { display:flex; align-items:flex-start; gap:46px; } .si_archive__header { flex: 0 0 320px; padding-top: 24px; } @@ -1011,103 +1010,97 @@ body{overflow-x:hidden;} .si_archive__progress { gap: 8px; } } - -/*maintenance page*/ -.mt-intro {display:flex;gap:80px;align-items:center;padding:80px 0 100px;} -.mt-intro__left {flex:0 0 400px;} -.mt-intro__right {flex:1;min-width:0;display:flex;justify-content:center;} - -.mt-title-line {overflow:hidden;padding-bottom:.06em;margin-bottom:-.06em;} -.mt-intro__title {font-size:clamp(28px,3vw,42px);font-weight:800;color:var(--color-primary);line-height:1.25;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;} - -/* ── OPS CENTER ── */ -.mt-ops {position:relative;width:100%;max-width:560px;aspect-ratio:1/1;} -.mt-ops__svg {width:100%;height:100%;overflow:visible;} - -.mt-ops__pulse {transform-origin:320px 320px;} - -@keyframes mt-pulse-1 { - 0% { transform: scale(1); opacity: 1; } - 100% { transform: scale(3.2); opacity: 0; } -} -@keyframes mt-pulse-2 { - 0% { transform: scale(1); opacity: 1; } - 100% { transform: scale(2.6); opacity: 0; } +/* ================================================================ + Maintenance Page + ================================================================ */ +.mt-wrap.sub-content { + background: linear-gradient(135deg, #f8f0ff 0%, #ffffff 60%); } - -.mt-ops__pulse--1 {animation: mt-pulse-1 2.8s ease-out infinite;} -.mt-ops__pulse--2 {animation: mt-pulse-2 2.8s ease-out infinite 1.4s;} - -.mt-ops__center {position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:108px;height:108px;border-radius:50%;background:var(--grad-brand);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;box-shadow:0 8px 32px rgba(123,63,160,.35);} -.mt-ops__center-label {font-size:.95rem;font-weight:900;color:#fff;letter-spacing:.08em;line-height:1;} -.mt-ops__center-sub {font-size:.6rem;font-weight:700;color:rgba(255,255,255,.7);letter-spacing:.15em;} - +.mt-intro { display:flex; gap:60px; align-items:center; padding:80px 0 100px; } +.mt-intro__left { flex:0 0 400px; } +.mt-intro__right { flex:1; min-width:0; display:flex; justify-content:right; align-items:center; } + +.mt-title-line { overflow:hidden; padding-bottom:.06em; margin-bottom:-.06em; } +.mt-intro__title { font-size:clamp(28px,3vw,42px); font-weight:800; color:var(--color-primary); line-height:1.25; 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; } + +/* ── 벤다이어그램 ── */ +.mt-venn { position:relative; width:100%; max-width:640px; } +.mt-venn__svg { width:100%; height:auto; display:block; overflow:visible; } + /* ── 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:10px;padding:20px 16px;border-right:1px solid rgba(255,255,255,.1);text-align:center;} -.mt-kpi__item:last-child {border-right:none;} -.mt-kpi__value {font-size:clamp(28px,3.5vw,52px);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;} - -.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;transition:transform .4s cubic-bezier(.22,1,.36,1);} -.mt-service-card:hover .mt-service-card__img-wrap {transform:translateY(-6px) scale(1.08);} -.mt-service-card__img {width:100%;height:100%;object-fit:contain;} - -.mt-service-card__num {font-size:.7rem;font-weight:800;letter-spacing:.12em;color:var(--pink);opacity:.8;} -.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);} - +.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:10px; padding:20px 16px; border-right:1px solid rgba(255,255,255,.1); text-align:center; } +.mt-kpi__item:last-child { border-right:none; } +.mt-kpi__value { font-size:clamp(28px,3.5vw,52px); 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; } + +.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; transition:transform .4s cubic-bezier(.22,1,.36,1); } +.mt-service-card:hover .mt-service-card__img-wrap { transform:translateY(-6px) scale(1.08); } +.mt-service-card__img { width:100%; height:100%; object-fit:contain; } + +.mt-service-card__num { font-size:.7rem; font-weight:800; letter-spacing:.12em; color:var(--pink); opacity:.8; } +.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 (min-width: 1660px) { + .si-archive-wrap .inner-wrap { + margin-left: 122.5px; + margin-right: 0; + padding-right: 0; + } } +@media (max-width: 1280px) { + .mt-intro { gap:40px; } + .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-ops {max-width:420px;} - .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;} + .mt-intro { flex-direction:column; gap:48px; padding:60px 0 80px; } + .mt-intro__left { flex:none; width:100%; } + .mt-venn { max-width:460px; } + .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-ops {max-width:340px;} - .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;} + .mt-intro { padding:48px 0 64px; } + .mt-venn { max-width:340px; } + .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;} -} \ No newline at end of file + .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; } +} + \ No newline at end of file diff --git a/src/pages/business/MaintenancePage.jsx b/src/pages/business/MaintenancePage.jsx index a7d064d..76a99e3 100644 --- a/src/pages/business/MaintenancePage.jsx +++ b/src/pages/business/MaintenancePage.jsx @@ -1,5 +1,6 @@ -import { useRef } from "react"; -import { motion, useInView, useAnimationFrame } from "framer-motion"; +import { useRef, useEffect, useState } from "react"; +import { motion, useInView } from "framer-motion"; +import { gsap } from "gsap"; import SubHero from "../../components/SubHero"; import useFadeIn from "../../hooks/useFadeIn"; @@ -11,7 +12,55 @@ const BUSINESS_NAV = [ { label: "운영 · 유지보수", to: "/business/maintenance" }, ]; -const SERVICES = [ +const CIRCLES = [ + { + id: "c0", + title: "모니터링", + items: [ + "시스템·네트워크 전 계층 감시", + "이상 징후 선제 포착", + "24/7 실시간 알람", + ], + grad: ["#d94889", "#a855f7"], + }, + { + id: "c1", + title: "장애 대응", + items: ["즉각적인 원인 분석", "신속한 시스템 복구", "재발 방지 체계 수립"], + grad: ["#a855f7", "#6366f1"], + }, + { + id: "c2", + title: "보안 관리", + items: ["정기 취약점 점검", "패치 및 업데이트 관리", "외부 위협 차단"], + grad: ["#6366f1", "#3b82f6"], + }, + { + id: "c3", + title: "기술 지원", + items: [ + "기술 문의 신속 응대", + "가이드 및 교육 제공", + "고객 역량 강화 지원", + ], + grad: ["#3b82f6", "#06b6d4"], + }, + { + id: "c4", + title: "지속적 개선", + items: ["정기 리포트 분석", "운영 효율 최적화", "서비스 품질 지속 향상"], + grad: ["#06b6d4", "#d94889"], + }, +]; + +const KPI = [ + { value: "99.9%", label: "서비스 가용성" }, + { value: "24/7", label: "365일 실시간 운영" }, + { value: "10m 24s", label: "평균 응답 시간" }, + { value: "100%", label: "SLA 준수율" }, +]; + +const SERVICES_CARD = [ { num: "01", title: "모니터링", @@ -44,113 +93,373 @@ const SERVICES = [ }, ]; -const KPI = [ - { value: "99.9%", label: "서비스 가용성" }, - { value: "24/7", label: "365일 실시간 운영" }, - { value: "10m 24s", label: "평균 응답 시간" }, - { value: "100%", label: "SLA 준수율" }, -]; +/* ── KPI 카운트업 ── */ +function KpiItem({ value, label, inView, delay }) { + const valRef = useRef(null); + const hasAnimated = useRef(false); + useEffect(() => { + if (!inView || hasAnimated.current) return; + hasAnimated.current = true; + const el = valRef.current; + const match = value.match(/^([^0-9]*)([0-9]+\.?[0-9]*)(.*)$/); + if (!match) { + gsap.from(el, { opacity: 0, y: 10, duration: 0.6, delay }); + return; + } + const prefix = match[1], + num = parseFloat(match[2]), + suffix = match[3]; + const decimals = match[2].includes(".") ? match[2].split(".")[1].length : 0; + const obj = { val: 0 }; + gsap.to(obj, { + val: num, + duration: 1.8, + delay, + ease: "power3.out", + onUpdate: () => { + el.textContent = prefix + obj.val.toFixed(decimals) + suffix; + }, + onComplete: () => { + el.textContent = value; + }, + }); + }, [inView]); // eslint-disable-line + return ( + + + {value} + + {label} + + ); +} + +/* ── 상수 ── */ +const N = 5; +const SVG_W = 760; +const SVG_H = 760; +const CX = SVG_W / 2; +const CY = SVG_H / 2; +const GUIDE_R = 240; +const OUTER_R = 310; +const CR = 108; + +const SMALL_ANGLES = Array.from({ length: N }, (_, i) => -90 + (360 / N) * i); +function getDeg(i) { + return SMALL_ANGLES[i]; +} +function getCenter(i) { + const rad = (getDeg(i) * Math.PI) / 180; + return { x: CX + GUIDE_R * Math.cos(rad), y: CY + GUIDE_R * Math.sin(rad) }; +} + +const DOT_OFFSETS = [0, 120, 240]; + +const ARC_DEG = 55; +const ROTATE_SPEED = 20; +const DOT_SPEED = 15; + +// spring-like easing for circle entrance +const springEase = [0.34, 1.56, 0.64, 1]; -/* 아이콘 5개 위치 — 정오각형, 상단에서 시작 */ -const NODE_ANGLES = [-90, -18, 54, 126, 198]; -const CX = 320, - CY = 320, - R = 190, - ICON_R = 44; +function VennDiagram({ inView }) { + const angleRef = useRef(-90); + const dotAngleRef = useRef(0); + const lastTime = useRef(null); + const rafRef = useRef(null); + + const arcRef = useRef(null); + const dotRefs = useRef([]); + const dotGlowRefs = useRef([]); + const activeIdxRef = useRef(null); + + const [activeFill, setActiveFill] = useState(null); + const holdFrames = useRef(0); + const HOLD = 90; + + useEffect(() => { + if (!inView) return; + let running = true; + lastTime.current = null; + + const arcLen = 2 * Math.PI * GUIDE_R; + + function tick(now) { + if (!running) return; + if (lastTime.current === null) lastTime.current = now; + const delta = (now - lastTime.current) / 1000; + lastTime.current = now; + + angleRef.current = (angleRef.current + ROTATE_SPEED * delta) % 360; + const a = angleRef.current; + + if (arcRef.current) { + const offset = + arcLen - (arcLen * ((((a + 90) % 360) + 360) % 360)) / 360; + arcRef.current.setAttribute("stroke-dashoffset", offset); + } + + const THRESHOLD = 22; + let found = null; + for (let i = 0; i < N; i++) { + const diff = (((a - SMALL_ANGLES[i]) % 360) + 360) % 360; + if (diff <= THRESHOLD) { + found = i; + break; + } + } + + if (found !== null) { + holdFrames.current = HOLD; + if (found !== activeIdxRef.current) { + activeIdxRef.current = found; + setActiveFill(found); + } + } else { + if (holdFrames.current > 0) { + holdFrames.current--; + } else if (activeIdxRef.current !== null) { + activeIdxRef.current = null; + setActiveFill(null); + } + } + + dotAngleRef.current = + (dotAngleRef.current - DOT_SPEED * delta + 360) % 360; + const da = dotAngleRef.current; + DOT_OFFSETS.forEach((offset, k) => { + const deg = da + offset; + const rad = (deg * Math.PI) / 180; + const dx = CX + OUTER_R * Math.cos(rad); + const dy = CY + OUTER_R * Math.sin(rad); + if (dotRefs.current[k]) { + dotRefs.current[k].setAttribute("cx", dx); + dotRefs.current[k].setAttribute("cy", dy); + } + if (dotGlowRefs.current[k]) { + dotGlowRefs.current[k].setAttribute("cx", dx); + dotGlowRefs.current[k].setAttribute("cy", dy); + } + }); + + rafRef.current = requestAnimationFrame(tick); + } + + rafRef.current = requestAnimationFrame(tick); + return () => { + running = false; + cancelAnimationFrame(rafRef.current); + }; + }, [inView]); + + const centers = CIRCLES.map((_, i) => getCenter(i)); + + const initDots = DOT_OFFSETS.map((offset) => { + const rad = (offset * Math.PI) / 180; + return { x: CX + OUTER_R * Math.cos(rad), y: CY + OUTER_R * Math.sin(rad) }; + }); + + const arcLen = 2 * Math.PI * GUIDE_R; + const dashLen = arcLen * (ARC_DEG / 360); + const gapLen = arcLen - dashLen; -function OpsCircle({ inView }) { return ( -
- +
+ {/* + ── 등장 애니메이션 ── + SVG 전체: 오른쪽에서 슬라이드 인 + fade + */} + - - - + + + - - - - - - - - - - - - - - - - - - - + {CIRCLES.map((c, i) => ( + + + + + ))} - {/* pulse 링 — CSS 애니메이션 */} - - - - {/* 궤도 링 */} - - - {/* 연결선 아이콘 → 중심 */} - {NODE_ANGLES.map((deg, i) => { - const rad = (deg * Math.PI) / 180; - const ix = CX + R * Math.cos(rad); - const iy = CY + R * Math.sin(rad); - const mx = CX + 80 * Math.cos(rad); - const my = CY + 80 * Math.sin(rad); - return ; - })} + {/* 바깥 장식 원 — fade in */} + - {/* 아이콘 노드 원 배경 */} - {NODE_ANGLES.map((deg, i) => { - const rad = (deg * Math.PI) / 180; - const ix = CX + R * Math.cos(rad); - const iy = CY + R * Math.sin(rad); - return ; - })} + {/* 가이드 원 실선 — fade in + scale */} + - {/* 아이콘 이미지 — SVG foreignObject */} - {NODE_ANGLES.map((deg, i) => { - const rad = (deg * Math.PI) / 180; - const ix = CX + R * Math.cos(rad); - const iy = CY + R * Math.sin(rad); - const size = 54; - return ( - - {SERVICES[i].title} - - ); - })} + {/* 그라데이션 호 — DOM 직접 조작 */} + + + {/* 도트 3개 — fade in */} + {initDots.map((pos, k) => ( + + (dotGlowRefs.current[k] = el)} + cx={pos.x} + cy={pos.y} + r="9" + fill="#a855f7" + opacity="0.18" + /> + (dotRefs.current[k] = el)} + cx={pos.x} + cy={pos.y} + r="5" + fill="#a855f7" + opacity="0.7" + /> + + ))} - {/* 아이콘 라벨 */} - {NODE_ANGLES.map((deg, i) => { - const rad = (deg * Math.PI) / 180; - const ix = CX + R * Math.cos(rad); - const iy = CY + R * Math.sin(rad); - const labelOffset = 58; - const lx = CX + (R + 68) * Math.cos(rad); - const ly = CY + (R + 68) * Math.sin(rad); + {/* + ── 소원 5개: stagger scale + fade in ── + 각 원을 motion.g로 감싸서 자신의 중심 기준으로 scale + delay: 0.45 + i * 0.1 → 순서대로 등장 + */} + {centers.map(({ x, y }, i) => { + const isActive = activeFill === i; return ( - - {SERVICES[i].title} - + + {/* 흰색 fill — 호를 가림 */} + + + {/* 그라데이션 fill — 활성일 때만 */} + + + {/* 테두리 */} + + + {/* 텍스트 */} + + {CIRCLES[i].title} + + + {CIRCLES[i].items.map((item, j) => ( + + {item} + + ))} + ); })} - - - {/* OPS CENTER 중앙 — DOM 오버레이 */} - - OPS - CENTER - +
); } +/* ── 메인 페이지 ── */ function MaintenancePage() { const ref = useFadeIn(); const introRef = useRef(null); @@ -171,68 +480,106 @@ function MaintenancePage() { } navItems={BUSINESS_NAV} /> - -
+
- {/* ── 인트로 ── */}
- + OUR SERVICE -
- {["운영·유지보수는", "서비스의 안정성을", "완성합니다"].map((line, i) => ( -
- - {line} - -
- ))} + {["운영·유지보수는", "서비스의 안정성을", "완성합니다"].map( + (line, i) => ( +
+ + {line} + +
+ ), + )}
- - + PAL Networks는 24/7 통합 모니터링과 체계적인 유지보수로
시스템의 가용성과 안정성을 지속적으로 보장합니다.
- - + 서비스 자세히 보기 - +
-
- +
- {/* ── KPI 바 ── */}
{KPI.map((k, i) => ( - - {k.value} - {k.label} - + ))}
- {/* ── 5컬럼 서비스 ── */}
- {SERVICES.map((svc, i) => ( - + {SERVICES_CARD.map((svc, i) => ( +
- {svc.title} + {svc.title}
{svc.num}

{svc.title}