Kaynağa Gözat

feat(stats): add comparison seo pages

Adam 1 ay önce
ebeveyn
işleme
233f069070

+ 4 - 1
packages/console/app/public/robots.txt

@@ -3,4 +3,7 @@ Allow: /
 
 # Disallow shared content pages
 Disallow: /s/
-Disallow: /share/
+Disallow: /share/
+
+Sitemap: https://opencode.ai/sitemap.xml
+Sitemap: https://opencode.ai/data/sitemap.xml

+ 1174 - 0
packages/stats/app/src/component/model-compare-detail.tsx

@@ -0,0 +1,1174 @@
+import "../routes/index.css"
+import { Link, Meta, Title } from "@solidjs/meta"
+import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
+import {
+  getStatsModelsComparisonData,
+  type ModelUsagePoint,
+  type StatsModelComparisonInput,
+  type StatsModelComparisonEntry,
+} from "@opencode-ai/stats-core/domain/home"
+import { runtime } from "@opencode-ai/stats-core/runtime"
+import { createAsync, query, useParams, useSearchParams } from "@solidjs/router"
+import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js"
+import { getRequestEvent } from "solid-js/web"
+import {
+  ComparisonCardsSection,
+  comparisonHref,
+  modelRefFromCatalog,
+  uniqueComparisonPairs,
+  type ComparisonModelRef,
+  type ComparisonPair,
+} from "../routes/compare-cards"
+import { ComparisonRadar } from "../routes/compare-radar"
+import {
+  catalogSlug,
+  findModelCatalogEntry,
+  formatCatalogLabName,
+  getModelCatalog,
+  type ModelCatalog,
+  type ModelCatalogEntry,
+} from "../routes/model-catalog"
+import {
+  applyThemePreference,
+  Footer,
+  getGitHubStars,
+  Header,
+  isThemePreference,
+  themeStorageKey,
+  type HeaderLink,
+  type ThemePreference,
+} from "../routes/stats-shell"
+import {
+  canonicalFamilyComparisonPath,
+  canonicalModelComparisonPath,
+  latestFamilyComparisonPath,
+  type ResolvedComparisonFamily,
+} from "../lib/comparison-pages"
+import { baseUrl } from "../lib/language"
+
+const compareHeaderLinks: readonly HeaderLink[] = [
+  { href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" },
+  { href: `${import.meta.env.BASE_URL}#leaderboard`, label: "Leaderboard" },
+  { href: `${import.meta.env.BASE_URL}#market-share`, label: "Market Share" },
+  { href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" },
+  { href: `${import.meta.env.BASE_URL}#session-cost`, label: "Session Cost" },
+]
+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" },
+]
+const heroLabs = [
+  { lab: "deepseek", label: "DeepSeek" },
+  { lab: "openai", label: "OpenAI" },
+  { lab: "anthropic", label: "Anthropic" },
+] as const
+const usageBarLimit = 60
+const comparisonModelLimit = 6
+
+type ComparisonModel = {
+  name: string
+  lab: string
+  labName: string
+  slug: string
+  catalog: ModelCatalogEntry | null
+  stats: StatsModelComparisonEntry | null
+}
+type ComparisonDirection = "higher" | "lower"
+type ComparisonDetailCell = {
+  value: string
+  unit?: string
+  href?: string
+  kind?: "boolean"
+  score?: number
+  trend?: number
+}
+type ComparisonDetailRow = {
+  label: string
+  direction?: ComparisonDirection
+  cells: ComparisonDetailCell[]
+}
+type ComparisonDetailSection = {
+  title: string
+  badge?: string
+  rows: ComparisonDetailRow[]
+  usage?: ModelUsagePoint[][]
+}
+type ComparisonModelSelection = {
+  lab: string
+  slug: string
+  catalog: ModelCatalogEntry | null | undefined
+}
+type ComparisonModels = [ComparisonModel, ComparisonModel, ...ComparisonModel[]]
+
+export type ModelCompareDetailPageProps = {
+  first?: { lab: string; slug: string }
+  second?: { lab: string; slug: string }
+  family?: { first: ResolvedComparisonFamily; second: ResolvedComparisonFamily }
+  catalog?: ModelCatalog
+}
+
+const getComparisonData = query(async (models: StatsModelComparisonInput[]) => {
+  "use server"
+  return runtime.runPromise(getStatsModelsComparisonData(models))
+}, "getStatsModelComparisonDetailData")
+
+export default function ModelCompareDetailPage(props: ModelCompareDetailPageProps = {}) {
+  const event = getRequestEvent()
+  event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400")
+  const params = useParams()
+  const [searchParams] = useSearchParams<{ add?: string }>()
+  const firstLabParam = createMemo(() => props.first?.lab ?? params.firstLab ?? "")
+  const firstModelParam = createMemo(() => props.first?.slug ?? params.firstModel ?? "")
+  const secondLabParam = createMemo(() => props.second?.lab ?? params.secondLab ?? "")
+  const secondModelParam = createMemo(() => props.second?.slug ?? params.secondModel ?? "")
+  const catalogData = createAsync(() => getModelCatalog())
+  const catalog = createMemo(() => props.catalog ?? catalogData())
+  const firstCatalog = createMemo(() => resolvedCatalogEntry(catalog(), firstLabParam(), firstModelParam()))
+  const secondCatalog = createMemo(() => resolvedCatalogEntry(catalog(), secondLabParam(), secondModelParam()))
+  const modelSelections = createMemo(() => {
+    const selected: ComparisonModelSelection[] = [
+      { lab: firstLabParam(), slug: firstModelParam(), catalog: firstCatalog() },
+      { lab: secondLabParam(), slug: secondModelParam(), catalog: secondCatalog() },
+      ...parseAdditionalModels(searchParams.add).map((model) => ({
+        ...model,
+        catalog: resolvedCatalogEntry(catalog(), model.lab, model.slug),
+      })),
+    ]
+    return selected
+      .filter(
+        (model, index) =>
+          index < 2 ||
+          selected.findIndex(
+            (candidate) => comparisonModelSelectionKey(candidate) === comparisonModelSelectionKey(model),
+          ) === index,
+      )
+      .slice(0, comparisonModelLimit) as [
+      ComparisonModelSelection,
+      ComparisonModelSelection,
+      ...ComparisonModelSelection[],
+    ]
+  })
+  const stats = createAsync(() => {
+    const selected = modelSelections()
+    if (catalog() === undefined || selected.some((model) => model.catalog === undefined))
+      return Promise.resolve(undefined)
+    return getComparisonData(
+      selected.map((model) => ({
+        provider: model.catalog?.lab ?? model.lab,
+        model: model.catalog?.slug ?? model.slug,
+      })),
+    )
+  })
+  const githubStars = createAsync(() => getGitHubStars())
+  const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
+  const [highlightBest, setHighlightBest] = createSignal(true)
+  const [addingModel, setAddingModel] = createSignal(false)
+  let comparisonHeadingScroll: HTMLDivElement | undefined
+  let comparisonBodyScroll: HTMLDivElement | undefined
+  const models = createMemo(
+    () =>
+      modelSelections().map((model, index) =>
+        buildComparisonModel(model.lab, model.slug, model.catalog ?? null, stats()?.models[index] ?? null),
+      ) as ComparisonModels,
+  )
+  const title = createMemo(() => {
+    if (props.family)
+      return `${props.family.first.name} vs ${props.family.second.name}: ${models()[0].name} vs ${models()[1].name}`
+    return `${models()[0].name} vs ${models()[1].name} - Model Comparison`
+  })
+  const description = createMemo(() => {
+    if (props.family)
+      return `Compare the latest ${props.family.first.name} and ${props.family.second.name} models: ${models()[0].name} vs ${models()[1].name}. See benchmarks, usage, price, context length, and features.`
+    return `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(() => {
+    if (props.family) return canonicalFamilyComparisonPath(props.family.first, props.family.second)
+    const first = firstCatalog()
+    const second = secondCatalog()
+    const source = catalog()
+    if (source && first && second)
+      return latestFamilyComparisonPath(source, first, second) ?? canonicalModelComparisonPath(first, second)
+    return canonicalModelComparisonPath(comparisonCatalogEntry(models()[0]), comparisonCatalogEntry(models()[1]))
+  })
+  const canonicalUrl = createMemo(() => new URL(canonicalPath(), baseUrl).toString())
+  const detailSections = createMemo(() => buildComparisonDetailSections(models()))
+  const relatedPairs = createMemo(() => buildRelatedPairs(catalog(), models()[0], models()[1]))
+  const selectorModels = createMemo(() =>
+    uniqueCatalogModels([...models().map(comparisonCatalogEntry), ...(catalog()?.models ?? [])]),
+  )
+  const selectedCatalogModels = createMemo(() => models().map(comparisonCatalogEntry))
+  const canAddModel = createMemo(
+    () =>
+      models().length < comparisonModelLimit &&
+      selectorModels().some((model) => !selectedCatalogModels().some((selected) => selected.id === model.id)),
+  )
+  const navigateToModels = (next: ModelCatalogEntry[]) => {
+    if (typeof window === "undefined" || next.length < 2) return
+    window.location.href = comparisonModelsHref(next)
+  }
+  const syncComparisonScroll = (source: HTMLDivElement, target: HTMLDivElement | undefined) => {
+    if (target && target.scrollLeft !== source.scrollLeft) target.scrollLeft = source.scrollLeft
+  }
+  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-layout="compare-detail" data-theme={themePreference()}>
+      <Show when={catalog() !== undefined}>
+        <Title>{title()}</Title>
+        <Meta name="description" content={description()} />
+        <Meta name="robots" content={models().length > 2 ? "noindex,follow" : "index,follow"} />
+        <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>
+      </Show>
+      <Header githubStars={githubStars() ?? "150K"} links={compareHeaderLinks} brandHref={import.meta.env.BASE_URL} />
+      <div data-component="container">
+        <div data-component="content">
+          <ComparisonHero
+            canAddModel={canAddModel()}
+            highlightBest={highlightBest()}
+            models={models()}
+            onAddModel={() => setAddingModel(true)}
+            onHighlightBestChange={() => setHighlightBest(!highlightBest())}
+          />
+          <Show when={addingModel()}>
+            <CompareModelSelectModal
+              blockedIds={selectedCatalogModels().map((model) => model.id)}
+              label="Add model"
+              models={selectorModels()}
+              onClose={() => setAddingModel(false)}
+              onSelect={(model) => {
+                setAddingModel(false)
+                navigateToModels([...selectedCatalogModels(), model])
+              }}
+            />
+          </Show>
+          <ComparisonRadar models={models()} catalogModels={catalog()?.models ?? []} />
+          <div
+            data-component="compare-detail-table"
+            data-model-count={models().length}
+            style={`--compare-detail-grid: ${comparisonDetailGridTemplate(models().length)}`}
+          >
+            <div
+              data-component="compare-detail-heading-scroll"
+              ref={(element) => (comparisonHeadingScroll = element)}
+              onScroll={(event) => syncComparisonScroll(event.currentTarget, comparisonBodyScroll)}
+            >
+              <ComparisonPairSelector catalogModels={selectorModels()} models={models()} />
+            </div>
+            <div
+              data-component="compare-detail-body-scroll"
+              ref={(element) => (comparisonBodyScroll = element)}
+              onScroll={(event) => syncComparisonScroll(event.currentTarget, comparisonHeadingScroll)}
+            >
+              <Show
+                when={stats() !== undefined}
+                fallback={
+                  <section data-section="compare-detail-matrix">
+                    <div data-component="empty-state" data-compact="true">
+                      <strong>Loading comparison</strong>
+                      <p>Loading model stats.</p>
+                    </div>
+                  </section>
+                }
+              >
+                <ComparisonDetailMatrix
+                  highlightBest={highlightBest()}
+                  modelCount={models().length}
+                  sections={detailSections()}
+                />
+              </Show>
+            </div>
+          </div>
+          <ComparisonCardsSection
+            pairs={relatedPairs()}
+            title="Related comparisons"
+            description="Other model pairs to check."
+            variant="featured"
+          />
+        </div>
+        <Footer
+          themePreference={themePreference()}
+          onThemePreferenceChange={updateThemePreference}
+          links={compareFooterLinks}
+          bridge={{ href: "#comparison", label: "MODEL COMPARISON" }}
+        />
+      </div>
+    </main>
+  )
+}
+
+function ComparisonHero(props: {
+  models: readonly ComparisonModel[]
+  canAddModel: boolean
+  highlightBest: boolean
+  onAddModel: () => void
+  onHighlightBestChange: () => void
+}) {
+  return (
+    <section id="overview" data-section="compare-detail-hero">
+      <nav data-component="compare-home-breadcrumb" aria-label="Breadcrumb">
+        <a data-slot="compare-home-crumb" href={import.meta.env.BASE_URL}>
+          Data
+        </a>
+        <span data-slot="compare-home-separator">/</span>
+        <a data-slot="compare-home-crumb" href={`${import.meta.env.BASE_URL}compare`}>
+          Compare
+        </a>
+      </nav>
+      <div data-slot="compare-detail-hero-grid">
+        <h1 aria-label={`Compare ${props.models.map((model) => model.name).join(", ")}`}>
+          <span>Compare</span>
+          <HeroModelStack />
+          <span>AI models</span>
+        </h1>
+        <div data-slot="compare-detail-actions">
+          <button
+            type="button"
+            data-slot="compare-detail-action"
+            data-active={props.highlightBest ? "true" : undefined}
+            aria-pressed={props.highlightBest}
+            onClick={props.onHighlightBestChange}
+          >
+            <span data-slot="compare-detail-highlight-icon" aria-hidden="true">
+              <i />
+              <i />
+            </span>
+            <span>Highlight best</span>
+          </button>
+          <button
+            type="button"
+            data-slot="compare-detail-action"
+            disabled={!props.canAddModel}
+            aria-haspopup="dialog"
+            onClick={props.onAddModel}
+          >
+            <span data-slot="compare-home-plus" aria-hidden="true">
+              +
+            </span>
+            <span>Add model</span>
+          </button>
+        </div>
+      </div>
+      <div data-slot="compare-home-pattern" aria-hidden="true" />
+    </section>
+  )
+}
+
+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 ComparisonPairSelector(props: { catalogModels: ModelCatalogEntry[]; models: readonly ComparisonModel[] }) {
+  const [activeIndex, setActiveIndex] = createSignal<number>()
+  const selectedModels = createMemo(() => props.models.map(comparisonCatalogEntry))
+  const activeSelected = createMemo(() => {
+    const index = activeIndex()
+    if (index === undefined) return undefined
+    return selectedModels()[index]
+  })
+  const blockedIds = createMemo(() =>
+    selectedModels()
+      .filter((_, index) => index !== activeIndex())
+      .map((model) => model.id),
+  )
+  const navigateToSelection = (index: number, model: ModelCatalogEntry) => {
+    if (typeof window === "undefined") return
+    window.location.href = comparisonModelsHref(
+      selectedModels().map((selected, selectedIndex) => (selectedIndex === index ? model : selected)),
+    )
+  }
+
+  return (
+    <section data-section="compare-detail-selector" aria-label="Selected models">
+      <div data-component="compare-detail-selector-grid">
+        <div data-slot="compare-detail-selector-spacer" aria-hidden="true" />
+        <For each={props.models}>
+          {(model, index) => (
+            <CompareDetailSelectButton
+              model={model}
+              label={`Model ${index() + 1}`}
+              column={index()}
+              last={index() === props.models.length - 1}
+              expanded={activeIndex() === index()}
+              onOpen={() => setActiveIndex(index())}
+            />
+          )}
+        </For>
+      </div>
+      <Show when={activeSelected()}>
+        {(selected) => (
+          <CompareModelSelectModal
+            blockedIds={blockedIds()}
+            label={`Choose model ${(activeIndex() ?? 0) + 1}`}
+            models={props.catalogModels}
+            selected={selected()}
+            onClose={() => setActiveIndex(undefined)}
+            onSelect={(model) => {
+              const index = activeIndex()
+              if (index === undefined) return
+              setActiveIndex(undefined)
+              navigateToSelection(index, model)
+            }}
+          />
+        )}
+      </Show>
+    </section>
+  )
+}
+
+function CompareDetailSelectButton(props: {
+  model: ComparisonModel
+  label: string
+  column: number
+  last: boolean
+  expanded: boolean
+  onOpen: () => void
+}) {
+  return (
+    <button
+      type="button"
+      data-slot="compare-detail-select-model"
+      data-column={props.column}
+      data-last={props.last ? "true" : undefined}
+      aria-label={props.label}
+      aria-haspopup="dialog"
+      aria-expanded={props.expanded}
+      onClick={props.onOpen}
+    >
+      <LabLogo lab={props.model.lab} label={props.model.labName} size="small" />
+      <span data-slot="compare-detail-select-name">{props.model.name}</span>
+      <ChevronDownIcon />
+    </button>
+  )
+}
+
+function CompareModelSelectModal(props: {
+  models: ModelCatalogEntry[]
+  selected?: ModelCatalogEntry
+  blockedIds: 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(() =>
+    uniqueCatalogModels([...(props.selected ? [props.selected] : []), ...props.models]).filter(
+      (model) => !props.blockedIds.includes(model.id),
+    ),
+  )
+  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 models = filteredModels()
+    if (models.some((model) => model.id === previewId())) return
+    const selected =
+      props.selected && models.some((model) => model.id === props.selected?.id) ? props.selected.id : undefined
+    setPreviewId(selected ?? 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}
+                    onMouseMove={() => 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)} />
+        <CompareModelFact label="URL" value={formatModelUrl(props.model)} href={modelHref(props.model)} />
+      </dl>
+    </aside>
+  )
+}
+
+function CompareModelFact(props: { label: string; value: string; href?: string }) {
+  return (
+    <div data-slot="compare-model-modal-fact">
+      <dt>{props.label}</dt>
+      <dd>
+        <Show when={props.href} fallback={props.value}>
+          {(href) => <a href={href()}>{props.value}</a>}
+        </Show>
+      </dd>
+    </div>
+  )
+}
+
+function ComparisonDetailMatrix(props: {
+  sections: ComparisonDetailSection[]
+  highlightBest: boolean
+  modelCount: number
+}) {
+  return (
+    <section id="comparison" data-section="compare-detail-matrix" aria-label="Model comparison">
+      <div data-component="compare-detail-matrix">
+        <For each={props.sections}>
+          {(section) => (
+            <section data-slot="compare-detail-group" aria-label={section.title}>
+              <ComparisonDetailSpacer modelCount={props.modelCount} />
+              <div data-slot="compare-detail-label" data-heading="true">
+                <strong>{section.title}</strong>
+                <Show when={section.badge}>{(badge) => <span>{badge()}</span>}</Show>
+              </div>
+              <For each={Array.from({ length: props.modelCount })}>
+                {(_, index) => (
+                  <div
+                    data-slot="compare-detail-value"
+                    data-column={index()}
+                    data-last={index() === props.modelCount - 1 ? "true" : undefined}
+                    data-heading="true"
+                    aria-hidden="true"
+                  />
+                )}
+              </For>
+              <For each={section.rows}>
+                {(row) => {
+                  const best = () => (props.highlightBest ? bestDetailCellIndex(row) : undefined)
+                  return (
+                    <>
+                      <div data-slot="compare-detail-label">{row.label}</div>
+                      <For each={row.cells}>
+                        {(cell, index) => (
+                          <ComparisonDetailValue
+                            best={best() === index()}
+                            cell={cell}
+                            column={index()}
+                            last={index() === props.modelCount - 1}
+                          />
+                        )}
+                      </For>
+                    </>
+                  )
+                }}
+              </For>
+              <Show when={section.usage}>
+                {(usage) => (
+                  <>
+                    <div data-slot="compare-detail-label" data-empty="true" />
+                    <For each={usage()}>
+                      {(modelUsage, index) => (
+                        <ComparisonUsageBars
+                          data={modelUsage}
+                          column={index()}
+                          last={index() === props.modelCount - 1}
+                        />
+                      )}
+                    </For>
+                  </>
+                )}
+              </Show>
+              <ComparisonDetailSpacer modelCount={props.modelCount} />
+            </section>
+          )}
+        </For>
+      </div>
+    </section>
+  )
+}
+
+function ComparisonDetailSpacer(props: { modelCount: number }) {
+  return (
+    <>
+      <div data-slot="compare-detail-label" data-spacer="true" aria-hidden="true" />
+      <For each={Array.from({ length: props.modelCount })}>
+        {(_, index) => (
+          <div
+            data-slot="compare-detail-value"
+            data-column={index()}
+            data-last={index() === props.modelCount - 1 ? "true" : undefined}
+            data-spacer="true"
+            aria-hidden="true"
+          />
+        )}
+      </For>
+    </>
+  )
+}
+
+function ComparisonDetailValue(props: { cell: ComparisonDetailCell; best: boolean; column: number; last: boolean }) {
+  return (
+    <div
+      data-slot="compare-detail-value"
+      data-column={props.column}
+      data-last={props.last ? "true" : undefined}
+      data-best={props.best ? "true" : undefined}
+    >
+      <Show when={props.cell.href} fallback={<ComparisonCellContent cell={props.cell} />}>
+        {(href) => (
+          <a href={href()} data-slot="compare-detail-value-link">
+            <ComparisonCellContent cell={props.cell} />
+          </a>
+        )}
+      </Show>
+    </div>
+  )
+}
+
+function ComparisonCellContent(props: { cell: ComparisonDetailCell }) {
+  return (
+    <Show
+      when={props.cell.kind === "boolean"}
+      fallback={
+        <span data-slot="compare-detail-value-main">
+          <span>{props.cell.value}</span>
+          <Show when={props.cell.unit}>{(unit) => <span data-slot="compare-detail-unit">{unit()}</span>}</Show>
+          <Show when={props.cell.trend !== undefined}>
+            <span
+              data-slot="compare-detail-trend"
+              data-trend={
+                props.cell.trend && props.cell.trend > 0
+                  ? "up"
+                  : props.cell.trend && props.cell.trend < 0
+                    ? "down"
+                    : "flat"
+              }
+            >
+              {props.cell.trend && props.cell.trend > 0 ? "+" : ""}
+              {formatPercent(props.cell.trend ?? 0)}
+            </span>
+          </Show>
+        </span>
+      }
+    >
+      <span data-slot="compare-detail-boolean" data-value={props.cell.value.toLowerCase()}>
+        {props.cell.value}
+      </span>
+    </Show>
+  )
+}
+
+function ComparisonUsageBars(props: { data: ModelUsagePoint[]; column: number; last: boolean }) {
+  const points = () => props.data.slice(-usageBarLimit)
+  const max = () => Math.max(...points().map((point) => point.tokens), 0)
+  return (
+    <div
+      data-slot="compare-detail-value"
+      data-column={props.column}
+      data-last={props.last ? "true" : undefined}
+      data-chart="true"
+    >
+      <Show
+        when={points().length > 0 && max() > 0}
+        fallback={<span data-slot="compare-detail-no-chart">No trend data</span>}
+      >
+        <span data-slot="compare-detail-bars" aria-hidden="true">
+          <For each={points()}>
+            {(point) => <i style={{ height: `${Math.max(4, Math.round((point.tokens / max()) * 40))}px` }} />}
+          </For>
+        </span>
+        <span data-slot="compare-detail-bar-dates">
+          <span>{formatUsageDate(points()[0]?.date)}</span>
+          <span>{formatUsageDate(points()[points().length - 1]?.date)}</span>
+        </span>
+      </Show>
+    </div>
+  )
+}
+
+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 resolvedCatalogEntry(catalog: ModelCatalog | undefined, lab: string, model: string) {
+  if (!catalog) return undefined
+  return findModelCatalogEntry(catalog, model, lab) ?? null
+}
+
+function parseAdditionalModels(value: string | undefined) {
+  return (value ?? "")
+    .split(",")
+    .flatMap((entry) => {
+      const parts = entry.split("/")
+      if (parts.length !== 2) return []
+      const lab = catalogSlug(parts[0])
+      const slug = catalogSlug(parts[1])
+      return lab && slug ? [{ lab, slug }] : []
+    })
+    .slice(0, comparisonModelLimit - 2)
+}
+
+function comparisonModelSelectionKey(model: ComparisonModelSelection) {
+  return model.catalog?.id ?? `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`
+}
+
+function comparisonModelsHref(models: ModelCatalogEntry[]) {
+  const path = comparisonHref(modelRefFromCatalog(models[0]), modelRefFromCatalog(models[1]))
+  const additional = models.slice(2).map((model) => `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`)
+  return additional.length === 0 ? path : `${path}?add=${additional.join(",")}`
+}
+
+function comparisonDetailGridTemplate(modelCount: number) {
+  return `minmax(0, var(--compare-detail-label-column)) repeat(${modelCount}, minmax(var(--compare-detail-model-column-min), 1fr))`
+}
+
+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 buildComparisonDetailSections(models: readonly ComparisonModel[]): ComparisonDetailSection[] {
+  return [
+    {
+      title: "Overview",
+      rows: [
+        comparisonDetailRow(
+          "Author",
+          models.map((model) => linkedTextCell(model.stats?.author ?? model.labName, labHref(model.lab))),
+        ),
+        comparisonDetailRow(
+          "Context length",
+          models.map((model) => limitCell(model.catalog?.limit?.context)),
+          "higher",
+        ),
+        comparisonDetailRow(
+          "Reasoning",
+          models.map((model) => booleanDetailCell(model.catalog?.reasoning)),
+          "higher",
+        ),
+        comparisonDetailRow(
+          "Input modalities",
+          models.map((model) => textCell(formatCatalogModalities(model.catalog?.modalities.input ?? []))),
+        ),
+        comparisonDetailRow(
+          "Output modalities",
+          models.map((model) => textCell(formatCatalogModalities(model.catalog?.modalities.output ?? []))),
+        ),
+        comparisonDetailRow(
+          "Providers",
+          models.map((model) => linkedTextCell(model.labName, labHref(model.lab))),
+        ),
+      ],
+    },
+    {
+      title: "Pricing",
+      rows: [
+        comparisonDetailRow(
+          "Input",
+          models.map((model) => priceCell(model.catalog?.cost?.input)),
+          "lower",
+        ),
+        comparisonDetailRow(
+          "Output",
+          models.map((model) => priceCell(model.catalog?.cost?.output)),
+          "lower",
+        ),
+        comparisonDetailRow(
+          "Cached input",
+          models.map((model) => priceCell(model.catalog?.cost?.cacheRead)),
+          "lower",
+        ),
+      ],
+    },
+    {
+      title: "Momentum",
+      badge: "Last 2 mo",
+      rows: [
+        comparisonDetailRow(
+          "Unique users",
+          models.map((model) => usageMetricCell(model.stats?.totals.uniqueUsers)),
+          "higher",
+        ),
+        comparisonDetailRow(
+          "Completed sessions",
+          models.map((model) => usageMetricCell(model.stats?.totals.sessions, "integer")),
+          "higher",
+        ),
+        comparisonDetailRow(
+          "Token share",
+          models.map((model) => percentCell(model.stats?.tokenShare)),
+          "higher",
+        ),
+        comparisonDetailRow(
+          "Tokens",
+          models.map((model) => tokenCell(model.stats?.totals.tokens, model.stats?.tokenChange)),
+          "higher",
+        ),
+      ],
+      usage: models.map((model) => model.stats?.usage ?? []),
+    },
+  ]
+}
+
+function comparisonDetailRow(
+  label: string,
+  cells: ComparisonDetailCell[],
+  direction?: ComparisonDirection,
+): ComparisonDetailRow {
+  return { label, direction, cells }
+}
+
+function bestDetailCellIndex(row: ComparisonDetailRow) {
+  if (!row.direction) return undefined
+  const scored = row.cells.flatMap((cell, index) => (cell.score === undefined ? [] : [{ index, score: cell.score }]))
+  if (scored.length < 2) return undefined
+  const score =
+    row.direction === "higher"
+      ? Math.max(...scored.map((cell) => cell.score))
+      : Math.min(...scored.map((cell) => cell.score))
+  const best = scored.filter((cell) => cell.score === score)
+  return best.length === 1 ? best[0].index : undefined
+}
+
+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 textCell(value: string): ComparisonDetailCell {
+  return { value }
+}
+
+function linkedTextCell(value: string, href: string): ComparisonDetailCell {
+  return { value, href }
+}
+
+function booleanDetailCell(value: boolean | undefined): ComparisonDetailCell {
+  if (value === undefined) return { value: "Unknown" }
+  return { value: value ? "True" : "False", kind: "boolean", score: value ? 1 : 0 }
+}
+
+function limitCell(value: number | undefined): ComparisonDetailCell {
+  return value === undefined ? { value: "Unknown" } : { value: formatTokens(value), score: value }
+}
+
+function priceCell(value: number | undefined): ComparisonDetailCell {
+  return value === undefined ? { value: "Unknown" } : { value: formatModelPrice(value), unit: "/ 1M", score: value }
+}
+
+function usageMetricCell(value: number | undefined, format: "compact" | "integer" = "compact"): ComparisonDetailCell {
+  if (value === undefined) return { value: "No usage" }
+  return { value: format === "integer" ? formatInteger(value) : formatTokens(value), score: value }
+}
+
+function percentCell(value: number | undefined): ComparisonDetailCell {
+  return value === undefined ? { value: "No usage" } : { value: formatPercent(value), score: value }
+}
+
+function tokenCell(value: number | undefined, trend: number | undefined): ComparisonDetailCell {
+  if (value === undefined) return { value: "No usage" }
+  return { value: formatTokens(value), score: value, trend }
+}
+
+function labHref(lab: string) {
+  return `${import.meta.env.BASE_URL}${catalogSlug(lab)}`
+}
+
+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 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 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(6).replace(/0+$/, "").replace(/\.$/, "")}`
+  return formatMoney(value)
+}
+
+function formatCatalogModalities(values: string[]) {
+  if (values.length === 0) return "Unknown"
+  return values
+    .map((value) => value.replace(/[-_]/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()))
+    .join(", ")
+}
+
+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 formatUsageDate(value: string | undefined) {
+  if (!value) return ""
+  const date = new Date(value)
+  if (Number.isNaN(date.getTime())) return value
+  return new Intl.DateTimeFormat("en", { month: "short", day: "numeric", timeZone: "UTC" }).format(date).toUpperCase()
+}
+
+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, Math.abs(value) >= 10 ? 1 : 2)}%`
+}
+
+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 trimNumber(value: number, digits: number) {
+  return Number(value.toFixed(digits)).toLocaleString("en")
+}
+
+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
+}
+
+function ChevronDownIcon() {
+  return (
+    <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" fill="none">
+      <path d="M4.75 6.25L8 9.5L11.25 6.25" stroke="currentColor" stroke-width="1.5" />
+    </svg>
+  )
+}

+ 286 - 0
packages/stats/app/src/lib/comparison-pages.ts

@@ -0,0 +1,286 @@
+import { catalogSlug, findModelCatalogEntry, type ModelCatalog, type ModelCatalogEntry } from "../routes/model-catalog"
+
+type ComparisonFamilyDefinition = {
+  slug: string
+  name: string
+  lab: string
+  prefixes: string[]
+  aliases?: string[]
+  preferredFamilies?: string[]
+}
+
+export type ResolvedComparisonFamily = ComparisonFamilyDefinition & {
+  model: ModelCatalogEntry
+}
+
+export const comparisonFamilies: ComparisonFamilyDefinition[] = [
+  {
+    slug: "gpt",
+    name: "GPT",
+    lab: "openai",
+    prefixes: ["gpt", "o"],
+    aliases: ["openai"],
+    preferredFamilies: ["gpt", "o"],
+  },
+  {
+    slug: "claude",
+    name: "Claude",
+    lab: "anthropic",
+    prefixes: ["claude"],
+    aliases: ["anthropic"],
+    preferredFamilies: ["claude-sonnet", "claude-opus"],
+  },
+  {
+    slug: "gemini",
+    name: "Gemini",
+    lab: "google",
+    prefixes: ["gemini"],
+    aliases: ["google"],
+    preferredFamilies: ["gemini-pro", "gemini-flash", "gemini"],
+  },
+  {
+    slug: "deepseek",
+    name: "DeepSeek",
+    lab: "deepseek",
+    prefixes: ["deepseek"],
+    preferredFamilies: ["deepseek-thinking", "deepseek"],
+  },
+  {
+    slug: "qwen",
+    name: "Qwen",
+    lab: "alibaba",
+    prefixes: ["qwen"],
+    aliases: ["alibaba"],
+    preferredFamilies: ["qwen"],
+  },
+  {
+    slug: "glm",
+    name: "GLM",
+    lab: "zhipuai",
+    prefixes: ["glm"],
+    aliases: ["zhipu", "zhipuai", "zai"],
+    preferredFamilies: ["glm"],
+  },
+  {
+    slug: "kimi",
+    name: "Kimi",
+    lab: "moonshotai",
+    prefixes: ["kimi"],
+    aliases: ["moonshot", "moonshotai"],
+    preferredFamilies: ["kimi-k2", "kimi-thinking"],
+  },
+  {
+    slug: "minimax",
+    name: "MiniMax",
+    lab: "minimax",
+    prefixes: ["minimax"],
+  },
+  {
+    slug: "grok",
+    name: "Grok",
+    lab: "xai",
+    prefixes: ["grok"],
+    aliases: ["xai"],
+    preferredFamilies: ["grok"],
+  },
+  {
+    slug: "mistral",
+    name: "Mistral",
+    lab: "mistral",
+    prefixes: ["mistral", "magistral", "devstral", "codestral"],
+    preferredFamilies: ["mistral-large", "mistral-medium", "mistral-small"],
+  },
+  {
+    slug: "llama",
+    name: "Llama",
+    lab: "meta",
+    prefixes: ["llama"],
+    aliases: ["meta"],
+  },
+  {
+    slug: "nemotron",
+    name: "Nemotron",
+    lab: "nvidia",
+    prefixes: ["nemotron", "llama-nemotron"],
+    aliases: ["nvidia"],
+  },
+  {
+    slug: "mimo",
+    name: "MiMo",
+    lab: "xiaomi",
+    prefixes: ["mimo"],
+    aliases: ["xiaomi"],
+  },
+  {
+    slug: "command",
+    name: "Command",
+    lab: "cohere",
+    prefixes: ["command"],
+    aliases: ["cohere"],
+    preferredFamilies: ["command-a", "command-r"],
+  },
+  {
+    slug: "sonar",
+    name: "Sonar",
+    lab: "perplexity",
+    prefixes: ["sonar"],
+    aliases: ["perplexity"],
+    preferredFamilies: ["sonar-pro", "sonar-reasoning", "sonar"],
+  },
+  {
+    slug: "longcat",
+    name: "LongCat",
+    lab: "meituan",
+    prefixes: ["longcat"],
+    aliases: ["meituan"],
+  },
+  {
+    slug: "step",
+    name: "Step",
+    lab: "stepfun",
+    prefixes: ["step"],
+    aliases: ["stepfun"],
+  },
+  {
+    slug: "mai",
+    name: "MAI",
+    lab: "microsoft",
+    prefixes: ["mai"],
+    aliases: ["microsoft"],
+  },
+]
+
+export function resolveComparisonFamily(catalog: ModelCatalog, value: string) {
+  const family = findComparisonFamily(value)
+  if (!family) return undefined
+  const model = comparisonFamilyCandidates(catalog, family.slug)[0]
+  if (!model) return undefined
+  return { ...family, model } satisfies ResolvedComparisonFamily
+}
+
+export function findComparisonFamily(value: string) {
+  const slug = catalogSlug(value)
+  return comparisonFamilies.find((family) => family.slug === slug || family.aliases?.includes(slug))
+}
+
+export function comparisonFamilyCandidates(catalog: ModelCatalog, value: string) {
+  const family = findComparisonFamily(value)
+  if (!family) return []
+  const matches = catalog.models
+    .filter((model) => model.lab === family.lab && isFamilyModel(model, family) && isGeneralComparisonModel(model))
+    .toSorted((a, b) => comparisonFamilyModelSort(a, b, family))
+  return matches.filter((model) => !isDuplicateAliasModel(model, matches))
+}
+
+export function comparisonSitemapModels(
+  catalog: ModelCatalog,
+  leaderboard: { model: string; provider: string }[] = [],
+) {
+  return uniqueModels([
+    ...comparisonFamilies.flatMap((family) => comparisonFamilyCandidates(catalog, family.slug).slice(0, 2)),
+    ...leaderboard.flatMap((entry) => {
+      const model =
+        findModelCatalogEntry(catalog, entry.model, entry.provider) ?? findModelCatalogEntry(catalog, entry.model)
+      return model && isGeneralComparisonModel(model) ? [model] : []
+    }),
+  ]).toSorted((a, b) => a.id.localeCompare(b.id))
+}
+
+export function canonicalModelComparisonPath(first: ModelCatalogEntry, second: ModelCatalogEntry) {
+  const models = [first, second].toSorted((a, b) => a.id.localeCompare(b.id))
+  return `/data/compare/${models[0].lab}/${models[0].slug}/${models[1].lab}/${models[1].slug}`
+}
+
+export function canonicalFamilyComparisonPath(first: ResolvedComparisonFamily, second: ResolvedComparisonFamily) {
+  const families = [first, second].toSorted((a, b) => a.slug.localeCompare(b.slug))
+  return `/data/compare/${families[0].slug}/${families[1].slug}`
+}
+
+export function latestFamilyComparisonPath(catalog: ModelCatalog, first: ModelCatalogEntry, second: ModelCatalogEntry) {
+  const firstFamily = comparisonFamilyForModel(catalog, first)
+  const secondFamily = comparisonFamilyForModel(catalog, second)
+  if (!firstFamily || !secondFamily || firstFamily.slug === secondFamily.slug) return undefined
+  if (firstFamily.model.id !== first.id || secondFamily.model.id !== second.id) return undefined
+  return canonicalFamilyComparisonPath(firstFamily, secondFamily)
+}
+
+export function comparisonFamilyForModel(catalog: ModelCatalog, model: ModelCatalogEntry) {
+  const family = comparisonFamilies.find(
+    (candidate) => candidate.lab === model.lab && isFamilyModel(model, candidate) && isGeneralComparisonModel(model),
+  )
+  if (!family) return undefined
+  const latest = comparisonFamilyCandidates(catalog, family.slug)[0]
+  if (!latest) return undefined
+  return { ...family, model: latest } satisfies ResolvedComparisonFamily
+}
+
+function isFamilyModel(model: ModelCatalogEntry, family: ComparisonFamilyDefinition) {
+  const values = [model.family, model.slug, model.name]
+    .filter((value): value is string => Boolean(value))
+    .map(catalogSlug)
+  return family.prefixes.some((prefix) => values.some((value) => value === prefix || value.startsWith(`${prefix}-`)))
+}
+
+function isGeneralComparisonModel(model: ModelCatalogEntry) {
+  const input = model.modalities.input.map(catalogSlug)
+  const output = model.modalities.output.map(catalogSlug)
+  if (!input.includes("text") || !output.includes("text")) return false
+  return !/(?:^|-)(?:audio|embedding|guard|image|moderation|omni|rerank|safety|speech|transcribe|tts|vision)(?:-|$)/.test(
+    model.slug,
+  )
+}
+
+function comparisonFamilyModelSort(
+  first: ModelCatalogEntry,
+  second: ModelCatalogEntry,
+  family: ComparisonFamilyDefinition,
+) {
+  return (
+    displayDateTime(second.releaseDate ?? second.lastUpdated) -
+      displayDateTime(first.releaseDate ?? first.lastUpdated) ||
+    preferredFamilyIndex(first, family) - preferredFamilyIndex(second, family) ||
+    modelVariantPenalty(first) - modelVariantPenalty(second) ||
+    first.slug.length - second.slug.length ||
+    first.name.localeCompare(second.name)
+  )
+}
+
+function preferredFamilyIndex(model: ModelCatalogEntry, family: ComparisonFamilyDefinition) {
+  const index = family.preferredFamilies?.indexOf(catalogSlug(model.family ?? "")) ?? -1
+  return index === -1 ? (family.preferredFamilies?.length ?? 0) : index
+}
+
+function modelVariantPenalty(model: ModelCatalogEntry) {
+  return /(?:highspeed|latest|preview|turbo|ultraspeed)/.test(model.slug) ? 1 : 0
+}
+
+function isDuplicateAliasModel(model: ModelCatalogEntry, models: ModelCatalogEntry[]) {
+  if (!/(?:-latest|-highspeed|-ultraspeed)$/.test(model.slug)) return false
+  return models.some(
+    (candidate) =>
+      candidate.id !== model.id &&
+      candidate.releaseDate === model.releaseDate &&
+      candidate.family === model.family &&
+      !/(?:-latest|-highspeed|-ultraspeed)$/.test(candidate.slug),
+  )
+}
+
+function uniqueModels(models: ModelCatalogEntry[]) {
+  return models.reduce<{ ids: Set<string>; models: ModelCatalogEntry[] }>(
+    (result, model) => {
+      if (result.ids.has(model.id)) return result
+      result.ids.add(model.id)
+      result.models.push(model)
+      return result
+    },
+    { ids: new Set(), models: [] },
+  ).models
+}
+
+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
+}

+ 7 - 2
packages/stats/app/src/routes/compare-cards.tsx

@@ -32,6 +32,11 @@ export function comparisonHref(first: ComparisonModelRef, second: ComparisonMode
   )}/${catalogSlug(second.slug)}`
 }
 
