Skip to content
Merged
Changes from 13 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
125 changes: 96 additions & 29 deletions components/shared/EnterpriseLogoGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import { Fragment } from "react";

Check warning on line 3 in components/shared/EnterpriseLogoGrid.tsx

View check run for this annotation

Claude / Claude Code Review

Unused Fragment import

Line 3 imports `Fragment` from "react" but it is never used — the file only uses anonymous fragments (`<>...</>`). This is dead code (likely leftover from copying the marquee pattern from `HeroStatsStrip.tsx`) and may trigger an unused-import lint warning. Safe to remove the import.
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
import Image from "next/image";
import type { StaticImageData } from "next/image";
import { motion } 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 +23,8 @@
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 +111,129 @@
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() {
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-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) => {
return (
<>
{/* Mobile: infinite scrolling marquee */}
<div
className={cn("sm:hidden overflow-hidden w-full mask-[linear-gradient(to_right,transparent,black_8%,black_92%,transparent)]", className)}
role="marquee"
aria-label="Enterprise customers using Langfuse"
>
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
<motion.div
className="flex items-center w-max py-2"
animate={{ x: ["0%", "-50%"] }}
transition={{
duration: MARQUEE_DURATION_SEC,
repeat: Infinity,
ease: "linear",
}}
>
<LogoMarqueeItems />
<div aria-hidden="true" className="contents">
<LogoMarqueeItems />
</div>

Check failure on line 202 in components/shared/EnterpriseLogoGrid.tsx

View check run for this annotation

Claude / Claude Code Review

Duplicate marquee links remain keyboard-focusable inside aria-hidden

The duplicate marquee copy is wrapped in `<div aria-hidden="true">` (lines 200-202), but `aria-hidden` only hides content from the accessibility tree — it does **not** remove descendants from keyboard tab order. The four logos with `customerStoryPath` (Canva, Khan Academy, SumUp, Merck) render real focusable `<a>` elements via `LinkBox`, so a keyboard user on mobile will tab through each story link twice, with no visual cue on the second pass — a WCAG 4.1.2 violation flagged by axe-core's `aria-
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
</motion.div>
</div>

{/* Desktop: grid layout */}
<div
className={cn(
"hidden sm:grid grid-cols-3 sm:grid-cols-6 auto-rows-fr px-2 py-2",
small && "sm:grid-cols-3",
className,
)}

Check warning on line 212 in components/shared/EnterpriseLogoGrid.tsx

View check run for this annotation

Claude / Claude Code Review

Dead grid-cols-3 class on desktop-only grid wrapper

Nit: the base `grid-cols-3` utility on the desktop grid wrapper is dead code. The wrapper is `hidden` below `sm` (so `display: grid` never applies there) and at `sm+` it is always overridden by `sm:grid-cols-6` (or `sm:grid-cols-3` when `small` is true). Suggest dropping it: `"hidden sm:grid sm:grid-cols-6 auto-rows-fr px-2 py-2"`.
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>
</>
);
};
Loading