feat(stats): add model comparison home

This commit is contained in:
Adam 2026-07-08 11:28:21 -05:00
commit ccd8676bee
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
2 changed files with 1411 additions and 79 deletions

View file

@ -1,11 +1,16 @@
import "../index.css" import "../index.css"
import { Link, Meta, Title } from "@solidjs/meta" import { Meta, Title } from "@solidjs/meta"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import { createAsync } from "@solidjs/router" import { createAsync } from "@solidjs/router"
import { createMemo, createSignal, onMount, Show } from "solid-js" import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js"
import { getRequestEvent } from "solid-js/web" import { getRequestEvent } from "solid-js/web"
import { ComparisonCardsSection, modelRefFromCatalog, uniqueComparisonPairs } from "../compare-cards" import { LocaleLinks } from "../../component/locale-links"
import { ComparisonSelector } from "../compare-selector" import { useI18n } from "../../context/i18n"
import { getModelCatalog, type ModelCatalogEntry } from "../model-catalog" import { useLanguage } from "../../context/language"
import { localizedUrl } from "../../lib/language"
import { comparisonHref, modelRefFromCatalog, type ComparisonModelRef } from "../compare-cards"
import { formatCatalogLabName, getModelCatalog, type ModelCatalogEntry } from "../model-catalog"
import { setStatsPageCacheHeaders } from "../stats-cache"
import { import {
applyThemePreference, applyThemePreference,
Footer, Footer,
@ -17,35 +22,75 @@ import {
type ThemePreference, type ThemePreference,
} from "../stats-shell" } from "../stats-shell"
const compareFallbackUrl = "https://stats.opencode.ai" const comparePath = "/data/compare"
const compareTitle = "AI Model Comparison" const compareTitle = "AI Model Comparison"
const compareDescription = const compareDescription =
"Compare AI models used in OpenCode by context, output, release date, usage, rank, token share, and cost." "Compare AI models on key metrics including benchmarks, price, context length, and other model features."
const compareHeaderLinks: readonly HeaderLink[] = [ const statsUnfurlPath = "banner.png"
{ href: "#compare-tool", label: "Compare" }, const uptimeBars = Array.from({ length: 10 }, (_, index) => index)
{ href: "#model-comparison", label: "Popular" }, const heroLabs = [
] { lab: "deepseek", label: "DeepSeek" },
const compareFooterLinks: readonly HeaderLink[] = [ { lab: "openai", label: "OpenAI" },
{ href: import.meta.env.BASE_URL, label: "Data Home" }, { lab: "anthropic", label: "Anthropic" },
{ href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" }, ] as const
{ href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" }, const categoryTemplates = [
{ href: `${import.meta.env.BASE_URL}compare`, label: "Model Compare" }, {
] title: "Flagship models",
description: "The latest top-tier model from major labs.",
kind: "flagship",
},
{
title: "Most affordable",
description: "Compact, lower-cost picks for high-throughput use.",
kind: "affordable",
},
{
title: "Best for code",
description: "Frequently chosen for programming tasks.",
kind: "code",
},
{
title: "Image generation",
description: "Compare top image models on cost and quality.",
kind: "image",
},
] as const
type CompareCategory = {
title: string
description: string
first: ComparisonModelRef
second: ComparisonModelRef
avatars: ComparisonModelRef[]
}
type CompareSlot = "first" | "second"
export default function ModelCompareIndex() { export default function ModelCompareIndex() {
const i18n = useI18n()
const language = useLanguage()
const event = getRequestEvent() const event = getRequestEvent()
event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400") setStatsPageCacheHeaders(event?.response.headers)
const catalog = createAsync(() => getModelCatalog()) const catalog = createAsync(() => getModelCatalog())
const githubStars = createAsync(() => getGitHubStars()) const githubStars = createAsync(() => getGitHubStars())
const [themePreference, setThemePreference] = createSignal<ThemePreference>("system") const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
const compareUrl = createMemo(() => const compareUrl = createMemo(() => localizedUrl(language.locale(), comparePath))
new URL( const statsUnfurlUrl = new URL(statsUnfurlPath, localizedUrl("en", "/data/")).toString()
`${import.meta.env.BASE_URL}compare`,
event?.request.url ?? (typeof window === "undefined" ? compareFallbackUrl : window.location.href),
).toString(),
)
const featuredModels = createMemo(() => (catalog()?.models ?? []).slice(0, 120)) const featuredModels = createMemo(() => (catalog()?.models ?? []).slice(0, 120))
const pairs = createMemo(() => buildPopularPairs(featuredModels())) const categories = createMemo(() => buildComparisonCategories(featuredModels()))
const compareHeaderLinks = createMemo<readonly HeaderLink[]>(() => [
{ href: `${import.meta.env.BASE_URL}#top-models`, label: i18n.t("nav.topModels") },
{ href: `${import.meta.env.BASE_URL}#leaderboard`, label: i18n.t("nav.leaderboard") },
{ href: `${import.meta.env.BASE_URL}#market-share`, label: i18n.t("nav.marketShare") },
{ href: `${import.meta.env.BASE_URL}#token-cost`, label: i18n.t("nav.tokenCost") },
{ href: `${import.meta.env.BASE_URL}#session-cost`, label: i18n.t("nav.sessionCost") },
])
const compareFooterLinks = createMemo<readonly HeaderLink[]>(() => [
{ href: `${import.meta.env.BASE_URL}#top-models`, label: i18n.t("nav.topModels") },
{ href: `${import.meta.env.BASE_URL}#leaderboard`, label: i18n.t("nav.leaderboard") },
{ href: `${import.meta.env.BASE_URL}#market-share`, label: i18n.t("nav.marketShare") },
{ href: `${import.meta.env.BASE_URL}#token-cost`, label: i18n.t("nav.tokenCost") },
{ href: `${import.meta.env.BASE_URL}#session-cost`, label: i18n.t("nav.sessionCost") },
])
const updateThemePreference = (preference: ThemePreference) => { const updateThemePreference = (preference: ThemePreference) => {
applyThemePreference(preference) applyThemePreference(preference)
setThemePreference(preference) setThemePreference(preference)
@ -65,84 +110,527 @@ export default function ModelCompareIndex() {
<main data-page="stats" data-theme={themePreference()}> <main data-page="stats" data-theme={themePreference()}>
<Title>{compareTitle}</Title> <Title>{compareTitle}</Title>
<Meta name="description" content={compareDescription} /> <Meta name="description" content={compareDescription} />
<Link rel="canonical" href={compareUrl()} /> <LocaleLinks path={comparePath} />
<Meta property="og:type" content="website" /> <Meta property="og:type" content="website" />
<Meta property="og:site_name" content="OpenCode" /> <Meta property="og:site_name" content="OpenCode" />
<Meta property="og:title" content={compareTitle} /> <Meta property="og:title" content={compareTitle} />
<Meta property="og:description" content={compareDescription} /> <Meta property="og:description" content={compareDescription} />
<Meta property="og:url" content={compareUrl()} /> <Meta property="og:url" content={compareUrl()} />
<Meta name="twitter:card" content="summary" /> <Meta property="og:image" content={statsUnfurlUrl} />
<Meta property="og:image:type" content="image/png" />
<Meta property="og:image:width" content="1200" />
<Meta property="og:image:height" content="630" />
<Meta property="og:image:alt" content={i18n.t("app.unfurlAlt")} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={compareTitle} /> <Meta name="twitter:title" content={compareTitle} />
<Meta name="twitter:description" content={compareDescription} /> <Meta name="twitter:description" content={compareDescription} />
<Header githubStars={githubStars() ?? "150K"} links={compareHeaderLinks} brandHref={import.meta.env.BASE_URL} /> <Meta name="twitter:image" content={statsUnfurlUrl} />
<Meta name="twitter:image:alt" content={i18n.t("app.unfurlAlt")} />
<Header githubStars={githubStars() ?? "150K"} links={compareHeaderLinks()} brandHref={import.meta.env.BASE_URL} />
<div data-component="container"> <div data-component="container">
<div data-component="content"> <div data-component="content">
<section id="compare-tool" data-section="model-hero"> <section id="compare-tool" data-section="compare-home-hero">
<a data-slot="model-back-link" href={import.meta.env.BASE_URL}> <nav data-component="compare-home-breadcrumb" aria-label="Breadcrumb">
Data <a data-slot="compare-home-crumb" href={language.route(import.meta.env.BASE_URL)}>
</a> Data
<div data-slot="model-hero-grid"> </a>
<div data-slot="model-hero-copy"> <span data-slot="compare-home-separator">/</span>
<span data-slot="model-id-tag">/compare</span> <span data-slot="compare-home-crumb" data-current="true">
<h1>Model Comparison</h1> Compare
<p>Choose two models and compare usage, cost, limits, and features.</p> </span>
</div> </nav>
<div data-component="model-rank-panel"> <div data-slot="compare-home-hero-grid">
<span>Model Pairs</span> <h1 aria-label="Compare AI models">
<strong>{formatPairCount(featuredModels().length)}</strong> <span>Compare</span>
<p>Available pairings from the model list.</p> <HeroModelStack />
</div> <span>AI models</span>
</h1>
<p>{compareDescription}</p>
</div> </div>
<div data-slot="model-hero-pattern" aria-hidden="true" /> <div data-slot="compare-home-pattern" aria-hidden="true" />
</section>
<section data-section="compare-home-selector" aria-label="Choose models to compare">
<Show <Show
when={featuredModels().length > 1} when={featuredModels().length > 1}
fallback={ fallback={
<div data-component="empty-state" data-compact="true"> <div data-component="empty-state" data-compact="true">
<strong>No models found</strong> <strong>No models found</strong>
<p>The model list could not be loaded.</p> <p>The model list could not be loaded.</p>
</div> </div>
} }
> >
<ComparisonSelector models={featuredModels()} /> <CompareHomeSelector models={featuredModels()} />
</Show> </Show>
</section> </section>
<ComparisonCardsSection <Show when={categories().length > 0}>
pairs={pairs()} <section id="model-comparison" data-section="compare-home-related">
title="Popular Model Comparisons" <p data-slot="section-title">
description="Common model pairs to start with." <strong>Related comparisons.</strong> <span>Other model pairs to check.</span>
/> </p>
<div data-component="compare-home-card-grid">
<For each={categories()}>{(category) => <CompareHomeCard category={category} />}</For>
</div>
</section>
</Show>
</div> </div>
<Footer <Footer
themePreference={themePreference()} themePreference={themePreference()}
onThemePreferenceChange={updateThemePreference} onThemePreferenceChange={updateThemePreference}
links={compareFooterLinks} links={compareFooterLinks()}
bridge={{ href: "#compare-tool", label: "MODEL COMPARE" }} bridge={null}
/> />
</div> </div>
</main> </main>
) )
} }
function buildPopularPairs(models: ModelCatalogEntry[]) { function CompareHomeSelector(props: { models: ModelCatalogEntry[] }) {
return uniqueComparisonPairs( const [firstId, setFirstId] = createSignal("")
( const [secondId, setSecondId] = createSignal("")
[ const [activeSlot, setActiveSlot] = createSignal<CompareSlot>()
[0, 1, "Popular pair"], const modelById = createMemo(() => new Map(props.models.map((model) => [model.id, model])))
[0, 2, "Top alternative"], const first = createMemo(() => modelById().get(firstId()))
[1, 2, "Nearby pair"], const second = createMemo(() => modelById().get(secondId()))
[2, 3, "Recent model pair"], const activeSelected = createMemo(() => (activeSlot() === "first" ? first() : second()))
[3, 4, "Model pair"], const activeBlockedId = createMemo(() => (activeSlot() === "first" ? secondId() : firstId()))
[4, 5, "Model pair"], const href = createMemo(() => {
] as const const firstModel = first()
).flatMap(([firstIndex, secondIndex, detail]) => { const secondModel = second()
const first = models[firstIndex] if (!firstModel || !secondModel || firstModel.id === secondModel.id) return undefined
const second = models[secondIndex] return comparisonHref(modelRefFromCatalog(firstModel), modelRefFromCatalog(secondModel))
return first && second ? [{ first: modelRefFromCatalog(first), second: modelRefFromCatalog(second), detail }] : [] })
}),
createEffect(() => {
if (!secondId() || secondId() !== firstId()) return
setSecondId("")
})
createEffect(() => {
const url = href()
if (!url || typeof window === "undefined") return
window.location.href = url
})
return (
<form
data-component="compare-home-selector"
aria-label="Model comparison selector"
onSubmit={(event) => event.preventDefault()}
>
<CompareHomeSelect
selected={first()}
label="First model"
expanded={activeSlot() === "first"}
onOpen={() => setActiveSlot("first")}
/>
<CompareHomeSelect
selected={second()}
label="Second model"
expanded={activeSlot() === "second"}
onOpen={() => setActiveSlot("second")}
/>
<Show when={activeSlot()}>
{(slot) => (
<CompareModelSelectModal
models={props.models}
selected={activeSelected()}
blockedId={activeBlockedId()}
label={slot() === "first" ? "Choose first model" : "Choose second model"}
onClose={() => setActiveSlot(undefined)}
onSelect={(model) => {
if (slot() === "first") setFirstId(model.id)
if (slot() === "second") setSecondId(model.id)
setActiveSlot(undefined)
}}
/>
)}
</Show>
</form>
) )
} }
function formatPairCount(count: number) { function CompareHomeSelect(props: {
if (count < 2) return "0" selected: ModelCatalogEntry | undefined
return new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 }).format((count * (count - 1)) / 2) label: string
expanded: boolean
onOpen: () => void
}) {
return (
<button
type="button"
data-slot="compare-home-select-panel"
data-selected={props.selected ? "true" : undefined}
aria-label={props.label}
aria-haspopup="dialog"
aria-expanded={props.expanded}
onClick={props.onOpen}
>
<span data-slot="compare-home-select-copy">
<span data-slot="compare-home-plus" aria-hidden="true">
+
</span>
<Show when={props.selected}>{(model) => <ModelAvatar model={model()} size="tiny" />}</Show>
<span data-slot="compare-home-select-name">{props.selected?.name ?? "Select a model"}</span>
</span>
</button>
)
}
function CompareModelSelectModal(props: {
models: ModelCatalogEntry[]
selected: ModelCatalogEntry | undefined
blockedId: string
label: string
onClose: () => void
onSelect: (model: ModelCatalogEntry) => void
}) {
let searchInput: HTMLInputElement | undefined
const [search, setSearch] = createSignal("")
const [previewId, setPreviewId] = createSignal(props.selected?.id ?? "")
const availableModels = createMemo(() => props.models.filter((model) => model.id !== props.blockedId))
const filteredModels = createMemo(() => {
const terms = search().trim().toLowerCase().split(/\s+/).filter(Boolean)
if (terms.length === 0) return availableModels()
return availableModels().filter((model) => terms.every((term) => modelSearchText(model).includes(term)))
})
const preview = createMemo(
() =>
filteredModels().find((model) => model.id === previewId()) ??
filteredModels()[0] ??
availableModels()[0],
)
createEffect(() => {
const selected = props.selected
const fallback = selected && selected.id !== props.blockedId ? selected.id : filteredModels()[0]?.id
if (!fallback) return
if (availableModels().some((model) => model.id === previewId())) return
setPreviewId(fallback)
})
createEffect(() => {
const models = filteredModels()
if (models.some((model) => model.id === previewId())) return
setPreviewId(models[0]?.id ?? "")
})
createEffect(() => {
if (typeof window === "undefined") return
window.requestAnimationFrame(() => searchInput?.focus())
})
return (
<div
data-component="compare-model-modal-scrim"
role="presentation"
onClick={props.onClose}
onKeyDown={(event) => {
if (event.key !== "Escape") return
props.onClose()
}}
>
<div
data-component="compare-model-modal"
role="dialog"
aria-modal="true"
aria-label={props.label}
onClick={(event) => event.stopPropagation()}
>
<div data-slot="compare-model-modal-list">
<label data-slot="compare-model-modal-search">
<span data-slot="compare-model-modal-search-icon" aria-hidden="true" />
<input
ref={searchInput}
value={search()}
placeholder="Search models"
aria-label="Search models"
onInput={(event) => setSearch(event.currentTarget.value)}
/>
</label>
<div data-slot="compare-model-modal-results">
<Show
when={filteredModels().length > 0}
fallback={
<div data-slot="compare-model-modal-empty">
<strong>No models found</strong>
<span>Try another search.</span>
</div>
}
>
<For each={filteredModels()}>
{(model) => (
<button
type="button"
data-slot="compare-model-modal-row"
data-active={preview()?.id === model.id ? "true" : undefined}
onPointerEnter={() => setPreviewId(model.id)}
onFocus={() => setPreviewId(model.id)}
onClick={() => props.onSelect(model)}
>
<span data-slot="compare-model-modal-row-main">
<ModelAvatar model={model} size="tiny" />
<span>{model.name}</span>
</span>
<Show when={isFreeModel(model)}>
<span data-slot="compare-model-modal-badge">Free</span>
</Show>
</button>
)}
</For>
</Show>
</div>
</div>
<div data-slot="compare-model-modal-divider" aria-hidden="true" />
<Show when={preview()}>{(model) => <CompareModelDetail model={model()} />}</Show>
</div>
</div>
)
}
function CompareModelDetail(props: { model: ModelCatalogEntry }) {
return (
<aside data-slot="compare-model-modal-detail">
<header data-slot="compare-model-modal-detail-header">
<ModelAvatar model={props.model} size="small" />
<strong>{props.model.name}</strong>
</header>
<div data-slot="compare-model-modal-description">
<p>{props.model.description ?? `${props.model.name} is an AI model from ${formatCatalogLabName(props.model.lab)}.`}</p>
<span aria-hidden="true" />
</div>
<dl data-slot="compare-model-modal-facts">
<CompareModelFact label="Release" value={formatCatalogDate(props.model.releaseDate)} />
<CompareModelFact label="Context" value={formatCatalogLimit(props.model.limit?.context)} />
<CompareModelFact label="Input" value={formatCatalogUnitPrice(props.model.cost?.input)} />
<CompareModelFact label="Output" value={formatCatalogUnitPrice(props.model.cost?.output)} />
<div data-slot="compare-model-modal-fact">
<dt>Uptime</dt>
<dd data-slot="compare-model-modal-uptime">
<For each={uptimeBars}>{(bar) => <span data-active={bar < 7 ? "true" : undefined} />}</For>
</dd>
</div>
<div data-slot="compare-model-modal-fact">
<dt>URL</dt>
<dd>
<a href={modelHref(props.model)}>{formatModelUrl(props.model)}</a>
</dd>
</div>
</dl>
</aside>
)
}
function CompareModelFact(props: { label: string; value: string }) {
return (
<div data-slot="compare-model-modal-fact">
<dt>{props.label}</dt>
<dd>{props.value}</dd>
</div>
)
}
function HeroModelStack() {
return (
<span data-slot="compare-home-avatar-stack" aria-hidden="true">
<For each={heroLabs}>
{(lab) => (
<span data-slot="compare-home-avatar-frame">
<LabLogo lab={lab.lab} label={lab.label} size="large" />
</span>
)}
</For>
</span>
)
}
function CompareHomeCard(props: { category: CompareCategory }) {
return (
<a
data-component="compare-home-card"
href={comparisonHref(props.category.first, props.category.second)}
aria-label={`${props.category.title}: ${props.category.first.name} vs ${props.category.second.name}`}
>
<span data-slot="compare-home-card-head">
<span>
<strong>{props.category.title}</strong>
<em>{props.category.description}</em>
</span>
<b aria-hidden="true" />
</span>
<span data-slot="compare-home-card-divider" aria-hidden="true" />
<span data-slot="compare-home-card-models">
<span>{props.category.first.name}</span>
<i aria-hidden="true">·</i>
<span>{props.category.second.name}</span>
</span>
<span data-slot="compare-home-card-avatars" aria-hidden="true">
<For each={props.category.avatars}>{(model) => <LabLogo lab={model.lab} label={model.name} size="small" />}</For>
</span>
</a>
)
}
function ModelAvatar(props: { model: ModelCatalogEntry; size: "large" | "small" | "tiny" }) {
return <LabLogo lab={props.model.lab} label={props.model.name} size={props.size} />
}
function LabLogo(props: { lab: string; label: string; size: "large" | "small" | "tiny" }) {
const iconId = () => getProviderIconId(props.lab)
return (
<span data-slot="compare-home-avatar" data-lab={iconId()} data-size={props.size} aria-label={props.label}>
<ProviderIcon aria-hidden="true" id={iconId()} />
</span>
)
}
function buildComparisonCategories(models: ModelCatalogEntry[]): CompareCategory[] {
return categoryTemplates.reduce<{ keys: Set<string>; categories: CompareCategory[] }>((result, template, index) => {
const candidates = categoryCandidates(template.kind, models)
const pair = categoryPair(candidates, models, index, result.keys)
if (!pair) return result
result.keys.add(comparisonKey(pair.first, pair.second))
const first = modelRefFromCatalog(pair.first)
const second = modelRefFromCatalog(pair.second)
result.categories.push({
title: template.title,
description: template.description,
first,
second,
avatars: [first, second],
})
return result
}, { keys: new Set(), categories: [] }).categories
}
function categoryPair(
candidates: ModelCatalogEntry[],
fallback: ModelCatalogEntry[],
offset: number,
usedKeys: Set<string>,
) {
const pool = uniqueModels([...candidates, ...fallback.slice(offset), ...fallback])
return pool
.flatMap((first, firstIndex) =>
pool.slice(firstIndex + 1).map((second) => ({
first,
second,
key: comparisonKey(first, second),
})),
)
.find((pair) => !usedKeys.has(pair.key))
}
function categoryCandidates(kind: (typeof categoryTemplates)[number]["kind"], models: ModelCatalogEntry[]) {
if (kind === "affordable")
return models
.filter((model) => model.cost)
.toSorted((a, b) => modelCost(a) - modelCost(b) || displayDateTime(b.releaseDate) - displayDateTime(a.releaseDate))
if (kind === "code")
return models.filter((model) => model.toolCall || model.reasoning).toSorted(recentModelSort)
if (kind === "image")
return models
.filter((model) => model.modalities.output.some((modality) => modality.toLowerCase().includes("image")))
.toSorted(recentModelSort)
return models.toSorted(recentModelSort)
}
function uniqueModels(models: (ModelCatalogEntry | undefined)[]) {
return models.reduce<{ keys: Set<string>; models: ModelCatalogEntry[] }>(
(result, model) => {
if (!model || result.keys.has(model.id)) return result
result.keys.add(model.id)
result.models.push(model)
return result
},
{ keys: new Set(), models: [] },
).models
}
function recentModelSort(a: ModelCatalogEntry, b: ModelCatalogEntry) {
return displayDateTime(b.releaseDate) - displayDateTime(a.releaseDate) || a.name.localeCompare(b.name)
}
function comparisonKey(first: ModelCatalogEntry, second: ModelCatalogEntry) {
return [first.id, second.id].toSorted().join("|")
}
function modelCost(model: ModelCatalogEntry) {
return (model.cost?.input ?? Number.POSITIVE_INFINITY) + (model.cost?.output ?? Number.POSITIVE_INFINITY)
}
function modelSearchText(model: ModelCatalogEntry) {
return `${model.name} ${formatCatalogLabName(model.lab)} ${model.id}`.toLowerCase()
}
function isFreeModel(model: ModelCatalogEntry) {
return model.cost?.input === 0 && model.cost.output === 0
}
function modelHref(model: ModelCatalogEntry) {
return `${import.meta.env.BASE_URL}${model.lab}/${model.slug}`
}
function formatModelUrl(model: ModelCatalogEntry) {
return `.../${model.slug}`
}
function formatCatalogLimit(value: number | undefined) {
return value === undefined ? "Unknown" : formatTokens(value)
}
function formatCatalogUnitPrice(value: number | undefined) {
if (value === undefined) return "Unknown"
return `${formatModelPrice(value)} / 1M`
}
function formatModelPrice(value: number) {
if (value > 0 && value < 0.01) return `$${value.toFixed(4)}`
return formatMoney(value)
}
function formatMoney(value: number) {
if (value >= 1_000_000) return `$${trimNumber(value / 1_000_000, value >= 10_000_000 ? 0 : 1)}M`
if (value >= 1_000) return `$${trimNumber(value / 1_000, value >= 10_000 ? 0 : 1)}K`
return `$${value.toFixed(value >= 10 ? 0 : 2)}`
}
function formatCatalogDate(value: string | undefined) {
if (!value) return "Unknown"
const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value)
if (!match) return value
return new Intl.DateTimeFormat("en", {
month: match[2] ? "short" : undefined,
day: match[3] ? "numeric" : undefined,
year: "numeric",
timeZone: "UTC",
}).format(new Date(Date.UTC(Number(match[1]), match[2] ? Number(match[2]) - 1 : 0, match[3] ? Number(match[3]) : 1)))
}
function formatTokens(value: number) {
if (value >= 1_000_000_000_000)
return `${trimNumber(value / 1_000_000_000_000, value >= 10_000_000_000_000 ? 0 : 1)}T`
if (value >= 1_000_000_000) return `${trimNumber(value / 1_000_000_000, value >= 10_000_000_000 ? 0 : 1)}B`
if (value >= 1_000_000) return `${trimNumber(value / 1_000_000, value >= 10_000_000 ? 0 : 1)}M`
if (value >= 1_000) return `${trimNumber(value / 1_000, value >= 10_000 ? 0 : 1)}K`
return String(Math.round(value))
}
function trimNumber(value: number, digits: number) {
return Number(value.toFixed(digits)).toLocaleString("en")
}
function displayDateTime(value: string | undefined) {
if (!value) return 0
const date = new Date(value)
if (!Number.isNaN(date.getTime())) return date.getTime()
const year = Number(value.match(/\d{4}/)?.[0] ?? 0)
return Number.isFinite(year) ? year : 0
}
function getProviderIconId(provider: string) {
const id = provider.toLowerCase().replace(/[^a-z0-9]+/g, "")
if (id === "moonshot") return "moonshotai"
if (id === "zhipu") return "zhipuai"
return id
} }

View file

@ -5489,6 +5489,742 @@
background: var(--stats-accent); background: var(--stats-accent);
} }
[data-page="stats"] [data-section="compare-home-hero"],
[data-page="stats"] [data-section="compare-home-selector"],
[data-page="stats"] [data-section="compare-home-related"] {
position: relative;
box-sizing: border-box;
color: var(--stats-text);
}
[data-page="stats"] [data-section="compare-home-hero"] {
display: grid;
align-content: end;
gap: 24px;
min-height: 316px;
padding: 128px 40px 40px;
}
[data-page="stats"] [data-component="compare-home-breadcrumb"] {
display: flex;
align-items: center;
gap: 8px;
height: 24px;
min-width: 0;
}
[data-page="stats"] [data-slot="compare-home-crumb"] {
display: inline-flex;
align-items: center;
height: 24px;
box-sizing: border-box;
padding: 0 8px;
color: var(--stats-hero-muted);
background: var(--stats-layer-2);
font-size: 13px;
font-weight: 500;
line-height: 1.1;
text-decoration: none;
white-space: nowrap;
}
[data-page="stats"] a[data-slot="compare-home-crumb"]:hover,
[data-page="stats"] a[data-slot="compare-home-crumb"]:focus-visible {
color: var(--stats-text);
outline: none;
text-decoration: none;
}
[data-page="stats"] [data-slot="compare-home-separator"] {
color: var(--stats-faint);
font-size: 13px;
font-weight: 500;
line-height: 1;
}
[data-page="stats"] [data-slot="compare-home-hero-grid"] {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(320px, 519px);
gap: 24px;
align-items: center;
}
[data-page="stats"] [data-section="compare-home-hero"] h1 {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px;
min-width: 0;
margin: 0;
color: var(--stats-text);
font-size: 40px;
font-weight: 500;
line-height: 60px;
letter-spacing: 0;
}
[data-page="stats"] [data-slot="compare-home-avatar-stack"] {
display: inline-flex;
align-items: center;
height: 46px;
padding-inline: 2px;
}
[data-page="stats"] [data-slot="compare-home-avatar-frame"] {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
padding: 3px;
background: var(--stats-bg);
}
[data-page="stats"] [data-slot="compare-home-avatar-frame"] + [data-slot="compare-home-avatar-frame"] {
margin-left: -16px;
}
[data-page="stats"] [data-slot="compare-home-avatar"] {
display: grid;
place-items: center;
flex: 0 0 auto;
color: #ffffff;
background: var(--stats-accent-text);
box-shadow: inset 0 0 0 1px #0000001a;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-lab="deepseek"] {
background: #5d6ff4;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-lab="openai"] {
background: #252525;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-lab="anthropic"] {
background: #c99b75;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-size="large"] {
width: 40px;
height: 40px;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-size="large"] svg {
width: 28px;
height: 28px;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-size="small"] {
width: 20px;
height: 20px;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-size="small"] svg {
width: 15px;
height: 15px;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-size="tiny"] {
width: 16px;
height: 16px;
}
[data-page="stats"] [data-slot="compare-home-avatar"][data-size="tiny"] svg {
width: 12px;
height: 12px;
}
[data-page="stats"] [data-slot="compare-home-hero-grid"] > p {
max-width: 520px;
margin: 0;
color: var(--stats-muted);
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
[data-page="stats"] [data-slot="compare-home-pattern"] {
width: calc(100% + 80px);
height: 16px;
margin-inline: -40px;
overflow: hidden;
background: color-mix(in srgb, var(--stats-text) 10%, transparent);
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
mask-position: center top;
mask-repeat: repeat;
mask-size: 6px 6px;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
-webkit-mask-position: center top;
-webkit-mask-repeat: repeat;
-webkit-mask-size: 6px 6px;
}
[data-page="stats"] [data-section="compare-home-selector"] {
min-height: 98px;
border-top: 1px solid var(--stats-line);
border-bottom: 1px solid var(--stats-line);
box-shadow:
inset 1px 0 var(--stats-line),
inset -1px 0 var(--stats-line);
}
[data-page="stats"] [data-component="compare-home-selector"] {
display: grid;
grid-template-columns: 1fr 1fr;
min-height: 96px;
}
[data-page="stats"] [data-component="compare-home-selector"] button[data-slot="compare-home-select-panel"] {
position: relative;
display: grid;
place-items: center;
min-width: 0;
min-height: 96px;
padding: 0;
border: 0;
appearance: none;
border-radius: 0;
color: var(--stats-text);
background: var(--stats-bg);
cursor: pointer;
font: inherit;
text-align: inherit;
}
[data-page="stats"]
[data-component="compare-home-selector"]
button[data-slot="compare-home-select-panel"]:first-child::before,
[data-page="stats"]
[data-component="compare-home-selector"]
button[data-slot="compare-home-select-panel"]:last-child::after {
position: absolute;
top: 0;
bottom: 0;
width: 1px;
content: "";
background: var(--stats-line);
pointer-events: none;
}
[data-page="stats"]
[data-component="compare-home-selector"]
button[data-slot="compare-home-select-panel"]:first-child::before {
left: 0;
}
[data-page="stats"]
[data-component="compare-home-selector"]
button[data-slot="compare-home-select-panel"]:last-child::after {
right: 0;
}
[data-page="stats"]
[data-component="compare-home-selector"]
button[data-slot="compare-home-select-panel"]
+ button[data-slot="compare-home-select-panel"] {
box-shadow: inset 1px 0 var(--stats-line);
}
[data-page="stats"] [data-component="compare-home-selector"] button[data-slot="compare-home-select-panel"]:hover,
[data-page="stats"] [data-component="compare-home-selector"] button[data-slot="compare-home-select-panel"]:focus-within {
background: var(--stats-layer);
}
[data-page="stats"] [data-component="compare-home-selector"] button[data-slot="compare-home-select-panel"]:focus-visible {
z-index: 1;
outline: 1px solid var(--stats-line-strong);
outline-offset: -1px;
}
[data-page="stats"] [data-slot="compare-home-select-copy"] {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
max-width: calc(100% - 48px);
pointer-events: none;
}
[data-page="stats"] [data-slot="compare-home-plus"],
[data-page="stats"] [data-slot="compare-home-select-name"] {
min-width: 0;
color: inherit;
font-size: 13px;
font-weight: 500;
line-height: 14px;
}
[data-page="stats"] [data-slot="compare-home-select-name"] {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-slot="compare-home-select-panel"][data-selected="true"] [data-slot="compare-home-plus"] {
display: none;
}
[data-page="stats"] [data-section="compare-home-related"] {
min-height: 425px;
margin-top: 40px;
padding: 80px 40px;
box-shadow:
inset 0 1px var(--stats-line),
inset 0 -1px var(--stats-line),
inset 1px 0 var(--stats-line),
inset -1px 0 var(--stats-line);
}
[data-page="stats"] [data-section="compare-home-related"] [data-slot="section-title"] {
margin-bottom: 40px;
}
[data-page="stats"] [data-component="compare-home-card-grid"] {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
[data-page="stats"] [data-component="compare-home-card"] {
position: relative;
display: grid;
grid-template-rows: 62px 1px 20px 20px;
gap: 16px;
min-width: 0;
height: 184px;
box-sizing: border-box;
padding: 16px;
overflow: hidden;
color: var(--stats-text);
background: var(--stats-layer);
border: 1px solid var(--stats-line);
box-shadow: 0 1px 2px #00000008;
text-decoration: none;
}
[data-page="stats"] [data-component="compare-home-card"]:hover,
[data-page="stats"] [data-component="compare-home-card"]:focus-visible {
border-color: var(--stats-line-strong);
outline: none;
text-decoration: none;
}
[data-page="stats"] [data-slot="compare-home-card-head"] {
display: grid;
grid-template-columns: minmax(0, 1fr) 32px;
gap: 16px;
min-width: 0;
}
[data-page="stats"] [data-slot="compare-home-card-head"] > span {
display: grid;
gap: 8px;
min-width: 0;
}
[data-page="stats"] [data-slot="compare-home-card-head"] strong,
[data-page="stats"] [data-slot="compare-home-card-head"] em,
[data-page="stats"] [data-slot="compare-home-card-models"] {
min-width: 0;
overflow: hidden;
font-size: 13px;
line-height: 20px;
text-overflow: ellipsis;
}
[data-page="stats"] [data-slot="compare-home-card-head"] strong {
color: var(--stats-text);
font-weight: 500;
white-space: nowrap;
}
[data-page="stats"] [data-slot="compare-home-card-head"] em {
display: -webkit-box;
color: var(--stats-muted);
font-style: normal;
font-weight: 400;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
[data-page="stats"] [data-slot="compare-home-card-head"] b {
position: relative;
display: grid;
place-items: center;
width: 32px;
height: 32px;
box-sizing: border-box;
border: 1px solid var(--stats-line);
background: var(--stats-bg);
box-shadow: 0 1px 1.5px #0000000f;
}
[data-page="stats"] [data-slot="compare-home-card-head"] b::before {
position: absolute;
top: 8px;
left: 8px;
width: 3px;
height: 3px;
content: "";
background: currentColor;
box-shadow:
7px 0 currentColor,
0 7px currentColor;
color: var(--stats-muted);
}
[data-page="stats"] [data-slot="compare-home-card-head"] b::after {
position: absolute;
right: 5px;
bottom: 5px;
width: 12px;
height: 12px;
box-sizing: border-box;
content: "";
background:
radial-gradient(circle at 4px 4px, transparent 2.5px, var(--stats-muted) 2.75px 4px, transparent 4.25px),
linear-gradient(var(--stats-muted) 0 0) 7px 8px / 5px 1.5px no-repeat;
transform: rotate(45deg);
}
[data-page="stats"] [data-slot="compare-home-card-divider"] {
height: 1px;
min-width: 0;
background: var(--stats-line);
}
[data-page="stats"] [data-slot="compare-home-card-models"] {
display: flex;
align-items: center;
gap: 6px;
color: var(--stats-muted);
font-weight: 400;
white-space: nowrap;
}
[data-page="stats"] [data-slot="compare-home-card-models"] span {
flex: 0 0 auto;
}
[data-page="stats"] [data-slot="compare-home-card-models"] i {
flex: 0 0 auto;
color: var(--stats-faint);
font-style: normal;
}
[data-page="stats"] [data-slot="compare-home-card-avatars"] {
display: inline-flex;
align-items: center;
gap: 8px;
min-width: 0;
}
[data-page="stats"] [data-component="compare-model-modal-scrim"] {
position: fixed;
inset: 0;
z-index: 1000;
display: grid;
place-items: center;
box-sizing: border-box;
padding: 24px;
background: rgb(0 0 0 / 40%);
}
[data-page="stats"] [data-component="compare-model-modal"] {
display: grid;
grid-template-columns: minmax(0, 440px) 1px minmax(0, 279px);
width: min(720px, calc(100vw - 48px));
height: min(480px, calc(100vh - 48px));
overflow: hidden;
color: var(--stats-text);
background: var(--stats-bg);
box-shadow:
0 0 0 0.5px rgb(0 0 0 / 12%),
0 8px 16px rgb(0 0 0 / 8%),
0 16px 32px rgb(0 0 0 / 4%);
}
[data-page="stats"] [data-slot="compare-model-modal-list"] {
display: grid;
grid-template-rows: 52px minmax(0, 1fr);
min-width: 0;
min-height: 0;
background: var(--stats-bg);
}
[data-page="stats"] [data-slot="compare-model-modal-search"] {
display: flex;
align-items: center;
gap: 12px;
box-sizing: border-box;
height: 52px;
padding: 16px;
border-bottom: 1px solid var(--stats-line);
color: var(--stats-muted);
background: var(--stats-bg);
}
[data-page="stats"] [data-slot="compare-model-modal-search-icon"] {
position: relative;
flex: 0 0 auto;
width: 16px;
height: 16px;
color: var(--stats-faint);
}
[data-page="stats"] [data-slot="compare-model-modal-search-icon"]::before {
position: absolute;
top: 3px;
left: 3px;
width: 6px;
height: 6px;
border: 1.5px solid currentColor;
border-radius: 999px;
content: "";
}
[data-page="stats"] [data-slot="compare-model-modal-search-icon"]::after {
position: absolute;
top: 10px;
left: 9px;
width: 5px;
height: 1.5px;
background: currentColor;
content: "";
transform: rotate(45deg);
transform-origin: left center;
}
[data-page="stats"] [data-slot="compare-model-modal-search"] input {
width: 100%;
min-width: 0;
height: 20px;
padding: 0;
border: 0;
outline: 0;
color: var(--stats-text);
background: transparent;
font: inherit;
font-size: 13px;
font-weight: 400;
line-height: 1.1;
}
[data-page="stats"] [data-slot="compare-model-modal-search"] input::placeholder {
color: var(--stats-faint);
opacity: 1;
}
[data-page="stats"] [data-slot="compare-model-modal-results"] {
min-width: 0;
min-height: 0;
overflow-y: auto;
background: var(--stats-layer);
}
[data-page="stats"] [data-component="compare-model-modal"] button[data-slot="compare-model-modal-row"] {
display: flex;
align-items: center;
gap: 16px;
width: 100%;
height: 49px;
box-sizing: border-box;
padding: 0 16px;
border: 0;
border-bottom: 1px solid var(--stats-line);
appearance: none;
border-radius: 0;
color: var(--stats-text);
background: transparent;
cursor: pointer;
font: inherit;
text-align: left;
}
[data-page="stats"] [data-component="compare-model-modal"] button[data-slot="compare-model-modal-row"]:hover,
[data-page="stats"] [data-component="compare-model-modal"] button[data-slot="compare-model-modal-row"]:focus-visible,
[data-page="stats"] [data-component="compare-model-modal"] button[data-slot="compare-model-modal-row"][data-active="true"] {
color: var(--stats-text);
background: var(--stats-layer-2);
outline: none;
}
[data-page="stats"] [data-slot="compare-model-modal-row-main"] {
display: flex;
align-items: center;
flex: 1 1 auto;
gap: 12px;
min-width: 0;
}
[data-page="stats"] [data-slot="compare-model-modal-row-main"] > span:last-child {
min-width: 0;
overflow: hidden;
color: var(--stats-text);
font-size: 13px;
font-weight: 500;
line-height: 1.1;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-slot="compare-model-modal-badge"] {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
height: 16px;
padding: 0 6px;
color: var(--stats-muted);
background: var(--stats-layer-2);
font-size: 11px;
font-weight: 500;
line-height: 1.1;
}
[data-page="stats"] [data-slot="compare-model-modal-empty"] {
display: grid;
gap: 8px;
padding: 24px 16px;
color: var(--stats-muted);
font-size: 13px;
line-height: 1.4;
}
[data-page="stats"] [data-slot="compare-model-modal-empty"] strong {
color: var(--stats-text);
font-weight: 500;
}
[data-page="stats"] [data-slot="compare-model-modal-divider"] {
width: 1px;
min-height: 0;
background: var(--stats-line);
}
[data-page="stats"] [data-slot="compare-model-modal-detail"] {
display: grid;
grid-template-rows: 52px minmax(0, 1fr) auto;
min-width: 0;
min-height: 0;
background: var(--stats-bg);
}
[data-page="stats"] [data-slot="compare-model-modal-detail-header"] {
display: flex;
align-items: center;
gap: 12px;
box-sizing: border-box;
height: 52px;
padding: 16px;
background: var(--stats-bg);
}
[data-page="stats"] [data-slot="compare-model-modal-detail-header"] strong {
min-width: 0;
overflow: hidden;
color: var(--stats-text);
font-size: 13px;
font-weight: 500;
line-height: 1.1;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-slot="compare-model-modal-description"] {
position: relative;
min-width: 0;
min-height: 0;
padding: 0 16px 16px;
overflow: hidden;
background: var(--stats-bg);
}
[data-page="stats"] [data-slot="compare-model-modal-description"] p {
margin: 0;
color: var(--stats-muted);
font-size: 11px;
font-weight: 400;
line-height: 1.5;
white-space: pre-wrap;
}
[data-page="stats"] [data-slot="compare-model-modal-description"] > span {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 32px;
background: linear-gradient(to bottom, transparent, var(--stats-bg));
pointer-events: none;
}
[data-page="stats"] [data-slot="compare-model-modal-facts"] {
display: grid;
gap: 8px;
box-sizing: border-box;
min-width: 0;
margin: 0;
padding: 16px;
overflow: hidden;
background: var(--stats-bg);
}
[data-page="stats"] [data-slot="compare-model-modal-fact"] {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 16px;
min-width: 0;
padding-bottom: 8px;
border-bottom: 1px solid var(--stats-line);
color: var(--stats-muted);
font-size: 11px;
font-weight: 500;
line-height: 1.5;
}
[data-page="stats"] [data-slot="compare-model-modal-fact"]:last-child {
padding-bottom: 0;
border-bottom: 0;
}
[data-page="stats"] [data-slot="compare-model-modal-fact"] dt,
[data-page="stats"] [data-slot="compare-model-modal-fact"] dd {
min-width: 0;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-slot="compare-model-modal-fact"] dd {
color: var(--stats-text);
text-align: right;
}
[data-page="stats"] [data-slot="compare-model-modal-fact"] a {
color: inherit;
text-decoration: underline;
text-decoration-color: color-mix(in srgb, var(--stats-text) 20%, transparent);
text-underline-offset: 2px;
}
[data-page="stats"] [data-slot="compare-model-modal-uptime"] {
display: inline-flex;
justify-content: flex-end;
gap: 2px;
}
[data-page="stats"] [data-slot="compare-model-modal-uptime"] span {
width: 3px;
height: 12px;
background: var(--stats-layer-2);
}
[data-page="stats"] [data-slot="compare-model-modal-uptime"] span[data-active="true"] {
background: #198b43;
}
[data-page="stats"][data-theme="dark"], [data-page="stats"][data-theme="dark"],
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) { :root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) {
color-scheme: dark; color-scheme: dark;
@ -5886,10 +6622,22 @@
[data-page="stats"] [data-section="model-hero"], [data-page="stats"] [data-section="model-hero"],
[data-page="stats"] [data-section="lab-hero"], [data-page="stats"] [data-section="lab-hero"],
[data-page="stats"] [data-section="model-momentum"], [data-page="stats"] [data-section="model-momentum"],
[data-page="stats"] [data-section="model-panel"] { [data-page="stats"] [data-section="model-panel"],
[data-page="stats"] [data-section="compare-home-hero"],
[data-page="stats"] [data-section="compare-home-related"] {
padding: 64px 32px; padding: 64px 32px;
} }
[data-page="stats"] [data-section="compare-home-hero"] {
min-height: 280px;
padding: 104px 32px 40px;
}
[data-page="stats"] [data-slot="compare-home-pattern"] {
width: calc(100% + 64px);
margin-inline: -32px;
}
[data-page="stats"] [data-section="lab-hero"] { [data-page="stats"] [data-section="lab-hero"] {
min-height: 280px; min-height: 280px;
padding: 104px 32px 40px; padding: 104px 32px 40px;
@ -5943,6 +6691,15 @@
align-items: start; align-items: start;
} }
[data-page="stats"] [data-slot="compare-home-hero-grid"] {
grid-template-columns: 1fr;
align-items: start;
}
[data-page="stats"] [data-component="compare-home-card-grid"] {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
[data-page="stats"] [data-section="lab-overview"] { [data-page="stats"] [data-section="lab-overview"] {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
@ -6165,10 +6922,87 @@
[data-page="stats"] [data-section="model-hero"], [data-page="stats"] [data-section="model-hero"],
[data-page="stats"] [data-section="lab-hero"], [data-page="stats"] [data-section="lab-hero"],
[data-page="stats"] [data-section="model-momentum"], [data-page="stats"] [data-section="model-momentum"],
[data-page="stats"] [data-section="model-panel"] { [data-page="stats"] [data-section="model-panel"],
[data-page="stats"] [data-section="compare-home-hero"],
[data-page="stats"] [data-section="compare-home-related"] {
padding: 48px 24px; padding: 48px 24px;
} }
[data-page="stats"] [data-section="compare-home-hero"] {
gap: 20px;
min-height: 316px;
padding: 72px 24px 40px;
}
[data-page="stats"] [data-slot="compare-home-pattern"] {
width: calc(100% + 48px);
margin-inline: -24px;
}
[data-page="stats"] [data-section="compare-home-hero"] h1 {
gap: 12px;
font-size: 32px;
line-height: 42px;
}
[data-page="stats"] [data-component="compare-home-breadcrumb"] {
flex-wrap: wrap;
height: auto;
}
[data-page="stats"] [data-component="compare-home-selector"] {
grid-template-columns: 1fr;
}
[data-page="stats"] [data-component="compare-home-selector"] button[data-slot="compare-home-select-panel"]::before,
[data-page="stats"] [data-component="compare-home-selector"] button[data-slot="compare-home-select-panel"]::after {
position: absolute;
top: 0;
bottom: 0;
width: 1px;
content: "";
background: var(--stats-line);
pointer-events: none;
}
[data-page="stats"] [data-component="compare-home-selector"] button[data-slot="compare-home-select-panel"]::before {
left: 0;
}
[data-page="stats"] [data-component="compare-home-selector"] button[data-slot="compare-home-select-panel"]::after {
right: 0;
}
[data-page="stats"] [data-component="compare-model-modal-scrim"] {
padding: 16px;
}
[data-page="stats"] [data-component="compare-model-modal"] {
grid-template-columns: 1fr;
width: calc(100vw - 32px);
height: min(520px, calc(100vh - 32px));
}
[data-page="stats"] [data-slot="compare-model-modal-detail"],
[data-page="stats"] [data-slot="compare-model-modal-divider"] {
display: none;
}
[data-page="stats"]
[data-component="compare-home-selector"]
button[data-slot="compare-home-select-panel"]
+ button[data-slot="compare-home-select-panel"] {
box-shadow: inset 0 1px var(--stats-line);
}
[data-page="stats"] [data-section="compare-home-related"] {
margin-top: 24px;
}
[data-page="stats"] [data-component="compare-home-card-grid"] {
grid-template-columns: 1fr;
}
[data-page="stats"] [data-slot="top-models-title"], [data-page="stats"] [data-slot="top-models-title"],
[data-page="stats"] [data-slot="section-title"] { [data-page="stats"] [data-slot="section-title"] {
margin-bottom: 32px; margin-bottom: 32px;
@ -6860,6 +7694,16 @@
} }
@media (max-width: 40rem) { @media (max-width: 40rem) {
[data-page="stats"] [data-section="compare-home-hero"] h1 {
align-items: flex-start;
flex-direction: column;
}
[data-page="stats"] [data-slot="compare-home-hero-grid"] > p {
font-size: 14px;
line-height: 22px;
}
[data-page="stats"] [data-slot="footer-grid"] { [data-page="stats"] [data-slot="footer-grid"] {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }