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
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> |
|
); |
|
}
|
|
|