Skip to content

Commit 1c7aa56

Browse files
authored
Merge pull request #92 from UoaWDCC/feat--about-us-sponsor-carousel
feat: about us page sponsor carousel
2 parents d1dc6fa + 23a407c commit 1c7aa56

File tree

6 files changed

+205
-146
lines changed

6 files changed

+205
-146
lines changed

src/app/(frontend)/aboutus/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import Hero from "@components/about/Hero";
2-
import CardsSection from "@components/about/CardsSection";
1+
import Hero from "@components/aboutus/Hero";
2+
import CardsSection from "@components/aboutus/CardsSection";
33

44
import { getAboutUs } from "@/actions/pageActions";
55

src/app/(frontend)/components/about/Card.tsx

Lines changed: 0 additions & 115 deletions
This file was deleted.
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"use client";
2+
3+
import React from "react";
4+
import Image from "next/image";
5+
6+
import { Media } from "@/payload-types";
7+
import { Button } from "../ui/button";
8+
9+
import { motion, useTransform, useScroll, useSpring } from "framer-motion";
10+
import parallaxConfig from "@/config/parallax";
11+
12+
export type CardProps = {
13+
icon: React.ReactNode;
14+
background: string | null;
15+
alt: string;
16+
title: string;
17+
summary: string;
18+
description?: string;
19+
link?: string | undefined;
20+
sponsorLogos?: { logo?: Media | string | null }[] | null;
21+
};
22+
23+
const ScrollingLogos = ({ logos }: { logos: { logo?: Media | string | null }[] }) => {
24+
const containerRef = React.useRef<HTMLDivElement>(null);
25+
const [duplicateCount, setDuplicateCount] = React.useState(2);
26+
const validLogos = logos.filter((item) => typeof item.logo === "object" && item.logo?.url); // Filter out invalid logos
27+
28+
// Calculate how many times to duplicate logos based on container width
29+
React.useEffect(() => {
30+
if (!containerRef.current || validLogos.length === 0) return;
31+
32+
const containerWidth = containerRef.current.offsetWidth;
33+
const itemWidth = 88; // 64px logo + 24px gap
34+
const totalLogosWidth = itemWidth * validLogos.length;
35+
36+
// Duplicate enough times to fill at least 2x the container width
37+
const needed = Math.ceil((containerWidth * 2) / totalLogosWidth);
38+
setDuplicateCount(Math.max(2, needed));
39+
}, [validLogos.length]);
40+
41+
if (validLogos.length === 0) return null;
42+
43+
const duplicatedLogos = Array(duplicateCount).fill(validLogos).flat();
44+
const duration = 40 + duplicatedLogos.length * 0.3; // Seconds to complete one full loop - increase/decrease the base number to speed up/slow down one loop
45+
46+
const LogoSet = ({ keyPrefix }: { keyPrefix: string }) => (
47+
<motion.div
48+
className="flex h-full shrink-0 items-center gap-23 pr-23"
49+
animate={{ x: [0, "-100%"] }}
50+
transition={{
51+
x: {
52+
repeat: Infinity,
53+
repeatType: "loop",
54+
duration: duration,
55+
ease: "linear",
56+
},
57+
}}
58+
>
59+
{duplicatedLogos.map((item, index) => {
60+
const logo = item.logo as Media;
61+
return (
62+
<div
63+
key={`${keyPrefix}-${index}`}
64+
className="relative h-full shrink-0"
65+
style={{ aspectRatio: `${logo.width || 1} / ${logo.height || 1}` }}
66+
>
67+
<Image
68+
src={logo.url!}
69+
alt={logo.alt || `sponsor ${(index % validLogos.length) + 1}`}
70+
fill
71+
className="rounded-md object-contain"
72+
quality={90}
73+
/>
74+
</div>
75+
);
76+
})}
77+
</motion.div>
78+
);
79+
80+
return (
81+
<div ref={containerRef} className="relative h-full w-full overflow-hidden">
82+
<div className="flex h-full">
83+
<LogoSet keyPrefix="first" />
84+
<LogoSet keyPrefix="second" />
85+
</div>
86+
87+
<div className="pointer-events-none absolute inset-y-0 left-0 w-8 bg-linear-to-r from-(--lightblue) to-transparent" />
88+
<div className="pointer-events-none absolute inset-y-0 right-0 w-8 bg-linear-to-l from-(--lightblue) to-transparent" />
89+
</div>
90+
);
91+
};
92+
93+
const Card = ({
94+
icon,
95+
background,
96+
alt,
97+
title,
98+
summary,
99+
description,
100+
link,
101+
sponsorLogos,
102+
}: CardProps) => {
103+
const isLinked = link ? link.trim() !== "" : false;
104+
const isSponsored = sponsorLogos && sponsorLogos.length > 0;
105+
106+
const { rangeIn, rangeOut, spring } = parallaxConfig;
107+
const { scrollY } = useScroll();
108+
// image parallax effect scroll speed
109+
const rawY = useTransform(scrollY, [0, rangeIn], [0, rangeOut]);
110+
// smooth motion
111+
const y = useSpring(rawY, spring);
112+
113+
return (
114+
<div className="group relative block h-[400px] w-full overflow-hidden rounded-lg px-18 py-18 text-(--lightblue)">
115+
{/* On Display: Background Image */}
116+
<motion.div
117+
className="absolute inset-x-0 -inset-y-[20%] z-0 will-change-transform"
118+
style={{ y }}
119+
>
120+
<Image
121+
src={background!}
122+
alt={alt}
123+
fill
124+
priority
125+
quality={90}
126+
sizes="(max-width: 768px) 110vw, 55vw"
127+
className="object-cover object-center"
128+
/>
129+
</motion.div>
130+
131+
{/* On Display: Content */}
132+
<div className="relative z-10 flex h-full flex-col items-center text-center">
133+
<div className="flex h-1/2 w-full flex-col items-center justify-between">
134+
<div className="flex justify-center">{icon}</div>
135+
136+
<h1 className="m-0! text-4xl! font-semibold!">{title}</h1>
137+
</div>
138+
139+
<div className="mt-4 flex h-1/2 w-full items-start">
140+
<p className="text-base">{summary}</p>
141+
</div>
142+
</div>
143+
144+
{/* On Hover: Background Colour */}
145+
<div className="absolute inset-0 z-10 transition-colors duration-500 group-hover:bg-(--navy)"></div>
146+
147+
{/* On Hover: Content */}
148+
<div className="absolute inset-0 z-20 flex flex-col items-center justify-start px-18 py-18 text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100">
149+
<div className="mb-4 flex justify-center">{icon}</div>
150+
151+
{isSponsored && (
152+
<div className="relative mb-4 flex h-24 w-full items-center overflow-hidden rounded-md bg-(--lightblue) py-3">
153+
{/* Visible Content */}
154+
<ScrollingLogos logos={sponsorLogos!} />
155+
</div>
156+
)}
157+
158+
{isLinked ? (
159+
<Button variant="link" asChild className="mt-7">
160+
<a href={link} target="_blank" rel="noopener noreferrer">
161+
<h1 className="m-0! text-3xl! font-semibold!">{description}</h1>
162+
</a>
163+
</Button>
164+
) : (
165+
<p className="text-center text-base">{description}</p>
166+
)}
167+
</div>
168+
</div>
169+
);
170+
};
171+
172+
export default Card;

