Browse Source

feat : 고객과 파트너 페이지 추가

remotes/origin/main
이시연 4 weeks ago
parent
commit
afcc34bb6f
  1. BIN
      public/images/partner/banner01.png
  2. BIN
      public/images/partner/banner02.png
  3. BIN
      public/images/partner/banner03.png
  4. BIN
      public/images/partner/banner04.png
  5. BIN
      public/images/partner/banner05.png
  6. BIN
      public/images/partner/banner06.png
  7. BIN
      public/images/partner/banner07.png
  8. BIN
      public/images/partner/banner08.png
  9. BIN
      public/images/partner/banner09.png
  10. BIN
      public/images/partner/banner10.png
  11. BIN
      public/images/partner/banner11.png
  12. BIN
      public/images/partner/banner12.png
  13. BIN
      public/images/partner/banner13.png
  14. BIN
      public/images/partner/banner14.png
  15. BIN
      public/images/partner/banner15.png
  16. BIN
      public/images/partner/banner16.png
  17. BIN
      public/images/partner/banner18.png
  18. BIN
      public/images/partner/banner19.png
  19. BIN
      public/images/partner/banner20.png
  20. BIN
      public/images/partner/banner21.png
  21. BIN
      public/images/partner/banner22.png
  22. BIN
      public/images/partner/banner23.png
  23. BIN
      public/images/partner/banner24.png
  24. 145
      src/components/MeshWaveBg.jsx
  25. 55
      src/components/SubHero.jsx
  26. 31
      src/css/common.css
  27. 366
      src/pages/company/PartnersPage.jsx

BIN
public/images/partner/banner01.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
public/images/partner/banner02.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
public/images/partner/banner03.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/images/partner/banner04.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
public/images/partner/banner05.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
public/images/partner/banner06.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
public/images/partner/banner07.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
public/images/partner/banner08.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
public/images/partner/banner09.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/images/partner/banner10.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
public/images/partner/banner11.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/images/partner/banner12.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/images/partner/banner13.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/images/partner/banner14.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/images/partner/banner15.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
public/images/partner/banner16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
public/images/partner/banner18.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
public/images/partner/banner19.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/images/partner/banner20.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/images/partner/banner21.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/images/partner/banner22.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/images/partner/banner23.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
public/images/partner/banner24.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

145
src/components/MeshWaveBg.jsx

