Skip to content

Commit e0ebb83

Browse files
feat(compare): show hardware vendor logos beside each chip on /compare
Render vendor marks beside each hardware label in the /compare pair cards (en + zh). NVIDIA uses its official full-color green mark (#76B900, new nvidia-color.svg); AMD's brand guidelines only allow the standard black or reversed-white logo, so amd.svg joins the shared monochrome dark-invert set, which reproduces the official reversed treatment in dark mode. Vendors without a logo mapping (e.g. Teacup) render no mark. 中文:在 /compare 对比卡片中的每个硬件名称旁展示厂商 logo(中英文页面)。 NVIDIA 使用官方全彩绿色标志(#76B900,新增 nvidia-color.svg);AMD 品牌 规范仅允许标准黑色或反白 logo,因此 amd.svg 加入共享的单色暗色反转集合, 在暗色模式下呈现官方反白效果。无 logo 映射的厂商不渲染标志。
1 parent 08aa25d commit e0ebb83

8 files changed

Lines changed: 125 additions & 3 deletions

File tree

‎packages/app/cypress/e2e/compare-precision.cy.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ describe('Compare precision index page', () => {
4242
.should('contain.text', '8K→1K')
4343
.and('have.attr', 'href')
4444
.and('match', /\/8k-1k$/u);
45+
// Vendor logos render beside each hardware label in the pair cards:
46+
// NVIDIA uses the full-color green mark, AMD its monochrome brand mark.
47+
cy.get('a[data-scenario] img[src="/logos/nvidia-color.svg"]').should('exist');
48+
cy.get('a[data-scenario] img[src="/logos/amd.svg"]').should('exist');
4549
});
4650

4751
it('ships the same AgentX-first hierarchy on the Simplified Chinese page', () => {
@@ -67,6 +71,8 @@ describe('Compare precision index page', () => {
6771
.first()
6872
.should('have.attr', 'href')
6973
.and('match', /^\/zh\/compare\/.+\/agentic$/u);
74+
cy.get('a[data-scenario] img[src="/logos/nvidia-color.svg"]').should('exist');
75+
cy.get('a[data-scenario] img[src="/logos/amd.svg"]').should('exist');
7076
});
7177

7278
it('renders the /compare-per-dollar index with precision and spec-decode CTA links', () => {
Lines changed: 1 addition & 0 deletions
Loading

‎packages/app/src/app/compare/page.tsx‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,14 @@ export default async function CompareIndexPage() {
179179
label={label}
180180
archLine={archLine}
181181
scenarioLabel={scenario.label}
182+
hardwareA={{
183+
label: aMeta?.label ?? a.toUpperCase(),
184+
vendor: aMeta?.vendor,
185+
}}
186+
hardwareB={{
187+
label: bMeta?.label ?? b.toUpperCase(),
188+
vendor: bMeta?.vendor,
189+
}}
182190
/>
183191
);
184192
})}

‎packages/app/src/app/zh/compare/page.tsx‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ export default async function CompareIndexPageZh() {
174174
label={label}
175175
archLine={archLine}
176176
scenarioLabel={scenario.label}
177+
hardwareA={{
178+
label: aMeta?.label ?? a.toUpperCase(),
179+
vendor: aMeta?.vendor,
180+
}}
181+
hardwareB={{
182+
label: bMeta?.label ?? b.toUpperCase(),
183+
vendor: bMeta?.vendor,
184+
}}
177185
/>
178186
);
179187
})}

‎packages/app/src/components/compare/compare-pair-card-link.tsx‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,24 @@ import { ArrowRight } from 'lucide-react';
44

55
import { track } from '@/lib/analytics';
66

7+
import { HwVendorLogo } from '@/components/ui/hw-vendor-logo';
8+
9+
/** One side of the "A vs B" pair: display label plus vendor for the logo. */
10+
interface PairHardware {
11+
label: string;
12+
vendor?: string;
13+
}
14+
715
interface ComparePairCardLinkProps {
816
href: string;
917
slug: string;
1018
label: string;
1119
archLine: string;
1220
scenarioLabel?: 'AgentX' | '8K→1K';
21+
/** When both sides are provided, the title renders each hardware label with
22+
* its vendor logo beside it instead of the plain `label` string. */
23+
hardwareA?: PairHardware;
24+
hardwareB?: PairHardware;
1325
}
1426