src/app/(frontend)/components/about/CardsSection.tsx renamed to src/app/(frontend)/components/aboutus/CardsSection.tsx

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,33 +35,37 @@ const CardsSection = ({ content }: CardsSectionProps) => {
3535
};
3636

3737
return (
38-
<section className="flex flex-col items-center w-full gap-6">
39-
{/* First Row */}
40-
<div className="flex flex-row justify-between items-center gap-6">
41-
<Card
42-
icon={<Eye className="h-12 w-12" />}
43-
background={getImageUrl(content.vision.background)}
44-
alt={getImageAlt(content.vision.background, "Vision Background")}
45-
title={content.vision.title}
46-
summary={content.vision.summary}
47-
description={content.vision.description ?? "Vision Description"}
48-
link={""}
49-
/>
38+
<section className="flex w-full flex-col items-center gap-6">
39+
{/* First Row: Vision & Story */}
40+
<div className="flex flex-row items-center justify-between gap-6">
41+
<div className="basis-13/21">
42+
<Card
43+
icon={<Eye className="h-12 w-12" />}
44+
background={getImageUrl(content.vision.background)}
45+
alt={getImageAlt(content.vision.background, "Vision Background")}
46+
title={content.vision.title}
47+
summary={content.vision.summary}
48+
description={content.vision.description}
49+
link={""}
50+
/>
51+
</div>
5052

51-
<Card
52-
icon={<History className="h-12 w-12" />}
53-
background={getImageUrl(content.story.background)}
54-
alt={getImageAlt(content.story.background, "Story Background")}
55-
title={content.story.title}
56-
summary={content.story.summary}
57-
description={`View ${content.story.title}`}
58-
link={"https://ausco.wdcc.co.nz/ourstory"}
59-
/>
53+
<div className="basis-8/20">
54+
<Card
55+
icon={<History className="h-12 w-12" />}
56+
background={getImageUrl(content.story.background)}
57+
alt={getImageAlt(content.story.background, "Story Background")}
58+
title={content.story.title}
59+
summary={content.story.summary}
60+
description={`View ${content.story.title}`}
61+
link={"https://ausco.wdcc.co.nz/ourstory"}
62+
/>
63+
</div>
6064
</div>
6165

62-
{/* Second Row */}
63-
<div className="flex flex-row justify-between items-center gap-6">
64-
<div className="basis-2/5">
66+
{/* Second Row: Constitution & Sponsors/Partnerships */}
67+
<div className="flex flex-row items-center justify-between gap-6">
68+
<div className="basis-8/20">
6569
<Card
6670
icon={<BookText className="h-12 w-12" />}
6771
background={getImageUrl(content.constitution.background)}
@@ -75,7 +79,7 @@ const CardsSection = ({ content }: CardsSectionProps) => {
7579
/>
7680
</div>
7781

78-
<div className="basis-3/5">
82+
<div className="basis-13/21">
7983
<Card
8084
icon={<Handshake className="h-12 w-12" />}
8185
background={getImageUrl(content.sponsorsAndPartnerships.background)}
@@ -85,9 +89,7 @@ const CardsSection = ({ content }: CardsSectionProps) => {
8589
)}
8690
title={content.sponsorsAndPartnerships.title}
8791
summary={content.sponsorsAndPartnerships.summary}
88-
description={
89-
content.sponsorsAndPartnerships.description ?? "Sponsors & Partnerships Description"
90-
}
92+
description={content.sponsorsAndPartnerships.description}
9193
link={""}
9294
sponsorLogos={content.sponsorsAndPartnerships.sponsorLogos}
9395
/>
File renamed without changes.

src/app/(frontend)/ourstory/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default async function OurStoryPage() {
1010

1111
return (
1212
<section className="bg-(--cream)">
13-
<div className="max-w-6xl mx-auto pt-44 pb-8 sm:pb-12 md:pb-16 px-6 flex flex-col items-center">
13+
<div className="mx-auto flex max-w-6xl flex-col items-center px-6 pt-44 pb-8 sm:pb-12 md:pb-16">
1414
<Header description={content.description} />
1515
<Timeline>
1616
<Establishment content={content.establishment} />

0 commit comments

Comments
 (0)