Browse Source

feat : 고객 파트너 카드형식 디자인

remotes/origin/main
이시연 4 weeks ago
parent
commit
739130cc76
  1. 155
      src/css/common.css
  2. 288
      src/pages/company/PartnersPage.jsx

155
src/css/common.css

@ -105,23 +105,140 @@ body{overflow-x:hidden;}
.ab-eyebrow--light{color:rgba(255,255,255,.3);} .ab-eyebrow--light{color:rgba(255,255,255,.3);}
/* ── 공통 섹션 헤딩 — 가운데 정렬 ── */ /* ── 공통 섹션 헤딩 — 가운데 정렬 ── */
.partner-section-heading{text-align:center;margin-bottom:20px;} /* ── Partners Page ── */
.partner-section-title{font-size:clamp(32px,4.5vw,60px);font-weight:800;color:#0a0f1e;line-height:1.08;letter-spacing:-0.04em;margin-bottom:14px;} .partners-wrap {
.partner-section-sub{font-size:clamp(13px,1.2vw,16px);color:rgba(26,31,94,.42);letter-spacing:-0.01em;} padding: 48px 0px;
.partner-hub-section{padding:72px 0 0;} display: flex;
.client-hub-svg{display:block;width:100%;height:auto;} flex-direction: column;
.partner-logo-section{padding:40px 0 60px;background:#f7f8fc;} gap: 40px;
.partner-logo-top{display:flex;justify-content:space-between;align-items:flex-end;max-width:1440px;margin:0 auto 24px;padding:0 40px;} max-width: 1440px;
.partner-logo-kicker{display:block;font-size:11px;font-weight:600;letter-spacing:0.12em;text-transform:uppercase;color:#1a1f5e;margin-bottom:6px;} margin: 0 auto;
.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 #e0e0eb;border-left:1px solid #e0e0eb;} .partners-eyebrow {
.partner-logo-card{display:flex;align-items:center;justify-content:center;padding:32px 24px;border-right:1px solid #e0e0eb;border-bottom:1px solid #e0e0eb;background:#fff;} font-size: 11px;
.partner-logo-card img{max-width:120px;max-height:52px;width:100%;object-fit:contain;} font-weight: 700;
@media(max-width:768px){.partner-logo-grid--partners{grid-template-columns:repeat(3,1fr);}} letter-spacing: .16em;
@media(max-width:480px){.partner-logo-grid--partners{grid-template-columns:repeat(2,1fr);}} text-transform: uppercase;
@keyframes orbitSpin { color: rgba(26,31,94,.38);
from { transform: rotate(0deg); } margin-bottom: 10px;
to { transform: rotate(360deg); } }
.partners-title {
font-size: 22px;
font-weight: 800;
color: #0a0f1e;
margin-bottom: 16px;
letter-spacing: -.03em;
}
.partners-divider {
height: 1px;
background: rgba(26,31,94,.08);
margin-bottom: 16px;
}
/* 고객사 8개 — 4열 */
.partners-grid-8 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
}
/* 협력사 15개 — 5열 */
.partners-grid-15 {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 8px;
}
.partner-card {
background: #fff;
border: 1px solid rgba(26,31,94,.09);
border-radius: 10px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: center;
height: 88px; /* 고정 높이 */
transition: border-color .2s, background .2s;
}
.partner-card:hover {
border-color: rgba(26,31,94,.2);
background: #fafbff;
}
.partner-card img {
width: 120px; /* 고정 너비 */
height: 48px; /* 고정 높이 */
object-fit: contain;
}
/* CTA */
.partners-cta {
background: var(--color-primary);
border-radius: 16px;
padding: 32px 40px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
flex-wrap: wrap;
}
.partners-cta-label {
font-size: 11px;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
color: rgba(255,255,255,.5);
margin-bottom: 8px;
}
.partners-cta-title {
font-size: clamp(18px, 2vw, 24px);
font-weight: 800;
color: #fff;
letter-spacing: -.03em;
line-height: 1.3;
}
.partners-cta-btn {
display: inline-flex;
align-items: center;
height: 48px;
padding: 0 28px;
background: #fff;
border-radius: 999px;
font-size: 14px;
font-weight: 700;
color: var(--color-primary);
text-decoration: none;
white-space: nowrap;
flex-shrink: 0;
transition: opacity .2s;
}
.partners-cta-btn:hover {
opacity: .88;
}
/* 반응형 */
@media (max-width: 1280px) {
.partners-wrap { padding: 48px 48px; }
}
@media (max-width: 1024px) {
.partners-wrap { padding: 40px 32px; }
.partners-grid-8 { grid-template-columns: repeat(4, 1fr); }
.partners-grid-15 { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 768px) {
.partners-wrap { padding: 32px 20px; }
.partners-grid-8 { grid-template-columns: repeat(2, 1fr); }
.partners-grid-15 { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 480px) {
.partners-grid-15 { grid-template-columns: repeat(2, 1fr); }
} }

288
src/pages/company/PartnersPage.jsx

@ -14,22 +14,18 @@ const COMPANY_NAV = [
{ label: "찾아오시는 길", to: "/company/location" }, { label: "찾아오시는 길", to: "/company/location" },
]; ];
const CX = 380;
const CY = 230;
const ORBIT_R = 165;
const CLIENTS = [ const CLIENTS = [
{ id: "airport", logo: "01", angle: -90 }, { id: "airport", logo: "01" },
{ id: "kac", logo: "09", angle: -45 }, { id: "kac", logo: "09" },
{ id: "mod", logo: "15", angle: 0 }, { id: "mod", logo: "15" },
{ id: "incheon", logo: "16", angle: 45 }, { id: "incheon", logo: "16" },
{ id: "jeju", logo: "20", angle: 90 }, { id: "jeju", logo: "20" },
{ id: "kiast", logo: "21", angle: 135 }, { id: "kiast", logo: "21" },
{ id: "kari", logo: "23", angle: 180 }, { id: "kari", logo: "23" },
{ id: "molit", logo: "24", angle: -135 }, { id: "molit", logo: "24" },
]; ];
const PARTNER_LOGOS = [ const PARTNERS = [
"02", "02",
"03", "03",
"04", "04",
@ -47,192 +43,26 @@ const PARTNER_LOGOS = [
"22", "22",
]; ];
function toRad(deg) { function ClientCard({ logo, basePath }) {
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);
const orbitRef = 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,
},
);
gsap.to(orbitRef.current, {
rotation: 360,
svgOrigin: `${CX} ${CY}`,
duration: 30,
ease: "none",
repeat: -1,
paused: true, //
scrollTrigger: {
trigger: svgRef.current,
start: "top 75%",
toggleActions: "play pause resume pause",
},
});
}, 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
ref={orbitRef}
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="#1e2d4f" opacity="1" />
<circle
cx={CX}
cy={CY}
r={44}
fill="none"
stroke="#3a4f7a"
strokeWidth="1.2"
/>
<text
x={CX}
y={CY - 9}
textAnchor="middle"
fill="#e8eaf0"
fontSize="12"
fontWeight="500"
letterSpacing="0.04em"
>
주요
</text>
<text
x={CX}
y={CY + 7}
textAnchor="middle"
fill="#e8eaf0"
fontSize="12"
fontWeight="500"
letterSpacing="0.04em"
>
고객사
</text>
<text
x={CX}
y={CY + 23}
textAnchor="middle"
fill="#8892b0"
fontSize="10"
fontWeight="500"
>
8 clients
</text>
</g>
{CLIENTS.map((c) => {
const pos = nodePos(c.angle);
const imgSrc = `${basePath}images/partner/banner${c.logo}.png`;
return ( return (
<g <div className="partner-card">
key={c.id} <img
className="hub-node" src={`${basePath}images/partner/banner${logo}.png`}
style={{ transformOrigin: `${pos.x}px ${pos.y}px` }} alt=""
> loading="lazy"
<circle
cx={pos.x}
cy={pos.y}
r={30}
fill="#f4f5f9"
stroke="#B8BCCF"
strokeWidth="1.5"
/> />
<image </div>
href={imgSrc}
x={pos.x - 22}
y={pos.y - 22}
width="44"
height="44"
preserveAspectRatio="xMidYMid meet"
/>
</g>
);
})}
</svg>
); );
} }
function LogoItem({ num, basePath }) { function PartnerCard({ logo, basePath }) {
const src = `${basePath}images/partner/banner${num}.png`;
return ( return (
<div className="partner-logo-card"> <div className="partner-card">
<img src={src} alt="" loading="lazy" /> <img
src={`${basePath}images/partner/banner${logo}.png`}
alt=""
loading="lazy"
/>
</div> </div>
); );
} }
@ -245,22 +75,16 @@ export default function PartnersPage() {
useEffect(() => { useEffect(() => {
const ctx = gsap.context(() => { const ctx = gsap.context(() => {
gsap.fromTo( gsap.fromTo(
".partner-logo-card", ".partner-card",
{ { opacity: 0, y: 20, scale: 0.97 },
opacity: 0,
y: 36,
scale: 0.96,
clipPath: "inset(0 100% 0 0 round 0px)",
},
{ {
opacity: 1, opacity: 1,
y: 0, y: 0,
scale: 1, scale: 1,
clipPath: "inset(0 0% 0 0 round 0px)", duration: 0.6,
duration: 0.85,
stagger: { each: 0.04, from: "start" }, stagger: { each: 0.04, from: "start" },
ease: "power3.out", ease: "power3.out",
scrollTrigger: { trigger: ".partner-logo-grid", start: "top 78%" }, scrollTrigger: { trigger: ".partners-wrap", start: "top 80%" },
}, },
); );
}, editorialRef); }, editorialRef);
@ -282,43 +106,45 @@ export default function PartnersPage() {
navItems={COMPANY_NAV} navItems={COMPANY_NAV}
/> />
<div className="partner-page" ref={editorialRef}> <div className="partners-wrap" ref={editorialRef}>
<div className="partner-editorial">
{/* 주요 고객사 */} {/* 주요 고객사 */}
<section className="partner-hub-section"> <section className="partners-section">
<div className="partner-section-heading"> <p className="partners-eyebrow">Clients</p>
<h2 className="partner-section-title"> <h2 className="partners-title">주요 고객사</h2>
함께 만들어온 <div className="partners-divider" />
<br /> <div className="partners-grid-8">
신뢰의 네트워크 {CLIENTS.map((c) => (
</h2> <ClientCard key={c.id} logo={c.logo} basePath={basePath} />
<p className="partner-section-sub"> ))}
공공·항공·국방 분야 핵심 기관과 오랜 협력 관계를 이어오고
있습니다
</p>
</div> </div>
<ClientHub basePath={basePath} />
</section> </section>
{/* 기술 협력사 */} {/* 기술 협력사 */}
<section className="partner-logo-section"> <section className="partners-section">
<div className="partner-logo-top"> <p className="partners-eyebrow">Partners</p>
<div> <h2 className="partners-title">기술 협력사</h2>
<span className="partner-logo-kicker">Partners</span> <div className="partners-divider" />
<h2 className="partner-section-title">기술 협력사</h2> <div className="partners-grid-15">
</div> {PARTNERS.map((num) => (
<div className="partner-logo-stat"> <PartnerCard key={num} logo={num} basePath={basePath} />
<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> </div>
</section> </section>
{/* CTA */}
<section className="partners-cta">
<div>
<p className="partners-cta-label">Become a Partner</p>
<h3 className="partners-cta-title">
팔네트웍스와 함께 성장할
<br />
파트너를 찾습니다
</h3>
</div> </div>
<a href="/contact/inquiry" className="partners-cta-btn">
협력 문의하기
</a>
</section>
</div> </div>
</article> </article>
); );

Loading…
Cancel
Save