Skip to content

Commit 590db98

Browse files
authored
clean up supporters page (#38)
* clean up supporters page: match carousel style, add NVIDIA/AMD logos, brand highlighting * dedupe CompanyLogo and highlightBrand into shared quote-utils * add Prime Intellect quote and logo * use flex-wrap with justify-evenly for company names on narrow screens * move see more supporters link below quote area to prevent overlap on mobile * fix carousel height measurement by placing hidden container inside visible one * reduce mobile gap between intro section and chart tabs * show tab dropdown up to lg breakpoint instead of sm * fix carousel height: use CSS grid overlay instead of JS measurement
1 parent 74964f3 commit 590db98

8 files changed

Lines changed: 117 additions & 100 deletions

File tree

packages/app/public/logos/amd.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

packages/app/public/logos/prime-intellect.svg

Lines changed: 18 additions & 0 deletions
Loading

packages/app/src/components/page-content.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function ChartTabs({ initialTab }: { initialTab: string }) {
109109
return (
110110
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
111111
{/* Mobile: Dropdown */}
112-
<div className="sm:hidden mb-4">
112+
<div className="lg:hidden mb-4">
113113
<div className="w-full border-t-2 border-secondary dark:border-primary pb-6" />
114114
<Card>
115115
<div className="space-y-2">
@@ -149,7 +149,7 @@ function ChartTabs({ initialTab }: { initialTab: string }) {
149149
</div>
150150

151151
{/* Desktop: Tabs */}
152-
<TabsList data-testid="chart-section-tabs" className="hidden sm:flex mb-4">
152+
<TabsList data-testid="chart-section-tabs" className="hidden lg:flex mb-4">
153153
<TabsTrigger
154154
data-testid="tab-trigger-inference"
155155
data-ph-capture-attribute-tab="inference"
@@ -252,7 +252,7 @@ export function PageContent({ initialTab = 'inference' }: { initialTab?: string
252252
<ExportNudge />
253253
<UnofficialRunProvider>
254254
<main className="relative min-h-screen">
255-
<div className="container mx-auto px-4 lg:px-8 flex flex-col gap-16 lg:gap-4">
255+
<div className="container mx-auto px-4 lg:px-8 flex flex-col gap-6 lg:gap-4">
256256
<section>
257257
<Card data-testid="intro-section">
258258
<h2 className="text-lg font-semibold mb-2">

packages/app/src/components/quote-carousel.tsx

Lines changed: 24 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useCallback, useEffect, useRef, useState } from 'react';
44

55
import { track } from '@/lib/analytics';
6+
import { CompanyLogo, highlightBrand } from '@/components/quotes/quote-utils';
67

78
export interface CarouselQuote {
89
text: string;
@@ -63,48 +64,14 @@ function buildCompanyQuotes(quotes: CarouselQuote[], order?: string[]): CompanyE
6364
return shuffleArray(entries);
6465
}
6566

66-
function CompanyLogo({ quote }: { quote: CarouselQuote }) {
67-
const [failed, setFailed] = useState(false);
68-
69-
if (!quote.logo || failed) {
70-
return (
71-
<div className="h-12 shrink-0 rounded-full bg-muted flex items-center justify-center px-3">
72-
<span className="text-xs font-bold text-muted-foreground">{quote.company[0]}</span>
73-
</div>
74-
);
75-
}
76-
77-
return (
78-
<img
79-
src={`/logos/${quote.logo}`}
80-
alt={quote.company}
81-
className="h-10 min-w-10 max-w-20 shrink-0 object-contain grayscale opacity-70 dark:invert"
82-
onError={() => setFailed(true)}
83-
/>
84-
);
85-
}
86-
87-
function highlightBrand(text: string) {
88-
const parts = text.split(/(InferenceMAX?|InferenceX?|InferenceMAX|InferenceX)/gi);
89-
return parts.map((part, i) =>
90-
/^inference(max|x)/i.test(part) ? (
91-
<span key={i} className="text-secondary dark:text-primary font-semibold">
92-
{part}
93-
</span>
94-
) : (
95-
part
96-
),
97-
);
98-
}
99-
10067
function QuoteBlock({ quote }: { quote: CarouselQuote }) {
10168
return (
10269
<blockquote className="w-full">
10370
<p className="text-sm lg:text-base leading-relaxed text-muted-foreground italic">
10471
&ldquo;{highlightBrand(quote.text)}&rdquo;
10572
</p>
10673
<footer className="mt-3 flex items-center gap-3">
107-
<CompanyLogo quote={quote} />
74+
<CompanyLogo company={quote.company} logo={quote.logo} />
10875
<div className="h-12 w-0.5 bg-secondary dark:bg-primary" />
10976
<div className="text-sm">
11077
<span className="font-semibold text-foreground">{quote.name}</span>
@@ -126,34 +93,13 @@ export function QuoteCarousel({
12693
const [entries, setEntries] = useState<CompanyEntry[]>([]);
12794
const [activeIndex, setActiveIndex] = useState(0);
12895
const [fading, setFading] = useState(false);
129-
const [measuredHeight, setMeasuredHeight] = useState(0);
130-
const measureRef = useRef<HTMLDivElement>(null);
13196
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
13297

13398
// Build shuffled company order on mount (client only)
13499
useEffect(() => {
135100
setEntries(buildCompanyQuotes(quotes, order));
136101
}, [quotes, order]);
137102

138-
// Measure tallest quote and update on resize
139-
useEffect(() => {
140-
const el = measureRef.current;
141-
if (!el) return;
142-
const measure = () => {
143-
const children = el.children;
144-
let max = 0;
145-
for (let i = 0; i < children.length; i++) {
146-
const h = (children[i] as HTMLElement).offsetHeight;
147-
if (h > max) max = h;
148-
}
149-
setMeasuredHeight(max);
150-
};
151-
measure();
152-
const observer = new ResizeObserver(measure);
153-
observer.observe(el);
154-
return () => observer.disconnect();
155-
}, [entries.length]);
156-
157103
const advance = useCallback(() => {
158104
setFading(true);
159105
setTimeout(() => {
@@ -187,26 +133,10 @@ export function QuoteCarousel({
187133

188134
if (entries.length === 0) return null;
189135

190-
const current = entries[activeIndex];
191-
192136
return (
193137
<div className="flex flex-col gap-4">
194-
{/* Hidden measurement container — renders all quotes to find tallest */}
195-
<div
196-
ref={measureRef}
197-
aria-hidden
198-
className="absolute left-0 right-0 overflow-hidden pointer-events-none"
199-
style={{ visibility: 'hidden', position: 'absolute', zIndex: -1 }}
200-
>
201-
{entries.map((e) => (
202-
<div key={e.company}>
203-
<QuoteBlock quote={e.quote} />
204-
</div>
205-
))}
206-
</div>
207-
208-
{/* Company logo strip — same shuffled order as cycling */}
209-
<div className="flex flex-wrap items-center justify-between gap-y-2 mx-4">
138+
{/* Company logo strip */}
139+
<div className="flex flex-wrap items-center justify-evenly gap-x-4 gap-y-2 mx-4">
210140
{entries.map((e, i) => (
211141
<button
212142
key={e.company}
@@ -223,22 +153,31 @@ export function QuoteCarousel({
223153
))}
224154
</div>
225155

226-
{/* Visible quote — fixed height from measurement */}
227-
<div className="relative flex items-start" style={{ minHeight: measuredHeight || undefined }}>
228-
<div
229-
className={`w-full transition-opacity duration-300 ease-in-out ${fading ? 'opacity-0' : 'opacity-100'}`}
230-
>
231-
<QuoteBlock quote={current.quote} />
232-
</div>
233-
{moreHref && (
156+
{/* All quotes stacked in same grid cell — tallest sets height */}
157+
<div className="grid">
158+
{entries.map((e, i) => (
159+
<div
160+
key={e.company}
161+
className={`col-start-1 row-start-1 transition-opacity duration-300 ease-in-out ${
162+
i === activeIndex && !fading ? 'opacity-100' : 'opacity-0'
163+
}`}
164+
aria-hidden={i !== activeIndex}
165+
>
166+
<QuoteBlock quote={e.quote} />
167+
</div>
168+
))}
169+
</div>
170+
171+
{moreHref && (
172+
<div className="flex justify-end">
234173
<a
235174
href={moreHref}
236-
className="absolute right-0 bottom-0 text-xs font-bold text-secondary dark:text-primary hover:underline transition-opacity"
175+
className="text-xs font-bold text-secondary dark:text-primary hover:underline"
237176
>
238177
See more supporters &rarr;
239178
</a>
240-
)}
241-
</div>
179+
</div>
180+
)}
242181
</div>
243182
);
244183
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
5+
export function CompanyLogo({ company, logo }: { company: string; logo?: string }) {
6+
const [failed, setFailed] = useState(false);
7+
8+
if (!logo || failed) {
9+
return (
10+
<div className="h-12 shrink-0 rounded-full bg-muted flex items-center justify-center px-3">
11+
<span className="text-xs font-bold text-muted-foreground">{company[0]}</span>
12+
</div>
13+
);
14+
}
15+
16+
return (
17+
<img
18+
src={`/logos/${logo}`}
19+
alt={company}
20+
className="h-10 min-w-10 max-w-20 shrink-0 object-contain grayscale opacity-70 dark:invert"
21+
onError={() => setFailed(true)}
22+
/>
23+
);
24+
}
25+
26+
export function highlightBrand(text: string) {
27+
const parts = text.split(/(InferenceMAX?|InferenceX?|InferenceMAX|InferenceX)/gi);
28+
return parts.map((part, i) =>
29+
/^inference(max|x)/i.test(part) ? (
30+
<span key={i} className="text-secondary dark:text-primary font-semibold">
31+
{part}
32+
</span>
33+
) : (
34+
part
35+
),
36+
);
37+
}

packages/app/src/components/quotes/quotes-content.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,38 @@
22

33
import { Card } from '@/components/ui/card';
44

5+
import { CompanyLogo, highlightBrand } from './quote-utils';
56
import { QUOTES } from './quotes-data';
67

78
function QuoteCard({
89
text,
910
name,
1011
title,
12+
company,
13+
logo,
1114
link,
1215
}: {
1316
text: string;
1417
name: string;
1518
title: string;
19+
company: string;
20+
logo?: string;
1621
link?: string;
1722
}) {
1823
const content = (
19-
<Card className="p-6 lg:p-8">
20-
<blockquote className="space-y-4">
21-
<p className="text-base lg:text-lg leading-relaxed text-foreground/90 italic">
22-
&ldquo;{text}&rdquo;
23-
</p>
24-
<footer className="text-sm text-muted-foreground pl-6">
25-
<span className="font-semibold text-foreground not-italic">&ndash; {name}</span>
26-
<span className="block mt-0.5">{title}</span>
27-
</footer>
28-
</blockquote>
29-
</Card>
24+
<blockquote className="space-y-4">
25+
<p className="text-base lg:text-lg leading-relaxed text-muted-foreground italic">
26+
&ldquo;{highlightBrand(text)}&rdquo;
27+
</p>
28+
<footer className="flex items-center gap-3">
29+
<CompanyLogo company={company} logo={logo} />
30+
<div className="h-12 w-0.5 bg-secondary dark:bg-primary" />
31+
<div className="text-sm">
32+
<span className="font-semibold text-foreground">{name}</span>
33+
<span className="block text-muted-foreground text-xs">{title}</span>
34+
</div>
35+
</footer>
36+
</blockquote>
3037
);
3138

3239
if (link) {
@@ -61,13 +68,15 @@ export function QuotesContent() {
6168
</p>
6269
</Card>
6370
<Card>
64-
<div className="flex flex-col gap-3 md:pl-6">
71+
<div className="flex flex-col gap-10 md:gap-12 mx-4 md:mx-8">
6572
{QUOTES.map((quote) => (
6673
<QuoteCard
6774
key={quote.name}
6875
text={quote.text}
6976
name={quote.name}
7077
title={quote.title}
78+
company={quote.company}
79+
logo={quote.logo}
7180
link={quote.link}
7281
/>
7382
))}

packages/app/src/components/quotes/quotes-data.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,35 @@ export const QUOTES: Quote[] = [
2020
name: 'Dr. Lisa Su',
2121
title: 'Chair and CEO, AMD',
2222
company: 'AMD',
23+
logo: 'amd.svg',
2324
},
2425
{
2526
text: "Inference demand is growing exponentially, driven by long-context reasoning. NVIDIA Grace Blackwell NVL72 was invented for this new era of thinking AI. NVIDIA is meeting that demand through constant hardware and software innovation to enable what's next in AI. By benchmarking frequently, InferenceMAX\u2122 gives the industry a transparent view of LLM inference performance on real-world workloads. The results are clear: Grace Blackwell NVL72 with TRT-LLM and Dynamo delivers unmatched performance per dollar and per megawatt\u2014powering the most productive and cost-effective AI factories in the world.",
2627
name: 'Jensen Huang',
2728
title: 'Founder & CEO, NVIDIA',
2829
company: 'NVIDIA',
30+
logo: 'nvidia.svg',
2931
},
3032
{
3133
text: "Speed is the moat. InferenceMAX\u2122's nightly benchmarks match the speed of improvement of the AMD software stack. It's fantastic to see AMD's MI300, MI325, and MI355 GPUs performing so well across diverse workloads and interactivity levels.",
3234
name: 'Anush Elangovan',
3335
title: 'VP GPU Software, AMD',
3436
company: 'AMD',
37+
logo: 'amd.svg',
3538
},
3639
{
3740
text: 'InferenceMAX\u2122 highlights workloads that the ML community cares about. At NVIDIA, we welcome these comparisons because they underscore the advantage of our full-stack approach\u2014from GPUs hardware to NVLink networking to NVL72 Rack Scale to Dynamo disaggregated serving that consistently delivers industry-leading inference performance and ROI at scale.',
3841
name: 'Ian Buck',
3942
title: 'VP & GM, Hyperscale, NVIDIA & Inventor of CUDA',
4043
company: 'NVIDIA',
44+
logo: 'nvidia.svg',
4145
},
4246
{
4347
text: "InferenceMAX\u2122's nightly results highlight the rapid pace of progress in the AMD software stack. It's exciting to witness the birth of an open project that provides a tied feedback loop between what the software team works on here at AMD and how it affects specific ML use cases across our MI300, MI325, and MI355 GPUs. I'm looking forward to see what's next for InferenceMAX and to showcase what the AMD platform can do. AMD GPUs will continue to get faster every week.",
4448
name: 'Quentin Colombet',
4549
title: 'Senior Director, AMD, Ex-Brium CEO',
4650
company: 'AMD',
51+
logo: 'amd.svg',
4752
},
4853
{
4954
text: "Our mission at Azure is to give customers the most performant, efficient, and cost-effective cloud for AI. SemiAnalysis InferenceMAX\u2122 supports that mission by providing transparent, reproducible benchmarks that track inference performance across GPUs and software stacks under realistic workloads. This continuous data on throughput, efficiency, and cost per watt strengthens our ability to tune Azure's inference platform for scale, helping customers build with confidence on Microsoft Cloud.",
@@ -151,4 +156,11 @@ export const QUOTES: Quote[] = [
151156
company: 'Vultr',
152157
logo: 'vultr.svg',
153158
},
159+
{
160+
text: "At Prime Intellect, we're pushing the frontier of AI post-training and open research. InferenceX\u2122 complements that work by providing open, reproducible benchmarks that track real-world inference performance across hardware and software stacks as they evolve. For researchers like us, having transparent, continuously updated data on throughput and efficiency means we can focus on building better models instead of second-guessing infrastructure. This is the kind of community-driven effort that accelerates progress for everyone.",
161+
name: 'Jack Min Ong',
162+
title: 'Researcher, Prime Intellect',
163+
company: 'Prime Intellect',
164+
logo: 'prime-intellect.svg',
165+
},
154166
];

0 commit comments

Comments
 (0)