+export function canonicalComparisonHref(first: ComparisonModelRef, second: ComparisonModelRef) {
+  const models = [first, second].toSorted((a, b) => modelKey(a).localeCompare(modelKey(b)))
+  return comparisonHref(models[0], models[1])
+}
+
 export function uniqueComparisonPairs(pairs: ComparisonPair[]) {
   return pairs.reduce<{ keys: Set<string>; pairs: ComparisonPair[] }>(
     (result, pair) => {
@@ -80,7 +85,7 @@ function FeaturedComparisonCard(props: { pair: ComparisonPair }) {
   return (
     <a
       data-component="compare-home-card"
-      href={comparisonHref(props.pair.first, props.pair.second)}
+      href={canonicalComparisonHref(props.pair.first, props.pair.second)}
       aria-label={`${props.pair.detail}: ${props.pair.first.name} vs ${props.pair.second.name}`}
     >
       <span data-slot="compare-home-card-head">
@@ -106,7 +111,7 @@ function FeaturedComparisonCard(props: { pair: ComparisonPair }) {
 
 function ComparisonPanelCard(props: { pair: ComparisonPair }) {
   return (
-    <a data-component="comparison-card" href={comparisonHref(props.pair.first, props.pair.second)}>
+    <a data-component="comparison-card" href={canonicalComparisonHref(props.pair.first, props.pair.second)}>
       <span>{props.pair.detail}</span>
       <strong>
         {props.pair.first.name} <em>vs</em> {props.pair.second.name}

+ 47 - 0
packages/stats/app/src/routes/compare/[firstFamily]/[secondFamily].tsx

@@ -0,0 +1,47 @@
+import { Meta, Title } from "@solidjs/meta"
+import { createAsync, useParams } from "@solidjs/router"
+import { createMemo, Show } from "solid-js"
+import ModelCompareDetailPage from "../../../component/model-compare-detail"
+import { resolveComparisonFamily } from "../../../lib/comparison-pages"
+import { getModelCatalog } from "../../model-catalog"
+
+export default function ModelCompareFamily() {
+  const params = useParams()
+  const catalog = createAsync(() => getModelCatalog())
+  const comparison = createMemo(() => {
+    const source = catalog()
+    if (!source) return undefined
+    const first = resolveComparisonFamily(source, params.firstFamily ?? "")
+    const second = resolveComparisonFamily(source, params.secondFamily ?? "")
+    if (!first || !second || first.slug === second.slug) return null
+    return { first, second }
+  })
+
+  return (
+    <Show
+      when={comparison()}
+      fallback={
+        <Show when={comparison() === null}>
+          <Title>Model comparison not found</Title>
+          <Meta name="robots" content="noindex,follow" />
+          <main data-page="stats">
+            <div data-component="empty-state">
+              <strong>Comparison not found</strong>
+              <p>Choose two model families to compare.</p>
+              <a href={`${import.meta.env.BASE_URL}compare`}>Compare models</a>
+            </div>
+          </main>
+        </Show>
+      }
+    >
+      {(resolved) => (
+        <ModelCompareDetailPage
+          first={{ lab: resolved().first.model.lab, slug: resolved().first.model.slug }}
+          second={{ lab: resolved().second.model.lab, slug: resolved().second.model.slug }}
+          family={resolved()}
+          catalog={catalog()}
+        />
+      )}
+    </Show>
+  )
+}

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

@@ -1,1155 +1 @@
-import "../../../../index.css"
-import { Link, Meta, Title } from "@solidjs/meta"
-import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
-import {
-  getStatsModelsComparisonData,
-  type ModelUsagePoint,
-  type StatsModelComparisonInput,
-  type StatsModelComparisonEntry,
-} from "@opencode-ai/stats-core/domain/home"
-import { runtime } from "@opencode-ai/stats-core/runtime"
-import { createAsync, query, useParams, useSearchParams } from "@solidjs/router"
-import { createEffect, createMemo, createSignal, For, onMount, Show } from "solid-js"
-import { getRequestEvent } from "solid-js/web"
-import {
-  ComparisonCardsSection,
-  comparisonHref,
-  modelRefFromCatalog,
-  uniqueComparisonPairs,
-  type ComparisonModelRef,
-  type ComparisonPair,
-} from "../../../../compare-cards"
-import { ComparisonRadar } from "../../../../compare-radar"
-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: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" },
-  { href: `${import.meta.env.BASE_URL}#leaderboard`, label: "Leaderboard" },
-  { href: `${import.meta.env.BASE_URL}#market-share`, label: "Market Share" },
-  { href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" },
-  { href: `${import.meta.env.BASE_URL}#session-cost`, label: "Session Cost" },
-]
-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" },
-]
-const heroLabs = [
-  { lab: "deepseek", label: "DeepSeek" },
-  { lab: "openai", label: "OpenAI" },
-  { lab: "anthropic", label: "Anthropic" },
-] as const
-const usageBarLimit = 60
-const comparisonModelLimit = 6
-
-type ComparisonModel = {
-  name: string
-  lab: string
-  labName: string
-  slug: string
-  catalog: ModelCatalogEntry | null
-  stats: StatsModelComparisonEntry | null
-}
-type ComparisonDirection = "higher" | "lower"
-type ComparisonDetailCell = {
-  value: string
-  unit?: string
-  href?: string
-  kind?: "boolean"
-  score?: number
-  trend?: number
-}
-type ComparisonDetailRow = {
-  label: string
-  direction?: ComparisonDirection
-  cells: ComparisonDetailCell[]
-}
-type ComparisonDetailSection = {
-  title: string
-  badge?: string
-  rows: ComparisonDetailRow[]
-  usage?: ModelUsagePoint[][]
-}
-type ComparisonModelSelection = {
-  lab: string
-  slug: string
-  catalog: ModelCatalogEntry | null | undefined
-}
-type ComparisonModels = [ComparisonModel, ComparisonModel, ...ComparisonModel[]]
-
-const getComparisonData = query(async (models: StatsModelComparisonInput[]) => {
-  "use server"
-  return runtime.runPromise(getStatsModelsComparisonData(models))
-}, "getStatsModelComparisonDetailData")
-
-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 [searchParams] = useSearchParams<{ add?: string }>()
-  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 modelSelections = createMemo(() => {
-    const selected: ComparisonModelSelection[] = [
-      { lab: firstLabParam(), slug: firstModelParam(), catalog: firstCatalog() },
-      { lab: secondLabParam(), slug: secondModelParam(), catalog: secondCatalog() },
-      ...parseAdditionalModels(searchParams.add).map((model) => ({
-        ...model,
-        catalog: resolvedCatalogEntry(catalog(), model.lab, model.slug),
-      })),
-    ]
-    return selected
-      .filter(
-        (model, index) =>
-          index < 2 ||
-          selected.findIndex(
-            (candidate) => comparisonModelSelectionKey(candidate) === comparisonModelSelectionKey(model),
-          ) === index,
-      )
-      .slice(0, comparisonModelLimit) as [
-      ComparisonModelSelection,
-      ComparisonModelSelection,
-      ...ComparisonModelSelection[],
-    ]
-  })
-  const stats = createAsync(() => {
-    const selected = modelSelections()
-    if (catalog() === undefined || selected.some((model) => model.catalog === undefined))
-      return Promise.resolve(undefined)
-    return getComparisonData(
-      selected.map((model) => ({
-        provider: model.catalog?.lab ?? model.lab,
-        model: model.catalog?.slug ?? model.slug,
-      })),
-    )
-  })
-  const githubStars = createAsync(() => getGitHubStars())
-  const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
-  const [highlightBest, setHighlightBest] = createSignal(true)
-  const [addingModel, setAddingModel] = createSignal(false)
-  let comparisonHeadingScroll: HTMLDivElement | undefined
-  let comparisonBodyScroll: HTMLDivElement | undefined
-  const models = createMemo(
-    () =>
-      modelSelections().map((model, index) =>
-        buildComparisonModel(model.lab, model.slug, model.catalog ?? null, stats()?.models[index] ?? null),
-      ) as ComparisonModels,
-  )
-  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 detailSections = createMemo(() => buildComparisonDetailSections(models()))
-  const relatedPairs = createMemo(() => buildRelatedPairs(catalog(), models()[0], models()[1]))
-  const selectorModels = createMemo(() =>
-    uniqueCatalogModels([...models().map(comparisonCatalogEntry), ...(catalog()?.models ?? [])]),
-  )
-  const selectedCatalogModels = createMemo(() => models().map(comparisonCatalogEntry))
-  const canAddModel = createMemo(
-    () =>
-      models().length < comparisonModelLimit &&
-      selectorModels().some((model) => !selectedCatalogModels().some((selected) => selected.id === model.id)),
-  )
-  const navigateToModels = (next: ModelCatalogEntry[]) => {
-    if (typeof window === "undefined" || next.length < 2) return
-    window.location.href = comparisonModelsHref(next)
-  }
-  const syncComparisonScroll = (source: HTMLDivElement, target: HTMLDivElement | undefined) => {
-    if (target && target.scrollLeft !== source.scrollLeft) target.scrollLeft = source.scrollLeft
-  }
-  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-layout="compare-detail" data-theme={themePreference()}>
-      <Title>{title()}</Title>
-      <Meta name="description" content={description()} />
-      <Meta name="robots" content={models().length > 2 ? "noindex,follow" : "index,follow"} />
-      <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
-            canAddModel={canAddModel()}
-            highlightBest={highlightBest()}
-            models={models()}
-            onAddModel={() => setAddingModel(true)}
-            onHighlightBestChange={() => setHighlightBest(!highlightBest())}
-          />
-          <Show when={addingModel()}>
-            <CompareModelSelectModal
-              blockedIds={selectedCatalogModels().map((model) => model.id)}
-              label="Add model"
-              models={selectorModels()}
-              onClose={() => setAddingModel(false)}
-              onSelect={(model) => {
-                setAddingModel(false)
-                navigateToModels([...selectedCatalogModels(), model])
-              }}
-            />
-          </Show>
-          <ComparisonRadar models={models()} catalogModels={catalog()?.models ?? []} />
-          <div
-            data-component="compare-detail-table"
-            data-model-count={models().length}
-            style={`--compare-detail-grid: ${comparisonDetailGridTemplate(models().length)}`}
-          >
-            <div
-              data-component="compare-detail-heading-scroll"
-              ref={(element) => (comparisonHeadingScroll = element)}
-              onScroll={(event) => syncComparisonScroll(event.currentTarget, comparisonBodyScroll)}
-            >
-              <ComparisonPairSelector catalogModels={selectorModels()} models={models()} />
-            </div>
-            <div
-              data-component="compare-detail-body-scroll"
-              ref={(element) => (comparisonBodyScroll = element)}
-              onScroll={(event) => syncComparisonScroll(event.currentTarget, comparisonHeadingScroll)}
-            >
-              <Show
-                when={stats() !== undefined}
-                fallback={
-                  <section data-section="compare-detail-matrix">
-                    <div data-component="empty-state" data-compact="true">
-                      <strong>Loading comparison</strong>
-                      <p>Loading model stats.</p>
-                    </div>
-                  </section>
-                }
-              >
-                <ComparisonDetailMatrix
-                  highlightBest={highlightBest()}
-                  modelCount={models().length}
-                  sections={detailSections()}
-                />
-              </Show>
-            </div>
-          </div>
-          <ComparisonCardsSection
-            pairs={relatedPairs()}
-            title="Related comparisons"
-            description="Other model pairs to check."
-            variant="featured"
-          />
-        </div>
-        <Footer
-          themePreference={themePreference()}
-          onThemePreferenceChange={updateThemePreference}
-          links={compareFooterLinks}
-          bridge={{ href: "#comparison", label: "MODEL COMPARISON" }}
-        />
-      </div>
-    </main>
-  )
-}
-
-function ComparisonHero(props: {
-  models: readonly ComparisonModel[]
-  canAddModel: boolean
-  highlightBest: boolean
-  onAddModel: () => void
-  onHighlightBestChange: () => void
-}) {
-  return (
-    <section id="overview" data-section="compare-detail-hero">
-      <nav data-component="compare-home-breadcrumb" aria-label="Breadcrumb">
-        <a data-slot="compare-home-crumb" href={import.meta.env.BASE_URL}>
-          Data
-        </a>
-        <span data-slot="compare-home-separator">/</span>
-        <a data-slot="compare-home-crumb" href={`${import.meta.env.BASE_URL}compare`}>
-          Compare
-        </a>
-      </nav>
-      <div data-slot="compare-detail-hero-grid">
-        <h1 aria-label={`Compare ${props.models.map((model) => model.name).join(", ")}`}>
-          <span>Compare</span>
-          <HeroModelStack />
-          <span>AI models</span>
-        </h1>
-        <div data-slot="compare-detail-actions">
-          <button
-            type="button"
-            data-slot="compare-detail-action"
-            data-active={props.highlightBest ? "true" : undefined}
-            aria-pressed={props.highlightBest}
-            onClick={props.onHighlightBestChange}
-          >
-            <span data-slot="compare-detail-highlight-icon" aria-hidden="true">
-              <i />
-              <i />
-            </span>
-            <span>Highlight best</span>
-          </button>
-          <button
-            type="button"
-            data-slot="compare-detail-action"
-            disabled={!props.canAddModel}
-            aria-haspopup="dialog"
-            onClick={props.onAddModel}
-          >
-            <span data-slot="compare-home-plus" aria-hidden="true">
-              +
-            </span>
-            <span>Add model</span>
-          </button>
-        </div>
-      </div>
-      <div data-slot="compare-home-pattern" aria-hidden="true" />
-    </section>
-  )
-}
-
-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 ComparisonPairSelector(props: { catalogModels: ModelCatalogEntry[]; models: readonly ComparisonModel[] }) {
-  const [activeIndex, setActiveIndex] = createSignal<number>()
-  const selectedModels = createMemo(() => props.models.map(comparisonCatalogEntry))
-  const activeSelected = createMemo(() => {
-    const index = activeIndex()
-    if (index === undefined) return undefined
-    return selectedModels()[index]
-  })
-  const blockedIds = createMemo(() =>
-    selectedModels()
-      .filter((_, index) => index !== activeIndex())
-      .map((model) => model.id),
-  )
-  const navigateToSelection = (index: number, model: ModelCatalogEntry) => {
-    if (typeof window === "undefined") return
-    window.location.href = comparisonModelsHref(
-      selectedModels().map((selected, selectedIndex) => (selectedIndex === index ? model : selected)),
-    )
-  }
-
-  return (
-    <section data-section="compare-detail-selector" aria-label="Selected models">
-      <div data-component="compare-detail-selector-grid">
-        <div data-slot="compare-detail-selector-spacer" aria-hidden="true" />
-        <For each={props.models}>
-          {(model, index) => (
-            <CompareDetailSelectButton
-              model={model}
-              label={`Model ${index() + 1}`}
-              column={index()}
-              last={index() === props.models.length - 1}
-              expanded={activeIndex() === index()}
-              onOpen={() => setActiveIndex(index())}
-            />
-          )}
-        </For>
-      </div>
-      <Show when={activeSelected()}>
-        {(selected) => (
-          <CompareModelSelectModal
-            blockedIds={blockedIds()}
-            label={`Choose model ${(activeIndex() ?? 0) + 1}`}
-            models={props.catalogModels}
-            selected={selected()}
-            onClose={() => setActiveIndex(undefined)}
-            onSelect={(model) => {
-              const index = activeIndex()
-              if (index === undefined) return
-              setActiveIndex(undefined)
-              navigateToSelection(index, model)
-            }}
-          />
-        )}
-      </Show>
-    </section>
-  )
-}
-
-function CompareDetailSelectButton(props: {
-  model: ComparisonModel
-  label: string
-  column: number
-  last: boolean
-  expanded: boolean
-  onOpen: () => void
-}) {
-  return (
-    <button
-      type="button"
-      data-slot="compare-detail-select-model"
-      data-column={props.column}
-      data-last={props.last ? "true" : undefined}
-      aria-label={props.label}
-      aria-haspopup="dialog"
-      aria-expanded={props.expanded}
-      onClick={props.onOpen}
-    >
-      <LabLogo lab={props.model.lab} label={props.model.labName} size="small" />
-      <span data-slot="compare-detail-select-name">{props.model.name}</span>
-      <ChevronDownIcon />
-    </button>
-  )
-}
-
-function CompareModelSelectModal(props: {
-  models: ModelCatalogEntry[]
-  selected?: ModelCatalogEntry
-  blockedIds: 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(() =>
-    uniqueCatalogModels([...(props.selected ? [props.selected] : []), ...props.models]).filter(
-      (model) => !props.blockedIds.includes(model.id),
-    ),
-  )
-  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 models = filteredModels()
-    if (models.some((model) => model.id === previewId())) return
-    const selected =
-      props.selected && models.some((model) => model.id === props.selected?.id) ? props.selected.id : undefined
-    setPreviewId(selected ?? 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}
-                    onMouseMove={() => 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)} />
-        <CompareModelFact label="URL" value={formatModelUrl(props.model)} href={modelHref(props.model)} />
-      </dl>
-    </aside>
-  )
-}
-
-function CompareModelFact(props: { label: string; value: string; href?: string }) {
-  return (
-    <div data-slot="compare-model-modal-fact">
-      <dt>{props.label}</dt>
-      <dd>
-        <Show when={props.href} fallback={props.value}>
-          {(href) => <a href={href()}>{props.value}</a>}
-        </Show>
-      </dd>
-    </div>
-  )
-}
-
-function ComparisonDetailMatrix(props: {
-  sections: ComparisonDetailSection[]
-  highlightBest: boolean
-  modelCount: number
-}) {
-  return (
-    <section id="comparison" data-section="compare-detail-matrix" aria-label="Model comparison">
-      <div data-component="compare-detail-matrix">
-        <For each={props.sections}>
-          {(section) => (
-            <section data-slot="compare-detail-group" aria-label={section.title}>
-              <ComparisonDetailSpacer modelCount={props.modelCount} />
-              <div data-slot="compare-detail-label" data-heading="true">
-                <strong>{section.title}</strong>
-                <Show when={section.badge}>{(badge) => <span>{badge()}</span>}</Show>
-              </div>
-              <For each={Array.from({ length: props.modelCount })}>
-                {(_, index) => (
-                  <div
-                    data-slot="compare-detail-value"
-                    data-column={index()}
-                    data-last={index() === props.modelCount - 1 ? "true" : undefined}
-                    data-heading="true"
-                    aria-hidden="true"
-                  />
-                )}
-              </For>
-              <For each={section.rows}>
-                {(row) => {
-                  const best = () => (props.highlightBest ? bestDetailCellIndex(row) : undefined)
-                  return (
-                    <>
-                      <div data-slot="compare-detail-label">{row.label}</div>
-                      <For each={row.cells}>
-                        {(cell, index) => (
-                          <ComparisonDetailValue
-                            best={best() === index()}
-                            cell={cell}
-                            column={index()}
-                            last={index() === props.modelCount - 1}
-                          />
-                        )}
-                      </For>
-                    </>
-                  )
-                }}
-              </For>
-              <Show when={section.usage}>
-                {(usage) => (
-                  <>
-                    <div data-slot="compare-detail-label" data-empty="true" />
-                    <For each={usage()}>
-                      {(modelUsage, index) => (
-                        <ComparisonUsageBars
-                          data={modelUsage}
-                          column={index()}
-                          last={index() === props.modelCount - 1}
-                        />
-                      )}
-                    </For>
-                  </>
-                )}
-              </Show>
-              <ComparisonDetailSpacer modelCount={props.modelCount} />
-            </section>
-          )}
-        </For>
-      </div>
-    </section>
-  )
-}
-
-function ComparisonDetailSpacer(props: { modelCount: number }) {
-  return (
-    <>
-      <div data-slot="compare-detail-label" data-spacer="true" aria-hidden="true" />
-      <For each={Array.from({ length: props.modelCount })}>
-        {(_, index) => (
-          <div
-            data-slot="compare-detail-value"
-            data-column={index()}
-            data-last={index() === props.modelCount - 1 ? "true" : undefined}
-            data-spacer="true"
-            aria-hidden="true"
-          />
-        )}
-      </For>
-    </>
-  )
-}
-
-function ComparisonDetailValue(props: { cell: ComparisonDetailCell; best: boolean; column: number; last: boolean }) {
-  return (
-    <div
-      data-slot="compare-detail-value"
-      data-column={props.column}
-      data-last={props.last ? "true" : undefined}
-      data-best={props.best ? "true" : undefined}
-    >
-      <Show when={props.cell.href} fallback={<ComparisonCellContent cell={props.cell} />}>
-        {(href) => (
-          <a href={href()} data-slot="compare-detail-value-link">
-            <ComparisonCellContent cell={props.cell} />
-          </a>
-        )}
-      </Show>
-    </div>
-  )
-}
-
-function ComparisonCellContent(props: { cell: ComparisonDetailCell }) {
-  return (
-    <Show
-      when={props.cell.kind === "boolean"}
-      fallback={
-        <span data-slot="compare-detail-value-main">
-          <span>{props.cell.value}</span>
-          <Show when={props.cell.unit}>{(unit) => <span data-slot="compare-detail-unit">{unit()}</span>}</Show>
-          <Show when={props.cell.trend !== undefined}>
-            <span
-              data-slot="compare-detail-trend"
-              data-trend={
-                props.cell.trend && props.cell.trend > 0
-                  ? "up"
-                  : props.cell.trend && props.cell.trend < 0
-                    ? "down"
-                    : "flat"
-              }
-            >
-              {props.cell.trend && props.cell.trend > 0 ? "+" : ""}
-              {formatPercent(props.cell.trend ?? 0)}
-            </span>
-          </Show>
-        </span>
-      }
-    >
-      <span data-slot="compare-detail-boolean" data-value={props.cell.value.toLowerCase()}>
-        {props.cell.value}
-      </span>
-    </Show>
-  )
-}
-
-function ComparisonUsageBars(props: { data: ModelUsagePoint[]; column: number; last: boolean }) {
-  const points = () => props.data.slice(-usageBarLimit)
-  const max = () => Math.max(...points().map((point) => point.tokens), 0)
-  return (
-    <div
-      data-slot="compare-detail-value"
-      data-column={props.column}
-      data-last={props.last ? "true" : undefined}
-      data-chart="true"
-    >
-      <Show
-        when={points().length > 0 && max() > 0}
-        fallback={<span data-slot="compare-detail-no-chart">No trend data</span>}
-      >
-        <span data-slot="compare-detail-bars" aria-hidden="true">
-          <For each={points()}>
-            {(point) => <i style={{ height: `${Math.max(4, Math.round((point.tokens / max()) * 40))}px` }} />}
-          </For>
-        </span>
-        <span data-slot="compare-detail-bar-dates">
-          <span>{formatUsageDate(points()[0]?.date)}</span>
-          <span>{formatUsageDate(points()[points().length - 1]?.date)}</span>
-        </span>
-      </Show>
-    </div>
-  )
-}
-
-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 resolvedCatalogEntry(catalog: ModelCatalog | undefined, lab: string, model: string) {
-  if (!catalog) return undefined
-  return findModelCatalogEntry(catalog, model, lab) ?? null
-}
-
-function parseAdditionalModels(value: string | undefined) {
-  return (value ?? "")
-    .split(",")
-    .flatMap((entry) => {
-      const parts = entry.split("/")
-      if (parts.length !== 2) return []
-      const lab = catalogSlug(parts[0])
-      const slug = catalogSlug(parts[1])
-      return lab && slug ? [{ lab, slug }] : []
-    })
-    .slice(0, comparisonModelLimit - 2)
-}
-
-function comparisonModelSelectionKey(model: ComparisonModelSelection) {
-  return model.catalog?.id ?? `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`
-}
-
-function comparisonModelsHref(models: ModelCatalogEntry[]) {
-  const path = comparisonHref(modelRefFromCatalog(models[0]), modelRefFromCatalog(models[1]))
-  const additional = models.slice(2).map((model) => `${catalogSlug(model.lab)}/${catalogSlug(model.slug)}`)
-  return additional.length === 0 ? path : `${path}?add=${additional.join(",")}`
-}
-
-function comparisonDetailGridTemplate(modelCount: number) {
-  return `minmax(0, var(--compare-detail-label-column)) repeat(${modelCount}, minmax(var(--compare-detail-model-column-min), 1fr))`
-}
-
-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 buildComparisonDetailSections(models: readonly ComparisonModel[]): ComparisonDetailSection[] {
-  return [
-    {
-      title: "Overview",
-      rows: [
-        comparisonDetailRow(
-          "Author",
-          models.map((model) => linkedTextCell(model.stats?.author ?? model.labName, labHref(model.lab))),
-        ),
-        comparisonDetailRow(
-          "Context length",
-          models.map((model) => limitCell(model.catalog?.limit?.context)),
-          "higher",
-        ),
-        comparisonDetailRow(
-          "Reasoning",
-          models.map((model) => booleanDetailCell(model.catalog?.reasoning)),
-          "higher",
-        ),
-        comparisonDetailRow(
-          "Input modalities",
-          models.map((model) => textCell(formatCatalogModalities(model.catalog?.modalities.input ?? []))),
-        ),
-        comparisonDetailRow(
-          "Output modalities",
-          models.map((model) => textCell(formatCatalogModalities(model.catalog?.modalities.output ?? []))),
-        ),
-        comparisonDetailRow(
-          "Providers",
-          models.map((model) => linkedTextCell(model.labName, labHref(model.lab))),
-        ),
-      ],
-    },
-    {
-      title: "Pricing",
-      rows: [
-        comparisonDetailRow(
-          "Input",
-          models.map((model) => priceCell(model.catalog?.cost?.input)),
-          "lower",
-        ),
-        comparisonDetailRow(
-          "Output",
-          models.map((model) => priceCell(model.catalog?.cost?.output)),
-          "lower",
-        ),
-        comparisonDetailRow(
-          "Cached input",
-          models.map((model) => priceCell(model.catalog?.cost?.cacheRead)),
-          "lower",
-        ),
-      ],
-    },
-    {
-      title: "Momentum",
-      badge: "Last 2 mo",
-      rows: [
-        comparisonDetailRow(
-          "Unique users",
-          models.map((model) => usageMetricCell(model.stats?.totals.uniqueUsers)),
-          "higher",
-        ),
-        comparisonDetailRow(
-          "Completed sessions",
-          models.map((model) => usageMetricCell(model.stats?.totals.sessions, "integer")),
-          "higher",
-        ),
-        comparisonDetailRow(
-          "Token share",
-          models.map((model) => percentCell(model.stats?.tokenShare)),
-          "higher",
-        ),
-        comparisonDetailRow(
-          "Tokens",
-          models.map((model) => tokenCell(model.stats?.totals.tokens, model.stats?.tokenChange)),
-          "higher",
-        ),
-      ],
-      usage: models.map((model) => model.stats?.usage ?? []),
-    },
-  ]
-}
-
-function comparisonDetailRow(
-  label: string,
-  cells: ComparisonDetailCell[],
-  direction?: ComparisonDirection,
-): ComparisonDetailRow {
-  return { label, direction, cells }
-}
-
-function bestDetailCellIndex(row: ComparisonDetailRow) {
-  if (!row.direction) return undefined
-  const scored = row.cells.flatMap((cell, index) => (cell.score === undefined ? [] : [{ index, score: cell.score }]))
-  if (scored.length < 2) return undefined
-  const score =
-    row.direction === "higher"
-      ? Math.max(...scored.map((cell) => cell.score))
-      : Math.min(...scored.map((cell) => cell.score))
-  const best = scored.filter((cell) => cell.score === score)
-  return best.length === 1 ? best[0].index : undefined
-}
-
-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 textCell(value: string): ComparisonDetailCell {
-  return { value }
-}
-
-function linkedTextCell(value: string, href: string): ComparisonDetailCell {
-  return { value, href }
-}
-
-function booleanDetailCell(value: boolean | undefined): ComparisonDetailCell {
-  if (value === undefined) return { value: "Unknown" }
-  return { value: value ? "True" : "False", kind: "boolean", score: value ? 1 : 0 }
-}
-
-function limitCell(value: number | undefined): ComparisonDetailCell {
-  return value === undefined ? { value: "Unknown" } : { value: formatTokens(value), score: value }
-}
-
-function priceCell(value: number | undefined): ComparisonDetailCell {
-  return value === undefined ? { value: "Unknown" } : { value: formatModelPrice(value), unit: "/ 1M", score: value }
-}
-
-function usageMetricCell(value: number | undefined, format: "compact" | "integer" = "compact"): ComparisonDetailCell {
-  if (value === undefined) return { value: "No usage" }
-  return { value: format === "integer" ? formatInteger(value) : formatTokens(value), score: value }
-}
-
-function percentCell(value: number | undefined): ComparisonDetailCell {
-  return value === undefined ? { value: "No usage" } : { value: formatPercent(value), score: value }
-}
-
-function tokenCell(value: number | undefined, trend: number | undefined): ComparisonDetailCell {
-  if (value === undefined) return { value: "No usage" }
-  return { value: formatTokens(value), score: value, trend }
-}
-
-function labHref(lab: string) {
-  return `${import.meta.env.BASE_URL}${catalogSlug(lab)}`
-}
-
-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 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 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(6).replace(/0+$/, "").replace(/\.$/, "")}`
-  return formatMoney(value)
-}
-
-function formatCatalogModalities(values: string[]) {
-  if (values.length === 0) return "Unknown"
-  return values
-    .map((value) => value.replace(/[-_]/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()))
-    .join(", ")
-}
-
-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 formatUsageDate(value: string | undefined) {
-  if (!value) return ""
-  const date = new Date(value)
-  if (Number.isNaN(date.getTime())) return value
-  return new Intl.DateTimeFormat("en", { month: "short", day: "numeric", timeZone: "UTC" }).format(date).toUpperCase()
-}
-
-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, Math.abs(value) >= 10 ? 1 : 2)}%`
-}
-
-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 trimNumber(value: number, digits: number) {
-  return Number(value.toFixed(digits)).toLocaleString("en")
-}
-
-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
-}
-
-function ChevronDownIcon() {
-  return (
-    <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" fill="none">
-      <path d="M4.75 6.25L8 9.5L11.25 6.25" stroke="currentColor" stroke-width="1.5" />
-    </svg>
-  )
-}
+export { default } from "../../../../../component/model-compare-detail"

+ 6 - 2
packages/stats/app/src/routes/model-catalog.ts

@@ -56,14 +56,18 @@ export type ModelCatalog = {
   labs: ModelCatalogLab[]
 }
 
-export const getModelCatalog = query(async () => {
-  "use server"
+export async function loadModelCatalog() {
   const [models, pricing, labs] = await Promise.all([
     fetchCatalogPayload(modelCatalogSourceUrl),
     fetchCatalogPayload(modelCatalogPricingUrl),
     fetchLabCatalogPayload(modelCatalogLabSourceUrl),
   ])
   return buildModelCatalog(models, pricing, labs)
+}
+
+export const getModelCatalog = query(async () => {
+  "use server"
+  return loadModelCatalog()
 }, "getModelCatalog")
 
 export function findModelCatalogEntry(catalog: ModelCatalog, model: string, lab?: string) {

+ 113 - 0
packages/stats/app/src/routes/sitemap.xml.ts

@@ -0,0 +1,113 @@
+import { getStatsHomeData } from "@opencode-ai/stats-core/domain/home"
+import { runtime } from "@opencode-ai/stats-core/runtime"
+import {
+  canonicalFamilyComparisonPath,
+  canonicalModelComparisonPath,
+  comparisonFamilies,
+  comparisonSitemapModels,
+  latestFamilyComparisonPath,
+  resolveComparisonFamily,
+} from "../lib/comparison-pages"
+import { baseUrl } from "../lib/language"
+import { loadModelCatalog } from "./model-catalog"
+
+type SitemapEntry = {
+  path: string
+  lastmod?: string
+}
+
+export async function GET() {
+  const [catalog, stats] = await Promise.all([
+    loadModelCatalog(),
+    runtime.runPromise(getStatsHomeData()).catch(() => undefined),
+  ])
+  const lastmod = sitemapDate(
+    stats?.updatedAt,
+    ...catalog.models.map((model) => model.lastUpdated ?? model.releaseDate),
+  )
+  const families = comparisonFamilies.flatMap((family) => {
+    const resolved = resolveComparisonFamily(catalog, family.slug)
+    return resolved ? [resolved] : []
+  })
+  const familyComparisons = families.flatMap((first, index) =>
+    families.slice(index + 1).map((second) => ({
+      path: canonicalFamilyComparisonPath(first, second),
+      lastmod: sitemapDate(
+        stats?.updatedAt,
+        first.model.lastUpdated ?? first.model.releaseDate,
+        second.model.lastUpdated ?? second.model.releaseDate,
+      ),
+    })),
+  )
+  const models = comparisonSitemapModels(catalog, stats?.leaderboard["All Users"]["2M"])
+  const modelComparisons = models.flatMap((first, index) =>
+    models.slice(index + 1).flatMap((second) => {
+      if (latestFamilyComparisonPath(catalog, first, second)) return []
+      return [
+        {
+          path: canonicalModelComparisonPath(first, second),
+          lastmod: sitemapDate(
+            stats?.updatedAt,
+            first.lastUpdated ?? first.releaseDate,
+            second.lastUpdated ?? second.releaseDate,
+          ),
+        },
+      ]
+    }),
+  )
+  const entries = uniqueSitemapEntries([{ path: "/data/compare", lastmod }, ...familyComparisons, ...modelComparisons])
+
+  return new Response(sitemapXml(entries), {
+    headers: {
+      "Cache-Control": "public, max-age=300, s-maxage=3600, stale-while-revalidate=86400",
+      "Content-Type": "application/xml; charset=utf-8",
+    },
+  })
+}
+
+function uniqueSitemapEntries(entries: SitemapEntry[]) {
+  return Object.values(
+    entries.reduce<Record<string, SitemapEntry>>((result, entry) => {
+      result[entry.path] = entry
+      return result
+    }, {}),
+  ).toSorted((a, b) => a.path.localeCompare(b.path))
+}
+
+function sitemapXml(entries: SitemapEntry[]) {
+  const urls = entries
+    .map(
+      (entry) => `  <url>
+    <loc>${escapeXml(new URL(entry.path, baseUrl).toString())}</loc>${
+      entry.lastmod
+        ? `
+    <lastmod>${entry.lastmod}</lastmod>`
+        : ""
+    }
+  </url>`,
+    )
+    .join("\n")
+  return `<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+${urls}
+</urlset>`
+}
+
+function sitemapDate(...values: (string | undefined | null)[]) {
+  const dates = values.flatMap((value) => {
+    if (!value) return []
+    const date = new Date(value)
+    return Number.isNaN(date.getTime()) ? [] : [date]
+  })
+  if (dates.length === 0) return undefined
+  return new Date(Math.min(Date.now(), Math.max(...dates.map((date) => date.getTime())))).toISOString().slice(0, 10)
+}
+
+function escapeXml(value: string) {
+  return value
+    .replaceAll("&", "&amp;")
+    .replaceAll('"', "&quot;")
+    .replaceAll("'", "&apos;")
+    .replaceAll("<", "&lt;")
+    .replaceAll(">", "&gt;")
+}