Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
6ee548c
feat: replace mobile logo grid with infinite scrolling marquee
cursoragent Apr 27, 2026
a72775a
Merge branch 'main' into cursor/mobile-logo-marquee-780c
felixkrrr Apr 28, 2026
0a63e98
style: reduce spacing between logos in mobile marquee
cursoragent Apr 28, 2026
3df9f92
style: slow down mobile logo marquee (30s -> 40s)
cursoragent Apr 28, 2026
c35f7ec
style: larger logos and tighter spacing in mobile marquee
cursoragent Apr 28, 2026
2116395
style: crop logo whitespace in mobile marquee for tighter layout
cursoragent Apr 28, 2026
7b7f2c8
style: crop top/bottom whitespace and enlarge logos in mobile marquee
cursoragent Apr 28, 2026
c517faa
style: enlarge mobile marquee logos and container height
cursoragent Apr 28, 2026
9da92bc
style: use CSS scale transform to enlarge mobile marquee logos
cursoragent Apr 28, 2026
7134605
style: reduce logo size (scale 125%) and remove horizontal spacing
cursoragent Apr 28, 2026
cb25dfc
style: use negative margin on logo containers to eliminate whitespace…
cursoragent Apr 28, 2026
40e3058
Merge branch 'main' into cursor/mobile-logo-marquee-780c
felixkrrr Apr 28, 2026
7e5a18d
a11y: hide duplicate marquee content from screen readers
cursoragent Apr 29, 2026
5d96652
address PR review comments: inert, dead class, unused import
cursoragent Apr 29, 2026
c18b981
a11y: respect prefers-reduced-motion and drop incorrect marquee role
cursoragent Apr 29, 2026
7882223
a11y: fix inert blocking clicks + add reduced-motion fallback
cursoragent Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 107 additions & 29 deletions components/shared/EnterpriseLogoGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import Image from "next/image";
import type { StaticImageData } from "next/image";
import { motion, useReducedMotion } from "framer-motion";
import adobeLogo from "../home/img/adobe.svg";
import canvaLogo from "../home/img/canva.svg";
import circlebackLogo from "../home/img/circleback.svg";
Expand All @@ -21,6 +22,8 @@ import twilioLogo from "../home/img/twilio.svg";
import { cn } from "@/lib/utils";
import { LinkBox } from "@/components/ui/link-box";

const MARQUEE_DURATION_SEC = 40;

type CompanyLogo = {
name: string;
logo: StaticImageData;
Expand Down Expand Up @@ -107,66 +110,141 @@ const companies: CompanyLogo[] = [
const LogoImage = ({
logo,
name,
compact = false,
}: {
logo: StaticImageData;
name: string;
compact?: boolean;
}) => {
if (compact) {
return (
<div className="overflow-hidden h-[40px] -mx-5 flex items-center">
<Image
src={logo}
alt={`${name} logo`}
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)]"
sizes="(max-width: 768px) 30vw"
priority={false}
/>
</div>
);
}

return (
<Image
src={logo}
alt={`${name} logo`}
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)]"
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)]"
sizes="(max-width: 768px) 50vw, (max-width: 1200px) 25vw, 20vw"
priority={false}
/>
);
};

interface EnterpriseLogoGridProps {
className?: string;
small?: boolean;
}
const visibleCompanies = companies.filter((c) => !c.hidden);

export const EnterpriseLogoGrid = ({
className = "",
small = false,
}: EnterpriseLogoGridProps) => {
function LogoMarqueeItems({ duplicate = false }: { duplicate?: boolean }) {
return (
<div
className={cn(
"grid grid-cols-3 sm:grid-cols-6 auto-rows-fr px-2 py-2",
small && "sm:grid-cols-3",
className,
)}
role="grid"
aria-label="Enterprise customers using Langfuse"
>
{companies.filter((c) => !c.hidden).map((company, index) => {
<>
{visibleCompanies.map((company) => {
const hasStory = Boolean(company.customerStoryPath);
return (
<LinkBox
key={company.name}
href={company.customerStoryPath}
tooltip={hasStory ? "Read story" : undefined}
tooltipPlacement="bottom-center"
className={cn(
"-mr-px -mb-px flex items-center justify-center !p-0",
index > 5 ? "hidden sm:flex" : "flex",
)}
role="gridcell"
className="shrink-0 flex items-center justify-center !p-0"
aria-hidden={duplicate || undefined}
tabIndex={duplicate ? -1 : undefined}
aria-label={
hasStory
? `Read ${company.name} user story`
: `${company.name} uses Langfuse`
}
>
<LogoImage
logo={company.logo}
name={company.name}
/>
<LogoImage logo={company.logo} name={company.name} compact />
</LinkBox>
);
})}
</div>
</>
);
}

interface EnterpriseLogoGridProps {
className?: string;
small?: boolean;
}

export const EnterpriseLogoGrid = ({
className = "",
small = false,
}: EnterpriseLogoGridProps) => {
const shouldReduceMotion = useReducedMotion();

return (
<>
{/* Mobile: scrolling marquee or static scroll fallback */}
{shouldReduceMotion ? (
<div
className={cn("sm:hidden overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", className)}
aria-label="Enterprise customers using Langfuse"
>
<div className="flex items-center w-max py-2">
<LogoMarqueeItems />
</div>
</div>
) : (
<div
className={cn("sm:hidden overflow-hidden w-full mask-[linear-gradient(to_right,transparent,black_8%,black_92%,transparent)]", className)}
aria-label="Enterprise customers using Langfuse"
>
<motion.div
className="flex items-center w-max py-2"
animate={{ x: ["0%", "-50%"] }}
transition={{
duration: MARQUEE_DURATION_SEC,
repeat: Infinity,
ease: "linear",
}}
>
<LogoMarqueeItems />
<LogoMarqueeItems duplicate />
</motion.div>
</div>
)}

{/* Desktop: grid layout */}
<div
className={cn(
"hidden sm:grid sm:grid-cols-6 auto-rows-fr px-2 py-2",
small && "sm:grid-cols-3",
className,
)}
Comment thread
claude[bot] marked this conversation as resolved.
role="grid"
aria-label="Enterprise customers using Langfuse"
>
{visibleCompanies.map((company) => {
const hasStory = Boolean(company.customerStoryPath);
return (
<LinkBox
key={company.name}
href={company.customerStoryPath}
tooltip={hasStory ? "Read story" : undefined}
tooltipPlacement="bottom-center"
className="-mr-px -mb-px flex items-center justify-center !p-0"
role="gridcell"
aria-label={
hasStory
? `Read ${company.name} user story`
: `${company.name} uses Langfuse`
}
>
<LogoImage logo={company.logo} name={company.name} />
</LinkBox>
);
})}
</div>
</>
);
};
2 changes: 1 addition & 1 deletion tsconfig.tsbuildinfo

Large diffs are not rendered by default.

Loading