You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

300 lines
7.4 KiB

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" },
{ label: "고객 및 협력사", to: "/company/partners" },
{ label: "찾아오시는 길", to: "/company/location" },
];
const CX = 380;
const CY = 230;
const ORBIT_R = 165;
const CLIENTS = [
{ 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 PARTNER_LOGOS = [
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"10",
"11",
"12",
"13",
"14",
"18",
"19",
"22",
];
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-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}>
<SubHero
label="Company"
title={
<>
<span style={{ color: "#111" }}>함께하는</span>
<br />
<em>고객과 파트너</em>
</>
}
desc="공공·항공·국방 분야 주요 기관과의 신뢰를 바탕으로 성장해왔습니다."
navItems={COMPANY_NAV}
/>
<div className="partner-page" ref={editorialRef}>
<div className="partner-editorial">
{/* 주요 고객사 */}
<section className="partner-hub-section">
<div className="partner-section-heading">
<h2 className="partner-section-title">
함께 만들어온
<br />
신뢰의 네트워크
</h2>
<p className="partner-section-sub">
공공·항공·국방 분야 핵심 기관과 오랜 협력 관계를 이어오고
있습니다
</p>
</div>
<ClientHub basePath={basePath} />
</section>
{/* 기술 협력사 */}
<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>
</div>
<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>
);
}