Skip to content

Commit 558297d

Browse files
feat: Replace mobile logo grid with infinite scrolling marquee (#2875)
Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: felixkrrr <[email protected]>
1 parent 1b7a8bd commit 558297d

2 files changed

Lines changed: 108 additions & 30 deletions

File tree

components/shared/EnterpriseLogoGrid.tsx

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import Image from "next/image";
44
import type { StaticImageData } from "next/image";
5+
import { motion, useReducedMotion } from "framer-motion";
56
import adobeLogo from "../home/img/adobe.svg";
67
import canvaLogo from "../home/img/canva.svg";
78
import circlebackLogo from "../home/img/circleback.svg";
@@ -21,6 +22,8 @@ import twilioLogo from "../home/img/twilio.svg";
2122
import { cn } from "@/lib/utils";
2223
import { LinkBox } from "@/components/ui/link-box";
2324

25+
const MARQUEE_DURATION_SEC = 40;
26+
2427
type CompanyLogo = {
2528
name: string;
2629
logo: StaticImageData;
@@ -107,66 +110,141 @@ const companies: CompanyLogo[] = [
107110
const LogoImage = ({
108111
logo,
109112
name,
113+
compact = false,
110114
}: {
111115
logo: StaticImageData;
112116
name: string;
117+
compact?: boolean;
113118
}) => {
119+
if (compact) {
120+
return (
121+
<div className="overflow-hidden h-[40px] -mx-5 flex items-center">
122+
<Image
123+
src={logo}
124+
alt={`${name} logo`}
125+
className="h-[56px] w-auto scale-125 transition-[filter] duration-200 hover:filter-[grayscale(1)_brightness(0)_contrast(1.15)] group-hover:filter-[grayscale(1)_brightness(0)_contrast(1.15)]"
126+
sizes="(max-width: 768px) 30vw"
127+
priority={false}
128+
/>
129+
</div>
130+
);
131+
}
132+
114133
return (
115134
<Image
116135
src={logo}
117136
alt={`${name} logo`}
118-
className="object-cover max-w-full transition-[filter] duration-200 h-[56px] hover:filter-[grayscale(1)_brightness(0)_contrast(1.15)] group-hover:filter-[grayscale(1)_brightness(0)_contrast(1.15)]"
137+
className="h-[56px] object-cover max-w-full transition-[filter] duration-200 hover:filter-[grayscale(1)_brightness(0)_contrast(1.15)] group-hover:filter-[grayscale(1)_brightness(0)_contrast(1.15)]"
119138
sizes="(max-width: 768px) 50vw, (max-width: 1200px) 25vw, 20vw"
120139
priority={false}
121140
/>
122141
);
123142
};
124143

125-
interface EnterpriseLogoGridProps {
126-
className?: string;
127-
small?: boolean;
128-
}
144+
const visibleCompanies = companies.filter((c) => !c.hidden);
129145

130-
export const EnterpriseLogoGrid = ({
131-
className = "",
132-
small = false,
133-
}: EnterpriseLogoGridProps) => {
146+
function LogoMarqueeItems({ duplicate = false }: { duplicate?: boolean }) {
134147
return (
135-
<div
136-
className={cn(
137-
"grid grid-cols-3 sm:grid-cols-6 auto-rows-fr px-2 py-2",
138-
small && "sm:grid-cols-3",
139-
className,
140-
)}
141-
role="grid"
142-
aria-label="Enterprise customers using Langfuse"
143-
>
144-
{companies.filter((c) => !c.hidden).map((company, index) => {
148+
<>
149+
{visibleCompanies.map((company) => {
145150
const hasStory = Boolean(company.customerStoryPath);
146151
return (
147152
<LinkBox
148153
key={company.name}
149154
href={company.customerStoryPath}
150155
tooltip={hasStory ? "Read story" : undefined}
151156
tooltipPlacement="bottom-center"
152-
className={cn(
153-
"-mr-px -mb-px flex items-center justify-center !p-0",
154-
index > 5 ? "hidden sm:flex" : "flex",
155-
)}
156-
role="gridcell"
157+
className="shrink-0 flex items-center justify-center !p-0"
158+
aria-hidden={duplicate || undefined}
159+
tabIndex={duplicate ? -1 : undefined}
157160
aria-label={
158161
hasStory
159162
? `Read ${company.name} user story`
160163
: `${company.name} uses Langfuse`
161164
}
162165
>
163-
<LogoImage
164-
logo={company.logo}
165-
name={company.name}
166-
/>
166+
<LogoImage logo={company.logo} name={company.name} compact />
167167
</LinkBox>
168168
);
169169
})}
170-
</div>
170+
</>
171+
);
172+
}
173+
174+
interface EnterpriseLogoGridProps {
175+
className?: string;
176+
small?: boolean;
177+
}
178+
179+
export const EnterpriseLogoGrid = ({
180+
className = "",
181+
small = false,
182+
}: EnterpriseLogoGridProps) => {
183+
const shouldReduceMotion = useReducedMotion();
184+
185+
return (
186+
<>
187+
{/* Mobile: scrolling marquee or static scroll fallback */}
188+
{shouldReduceMotion ? (
189+
<div
190+
className={cn("sm:hidden overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", className)}
191+
aria-label="Enterprise customers using Langfuse"
192+
>
193+
<div className="flex items-center w-max py-2">
194+
<LogoMarqueeItems />
195+
</div>
196+
</div>
197+
) : (
198+
<div
199+
className={cn("sm:hidden overflow-hidden w-full mask-[linear-gradient(to_right,transparent,black_8%,black_92%,transparent)]", className)}
200+
aria-label="Enterprise customers using Langfuse"
201+
>
202+
<motion.div
203+
className="flex items-center w-max py-2"
204+
animate={{ x: ["0%", "-50%"] }}
205+
transition={{
206+
duration: MARQUEE_DURATION_SEC,
207+
repeat: Infinity,
208+
ease: "linear",
209+
}}
210+
>
211+
<LogoMarqueeItems />
212+
<LogoMarqueeItems duplicate />
213+
</motion.div>
214+
</div>
215+
)}
216+
217+
{/* Desktop: grid layout */}
218+
<div
219+
className={cn(
220+
"hidden sm:grid sm:grid-cols-6 auto-rows-fr px-2 py-2",
221+
small && "sm:grid-cols-3",
222+
className,
223+
)}
224+
role="grid"
225+
aria-label="Enterprise customers using Langfuse"
226+
>
227+
{visibleCompanies.map((company) => {
228+
const hasStory = Boolean(company.customerStoryPath);
229+
return (
230+
<LinkBox
231+
key={company.name}
232+
href={company.customerStoryPath}
233+
tooltip={hasStory ? "Read story" : undefined}
234+
tooltipPlacement="bottom-center"
235+
className="-mr-px -mb-px flex items-center justify-center !p-0"
236+
role="gridcell"
237+
aria-label={
238+
hasStory
239+
? `Read ${company.name} user story`
240+
: `${company.name} uses Langfuse`
241+
}
242+
>
243+
<LogoImage logo={company.logo} name={company.name} />
244+
</LinkBox>
245+
);
246+
})}
247+
</div>
248+
</>
171249
);
172250
};

tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)