1527
export function ComparePairCardLink({
@@ -18,6 +30,8 @@ export function ComparePairCardLink({
1830
label,
1931
archLine,
2032
scenarioLabel,
33+
hardwareA,
34+
hardwareB,
2135
}: ComparePairCardLinkProps) {
2236
return (
2337
<a
@@ -40,7 +54,21 @@ export function ComparePairCardLink({
4054
<div className="min-w-0 flex-1">
4155
<div className="flex flex-wrap items-center gap-2">
4256
<h3 className="font-semibold text-sm leading-tight group-hover:text-brand transition-colors duration-200">
43-
{label}
57+
{hardwareA && hardwareB ? (
58+
<>
59+
<span className="inline-flex items-center gap-1.5">
60+
<HwVendorLogo vendor={hardwareA.vendor} />
61+
{hardwareA.label}
62+
</span>{' '}
63+
<span className="font-normal text-muted-foreground">vs</span>{' '}
64+
<span className="inline-flex items-center gap-1.5">
65+
<HwVendorLogo vendor={hardwareB.vendor} />
66+
{hardwareB.label}
67+
</span>
68+
</>
69+
) : (
70+
label
71+
)}
4472
</h3>
4573
{scenarioLabel && (
4674
<span className="inline-flex min-h-5 items-center rounded-full border border-brand/30 bg-brand/10 px-2 font-mono text-[10px] font-semibold leading-none text-brand">
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
5+
import { isMonochromeLogo } from '@/lib/model-logos';
6+
import { getHwVendorLogo } from '@/lib/vendor-logos';
7+
import { cn } from '@/lib/utils';
8+
9+
/**
10+
* Small inline hardware-vendor mark, rendered beside a GPU label (e.g. in the
11+
* `/compare` pair cards). Resolves the asset via `getHwVendorLogo` and renders
12+
* nothing when the vendor has no configured logo or the asset fails to load,
13+
* so surrounding text never gains a broken-image box.
14+
*
15+
* Full-color marks (NVIDIA green) render as-is in both themes; monochrome
16+
* marks (AMD — black / reversed-white only per brand guidelines) get the
17+
* shared dark-mode invert. The image is decorative — the hardware label is
18+
* the adjacent text — so it is hidden from assistive technology.
19+
*/
20+
export function HwVendorLogo({ vendor, className }: { vendor?: string; className?: string }) {
21+
// Track WHICH asset failed rather than a boolean, mirroring ModelLogo: a
22+
// reused instance should not suppress a different vendor's logo after one
23+
// load error.
24+
const [failedSrc, setFailedSrc] = useState<string | null>(null);
25+
const logo = getHwVendorLogo(vendor);
26+
27+
if (!logo || failedSrc === logo) return null;
28+
29+
return (
30+
<img
31+
src={`/logos/${logo}`}
32+
alt=""
33+
aria-hidden="true"
34+
className={cn(
35+
// Height-bounded with natural aspect ratio: the NVIDIA eye mark is
36+
// square-ish while the AMD lockup is a wide wordmark; max-w keeps the
37+
// wordmark from crowding the card title.
38+
'inline-block h-4 w-auto max-w-12 shrink-0 object-contain align-text-bottom',
39+
isMonochromeLogo(logo) && 'dark:invert',
40+
className,
41+
)}
42+
onError={() => setFailedSrc(logo)}
43+
/>
44+
);
45+
}

‎packages/app/src/lib/model-logos.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
* Logo files under `public/logos/` whose brand mark is plain black
33
* (`currentColor` SVGs): they need a dark-mode invert to stay visible.
44
* Full-color logos render as-is in both themes. Shared by every surface that
5-
* renders model/developer logos (`/model` pages, inference chart captions).
5+
* renders model/developer or hardware-vendor logos (`/model` pages, inference
6+
* chart captions, `/compare` pair cards).
7+
*
8+
* `amd.svg` is here because AMD's brand guidelines only permit the standard
9+
* black logo or the reversed-out white logo — no color variant exists, so the
10+
* dark-mode invert reproduces the official reversed treatment.
611
*/
7-
export const MONOCHROME_LOGO_FILES: ReadonlySet<string> = new Set(['openai.svg']);
12+
export const MONOCHROME_LOGO_FILES: ReadonlySet<string> = new Set(['amd.svg', 'openai.svg']);
813

914
/** Whether a logo file needs a dark-mode invert to stay visible. */
1015
export function isMonochromeLogo(file: string): boolean {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Maps `HW_REGISTRY` vendor names to logo files under `public/logos/`.
3+
*
4+
* NVIDIA uses its official full-color mark (NVIDIA green, #76B900), which is
5+
* legible on both themes as-is. AMD's corporate mark is monochrome by design —
6+
* the brand guidelines only permit the standard black logo or the reversed-out
7+
* white logo (the legacy green-arrow mark is retired), so it relies on the
8+
* shared dark-mode invert from `isMonochromeLogo` instead of a color variant.
9+
*
10+
* Vendors without an entry (e.g. Teacup) simply render no logo — surfaces
11+
* treat the vendor mark as optional decoration beside the hardware label.
12+
*/
13+
export const HW_VENDOR_LOGOS: Record<string, string> = {
14+
NVIDIA: 'nvidia-color.svg',
15+
AMD: 'amd.svg',
16+
};
17+
18+
/** Logo filename under `/logos/` for a hardware vendor, if one exists. */
19+
export function getHwVendorLogo(vendor: string | undefined): string | undefined {
20+
return vendor ? HW_VENDOR_LOGOS[vendor] : undefined;
21+
}

0 commit comments

Comments
 (0)