@ -0,0 +1,145 @@
import { useEffect, useRef } from "react";
export default function MeshWaveBg() {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
let raf;
let t = 0;
const COLS = 18;
const ROWS = 10;
const COLORS = ["#d94889", "#7b3fa0", "#593a84", "#198dc7", "#1a1f5e"];
function resize() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
}
function getPoint(col, row, w, h) {
const cellW = w / (COLS - 1);
const cellH = h / (ROWS - 1);
const baseX = col * cellW;
const baseY = row * cellH;
const waveX =
Math.sin(t * 0.6 + row * 0.7 + col * 0.3) * cellW * 0.28 +
Math.sin(t * 0.3 + col * 0.5) * cellW * 0.12;
const waveY =
Math.sin(t * 0.5 + col * 0.6 + row * 0.4) * cellH * 0.38 +
Math.cos(t * 0.4 + row * 0.5) * cellH * 0.14;
return { x: baseX + waveX, y: baseY + waveY };
}
function draw() {
const w = canvas.width;
const h = canvas.height;
ctx.clearRect(0, 0, w, h);
t += 0.008;
const pts = [];
for (let r = 0; r < ROWS; r++) {
pts[r] = [];
for (let c = 0; c < COLS; c++) {
pts[r][c] = getPoint(c, r, w, h);
}
}
//
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const p = pts[r][c];
const colorIdx = (r * COLS + c) % COLORS.length;
const alpha = 0.06 + ((Math.sin(t + r + c) + 1) / 2) * 0.07;
//
if (c < COLS - 1) {
const p2 = pts[r][c + 1];
const grad = ctx.createLinearGradient(p.x, p.y, p2.x, p2.y);
grad.addColorStop(0, COLORS[colorIdx] + toHex(alpha));
grad.addColorStop(
1,
COLORS[(colorIdx + 1) % COLORS.length] + toHex(alpha),
);
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = grad;
ctx.lineWidth = 0.7;
ctx.stroke();
}
//
if (r < ROWS - 1) {
const p2 = pts[r + 1][c];
const grad = ctx.createLinearGradient(p.x, p.y, p2.x, p2.y);
grad.addColorStop(0, COLORS[colorIdx] + toHex(alpha));
grad.addColorStop(
1,
COLORS[(colorIdx + 2) % COLORS.length] + toHex(alpha),
);
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = grad;
ctx.lineWidth = 0.7;
ctx.stroke();
}
//
if (c < COLS - 1 && r < ROWS - 1) {
const p2 = pts[r + 1][c + 1];
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = COLORS[colorIdx] + toHex(alpha * 0.5);
ctx.lineWidth = 0.4;
ctx.stroke();
}
//
const nodeAlpha =
0.12 + ((Math.sin(t * 1.2 + r * 1.3 + c * 0.9) + 1) / 2) * 0.18;
ctx.beginPath();
ctx.arc(p.x, p.y, 1.2, 0, Math.PI * 2);
ctx.fillStyle = COLORS[colorIdx] + toHex(nodeAlpha);
ctx.fill();
}
}
raf = requestAnimationFrame(draw);
}
function toHex(alpha) {
return Math.round(Math.min(1, Math.max(0, alpha)) * 255)
.toString(16)
.padStart(2, "0");
}
resize();
draw();
const ro = new ResizeObserver(resize);
ro.observe(canvas);
return () => {
cancelAnimationFrame(raf);
ro.disconnect();
};
}, []);
return (
<canvas
ref={canvasRef}
style={{
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
}}
/>
);
}

55
src/components/SubHero.jsx

@ -105,7 +105,8 @@ function NetworkGlobe() {
const d = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (d < 0.55) {
const depth = (a.sz + b.sz) * 0.5;
const alpha = Math.max(0, 0.05 + (depth + 1) * 0.1) * (1 - d / 0.55);
const alpha =
Math.max(0, 0.05 + (depth + 1) * 0.1) * (1 - d / 0.55);
const aHex = Math.round(Math.min(255, alpha * 255))
.toString(16)
.padStart(2, "0");
@ -134,7 +135,14 @@ function NetworkGlobe() {
.toString(16)
.padStart(2, "0");
const glow = ctx.createRadialGradient(n.sx, n.sy, 0, n.sx, n.sy, r * 3.5);
const glow = ctx.createRadialGradient(
n.sx,
n.sy,
0,
n.sx,
n.sy,
r * 3.5,
);
glow.addColorStop(0, n.color + gHex);
glow.addColorStop(1, n.color + "00");
ctx.beginPath();
@ -172,7 +180,12 @@ function NetworkGlobe() {
function TitleLine({ children, delay }) {
return (
<span className="sh4-title-line">
<motion.span className="sh4-title-line-inner" initial={{ y: "105%" }} animate={{ y: "0%" }} transition={{ duration: 1.1, delay, ease: [0.16, 1, 0.3, 1] }}>
<motion.span
className="sh4-title-line-inner"
initial={{ y: "105%" }}
animate={{ y: "0%" }}
transition={{ duration: 1.1, delay, ease: [0.16, 1, 0.3, 1] }}
>
{children}
</motion.span>
</span>
@ -202,7 +215,9 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
))
: // JSX: <br/> TitleLine
(() => {
const children = Array.isArray(title.props?.children) ? title.props.children : [title];
const children = Array.isArray(title.props?.children)
? title.props.children
: [title];
const lines = [];
let current = [];
children.forEach((child, i) => {
@ -228,7 +243,12 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
<section className={`sh4${rightSlot ? " sh4--split" : ""}`}>
<div className="sh4-inner">
<div className="sh4-left">
<motion.span className="sh4-label" initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}>
<motion.span
className="sh4-label"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
>
{menuMap["/" + pathname.split("/")[1]]?.label}
</motion.span>
<h1 className="sh4-title">{titleContent}</h1>
@ -239,7 +259,11 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
animate={{ opacity: 1, y: 0 }}
transition={{
duration: 0.6,
delay: 0.1 + (typeof title === "string" ? title.split("\n").length : 2) * 0.1 + 0.15,
delay:
0.1 +
(typeof title === "string" ? title.split("\n").length : 2) *
0.1 +
0.15,
ease: [0.16, 1, 0.3, 1],
}}
>
@ -249,7 +273,12 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
</div>
{rightSlot && (
<motion.div className="sh4-right" initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 1, delay: 0.3 }}>
<motion.div
className="sh4-right"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 0.3 }}
>
{rightSlot}
</motion.div>
)}
@ -257,10 +286,18 @@ export default function SubHero({ title, desc, navItems, rightSlot }) {
</section>
{navItems?.length > 1 && (
<nav ref={navRef} className={`sh4-nav-wrap${isPill ? " is-pill" : ""}`} aria-label="Sub Navigation">
<nav
ref={navRef}
className={`sh4-nav-wrap${isPill ? " is-pill" : ""}`}
aria-label="Sub Navigation"
>
<div className="sh4-nav">
{navItems.map((item) => (
<Link key={item.to} to={item.to} className={`sh4-nav-tab${pathname === item.to ? " sh4-nav-tab--active" : ""}`}>
<Link
key={item.to}
to={item.to}
className={`sh4-nav-tab${pathname === item.to ? " sh4-nav-tab--active" : ""}`}
>
{item.label}
</Link>
))}

31
src/css/common.css

@ -27,9 +27,19 @@ body{overflow-x:hidden;}
.sub-layout{min-height:calc(100vh - var(--header-height));padding-top:var(--header-height);}
/*sh4 subhero*/
.sh4{position:relative;overflow:hidden;margin-top:calc(-1 * var(--header-height));padding:calc(var(--header-height) + 80px) 80px 96px;background:#fff;}
.sh4 {
position: relative;
overflow: hidden;
margin-top: calc(-1 * var(--header-height));
padding: calc(var(--header-height) + 80px) 80px 96px;
background:
radial-gradient(ellipse 80% 60% at 70% 110%, rgba(217, 72, 137, 0.10) 0%, transparent 60%),
radial-gradient(ellipse 60% 50% at 10% -10%, rgba(123, 63, 160, 0.10) 0%, transparent 55%),
radial-gradient(ellipse 50% 40% at 90% 10%, rgba(25, 141, 199, 0.08) 0%, transparent 50%),
linear-gradient(155deg, #f5f4fb 0%, #faf5fc 35%, #f0f6fb 70%, #f8f4fa 100%);
}
/* .sh4{position:relative;overflow:hidden;margin-top:calc(-1 * var(--header-height));padding:calc(var(--header-height) + 80px) 80px 96px;background:#fff;} */
.sh4-inner{position:relative;z-index:2;max-width:1440px;margin:0 auto;}
.sh4-left{}
.sh4-right{position:relative;height:100%;min-height:320px;}
.sh4-label{display:block;font-size:11px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:rgba(26,31,94,.38);margin-bottom:20px;opacity:0;transform:translateY(10px);animation:sh4Up .55s cubic-bezier(.16,1,.3,1) forwards;}
.sh4-title{margin:0 0 20px;font-size:clamp(40px,6vw,80px);font-weight:900;line-height:1.05;letter-spacing:-.055em;color:var(--navy);}
@ -94,3 +104,20 @@ body{overflow-x:hidden;}
.ab-eyebrow{display:block;font-size:11px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;color:rgba(26,31,94,.35);margin-bottom:40px;}
.ab-eyebrow--light{color:rgba(255,255,255,.3);}
/* ── 공통 섹션 헤딩 — 가운데 정렬 ── */
.partner-section-heading{text-align:center;margin-bottom:24px;}
.partner-section-title{font-size:clamp(28px,4vw,52px);font-weight:700;color:#0f1628;line-height:1.25;margin-bottom:24px;}
.partner-section-sub{font-size:clamp(13px,1.4vw,16px);color:#9499b0;letter-spacing:-0.01em;}
.partner-hub-section{padding:40px 0 0;}
.client-hub-svg{display:block;width:100%;height:auto;}
.partner-logo-section{padding:40px 0 60px;background:#f7f8fc;}
.partner-logo-top{display:flex;justify-content:space-between;align-items:flex-end;max-width:1440px;margin:0 auto 24px;padding:0 40px;}
.partner-logo-kicker{display:block;font-size:11px;font-weight:600;letter-spacing:0.12em;text-transform:uppercase;color:var(--navy);margin-bottom:6px;}
.partner-logo-stat{display:flex;flex-direction:column;align-items:flex-end;}
.partner-logo-stat__num{font-size:48px;font-weight:700;color:#0f1628;line-height:1;}
.partner-logo-stat__label{font-size:12px;color:#9499b0;margin-top:4px;}
.partner-logo-grid--partners{display:grid;grid-template-columns:repeat(5,1fr);max-width:1440px;margin:0 auto;background-color:#fff;border-top:1px solid #ebebf0;border-left:1px solid #ebebf0;}
.partner-logo-card{display:flex;align-items:center;justify-content:center;padding:32px 24px;border-right:1px solid #ebebf0;border-bottom:1px solid #ebebf0;background:#fff;}
.partner-logo-card img{max-width:120px;max-height:52px;width:100%;object-fit:contain;}
@media(max-width:768px){.partner-logo-grid--partners{grid-template-columns:repeat(3,1fr);}}
@media(max-width:480px){.partner-logo-grid--partners{grid-template-columns:repeat(2,1fr);}}

366
src/pages/company/PartnersPage.jsx

@ -1,6 +1,12 @@
import { useEffect, useRef } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import SubHero from "../../components/SubHero";
import useFadeIn from "../../hooks/useFadeIn";
gsap.registerPlugin(ScrollTrigger);
const COMPANY_NAV = [
{ label: "회사소개", to: "/company/about" },
{ label: "연혁", to: "/company/history" },
@ -8,37 +14,233 @@ const COMPANY_NAV = [
{ label: "찾아오시는 길", to: "/company/location" },
];
const CX = 380;
const CY = 230;
const ORBIT_R = 165;
const CLIENTS = [
{ name: "인천국제공항공사", type: "공공기관" },
{ name: "한국공항공사", type: "공공기관" },
{ name: "국방부", type: "정부기관" },
{ name: "인천광역시", type: "지방자치단체" },
{ name: "제주도관광공사", type: "공공기관" },
{ name: "항공안전기술원", type: "공공기관" },
{ name: "한국항공우주연구원", type: "연구기관" },
{ name: "국토교통부", type: "정부기관" },
{ id: "airport", logo: "01", angle: -90 },
{ id: "kac", logo: "09", angle: -45 },
{ id: "mod", logo: "15", angle: 0 },
{ id: "incheon", logo: "16", angle: 45 },
{ id: "jeju", logo: "20", angle: 90 },
{ id: "kiast", logo: "21", angle: 135 },
{ id: "kari", logo: "23", angle: 180 },
{ id: "molit", logo: "24", angle: -135 },
];
const PARTNERS = [
{ name: "KT", type: "클라우드 파트너" },
{ name: "마이크로소프트", type: "기술 파트너" },
{ name: "오라클", type: "기술 파트너" },
{ name: "삼성SDS", type: "SI 파트너" },
{ name: "한화시스템", type: "방산·항공" },
{ name: "LG CNS", type: "SI 파트너" },
const PARTNER_LOGOS = [
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"10",
"11",
"12",
"13",
"14",
"18",
"19",
"22",
];
function PartnerCard({ name, type }) {
function toRad(deg) {
return (deg * Math.PI) / 180;
}
function nodePos(angle) {
return {
x: CX + ORBIT_R * Math.cos(toRad(angle)),
y: CY + ORBIT_R * Math.sin(toRad(angle)),
};
}
function ClientHub({ basePath }) {
const svgRef = useRef(null);
useEffect(() => {
const ctx = gsap.context(() => {
gsap.fromTo(
".hub-edge",
{ strokeDashoffset: 200, opacity: 0 },
{
strokeDashoffset: 0,
opacity: 1,
duration: 1.1,
stagger: 0.07,
ease: "power2.out",
scrollTrigger: { trigger: svgRef.current, start: "top 75%" },
},
);
gsap.fromTo(
".hub-center",
{ scale: 0.6, opacity: 0, transformOrigin: `${CX}px ${CY}px` },
{
scale: 1,
opacity: 1,
duration: 0.7,
ease: "back.out(1.6)",
scrollTrigger: { trigger: svgRef.current, start: "top 80%" },
},
);
gsap.fromTo(
".hub-node",
{ scale: 0, opacity: 0 },
{
scale: 1,
opacity: 1,
duration: 0.55,
stagger: 0.06,
ease: "back.out(1.4)",
scrollTrigger: { trigger: svgRef.current, start: "top 75%" },
delay: 0.35,
},
);
}, svgRef);
return () => ctx.revert();
}, []);
return (
<svg
ref={svgRef}
className="client-hub-svg"
viewBox="0 0 760 480"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx={CX}
cy={CY}
r={ORBIT_R}
fill="none"
stroke="#e4e4ec"
strokeWidth="1"
strokeDasharray="5 7"
/>
{CLIENTS.map((c) => {
const pos = nodePos(c.angle);
return (
<line
key={c.id}
className="hub-edge"
x1={CX}
y1={CY}
x2={pos.x}
y2={pos.y}
stroke="#d8d8e4"
strokeWidth="1"
strokeDasharray="200"
/>
);
})}
<g className="hub-center">
<circle cx={CX} cy={CY} r={44} fill="#0f1628" />
<text
x={CX}
y={CY - 9}
textAnchor="middle"
fill="#fff"
fontSize="13"
fontWeight="600"
letterSpacing="0.04em"
>
주요
</text>
<text
x={CX}
y={CY + 7}
textAnchor="middle"
fill="#fff"
fontSize="13"
fontWeight="600"
letterSpacing="0.04em"
>
고객사
</text>
<text
x={CX}
y={CY + 23}
textAnchor="middle"
fill="#8892b0"
fontSize="10"
>
8 clients
</text>
</g>
{CLIENTS.map((c) => {
const pos = nodePos(c.angle);
const imgSrc = `${basePath}images/partner/banner${c.logo}.png`;
return (
<g
key={c.id}
className="hub-node"
style={{ transformOrigin: `${pos.x}px ${pos.y}px` }}
>
<circle
cx={pos.x}
cy={pos.y}
r={30}
fill="#fff"
stroke="#e2e2ea"
strokeWidth="1"
/>
<image
href={imgSrc}
x={pos.x - 20}
y={pos.y - 20}
width="40"
height="40"
preserveAspectRatio="xMidYMid meet"
/>
</g>
);
})}
</svg>
);
}
function LogoItem({ num, basePath }) {
const src = `${basePath}images/partner/banner${num}.png`;
return (
<div className="partner-card">
<span className="partner-card-name">{name}</span>
<span className="partner-card-type">{type}</span>
<div className="partner-logo-card">
<img src={src} alt="" loading="lazy" />
</div>
);
}
export default function PartnersPage() {
const ref = useFadeIn();
const editorialRef = useRef(null);
const basePath = import.meta.env.BASE_URL;
useEffect(() => {
const ctx = gsap.context(() => {
gsap.fromTo(
".partner-logo-card",
{
opacity: 0,
y: 36,
scale: 0.96,
clipPath: "inset(0 100% 0 0 round 0px)",
},
{
opacity: 1,
y: 0,
scale: 1,
clipPath: "inset(0 0% 0 0 round 0px)",
duration: 0.85,
stagger: { each: 0.04, from: "start" },
ease: "power3.out",
scrollTrigger: { trigger: ".partner-logo-grid", start: "top 78%" },
},
);
}, editorialRef);
return () => ctx.revert();
}, []);
return (
<article ref={ref}>
@ -55,116 +257,44 @@ export default function PartnersPage() {
navItems={COMPANY_NAV}
/>
<div className="sub-content">
<div className="partner-page" ref={editorialRef}>
<div className="partner-editorial">
{/* 주요 고객사 */}
<section className="sub-section">
<span className="sub-section-eyebrow sub-fade-in">Clients</span>
<h2 className="sub-section-title sub-fade-in">주요 고객사</h2>
<p className="sub-section-lead sub-fade-in">
인천국제공항공사, 국방부 항공·공공 분야 핵심 기관들과 장기
파트너십을 유지하며 안정적인 IT 서비스를 제공합니다.
<section className="partner-hub-section">
<div className="partner-section-heading">
<h2 className="partner-section-title">
함께 만들어온
<br />
신뢰의 네트워크
</h2>
<p className="partner-section-sub">
공공·항공·국방 분야 핵심 기관과 오랜 협력 관계를 이어오고
있습니다
</p>
<div className="partners-grid" style={{ marginTop: "40px" }}>
{CLIENTS.map((c, i) => (
<div
key={c.name}
className="sub-fade-in"
style={{ transitionDelay: `${i * 50}ms` }}
>
<PartnerCard {...c} />
</div>
))}
</div>
<ClientHub basePath={basePath} />
</section>
{/* 협력사 */}
<section className="sub-section">
<span className="sub-section-eyebrow sub-fade-in">Partners</span>
<h2 className="sub-section-title sub-fade-in">기술 협력사</h2>
<p className="sub-section-lead sub-fade-in">
국내외 선도 IT 기업들과의 협력을 통해 최적의 기술 솔루션을 고객에게
제공합니다.
</p>
<div className="partners-grid" style={{ marginTop: "40px" }}>
{PARTNERS.map((p, i) => (
<div
key={p.name}
className="sub-fade-in"
style={{ transitionDelay: `${i * 50}ms` }}
>
<PartnerCard {...p} />
{/* 기술 협력사 */}
<section className="partner-logo-section">
<div className="partner-logo-top">
<div>
<span className="partner-logo-kicker">Partners</span>
<h2 className="partner-section-title">기술 협력사</h2>
</div>
))}
<div className="partner-logo-stat">
<span className="partner-logo-stat__num">15</span>
<span className="partner-logo-stat__label">협력사</span>
</div>
</section>
{/* 협력 제안 CTA */}
<section className="sub-section sub-fade-in">
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
flexWrap: "wrap",
gap: "24px",
padding: "48px 56px",
background: "var(--color-primary)",
borderRadius: "24px",
}}
>
<div>
<p
style={{
margin: "0 0 8px",
fontSize: "13px",
fontWeight: 700,
letterSpacing: ".12em",
textTransform: "uppercase",
color: "rgba(255,255,255,.65)",
}}
>
Become a Partner
</p>
<h3
style={{
margin: 0,
fontSize: "clamp(22px,2.2vw,30px)",
fontWeight: 800,
color: "#fff",
letterSpacing: "-.03em",
lineHeight: 1.25,
}}
>
팔네트웍스와 함께 성장할
<br />
파트너를 찾습니다
</h3>
</div>
<a
href="/contact/inquiry"
style={{
display: "inline-flex",
alignItems: "center",
height: "52px",
padding: "0 28px",
background: "#fff",
borderRadius: "999px",
fontSize: "15px",
fontWeight: 700,
color: "var(--color-primary)",
textDecoration: "none",
whiteSpace: "nowrap",
letterSpacing: "-.01em",
flexShrink: 0,
}}
>
협력 문의하기
</a>
<div className="partner-logo-grid partner-logo-grid--partners">
{PARTNER_LOGOS.map((num) => (
<LogoItem key={num} num={num} basePath={basePath} />
))}
</div>
</section>
</div>
</div>
</article>
);
}

Loading…
Cancel
Save