Просмотр исходного кода

feat(stats): add model comparison pages

Adam 2 месяцев назад
Родитель
Сommit
5a533e366f

+ 59 - 0
packages/stats/app/src/routes/[lab]/[model].tsx

@@ -33,6 +33,12 @@ import {
 import { SectionHeading } from "../section-heading"
 import { SectionHeading } from "../section-heading"
 import { runStatsEffect } from "../../stats-runtime"
 import { runStatsEffect } from "../../stats-runtime"
 import { setStatsPageCacheHeaders } from "../stats-cache"
 import { setStatsPageCacheHeaders } from "../stats-cache"
+import {
+  ComparisonCardsSection,
+  modelRefFromCatalog,
+  uniqueComparisonPairs,
+  type ComparisonModelRef,
+} from "../compare-cards"
 import {
 import {
   applyThemePreference,
   applyThemePreference,
   Footer,
   Footer,
@@ -189,6 +195,11 @@ export default function StatsModel() {
                 <ModelEfficiencySection data={stats() ?? null} catalog={catalogEntry() ?? null} />
                 <ModelEfficiencySection data={stats() ?? null} catalog={catalogEntry() ?? null} />
                 <ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
                 <ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
                 <ModelPeersSection data={stats() ?? null} />
                 <ModelPeersSection data={stats() ?? null} />
+                <ComparisonCardsSection
+                  pairs={modelComparisonPairs(catalog(), catalogEntry() ?? null, stats() ?? null)}
+                  title="Compare This Model"
+                  description="Other models to compare with this one."
+                />
               </>
               </>
             </Show>
             </Show>
           </Show>
           </Show>
@@ -197,6 +208,7 @@ export default function StatsModel() {
           themePreference={themePreference()}
           themePreference={themePreference()}
           onThemePreferenceChange={updateThemePreference}
           onThemePreferenceChange={updateThemePreference}
           links={modelFooterLinks()}
           links={modelFooterLinks()}
+          bridge={{ href: "#model-comparison", label: "MODEL COMPARISONS" }}
         />
         />
       </div>
       </div>
     </main>
     </main>
@@ -1172,6 +1184,53 @@ function ModelEmptyState(props: { title: string; description: string; compact?:
   )
   )
 }
 }
 
 
+function modelComparisonPairs(
+  catalog: ModelCatalog | undefined,
+  catalogEntry: ModelCatalogEntry | null,
+  data: StatsModelData | null,
+) {
+  const current = modelComparisonRef(catalogEntry, data)
+  if (!current) return []
+  const peerPairs = (data?.peers ?? [])
+    .filter((peer) => peer.model !== data?.model)
+    .slice(0, 3)
+    .map((peer) => ({
+      first: current,
+      second: {
+        name: peer.model,
+        lab: peer.provider,
+        slug: peer.slug,
+        labName: peer.author,
+        metric: `#${peer.rank} / ${formatTokens(peer.tokens)}`,
+      },
+      detail: "Usage peer",
+    }))
+  const catalogPairs = (catalogEntry && catalog ? catalog.labs.find((lab) => lab.id === catalogEntry.lab)?.models ?? [] : [])
+    .filter((model) => model.id !== catalogEntry?.id)
+    .slice(0, 3)
+    .map((model) => ({
+      first: current,
+      second: modelRefFromCatalog(model),
+      detail: "Same lab pair",
+    }))
+  return uniqueComparisonPairs([...peerPairs, ...catalogPairs])
+}
+
+function modelComparisonRef(
+  catalogEntry: ModelCatalogEntry | null,
+  data: StatsModelData | null,
+): ComparisonModelRef | undefined {
+  if (catalogEntry) return modelRefFromCatalog(catalogEntry)
+  if (!data) return undefined
+  return {
+    name: data.model,
+    lab: data.provider,
+    slug: data.slug,
+    labName: data.author,
+    metric: `#${data.rank}`,
+  }
+}
+
 function getProviderIconId(author: string) {
 function getProviderIconId(author: string) {
   if (author === "MiniMax") return "minimax"
   if (author === "MiniMax") return "minimax"
   if (author === "Moonshot") return "moonshotai"
   if (author === "Moonshot") return "moonshotai"

+ 36 - 0
packages/stats/app/src/routes/[lab]/index.tsx

@@ -28,6 +28,11 @@ import {
 import { SectionHeading } from "../section-heading"
 import { SectionHeading } from "../section-heading"
 import { runStatsEffect } from "../../stats-runtime"
 import { runStatsEffect } from "../../stats-runtime"
 import { setStatsPageCacheHeaders } from "../stats-cache"
 import { setStatsPageCacheHeaders } from "../stats-cache"
+import {
+  ComparisonCardsSection,
+  modelRefFromCatalog,
+  uniqueComparisonPairs,
+} from "../compare-cards"
 import {
 import {
   applyThemePreference,
   applyThemePreference,
   Footer,
   Footer,
@@ -149,6 +154,11 @@ export default function StatsLab() {
                     labs={catalog()?.labs ?? []}
                     labs={catalog()?.labs ?? []}
                     market={homeStats()?.market["2M"] ?? []}
                     market={homeStats()?.market["2M"] ?? []}
                   />
                   />
+                  <ComparisonCardsSection
+                    pairs={labComparisonPairs(data(), stats()?.models ?? [])}
+                    title={`${data().name} Model Comparisons`}
+                    description="Model pairs from this lab."
+                  />
                 </>
                 </>
               )}
               )}
             </Show>
             </Show>
@@ -158,6 +168,7 @@ export default function StatsLab() {
           themePreference={themePreference()}
           themePreference={themePreference()}
           onThemePreferenceChange={updateThemePreference}
           onThemePreferenceChange={updateThemePreference}
           links={labFooterLinks()}
           links={labFooterLinks()}
+          bridge={{ href: "#model-comparison", label: "MODEL COMPARISONS" }}
         />
         />
       </div>
       </div>
     </main>
     </main>
@@ -732,6 +743,31 @@ function LabEmptyState(props: { title: string; description: string }) {
   )
   )
 }
 }
 
 
+function labComparisonPairs(lab: ModelCatalogLab, usage: LabUsageModelEntry[]) {
+  const usageRefs = usage.slice(0, 4).map((model) => ({
+    name: model.model,
+    lab: model.provider,
+    slug: model.slug,
+    labName: model.author,
+    metric: formatTokens(model.tokens),
+  }))
+  const refs = usageRefs.length > 1 ? usageRefs : lab.models.slice(0, 4).map(modelRefFromCatalog)
+  return uniqueComparisonPairs(
+    (
+      [
+        [0, 1, "Most-used lab pair"],
+        [0, 2, "Lab alternative"],
+        [1, 2, "Adjacent lab pair"],
+        [2, 3, "Same lab pair"],
+      ] as const
+    ).flatMap(([firstIndex, secondIndex, detail]) => {
+      const first = refs[firstIndex]
+      const second = refs[secondIndex]
+      return first && second ? [{ first, second, detail }] : []
+    }),
+  )
+}
+
 type RelatedLabEntry = { lab: ModelCatalogLab; share: number; tokens: number }
 type RelatedLabEntry = { lab: ModelCatalogLab; share: number; tokens: number }
 
 
 function relatedLabs(current: ModelCatalogLab, labs: ModelCatalogLab[], market: MarketDay[]): RelatedLabEntry[] {
 function relatedLabs(current: ModelCatalogLab, labs: ModelCatalogLab[], market: MarketDay[]): RelatedLabEntry[] {

+ 88 - 0
packages/stats/app/src/routes/compare-cards.tsx

@@ -0,0 +1,88 @@
+import { For, Show } from "solid-js"
+import { catalogSlug, formatCatalogLabName, type ModelCatalogEntry } from "./model-catalog"
+
+export type ComparisonModelRef = {
+  name: string
+  lab: string
+  slug: string
+  labName?: string
+  metric?: string
+}
+
+export type ComparisonPair = {
+  first: ComparisonModelRef
+  second: ComparisonModelRef
+  detail: string
+}
+
+export function modelRefFromCatalog(entry: ModelCatalogEntry): ComparisonModelRef {
+  return {
+    name: entry.name,
+    lab: entry.lab,
+    slug: entry.slug,
+    labName: formatCatalogLabName(entry.lab),
+  }
+}
+
+export function comparisonHref(first: ComparisonModelRef, second: ComparisonModelRef) {
+  return `${import.meta.env.BASE_URL}compare/${catalogSlug(first.lab)}/${catalogSlug(first.slug)}/${catalogSlug(
+    second.lab,
+  )}/${catalogSlug(second.slug)}`
+}
+
+export function uniqueComparisonPairs(pairs: ComparisonPair[]) {
+  return pairs.reduce<{ keys: Set<string>; pairs: ComparisonPair[] }>(
+    (result, pair) => {
+      const key = [modelKey(pair.first), modelKey(pair.second)].toSorted().join("|")
+      if (result.keys.has(key) || modelKey(pair.first) === modelKey(pair.second)) return result
+      result.keys.add(key)
+      result.pairs.push(pair)
+      return result
+    },
+    { keys: new Set(), pairs: [] },
+  ).pairs
+}
+
+export function ComparisonCardsSection(props: {
+  pairs: ComparisonPair[]
+  title?: string
+  description?: string
+  compact?: boolean
+}) {
+  return (
+    <Show when={props.pairs.length > 0}>
+      <section id="model-comparison" data-section="model-panel" data-variant={props.compact ? "compact" : undefined}>
+        <p data-slot="section-title">
+          <strong>{props.title ?? "Model Comparisons"}.</strong>{" "}
+          <span>{props.description ?? "Compare usage, cost, limits, and features."}</span>
+        </p>
+        <div data-component="comparison-card-grid">
+          <For each={props.pairs}>
+            {(pair) => (
+              <a data-component="comparison-card" href={comparisonHref(pair.first, pair.second)}>
+                <span>{pair.detail}</span>
+                <strong>
+                  {pair.first.name} <em>vs</em> {pair.second.name}
+                </strong>
+                <p>
+                  <b>{pair.first.labName ?? formatCatalogLabName(pair.first.lab)}</b>
+                  <i />
+                  <b>{pair.second.labName ?? formatCatalogLabName(pair.second.lab)}</b>
+                </p>
+                <Show when={pair.first.metric || pair.second.metric}>
+                  <small>
+                    {pair.first.metric ?? "Listed"} / {pair.second.metric ?? "Listed"}
+                  </small>
+                </Show>
+              </a>
+            )}
+          </For>
+        </div>
+      </section>
+    </Show>
+  )
+}
+
+function modelKey(model: ComparisonModelRef) {
+  return `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`
+}

+ 83 - 0
packages/stats/app/src/routes/compare-selector.tsx

@@ -0,0 +1,83 @@
+import { createEffect, createMemo, createSignal, For } from "solid-js"
+import { comparisonHref, modelRefFromCatalog } from "./compare-cards"
+import type { ModelCatalogEntry } from "./model-catalog"
+
+export function ComparisonSelector(props: {
+  models: ModelCatalogEntry[]
+  firstId?: string
+  secondId?: string
+  label?: string
+}) {
+  const [firstId, setFirstId] = createSignal(props.firstId ?? props.models[0]?.id ?? "")
+  const [secondId, setSecondId] = createSignal(differentModelId(props.models, firstId(), props.secondId))
+  const modelById = createMemo(() => new Map(props.models.map((model) => [model.id, model])))
+  const href = createMemo(() => {
+    const first = modelById().get(firstId())
+    const second = modelById().get(secondId())
+    if (!first || !second || first.id === second.id) return undefined
+    return comparisonHref(modelRefFromCatalog(first), modelRefFromCatalog(second))
+  })
+
+  createEffect(() => {
+    if (firstId() && modelById().has(firstId())) return
+    const next = props.firstId && modelById().has(props.firstId) ? props.firstId : props.models[0]?.id
+    if (next) setFirstId(next)
+  })
+
+  createEffect(() => {
+    if (secondId() && secondId() !== firstId() && modelById().has(secondId())) return
+    setSecondId(differentModelId(props.models, firstId(), props.secondId))
+  })
+
+  return (
+    <form
+      data-component="comparison-selector"
+      aria-label={props.label ?? "Model comparison selector"}
+      onSubmit={(event) => {
+        event.preventDefault()
+        const url = href()
+        if (!url || typeof window === "undefined") return
+        window.location.href = url
+      }}
+    >
+      <label>
+        <span>First model</span>
+        <select value={firstId()} onInput={(event) => setFirstId(event.currentTarget.value)} required>
+          <option value="" disabled>
+            Select model
+          </option>
+          <For each={props.models}>
+            {(model) => (
+              <option value={model.id}>
+                {model.name} ({model.lab})
+              </option>
+            )}
+          </For>
+        </select>
+      </label>
+      <label>
+        <span>Second model</span>
+        <select value={secondId()} onInput={(event) => setSecondId(event.currentTarget.value)} required>
+          <option value="" disabled>
+            Select model
+          </option>
+          <For each={props.models}>
+            {(model) => (
+              <option value={model.id} disabled={model.id === firstId()}>
+                {model.name} ({model.lab})
+              </option>
+            )}
+          </For>
+        </select>
+      </label>
+      <button type="submit" disabled={!href()}>
+        Compare models
+      </button>
+    </form>
+  )
+}
+
+function differentModelId(models: ModelCatalogEntry[], firstId: string, preferredId: string | undefined) {
+  if (preferredId && preferredId !== firstId && models.some((model) => model.id === preferredId)) return preferredId
+  return models.find((model) => model.id !== firstId)?.id ?? ""
+}

+ 608 - 0
packages/stats/app/src/routes/compare/[firstLab]/[firstModel]/[secondLab]/[secondModel].tsx

@@ -0,0 +1,608 @@
+import "../../../../index.css"
+import { Link, Meta, Title } from "@solidjs/meta"
+import {
+  getStatsModelComparisonData,
+  type StatsModelComparisonEntry,
+} from "@opencode-ai/stats-core/domain/home"
+import { runtime } from "@opencode-ai/stats-core/runtime"
+import { createAsync, query, useParams } from "@solidjs/router"
+import { createMemo, createSignal, For, onMount, Show } from "solid-js"
+import { getRequestEvent } from "solid-js/web"
+import {
+  ComparisonCardsSection,
+  modelRefFromCatalog,
+  uniqueComparisonPairs,
+  type ComparisonModelRef,
+  type ComparisonPair,
+} from "../../../../compare-cards"
+import { ComparisonSelector } from "../../../../compare-selector"
+import {
+  catalogSlug,
+  findModelCatalogEntry,
+  formatCatalogLabName,
+  getModelCatalog,
+  type ModelCatalog,
+  type ModelCatalogEntry,
+} from "../../../../model-catalog"
+import {
+  applyThemePreference,
+  Footer,
+  getGitHubStars,
+  Header,
+  isThemePreference,
+  themeStorageKey,
+  type HeaderLink,
+  type ThemePreference,
+} from "../../../../stats-shell"
+
+const compareFallbackUrl = "https://stats.opencode.ai"
+const compareHeaderLinks: readonly HeaderLink[] = [
+  { href: "#overview", label: "Overview" },
+  { href: "#comparison", label: "Comparison" },
+  { href: "#compare-tool", label: "Compare" },
+  { href: "#model-comparison", label: "Related" },
+]
+const compareFooterLinks: readonly HeaderLink[] = [
+  { href: import.meta.env.BASE_URL, label: "Data Home" },
+  { href: `${import.meta.env.BASE_URL}compare`, label: "Model Compare" },
+  { href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" },
+  { href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" },
+]
+
+type ComparisonModel = {
+  name: string
+  lab: string
+  labName: string
+  slug: string
+  catalog: ModelCatalogEntry | null
+  stats: StatsModelComparisonEntry | null
+}
+type ComparisonDirection = "higher" | "lower"
+type ComparisonCell = { value: string; detail?: string; score?: number }
+type ComparisonRow = {
+  label: string
+  description: string
+  direction: ComparisonDirection
+  cells: [ComparisonCell, ComparisonCell]
+}
+
+const getComparisonData = query(
+  async (firstLab: string, firstModel: string, secondLab: string, secondModel: string) => {
+    "use server"
+    return runtime.runPromise(getStatsModelComparisonData(firstLab, firstModel, secondLab, secondModel))
+  },
+  "getStatsModelComparisonData",
+)
+
+export default function ModelComparePair() {
+  const event = getRequestEvent()
+  event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400")
+  const params = useParams()
+  const firstLabParam = createMemo(() => params.firstLab ?? "")
+  const firstModelParam = createMemo(() => params.firstModel ?? "")
+  const secondLabParam = createMemo(() => params.secondLab ?? "")
+  const secondModelParam = createMemo(() => params.secondModel ?? "")
+  const catalog = createAsync(() => getModelCatalog())
+  const firstCatalog = createMemo(() => resolvedCatalogEntry(catalog(), firstLabParam(), firstModelParam()))
+  const secondCatalog = createMemo(() => resolvedCatalogEntry(catalog(), secondLabParam(), secondModelParam()))
+  const stats = createAsync(() => {
+    if (catalog() === undefined || firstCatalog() === undefined || secondCatalog() === undefined)
+      return Promise.resolve(undefined)
+    return getComparisonData(
+      firstCatalog()?.lab ?? firstLabParam(),
+      firstCatalog()?.slug ?? firstModelParam(),
+      secondCatalog()?.lab ?? secondLabParam(),
+      secondCatalog()?.slug ?? secondModelParam(),
+    )
+  })
+  const githubStars = createAsync(() => getGitHubStars())
+  const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
+  const models = createMemo(
+    () =>
+      [
+        buildComparisonModel(firstLabParam(), firstModelParam(), firstCatalog() ?? null, stats()?.models[0] ?? null),
+        buildComparisonModel(secondLabParam(), secondModelParam(), secondCatalog() ?? null, stats()?.models[1] ?? null),
+      ] as const,
+  )
+  const title = createMemo(() => `${models()[0].name} vs ${models()[1].name} - Model Comparison`)
+  const description = createMemo(
+    () =>
+      `Compare ${models()[0].name} and ${models()[1].name} by usage, rank, context window, output limit, cache ratio, and cost across OpenCode data.`,
+  )
+  const canonicalPath = createMemo(
+    () =>
+      `${import.meta.env.BASE_URL}compare/${catalogSlug(models()[0].lab)}/${catalogSlug(models()[0].slug)}/${catalogSlug(
+        models()[1].lab,
+      )}/${catalogSlug(models()[1].slug)}`,
+  )
+  const canonicalUrl = createMemo(() =>
+    new URL(
+      canonicalPath(),
+      event?.request.url ?? (typeof window === "undefined" ? compareFallbackUrl : window.location.href),
+    ).toString(),
+  )
+  const rows = createMemo(() => buildComparisonRows(models()[0], models()[1]))
+  const relatedPairs = createMemo(() => buildRelatedPairs(catalog(), models()[0], models()[1]))
+  const selectorModels = createMemo(() =>
+    uniqueCatalogModels([comparisonCatalogEntry(models()[0]), comparisonCatalogEntry(models()[1]), ...(catalog()?.models ?? [])]),
+  )
+  const structuredData = createMemo(() =>
+    JSON.stringify({
+      "@context": "https://schema.org",
+      "@type": "WebPage",
+      name: title(),
+      description: description(),
+      url: canonicalUrl(),
+      about: models().map((model) => ({
+        "@type": "SoftwareApplication",
+        name: model.name,
+        applicationCategory: "AI model",
+        provider: model.labName,
+      })),
+    }),
+  )
+  const updateThemePreference = (preference: ThemePreference) => {
+    applyThemePreference(preference)
+    setThemePreference(preference)
+    if (typeof window === "undefined") return
+    window.localStorage.setItem(themeStorageKey, preference)
+  }
+
+  onMount(() => {
+    if (typeof window === "undefined") return
+    const preference = window.localStorage.getItem(themeStorageKey)
+    const nextPreference = isThemePreference(preference) ? preference : "system"
+    applyThemePreference(nextPreference)
+    setThemePreference(nextPreference)
+  })
+
+  return (
+    <main data-page="stats" data-theme={themePreference()}>
+      <Title>{title()}</Title>
+      <Meta name="description" content={description()} />
+      <Link rel="canonical" href={canonicalUrl()} />
+      <Meta property="og:type" content="website" />
+      <Meta property="og:site_name" content="OpenCode" />
+      <Meta property="og:title" content={title()} />
+      <Meta property="og:description" content={description()} />
+      <Meta property="og:url" content={canonicalUrl()} />
+      <Meta name="twitter:card" content="summary" />
+      <Meta name="twitter:title" content={title()} />
+      <Meta name="twitter:description" content={description()} />
+      <script type="application/ld+json">{structuredData()}</script>
+      <Header githubStars={githubStars() ?? "150K"} links={compareHeaderLinks} brandHref={import.meta.env.BASE_URL} />
+      <div data-component="container">
+        <div data-component="content">
+          <ComparisonHero models={models()} />
+          <section id="comparison" data-section="model-panel">
+            <p data-slot="section-title">
+              <strong>Comparison Table.</strong>{" "}
+              <span>Compare usage, cost, limits, and features.</span>
+            </p>
+            <Show
+              when={stats() !== undefined}
+	              fallback={
+	                <div data-component="empty-state" data-compact="true">
+	                  <strong>Loading comparison</strong>
+	                  <p>Loading stats for both models.</p>
+	                </div>
+	              }
+            >
+              <ComparisonTable models={models()} rows={rows()} />
+            </Show>
+          </section>
+          <section id="compare-tool" data-section="model-panel" data-variant="compact">
+            <p data-slot="section-title">
+              <strong>Compare Another Pair.</strong> <span>Choose two models to compare.</span>
+            </p>
+            <Show
+              when={selectorModels().length > 1}
+	              fallback={
+	                <div data-component="empty-state" data-compact="true">
+	                  <strong>No models found</strong>
+	                  <p>The model list could not be loaded.</p>
+	                </div>
+	              }
+            >
+              <ComparisonSelector
+                models={selectorModels()}
+                firstId={comparisonCatalogEntry(models()[0]).id}
+                secondId={comparisonCatalogEntry(models()[1]).id}
+              />
+            </Show>
+          </section>
+          <ComparisonCardsSection
+            pairs={relatedPairs()}
+            title="Related Model Comparisons"
+            description="Other model pairs to check."
+          />
+        </div>
+        <Footer
+          themePreference={themePreference()}
+          onThemePreferenceChange={updateThemePreference}
+          links={compareFooterLinks}
+          bridge={{ href: "#comparison", label: "COMPARE TABLE" }}
+        />
+      </div>
+    </main>
+  )
+}
+
+function ComparisonHero(props: { models: readonly [ComparisonModel, ComparisonModel] }) {
+  return (
+    <section id="overview" data-section="model-hero">
+      <a data-slot="model-back-link" href={`${import.meta.env.BASE_URL}compare`}>
+        Compare
+      </a>
+      <div data-slot="model-hero-copy">
+        <h1>
+          {props.models[0].name} vs {props.models[1].name}
+        </h1>
+        <p>Compare usage, cost, limits, and features for these two models.</p>
+      </div>
+      <div data-slot="model-hero-pattern" aria-hidden="true" />
+    </section>
+  )
+}
+
+function ComparisonTable(props: { models: readonly [ComparisonModel, ComparisonModel]; rows: ComparisonRow[] }) {
+  return (
+    <div data-component="comparison-table-wrap">
+      <table data-component="comparison-table">
+        <caption>
+          {props.models[0].name} compared with {props.models[1].name}
+        </caption>
+        <thead>
+          <tr>
+            <th scope="col">Metric</th>
+            <For each={props.models}>{(model) => <th scope="col">{model.name}</th>}</For>
+          </tr>
+        </thead>
+        <tbody>
+          <For each={props.rows}>
+            {(row) => {
+              const best = () => bestCellIndex(row)
+              return (
+                <tr>
+                  <th scope="row">
+                    <strong>{row.label}</strong>
+                    <span>{row.description}</span>
+                  </th>
+                  <For each={row.cells}>
+                    {(cell, index) => (
+                      <td data-best={best() === index() ? "true" : undefined}>
+                        <strong>{cell.value}</strong>
+                        <Show when={cell.detail}>{(detail) => <span>{detail()}</span>}</Show>
+                      </td>
+                    )}
+                  </For>
+                </tr>
+              )
+            }}
+          </For>
+        </tbody>
+      </table>
+    </div>
+  )
+}
+
+function resolvedCatalogEntry(catalog: ModelCatalog | undefined, lab: string, model: string) {
+  if (!catalog) return undefined
+  return findModelCatalogEntry(catalog, model, lab) ?? null
+}
+
+function buildComparisonModel(
+  labParam: string,
+  modelParam: string,
+  catalog: ModelCatalogEntry | null,
+  stats: StatsModelComparisonEntry | null,
+): ComparisonModel {
+  return {
+    name: catalog?.name ?? stats?.model ?? formatParamName(modelParam),
+    lab: catalog?.lab ?? stats?.provider ?? catalogSlug(labParam),
+    labName: formatCatalogLabName(catalog?.lab ?? stats?.provider ?? labParam),
+    slug: catalog?.slug ?? stats?.slug ?? catalogSlug(modelParam),
+    catalog,
+    stats,
+  }
+}
+
+function comparisonCatalogEntry(model: ComparisonModel): ModelCatalogEntry {
+  if (model.catalog) return model.catalog
+  return {
+    id: `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`,
+    lab: catalogSlug(model.lab),
+    slug: catalogSlug(model.slug),
+    name: model.name,
+    modalities: { input: [], output: [] },
+    openWeights: false,
+    reasoning: false,
+    toolCall: false,
+    attachment: false,
+    temperature: false,
+    weights: [],
+    benchmarks: [],
+  }
+}
+
+function uniqueCatalogModels(models: ModelCatalogEntry[]) {
+  return Object.values(
+    models.reduce<Record<string, ModelCatalogEntry>>((result, model) => {
+      result[model.id] = result[model.id] ?? model
+      return result
+    }, {}),
+  )
+}
+
+function buildComparisonRows(first: ComparisonModel, second: ComparisonModel): ComparisonRow[] {
+  return [
+    comparisonRow(
+	      "Recent Rank",
+	      "Lower is better.",
+	      {
+	        value: first.stats?.rank == null ? "No usage" : `#${first.stats.rank}`,
+	        score: first.stats?.rank ?? undefined,
+	      },
+	      {
+	        value: second.stats?.rank == null ? "No usage" : `#${second.stats.rank}`,
+	        score: second.stats?.rank ?? undefined,
+	      },
+	      "lower",
+	    ),
+    comparisonRow(
+      "Token Share",
+      "Share of recent OpenCode usage.",
+      { value: first.stats ? formatPercent(first.stats.tokenShare) : "No usage", score: first.stats?.tokenShare },
+      { value: second.stats ? formatPercent(second.stats.tokenShare) : "No usage", score: second.stats?.tokenShare },
+      "higher",
+    ),
+    comparisonRow(
+      "Tokens",
+      "Recent token volume.",
+      { value: first.stats ? formatTokens(first.stats.totals.tokens) : "No usage", score: first.stats?.totals.tokens },
+      {
+        value: second.stats ? formatTokens(second.stats.totals.tokens) : "No usage",
+        score: second.stats?.totals.tokens,
+      },
+      "higher",
+    ),
+    comparisonRow(
+      "Sessions",
+      "Recent session count.",
+      {
+        value: first.stats ? formatInteger(first.stats.totals.sessions) : "No usage",
+        score: first.stats?.totals.sessions,
+      },
+      {
+        value: second.stats ? formatInteger(second.stats.totals.sessions) : "No usage",
+        score: second.stats?.totals.sessions,
+      },
+      "higher",
+    ),
+    comparisonRow(
+      "Cost / 1M Tokens",
+      "Lower is better.",
+      {
+        value: first.stats ? formatMoney(first.stats.totals.costPerMillion) : "No usage",
+        score: positiveScore(first.stats?.totals.costPerMillion),
+      },
+      {
+        value: second.stats ? formatMoney(second.stats.totals.costPerMillion) : "No usage",
+        score: positiveScore(second.stats?.totals.costPerMillion),
+      },
+      "lower",
+    ),
+    comparisonRow(
+      "Cost / Session",
+      "Lower is better.",
+      {
+        value: first.stats ? formatSessionCost(first.stats.totals.costPerSession) : "No usage",
+        score: positiveScore(first.stats?.totals.costPerSession),
+      },
+      {
+        value: second.stats ? formatSessionCost(second.stats.totals.costPerSession) : "No usage",
+        score: positiveScore(second.stats?.totals.costPerSession),
+      },
+      "lower",
+    ),
+    comparisonRow(
+      "Cache Ratio",
+      "Higher is better.",
+      { value: first.stats ? formatPercent(first.stats.totals.cacheRatio) : "No usage", score: first.stats?.totals.cacheRatio },
+      {
+        value: second.stats ? formatPercent(second.stats.totals.cacheRatio) : "No usage",
+        score: second.stats?.totals.cacheRatio,
+      },
+      "higher",
+    ),
+    comparisonRow(
+      "Context Window",
+      "Higher limit is better.",
+      {
+        value: formatCatalogLimit(first.catalog?.limit?.context),
+        score: first.catalog?.limit?.context,
+      },
+      {
+        value: formatCatalogLimit(second.catalog?.limit?.context),
+        score: second.catalog?.limit?.context,
+      },
+      "higher",
+    ),
+    comparisonRow(
+      "Output Limit",
+      "Higher limit is better.",
+      {
+        value: formatCatalogLimit(first.catalog?.limit?.output),
+        score: first.catalog?.limit?.output,
+      },
+      {
+        value: formatCatalogLimit(second.catalog?.limit?.output),
+        score: second.catalog?.limit?.output,
+      },
+      "higher",
+    ),
+    comparisonRow(
+      "Release Date",
+      "Newer release is highlighted.",
+      {
+        value: formatCatalogDate(first.catalog?.releaseDate),
+        score: catalogDateScore(first.catalog?.releaseDate),
+      },
+      {
+        value: formatCatalogDate(second.catalog?.releaseDate),
+        score: catalogDateScore(second.catalog?.releaseDate),
+      },
+      "higher",
+    ),
+    comparisonRow(
+      "Reasoning",
+      "Supports reasoning.",
+      booleanCell(first.catalog?.reasoning),
+      booleanCell(second.catalog?.reasoning),
+      "higher",
+    ),
+    comparisonRow(
+      "Tool Calling",
+      "Supports tool calls.",
+      booleanCell(first.catalog?.toolCall),
+      booleanCell(second.catalog?.toolCall),
+      "higher",
+    ),
+    comparisonRow(
+      "Attachments",
+      "Supports attachments.",
+      booleanCell(first.catalog?.attachment),
+      booleanCell(second.catalog?.attachment),
+      "higher",
+    ),
+    comparisonRow(
+      "Open Weights",
+      "Open weights available.",
+      booleanCell(first.catalog?.openWeights),
+      booleanCell(second.catalog?.openWeights),
+      "higher",
+    ),
+  ]
+}
+
+function comparisonRow(
+  label: string,
+  description: string,
+  first: ComparisonCell,
+  second: ComparisonCell,
+  direction: ComparisonDirection,
+): ComparisonRow {
+  return { label, description, direction, cells: [first, second] }
+}
+
+function bestCellIndex(row: ComparisonRow) {
+  const [first, second] = row.cells.map((cell) => cell.score)
+  if (first === undefined || second === undefined || first === second) return undefined
+  if (row.direction === "higher") return first > second ? 0 : 1
+  return first < second ? 0 : 1
+}
+
+function buildRelatedPairs(
+  catalog: ModelCatalog | undefined,
+  first: ComparisonModel,
+  second: ComparisonModel,
+): ComparisonPair[] {
+  const current = [comparisonRef(first), comparisonRef(second)] as const
+  const alternatives = (catalog?.models ?? [])
+    .filter((model) => model.id !== first.catalog?.id && model.id !== second.catalog?.id)
+    .slice(0, 4)
+    .map(modelRefFromCatalog)
+
+  return uniqueComparisonPairs([
+    ...alternatives.slice(0, 3).flatMap((model, index) => [
+      { first: current[0], second: model, detail: index === 0 ? "Nearby alternative" : "Related comparison" },
+      { first: current[1], second: model, detail: index === 0 ? "Nearby alternative" : "Related comparison" },
+    ]),
+  ]).slice(0, 6)
+}
+
+function comparisonRef(model: ComparisonModel): ComparisonModelRef {
+  return {
+    name: model.name,
+    lab: model.lab,
+    slug: model.slug,
+    labName: model.labName,
+    metric: model.stats ? `#${model.stats.rank}` : "Catalog",
+  }
+}
+
+function positiveScore(value: number | undefined) {
+  return value && value > 0 ? value : undefined
+}
+
+function booleanCell(value: boolean | undefined): ComparisonCell {
+  if (value === undefined) return { value: "Unknown" }
+  return { value: value ? "Yes" : "No", score: value ? 1 : 0 }
+}
+
+function catalogDateScore(value: string | undefined) {
+  if (!value) return undefined
+  const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value)
+  if (!match) return undefined
+  return Date.UTC(Number(match[1]), match[2] ? Number(match[2]) - 1 : 0, match[3] ? Number(match[3]) : 1)
+}
+
+function formatParamName(value: string) {
+  return value
+    .replace(/[-_]/g, " ")
+    .replace(/\b\w/g, (letter) => letter.toUpperCase())
+    .trim()
+}
+
+function formatCatalogLimit(value: number | undefined) {
+  return value === undefined ? "Unknown" : formatTokens(value)
+}
+
+function formatCatalogDate(value: string | undefined) {
+  if (!value) return "Unknown"
+  const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value)
+  if (!match) return value
+  const year = Number(match[1])
+  const month = match[2] ? Number(match[2]) - 1 : 0
+  const day = match[3] ? Number(match[3]) : 1
+  return new Intl.DateTimeFormat("en", {
+    month: match[2] ? "short" : undefined,
+    day: match[3] ? "numeric" : undefined,
+    year: "numeric",
+    timeZone: "UTC",
+  }).format(new Date(Date.UTC(year, month, day)))
+}
+
+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 formatInteger(value: number) {
+  return new Intl.NumberFormat("en").format(value)
+}
+
+function formatPercent(value: number) {
+  return `${trimNumber(value, value >= 10 ? 1 : 2)}%`
+}
+
+function formatMoney(value: number) {
+  if (value >= 1) return `$${trimNumber(value, 2)}`
+  if (value > 0) return `$${value.toFixed(4)}`
+  return "$0"
+}
+
+function formatSessionCost(value: number) {
+  if (value >= 1) return `$${trimNumber(value, 2)}`
+  if (value >= 0.01) return `$${value.toFixed(2)}`
+  if (value > 0) return `$${value.toFixed(4)}`
+  return "$0"
+}
+
+function trimNumber(value: number, digits: number) {
+  return Number(value.toFixed(digits)).toLocaleString("en")
+}

+ 148 - 0
packages/stats/app/src/routes/compare/index.tsx

@@ -0,0 +1,148 @@
+import "../index.css"
+import { Link, Meta, Title } from "@solidjs/meta"
+import { createAsync } from "@solidjs/router"
+import { createMemo, createSignal, onMount, Show } from "solid-js"
+import { getRequestEvent } from "solid-js/web"
+import { ComparisonCardsSection, modelRefFromCatalog, uniqueComparisonPairs } from "../compare-cards"
+import { ComparisonSelector } from "../compare-selector"
+import { getModelCatalog, type ModelCatalogEntry } from "../model-catalog"
+import {
+  applyThemePreference,
+  Footer,
+  getGitHubStars,
+  Header,
+  isThemePreference,
+  themeStorageKey,
+  type HeaderLink,
+  type ThemePreference,
+} from "../stats-shell"
+
+const compareFallbackUrl = "https://stats.opencode.ai"
+const compareTitle = "AI Model Comparison"
+const compareDescription =
+  "Compare AI models used in OpenCode by context, output, release date, usage, rank, token share, and cost."
+const compareHeaderLinks: readonly HeaderLink[] = [
+  { href: "#compare-tool", label: "Compare" },
+  { href: "#model-comparison", label: "Popular" },
+]
+const compareFooterLinks: readonly HeaderLink[] = [
+  { href: import.meta.env.BASE_URL, label: "Data Home" },
+  { href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" },
+  { href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" },
+  { href: `${import.meta.env.BASE_URL}compare`, label: "Model Compare" },
+]
+
+export default function ModelCompareIndex() {
+  const event = getRequestEvent()
+  event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400")
+  const catalog = createAsync(() => getModelCatalog())
+  const githubStars = createAsync(() => getGitHubStars())
+  const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
+  const compareUrl = createMemo(() =>
+    new URL(
+      `${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 pairs = createMemo(() => buildPopularPairs(featuredModels()))
+  const updateThemePreference = (preference: ThemePreference) => {
+    applyThemePreference(preference)
+    setThemePreference(preference)
+    if (typeof window === "undefined") return
+    window.localStorage.setItem(themeStorageKey, preference)
+  }
+
+  onMount(() => {
+    if (typeof window === "undefined") return
+    const preference = window.localStorage.getItem(themeStorageKey)
+    const nextPreference = isThemePreference(preference) ? preference : "system"
+    applyThemePreference(nextPreference)
+    setThemePreference(nextPreference)
+  })
+
+  return (
+    <main data-page="stats" data-theme={themePreference()}>
+      <Title>{compareTitle}</Title>
+      <Meta name="description" content={compareDescription} />
+      <Link rel="canonical" href={compareUrl()} />
+      <Meta property="og:type" content="website" />
+      <Meta property="og:site_name" content="OpenCode" />
+      <Meta property="og:title" content={compareTitle} />
+      <Meta property="og:description" content={compareDescription} />
+      <Meta property="og:url" content={compareUrl()} />
+      <Meta name="twitter:card" content="summary" />
+      <Meta name="twitter:title" content={compareTitle} />
+      <Meta name="twitter:description" content={compareDescription} />
+      <Header githubStars={githubStars() ?? "150K"} links={compareHeaderLinks} brandHref={import.meta.env.BASE_URL} />
+      <div data-component="container">
+        <div data-component="content">
+          <section id="compare-tool" data-section="model-hero">
+            <a data-slot="model-back-link" href={import.meta.env.BASE_URL}>
+              Data
+            </a>
+            <div data-slot="model-hero-grid">
+              <div data-slot="model-hero-copy">
+                <span data-slot="model-id-tag">/compare</span>
+                <h1>Model Comparison</h1>
+                <p>Choose two models and compare usage, cost, limits, and features.</p>
+              </div>
+              <div data-component="model-rank-panel">
+                <span>Model Pairs</span>
+                <strong>{formatPairCount(featuredModels().length)}</strong>
+                <p>Available pairings from the model list.</p>
+              </div>
+            </div>
+            <div data-slot="model-hero-pattern" aria-hidden="true" />
+            <Show
+              when={featuredModels().length > 1}
+	              fallback={
+	                <div data-component="empty-state" data-compact="true">
+	                  <strong>No models found</strong>
+	                  <p>The model list could not be loaded.</p>
+	                </div>
+	              }
+            >
+              <ComparisonSelector models={featuredModels()} />
+            </Show>
+          </section>
+          <ComparisonCardsSection
+            pairs={pairs()}
+            title="Popular Model Comparisons"
+            description="Common model pairs to start with."
+          />
+        </div>
+        <Footer
+          themePreference={themePreference()}
+          onThemePreferenceChange={updateThemePreference}
+          links={compareFooterLinks}
+          bridge={{ href: "#compare-tool", label: "MODEL COMPARE" }}
+        />
+      </div>
+    </main>
+  )
+}
+
+function buildPopularPairs(models: ModelCatalogEntry[]) {
+  return uniqueComparisonPairs(
+    (
+      [
+        [0, 1, "Popular pair"],
+        [0, 2, "Top alternative"],
+        [1, 2, "Nearby pair"],
+        [2, 3, "Recent model pair"],
+        [3, 4, "Model pair"],
+        [4, 5, "Model pair"],
+      ] as const
+    ).flatMap(([firstIndex, secondIndex, detail]) => {
+      const first = models[firstIndex]
+      const second = models[secondIndex]
+      return first && second ? [{ first: modelRefFromCatalog(first), second: modelRefFromCatalog(second), detail }] : []
+    }),
+  )
+}
+
+function formatPairCount(count: number) {
+  if (count < 2) return "0"
+  return new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 }).format((count * (count - 1)) / 2)
+}

+ 253 - 2
packages/stats/app/src/routes/index.css

@@ -5252,6 +5252,243 @@
   color: var(--stats-text);
   color: var(--stats-text);
 }
 }
 
 
+[data-page="stats"] [data-section="model-panel"][data-variant="compact"] {
+  padding-top: 56px;
+  padding-bottom: 56px;
+}
+
+[data-page="stats"] [data-component="comparison-card-grid"] {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 8px;
+}
+
+[data-page="stats"] [data-component="comparison-card"] {
+  display: grid;
+  align-content: space-between;
+  gap: 18px;
+  min-height: 168px;
+  min-width: 0;
+  padding: 16px;
+  border: 1px solid var(--stats-line);
+  background: var(--stats-layer);
+  color: var(--stats-text);
+  text-decoration: none;
+}
+
+[data-page="stats"] [data-component="comparison-card"]:hover,
+[data-page="stats"] [data-component="comparison-card"]:focus-visible {
+  border-color: var(--stats-text);
+  outline: none;
+  background: var(--stats-bg);
+  text-decoration: none;
+}
+
+[data-page="stats"] [data-component="comparison-card"] span,
+[data-page="stats"] [data-component="comparison-card"] p,
+[data-page="stats"] [data-component="comparison-card"] small {
+  color: var(--stats-muted);
+  font-size: 11px;
+  font-weight: 500;
+  line-height: 1.35;
+}
+
+[data-page="stats"] [data-component="comparison-card"] strong {
+  min-width: 0;
+  color: var(--stats-text);
+  font-size: 20px;
+  font-weight: 500;
+  line-height: 1.08;
+  overflow-wrap: anywhere;
+}
+
+[data-page="stats"] [data-component="comparison-card"] em {
+  color: var(--stats-faint);
+  font: inherit;
+}
+
+[data-page="stats"] [data-component="comparison-card"] p {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin: 0;
+}
+
+[data-page="stats"] [data-component="comparison-card"] b {
+  min-width: 0;
+  overflow: hidden;
+  font: inherit;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+[data-page="stats"] [data-component="comparison-card"] i {
+  flex: 0 0 auto;
+  width: 16px;
+  height: 1px;
+  background: var(--stats-line-strong);
+}
+
+[data-page="stats"] [data-component="comparison-selector"] {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
+  gap: 8px;
+  align-items: end;
+  max-width: 920px;
+}
+
+[data-page="stats"] [data-component="comparison-selector"] label {
+  display: grid;
+  gap: 8px;
+  min-width: 0;
+}
+
+[data-page="stats"] [data-component="comparison-selector"] label span {
+  color: var(--stats-muted);
+  font-size: 11px;
+  font-weight: 500;
+  line-height: 1.2;
+}
+
+[data-page="stats"] [data-component="comparison-selector"] select,
+[data-page="stats"] [data-component="comparison-selector"] button {
+  box-sizing: border-box;
+  height: 40px;
+  min-width: 0;
+  border: 1px solid var(--stats-line);
+  border-radius: 0;
+  appearance: none;
+  font: inherit;
+  font-size: 13px;
+  font-weight: 500;
+  line-height: 1;
+}
+
+[data-page="stats"] [data-component="comparison-selector"] select {
+  width: 100%;
+  padding: 0 36px 0 12px;
+  background:
+    linear-gradient(45deg, transparent 50%, var(--stats-muted) 50%) calc(100% - 18px) 17px / 5px 5px no-repeat,
+    linear-gradient(135deg, var(--stats-muted) 50%, transparent 50%) calc(100% - 13px) 17px / 5px 5px no-repeat,
+    var(--stats-bg);
+  color: var(--stats-text);
+}
+
+[data-page="stats"] [data-component="comparison-selector"] button {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 14px;
+  background: var(--stats-text);
+  color: var(--stats-bg);
+  cursor: pointer;
+  text-decoration: none;
+  white-space: nowrap;
+}
+
+[data-page="stats"] [data-component="comparison-selector"] button:disabled {
+  cursor: not-allowed;
+  opacity: 0.45;
+}
+
+[data-page="stats"] [data-component="comparison-selector"] select:focus-visible,
+[data-page="stats"] [data-component="comparison-selector"] button:focus-visible {
+  outline: 2px solid var(--stats-accent);
+  outline-offset: 2px;
+}
+
+[data-page="stats"] [data-component="comparison-table-wrap"] {
+  overflow-x: auto;
+  border: 1px solid var(--stats-line);
+  background: var(--stats-layer);
+}
+
+[data-page="stats"] [data-component="comparison-table"] {
+  width: 100%;
+  min-width: 720px;
+  border-collapse: collapse;
+  color: var(--stats-text);
+  font-size: 13px;
+  line-height: 1.35;
+}
+
+[data-page="stats"] [data-component="comparison-table"] caption {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  overflow: hidden;
+  clip: rect(0 0 0 0);
+  white-space: nowrap;
+}
+
+[data-page="stats"] [data-component="comparison-table"] th,
+[data-page="stats"] [data-component="comparison-table"] td {
+  min-width: 0;
+  padding: 14px 16px;
+  border-bottom: 1px solid var(--stats-line);
+  border-left: 1px solid var(--stats-line);
+  text-align: left;
+  vertical-align: top;
+}
+
+[data-page="stats"] [data-component="comparison-table"] th:first-child {
+  border-left: 0;
+}
+
+[data-page="stats"] [data-component="comparison-table"] thead th {
+  color: var(--stats-muted);
+  font-size: 11px;
+  font-weight: 600;
+  line-height: 1.2;
+  text-transform: uppercase;
+}
+
+[data-page="stats"] [data-component="comparison-table"] tbody tr:last-child th,
+[data-page="stats"] [data-component="comparison-table"] tbody tr:last-child td {
+  border-bottom: 0;
+}
+
+[data-page="stats"] [data-component="comparison-table"] tbody th {
+  width: 34%;
+  background: var(--stats-bg);
+}
+
+[data-page="stats"] [data-component="comparison-table"] tbody th strong,
+[data-page="stats"] [data-component="comparison-table"] td strong {
+  display: block;
+  color: var(--stats-text);
+  font-size: 13px;
+  font-weight: 600;
+  line-height: 1.25;
+  overflow-wrap: anywhere;
+}
+
+[data-page="stats"] [data-component="comparison-table"] tbody th span,
+[data-page="stats"] [data-component="comparison-table"] td span {
+  display: block;
+  margin-top: 5px;
+  color: var(--stats-muted);
+  font-size: 11px;
+  font-weight: 500;
+  line-height: 1.35;
+}
+
+[data-page="stats"] [data-component="comparison-table"] td[data-best="true"] {
+  position: relative;
+  background: var(--stats-bg);
+  box-shadow: inset 0 0 0 1px var(--stats-accent);
+}
+
+[data-page="stats"] [data-component="comparison-table"] td[data-best="true"]::before {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 3px;
+  content: "";
+  background: var(--stats-accent);
+}
+
 [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;
@@ -5784,10 +6021,19 @@
   [data-page="stats"] [data-component="model-metric-grid"],
   [data-page="stats"] [data-component="model-metric-grid"],
   [data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
   [data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
   [data-page="stats"] [data-component="model-efficiency-grid"],
   [data-page="stats"] [data-component="model-efficiency-grid"],
-  [data-page="stats"] [data-component="lab-model-grid"] {
+  [data-page="stats"] [data-component="lab-model-grid"],
+  [data-page="stats"] [data-component="comparison-card-grid"] {
     grid-template-columns: 1fr 1fr;
     grid-template-columns: 1fr 1fr;
   }
   }
 
 
+  [data-page="stats"] [data-component="comparison-selector"] {
+    grid-template-columns: 1fr;
+  }
+
+  [data-page="stats"] [data-component="comparison-selector"] button {
+    width: 100%;
+  }
+
   [data-page="stats"] [data-component="model-peer-list"] a {
   [data-page="stats"] [data-component="model-peer-list"] a {
     grid-template-columns: 32px 32px minmax(0, 1fr) minmax(64px, auto);
     grid-template-columns: 32px 32px minmax(0, 1fr) minmax(64px, auto);
     gap: 12px;
     gap: 12px;
@@ -6212,10 +6458,15 @@
   [data-page="stats"] [data-component="model-metric-grid"],
   [data-page="stats"] [data-component="model-metric-grid"],
   [data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
   [data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
   [data-page="stats"] [data-component="model-efficiency-grid"],
   [data-page="stats"] [data-component="model-efficiency-grid"],
-  [data-page="stats"] [data-component="lab-model-grid"] {
+  [data-page="stats"] [data-component="lab-model-grid"],
+  [data-page="stats"] [data-component="comparison-card-grid"] {
     grid-template-columns: 1fr;
     grid-template-columns: 1fr;
   }
   }
 
 
+  [data-page="stats"] [data-component="comparison-table"] {
+    min-width: 640px;
+  }
+
   [data-page="stats"] [data-component="model-metric"] {
   [data-page="stats"] [data-component="model-metric"] {
     min-height: 112px;
     min-height: 112px;
   }
   }

+ 37 - 1
packages/stats/app/src/routes/index.tsx

@@ -34,6 +34,7 @@ import { localizedUrl } from "../lib/language"
 import { findModelCatalogEntry, getModelCatalog, type ModelCatalog } from "./model-catalog"
 import { findModelCatalogEntry, getModelCatalog, type ModelCatalog } from "./model-catalog"
 import { SectionHeading } from "./section-heading"
 import { SectionHeading } from "./section-heading"
 import { setStatsPageCacheHeaders } from "./stats-cache"
 import { setStatsPageCacheHeaders } from "./stats-cache"
+import { ComparisonCardsSection, uniqueComparisonPairs, type ComparisonModelRef } from "./compare-cards"
 import {
 import {
   applyThemePreference,
   applyThemePreference,
   Footer,
   Footer,
@@ -48,6 +49,12 @@ import {
 const products = ["All Users", "Zen", "Go"] as const
 const products = ["All Users", "Zen", "Go"] as const
 const tokenProducts = ["Zen", "Go"] as const
 const tokenProducts = ["Zen", "Go"] as const
 const ranges = ["1D", "1W", "2W", "1M", "2M"] as const
 const ranges = ["1D", "1W", "2W", "1M", "2M"] as const
+const comparisonPairIndexes = [
+  [0, 1, "Top two by recent usage"],
+  [0, 2, "Leader vs challenger"],
+  [1, 2, "Adjacent leaderboard pair"],
+  [2, 3, "Top model alternative"],
+] as const
 const statsUnfurlPath = "banner.jpg"
 const statsUnfurlPath = "banner.jpg"
 const usageColors = [
 const usageColors = [
   "#ed6aff",
   "#ed6aff",
@@ -183,11 +190,20 @@ export default function StatsHome() {
                 <CacheRatioSection data={stats().cacheRatio} />
                 <CacheRatioSection data={stats().cacheRatio} />
                 <MarketShareSection data={stats().market} />
                 <MarketShareSection data={stats().market} />
                 <GeoBreakdownSection data={stats().country} />
                 <GeoBreakdownSection data={stats().country} />
+                <ComparisonCardsSection
+                  pairs={homeComparisonPairs(stats().leaderboard["All Users"]["2M"])}
+                  title="Model Comparisons"
+                  description="Popular model pairs from the leaderboard."
+                />
               </>
               </>
             )}
             )}
           </Show>
           </Show>
         </div>
         </div>
-        <Footer themePreference={themePreference()} onThemePreferenceChange={updateThemePreference} />
+        <Footer
+          themePreference={themePreference()}
+          onThemePreferenceChange={updateThemePreference}
+          bridge={{ href: "#model-comparison", label: "MODEL COMPARISONS" }}
+        />
       </div>
       </div>
     </main>
     </main>
   )
   )
@@ -1969,6 +1985,26 @@ function formatSessionCost(value: number) {
   return `$${value.toFixed(4)}`
   return `$${value.toFixed(4)}`
 }
 }
 
 
+function homeComparisonPairs(leaderboard: LeaderboardEntry[]) {
+  return uniqueComparisonPairs(
+    comparisonPairIndexes.flatMap(([firstIndex, secondIndex, detail]) => {
+      const first = leaderboard[firstIndex]
+      const second = leaderboard[secondIndex]
+      return first && second ? [{ first: leaderboardRef(first), second: leaderboardRef(second), detail }] : []
+    }),
+  )
+}
+
+function leaderboardRef(entry: LeaderboardEntry): ComparisonModelRef {
+  return {
+    name: entry.model,
+    lab: entry.provider,
+    slug: modelSlug(entry.model),
+    labName: entry.author,
+    metric: `#${entry.rank} / ${formatBillions(entry.tokens)}`,
+  }
+}
+
 function modelSlug(value: string) {
 function modelSlug(value: string) {
   return value
   return value
     .trim()
     .trim()

+ 4 - 1
packages/stats/app/src/routes/stats-shell.tsx

@@ -216,6 +216,7 @@ export function Footer(props: {
   themePreference: ThemePreference
   themePreference: ThemePreference
   onThemePreferenceChange: (preference: ThemePreference) => void
   onThemePreferenceChange: (preference: ThemePreference) => void
   links?: readonly HeaderLink[]
   links?: readonly HeaderLink[]
+  bridge?: HeaderLink | null
 }) {
 }) {
   const i18n = useI18n()
   const i18n = useI18n()
   const language = useLanguage()
   const language = useLanguage()
@@ -241,10 +242,12 @@ export function Footer(props: {
     { href: githubLink.href, label: i18n.t("header.github") },
     { href: githubLink.href, label: i18n.t("header.github") },
     { href: "https://www.youtube.com/@anomaly-co", label: i18n.t("footer.youtube") },
     { href: "https://www.youtube.com/@anomaly-co", label: i18n.t("footer.youtube") },
   ]
   ]
+  const bridge = () =>
+    props.bridge === undefined ? { href: "#geo-breakdown", label: i18n.t("nav.geoBreakdown").toUpperCase() } : props.bridge
 
 
   return (
   return (
     <footer data-component="footer">
     <footer data-component="footer">
-      <SectionBridge label={i18n.t("nav.geoBreakdown").toUpperCase()} href="#geo-breakdown" />
+      <Show when={bridge()}>{(link) => <SectionBridge label={link().label} href={link().href} />}</Show>
       <div data-slot="footer-grid">
       <div data-slot="footer-grid">
         <a data-slot="footer-mark" href="https://opencode.ai" aria-label={i18n.t("footer.homeAria")}>
         <a data-slot="footer-mark" href="https://opencode.ai" aria-label={i18n.t("footer.homeAria")}>
           <OpenCodeMark />
           <OpenCodeMark />

+ 57 - 1
packages/stats/core/src/domain/home.ts

@@ -1,8 +1,9 @@
 import { Client } from "@planetscale/database"
 import { Client } from "@planetscale/database"
 import { Effect } from "effect"
 import { Effect } from "effect"
 import { Resource } from "sst/resource"
 import { Resource } from "sst/resource"
+import { DatabaseError } from "../database"
 import type { GeoStatMetric } from "./geo"
 import type { GeoStatMetric } from "./geo"
-import type { ModelStatMetric } from "./model"
+import { ModelStatRepo, type ModelStatMetric } from "./model"
 import type { ProviderStatMetric } from "./provider"
 import type { ProviderStatMetric } from "./provider"
 
 
 export type UsageProduct = "All Users" | "Zen" | "Go" | "Enterprise"
 export type UsageProduct = "All Users" | "Zen" | "Go" | "Enterprise"
@@ -81,6 +82,23 @@ export type StatsLabData = {
   usage: ModelUsagePoint[]
   usage: ModelUsagePoint[]
   models: LabUsageModelEntry[]
   models: LabUsageModelEntry[]
 }
 }
+export type StatsModelComparisonEntry = {
+  updatedAt: string | null
+  model: string
+  slug: string
+  provider: string
+  author: string
+  rank: number | null
+  previousRank: number | null
+  totalModels: number
+  tokenShare: number
+  tokenChange: number
+  totals: StatsModelData["totals"]
+}
+export type StatsModelComparisonData = {
+  updatedAt: string | null
+  models: [StatsModelComparisonEntry | null, StatsModelComparisonEntry | null]
+}
 export type StatsHomeData = {
 export type StatsHomeData = {
   updatedAt: string | null
   updatedAt: string | null
   usage: Record<UsageProduct, Record<UsageRange, UsagePoint[]>>
   usage: Record<UsageProduct, Record<UsageRange, UsagePoint[]>>
@@ -271,6 +289,27 @@ function dateValue(value: unknown) {
   return value instanceof Date ? value : new Date(stringValue(value))
   return value instanceof Date ? value : new Date(stringValue(value))
 }
 }
 
 
+export const getStatsModelComparisonData: (
+  firstProvider: string,
+  firstModel: string,
+  secondProvider: string,
+  secondModel: string,
+) => Effect.Effect<StatsModelComparisonData, DatabaseError, ModelStatRepo> = Effect.fn("StatsModelComparison.getData")(
+  function* (firstProvider, firstModel, secondProvider, secondModel) {
+    const modelStats = yield* ModelStatRepo
+    const rows = yield* modelStats.listDaily()
+    const first = toComparisonEntry(buildStatsModelData(firstModel, rows, [], firstProvider))
+    const second = toComparisonEntry(buildStatsModelData(secondModel, rows, [], secondProvider))
+    const latest = [first?.updatedAt, second?.updatedAt]
+      .flatMap((value) => (value ? [dateTime(value)] : []))
+      .toSorted((a, b) => b - a)[0]
+    return {
+      updatedAt: latest === undefined ? null : new Date(latest).toISOString(),
+      models: [first, second],
+    }
+  },
+)
+
 function buildStatsHomeData(
 function buildStatsHomeData(
   modelRows: ModelStatMetric[],
   modelRows: ModelStatMetric[],
   providerRows: ProviderStatMetric[],
   providerRows: ProviderStatMetric[],
@@ -448,6 +487,23 @@ function buildStatsLabData(providerParam: string, modelRows: ModelStatMetric[]):
   }
   }
 }
 }
 
 
+function toComparisonEntry(data: StatsModelData | null): StatsModelComparisonEntry | null {
+  if (!data) return null
+  return {
+    updatedAt: data.updatedAt,
+    model: data.model,
+    slug: data.slug,
+    provider: data.provider,
+    author: data.author,
+    rank: data.rank,
+    previousRank: data.previousRank,
+    totalModels: data.totalModels,
+    tokenShare: data.tokenShare,
+    tokenChange: data.tokenChange,
+    totals: data.totals,
+  }
+}
+
 function emptyStatsHomeData(): StatsHomeData {
 function emptyStatsHomeData(): StatsHomeData {
   return {
   return {
     updatedAt: null,
     updatedAt: null,